{"context":{"domain":"bigaton-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":"69b15e6093419d8151c50e4c","name":"Lucas Bigaton","domain":"bigaton-playground","visibility":"public","settings":{"defaultAccessPolicy":"private"},"updatedAt":"2026-08-14T19:26:29.927Z","createdAt":"2026-03-11T12: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":"6a8387f1e1c15d8b3027c5da","component":"data2-core-types@Component","system":"69b15e6093419d8151c50e4c","params":{"extends":"data2-core-primitives@Block","component":"mcbaseviewer","package":"bigaton-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":"// `acc` do dump e um saco unico; no JOGO sao TRES slots independentes, distinguidos pelo islot\n// (Af=face acc, Ay=eye acc, Ae=earring) -- que e o mesmo criterio canonico usado no resto do projeto.\n// Separar nao e so organizacao da grade: com um slot so, oculos e brinco nunca poderiam coexistir.\nconst CATS=['hair','face','cape','cap','weapon','outfit','coat','pants','shoes','glove','offhand','accface','acceye','accear'];\n// `offhand` NAO e \"shield\": pelo BLUEPRINT 7.1 o islot `Si` e o slot de off-hand, e ele aceita\n// shield OU katara. Com a katara na mao principal (como estava) nao dava para montar um Dual\n// Blade -- adaga na principal + katara na off-hand -- e dava para usar katara e shield juntos.\n// `coat` e o nome do arquivo dos devs do Maple; na comunidade (e na nossa interface) e `top`.\nconst ROTULO={accface:'face acc',acceye:'eye acc',accear:'earring',offhand:'off-hand',coat:'top'};\n// A coluna de equipados tem ordem FIXA, nao a ordem em que as pecas foram equipadas.\nconst ORDEM_EQ=['hair','face','cap','accear','accface','acceye','outfit','coat','pants','glove','weapon','offhand','cape','shoes'];\n// Paleta de cor do CABELO. Medido no catalogo inteiro: hair e a UNICA categoria com variante de\n// cor (2.176 estilos); as outras 13 tem exatamente 1 cor por estilo. E a cor NAO e a posicao na\n// lista, e o ULTIMO DIGITO do id -- o estilo 00037361 comeca no digito 1 (vermelho), nao no 0.\n// Um unico estilo (00030920) tem digitos 8 e 9, que sao copias do 00030930/31; ate serem tirados\n// do indice eles caem no quadradinho neutro com o numero, em vez de mentir uma cor.\nconst PALETA=['#191919','#c0392b','#e07b1f','#f0c419','#2f9e44','#2b6cb0','#8e44ad','#7a5230'];\nconst NOMECOR=['black','red','orange','yellow','green','blue','purple','brown'];\n// Grupos do painel de Action. MANUAL de proposito por enquanto (decisao do usuario): se um dia\n// virar coluna da tabela `mc-action`, some daqui e passa a sair do dado.\nconst MOVENDO={walk1:1,walk2:1,jump:1,fly:1,ladder:1,rope:1};\nconst GRUPOS=['Stand','Moving','Swing','Stab','Shoot'];\nfunction grupoUI(a){\n  if(a.indexOf('swing')===0)return 'Swing';\n  if(a.indexOf('stab')===0)return 'Stab';   // `proneStab` NAO cai aqui: comeca com 'prone'\n  if(a.indexOf('shoot')===0)return 'Shoot';\n  return MOVENDO[a]?'Moving':'Stand';\n}\nconst CATDE=Object.create(null);   // id do item -> categoria real; e o que faz a aba All equipar certo\n// ESCONDIDOS DA NAVEGACAO -- nao da base. Eluna Earrings: 8 variantes x 5 niveis = 40 ids\n// (01032270..01032309), e os 5 niveis de cada variante sao a MESMA arte -- medido: `geo`, `uv` e\n// `px` identicos, e os 5 PNGs de atlas com o MESMO sha1, byte a byte. Ficam so os Lv.1 (ultimo\n// digito 0 ou 5), tirando 32 linhas que o randomize sorteava com peso desproporcional.\n// So a LISTA navegavel encolhe: `META` e `CATDE` continuam populados para os 40, entao um desses\n// ids ainda renderiza normalmente se vier de fora (histórico de undo, estado salvo, link).\n// PORTRAIT por AI. O prompt fica aqui em cima de proposito: a fase 2 e calibrar ESTE texto, e\n// procurar por ele no meio do render seria pior. O `draw` devolve {data:[{url}]} com url =\n// data URI de ~3,2 MB; quem converte para arquivo do Drive e devolve URL curta e o HANDLER.\nconst RUSH_PORTRAIT='mcportrait';\nconst STORE_PORTRAIT='mcportraitimg';\n// Ordem B (escolhida 2026-08-28 no teste de 3 montagens): SPRITE primeiro, referencia de estilo\n// depois. A ref de estilo e APENDADA no handler, server-side, para ser trocavel sem republicar\n// isto; o componente manda so o sprite, no campo `imagem`.\n// Nao vai nome de item no texto: nome enviesa (a arma \"Chaos Devil's Sunrise\" saiu com o nome\n// escrito na lamina). O sprite carrega a forma e a cor; o texto so descreve estilo e enquadramento.\nconst PROMPT_BASE='Redraw a character as a high-resolution illustration.\\n\\nSTYLE -- this is the target rendering, taken from the SECOND image: soft chibi anime portrait, thin dark outline, smooth gradient shading, glossy hair with a bright highlight band across the crown, very large glossy eyes with a dark upper lash line and a white catchlight, no drawn nose, soft cheek blush, clean smooth skin. The output must look hand-drawn and fully smooth, at the same finish as the second image.\\n\\nThe FIRST image is a tiny low-resolution game sprite. It is DATA, not artwork: it only tells you the hairstyle and its colour, the eye and eyebrow design, the headwear if any, and the collar and garment colours. Do not enlarge it, do not trace it, and do not carry over its blocky edges.\\n\\nDraw that character fresh, in the style described above. Invent nothing that is not visible in the sprite, and always give the character clearly drawn open eyes.\\n\\nFraming: square, tight bust. Centre the head horizontally and let it fill most of the frame width. The chin must sit at four fifths of the frame height and the top of the hair must reach the top edge. Crop at the base of the neck: show the head and only a sliver of the shoulders. Do not draw arms, hands, chest or torso, and do not zoom out. No text, no letters, no watermark, no border.';\nconst SEM_PREVIEW=Object.create(null);\nfor(let n=2270;n<=2309;n++)if(n%5!==0)SEM_PREVIEW['0103'+n]=1;\n// Cadeado: os dois SVGs de UIAssets, inline. Sao ~300 bytes cada, entao virar arquivo custaria\n// uma requisicao por thumb para economizar nada. Sem `fill` no path: quem manda na cor e o CSS,\n// via `fill:currentColor` -- por isso o mesmo icone serve verde no palco e escuro no cantinho.\nconst DLOCK='M12,17A2,2 0 0,0 14,15C14,13.89 13.1,13 12,13A2,2 0 0,0 10,15A2,2 0 0,0 12,17M18,8A2,2 0 0,1 20,10V20A2,2 0 0,1 18,22H6A2,2 0 0,1 4,20V10C4,8.89 4.9,8 6,8H7V6A5,5 0 0,1 12,1A5,5 0 0,1 17,6V8H18M12,3A3,3 0 0,0 9,6V8H15V6A3,3 0 0,0 12,3Z';\nconst DLOCKAB='M18,8A2,2 0 0,1 20,10V20A2,2 0 0,1 18,22H6C4.89,22 4,21.1 4,20V10A2,2 0 0,1 6,8H15V6A3,3 0 0,0 12,3A3,3 0 0,0 9,6H7A5,5 0 0,1 12,1A5,5 0 0,1 17,6V8H18M12,17A2,2 0 0,0 14,15A2,2 0 0,0 12,13A2,2 0 0,0 10,15A2,2 0 0,0 12,17Z';\nfunction cadeado(aberto){return `<svg class=\"lk\" viewBox=\"0 0 24 24\" aria-hidden=\"true\"><path d=\"${aberto?DLOCKAB:DLOCK}\"/></svg>`;}\n// Olho cortado = item OCULTO (equipado, mas nao desenhado). Icone de traco, nao de preenchimento:\n// stroke/fill vao no CSS. Tirei o `class=\"lucide ...\"` do original de proposito -- classe de\n// biblioteca de terceiros aqui so viraria nome morto que ninguem sabe se pode remover depois.\nconst DOLHO='<path d=\"M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49\"/><path d=\"M14.084 14.158a3 3 0 0 1-4.242-4.242\"/><path d=\"M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143\"/><path d=\"m2 2 20 20\"/>';\nfunction olho(){return `<svg class=\"olho\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">${DOLHO}</svg>`;}\n// TIMING E SEQUENCIA saem da tabela `mc-action` (2026-08-27). Antes eram quatro constantes e uma\n// funcao aqui dentro: PASSO=208 fixo, dois fatores ajustados a mao e a lista VAIVEM. O WZ tem\n// `delay` em TODO frame do corpo e o compilador descartava -- a tabela devolveu esse dado. Nada\n// aqui e estimado: `frameDelays` e o delay do jogo, `playback`/`loopStart` descrevem a sequencia.\nconst ACOES='https://bigaton-playground.data2apis.com/dataset/mc-actionDataSet';\nconst ACT=Object.create(null);   // key da action -> linha da tabela\nlet ACTORD=[];                   // keys na ordem da tabela (o preview mostra nessa ordem)\nconst PASSO_PADRAO=208;          // fallback so para uma action que a tabela nao conheca\n// O eixo do mundo (x=0,y=0 do personagem) sai do TAMANHO do canvas, nao de constante fixa:\n// horizontal no meio, vertical em 72% da altura -- a origem cai perto dos pes e o corpo cresce\n// para cima (y negativo), entao 72% e o que deixa o personagem opticamente centrado. Mesma\n// proporcao do stage do engine local.\n// 42 = 7 linhas cheias de 6. Com 40 a ultima linha ficava com 4 e a grade parecia quebrada.\nconst EIXO_Y=0.72, POR_PAGINA=42;\nconst MEDIA='https://bigaton-playground.data2.media/file/';\n\nconst INDEXREF=Object.create(null); // cat -> ref do idx JSON (caminho NOVO)\nconst IDX=Object.create(null);      // cat -> {sheets:[url], items:[...]} (fetch cacheado)\nconst BLOCO=Object.create(null);    // blockKey/geoRef -> bloco parseado\nconst ITENS=Object.create(null);    // id -> {uv,geo,atlasRef, uvEf?,geoEf?,atlasEfRef?}\nconst IMG=Object.create(null);      // ref -> Image\nconst IMGBASE=Object.create(null);   // caminho do sprite da base -> Image (o caminho ja carrega o id da skin, entao nao colide)\nconst SKINPIX=Object.create(null);   // skinId -> {geo,pix} do arquivo do Drive, buscado sob demanda\nconst META=Object.create(null);      // id -> linha do idx (vs do cap, efRef da arma)\nlet ZI2SMAP=null;                    // zi -> codigo de smap (vem no idxcap.json)\nlet REGRAS=null;                     // conflitos de slot (mcregras.json)\nlet EFI=null;                        // id -> {a:atlasRef, b:blocoRef, x:[uv do ItemEff no atlas BASE]}\nconst BLOCOEI=Object.create(null);   // blocoRef -> bloco de ItemEff parseado\nlet TEMPO=0;                         // relogio do ItemEff em ms; so anda com o Play ligado\n// O ItemEff tem EIXO PROPRIO: ciclo de animacao com `delay` por frame, SEM relacao com o numero\n// de frames da acao. Um efeito de 16 frames num `stand1` de 3 rodava so 3 se fosse amarrado ao\n// corpo. O `e` traz o efeito inteiro e o canto de cada frame sai de `ancora - origin`.\n// EF_VEL e ajuste de APRESENTACAO (fator na leitura do relogio, nao no dado) -- mesmo do local.\nconst EF_VEL=1.15;\nconst WPSI=Object.create(null);      // arma que ocupa TAMBEM a off-hand\n\nfunction esc(s){return String(s==null?'':s).replace(/[<>&\"]/g,c=>({'<':'&lt;','>':'&gt;','&':'&amp;','\"':'&quot;'}[c]));}\n// O endpoint do dataset NAO devolve ordenado -- ordenar por `order` aqui e o que preserva a\n// ordem do preview. A tabela e opcional: sem ela tudo cai no fallback e o preview segue de pe.\nasync function carregaAcoes(){\n  const d=await (await fetch(ACOES)).json();\n  const rows=((d&&d.data)||[]).slice().sort((a,b)=>(a.order|0)-(b.order|0));\n  ACTORD=rows.map(r=>r.key);\n  for(const r of rows)ACT[r.key]=r;\n  return rows.length;\n}\n// A skin diz QUAIS actions existem (autoridade de render); a tabela diz em que ordem mostrar e\n// com que nome. Action fora da tabela vai para o fim, nunca some.\nfunction ordenaAcoes(ks){\n  const pos=a=>{const i=ACTORD.indexOf(a);return i<0?9999:i;};\n  return ks.slice().sort((a,b)=>pos(a)-pos(b)||a.localeCompare(b));\n}\nfunction rotuloAcao(a){return (ACT[a]&&ACT[a].name)||a;}\n// `loopStart` separa a INTRO (frames que so tocam ao entrar na action) da parte que repete.\n// E o caso do walk: o frame 0 e transicao, o loop e 1,2,3,2 -- antes o frame 0 voltava a cada\n// volta porque a sequencia era montada sempre a partir do zero.\nfunction sequencia(a,n){\n  const r=ACT[a]||null;\n  const modo=r?r.playback:(n>2?'pingpong':'loop');\n  const ls=Math.min(Math.max(r?(r.loopStart|0):0,0),Math.max(n-1,0));\n  const intro=[];for(let i=0;i<ls;i++)intro.push(i);\n  const ciclo=[];for(let i=ls;i<n;i++)ciclo.push(i);\n  if(modo==='pingpong')for(let i=n-2;i>ls;i--)ciclo.push(i);\n  return{intro,ciclo,modo};\n}\n// Duracao de um frame: o delay do WZ dividido pelo multiplicador da tabela (actionSpeed 1 = o\n// tempo original do jogo, 2 = o dobro da velocidade). Mesma forma que o ItemEff ja usava.\nfunction duracao(a,i){\n  const r=ACT[a];\n  const fd=r&&r.frameDelays;\n  const d=(fd&&fd.length)?fd[Math.min(i,fd.length-1)]:PASSO_PADRAO;\n  const v=(r&&Number(r.actionSpeed)>0)?Number(r.actionSpeed):1;\n  return Math.max(16,(Number(d)||PASSO_PADRAO)/v);\n}\n// AQUI O PREVIEW DIVERGE DO mcbuilder DE PROPOSITO. La o `playback:'once'` da tabela e regra de\n// jogo e o golpe toca uma vez so. Aqui isto e ferramenta de INSPECAO: no modo 'loop' tudo repete,\n// inclusive os ataques, porque olhar um golpe girando e o ponto. A tabela continua mandando na\n// SEQUENCIA (intro + pingpong via loopStart) e na DURACAO de cada frame -- o modo so decide se\n// repete ou nao. 'uma' = Play Once: percorre intro+ciclo inteiros e avisa `terminou` para o\n// preview voltar ao frame 0 e parar.\nfunction frameNoTempo(a,n,ms,modo){\n  const {intro,ciclo}=sequencia(a,n);\n  const passos=intro.concat(ciclo);\n  if(!passos.length)return{frame:0,terminou:true};\n  let t=ms;\n  if(modo==='uma'){\n    for(const f of passos){const d=duracao(a,f);if(t<d)return{frame:f,terminou:false};t-=d;}\n    return{frame:passos[0],terminou:true};\n  }\n  for(const f of intro){const d=duracao(a,f);if(t<d)return{frame:f,terminou:false};t-=d;}\n  let total=0;for(const f of ciclo)total+=duracao(a,f);\n  if(total<=0)return{frame:ciclo[0],terminou:false};\n  t=t%total;\n  for(const f of ciclo){const d=duracao(a,f);if(t<d)return{frame:f,terminou:false};t-=d;}\n  return{frame:ciclo[ciclo.length-1],terminou:false};\n}\nfunction keys(g,a){return Object.keys((g||{})[a]||{});}\nfunction imgBase(sk,p){if(IMGBASE[p])return IMGBASE[p];const v=(sk.pix||{})[p];if(!v)return null;const im=new Image();im.src=v;IMGBASE[p]=im;return im;}\n// `crossOrigin` ANTES do src (depois nao vale) -- e o que impede o canvas de ficar CONTAMINADO.\n// Sem isso `toDataURL` lanca SecurityError e nao existe portrait imagem-para-imagem. O Drive\n// manda `Access-Control-Allow-Origin: *` nos TRES saltos (media -> apis -> files), inclusive no\n// 200 final, entao pedir CORS aqui nao quebra o carregamento.\nfunction img(ref){if(!ref)return null;if(IMG[ref])return IMG[ref];const im=new Image();im.crossOrigin='anonymous';im.src=MEDIA+ref;IMG[ref]=im;return im;}\n\nfunction pagina(s){\n  const q=(s.q||'').trim().toLowerCase();\n  const lista=q?(s.cat||[]).filter(r=>(r.busca||'').indexOf(q)>=0):(s.cat||[]);\n  const paginas=Math.max(1,Math.ceil(lista.length/POR_PAGINA));\n  const pag=Math.min(s.pag||0,paginas-1);\n  return{q,lista,paginas,pag,fatia:lista.slice(pag*POR_PAGINA,pag*POR_PAGINA+POR_PAGINA)};\n}\n\n// O item e indexado pela CHAVE DE FRAME DO CORPO (`fr`), nunca por posicao na lista de chaves.\n// Em 36.296 pares (item,acao) o item NAO tem todos os frames -- a peca some naquele frame porque\n// o corpo a esconde (swingOF/swingTF giram de costas: sem face; varias swings escondem a mao).\n// Indexar por posicao com modulo, como estava, desalinhava tudo: no frame 0 desenhava o frame 1,\n// e num frame em que a peca deveria SUMIR desenhava o sprite de outro frame na ancora daquele\n// outro frame -- e por isso a face/glove aparecia \"torta\" nessas acoes.\n// Ausente = nao desenha. E a mesma regra do engine local: `(geo[acao]||{})[frame]||[]`.\n// ItemEff: um grupo por elemento do efeito, cada um com ciclo proprio percorrido pelo relogio.\nfunction camadasEfItem(item,acao,fr,L){\n  const ei=item.ei;if(!ei||!ei.e||!ei.e.p)return;\n  const refs=(ei.e.q||{})[acao], anc=((ei.e.a||{})[acao]||{})[fr];\n  if(!refs||!anc)return;\n  for(let gi=0;gi<refs.length;gi++){\n    const grupo=ei.e.p[refs[gi]], A=anc[gi];\n    if(!grupo||!grupo.length||!A)continue;\n    let ciclo=0;for(const f of grupo)ciclo+=(f[3]||100);\n    let tt=ciclo?(TEMPO*EF_VEL)%ciclo:0,k=0;\n    for(let i=0;i<grupo.length;i++){tt-=(grupo[i][3]||100);if(tt<0){k=i;break;}}\n    const f=grupo[k],uv=ei.uv[f[0]];\n    // f = [png, ox, oy, delay, zi, w, noflip] -- o efeito tem a propria flag de noflip (2 itens)\n    if(uv)L.push({ref:item.eiRef,uv:uv,x:A[0]-f[1],y:A[1]-f[2],zi:f[4],dono:item.itemId,nfw:(f[6]?f[5]:0)});\n  }\n}\nfunction camadasItem(item,acao,fr,efeito,efItem,L,vskin,st49){\n  const dono=item.itemId;\n  // Os sprites do ItemEff foram empacotados DENTRO do atlas base do item (medido: todo item com\n  // bloco `e` tambem tem caminhos `Effect/` no `g`). Desenha-los como camada comum e o que fazia o\n  // efeito ficar sempre ligado e com a animacao errada. `ex` = os indices de uv que sao ItemEff;\n  // eles saem daqui SEMPRE, e o efeito volta pelo eixo proprio quando o toggle esta ligado.\n  const ex=item.eiX;\n  // hairShade por SKIN (2026-08-20): `s` mapeia indice-de-skin -> sprite no atlas do proprio item,\n  // e `s['0']` diz QUAL camada e a trocavel. A troca e por IDENTIDADE com esse indice, nunca por\n  // nome de arquivo -- mesma regra do engine local. Sem `s`, ou sem variante para a skin atual,\n  // o sprite e o de sempre; por isso os 17.255 cabelos sem variante nao pagam nada aqui.\n  const sv=item.s,alvo=sv?sv['0']:-1;\n  // STANCE 49 -- a pose em que a arma e segurada na OUTRA mao; as armas de cash usam para cobrir\n  // gun. `o` guarda so o diff POR ACAO, e acao ausente em `o` significa \"identica a `g`\": cair em\n  // `g` e o comportamento CERTO, nao uma falha. So arma declara `o` (1.829 nos blocos publicados).\n  // A troca e por ACAO INTEIRA, nunca merge frame a frame: medido, 46 pares (item,acao) existem em\n  // `o` e NAO em `g` -- com a stance ligada a arma desenha onde normalmente nao desenharia, e um\n  // merge por frame perderia exatamente esses.\n  const go=(st49&&item.geo.o&&item.geo.o[acao])?item.geo.o[acao]:(item.geo.g[acao]||{});\n  for(const c of (go[fr]||[])){\n    if(ex&&ex[c[0]])continue;\n    let si=c[0];\n    if(sv&&si===alvo&&sv[vskin]!=null)si=sv[vskin];\n    // 5o elemento = largura do sprite, presente SO em item `noflip` (medido: 1 item na base\n    // inteira). Cuidado: nas camadas da BASE o 5o elemento e a chave da parte, nao largura --\n    // por isso `nfw` so e lido aqui, nunca no laco da skin.\n    const uv=item.uv[si];if(uv)L.push({ref:item.atlasRef,uv:uv,x:c[1],y:c[2],zi:c[3],dono:dono,nfw:(c.length>4?c[4]:0)});}\n  if(efItem)camadasEfItem(item,acao,fr,L);\n  if(efeito&&item.geoEf&&item.atlasEfRef){\n    for(const c of ((item.geoEf.g[acao]||{})[fr]||[])){const uv=item.uvEf[c[0]];if(uv)L.push({ref:item.atlasEfRef,uv:uv,x:c[1],y:c[2],zi:c[3],dono:dono});}}\n}\nfunction compor(sk,equip,acao,frame,efeito,efItem,st49){\n  const gk=keys(sk.geo,acao);if(!gk.length)return{L:[],fr:'0',n:0};\n  const fr=gk[((frame%gk.length)+gk.length)%gk.length];\n  const esconder={};\n  for(const slot in equip){const it=ITENS[equip[slot]];if(!it)continue;const h=((it.geo.h||{})[acao]||{})[fr]||[];for(const k of h)esconder[k]=1;}\n  let L=[];\n  for(const c of (sk.geo[acao][fr]||[])){if(esconder[c[4]])continue;L.push({base:c[0],x:c[1],y:c[2],zi:c[3],dono:'base'});}\n  const vskin=String(parseInt(sk.skinId,10)-2000);\n  for(const slot in equip){const it=ITENS[equip[slot]];if(it)camadasItem(it,acao,fr,efeito,efItem,L,vskin,st49);}\n  // VSLOT -- a MESMA regra do engine local (preview.html): o chapeu declara os codigos de smap\n  // que ele OCUPA; toda camada de OUTRO item cujo zi mapeia para um desses codigos some. O proprio\n  // cap e a base sao isentos (a base ja veio resolvida pelo `h`). E so por isso que o cabelo troca\n  // de silhueta debaixo do chapeu -- nao existe sprite alternativo, existe camada escondida.\n  let ocultas=0;\n  const capId=equip.cap;\n  if(capId&&ZI2SMAP){\n    const v=(META[capId]&&META[capId].vs)||'';\n    if(v){\n      const occ=Object.create(null);\n      for(let i=0;i<v.length;i+=2)occ[v.slice(i,i+2)]=1;\n      const antes=L.length;\n      L=L.filter(c=>{\n        if(c.dono===capId||c.dono==='base')return true;\n        const cod=(c.zi>=0&&c.zi<ZI2SMAP.length)?ZI2SMAP[c.zi]:'';\n        return !cod||!occ[cod];\n      });\n      ocultas=antes-L.length;\n    }\n  }\n  L.sort((a,b)=>b.zi-a.zi);\n  return{L,fr,n:gk.length,ocultas};\n}\n\nexport default {\n  css:`\n  .wrap{--bg:#101014;--bg2:#15151b;--pan:#191920;--lin:#282833;--txt:#e8e8ee;--dim:#83838f;\n        --ac:#6c8cff;--ac2:#a86cff;--warn:#e3b341;--neon:#3dff9e;\n        height:100%;display:grid;grid-template-columns:1fr 372px;background:var(--bg);\n        color:var(--txt);font:13px/1.45 ui-sans-serif,system-ui,-apple-system,sans-serif;overflow:hidden}\n  .esq{display:grid;grid-template-rows:auto 1fr auto;overflow:hidden;min-width:0}\n\n  /* cabecalho ------------------------------------------------------------- */\n  .top{display:flex;align-items:center;gap:10px;padding:11px 16px;border-bottom:1px solid var(--lin);\n       background:linear-gradient(180deg,#1b1b23,#15151b)}\n  .mark{width:22px;height:22px;border-radius:6px;flex:none;\n        background:linear-gradient(135deg,var(--ac),var(--ac2));\n        box-shadow:0 0 0 1px #ffffff1a inset,0 2px 10px -2px var(--ac)}\n  .tit{font-weight:650;letter-spacing:.2px}\n  .tit small{display:block;font-weight:400;font-size:10px;color:var(--dim);letter-spacing:.5px;text-transform:uppercase}\n  .top .badge{margin-left:auto}\n\n  /* palco ----------------------------------------------------------------- */\n  .stage{position:relative;overflow:hidden;\n    background:\n      radial-gradient(120% 90% at 50% 34%,#2a2a36 0%,#17171e 55%,#0d0d11 100%),\n      repeating-conic-gradient(#ffffff05 0 25%,transparent 0 50%) 0 0/22px 22px}\n  .stage:after{content:\"\";position:absolute;inset:0;pointer-events:none;\n    box-shadow:inset 0 0 90px 12px #00000073}\n  /* O data-d2-static mora no WRAPPER, nao no canvas. Ele impede o morph de mexer nos FILHOS do\n     elemento marcado -- posto no proprio canvas nao protegia nada (canvas nao tem filhos) e o\n     morph reaplicava o elemento a cada re-render, resetando o backing store para o default\n     300x150 do browser. Era essa a piscada. Contrato em code-component-lifecycle-traps secao 10.\n     ATENCAO: nada de crase neste bloco -- ele inteiro e um template literal. */\n  .cvwrap{position:absolute;inset:0}\n  canvas.big{display:block;width:100%;height:100%;image-rendering:pixelated}\n\n  /* barra ----------------------------------------------------------------- */\n  /* equipados: dentro do palco, coluna a direita -- o painel lateral ficava comprido demais */\n  .eqbar{position:absolute;right:10px;top:10px;display:flex;flex-direction:column;gap:7px;z-index:2}\n  .eqi{position:relative;width:44px;height:44px;border-radius:9px;overflow:hidden;\n    background:linear-gradient(180deg,#fbfbfd,#e7e7ec);border:1px solid #ffffff26;\n    display:flex;align-items:center;justify-content:center;box-shadow:0 3px 10px -2px #000a}\n  .eqi .thc{transform:scale(.8);transform-origin:center}\n  .eqi .x{position:absolute;top:0;right:0;width:15px;height:15px;line-height:14px;text-align:center;\n    font:700 9px ui-sans-serif,system-ui;color:#fff;background:#e04a4a;border-bottom-left-radius:7px;\n    cursor:pointer;opacity:0;transition:opacity .1s}\n  .eqi:hover .x{opacity:1}\n  .eqi .semic{width:40px;height:40px;border:0}\n  .barra{border-top:1px solid var(--lin);padding:10px 16px;background:var(--pan);\n         display:flex;gap:7px;align-items:center;flex-wrap:wrap}\n  /* a regra generica de input da padding/borda/fundo -- o checkbox precisa desfazer isso */\n  .barra .chk{display:flex;align-items:center;gap:6px;cursor:pointer;user-select:none;white-space:nowrap;\n    font:600 12px ui-sans-serif,system-ui,sans-serif;color:var(--dim);transition:color .12s}\n  .barra .chk:hover{color:var(--txt)}\n  .barra .chk input{width:auto;padding:0;border:0;background:none;margin:0;accent-color:var(--ac);cursor:pointer}\n  /* play troca de glifo e randomize troca de rotulo: sem largura minima, a barra inteira desliza */\n  .barra .play{min-width:82px}\n  .barra .play1{min-width:78px}\n  .barra .rnd{min-width:122px}\n  .barra .flip{min-width:52px}\n  .barra .port{min-width:96px}\n  /* O src da imagem e 3,2 MB de data-URI. Ele NUNCA entra na string do render: o <img> mora num\n     data-d2-static e recebe o src imperativamente, senao o morph diffaria 3 MB a cada interacao. */\n  .portwrap{position:absolute;inset:0;z-index:6;display:none;align-items:center;justify-content:center;\n    background:#05060ae8}\n  .portwrap.on{display:flex}\n  /* O <img> e filho FLEX: sem align-items/justify-content o stretch padrao deforma a imagem\n     para preencher a caixa, e o retrato quadrado saia esticado. max-width/height sozinhos nao\n     seguram porque o stretch age no eixo cruzado. width/height auto + object-fit fecham. */\n  .portbox{max-width:76%;max-height:76%;display:flex;align-items:center;justify-content:center}\n  .portimg{max-width:100%;max-height:100%;width:auto;height:auto;object-fit:contain;\n    border-radius:12px;box-shadow:0 18px 50px -12px #000}\n  .portx{position:absolute;top:12px;right:12px}\n  .sep{width:1px;height:20px;background:var(--lin);margin:0 3px}\n\n  /* scrollbar-gutter reserva a barra de rolagem SEMPRE. Sem isso, abrir uma categoria que faz o\n     painel crescer fazia a barra aparecer, a largura util encolher ~9px e a fileira de slots\n     re-quebrar -- era por isso que o off-hand pulava de linha ao selecionar hair. */\n  .side{border-left:1px solid var(--lin);padding:16px;overflow:auto;background:var(--bg2);min-width:0;\n    scrollbar-gutter:stable}\n  .side::-webkit-scrollbar{width:9px}\n  .side::-webkit-scrollbar-thumb{background:#33333f;border-radius:9px;border:2px solid var(--bg2)}\n  .side::-webkit-scrollbar-thumb:hover{background:#41414f}\n\n  h3{margin:0 0 9px;font-size:10px;text-transform:uppercase;letter-spacing:.9px;color:var(--dim);\n     display:flex;align-items:center;gap:8px}\n  h3:before{content:\"\";width:3px;height:11px;border-radius:2px;background:var(--ac);opacity:.85;flex:none}\n  h3 em{font-style:normal;color:#5e5e6b;letter-spacing:.4px}\n  .grp{margin-bottom:17px}\n\n  select,button,input{font:600 12px ui-sans-serif,system-ui,sans-serif;padding:6px 10px;border-radius:7px;\n    border:1px solid #34343f;background:linear-gradient(180deg,#2a2a34,#232430);color:var(--txt);\n    transition:background .12s,border-color .12s,box-shadow .12s}\n  button{cursor:pointer}\n  button:hover{border-color:#4a4a5a;background:linear-gradient(180deg,#33333f,#2a2a36)}\n  button:active{transform:translateY(1px)}\n  button:disabled{opacity:.38;cursor:default;transform:none}\n  select,input{width:100%;box-sizing:border-box}input{font-weight:400}\n  input:focus,select:focus{outline:none;border-color:var(--ac);box-shadow:0 0 0 3px #6c8cff2e}\n  button.on{background:linear-gradient(180deg,#6c8cff,#5474ea);border-color:#6c8cff;color:#fff;\n            box-shadow:0 2px 12px -4px var(--ac)}\n  button.on:hover{background:linear-gradient(180deg,#7b98ff,#5f7ef0)}\n  .rnd{background:linear-gradient(180deg,#3a2f5e,#2b2545);border-color:#4b3f79;color:#d9d2ff}\n  .rnd:hover{background:linear-gradient(180deg,#463878,#332b55);border-color:#5d4e96}\n\n  .acts{display:flex;flex-wrap:wrap;gap:4px}.acts button{padding:3px 7px;font-size:10px;border-radius:5px}\n  .catbar{display:flex;gap:5px;flex-wrap:wrap}.catbar button{padding:4px 10px;font-size:11px;text-transform:capitalize}\n  /* altura reservada para as 7 linhas: a ultima pagina (ou uma busca curta) nao sobe a paginacao */\n  .grade{display:grid;grid-template-columns:repeat(6,1fr);gap:6px;\n    grid-auto-rows:52px;min-height:400px;align-content:start}\n  .cel{background:linear-gradient(180deg,#fafafa,#e6e6ea);border:2px solid transparent;border-radius:8px;\n    height:52px;display:flex;align-items:center;justify-content:center;cursor:pointer;overflow:hidden;\n    padding:0;position:relative;box-shadow:0 1px 3px #0006;transition:transform .1s,box-shadow .1s,border-color .1s}\n  .cel:hover{border-color:#8aa4ff;transform:translateY(-1px);box-shadow:0 4px 12px -3px #000a}\n  .cel.on{border-color:var(--ac);box-shadow:0 0 0 3px #6c8cff40,0 4px 12px -3px #000a}\n  .cel img{max-width:46px;max-height:46px;image-rendering:pixelated}\n  .cel .thc{image-rendering:pixelated}\n  .badgec{position:absolute;bottom:2px;right:3px;font:9px ui-monospace,monospace;color:#2a2a33;\n    background:#ffffffd9;border-radius:4px;padding:0 3px}\n  .semic{display:inline-flex;align-items:center;justify-content:center;width:46px;height:46px;\n    border:1px dashed #b7b7c2;border-radius:5px;color:#7a7a86;font:10px ui-monospace,monospace}\n  .pag{display:flex;gap:7px;align-items:center;margin-top:9px}.pag button{padding:4px 10px}\n  /* o contador cresce de 1 para 966 e empurrava a seta da direita */\n  .pag .badge{min-width:74px;text-align:center}\n  .cores{display:flex;gap:5px;flex-wrap:wrap;margin-top:8px}\n  .cores button{width:27px;height:27px;padding:0;border-radius:7px;font-size:11px}\n  .equip{display:flex;flex-direction:column;gap:4px}\n  .equip .lin{display:flex;justify-content:space-between;align-items:center;gap:8px;\n    background:#22222c;border:1px solid #2e2e3a;border-radius:7px;padding:5px 9px}\n  .equip .lin b{font-weight:650;color:var(--ac);text-transform:capitalize;font-size:11px}\n  .equip .lin code{font:11px ui-monospace,monospace;color:var(--dim)}\n  .equip .x{cursor:pointer;color:#ff7b7b;font-weight:700;line-height:1;padding:0 2px}\n  .equip .x:hover{color:#ff5252}\n  .badge{font:11px ui-monospace,monospace;color:var(--dim)}\n  .boot{padding:26px;color:var(--dim)}.carregando{color:var(--warn)}\n\n  /* HUD do palco: frames, action e a coluna de equipados, ancorados no topo direito ------- */\n  .hud{position:absolute;right:10px;top:10px;display:flex;align-items:flex-start;gap:8px;z-index:3}\n  /* undo/redo: so o glifo. Precisa zerar fundo e borda tambem no :hover, senao a regra generica\n     de button la de cima devolve o gradiente e o contorno. */\n  .hist{position:absolute;right:12px;bottom:10px;display:flex;gap:1px;z-index:3}\n  .hist button{background:none;border:0;padding:1px 7px;color:var(--dim);font-size:18px;line-height:1;\n    opacity:.5;transition:opacity .12s,color .12s}\n  .hist button:hover:not(:disabled){background:none;border:0;opacity:1;color:var(--txt)}\n  .hist button:disabled{opacity:.14}\n  .hudbox{background:#12121aee;border:1px solid #2e2e3a;border-radius:10px;padding:7px 9px;\n    box-shadow:0 6px 20px -8px #000}\n  .hudbox h4{margin:0 0 6px;font:700 9px ui-sans-serif,system-ui;text-transform:uppercase;\n    letter-spacing:.8px;color:var(--dim)}\n  /* Larguras FIXAS no HUD. Antes cada caixa se ajustava ao conteudo: mudar de action trocava o\n     rotulo do botao, mudar de frame trocava a quantidade de botoes, e tudo deslizava. Agora cada\n     uma reserva a pior hipotese (5 frames, o rotulo mais longo) e nada mais anda. */\n  /* botao de frame com largura FIXA: 5 x 20 + 4 x 4 = 116 = o pior caso (shoot2, 5 frames).\n     O min-width vale para o CONTENT box (a caixa nao e border-box), entao e 116 e nao 136 --\n     somar o padding aqui era o que estava deixando a folga a direita do ultimo botao. */\n  .frbox{min-width:116px}\n  .frbox .acts{flex-wrap:nowrap}\n  .frbox .acts button{width:20px;padding:3px 0;text-align:center}\n  .acwrap{position:relative}\n  /* a seta fica PRESA na direita: label e seta sao dois filhos com space-between, em vez de a\n     seta ser texto colado no fim do rotulo e passear conforme o nome muda de tamanho */\n  .acbtn{font-size:11px;padding:4px 9px;width:106px;display:flex;align-items:center;\n    justify-content:space-between;gap:6px}\n  .acbtn .acnome{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n  .acbtn .acseta{flex:none;opacity:.55;font-size:9px}\n  .acpop{position:absolute;right:0;top:calc(100% + 6px);width:296px;max-height:330px;overflow:auto;\n    background:#12121af7;border:1px solid #2e2e3a;border-radius:10px;padding:10px 11px;\n    box-shadow:0 12px 30px -8px #000;z-index:5}\n  .acpop .gr{margin-bottom:10px}\n  .acpop .gr:last-child{margin-bottom:0}\n  .acpop h4{margin:0 0 5px}\n  /* a eqbar deixou de se posicionar sozinha: agora e um item do flex do HUD */\n  /* a coluna reserva a largura mesmo vazia, senao equipar a primeira peca empurrava o HUD inteiro */\n  .eqbar{position:static;display:flex;flex-direction:column;gap:7px;width:44px;flex:none}\n  .mestre.vazio{visibility:hidden;pointer-events:none}\n  .eqi .eqc{position:absolute;inset:0;background:transparent;border:0;padding:0;cursor:pointer;z-index:1}\n  /* A thumb tem fundo CLARO, entao icone branco em cima dela some. Quem cria o contraste e este\n     veu escuro: fraco no hover (previa) e forte quando o item esta oculto (estado). Deixei a\n     peca em grayscale em vez de quase transparente -- com o veu por cima da para continuar\n     reconhecendo QUAL item esta escondido, que era o que se perdia. */\n  .eqi:after{content:\"\";position:absolute;inset:0;z-index:1;pointer-events:none;\n    background:#05060a;opacity:0;transition:opacity .12s}\n  .eqi:hover:after{opacity:.5}\n  .eqi.off:after{opacity:.68}\n  .eqi.off:hover:after{opacity:.74}\n  .eqi.off .thc,.eqi.off .semic{filter:grayscale(1) brightness(.92)}\n  .eqbar.trmodo .eqi:hover:after{opacity:0}\n  /* O olho cortado aparece FANTASMA no hover (previa do que o clique faz) e SOLIDO quando o item\n     esta oculto (o estado). Mesmo icone nos dois papeis: o que muda e a opacidade, entao a previa\n     ja mostra exatamente onde a marca vai ficar. */\n  /* Centralizado por inset+margin:auto, NAO por left/top:50% com margem negativa. Sob o scale do\n     :active os 50% caem em pixel fracionario e o arredondamento muda de lado a cada quadro -- o\n     icone sambava. Assim o browser reparte a sobra simetricamente. */\n  .eqi .olho{position:absolute;inset:0;margin:auto;width:22px;height:22px;\n    z-index:2;pointer-events:none;opacity:0;transition:opacity .12s;\n    fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;\n    color:#f4f6f9;filter:drop-shadow(0 0 4px #000000c4)}\n  .eqi:hover .olho{opacity:.72}\n  .eqi.off .olho{opacity:1;filter:drop-shadow(0 0 6px #000000e0)}\n  /* confirmacao do clique: a thumb afunda por um instante. E o unico feedback que nao depende de\n     re-render, entao nao pisca de novo quando qualquer outro estado muda. */\n  /* will-change promove a thumb a uma CAMADA propria: o compositor escala o bitmap ja pronto, em\n     vez de o layout reposicionar cada filho e encaixar cada um num pixel de dispositivo inteiro.\n     Sem isto o icone sambava em zoom quebrado (110%, 125%) e ficava quieto em 100% e 150% --\n     exatamente onde o fator de escala cai em inteiro. Sao 14 thumbs, entao o custo de camada e\n     irrelevante. */\n  .eqi{transition:transform .08s;transform-origin:center;will-change:transform;\n    backface-visibility:hidden}\n  .eqi:active{transform:scale(.93)}\n  /* no modo cadeado o olho sai de cena: ali o clique tranca, nao oculta */\n  .eqbar.trmodo .eqi .olho{display:none}\n  .eqi .x,.eqi .cad,.eqi .trv{z-index:3}\n  /* No modo cadeado o estado e COR + FORMA: destravado = cadeado aberto quase branco; travado =\n     cadeado fechado em verde neon. O halo do aberto e ESCURO, nao branco: brilho branco atras de\n     icone branco nao separa nada -- quem cria a borda contra a thumb clara e a sombra. Nos dois\n     casos drop-shadow (e nao box-shadow), que segue o contorno do icone em vez da caixa. */\n  .eqi .cad{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;\n    background:#000000a6;cursor:pointer;color:#f4f6f9}\n  .eqi .cad .lk{filter:drop-shadow(0 0 4px #000000ad)}\n  .eqi .cad.tr{background:#001b0fc9;color:var(--neon)}\n  .eqi .cad.tr .lk{filter:drop-shadow(0 0 6px #3dff9e70)}\n  .eqi .trv{position:absolute;top:0;left:0;width:16px;height:16px;display:flex;align-items:center;\n    justify-content:center;background:#0b1f16e8;color:var(--neon);border-bottom-right-radius:7px}\n  .mestre{width:28px;height:28px;padding:0;flex:none;display:flex;align-items:center;\n    justify-content:center;color:var(--dim)}\n  /* O mestre NUNCA abre: ele nao representa travado/destravado, e sim \"estou no modo\". */\n  .mestre.on{color:var(--neon);border-color:var(--neon);background:linear-gradient(180deg,#123d2a,#0d2f20);\n    box-shadow:0 0 0 2px #3dff9e2e,0 2px 12px -4px var(--neon)}\n  .mestre.on:hover{border-color:var(--neon);background:linear-gradient(180deg,#17472f,#113626)}\n\n  /* Cadeado: SVG inline, tamanho por contexto e cor por currentColor.\n     ATENCAO: nada de crase neste bloco -- ele inteiro e um template literal. */\n  .lk{display:block;fill:currentColor}\n  .mestre .lk{width:17px;height:17px}\n  .eqi .cad .lk{width:21px;height:21px}\n  .eqi .trv .lk{width:11px;height:11px}\n  .cores button.sw{width:24px;height:24px;border-radius:6px;border:2px solid #34343f;padding:0}\n  .cores button.sw.on{border-color:#fff;box-shadow:0 0 0 2px var(--ac)}\n  `,\n\n  render(d2){\n    const s=d2.state;\n    if(s.erro)return `<div class=\"boot\">failed<br><span class=\"badge\" style=\"color:#ff8080\">${esc(s.erro)}</span></div>`;\n    if(!s.pronto)return `<div class=\"boot\">loading…<br><span class=\"badge\">${esc(s.etapa||'')}</span></div>`;\n    const skr=SKINPIX[(s.skins[s.iSkin]||{}).id]||{};\n    const acoes=ordenaAcoes(Object.keys(skr.geo||{}));\n    const acao=s.acao&&acoes.indexOf(s.acao)>=0?s.acao:acoes[0];\n    // quantos frames o CORPO tem nesta action -- a skin e a autoridade, nao o `frames` da tabela\n    const nfr=keys(skr.geo,acao).length||1;\n    const {lista,paginas,pag,fatia}=pagina(s);\n    // Na aba All nao existe UMA categoria: o seletor de cor passa a valer para o slot do ultimo\n    // item equipado (`corCat`), e o destaque da grade olha TODOS os equipados, nao um so.\n    const corCat=s.catAtual==='all'?(s.corCat||'hair'):s.catAtual;\n    const eqid=s.equip[corCat];\n    const estilo=((IDX[corCat]||{}).items||[]).find(r=>r.cores&&r.cores.some(cc=>cc[0]===eqid));\n    const EQIDS=Object.create(null);for(const k in s.equip)EQIDS[s.equip[k]]=1;\n    // miniatura do equipado: META[variante] aponta para a linha do ESTILO, que e quem tem o `ic`;\n    // as coords sao da folha da categoria daquele slot, por isso o sheet vem de IDX[slot].\n    const eqbar=ORDEM_EQ.filter(sl=>s.equip[sl]).map(sl=>{\n      const id=s.equip[sl],r=META[id],ix=IDX[sl];let th;\n      if(r&&r.ic&&ix){const[sh,x,y,w,h]=r.ic;\n        th=`<span class=\"thc\" style=\"display:inline-block;width:${w}px;height:${h}px;background-image:url(${esc(ix.sheets[sh])});background-position:-${x}px -${y}px\"></span>`;}\n      else th=`<span class=\"semic\">${esc(String(id).slice(-4))}</span>`;\n      const trav=!!s.travado[sl], oc=!!s.oculto[sl];\n      // No modo cadeado o centro da thumb tranca/destranca; fora dele, o centro OCULTA sem\n      // desequipar. Travado fora do modo vira so o cadeadinho no canto oposto ao X.\n      const cad=s.travaModo?`<span class=\"cad ${trav?'tr':''}\" data-slot=\"${esc(sl)}\">${cadeado(!trav)}</span>`:'';\n      const tv=(!s.travaModo&&trav)?`<span class=\"trv\" title=\"kept by randomize\">${cadeado(false)}</span>`:'';\n      const est=oc?' · hidden':(trav?' · locked':'');\n      // O X some no modo cadeado: ele nao e clicavel ali (o `.cad` esta por cima) e aparecer no\n      // hover atras do cadeado so confundia.\n      const bx=s.travaModo?'':`<span class=\"x\" data-slot=\"${esc(sl)}\">✕</span>`;\n      return `<div class=\"eqi ${oc?'off':''}\" title=\"${esc(ROTULO[sl]||sl)} · ${esc((r&&r.name)||'')} · ${esc(id)}${est}\">${th}${olho()}<button class=\"eqc\" data-slot=\"${esc(sl)}\"></button>${tv}${bx}${cad}</div>`;\n    }).join('');\n    const frbox=`<div class=\"hudbox frbox\"><h4>frame <span class=\"frameind\">–</span> / ${nfr-1}</h4>\n      <div class=\"acts frames\">${Array.from({length:nfr},(x,i)=>`<button class=\"fr\" data-f=\"${i}\">${i}</button>`).join('')}</div></div>`;\n    const acgr=GRUPOS.map(g=>{\n      const ks=acoes.filter(a=>grupoUI(a)===g);\n      if(!ks.length)return '';\n      return `<div class=\"gr\"><h4>${g}</h4><div class=\"acts\">${ks.map(a=>`<button class=\"ac ${a===acao?'on':''}\" data-a=\"${esc(a)}\" title=\"${esc(a)}\">${esc(rotuloAcao(a))}</button>`).join('')}</div></div>`;\n    }).join('');\n    const acbox=`<div class=\"hudbox acwrap\"><h4>action</h4>\n      <button class=\"acbtn\"><span class=\"acnome\">${esc(rotuloAcao(acao))}</span><span class=\"acseta\">▾</span></button>\n      ${s.acAberto?`<div class=\"acpop\">${acgr}</div>`:''}</div>`;\n    // O mestre e a coluna sao SEMPRE renderizados -- so ficam invisiveis quando nao ha nada\n    // equipado. Antes eles nao existiam no DOM, entao equipar a primeira peca empurrava o botao\n    // Action e a caixa de frames para a esquerda de uma vez.\n    const hud=`<div class=\"hud\">${frbox}${acbox}`+\n      `<button class=\"mestre ${s.travaModo?'on':''} ${eqbar?'':'vazio'}\" title=\"lock slots against randomize\">${cadeado(false)}</button>`+\n      `<div class=\"eqbar ${s.travaModo?'trmodo':''}\">${eqbar}</div></div>`;\n    return `<div class=\"wrap\">\n      <div class=\"esq\">\n        <div class=\"top\">\n          <span class=\"mark\"></span>\n          <span class=\"tit\">MapleCloset<small>character preview</small></span>\n          <span class=\"badge\">${esc(rotuloAcao(acao))} · ${Object.keys(s.equip).length} equipped</span>\n        </div>\n        <div class=\"stage\"><div class=\"cvwrap\" data-d2-static><canvas class=\"big\"></canvas></div>${hud}<div class=\"hist\">\n          <button class=\"und\" ${s.podeUnd?'':'disabled'} title=\"undo\">‹</button>\n          <button class=\"red\" ${s.podeRed?'':'disabled'} title=\"redo\">›</button>\n        </div>\n        <div class=\"portwrap ${s.verPortrait?'on':''}\">\n          <div class=\"portbox\" data-d2-static><img class=\"portimg\" alt=\"\"/></div>\n          <button class=\"portx\">close</button>\n        </div></div>\n        <div class=\"barra\">\n          <button class=\"play ${s.modo==='loop'?'on':''}\">${s.modo==='loop'?'❚❚':'▶'} loop</button>\n          <button class=\"play1 ${s.modo==='uma'?'on':''}\">▶| once</button>\n          <span class=\"sep\"></span>\n          <button class=\"zm\">−</button><button class=\"zp\">+</button><span class=\"badge\">${s.zoom}×</span>\n          <button class=\"flip ${s.flip?'on':''}\" title=\"espelha o personagem; os sprites do jogo olham para a esquerda por padrao\">flip</button>\n          <span class=\"sep\"></span>\n          <button class=\"ef ${s.efeito?'on':''}\">weapon fx</button>\n          <button class=\"efi ${s.efItem?'on':''}\">item fx</button>\n          <label class=\"chk\" title=\"stance 49: a arma desenhada como se estivesse na outra mao (as cash usam para cobrir gun)\"><input type=\"checkbox\" class=\"st49\" ${s.stance49?'checked':''}/><span>gun stance</span></label>\n          <span class=\"sep\"></span>\n          <button class=\"rnd\">${s.sorteando?'randomizing…':'⚄ randomize'}</button>\n          <button class=\"port\" ${s.gerando?'disabled':''}>${s.gerando?'generating…':'portrait'}</button>\n          ${s.carregando?'<span class=\"badge carregando\">loading…</span>':''}\n          ${s.diag?`<span class=\"badge\" style=\"color:var(--warn)\">${esc(s.diag)}</span>`:''}\n        </div>\n      </div>\n      <div class=\"side\">\n        <div class=\"grp\"><h3>skin tone</h3><select class=\"sk\">${s.skins.map((k,i)=>`<option value=\"${i}\" ${i===s.iSkin?'selected':''}>${esc(k.name)}</option>`).join('')}</select></div>\n        <div class=\"grp\"><h3>slot</h3><div class=\"catbar\">${['all'].concat(CATS).map(c=>`<button class=\"catsel ${c===s.catAtual?'on':''}\" data-c=\"${c}\">${ROTULO[c]||c}</button>`).join('')}</div></div>\n        <div class=\"grp\"><h3>${esc(ROTULO[s.catAtual]||s.catAtual)} <em>${lista.length}${s.q?' found':''}</em></h3>\n          <input class=\"busca\" placeholder=\"search by name or id…\" value=\"${esc(s.q||'')}\"/>\n          <div class=\"grade\" style=\"margin-top:8px\">\n            ${fatia.map(r=>{\n              const rid=r.id;\n              // Na aba All cada linha traz a folha de icone da PROPRIA categoria: o `ic[0]` indexa\n              // o iconSheets daquela cat, entao usar a folha errada desenharia outro item.\n              const rc=s.catAtual==='all'?(CATDE[rid]||''):s.catAtual;\n              const sheets=(IDX[rc]||{}).sheets||null;\n              const on=!!EQIDS[rid]||(r.cores&&r.cores.some(cc=>EQIDS[cc[0]]));\n              let mini='';\n              if(r.ic&&sheets){const[sh,x,y,w,h]=r.ic;\n                mini=`<span class=\"thc\" style=\"display:inline-block;width:${w}px;height:${h}px;background-image:url(${esc(sheets[sh])});background-position:-${x}px -${y}px\"></span>`;}\n              // 2 itens do dump inteiro nao tem icone em lugar nenhum (nem _Canvas, nem _outlink,\n              // nem inline). O canvas ao vivo nao serve mais: no caminho do idx o prefetch nao\n              // carrega geometria. Placeholder com o final do id, que ao menos identifica a celula.\n              else mini=`<span class=\"thc semic\">${esc(rid.slice(-4))}</span>`;\n              return `<button class=\"cel ${on?'on':''}\" data-id=\"${esc(rid)}\" data-cat=\"${esc(rc)}\" data-geo=\"${esc(r.geoRef||'')}\" data-ref=\"${esc(r.cores?r.cores[0][1]:'')}\" title=\"${esc(r.name)} · ${esc(rid)}\">${mini}</button>`;\n            }).join('')}\n          </div>\n          <div class=\"pag\"><button class=\"pp\" ${pag<=0?'disabled':''}>‹</button><span class=\"badge\">${pag+1} / ${paginas}</span><button class=\"np\" ${pag>=paginas-1?'disabled':''}>›</button></div>\n        </div>\n        ${(estilo&&estilo.cores&&estilo.cores.length>1)?`<div class=\"grp\"><h3>colour <em>${esc(estilo.name)}</em></h3><div class=\"cores\">${estilo.cores.map(cc=>{\n          const dg=Number(String(cc[0]).slice(-1));\n          const cor=PALETA[dg];\n          return `<button class=\"cor ${cor?'sw':''} ${cc[0]===eqid?'on':''}\" data-id=\"${esc(cc[0])}\" data-ref=\"${esc(cc[1])}\" data-geo=\"${esc(estilo.geoRef)}\" data-cat=\"${esc(corCat)}\" title=\"${esc(NOMECOR[dg]||('variant '+dg))}\" ${cor?`style=\"background:${cor}\"`:''}>${cor?'':dg}</button>`;\n        }).join('')}</div></div>`:''}\n        <div class=\"grp\"><h3>source</h3><div class=\"badge\">static index + packed icon sheets</div></div>\n      </div>\n    </div>`;\n  },\n\n  mounted(d2){\n    const s=d2.state;\n    s.pronto=false;s.iSkin=0;s.acao='stand1';s.frame=0;s.zoom=3;s.modo='parado';s.equip={};s.q='';s.pag=0;s.cat=[];s.carregando=false;s.efeito=false;s.efItem=true;s.stance49=false;s.flip=false;s._t0=0;s.catAtual='hair';s.sorteando=false;\n    // `oculto` = nao desenha mas continua equipado. `travado` = o randomize nao roleta esse slot.\n    // `travaModo` = o cadeado mestre esta aberto, mostrando o toggle no centro de cada thumb.\n    s.oculto={};s.travado={};s.travaModo=false;s.acAberto=false;s.corCat='hair';\n    s.podeUnd=false;s.podeRed=false;s.gerando=false;s.verPortrait=false;\n\n    // As skins SAIRAM da tabela `mcskinpix` (2026-08-20). Eram 46 x ~137 KB = 6,3 MB baixados\n    // inteiros no boot por `loadAll()` para exibir UMA. Agora `_skin` e um indice de 3,4 KB e\n    // cada skin e um arquivo do Drive buscado sob demanda e cacheado aqui.\n    async function carregaSkin(i){\n      const r=s.skins[i];if(!r)return false;\n      if(!SKINPIX[r.id])SKINPIX[r.id]=await (await fetch(MEDIA+r.ref)).json();\n      s.iSkin=i;return true;\n    }\n    function skinAtual(){return SKINPIX[(s.skins[s.iSkin]||{}).id]||{};}\n\n    // HISTORICO -- so o que muda o PERSONAGEM entra: `equip` e a skin. Oculto, trava, play, frame,\n    // zoom e categoria sao estado de ferramenta, nao de look, e ficam de fora de proposito.\n    // Um snapshot e o proprio objeto `equip`: todos os pontos que mexem nele ja TROCAM o objeto\n    // em vez de mutar, entao nao existe copia aqui -- sao ~14 ponteiros para strings que ja\n    // estavam na memoria (vieram do idx). Voltar tambem nunca busca rede: so da para voltar a um\n    // estado em que ja se esteve, e a geometria/atlas daquele estado ja esta em cache.\n    let HIST=[],POS=-1;\n    const MAXH=1000;\n    function sinaliza(){s.podeUnd=POS>0;s.podeRed=POS<HIST.length-1;}\n    // O UNICO ponto que empilha. Se alguma mudanca de equip/skin nao passar por aqui, ela some do\n    // historico em silencio -- e o jeito de nao esquecer e nunca escrever `s.equip=` fora daqui.\n    function marca(){\n      if(POS<HIST.length-1)HIST=HIST.slice(0,POS+1);   // acao nova apaga o futuro (o redo)\n      HIST.push({equip:s.equip,iSkin:s.iSkin});\n      if(HIST.length>MAXH)HIST.shift();\n      POS=HIST.length-1;sinaliza();\n    }\n    function defineEquip(novo){s.equip=novo;marca();}\n    // Aplica SEM empilhar. Se isto chamasse marca(), o undo empilharia a si mesmo e voce nunca\n    // sairia do lugar -- e o bug classico desta feature.\n    function aplicaHist(i){\n      const e=HIST[i];if(!e)return;\n      POS=i;s.equip=e.equip;\n      if(e.iSkin!==s.iSkin)carregaSkin(e.iSkin);\n      sinaliza();\n    }\n    function desfaz(){if(POS>0)aplicaHist(POS-1);}\n    function refaz(){if(POS<HIST.length-1)aplicaHist(POS+1);}\n\n    // carrega geometria+atlas de um item. geoRef = ref do bloco (novo) OU calcula pela chave (antigo).\n    async function carregaItem(id,geoRef,atlasRef,cat){\n      if(ITENS[id]&&ITENS[id].pronto&&(!EFI||!EFI[id]||ITENS[id].ei))return ITENS[id];\n      if(!geoRef)return null;\n      if(!BLOCO[geoRef]){const r=await fetch(MEDIA+geoRef);BLOCO[geoRef]=await r.json();}\n      const bl=BLOCO[geoRef]||{};\n      // popula todos do bloco (mesma geo); atlasRef vem por item\n      for(const iid in bl){if(!ITENS[iid]){const it=bl[iid];it.itemId=iid;ITENS[iid]=it;}}\n      // as armas \"so-efeito\" (todo o sprite mora no .effect.png) NAO tem atlas base: atlasRef\n      // fica null de proposito e elas somem quando o toggle de efeito esta desligado.\n      // ItemEff: bloco proprio (geoitemeff) + atlas proprio, carregados sob demanda.\n      const m=EFI&&EFI[id];\n      if(m&&ITENS[id]&&!ITENS[id].ei){\n        if(!BLOCOEI[m.b])BLOCOEI[m.b]=await (await fetch(MEDIA+m.b)).json();\n        const e=BLOCOEI[m.b][id];\n        if(e){ITENS[id].ei=e;ITENS[id].eiRef=m.a;\n          const x=Object.create(null);for(const i of (m.x||[]))x[i]=1;ITENS[id].eiX=x;}\n      }\n      if(ITENS[id]){ITENS[id].atlasRef=atlasRef||null;if(ITENS[id].geoEf)ITENS[id].atlasEfRef=(META[id]&&META[id].efRef)||null;ITENS[id].pronto=1;}\n      return ITENS[id]||null;\n    }\n    async function prefetch(){\n      // nada a fazer: a thumb e recorte CSS da folha, entao a grade nao precisa de geometria.\n      // Mantido como no-op para nao espalhar `if` nos call sites.\n    }\n    // Um carregador SO (o `garanteIdx` la de baixo). Antes esta funcao tinha a propria copia do\n    // fetch+META, e duas copias da mesma regra ja divergiram varias vezes neste projeto.\n    async function trocaCategoria(cat){\n      s.catAtual=cat;s.q='';s.pag=0;\n      if(cat==='all'){\n        // A aba All existe para a BUSCA achar qualquer item. Custa os 14 indices (~8,6 MB) na\n        // primeira vez -- o mesmo que o randomize ja paga -- e depois fica tudo em cache.\n        await Promise.all(CATS.map(garanteIdx));\n        let t=[];for(const c of CATS)t=t.concat((IDX[c]&&IDX[c].items)||[]);\n        s.cat=t;return;\n      }\n      await garanteIdx(cat);\n      s.cat=(IDX[cat]&&IDX[cat].items)||[];\n    }\n\n    // CONFLITO DE SLOT -- regra do JOGO, copiada do engine local (preview.html), nao inventada:\n    //  . outfit declara `vslot=MaPn`: ocupa coat E pants -> os tres nunca coexistem.\n    //  . arma com `islot=WpSi` ocupa a mao principal E a off-hand -> nao coexiste com shield.\n    // Resolver no nivel do SLOT (limpar o outro) e nao no render: e assim que o jogo faz, e\n    // desenhar os dois duplicaria o `mailArm` (medido no projeto local).\n    function aplicaConflito(e,cat){\n      for(const par of ((REGRAS&&REGRAS.exclusivos)||[])){\n        const a=par[0],bs=par[1];\n        if(cat===a){for(const b of bs)delete e[b];}\n        else if(bs.indexOf(cat)>=0)delete e[a];\n      }\n      const off=(REGRAS&&REGRAS.offhand)||'shield';\n      if(cat==='weapon'&&WPSI[e.weapon])delete e[off];\n      if(cat===off&&e[off]&&WPSI[e.weapon])delete e.weapon;\n      return e;\n    }\n    // Item SEM geometria: os 757 que nao desenham nada e mesmo assim EXISTEM -- todos declaram\n    // vslot. Servem para ocupar o slot e esconder (Bald Wig, Transparent Hat, as armas so-stat).\n    // `geoRef` e `atlasRef` sao null de proposito: nao ha bloco nem atlas para buscar. `compor`\n    // ja tolera ITENS[id] ausente, e o `vs` do vslot vem do META, que sai do idx.\n    function poeNoSlot(e,cat,id){\n      if(e[cat]===id){const x=Object.assign({},e);delete x[cat];return x;}\n      return aplicaConflito(Object.assign({},e,{[cat]:id}),cat);\n    }\n    async function equipar(id,geoRef,atlasRef,cat){\n      // Na aba All o slot vem da linha clicada (data-cat), nao da categoria aberta.\n      cat=cat||CATDE[id]||s.catAtual;\n      if(!cat||cat==='all'){s.diag='slot desconhecido para '+id;return;}\n      s.corCat=cat;\n      if(!geoRef){s.diag='';defineEquip(poeNoSlot(s.equip,cat,id));return;}\n      let it=null;\n      try{ it=await carregaItem(id,geoRef,atlasRef,cat); }\n      catch(err){ s.diag='failed: '+((err&&err.message)||err); return; }\n      if(!it){ s.diag='item not found: '+id; return; }\n      s.diag='';\n      img(it.atlasRef);\n      defineEquip(poeNoSlot(s.equip,cat,id));\n    }\n\n    // RANDOMIZE -- as mesmas regras do sorteio do engine local:\n    //  . face e hair SEMPRE entram (nao existe personagem sem rosto nem careca por ausencia de dado)\n    //  . OU um outfit, OU o par top+pants -- nunca os dois (mesma regra de slot do clique), e\n    //    nunca NENHUM dos dois: a cobertura e garantida, igual face e hair\n    //  . shield so quando a arma nao ocupa a off-hand\n    //  . os toggles de efeito NAO entram: sao switches de INSPECAO, nao pecas de equipamento --\n    //    sortear isso destruiria a comparacao que o usuario esta fazendo.\n    const um=a=>a&&a.length?a[Math.floor(Math.random()*a.length)]:null;\n    const talvez=(a,p)=>Math.random()<(p==null?0.5:p)?um(a):null;\n    // Sorteia a cor, carrega e equipa. Devolve se REALMENTE entrou -- `carregaItem` pode falhar, e\n    // sem esse retorno a garantia de cobertura viraria \"tentei\", que nao e garantia.\n    async function poe(eq,cat,r){\n      if(!r)return false;\n      const cor=um(r.cores)||[r.id,null];   // o equipado e uma VARIANTE, nao o estilo\n      if(!r.geoRef){eq[cat]=cor[0];return true;}   // item invisivel: equipa sem carregar\n      const it=await carregaItem(cor[0],r.geoRef,cor[1],cat);\n      if(!it)return false;\n      img(it.atlasRef);eq[cat]=cor[0];return true;\n    }\n    async function garanteIdx(cat){\n      if(IDX[cat]||!INDEXREF[cat])return;\n      const d=await (await fetch(MEDIA+INDEXREF[cat])).json();\n      const linhas=d.items||[];\n      // A lista navegavel e filtrada; o META/CATDE abaixo varre TODAS as linhas de proposito.\n      IDX[cat]={sheets:(d.iconSheets||[]).map(r=>MEDIA+r),items:linhas.filter(r=>!SEM_PREVIEW[r.id])};\n      if(d.zi2smap)ZI2SMAP=d.zi2smap;\n      for(const r of linhas){\n        META[r.id]=r;CATDE[r.id]=cat;\n        if(r.cores)for(const cc of r.cores){if(!META[cc[0]])META[cc[0]]=r;CATDE[cc[0]]=cat;}\n      }\n    }\n    async function sorteia(){\n      if(s.sorteando)return;\n      s.sorteando=true;\n      try{\n        await Promise.all(CATS.map(garanteIdx));\n        const pega=c=>(IDX[c]&&IDX[c].items)||[];\n        const escolha={};\n        for(const c of ['face','hair'])escolha[c]=um(pega(c));\n        if(Math.random()<0.4)escolha.outfit=um(pega('outfit'));\n        if(!escolha.outfit){escolha.coat=um(pega('coat'));escolha.pants=um(pega('pants'));}\n        for(const c of ['cap','cape','glove','shoes','accface','acceye','accear'])escolha[c]=talvez(pega(c));\n        escolha.weapon=talvez(pega('weapon'));\n        const eq={};\n        for(const c in escolha)await poe(eq,c,escolha[c]);\n        if(!WPSI[eq.weapon]){\n          const r=talvez(pega('offhand'));\n          if(r&&!r.geoRef)eq.offhand=r.id;\n          else if(r){const it=await carregaItem(r.id,r.geoRef,r.cores[0][1],'offhand');if(it){img(it.atlasRef);eq.offhand=r.id;}}\n        }\n        // Slot travado nao e roletado: o item atual fica, e ele vence o conflito de slot.\n        for(const c of ORDEM_EQ){\n          if(!s.travado[c]||!s.equip[c])continue;\n          eq[c]=s.equip[c];aplicaConflito(eq,c);\n        }\n        // COBERTURA: outfit OU (top + pants), nunca nu. Checada DEPOIS das travas de propósito --\n        // travar so o top e sortear um outfit deixaria sem calca, porque o conflito de slot remove\n        // o outfit e ninguem repoe a peca que faltou. O laco tenta de novo porque `poe` pode\n        // falhar ao carregar; sem o limite isso seria um while(true) num caso de rede ruim.\n        if(!eq.outfit){\n          for(let t=0;!eq.coat&&t<6;t++)await poe(eq,'coat',um(pega('coat')));\n          for(let t=0;!eq.pants&&t<6;t++)await poe(eq,'pants',um(pega('pants')));\n        }\n        // um randomize inteiro e UM passo de historico, nao um por slot\n        defineEquip(eq);\n      }catch(err){s.diag='randomize failed: '+((err&&err.message)||err);}\n      finally{s.sorteando=false;}\n    }\n\n    (async()=>{\n     try{\n      // `zmap` SAIU do boot (2026-08-20): era lido e nunca usado -- o `zi2smap` do vslot vem\n      // dentro do idxcap.json. Eram 2 leituras de tabela por abertura, uma delas inutil.\n      s.etapa='loading index';\n      const mi=await d2.rush('mcindexall').invoke({});\n      for(const r of ((mi&&mi.data&&mi.data.rows)||[]))INDEXREF[r.cat]=r.ref;\n      s.etapa='loading actions';\n      try{await carregaAcoes();}catch(err){d2.log('[mcbaseviewer] tabela de actions indisponivel, usando o passo padrao: '+((err&&err.message)||err));}\n      s.etapa='loading skins';\n      const li=await (await fetch(MEDIA+INDEXREF._skin)).json();\n      s.skins=(li.items||[]).slice().sort((a,b)=>String(a.id).localeCompare(String(b.id)));\n      await carregaSkin(0);\n      // mcgeoblocksall/mcatlasidxall SAIRAM do boot (2026-08-20): todo geoRef/atlasRef vem do idx.\n      if(INDEXREF._regras){\n        REGRAS=await (await fetch(MEDIA+INDEXREF._regras)).json();\n        for(const w of (REGRAS.wpsi||[]))WPSI[w]=1;\n      }\n      if(INDEXREF._itemeff)EFI=await (await fetch(MEDIA+INDEXREF._itemeff)).json();\n      await trocaCategoria(CATS[0]);\n      s.pronto=true;marca();await prefetch();\n     }catch(err){s.erro=(err&&err.message)?err.message:String(err);}\n    })();\n\n    // `desenhaThumbs` REMOVIDO (2026-08-20): desenhava thumb ao vivo em `canvas.th`, que nao\n    // existe mais desde que toda categoria passou a ter folha de icone. Ele rodava dentro do rAF,\n    // entao era uma varredura de DOM (`$$('canvas.th')`) por FRAME, 60x por segundo, sempre vazia.\n\n    let raf=null;\n    function draw(){\n      raf=requestAnimationFrame(draw);\n      if(!s.pronto)return;\n      const cv=d2.$('canvas.big');if(!cv)return;\n      const dpr=window.devicePixelRatio||1,W=cv.clientWidth,H=cv.clientHeight;\n      // A PISCADA vinha daqui. A cada re-render o palco passa por um quadro com clientWidth 0; a\n      // linha de baixo entao escrevia cv.width=0, e ESCREVER em cv.width destroi o bitmap. O\n      // quadro saia em branco e so o rAF seguinte redesenhava. O mcbuilder ja tinha esta guarda,\n      // o viewer nao -- comparar os dois foi o que apontou.\n      if(!W||!H)return;\n      // compara a ALTURA tambem: so a largura deixava o canvas com altura errada quando apenas\n      // ela mudava (redimensionar a janela na vertical).\n      if(cv.width!==Math.round(W*dpr)||cv.height!==Math.round(H*dpr)){\n        cv.width=Math.round(W*dpr);cv.height=Math.round(H*dpr);\n      }\n      const ctx=cv.getContext('2d');ctx.setTransform(dpr,0,0,dpr,0,0);ctx.imageSmoothingEnabled=false;ctx.clearRect(0,0,W,H);\n      const CX=Math.round(W/2), CY=Math.round(H*EIXO_Y);\n      // guias discretas + sombra de chao, so para dar profundidade ao palco\n      ctx.fillStyle='rgba(255,255,255,0.045)';ctx.fillRect(CX,0,1,H);ctx.fillRect(0,CY,W,1);\n      const rs=44*s.zoom;\n      const sg=ctx.createRadialGradient(CX,CY,0,CX,CY,rs);\n      sg.addColorStop(0,'rgba(0,0,0,0.42)');sg.addColorStop(1,'rgba(0,0,0,0)');\n      ctx.save();ctx.translate(CX,CY);ctx.scale(1,0.22);ctx.translate(-CX,-CY);\n      ctx.fillStyle=sg;ctx.beginPath();ctx.arc(CX,CY,rs,0,6.2832);ctx.fill();ctx.restore();\n      const sk=skinAtual();\n      const acoes=ordenaAcoes(Object.keys(sk.geo||{}));const acao=s.acao&&acoes.indexOf(s.acao)>=0?s.acao:acoes[0];\n      const nfr=keys(sk.geo,acao).length||1;\n      let frame=Math.min(Math.max(s.frame||0,0),nfr-1);\n      if(s.modo!=='parado'){\n        const r=frameNoTempo(acao,nfr,Date.now()-s._t0,s.modo);\n        frame=r.frame;TEMPO=Date.now()-s._t0;\n        // Play Once acabou: volta ao frame 0 e para.\n        if(r.terminou){s.modo='parado';s.frame=0;frame=0;TEMPO=0;}\n      }\n      // O indicador e o botao do frame ativo sao escritos DIRETO no DOM, nao por re-render: o\n      // frame muda ate ~6x/s e re-renderizar redesenharia a grade de 40 celulas junto. A guarda\n      // pelo texto tambem cura sozinha depois de um re-render (o span volta a '–').\n      const ind=d2.$('.frameind');\n      if(ind&&ind.textContent!==String(frame)){\n        ind.textContent=String(frame);\n        // Itera os FILHOS do container em vez de usar o seletor-multiplo: mais barato, e evita\n        // escrever o cifrao dobrado, que o patcher come como escape de replace.\n        const cx=d2.$('.frames');\n        if(cx)for(const b of cx.children)b.classList.toggle('on',Number(b.getAttribute('data-f'))===frame);\n      }\n      // OCULTAR: some com o slot ANTES de compor. Assim a peca some, o `h` dela para de esconder\n      // parte do corpo e o vslot dela para de valer -- ocultar o chapeu devolve o cabelo, que e o\n      // comportamento pedido. Uma linha em vez de tres regras espalhadas dentro de `compor`.\n      const eqv=Object.create(null);\n      for(const k in s.equip)if(!s.oculto[k])eqv[k]=s.equip[k];\n      const {L}=compor(sk,eqv,acao,frame,s.efeito,s.efItem,s.stance49===true);const z=s.zoom;\n      // FLIP: espelho puro do COMPOSTO em torno do eixo x=0 do mundo (CX). Nao mexe em zi nem em\n      // ancora -- e por isso ancoras, vslot e ItemEff viram juntos, de graca. O eixo NAO e o centro\n      // do bounding box. Os sprites do jogo olham para a ESQUERDA, entao ligado = virado a direita.\n      // As guias e a sombra ficam FORA do espelho de proposito (sao do palco, nao do personagem).\n      const vira=s.flip===true;\n      if(vira){ctx.save();ctx.translate(CX*2,0);ctx.scale(-1,1);}\n      for(const c of L){\n        if(c.base!==undefined){const im=imgBase(sk,c.base);if(!im||!im.complete||!im.naturalWidth)continue;ctx.drawImage(im,CX+c.x*z,CY+c.y*z,im.naturalWidth*z,im.naturalHeight*z);}\n        else{\n          const at=img(c.ref);if(!at||!at.complete||!at.naturalWidth)continue;const[u,v,w,h]=c.uv;\n          // `noflip`: a POSICAO espelha, o BITMAP nao. Sai do espelho e desenha no x refletido,\n          // que e a formula do contrato: translate(cx - (x+w)*zoom, cy + y*zoom) scale(zoom).\n          if(vira&&c.nfw){\n            ctx.save();ctx.setTransform(dpr,0,0,dpr,0,0);\n            ctx.drawImage(at,u,v,w,h,CX-(c.x+c.nfw)*z,CY+c.y*z,w*z,h*z);\n            ctx.restore();\n          }else ctx.drawImage(at,u,v,w,h,CX+c.x*z,CY+c.y*z,w*z,h*z);\n        }\n      }\n      if(vira)ctx.restore();\n    }\n    raf=requestAnimationFrame(draw);\n\n    d2.on('.sk','change',async (e,el)=>{s.carregando=true;try{await carregaSkin(Number(el.value));marca();}finally{s.carregando=false;}});\n    d2.on('.und','click',()=>{desfaz();});\n    d2.on('.red','click',()=>{refaz();});\n    // Ctrl+Z / Ctrl+Y (e Ctrl+Shift+Z). Dentro do builder do data2 o editor costuma capturar o\n    // atalho antes de chegar aqui; no app publicado funciona. Os botoes sao a via confiavel.\n    d2.on(window,'keydown',e=>{\n      if(!(e.ctrlKey||e.metaKey))return;\n      const alvo=e.target&&e.target.tagName;\n      if(alvo==='INPUT'||alvo==='TEXTAREA'||alvo==='SELECT')return;   // nao rouba o ctrl+z da busca\n      const k=String(e.key||'').toLowerCase();\n      if(k==='z'&&!e.shiftKey){e.preventDefault();desfaz();}\n      else if(k==='y'||(k==='z'&&e.shiftKey)){e.preventDefault();refaz();}\n    });\n    // trocar de action reinicia o relogio, senao a nova comeca no meio do ciclo da anterior\n    d2.on('.ac','click',(e,el)=>{s.acao=el.getAttribute('data-a');s.frame=0;s._t0=Date.now();TEMPO=0;s.acAberto=false;});\n    d2.on('.acbtn','click',()=>{s.acAberto=!s.acAberto;});\n    d2.on('.mestre','click',()=>{s.travaModo=!s.travaModo;});\n    // No modo cadeado o centro tranca; fora dele, oculta. O `.cad` fica por cima do `.eqc`, mas\n    // os dois fazem a mesma coisa no modo cadeado, entao qualquer um que pegue o clique serve.\n    d2.on('.eqc','click',(e,el)=>{\n      const sl=el.getAttribute('data-slot');\n      if(s.travaModo){const t=Object.assign({},s.travado);t[sl]=!t[sl];s.travado=t;}\n      else{const o=Object.assign({},s.oculto);o[sl]=!o[sl];s.oculto=o;}\n    });\n    d2.on('.cad','click',(e,el)=>{const sl=el.getAttribute('data-slot');const t=Object.assign({},s.travado);t[sl]=!t[sl];s.travado=t;});\n    d2.on('.fr','click',(e,el)=>{s.modo='parado';s.frame=Number(el.getAttribute('data-f'))||0;TEMPO=0;});\n    // So acende o \"loading\" quando ha o que baixar. Ligar/desligar o aviso gera DOIS re-renders,\n    // e navegar entre categorias ja em cache nao precisa de nenhum.\n    d2.on('.catsel','click',async (e,el)=>{\n      const c=el.getAttribute('data-c');\n      const emCache=c==='all'?CATS.every(x=>IDX[x]):!!IDX[c];\n      if(!emCache)s.carregando=true;\n      try{await trocaCategoria(c);await prefetch();}\n      finally{if(!emCache)s.carregando=false;}\n    });\n    d2.on('.cel','click',async (e,el)=>{await equipar(el.getAttribute('data-id'),el.getAttribute('data-geo')||null,el.getAttribute('data-ref')||null,el.getAttribute('data-cat')||null);});\n    d2.on('.cor','click',async (e,el)=>{const id=el.getAttribute('data-id');const ct=el.getAttribute('data-cat')||s.catAtual;const it=await carregaItem(id,el.getAttribute('data-geo')||null,el.getAttribute('data-ref')||null,ct);if(it){img(it.atlasRef);defineEquip(Object.assign({},s.equip,{[ct]:id}));}});\n    // desequipar tambem limpa o oculto/travado do slot, senao a marca fica pendurada no proximo\n    d2.on('.x','click',(e,el)=>{\n      const sl=el.getAttribute('data-slot');\n      const ee=Object.assign({},s.equip);delete ee[sl];defineEquip(ee);\n      const o=Object.assign({},s.oculto);delete o[sl];s.oculto=o;\n      const t=Object.assign({},s.travado);delete t[sl];s.travado=t;\n    });\n    let tb=null;\n    d2.on('.busca','input',(e,el)=>{s.q=el.value;s.pag=0;if(tb)clearTimeout(tb);tb=setTimeout(prefetch,400);});\n    d2.on('.pp','click',()=>{s.pag=Math.max(0,(s.pag||0)-1);prefetch();});\n    d2.on('.np','click',()=>{s.pag=(s.pag||0)+1;prefetch();});\n    d2.on('.zm','click',()=>{s.zoom=Math.max(1,s.zoom-1);});\n    d2.on('.zp','click',()=>{s.zoom=Math.min(8,s.zoom+1);});\n    d2.on('.ef','click',()=>{s.efeito=!s.efeito;});\n    d2.on('.efi','click',()=>{s.efItem=!s.efItem;});\n    d2.on('.st49','change',(e,el)=>{s.stance49=el.checked;});\n    d2.on('.flip','click',()=>{s.flip=!s.flip;});\n    // QUADRO FIXO -- nao enquadra pelo bbox de cada personagem. A base nao se mexe (medido: 45\n    // das 46 skins tem geometria identica em stand1/frame0; a 00002020 difere 1px na orelha),\n    // entao o quadro tambem nao pode se mexer: com bbox, um chapeu alto mudava o zoom e o corte\n    // de TODO o retrato, e dois personagens nao davam para comparar.\n    // Os numeros NAO sao escolhidos, sao ajustados as 6 referencias de estilo (UIAssets), medidas\n    // com o mesmo segmentador: queixo 80,4% +-2,3 / largura da cabeca 88,7% +-8,7 / eixo 49,6% +-2,3.\n    // Este quadro entrega queixo 80,9 / largura 87,3 / eixo 48,7 -- as tres dentro do desvio.\n    // Verificado em 6 personagens diferentes: o corpo cai nos MESMOS pixels (torso x 140..349).\n    // O quadro antigo (x-35 y-82 z8) dava largura 70,1%, abaixo de TODAS as referencias: o sprite\n    // ia enquadrado menor que o estilo alvo, e o modelo brigava com a propria referencia.\n    // Cortar cabelo alto e aceito (decisao do usuario) -- 3 das 6 refs tem cabelo na borda de cima.\n    // 512/51 = 10 exato: o zoom precisa ser INTEIRO, senao o nearest-neighbor borra.\n    const QX0=-28,QY0=-71,QLADO=51;\n    function exportaPersonagem(lado){\n      const sk=skinAtual();if(!sk||!sk.geo)return null;\n      // SEMPRE stand1/frame0 e sem flip: a referencia nao pode depender de onde a animacao\n      // estava, nem dos toggles de fx, senao dois retratos do mesmo personagem saem diferentes.\n      const acoes=Object.keys(sk.geo||{});\n      const acao=acoes.indexOf('stand1')>=0?'stand1':acoes[0];\n      const eqv=Object.create(null);\n      for(const k in s.equip)if(!s.oculto[k])eqv[k]=s.equip[k];\n      const {L}=compor(sk,eqv,acao,0,false,true,s.stance49===true);\n      const z=Math.max(1,Math.round(lado/QLADO));\n      const cv=document.createElement('canvas');cv.width=lado;cv.height=lado;\n      const g=cv.getContext('2d');\n      // Cinza neutro, nao branco e nao transparente: PNG com alpha costuma virar preto no gerador,\n      // e o branco competia com a pele e com o brilho do cabelo. As 6 refs NAO concordam entre si\n      // no fundo (3 claras, 3 escuras), entao fundo nao se aprende delas -- vem daqui.\n      g.fillStyle='#808080';g.fillRect(0,0,lado,lado);\n      g.imageSmoothingEnabled=false;\n      let faltou=0,pintou=0;\n      for(const c of L){\n        if(c.base!==undefined){\n          const im=imgBase(sk,c.base);\n          if(!im||!im.complete||!im.naturalWidth){faltou++;continue;}\n          g.drawImage(im,(c.x-QX0)*z,(c.y-QY0)*z,im.naturalWidth*z,im.naturalHeight*z);pintou++;\n        }else{\n          const at=img(c.ref);\n          if(!at||!at.complete||!at.naturalWidth){faltou++;continue;}\n          const u=c.uv[0],v=c.uv[1],w=c.uv[2],h=c.uv[3];\n          g.drawImage(at,u,v,w,h,(c.x-QX0)*z,(c.y-QY0)*z,w*z,h*z);pintou++;\n        }\n      }\n      if(!pintou)return null;\n      return {url:cv.toDataURL('image/png'),faltou:faltou};\n    }\n    function descrevePersonagem(){\n      const partes=[];\n      const sk=(s.skins[s.iSkin]||{}).name;\n      if(sk)partes.push('skin tone '+sk);\n      for(const sl of ORDEM_EQ){\n        const id=s.equip[sl];\n        if(!id||s.oculto[sl])continue;\n        const r=META[id];\n        partes.push((ROTULO[sl]||sl)+' '+((r&&r.name)||id));\n      }\n      return partes.length?partes.join(', ')+'.':'a plain character with no equipment.';\n    }\n    d2.on('.rnd','click',async()=>{await sorteia();});\n    d2.on('.portx','click',()=>{s.verPortrait=false;});\n    d2.on('.port','click',async()=>{\n      if(s.gerando)return;\n      s.gerando=true;s.diag='';\n      try{\n        const ref=exportaPersonagem(512);\n        if(!ref)throw new Error('nada desenhado ainda para usar de referencia');\n        if(ref.faltou)s.diag='portrait: '+ref.faltou+' camada(s) ainda carregando, a referencia saiu incompleta';\n        const p=PROMPT_BASE;\n        const st=d2.store(STORE_PORTRAIT);\n        if(st)st.set('prompt',p);\n        // Manda UMA imagem (o personagem, data URI do canvas). A referencia de ESTILO entra no\n        // handler, server-side, como SEGUNDA imagem: assim ela e trocavel sem republicar isto.\n        // O handler tambem grava a saida no Drive e devolve URL -- antes voltava data URI de 3 MB.\n        const r=await d2.rush(RUSH_PORTRAIT).invoke({prompt:p,imagem:ref.url,quantity:1});\n        const u=r&&r.data&&r.data.url;\n        if(!u)throw new Error('a resposta nao trouxe imagem');\n        if(st)st.set('url',u);\n        const im=d2.$('.portimg');if(im)im.src=u;\n        s.verPortrait=true;\n      }catch(err){s.diag='portrait: '+((err&&err.message)||err);}\n      finally{s.gerando=false;}\n    });\n    // o relogio do ItemEff zera junto com o do corpo, senao o efeito comeca no meio do ciclo\n    d2.on('.play','click',()=>{s.modo=s.modo==='loop'?'parado':'loop';s._t0=Date.now();TEMPO=0;});\n    d2.on('.play1','click',()=>{s.modo='uma';s._t0=Date.now();TEMPO=0;});\n    d2.onUnmount(()=>{if(raf)cancelAnimationFrame(raf);});\n  }\n};\n"}},"skins":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"skins"}}}},"zmap":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"zmap"}}}}}}}},"__d2PrevCode":{"code":"// `acc` do dump e um saco unico; no JOGO sao TRES slots independentes, distinguidos pelo islot\n// (Af=face acc, Ay=eye acc, Ae=earring) -- que e o mesmo criterio canonico usado no resto do projeto.\n// Separar nao e so organizacao da grade: com um slot so, oculos e brinco nunca poderiam coexistir.\nconst CATS=['hair','face','cape','cap','weapon','outfit','coat','pants','shoes','glove','offhand','accface','acceye','accear'];\n// `offhand` NAO e \"shield\": pelo BLUEPRINT 7.1 o islot `Si` e o slot de off-hand, e ele aceita\n// shield OU katara. Com a katara na mao principal (como estava) nao dava para montar um Dual\n// Blade -- adaga na principal + katara na off-hand -- e dava para usar katara e shield juntos.\n// `coat` e o nome do arquivo dos devs do Maple; na comunidade (e na nossa interface) e `top`.\nconst ROTULO={accface:'face acc',acceye:'eye acc',accear:'earring',offhand:'off-hand',coat:'top'};\n// A coluna de equipados tem ordem FIXA, nao a ordem em que as pecas foram equipadas.\nconst ORDEM_EQ=['hair','face','cap','accear','accface','acceye','outfit','coat','pants','glove','weapon','offhand','cape','shoes'];\n// Paleta de cor do CABELO. Medido no catalogo inteiro: hair e a UNICA categoria com variante de\n// cor (2.176 estilos); as outras 13 tem exatamente 1 cor por estilo. E a cor NAO e a posicao na\n// lista, e o ULTIMO DIGITO do id -- o estilo 00037361 comeca no digito 1 (vermelho), nao no 0.\n// Um unico estilo (00030920) tem digitos 8 e 9, que sao copias do 00030930/31; ate serem tirados\n// do indice eles caem no quadradinho neutro com o numero, em vez de mentir uma cor.\nconst PALETA=['#191919','#c0392b','#e07b1f','#f0c419','#2f9e44','#2b6cb0','#8e44ad','#7a5230'];\nconst NOMECOR=['black','red','orange','yellow','green','blue','purple','brown'];\n// Grupos do painel de Action. MANUAL de proposito por enquanto (decisao do usuario): se um dia\n// virar coluna da tabela `mc-action`, some daqui e passa a sair do dado.\nconst MOVENDO={walk1:1,walk2:1,jump:1,fly:1,ladder:1,rope:1};\nconst GRUPOS=['Stand','Moving','Swing','Stab','Shoot'];\nfunction grupoUI(a){\n  if(a.indexOf('swing')===0)return 'Swing';\n  if(a.indexOf('stab')===0)return 'Stab';   // `proneStab` NAO cai aqui: comeca com 'prone'\n  if(a.indexOf('shoot')===0)return 'Shoot';\n  return MOVENDO[a]?'Moving':'Stand';\n}\nconst CATDE=Object.create(null);   // id do item -> categoria real; e o que faz a aba All equipar certo\n// ESCONDIDOS DA NAVEGACAO -- nao da base. Eluna Earrings: 8 variantes x 5 niveis = 40 ids\n// (01032270..01032309), e os 5 niveis de cada variante sao a MESMA arte -- medido: `geo`, `uv` e\n// `px` identicos, e os 5 PNGs de atlas com o MESMO sha1, byte a byte. Ficam so os Lv.1 (ultimo\n// digito 0 ou 5), tirando 32 linhas que o randomize sorteava com peso desproporcional.\n// So a LISTA navegavel encolhe: `META` e `CATDE` continuam populados para os 40, entao um desses\n// ids ainda renderiza normalmente se vier de fora (histórico de undo, estado salvo, link).\n// PORTRAIT por AI. O prompt fica aqui em cima de proposito: a fase 2 e calibrar ESTE texto, e\n// procurar por ele no meio do render seria pior. O `draw` devolve {data:[{url}]} com url =\n// data URI de ~3,2 MB; quem converte para arquivo do Drive e devolve URL curta e o HANDLER.\nconst RUSH_PORTRAIT='mcportrait';\nconst STORE_PORTRAIT='mcportraitimg';\n// Ordem B (escolhida 2026-08-28 no teste de 3 montagens): SPRITE primeiro, referencia de estilo\n// depois. A ref de estilo e APENDADA no handler, server-side, para ser trocavel sem republicar\n// isto; o componente manda so o sprite, no campo `imagem`.\n// Nao vai nome de item no texto: nome enviesa (a arma \"Chaos Devil's Sunrise\" saiu com o nome\n// escrito na lamina). O sprite carrega a forma e a cor; o texto so descreve estilo e enquadramento.\nconst PROMPT_BASE='Draw one square character portrait.\\n\\nThe FIRST image is a small pixel sprite of the character to draw. It is a design description, never artwork to copy: read from it the hairstyle and its colour, the eye and eyebrow design, the headwear if any, and the collar and garment colours.\\n\\nThe SECOND image is a STYLE REFERENCE. Match its rendering exactly: soft chibi anime portrait, thin dark outline, smooth gradient shading, glossy hair with a bright highlight band across the crown, very large glossy eyes with a dark upper lash line and a white catchlight, no drawn nose, soft cheek blush, clean smooth skin.\\n\\nRedraw the character from the first image at high resolution in the style of the second. Invent nothing that is not visible in the sprite, and always give the character clearly drawn open eyes.\\n\\nFraming: square, tight bust. Centre the head horizontally and let it fill most of the frame width. The chin must sit at four fifths of the frame height and the top of the hair must reach the top edge. Crop at the base of the neck: show the head and only a sliver of the shoulders. Do not draw arms, hands, chest or torso, and do not zoom out. No text, no letters, no watermark, no border.';\nconst SEM_PREVIEW=Object.create(null);\nfor(let n=2270;n<=2309;n++)if(n%5!==0)SEM_PREVIEW['0103'+n]=1;\n// Cadeado: os dois SVGs de UIAssets, inline. Sao ~300 bytes cada, entao virar arquivo custaria\n// uma requisicao por thumb para economizar nada. Sem `fill` no path: quem manda na cor e o CSS,\n// via `fill:currentColor` -- por isso o mesmo icone serve verde no palco e escuro no cantinho.\nconst DLOCK='M12,17A2,2 0 0,0 14,15C14,13.89 13.1,13 12,13A2,2 0 0,0 10,15A2,2 0 0,0 12,17M18,8A2,2 0 0,1 20,10V20A2,2 0 0,1 18,22H6A2,2 0 0,1 4,20V10C4,8.89 4.9,8 6,8H7V6A5,5 0 0,1 12,1A5,5 0 0,1 17,6V8H18M12,3A3,3 0 0,0 9,6V8H15V6A3,3 0 0,0 12,3Z';\nconst DLOCKAB='M18,8A2,2 0 0,1 20,10V20A2,2 0 0,1 18,22H6C4.89,22 4,21.1 4,20V10A2,2 0 0,1 6,8H15V6A3,3 0 0,0 12,3A3,3 0 0,0 9,6H7A5,5 0 0,1 12,1A5,5 0 0,1 17,6V8H18M12,17A2,2 0 0,0 14,15A2,2 0 0,0 12,13A2,2 0 0,0 10,15A2,2 0 0,0 12,17Z';\nfunction cadeado(aberto){return `<svg class=\"lk\" viewBox=\"0 0 24 24\" aria-hidden=\"true\"><path d=\"${aberto?DLOCKAB:DLOCK}\"/></svg>`;}\n// Olho cortado = item OCULTO (equipado, mas nao desenhado). Icone de traco, nao de preenchimento:\n// stroke/fill vao no CSS. Tirei o `class=\"lucide ...\"` do original de proposito -- classe de\n// biblioteca de terceiros aqui so viraria nome morto que ninguem sabe se pode remover depois.\nconst DOLHO='<path d=\"M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49\"/><path d=\"M14.084 14.158a3 3 0 0 1-4.242-4.242\"/><path d=\"M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143\"/><path d=\"m2 2 20 20\"/>';\nfunction olho(){return `<svg class=\"olho\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">${DOLHO}</svg>`;}\n// TIMING E SEQUENCIA saem da tabela `mc-action` (2026-08-27). Antes eram quatro constantes e uma\n// funcao aqui dentro: PASSO=208 fixo, dois fatores ajustados a mao e a lista VAIVEM. O WZ tem\n// `delay` em TODO frame do corpo e o compilador descartava -- a tabela devolveu esse dado. Nada\n// aqui e estimado: `frameDelays` e o delay do jogo, `playback`/`loopStart` descrevem a sequencia.\nconst ACOES='https://bigaton-playground.data2apis.com/dataset/mc-actionDataSet';\nconst ACT=Object.create(null);   // key da action -> linha da tabela\nlet ACTORD=[];                   // keys na ordem da tabela (o preview mostra nessa ordem)\nconst PASSO_PADRAO=208;          // fallback so para uma action que a tabela nao conheca\n// O eixo do mundo (x=0,y=0 do personagem) sai do TAMANHO do canvas, nao de constante fixa:\n// horizontal no meio, vertical em 72% da altura -- a origem cai perto dos pes e o corpo cresce\n// para cima (y negativo), entao 72% e o que deixa o personagem opticamente centrado. Mesma\n// proporcao do stage do engine local.\n// 42 = 7 linhas cheias de 6. Com 40 a ultima linha ficava com 4 e a grade parecia quebrada.\nconst EIXO_Y=0.72, POR_PAGINA=42;\nconst MEDIA='https://bigaton-playground.data2.media/file/';\n\nconst INDEXREF=Object.create(null); // cat -> ref do idx JSON (caminho NOVO)\nconst IDX=Object.create(null);      // cat -> {sheets:[url], items:[...]} (fetch cacheado)\nconst BLOCO=Object.create(null);    // blockKey/geoRef -> bloco parseado\nconst ITENS=Object.create(null);    // id -> {uv,geo,atlasRef, uvEf?,geoEf?,atlasEfRef?}\nconst IMG=Object.create(null);      // ref -> Image\nconst IMGBASE=Object.create(null);   // caminho do sprite da base -> Image (o caminho ja carrega o id da skin, entao nao colide)\nconst SKINPIX=Object.create(null);   // skinId -> {geo,pix} do arquivo do Drive, buscado sob demanda\nconst META=Object.create(null);      // id -> linha do idx (vs do cap, efRef da arma)\nlet ZI2SMAP=null;                    // zi -> codigo de smap (vem no idxcap.json)\nlet REGRAS=null;                     // conflitos de slot (mcregras.json)\nlet EFI=null;                        // id -> {a:atlasRef, b:blocoRef, x:[uv do ItemEff no atlas BASE]}\nconst BLOCOEI=Object.create(null);   // blocoRef -> bloco de ItemEff parseado\nlet TEMPO=0;                         // relogio do ItemEff em ms; so anda com o Play ligado\n// O ItemEff tem EIXO PROPRIO: ciclo de animacao com `delay` por frame, SEM relacao com o numero\n// de frames da acao. Um efeito de 16 frames num `stand1` de 3 rodava so 3 se fosse amarrado ao\n// corpo. O `e` traz o efeito inteiro e o canto de cada frame sai de `ancora - origin`.\n// EF_VEL e ajuste de APRESENTACAO (fator na leitura do relogio, nao no dado) -- mesmo do local.\nconst EF_VEL=1.15;\nconst WPSI=Object.create(null);      // arma que ocupa TAMBEM a off-hand\n\nfunction esc(s){return String(s==null?'':s).replace(/[<>&\"]/g,c=>({'<':'&lt;','>':'&gt;','&':'&amp;','\"':'&quot;'}[c]));}\n// O endpoint do dataset NAO devolve ordenado -- ordenar por `order` aqui e o que preserva a\n// ordem do preview. A tabela e opcional: sem ela tudo cai no fallback e o preview segue de pe.\nasync function carregaAcoes(){\n  const d=await (await fetch(ACOES)).json();\n  const rows=((d&&d.data)||[]).slice().sort((a,b)=>(a.order|0)-(b.order|0));\n  ACTORD=rows.map(r=>r.key);\n  for(const r of rows)ACT[r.key]=r;\n  return rows.length;\n}\n// A skin diz QUAIS actions existem (autoridade de render); a tabela diz em que ordem mostrar e\n// com que nome. Action fora da tabela vai para o fim, nunca some.\nfunction ordenaAcoes(ks){\n  const pos=a=>{const i=ACTORD.indexOf(a);return i<0?9999:i;};\n  return ks.slice().sort((a,b)=>pos(a)-pos(b)||a.localeCompare(b));\n}\nfunction rotuloAcao(a){return (ACT[a]&&ACT[a].name)||a;}\n// `loopStart` separa a INTRO (frames que so tocam ao entrar na action) da parte que repete.\n// E o caso do walk: o frame 0 e transicao, o loop e 1,2,3,2 -- antes o frame 0 voltava a cada\n// volta porque a sequencia era montada sempre a partir do zero.\nfunction sequencia(a,n){\n  const r=ACT[a]||null;\n  const modo=r?r.playback:(n>2?'pingpong':'loop');\n  const ls=Math.min(Math.max(r?(r.loopStart|0):0,0),Math.max(n-1,0));\n  const intro=[];for(let i=0;i<ls;i++)intro.push(i);\n  const ciclo=[];for(let i=ls;i<n;i++)ciclo.push(i);\n  if(modo==='pingpong')for(let i=n-2;i>ls;i--)ciclo.push(i);\n  return{intro,ciclo,modo};\n}\n// Duracao de um frame: o delay do WZ dividido pelo multiplicador da tabela (actionSpeed 1 = o\n// tempo original do jogo, 2 = o dobro da velocidade). Mesma forma que o ItemEff ja usava.\nfunction duracao(a,i){\n  const r=ACT[a];\n  const fd=r&&r.frameDelays;\n  const d=(fd&&fd.length)?fd[Math.min(i,fd.length-1)]:PASSO_PADRAO;\n  const v=(r&&Number(r.actionSpeed)>0)?Number(r.actionSpeed):1;\n  return Math.max(16,(Number(d)||PASSO_PADRAO)/v);\n}\n// AQUI O PREVIEW DIVERGE DO mcbuilder DE PROPOSITO. La o `playback:'once'` da tabela e regra de\n// jogo e o golpe toca uma vez so. Aqui isto e ferramenta de INSPECAO: no modo 'loop' tudo repete,\n// inclusive os ataques, porque olhar um golpe girando e o ponto. A tabela continua mandando na\n// SEQUENCIA (intro + pingpong via loopStart) e na DURACAO de cada frame -- o modo so decide se\n// repete ou nao. 'uma' = Play Once: percorre intro+ciclo inteiros e avisa `terminou` para o\n// preview voltar ao frame 0 e parar.\nfunction frameNoTempo(a,n,ms,modo){\n  const {intro,ciclo}=sequencia(a,n);\n  const passos=intro.concat(ciclo);\n  if(!passos.length)return{frame:0,terminou:true};\n  let t=ms;\n  if(modo==='uma'){\n    for(const f of passos){const d=duracao(a,f);if(t<d)return{frame:f,terminou:false};t-=d;}\n    return{frame:passos[0],terminou:true};\n  }\n  for(const f of intro){const d=duracao(a,f);if(t<d)return{frame:f,terminou:false};t-=d;}\n  let total=0;for(const f of ciclo)total+=duracao(a,f);\n  if(total<=0)return{frame:ciclo[0],terminou:false};\n  t=t%total;\n  for(const f of ciclo){const d=duracao(a,f);if(t<d)return{frame:f,terminou:false};t-=d;}\n  return{frame:ciclo[ciclo.length-1],terminou:false};\n}\nfunction keys(g,a){return Object.keys((g||{})[a]||{});}\nfunction imgBase(sk,p){if(IMGBASE[p])return IMGBASE[p];const v=(sk.pix||{})[p];if(!v)return null;const im=new Image();im.src=v;IMGBASE[p]=im;return im;}\n// `crossOrigin` ANTES do src (depois nao vale) -- e o que impede o canvas de ficar CONTAMINADO.\n// Sem isso `toDataURL` lanca SecurityError e nao existe portrait imagem-para-imagem. O Drive\n// manda `Access-Control-Allow-Origin: *` nos TRES saltos (media -> apis -> files), inclusive no\n// 200 final, entao pedir CORS aqui nao quebra o carregamento.\nfunction img(ref){if(!ref)return null;if(IMG[ref])return IMG[ref];const im=new Image();im.crossOrigin='anonymous';im.src=MEDIA+ref;IMG[ref]=im;return im;}\n\nfunction pagina(s){\n  const q=(s.q||'').trim().toLowerCase();\n  const lista=q?(s.cat||[]).filter(r=>(r.busca||'').indexOf(q)>=0):(s.cat||[]);\n  const paginas=Math.max(1,Math.ceil(lista.length/POR_PAGINA));\n  const pag=Math.min(s.pag||0,paginas-1);\n  return{q,lista,paginas,pag,fatia:lista.slice(pag*POR_PAGINA,pag*POR_PAGINA+POR_PAGINA)};\n}\n\n// O item e indexado pela CHAVE DE FRAME DO CORPO (`fr`), nunca por posicao na lista de chaves.\n// Em 36.296 pares (item,acao) o item NAO tem todos os frames -- a peca some naquele frame porque\n// o corpo a esconde (swingOF/swingTF giram de costas: sem face; varias swings escondem a mao).\n// Indexar por posicao com modulo, como estava, desalinhava tudo: no frame 0 desenhava o frame 1,\n// e num frame em que a peca deveria SUMIR desenhava o sprite de outro frame na ancora daquele\n// outro frame -- e por isso a face/glove aparecia \"torta\" nessas acoes.\n// Ausente = nao desenha. E a mesma regra do engine local: `(geo[acao]||{})[frame]||[]`.\n// ItemEff: um grupo por elemento do efeito, cada um com ciclo proprio percorrido pelo relogio.\nfunction camadasEfItem(item,acao,fr,L){\n  const ei=item.ei;if(!ei||!ei.e||!ei.e.p)return;\n  const refs=(ei.e.q||{})[acao], anc=((ei.e.a||{})[acao]||{})[fr];\n  if(!refs||!anc)return;\n  for(let gi=0;gi<refs.length;gi++){\n    const grupo=ei.e.p[refs[gi]], A=anc[gi];\n    if(!grupo||!grupo.length||!A)continue;\n    let ciclo=0;for(const f of grupo)ciclo+=(f[3]||100);\n    let tt=ciclo?(TEMPO*EF_VEL)%ciclo:0,k=0;\n    for(let i=0;i<grupo.length;i++){tt-=(grupo[i][3]||100);if(tt<0){k=i;break;}}\n    const f=grupo[k],uv=ei.uv[f[0]];\n    // f = [png, ox, oy, delay, zi, w, noflip] -- o efeito tem a propria flag de noflip (2 itens)\n    if(uv)L.push({ref:item.eiRef,uv:uv,x:A[0]-f[1],y:A[1]-f[2],zi:f[4],dono:item.itemId,nfw:(f[6]?f[5]:0)});\n  }\n}\nfunction camadasItem(item,acao,fr,efeito,efItem,L,vskin,st49){\n  const dono=item.itemId;\n  // Os sprites do ItemEff foram empacotados DENTRO do atlas base do item (medido: todo item com\n  // bloco `e` tambem tem caminhos `Effect/` no `g`). Desenha-los como camada comum e o que fazia o\n  // efeito ficar sempre ligado e com a animacao errada. `ex` = os indices de uv que sao ItemEff;\n  // eles saem daqui SEMPRE, e o efeito volta pelo eixo proprio quando o toggle esta ligado.\n  const ex=item.eiX;\n  // hairShade por SKIN (2026-08-20): `s` mapeia indice-de-skin -> sprite no atlas do proprio item,\n  // e `s['0']` diz QUAL camada e a trocavel. A troca e por IDENTIDADE com esse indice, nunca por\n  // nome de arquivo -- mesma regra do engine local. Sem `s`, ou sem variante para a skin atual,\n  // o sprite e o de sempre; por isso os 17.255 cabelos sem variante nao pagam nada aqui.\n  const sv=item.s,alvo=sv?sv['0']:-1;\n  // STANCE 49 -- a pose em que a arma e segurada na OUTRA mao; as armas de cash usam para cobrir\n  // gun. `o` guarda so o diff POR ACAO, e acao ausente em `o` significa \"identica a `g`\": cair em\n  // `g` e o comportamento CERTO, nao uma falha. So arma declara `o` (1.829 nos blocos publicados).\n  // A troca e por ACAO INTEIRA, nunca merge frame a frame: medido, 46 pares (item,acao) existem em\n  // `o` e NAO em `g` -- com a stance ligada a arma desenha onde normalmente nao desenharia, e um\n  // merge por frame perderia exatamente esses.\n  const go=(st49&&item.geo.o&&item.geo.o[acao])?item.geo.o[acao]:(item.geo.g[acao]||{});\n  for(const c of (go[fr]||[])){\n    if(ex&&ex[c[0]])continue;\n    let si=c[0];\n    if(sv&&si===alvo&&sv[vskin]!=null)si=sv[vskin];\n    // 5o elemento = largura do sprite, presente SO em item `noflip` (medido: 1 item na base\n    // inteira). Cuidado: nas camadas da BASE o 5o elemento e a chave da parte, nao largura --\n    // por isso `nfw` so e lido aqui, nunca no laco da skin.\n    const uv=item.uv[si];if(uv)L.push({ref:item.atlasRef,uv:uv,x:c[1],y:c[2],zi:c[3],dono:dono,nfw:(c.length>4?c[4]:0)});}\n  if(efItem)camadasEfItem(item,acao,fr,L);\n  if(efeito&&item.geoEf&&item.atlasEfRef){\n    for(const c of ((item.geoEf.g[acao]||{})[fr]||[])){const uv=item.uvEf[c[0]];if(uv)L.push({ref:item.atlasEfRef,uv:uv,x:c[1],y:c[2],zi:c[3],dono:dono});}}\n}\nfunction compor(sk,equip,acao,frame,efeito,efItem,st49){\n  const gk=keys(sk.geo,acao);if(!gk.length)return{L:[],fr:'0',n:0};\n  const fr=gk[((frame%gk.length)+gk.length)%gk.length];\n  const esconder={};\n  for(const slot in equip){const it=ITENS[equip[slot]];if(!it)continue;const h=((it.geo.h||{})[acao]||{})[fr]||[];for(const k of h)esconder[k]=1;}\n  let L=[];\n  for(const c of (sk.geo[acao][fr]||[])){if(esconder[c[4]])continue;L.push({base:c[0],x:c[1],y:c[2],zi:c[3],dono:'base'});}\n  const vskin=String(parseInt(sk.skinId,10)-2000);\n  for(const slot in equip){const it=ITENS[equip[slot]];if(it)camadasItem(it,acao,fr,efeito,efItem,L,vskin,st49);}\n  // VSLOT -- a MESMA regra do engine local (preview.html): o chapeu declara os codigos de smap\n  // que ele OCUPA; toda camada de OUTRO item cujo zi mapeia para um desses codigos some. O proprio\n  // cap e a base sao isentos (a base ja veio resolvida pelo `h`). E so por isso que o cabelo troca\n  // de silhueta debaixo do chapeu -- nao existe sprite alternativo, existe camada escondida.\n  let ocultas=0;\n  const capId=equip.cap;\n  if(capId&&ZI2SMAP){\n    const v=(META[capId]&&META[capId].vs)||'';\n    if(v){\n      const occ=Object.create(null);\n      for(let i=0;i<v.length;i+=2)occ[v.slice(i,i+2)]=1;\n      const antes=L.length;\n      L=L.filter(c=>{\n        if(c.dono===capId||c.dono==='base')return true;\n        const cod=(c.zi>=0&&c.zi<ZI2SMAP.length)?ZI2SMAP[c.zi]:'';\n        return !cod||!occ[cod];\n      });\n      ocultas=antes-L.length;\n    }\n  }\n  L.sort((a,b)=>b.zi-a.zi);\n  return{L,fr,n:gk.length,ocultas};\n}\n\nexport default {\n  css:`\n  .wrap{--bg:#101014;--bg2:#15151b;--pan:#191920;--lin:#282833;--txt:#e8e8ee;--dim:#83838f;\n        --ac:#6c8cff;--ac2:#a86cff;--warn:#e3b341;--neon:#3dff9e;\n        height:100%;display:grid;grid-template-columns:1fr 372px;background:var(--bg);\n        color:var(--txt);font:13px/1.45 ui-sans-serif,system-ui,-apple-system,sans-serif;overflow:hidden}\n  .esq{display:grid;grid-template-rows:auto 1fr auto;overflow:hidden;min-width:0}\n\n  /* cabecalho ------------------------------------------------------------- */\n  .top{display:flex;align-items:center;gap:10px;padding:11px 16px;border-bottom:1px solid var(--lin);\n       background:linear-gradient(180deg,#1b1b23,#15151b)}\n  .mark{width:22px;height:22px;border-radius:6px;flex:none;\n        background:linear-gradient(135deg,var(--ac),var(--ac2));\n        box-shadow:0 0 0 1px #ffffff1a inset,0 2px 10px -2px var(--ac)}\n  .tit{font-weight:650;letter-spacing:.2px}\n  .tit small{display:block;font-weight:400;font-size:10px;color:var(--dim);letter-spacing:.5px;text-transform:uppercase}\n  .top .badge{margin-left:auto}\n\n  /* palco ----------------------------------------------------------------- */\n  .stage{position:relative;overflow:hidden;\n    background:\n      radial-gradient(120% 90% at 50% 34%,#2a2a36 0%,#17171e 55%,#0d0d11 100%),\n      repeating-conic-gradient(#ffffff05 0 25%,transparent 0 50%) 0 0/22px 22px}\n  .stage:after{content:\"\";position:absolute;inset:0;pointer-events:none;\n    box-shadow:inset 0 0 90px 12px #00000073}\n  /* O data-d2-static mora no WRAPPER, nao no canvas. Ele impede o morph de mexer nos FILHOS do\n     elemento marcado -- posto no proprio canvas nao protegia nada (canvas nao tem filhos) e o\n     morph reaplicava o elemento a cada re-render, resetando o backing store para o default\n     300x150 do browser. Era essa a piscada. Contrato em code-component-lifecycle-traps secao 10.\n     ATENCAO: nada de crase neste bloco -- ele inteiro e um template literal. */\n  .cvwrap{position:absolute;inset:0}\n  canvas.big{display:block;width:100%;height:100%;image-rendering:pixelated}\n\n  /* barra ----------------------------------------------------------------- */\n  /* equipados: dentro do palco, coluna a direita -- o painel lateral ficava comprido demais */\n  .eqbar{position:absolute;right:10px;top:10px;display:flex;flex-direction:column;gap:7px;z-index:2}\n  .eqi{position:relative;width:44px;height:44px;border-radius:9px;overflow:hidden;\n    background:linear-gradient(180deg,#fbfbfd,#e7e7ec);border:1px solid #ffffff26;\n    display:flex;align-items:center;justify-content:center;box-shadow:0 3px 10px -2px #000a}\n  .eqi .thc{transform:scale(.8);transform-origin:center}\n  .eqi .x{position:absolute;top:0;right:0;width:15px;height:15px;line-height:14px;text-align:center;\n    font:700 9px ui-sans-serif,system-ui;color:#fff;background:#e04a4a;border-bottom-left-radius:7px;\n    cursor:pointer;opacity:0;transition:opacity .1s}\n  .eqi:hover .x{opacity:1}\n  .eqi .semic{width:40px;height:40px;border:0}\n  .barra{border-top:1px solid var(--lin);padding:10px 16px;background:var(--pan);\n         display:flex;gap:7px;align-items:center;flex-wrap:wrap}\n  /* a regra generica de input da padding/borda/fundo -- o checkbox precisa desfazer isso */\n  .barra .chk{display:flex;align-items:center;gap:6px;cursor:pointer;user-select:none;white-space:nowrap;\n    font:600 12px ui-sans-serif,system-ui,sans-serif;color:var(--dim);transition:color .12s}\n  .barra .chk:hover{color:var(--txt)}\n  .barra .chk input{width:auto;padding:0;border:0;background:none;margin:0;accent-color:var(--ac);cursor:pointer}\n  /* play troca de glifo e randomize troca de rotulo: sem largura minima, a barra inteira desliza */\n  .barra .play{min-width:82px}\n  .barra .play1{min-width:78px}\n  .barra .rnd{min-width:122px}\n  .barra .flip{min-width:52px}\n  .barra .port{min-width:96px}\n  /* O src da imagem e 3,2 MB de data-URI. Ele NUNCA entra na string do render: o <img> mora num\n     data-d2-static e recebe o src imperativamente, senao o morph diffaria 3 MB a cada interacao. */\n  .portwrap{position:absolute;inset:0;z-index:6;display:none;align-items:center;justify-content:center;\n    background:#05060ae8}\n  .portwrap.on{display:flex}\n  .portbox{max-width:76%;max-height:76%;display:flex}\n  .portimg{max-width:100%;max-height:100%;border-radius:12px;box-shadow:0 18px 50px -12px #000}\n  .portx{position:absolute;top:12px;right:12px}\n  .sep{width:1px;height:20px;background:var(--lin);margin:0 3px}\n\n  /* scrollbar-gutter reserva a barra de rolagem SEMPRE. Sem isso, abrir uma categoria que faz o\n     painel crescer fazia a barra aparecer, a largura util encolher ~9px e a fileira de slots\n     re-quebrar -- era por isso que o off-hand pulava de linha ao selecionar hair. */\n  .side{border-left:1px solid var(--lin);padding:16px;overflow:auto;background:var(--bg2);min-width:0;\n    scrollbar-gutter:stable}\n  .side::-webkit-scrollbar{width:9px}\n  .side::-webkit-scrollbar-thumb{background:#33333f;border-radius:9px;border:2px solid var(--bg2)}\n  .side::-webkit-scrollbar-thumb:hover{background:#41414f}\n\n  h3{margin:0 0 9px;font-size:10px;text-transform:uppercase;letter-spacing:.9px;color:var(--dim);\n     display:flex;align-items:center;gap:8px}\n  h3:before{content:\"\";width:3px;height:11px;border-radius:2px;background:var(--ac);opacity:.85;flex:none}\n  h3 em{font-style:normal;color:#5e5e6b;letter-spacing:.4px}\n  .grp{margin-bottom:17px}\n\n  select,button,input{font:600 12px ui-sans-serif,system-ui,sans-serif;padding:6px 10px;border-radius:7px;\n    border:1px solid #34343f;background:linear-gradient(180deg,#2a2a34,#232430);color:var(--txt);\n    transition:background .12s,border-color .12s,box-shadow .12s}\n  button{cursor:pointer}\n  button:hover{border-color:#4a4a5a;background:linear-gradient(180deg,#33333f,#2a2a36)}\n  button:active{transform:translateY(1px)}\n  button:disabled{opacity:.38;cursor:default;transform:none}\n  select,input{width:100%;box-sizing:border-box}input{font-weight:400}\n  input:focus,select:focus{outline:none;border-color:var(--ac);box-shadow:0 0 0 3px #6c8cff2e}\n  button.on{background:linear-gradient(180deg,#6c8cff,#5474ea);border-color:#6c8cff;color:#fff;\n            box-shadow:0 2px 12px -4px var(--ac)}\n  button.on:hover{background:linear-gradient(180deg,#7b98ff,#5f7ef0)}\n  .rnd{background:linear-gradient(180deg,#3a2f5e,#2b2545);border-color:#4b3f79;color:#d9d2ff}\n  .rnd:hover{background:linear-gradient(180deg,#463878,#332b55);border-color:#5d4e96}\n\n  .acts{display:flex;flex-wrap:wrap;gap:4px}.acts button{padding:3px 7px;font-size:10px;border-radius:5px}\n  .catbar{display:flex;gap:5px;flex-wrap:wrap}.catbar button{padding:4px 10px;font-size:11px;text-transform:capitalize}\n  /* altura reservada para as 7 linhas: a ultima pagina (ou uma busca curta) nao sobe a paginacao */\n  .grade{display:grid;grid-template-columns:repeat(6,1fr);gap:6px;\n    grid-auto-rows:52px;min-height:400px;align-content:start}\n  .cel{background:linear-gradient(180deg,#fafafa,#e6e6ea);border:2px solid transparent;border-radius:8px;\n    height:52px;display:flex;align-items:center;justify-content:center;cursor:pointer;overflow:hidden;\n    padding:0;position:relative;box-shadow:0 1px 3px #0006;transition:transform .1s,box-shadow .1s,border-color .1s}\n  .cel:hover{border-color:#8aa4ff;transform:translateY(-1px);box-shadow:0 4px 12px -3px #000a}\n  .cel.on{border-color:var(--ac);box-shadow:0 0 0 3px #6c8cff40,0 4px 12px -3px #000a}\n  .cel img{max-width:46px;max-height:46px;image-rendering:pixelated}\n  .cel .thc{image-rendering:pixelated}\n  .badgec{position:absolute;bottom:2px;right:3px;font:9px ui-monospace,monospace;color:#2a2a33;\n    background:#ffffffd9;border-radius:4px;padding:0 3px}\n  .semic{display:inline-flex;align-items:center;justify-content:center;width:46px;height:46px;\n    border:1px dashed #b7b7c2;border-radius:5px;color:#7a7a86;font:10px ui-monospace,monospace}\n  .pag{display:flex;gap:7px;align-items:center;margin-top:9px}.pag button{padding:4px 10px}\n  /* o contador cresce de 1 para 966 e empurrava a seta da direita */\n  .pag .badge{min-width:74px;text-align:center}\n  .cores{display:flex;gap:5px;flex-wrap:wrap;margin-top:8px}\n  .cores button{width:27px;height:27px;padding:0;border-radius:7px;font-size:11px}\n  .equip{display:flex;flex-direction:column;gap:4px}\n  .equip .lin{display:flex;justify-content:space-between;align-items:center;gap:8px;\n    background:#22222c;border:1px solid #2e2e3a;border-radius:7px;padding:5px 9px}\n  .equip .lin b{font-weight:650;color:var(--ac);text-transform:capitalize;font-size:11px}\n  .equip .lin code{font:11px ui-monospace,monospace;color:var(--dim)}\n  .equip .x{cursor:pointer;color:#ff7b7b;font-weight:700;line-height:1;padding:0 2px}\n  .equip .x:hover{color:#ff5252}\n  .badge{font:11px ui-monospace,monospace;color:var(--dim)}\n  .boot{padding:26px;color:var(--dim)}.carregando{color:var(--warn)}\n\n  /* HUD do palco: frames, action e a coluna de equipados, ancorados no topo direito ------- */\n  .hud{position:absolute;right:10px;top:10px;display:flex;align-items:flex-start;gap:8px;z-index:3}\n  /* undo/redo: so o glifo. Precisa zerar fundo e borda tambem no :hover, senao a regra generica\n     de button la de cima devolve o gradiente e o contorno. */\n  .hist{position:absolute;right:12px;bottom:10px;display:flex;gap:1px;z-index:3}\n  .hist button{background:none;border:0;padding:1px 7px;color:var(--dim);font-size:18px;line-height:1;\n    opacity:.5;transition:opacity .12s,color .12s}\n  .hist button:hover:not(:disabled){background:none;border:0;opacity:1;color:var(--txt)}\n  .hist button:disabled{opacity:.14}\n  .hudbox{background:#12121aee;border:1px solid #2e2e3a;border-radius:10px;padding:7px 9px;\n    box-shadow:0 6px 20px -8px #000}\n  .hudbox h4{margin:0 0 6px;font:700 9px ui-sans-serif,system-ui;text-transform:uppercase;\n    letter-spacing:.8px;color:var(--dim)}\n  /* Larguras FIXAS no HUD. Antes cada caixa se ajustava ao conteudo: mudar de action trocava o\n     rotulo do botao, mudar de frame trocava a quantidade de botoes, e tudo deslizava. Agora cada\n     uma reserva a pior hipotese (5 frames, o rotulo mais longo) e nada mais anda. */\n  /* botao de frame com largura FIXA: 5 x 20 + 4 x 4 = 116 = o pior caso (shoot2, 5 frames).\n     O min-width vale para o CONTENT box (a caixa nao e border-box), entao e 116 e nao 136 --\n     somar o padding aqui era o que estava deixando a folga a direita do ultimo botao. */\n  .frbox{min-width:116px}\n  .frbox .acts{flex-wrap:nowrap}\n  .frbox .acts button{width:20px;padding:3px 0;text-align:center}\n  .acwrap{position:relative}\n  /* a seta fica PRESA na direita: label e seta sao dois filhos com space-between, em vez de a\n     seta ser texto colado no fim do rotulo e passear conforme o nome muda de tamanho */\n  .acbtn{font-size:11px;padding:4px 9px;width:106px;display:flex;align-items:center;\n    justify-content:space-between;gap:6px}\n  .acbtn .acnome{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n  .acbtn .acseta{flex:none;opacity:.55;font-size:9px}\n  .acpop{position:absolute;right:0;top:calc(100% + 6px);width:296px;max-height:330px;overflow:auto;\n    background:#12121af7;border:1px solid #2e2e3a;border-radius:10px;padding:10px 11px;\n    box-shadow:0 12px 30px -8px #000;z-index:5}\n  .acpop .gr{margin-bottom:10px}\n  .acpop .gr:last-child{margin-bottom:0}\n  .acpop h4{margin:0 0 5px}\n  /* a eqbar deixou de se posicionar sozinha: agora e um item do flex do HUD */\n  /* a coluna reserva a largura mesmo vazia, senao equipar a primeira peca empurrava o HUD inteiro */\n  .eqbar{position:static;display:flex;flex-direction:column;gap:7px;width:44px;flex:none}\n  .mestre.vazio{visibility:hidden;pointer-events:none}\n  .eqi .eqc{position:absolute;inset:0;background:transparent;border:0;padding:0;cursor:pointer;z-index:1}\n  /* A thumb tem fundo CLARO, entao icone branco em cima dela some. Quem cria o contraste e este\n     veu escuro: fraco no hover (previa) e forte quando o item esta oculto (estado). Deixei a\n     peca em grayscale em vez de quase transparente -- com o veu por cima da para continuar\n     reconhecendo QUAL item esta escondido, que era o que se perdia. */\n  .eqi:after{content:\"\";position:absolute;inset:0;z-index:1;pointer-events:none;\n    background:#05060a;opacity:0;transition:opacity .12s}\n  .eqi:hover:after{opacity:.5}\n  .eqi.off:after{opacity:.68}\n  .eqi.off:hover:after{opacity:.74}\n  .eqi.off .thc,.eqi.off .semic{filter:grayscale(1) brightness(.92)}\n  .eqbar.trmodo .eqi:hover:after{opacity:0}\n  /* O olho cortado aparece FANTASMA no hover (previa do que o clique faz) e SOLIDO quando o item\n     esta oculto (o estado). Mesmo icone nos dois papeis: o que muda e a opacidade, entao a previa\n     ja mostra exatamente onde a marca vai ficar. */\n  /* Centralizado por inset+margin:auto, NAO por left/top:50% com margem negativa. Sob o scale do\n     :active os 50% caem em pixel fracionario e o arredondamento muda de lado a cada quadro -- o\n     icone sambava. Assim o browser reparte a sobra simetricamente. */\n  .eqi .olho{position:absolute;inset:0;margin:auto;width:22px;height:22px;\n    z-index:2;pointer-events:none;opacity:0;transition:opacity .12s;\n    fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;\n    color:#f4f6f9;filter:drop-shadow(0 0 4px #000000c4)}\n  .eqi:hover .olho{opacity:.72}\n  .eqi.off .olho{opacity:1;filter:drop-shadow(0 0 6px #000000e0)}\n  /* confirmacao do clique: a thumb afunda por um instante. E o unico feedback que nao depende de\n     re-render, entao nao pisca de novo quando qualquer outro estado muda. */\n  /* will-change promove a thumb a uma CAMADA propria: o compositor escala o bitmap ja pronto, em\n     vez de o layout reposicionar cada filho e encaixar cada um num pixel de dispositivo inteiro.\n     Sem isto o icone sambava em zoom quebrado (110%, 125%) e ficava quieto em 100% e 150% --\n     exatamente onde o fator de escala cai em inteiro. Sao 14 thumbs, entao o custo de camada e\n     irrelevante. */\n  .eqi{transition:transform .08s;transform-origin:center;will-change:transform;\n    backface-visibility:hidden}\n  .eqi:active{transform:scale(.93)}\n  /* no modo cadeado o olho sai de cena: ali o clique tranca, nao oculta */\n  .eqbar.trmodo .eqi .olho{display:none}\n  .eqi .x,.eqi .cad,.eqi .trv{z-index:3}\n  /* No modo cadeado o estado e COR + FORMA: destravado = cadeado aberto quase branco; travado =\n     cadeado fechado em verde neon. O halo do aberto e ESCURO, nao branco: brilho branco atras de\n     icone branco nao separa nada -- quem cria a borda contra a thumb clara e a sombra. Nos dois\n     casos drop-shadow (e nao box-shadow), que segue o contorno do icone em vez da caixa. */\n  .eqi .cad{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;\n    background:#000000a6;cursor:pointer;color:#f4f6f9}\n  .eqi .cad .lk{filter:drop-shadow(0 0 4px #000000ad)}\n  .eqi .cad.tr{background:#001b0fc9;color:var(--neon)}\n  .eqi .cad.tr .lk{filter:drop-shadow(0 0 6px #3dff9e70)}\n  .eqi .trv{position:absolute;top:0;left:0;width:16px;height:16px;display:flex;align-items:center;\n    justify-content:center;background:#0b1f16e8;color:var(--neon);border-bottom-right-radius:7px}\n  .mestre{width:28px;height:28px;padding:0;flex:none;display:flex;align-items:center;\n    justify-content:center;color:var(--dim)}\n  /* O mestre NUNCA abre: ele nao representa travado/destravado, e sim \"estou no modo\". */\n  .mestre.on{color:var(--neon);border-color:var(--neon);background:linear-gradient(180deg,#123d2a,#0d2f20);\n    box-shadow:0 0 0 2px #3dff9e2e,0 2px 12px -4px var(--neon)}\n  .mestre.on:hover{border-color:var(--neon);background:linear-gradient(180deg,#17472f,#113626)}\n\n  /* Cadeado: SVG inline, tamanho por contexto e cor por currentColor.\n     ATENCAO: nada de crase neste bloco -- ele inteiro e um template literal. */\n  .lk{display:block;fill:currentColor}\n  .mestre .lk{width:17px;height:17px}\n  .eqi .cad .lk{width:21px;height:21px}\n  .eqi .trv .lk{width:11px;height:11px}\n  .cores button.sw{width:24px;height:24px;border-radius:6px;border:2px solid #34343f;padding:0}\n  .cores button.sw.on{border-color:#fff;box-shadow:0 0 0 2px var(--ac)}\n  `,\n\n  render(d2){\n    const s=d2.state;\n    if(s.erro)return `<div class=\"boot\">failed<br><span class=\"badge\" style=\"color:#ff8080\">${esc(s.erro)}</span></div>`;\n    if(!s.pronto)return `<div class=\"boot\">loading…<br><span class=\"badge\">${esc(s.etapa||'')}</span></div>`;\n    const skr=SKINPIX[(s.skins[s.iSkin]||{}).id]||{};\n    const acoes=ordenaAcoes(Object.keys(skr.geo||{}));\n    const acao=s.acao&&acoes.indexOf(s.acao)>=0?s.acao:acoes[0];\n    // quantos frames o CORPO tem nesta action -- a skin e a autoridade, nao o `frames` da tabela\n    const nfr=keys(skr.geo,acao).length||1;\n    const {lista,paginas,pag,fatia}=pagina(s);\n    // Na aba All nao existe UMA categoria: o seletor de cor passa a valer para o slot do ultimo\n    // item equipado (`corCat`), e o destaque da grade olha TODOS os equipados, nao um so.\n    const corCat=s.catAtual==='all'?(s.corCat||'hair'):s.catAtual;\n    const eqid=s.equip[corCat];\n    const estilo=((IDX[corCat]||{}).items||[]).find(r=>r.cores&&r.cores.some(cc=>cc[0]===eqid));\n    const EQIDS=Object.create(null);for(const k in s.equip)EQIDS[s.equip[k]]=1;\n    // miniatura do equipado: META[variante] aponta para a linha do ESTILO, que e quem tem o `ic`;\n    // as coords sao da folha da categoria daquele slot, por isso o sheet vem de IDX[slot].\n    const eqbar=ORDEM_EQ.filter(sl=>s.equip[sl]).map(sl=>{\n      const id=s.equip[sl],r=META[id],ix=IDX[sl];let th;\n      if(r&&r.ic&&ix){const[sh,x,y,w,h]=r.ic;\n        th=`<span class=\"thc\" style=\"display:inline-block;width:${w}px;height:${h}px;background-image:url(${esc(ix.sheets[sh])});background-position:-${x}px -${y}px\"></span>`;}\n      else th=`<span class=\"semic\">${esc(String(id).slice(-4))}</span>`;\n      const trav=!!s.travado[sl], oc=!!s.oculto[sl];\n      // No modo cadeado o centro da thumb tranca/destranca; fora dele, o centro OCULTA sem\n      // desequipar. Travado fora do modo vira so o cadeadinho no canto oposto ao X.\n      const cad=s.travaModo?`<span class=\"cad ${trav?'tr':''}\" data-slot=\"${esc(sl)}\">${cadeado(!trav)}</span>`:'';\n      const tv=(!s.travaModo&&trav)?`<span class=\"trv\" title=\"kept by randomize\">${cadeado(false)}</span>`:'';\n      const est=oc?' · hidden':(trav?' · locked':'');\n      // O X some no modo cadeado: ele nao e clicavel ali (o `.cad` esta por cima) e aparecer no\n      // hover atras do cadeado so confundia.\n      const bx=s.travaModo?'':`<span class=\"x\" data-slot=\"${esc(sl)}\">✕</span>`;\n      return `<div class=\"eqi ${oc?'off':''}\" title=\"${esc(ROTULO[sl]||sl)} · ${esc((r&&r.name)||'')} · ${esc(id)}${est}\">${th}${olho()}<button class=\"eqc\" data-slot=\"${esc(sl)}\"></button>${tv}${bx}${cad}</div>`;\n    }).join('');\n    const frbox=`<div class=\"hudbox frbox\"><h4>frame <span class=\"frameind\">–</span> / ${nfr-1}</h4>\n      <div class=\"acts frames\">${Array.from({length:nfr},(x,i)=>`<button class=\"fr\" data-f=\"${i}\">${i}</button>`).join('')}</div></div>`;\n    const acgr=GRUPOS.map(g=>{\n      const ks=acoes.filter(a=>grupoUI(a)===g);\n      if(!ks.length)return '';\n      return `<div class=\"gr\"><h4>${g}</h4><div class=\"acts\">${ks.map(a=>`<button class=\"ac ${a===acao?'on':''}\" data-a=\"${esc(a)}\" title=\"${esc(a)}\">${esc(rotuloAcao(a))}</button>`).join('')}</div></div>`;\n    }).join('');\n    const acbox=`<div class=\"hudbox acwrap\"><h4>action</h4>\n      <button class=\"acbtn\"><span class=\"acnome\">${esc(rotuloAcao(acao))}</span><span class=\"acseta\">▾</span></button>\n      ${s.acAberto?`<div class=\"acpop\">${acgr}</div>`:''}</div>`;\n    // O mestre e a coluna sao SEMPRE renderizados -- so ficam invisiveis quando nao ha nada\n    // equipado. Antes eles nao existiam no DOM, entao equipar a primeira peca empurrava o botao\n    // Action e a caixa de frames para a esquerda de uma vez.\n    const hud=`<div class=\"hud\">${frbox}${acbox}`+\n      `<button class=\"mestre ${s.travaModo?'on':''} ${eqbar?'':'vazio'}\" title=\"lock slots against randomize\">${cadeado(false)}</button>`+\n      `<div class=\"eqbar ${s.travaModo?'trmodo':''}\">${eqbar}</div></div>`;\n    return `<div class=\"wrap\">\n      <div class=\"esq\">\n        <div class=\"top\">\n          <span class=\"mark\"></span>\n          <span class=\"tit\">MapleCloset<small>character preview</small></span>\n          <span class=\"badge\">${esc(rotuloAcao(acao))} · ${Object.keys(s.equip).length} equipped</span>\n        </div>\n        <div class=\"stage\"><div class=\"cvwrap\" data-d2-static><canvas class=\"big\"></canvas></div>${hud}<div class=\"hist\">\n          <button class=\"und\" ${s.podeUnd?'':'disabled'} title=\"undo\">‹</button>\n          <button class=\"red\" ${s.podeRed?'':'disabled'} title=\"redo\">›</button>\n        </div>\n        <div class=\"portwrap ${s.verPortrait?'on':''}\">\n          <div class=\"portbox\" data-d2-static><img class=\"portimg\" alt=\"\"/></div>\n          <button class=\"portx\">close</button>\n        </div></div>\n        <div class=\"barra\">\n          <button class=\"play ${s.modo==='loop'?'on':''}\">${s.modo==='loop'?'❚❚':'▶'} loop</button>\n          <button class=\"play1 ${s.modo==='uma'?'on':''}\">▶| once</button>\n          <span class=\"sep\"></span>\n          <button class=\"zm\">−</button><button class=\"zp\">+</button><span class=\"badge\">${s.zoom}×</span>\n          <button class=\"flip ${s.flip?'on':''}\" title=\"espelha o personagem; os sprites do jogo olham para a esquerda por padrao\">flip</button>\n          <span class=\"sep\"></span>\n          <button class=\"ef ${s.efeito?'on':''}\">weapon fx</button>\n          <button class=\"efi ${s.efItem?'on':''}\">item fx</button>\n          <label class=\"chk\" title=\"stance 49: a arma desenhada como se estivesse na outra mao (as cash usam para cobrir gun)\"><input type=\"checkbox\" class=\"st49\" ${s.stance49?'checked':''}/><span>gun stance</span></label>\n          <span class=\"sep\"></span>\n          <button class=\"rnd\">${s.sorteando?'randomizing…':'⚄ randomize'}</button>\n          <button class=\"port\" ${s.gerando?'disabled':''}>${s.gerando?'generating…':'portrait'}</button>\n          ${s.carregando?'<span class=\"badge carregando\">loading…</span>':''}\n          ${s.diag?`<span class=\"badge\" style=\"color:var(--warn)\">${esc(s.diag)}</span>`:''}\n        </div>\n      </div>\n      <div class=\"side\">\n        <div class=\"grp\"><h3>skin tone</h3><select class=\"sk\">${s.skins.map((k,i)=>`<option value=\"${i}\" ${i===s.iSkin?'selected':''}>${esc(k.name)}</option>`).join('')}</select></div>\n        <div class=\"grp\"><h3>slot</h3><div class=\"catbar\">${['all'].concat(CATS).map(c=>`<button class=\"catsel ${c===s.catAtual?'on':''}\" data-c=\"${c}\">${ROTULO[c]||c}</button>`).join('')}</div></div>\n        <div class=\"grp\"><h3>${esc(ROTULO[s.catAtual]||s.catAtual)} <em>${lista.length}${s.q?' found':''}</em></h3>\n          <input class=\"busca\" placeholder=\"search by name or id…\" value=\"${esc(s.q||'')}\"/>\n          <div class=\"grade\" style=\"margin-top:8px\">\n            ${fatia.map(r=>{\n              const rid=r.id;\n              // Na aba All cada linha traz a folha de icone da PROPRIA categoria: o `ic[0]` indexa\n              // o iconSheets daquela cat, entao usar a folha errada desenharia outro item.\n              const rc=s.catAtual==='all'?(CATDE[rid]||''):s.catAtual;\n              const sheets=(IDX[rc]||{}).sheets||null;\n              const on=!!EQIDS[rid]||(r.cores&&r.cores.some(cc=>EQIDS[cc[0]]));\n              let mini='';\n              if(r.ic&&sheets){const[sh,x,y,w,h]=r.ic;\n                mini=`<span class=\"thc\" style=\"display:inline-block;width:${w}px;height:${h}px;background-image:url(${esc(sheets[sh])});background-position:-${x}px -${y}px\"></span>`;}\n              // 2 itens do dump inteiro nao tem icone em lugar nenhum (nem _Canvas, nem _outlink,\n              // nem inline). O canvas ao vivo nao serve mais: no caminho do idx o prefetch nao\n              // carrega geometria. Placeholder com o final do id, que ao menos identifica a celula.\n              else mini=`<span class=\"thc semic\">${esc(rid.slice(-4))}</span>`;\n              return `<button class=\"cel ${on?'on':''}\" data-id=\"${esc(rid)}\" data-cat=\"${esc(rc)}\" data-geo=\"${esc(r.geoRef||'')}\" data-ref=\"${esc(r.cores?r.cores[0][1]:'')}\" title=\"${esc(r.name)} · ${esc(rid)}\">${mini}</button>`;\n            }).join('')}\n          </div>\n          <div class=\"pag\"><button class=\"pp\" ${pag<=0?'disabled':''}>‹</button><span class=\"badge\">${pag+1} / ${paginas}</span><button class=\"np\" ${pag>=paginas-1?'disabled':''}>›</button></div>\n        </div>\n        ${(estilo&&estilo.cores&&estilo.cores.length>1)?`<div class=\"grp\"><h3>colour <em>${esc(estilo.name)}</em></h3><div class=\"cores\">${estilo.cores.map(cc=>{\n          const dg=Number(String(cc[0]).slice(-1));\n          const cor=PALETA[dg];\n          return `<button class=\"cor ${cor?'sw':''} ${cc[0]===eqid?'on':''}\" data-id=\"${esc(cc[0])}\" data-ref=\"${esc(cc[1])}\" data-geo=\"${esc(estilo.geoRef)}\" data-cat=\"${esc(corCat)}\" title=\"${esc(NOMECOR[dg]||('variant '+dg))}\" ${cor?`style=\"background:${cor}\"`:''}>${cor?'':dg}</button>`;\n        }).join('')}</div></div>`:''}\n        <div class=\"grp\"><h3>source</h3><div class=\"badge\">static index + packed icon sheets</div></div>\n      </div>\n    </div>`;\n  },\n\n  mounted(d2){\n    const s=d2.state;\n    s.pronto=false;s.iSkin=0;s.acao='stand1';s.frame=0;s.zoom=3;s.modo='parado';s.equip={};s.q='';s.pag=0;s.cat=[];s.carregando=false;s.efeito=false;s.efItem=true;s.stance49=false;s.flip=false;s._t0=0;s.catAtual='hair';s.sorteando=false;\n    // `oculto` = nao desenha mas continua equipado. `travado` = o randomize nao roleta esse slot.\n    // `travaModo` = o cadeado mestre esta aberto, mostrando o toggle no centro de cada thumb.\n    s.oculto={};s.travado={};s.travaModo=false;s.acAberto=false;s.corCat='hair';\n    s.podeUnd=false;s.podeRed=false;s.gerando=false;s.verPortrait=false;\n\n    // As skins SAIRAM da tabela `mcskinpix` (2026-08-20). Eram 46 x ~137 KB = 6,3 MB baixados\n    // inteiros no boot por `loadAll()` para exibir UMA. Agora `_skin` e um indice de 3,4 KB e\n    // cada skin e um arquivo do Drive buscado sob demanda e cacheado aqui.\n    async function carregaSkin(i){\n      const r=s.skins[i];if(!r)return false;\n      if(!SKINPIX[r.id])SKINPIX[r.id]=await (await fetch(MEDIA+r.ref)).json();\n      s.iSkin=i;return true;\n    }\n    function skinAtual(){return SKINPIX[(s.skins[s.iSkin]||{}).id]||{};}\n\n    // HISTORICO -- so o que muda o PERSONAGEM entra: `equip` e a skin. Oculto, trava, play, frame,\n    // zoom e categoria sao estado de ferramenta, nao de look, e ficam de fora de proposito.\n    // Um snapshot e o proprio objeto `equip`: todos os pontos que mexem nele ja TROCAM o objeto\n    // em vez de mutar, entao nao existe copia aqui -- sao ~14 ponteiros para strings que ja\n    // estavam na memoria (vieram do idx). Voltar tambem nunca busca rede: so da para voltar a um\n    // estado em que ja se esteve, e a geometria/atlas daquele estado ja esta em cache.\n    let HIST=[],POS=-1;\n    const MAXH=1000;\n    function sinaliza(){s.podeUnd=POS>0;s.podeRed=POS<HIST.length-1;}\n    // O UNICO ponto que empilha. Se alguma mudanca de equip/skin nao passar por aqui, ela some do\n    // historico em silencio -- e o jeito de nao esquecer e nunca escrever `s.equip=` fora daqui.\n    function marca(){\n      if(POS<HIST.length-1)HIST=HIST.slice(0,POS+1);   // acao nova apaga o futuro (o redo)\n      HIST.push({equip:s.equip,iSkin:s.iSkin});\n      if(HIST.length>MAXH)HIST.shift();\n      POS=HIST.length-1;sinaliza();\n    }\n    function defineEquip(novo){s.equip=novo;marca();}\n    // Aplica SEM empilhar. Se isto chamasse marca(), o undo empilharia a si mesmo e voce nunca\n    // sairia do lugar -- e o bug classico desta feature.\n    function aplicaHist(i){\n      const e=HIST[i];if(!e)return;\n      POS=i;s.equip=e.equip;\n      if(e.iSkin!==s.iSkin)carregaSkin(e.iSkin);\n      sinaliza();\n    }\n    function desfaz(){if(POS>0)aplicaHist(POS-1);}\n    function refaz(){if(POS<HIST.length-1)aplicaHist(POS+1);}\n\n    // carrega geometria+atlas de um item. geoRef = ref do bloco (novo) OU calcula pela chave (antigo).\n    async function carregaItem(id,geoRef,atlasRef,cat){\n      if(ITENS[id]&&ITENS[id].pronto&&(!EFI||!EFI[id]||ITENS[id].ei))return ITENS[id];\n      if(!geoRef)return null;\n      if(!BLOCO[geoRef]){const r=await fetch(MEDIA+geoRef);BLOCO[geoRef]=await r.json();}\n      const bl=BLOCO[geoRef]||{};\n      // popula todos do bloco (mesma geo); atlasRef vem por item\n      for(const iid in bl){if(!ITENS[iid]){const it=bl[iid];it.itemId=iid;ITENS[iid]=it;}}\n      // as armas \"so-efeito\" (todo o sprite mora no .effect.png) NAO tem atlas base: atlasRef\n      // fica null de proposito e elas somem quando o toggle de efeito esta desligado.\n      // ItemEff: bloco proprio (geoitemeff) + atlas proprio, carregados sob demanda.\n      const m=EFI&&EFI[id];\n      if(m&&ITENS[id]&&!ITENS[id].ei){\n        if(!BLOCOEI[m.b])BLOCOEI[m.b]=await (await fetch(MEDIA+m.b)).json();\n        const e=BLOCOEI[m.b][id];\n        if(e){ITENS[id].ei=e;ITENS[id].eiRef=m.a;\n          const x=Object.create(null);for(const i of (m.x||[]))x[i]=1;ITENS[id].eiX=x;}\n      }\n      if(ITENS[id]){ITENS[id].atlasRef=atlasRef||null;if(ITENS[id].geoEf)ITENS[id].atlasEfRef=(META[id]&&META[id].efRef)||null;ITENS[id].pronto=1;}\n      return ITENS[id]||null;\n    }\n    async function prefetch(){\n      // nada a fazer: a thumb e recorte CSS da folha, entao a grade nao precisa de geometria.\n      // Mantido como no-op para nao espalhar `if` nos call sites.\n    }\n    // Um carregador SO (o `garanteIdx` la de baixo). Antes esta funcao tinha a propria copia do\n    // fetch+META, e duas copias da mesma regra ja divergiram varias vezes neste projeto.\n    async function trocaCategoria(cat){\n      s.catAtual=cat;s.q='';s.pag=0;\n      if(cat==='all'){\n        // A aba All existe para a BUSCA achar qualquer item. Custa os 14 indices (~8,6 MB) na\n        // primeira vez -- o mesmo que o randomize ja paga -- e depois fica tudo em cache.\n        await Promise.all(CATS.map(garanteIdx));\n        let t=[];for(const c of CATS)t=t.concat((IDX[c]&&IDX[c].items)||[]);\n        s.cat=t;return;\n      }\n      await garanteIdx(cat);\n      s.cat=(IDX[cat]&&IDX[cat].items)||[];\n    }\n\n    // CONFLITO DE SLOT -- regra do JOGO, copiada do engine local (preview.html), nao inventada:\n    //  . outfit declara `vslot=MaPn`: ocupa coat E pants -> os tres nunca coexistem.\n    //  . arma com `islot=WpSi` ocupa a mao principal E a off-hand -> nao coexiste com shield.\n    // Resolver no nivel do SLOT (limpar o outro) e nao no render: e assim que o jogo faz, e\n    // desenhar os dois duplicaria o `mailArm` (medido no projeto local).\n    function aplicaConflito(e,cat){\n      for(const par of ((REGRAS&&REGRAS.exclusivos)||[])){\n        const a=par[0],bs=par[1];\n        if(cat===a){for(const b of bs)delete e[b];}\n        else if(bs.indexOf(cat)>=0)delete e[a];\n      }\n      const off=(REGRAS&&REGRAS.offhand)||'shield';\n      if(cat==='weapon'&&WPSI[e.weapon])delete e[off];\n      if(cat===off&&e[off]&&WPSI[e.weapon])delete e.weapon;\n      return e;\n    }\n    // Item SEM geometria: os 757 que nao desenham nada e mesmo assim EXISTEM -- todos declaram\n    // vslot. Servem para ocupar o slot e esconder (Bald Wig, Transparent Hat, as armas so-stat).\n    // `geoRef` e `atlasRef` sao null de proposito: nao ha bloco nem atlas para buscar. `compor`\n    // ja tolera ITENS[id] ausente, e o `vs` do vslot vem do META, que sai do idx.\n    function poeNoSlot(e,cat,id){\n      if(e[cat]===id){const x=Object.assign({},e);delete x[cat];return x;}\n      return aplicaConflito(Object.assign({},e,{[cat]:id}),cat);\n    }\n    async function equipar(id,geoRef,atlasRef,cat){\n      // Na aba All o slot vem da linha clicada (data-cat), nao da categoria aberta.\n      cat=cat||CATDE[id]||s.catAtual;\n      if(!cat||cat==='all'){s.diag='slot desconhecido para '+id;return;}\n      s.corCat=cat;\n      if(!geoRef){s.diag='';defineEquip(poeNoSlot(s.equip,cat,id));return;}\n      let it=null;\n      try{ it=await carregaItem(id,geoRef,atlasRef,cat); }\n      catch(err){ s.diag='failed: '+((err&&err.message)||err); return; }\n      if(!it){ s.diag='item not found: '+id; return; }\n      s.diag='';\n      img(it.atlasRef);\n      defineEquip(poeNoSlot(s.equip,cat,id));\n    }\n\n    // RANDOMIZE -- as mesmas regras do sorteio do engine local:\n    //  . face e hair SEMPRE entram (nao existe personagem sem rosto nem careca por ausencia de dado)\n    //  . OU um outfit, OU o par top+pants -- nunca os dois (mesma regra de slot do clique), e\n    //    nunca NENHUM dos dois: a cobertura e garantida, igual face e hair\n    //  . shield so quando a arma nao ocupa a off-hand\n    //  . os toggles de efeito NAO entram: sao switches de INSPECAO, nao pecas de equipamento --\n    //    sortear isso destruiria a comparacao que o usuario esta fazendo.\n    const um=a=>a&&a.length?a[Math.floor(Math.random()*a.length)]:null;\n    const talvez=(a,p)=>Math.random()<(p==null?0.5:p)?um(a):null;\n    // Sorteia a cor, carrega e equipa. Devolve se REALMENTE entrou -- `carregaItem` pode falhar, e\n    // sem esse retorno a garantia de cobertura viraria \"tentei\", que nao e garantia.\n    async function poe(eq,cat,r){\n      if(!r)return false;\n      const cor=um(r.cores)||[r.id,null];   // o equipado e uma VARIANTE, nao o estilo\n      if(!r.geoRef){eq[cat]=cor[0];return true;}   // item invisivel: equipa sem carregar\n      const it=await carregaItem(cor[0],r.geoRef,cor[1],cat);\n      if(!it)return false;\n      img(it.atlasRef);eq[cat]=cor[0];return true;\n    }\n    async function garanteIdx(cat){\n      if(IDX[cat]||!INDEXREF[cat])return;\n      const d=await (await fetch(MEDIA+INDEXREF[cat])).json();\n      const linhas=d.items||[];\n      // A lista navegavel e filtrada; o META/CATDE abaixo varre TODAS as linhas de proposito.\n      IDX[cat]={sheets:(d.iconSheets||[]).map(r=>MEDIA+r),items:linhas.filter(r=>!SEM_PREVIEW[r.id])};\n      if(d.zi2smap)ZI2SMAP=d.zi2smap;\n      for(const r of linhas){\n        META[r.id]=r;CATDE[r.id]=cat;\n        if(r.cores)for(const cc of r.cores){if(!META[cc[0]])META[cc[0]]=r;CATDE[cc[0]]=cat;}\n      }\n    }\n    async function sorteia(){\n      if(s.sorteando)return;\n      s.sorteando=true;\n      try{\n        await Promise.all(CATS.map(garanteIdx));\n        const pega=c=>(IDX[c]&&IDX[c].items)||[];\n        const escolha={};\n        for(const c of ['face','hair'])escolha[c]=um(pega(c));\n        if(Math.random()<0.4)escolha.outfit=um(pega('outfit'));\n        if(!escolha.outfit){escolha.coat=um(pega('coat'));escolha.pants=um(pega('pants'));}\n        for(const c of ['cap','cape','glove','shoes','accface','acceye','accear'])escolha[c]=talvez(pega(c));\n        escolha.weapon=talvez(pega('weapon'));\n        const eq={};\n        for(const c in escolha)await poe(eq,c,escolha[c]);\n        if(!WPSI[eq.weapon]){\n          const r=talvez(pega('offhand'));\n          if(r&&!r.geoRef)eq.offhand=r.id;\n          else if(r){const it=await carregaItem(r.id,r.geoRef,r.cores[0][1],'offhand');if(it){img(it.atlasRef);eq.offhand=r.id;}}\n        }\n        // Slot travado nao e roletado: o item atual fica, e ele vence o conflito de slot.\n        for(const c of ORDEM_EQ){\n          if(!s.travado[c]||!s.equip[c])continue;\n          eq[c]=s.equip[c];aplicaConflito(eq,c);\n        }\n        // COBERTURA: outfit OU (top + pants), nunca nu. Checada DEPOIS das travas de propósito --\n        // travar so o top e sortear um outfit deixaria sem calca, porque o conflito de slot remove\n        // o outfit e ninguem repoe a peca que faltou. O laco tenta de novo porque `poe` pode\n        // falhar ao carregar; sem o limite isso seria um while(true) num caso de rede ruim.\n        if(!eq.outfit){\n          for(let t=0;!eq.coat&&t<6;t++)await poe(eq,'coat',um(pega('coat')));\n          for(let t=0;!eq.pants&&t<6;t++)await poe(eq,'pants',um(pega('pants')));\n        }\n        // um randomize inteiro e UM passo de historico, nao um por slot\n        defineEquip(eq);\n      }catch(err){s.diag='randomize failed: '+((err&&err.message)||err);}\n      finally{s.sorteando=false;}\n    }\n\n    (async()=>{\n     try{\n      // `zmap` SAIU do boot (2026-08-20): era lido e nunca usado -- o `zi2smap` do vslot vem\n      // dentro do idxcap.json. Eram 2 leituras de tabela por abertura, uma delas inutil.\n      s.etapa='loading index';\n      const mi=await d2.rush('mcindexall').invoke({});\n      for(const r of ((mi&&mi.data&&mi.data.rows)||[]))INDEXREF[r.cat]=r.ref;\n      s.etapa='loading actions';\n      try{await carregaAcoes();}catch(err){d2.log('[mcbaseviewer] tabela de actions indisponivel, usando o passo padrao: '+((err&&err.message)||err));}\n      s.etapa='loading skins';\n      const li=await (await fetch(MEDIA+INDEXREF._skin)).json();\n      s.skins=(li.items||[]).slice().sort((a,b)=>String(a.id).localeCompare(String(b.id)));\n      await carregaSkin(0);\n      // mcgeoblocksall/mcatlasidxall SAIRAM do boot (2026-08-20): todo geoRef/atlasRef vem do idx.\n      if(INDEXREF._regras){\n        REGRAS=await (await fetch(MEDIA+INDEXREF._regras)).json();\n        for(const w of (REGRAS.wpsi||[]))WPSI[w]=1;\n      }\n      if(INDEXREF._itemeff)EFI=await (await fetch(MEDIA+INDEXREF._itemeff)).json();\n      await trocaCategoria(CATS[0]);\n      s.pronto=true;marca();await prefetch();\n     }catch(err){s.erro=(err&&err.message)?err.message:String(err);}\n    })();\n\n    // `desenhaThumbs` REMOVIDO (2026-08-20): desenhava thumb ao vivo em `canvas.th`, que nao\n    // existe mais desde que toda categoria passou a ter folha de icone. Ele rodava dentro do rAF,\n    // entao era uma varredura de DOM (`$$('canvas.th')`) por FRAME, 60x por segundo, sempre vazia.\n\n    let raf=null;\n    function draw(){\n      raf=requestAnimationFrame(draw);\n      if(!s.pronto)return;\n      const cv=d2.$('canvas.big');if(!cv)return;\n      const dpr=window.devicePixelRatio||1,W=cv.clientWidth,H=cv.clientHeight;\n      // A PISCADA vinha daqui. A cada re-render o palco passa por um quadro com clientWidth 0; a\n      // linha de baixo entao escrevia cv.width=0, e ESCREVER em cv.width destroi o bitmap. O\n      // quadro saia em branco e so o rAF seguinte redesenhava. O mcbuilder ja tinha esta guarda,\n      // o viewer nao -- comparar os dois foi o que apontou.\n      if(!W||!H)return;\n      // compara a ALTURA tambem: so a largura deixava o canvas com altura errada quando apenas\n      // ela mudava (redimensionar a janela na vertical).\n      if(cv.width!==Math.round(W*dpr)||cv.height!==Math.round(H*dpr)){\n        cv.width=Math.round(W*dpr);cv.height=Math.round(H*dpr);\n      }\n      const ctx=cv.getContext('2d');ctx.setTransform(dpr,0,0,dpr,0,0);ctx.imageSmoothingEnabled=false;ctx.clearRect(0,0,W,H);\n      const CX=Math.round(W/2), CY=Math.round(H*EIXO_Y);\n      // guias discretas + sombra de chao, so para dar profundidade ao palco\n      ctx.fillStyle='rgba(255,255,255,0.045)';ctx.fillRect(CX,0,1,H);ctx.fillRect(0,CY,W,1);\n      const rs=44*s.zoom;\n      const sg=ctx.createRadialGradient(CX,CY,0,CX,CY,rs);\n      sg.addColorStop(0,'rgba(0,0,0,0.42)');sg.addColorStop(1,'rgba(0,0,0,0)');\n      ctx.save();ctx.translate(CX,CY);ctx.scale(1,0.22);ctx.translate(-CX,-CY);\n      ctx.fillStyle=sg;ctx.beginPath();ctx.arc(CX,CY,rs,0,6.2832);ctx.fill();ctx.restore();\n      const sk=skinAtual();\n      const acoes=ordenaAcoes(Object.keys(sk.geo||{}));const acao=s.acao&&acoes.indexOf(s.acao)>=0?s.acao:acoes[0];\n      const nfr=keys(sk.geo,acao).length||1;\n      let frame=Math.min(Math.max(s.frame||0,0),nfr-1);\n      if(s.modo!=='parado'){\n        const r=frameNoTempo(acao,nfr,Date.now()-s._t0,s.modo);\n        frame=r.frame;TEMPO=Date.now()-s._t0;\n        // Play Once acabou: volta ao frame 0 e para.\n        if(r.terminou){s.modo='parado';s.frame=0;frame=0;TEMPO=0;}\n      }\n      // O indicador e o botao do frame ativo sao escritos DIRETO no DOM, nao por re-render: o\n      // frame muda ate ~6x/s e re-renderizar redesenharia a grade de 40 celulas junto. A guarda\n      // pelo texto tambem cura sozinha depois de um re-render (o span volta a '–').\n      const ind=d2.$('.frameind');\n      if(ind&&ind.textContent!==String(frame)){\n        ind.textContent=String(frame);\n        // Itera os FILHOS do container em vez de usar o seletor-multiplo: mais barato, e evita\n        // escrever o cifrao dobrado, que o patcher come como escape de replace.\n        const cx=d2.$('.frames');\n        if(cx)for(const b of cx.children)b.classList.toggle('on',Number(b.getAttribute('data-f'))===frame);\n      }\n      // OCULTAR: some com o slot ANTES de compor. Assim a peca some, o `h` dela para de esconder\n      // parte do corpo e o vslot dela para de valer -- ocultar o chapeu devolve o cabelo, que e o\n      // comportamento pedido. Uma linha em vez de tres regras espalhadas dentro de `compor`.\n      const eqv=Object.create(null);\n      for(const k in s.equip)if(!s.oculto[k])eqv[k]=s.equip[k];\n      const {L}=compor(sk,eqv,acao,frame,s.efeito,s.efItem,s.stance49===true);const z=s.zoom;\n      // FLIP: espelho puro do COMPOSTO em torno do eixo x=0 do mundo (CX). Nao mexe em zi nem em\n      // ancora -- e por isso ancoras, vslot e ItemEff viram juntos, de graca. O eixo NAO e o centro\n      // do bounding box. Os sprites do jogo olham para a ESQUERDA, entao ligado = virado a direita.\n      // As guias e a sombra ficam FORA do espelho de proposito (sao do palco, nao do personagem).\n      const vira=s.flip===true;\n      if(vira){ctx.save();ctx.translate(CX*2,0);ctx.scale(-1,1);}\n      for(const c of L){\n        if(c.base!==undefined){const im=imgBase(sk,c.base);if(!im||!im.complete||!im.naturalWidth)continue;ctx.drawImage(im,CX+c.x*z,CY+c.y*z,im.naturalWidth*z,im.naturalHeight*z);}\n        else{\n          const at=img(c.ref);if(!at||!at.complete||!at.naturalWidth)continue;const[u,v,w,h]=c.uv;\n          // `noflip`: a POSICAO espelha, o BITMAP nao. Sai do espelho e desenha no x refletido,\n          // que e a formula do contrato: translate(cx - (x+w)*zoom, cy + y*zoom) scale(zoom).\n          if(vira&&c.nfw){\n            ctx.save();ctx.setTransform(dpr,0,0,dpr,0,0);\n            ctx.drawImage(at,u,v,w,h,CX-(c.x+c.nfw)*z,CY+c.y*z,w*z,h*z);\n            ctx.restore();\n          }else ctx.drawImage(at,u,v,w,h,CX+c.x*z,CY+c.y*z,w*z,h*z);\n        }\n      }\n      if(vira)ctx.restore();\n    }\n    raf=requestAnimationFrame(draw);\n\n    d2.on('.sk','change',async (e,el)=>{s.carregando=true;try{await carregaSkin(Number(el.value));marca();}finally{s.carregando=false;}});\n    d2.on('.und','click',()=>{desfaz();});\n    d2.on('.red','click',()=>{refaz();});\n    // Ctrl+Z / Ctrl+Y (e Ctrl+Shift+Z). Dentro do builder do data2 o editor costuma capturar o\n    // atalho antes de chegar aqui; no app publicado funciona. Os botoes sao a via confiavel.\n    d2.on(window,'keydown',e=>{\n      if(!(e.ctrlKey||e.metaKey))return;\n      const alvo=e.target&&e.target.tagName;\n      if(alvo==='INPUT'||alvo==='TEXTAREA'||alvo==='SELECT')return;   // nao rouba o ctrl+z da busca\n      const k=String(e.key||'').toLowerCase();\n      if(k==='z'&&!e.shiftKey){e.preventDefault();desfaz();}\n      else if(k==='y'||(k==='z'&&e.shiftKey)){e.preventDefault();refaz();}\n    });\n    // trocar de action reinicia o relogio, senao a nova comeca no meio do ciclo da anterior\n    d2.on('.ac','click',(e,el)=>{s.acao=el.getAttribute('data-a');s.frame=0;s._t0=Date.now();TEMPO=0;s.acAberto=false;});\n    d2.on('.acbtn','click',()=>{s.acAberto=!s.acAberto;});\n    d2.on('.mestre','click',()=>{s.travaModo=!s.travaModo;});\n    // No modo cadeado o centro tranca; fora dele, oculta. O `.cad` fica por cima do `.eqc`, mas\n    // os dois fazem a mesma coisa no modo cadeado, entao qualquer um que pegue o clique serve.\n    d2.on('.eqc','click',(e,el)=>{\n      const sl=el.getAttribute('data-slot');\n      if(s.travaModo){const t=Object.assign({},s.travado);t[sl]=!t[sl];s.travado=t;}\n      else{const o=Object.assign({},s.oculto);o[sl]=!o[sl];s.oculto=o;}\n    });\n    d2.on('.cad','click',(e,el)=>{const sl=el.getAttribute('data-slot');const t=Object.assign({},s.travado);t[sl]=!t[sl];s.travado=t;});\n    d2.on('.fr','click',(e,el)=>{s.modo='parado';s.frame=Number(el.getAttribute('data-f'))||0;TEMPO=0;});\n    // So acende o \"loading\" quando ha o que baixar. Ligar/desligar o aviso gera DOIS re-renders,\n    // e navegar entre categorias ja em cache nao precisa de nenhum.\n    d2.on('.catsel','click',async (e,el)=>{\n      const c=el.getAttribute('data-c');\n      const emCache=c==='all'?CATS.every(x=>IDX[x]):!!IDX[c];\n      if(!emCache)s.carregando=true;\n      try{await trocaCategoria(c);await prefetch();}\n      finally{if(!emCache)s.carregando=false;}\n    });\n    d2.on('.cel','click',async (e,el)=>{await equipar(el.getAttribute('data-id'),el.getAttribute('data-geo')||null,el.getAttribute('data-ref')||null,el.getAttribute('data-cat')||null);});\n    d2.on('.cor','click',async (e,el)=>{const id=el.getAttribute('data-id');const ct=el.getAttribute('data-cat')||s.catAtual;const it=await carregaItem(id,el.getAttribute('data-geo')||null,el.getAttribute('data-ref')||null,ct);if(it){img(it.atlasRef);defineEquip(Object.assign({},s.equip,{[ct]:id}));}});\n    // desequipar tambem limpa o oculto/travado do slot, senao a marca fica pendurada no proximo\n    d2.on('.x','click',(e,el)=>{\n      const sl=el.getAttribute('data-slot');\n      const ee=Object.assign({},s.equip);delete ee[sl];defineEquip(ee);\n      const o=Object.assign({},s.oculto);delete o[sl];s.oculto=o;\n      const t=Object.assign({},s.travado);delete t[sl];s.travado=t;\n    });\n    let tb=null;\n    d2.on('.busca','input',(e,el)=>{s.q=el.value;s.pag=0;if(tb)clearTimeout(tb);tb=setTimeout(prefetch,400);});\n    d2.on('.pp','click',()=>{s.pag=Math.max(0,(s.pag||0)-1);prefetch();});\n    d2.on('.np','click',()=>{s.pag=(s.pag||0)+1;prefetch();});\n    d2.on('.zm','click',()=>{s.zoom=Math.max(1,s.zoom-1);});\n    d2.on('.zp','click',()=>{s.zoom=Math.min(8,s.zoom+1);});\n    d2.on('.ef','click',()=>{s.efeito=!s.efeito;});\n    d2.on('.efi','click',()=>{s.efItem=!s.efItem;});\n    d2.on('.st49','change',(e,el)=>{s.stance49=el.checked;});\n    d2.on('.flip','click',()=>{s.flip=!s.flip;});\n    // QUADRO FIXO -- nao enquadra pelo bbox de cada personagem. A base nao se mexe (medido: 45\n    // das 46 skins tem geometria identica em stand1/frame0; a 00002020 difere 1px na orelha),\n    // entao o quadro tambem nao pode se mexer: com bbox, um chapeu alto mudava o zoom e o corte\n    // de TODO o retrato, e dois personagens nao davam para comparar.\n    // Os numeros NAO sao escolhidos, sao ajustados as 6 referencias de estilo (UIAssets), medidas\n    // com o mesmo segmentador: queixo 80,4% +-2,3 / largura da cabeca 88,7% +-8,7 / eixo 49,6% +-2,3.\n    // Este quadro entrega queixo 80,9 / largura 87,3 / eixo 48,7 -- as tres dentro do desvio.\n    // Verificado em 6 personagens diferentes: o corpo cai nos MESMOS pixels (torso x 140..349).\n    // O quadro antigo (x-35 y-82 z8) dava largura 70,1%, abaixo de TODAS as referencias: o sprite\n    // ia enquadrado menor que o estilo alvo, e o modelo brigava com a propria referencia.\n    // Cortar cabelo alto e aceito (decisao do usuario) -- 3 das 6 refs tem cabelo na borda de cima.\n    // 512/51 = 10 exato: o zoom precisa ser INTEIRO, senao o nearest-neighbor borra.\n    const QX0=-28,QY0=-71,QLADO=51;\n    function exportaPersonagem(lado){\n      const sk=skinAtual();if(!sk||!sk.geo)return null;\n      // SEMPRE stand1/frame0 e sem flip: a referencia nao pode depender de onde a animacao\n      // estava, nem dos toggles de fx, senao dois retratos do mesmo personagem saem diferentes.\n      const acoes=Object.keys(sk.geo||{});\n      const acao=acoes.indexOf('stand1')>=0?'stand1':acoes[0];\n      const eqv=Object.create(null);\n      for(const k in s.equip)if(!s.oculto[k])eqv[k]=s.equip[k];\n      const {L}=compor(sk,eqv,acao,0,false,true,s.stance49===true);\n      const z=Math.max(1,Math.round(lado/QLADO));\n      const cv=document.createElement('canvas');cv.width=lado;cv.height=lado;\n      const g=cv.getContext('2d');\n      // Cinza neutro, nao branco e nao transparente: PNG com alpha costuma virar preto no gerador,\n      // e o branco competia com a pele e com o brilho do cabelo. As 6 refs NAO concordam entre si\n      // no fundo (3 claras, 3 escuras), entao fundo nao se aprende delas -- vem daqui.\n      g.fillStyle='#808080';g.fillRect(0,0,lado,lado);\n      g.imageSmoothingEnabled=false;\n      let faltou=0,pintou=0;\n      for(const c of L){\n        if(c.base!==undefined){\n          const im=imgBase(sk,c.base);\n          if(!im||!im.complete||!im.naturalWidth){faltou++;continue;}\n          g.drawImage(im,(c.x-QX0)*z,(c.y-QY0)*z,im.naturalWidth*z,im.naturalHeight*z);pintou++;\n        }else{\n          const at=img(c.ref);\n          if(!at||!at.complete||!at.naturalWidth){faltou++;continue;}\n          const u=c.uv[0],v=c.uv[1],w=c.uv[2],h=c.uv[3];\n          g.drawImage(at,u,v,w,h,(c.x-QX0)*z,(c.y-QY0)*z,w*z,h*z);pintou++;\n        }\n      }\n      if(!pintou)return null;\n      return {url:cv.toDataURL('image/png'),faltou:faltou};\n    }\n    function descrevePersonagem(){\n      const partes=[];\n      const sk=(s.skins[s.iSkin]||{}).name;\n      if(sk)partes.push('skin tone '+sk);\n      for(const sl of ORDEM_EQ){\n        const id=s.equip[sl];\n        if(!id||s.oculto[sl])continue;\n        const r=META[id];\n        partes.push((ROTULO[sl]||sl)+' '+((r&&r.name)||id));\n      }\n      return partes.length?partes.join(', ')+'.':'a plain character with no equipment.';\n    }\n    d2.on('.rnd','click',async()=>{await sorteia();});\n    d2.on('.portx','click',()=>{s.verPortrait=false;});\n    d2.on('.port','click',async()=>{\n      if(s.gerando)return;\n      s.gerando=true;s.diag='';\n      try{\n        const ref=exportaPersonagem(512);\n        if(!ref)throw new Error('nada desenhado ainda para usar de referencia');\n        if(ref.faltou)s.diag='portrait: '+ref.faltou+' camada(s) ainda carregando, a referencia saiu incompleta';\n        const p=PROMPT_BASE;\n        const st=d2.store(STORE_PORTRAIT);\n        if(st)st.set('prompt',p);\n        // Manda UMA imagem (o personagem, data URI do canvas). A referencia de ESTILO entra no\n        // handler, server-side, como SEGUNDA imagem: assim ela e trocavel sem republicar isto.\n        // O handler tambem grava a saida no Drive e devolve URL -- antes voltava data URI de 3 MB.\n        const r=await d2.rush(RUSH_PORTRAIT).invoke({prompt:p,imagem:ref.url,quantity:1});\n        const u=r&&r.data&&r.data.url;\n        if(!u)throw new Error('a resposta nao trouxe imagem');\n        if(st)st.set('url',u);\n        const im=d2.$('.portimg');if(im)im.src=u;\n        s.verPortrait=true;\n      }catch(err){s.diag='portrait: '+((err&&err.message)||err);}\n      finally{s.gerando=false;}\n    });\n    // o relogio do ItemEff zera junto com o do corpo, senao o efeito comeca no meio do ciclo\n    d2.on('.play','click',()=>{s.modo=s.modo==='loop'?'parado':'loop';s._t0=Date.now();TEMPO=0;});\n    d2.on('.play1','click',()=>{s.modo='uma';s._t0=Date.now();TEMPO=0;});\n    d2.onUnmount(()=>{if(raf)cancelAnimationFrame(raf);});\n  }\n};\n","at":"2026-08-28T22:41:33.764Z"}}}]}},{"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":[]}},{"reference":"ComponentInstancesTableDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[{"_id":"6a8387f65a7c75b8a2c3aeff","component":"data2-core-primitives@Artboard","system":"69b15e6093419d8151c50e4c","displayName":"MapleCloset Base","params":{"title":{"component":"data2-core-primitives@String","params":{"content":"MapleCloset Base"}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1000,"height":700}},"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0}}},"reference":"ISmxxzJxhsIf25Gs_cYXO","deleteMetadata":{}},{"_id":"a086f78485d00aa81cf66df6","parent":"6a8387f65a7c75b8a2c3aeff","component":"bigaton-playground@mcbaseviewer","system":"69b15e6093419d8151c50e4c","displayName":"Base Viewer","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":1000,"height":700}}}},"reference":"NyWfXj62hXDcdap_tOA5V","deleteMetadata":{}}]}},{"_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":"6a86583605157f68a8ddba69","displayName":"MapleCloset_App","reference":"nOvVqG0KnAOf4iUgzNU2W","params":{"domain":{"component":"data2-core-primitives@String","params":{"content":"mapleclosetdressroom"}},"entryPoint":{"params":{"component":"data2-core-primitives@Artboard","reference":"ISmxxzJxhsIf25Gs_cYXO"},"component":"data2-core-types@ComponentInstanceReference"}},"system":"69b15e6093419d8151c50e4c","component":"data2-core-primitives@Application"}]}},{"reference":"DataComponentInstancesTableDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[]}},{"reference":"BundlesTableDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[]}}],"processesParams":{}}}}