{"context":{"domain":"lucca-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":"6172b957976026c073ab9058","name":"Lucca`s Playground","domain":"lucca-playground","visibility":"public","settings":{"defaultAccessPolicy":"private"},"updatedAt":"2026-08-14T19:26:29.927Z"}]}},{"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":"6a73ee571204b3d36b63499e","component":"data2-core-types@Component","system":"6172b957976026c073ab9058","params":{"extends":"data2-core-primitives@Block","component":"pixeldoomEditor","package":"lucca-playground","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"const CELL = 32\nconst TOOLS = [\n  { id: '0', label: 'Vazio', sw: '#2f3542' },\n  { id: '1', label: 'Parede 1', sw: '#b33939' },\n  { id: '2', label: 'Parede 2', sw: '#4a69bd' },\n  { id: '3', label: 'Parede 3', sw: '#38ada9' },\n  { id: '4', label: 'Parede 4', sw: '#e58e26' },\n  { id: '5', label: 'Saída', sw: '#f6e58d' },\n  { id: 'player', label: 'Início Jogador', sw: '#00e6e6' },\n  { id: 'enemy', label: 'Inimigo', sw: '#e84118' },\n]\nconst SWATCH = { '0': '#2f3542', '1': '#b33939', '2': '#4a69bd', '3': '#38ada9', '4': '#e58e26', '5': '#f6e58d' }\n\nfunction esc(v) {\n  return String(v == null ? '' : v).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;')\n}\nfunction blankGrid(w, h) { return new Array(w * h).fill(0) }\nfunction resizeGrid(oldGrid, oldW, oldH, newW, newH) {\n  const g = new Array(newW * newH).fill(0)\n  const mh = Math.min(oldH, newH), mw = Math.min(oldW, newW)\n  for (let y = 0; y < mh; y++) {\n    for (let x = 0; x < mw; x++) g[y * newW + x] = oldGrid[y * oldW + x] || 0\n  }\n  return g\n}\nfunction drawGrid(cv, s, draftGrid) {\n  if (!cv || !s || !s.width) return\n  const w = s.width, h = s.height\n  const grid = draftGrid || s.grid || []\n  const dpr = window.devicePixelRatio || 1\n  const cssW = w * CELL, cssH = h * CELL\n  const needW = Math.round(cssW * dpr), needH = Math.round(cssH * dpr)\n  if (cv.width !== needW || cv.height !== needH) { cv.width = needW; cv.height = needH }\n  const ctx = cv.getContext('2d')\n  ctx.setTransform(dpr, 0, 0, dpr, 0, 0)\n  for (let y = 0; y < h; y++) {\n    for (let x = 0; x < w; x++) {\n      const v = grid[y * w + x] || 0\n      ctx.fillStyle = SWATCH[String(v)] || '#2f3542'\n      ctx.fillRect(x * CELL, y * CELL, CELL, CELL)\n      ctx.strokeStyle = 'rgba(0,0,0,0.25)'\n      ctx.strokeRect(x * CELL + 0.5, y * CELL + 0.5, CELL - 1, CELL - 1)\n    }\n  }\n  const things = s.things || []\n  ctx.font = `${Math.floor(CELL * 0.7)}px sans-serif`\n  ctx.textAlign = 'center'\n  ctx.textBaseline = 'middle'\n  things.forEach((t) => { if (t.type === 'enemy') ctx.fillText('👹', t.x * CELL, t.y * CELL) })\n  const ps = s.playerStart || { x: 1.5, y: 1.5, angle: 0 }\n  ctx.save()\n  ctx.translate(ps.x * CELL, ps.y * CELL)\n  ctx.rotate(ps.angle || 0)\n  ctx.fillStyle = '#00e6e6'\n  ctx.beginPath(); ctx.moveTo(12, 0); ctx.lineTo(-8, 8); ctx.lineTo(-8, -8); ctx.closePath(); ctx.fill()\n  ctx.restore()\n}\n\nexport default {\n  css: `\n    .app{position:absolute;inset:0;display:flex;font-family:'Courier New',monospace;background:#0d0f14;color:#e8e8ee;overflow:hidden}\n    .sidebar{width:300px;flex:0 0 300px;background:#161822;border-right:1px solid #2a2e3d;padding:16px;box-sizing:border-box;overflow-y:auto;display:flex;flex-direction:column;gap:12px}\n    .sidebar h1{font-size:15px;letter-spacing:2px;margin:0 0 4px;color:#f6e58d;text-transform:uppercase}\n    .field{display:flex;flex-direction:column;gap:4px;font-size:11px;color:#9aa0b4}\n    .field input{background:#0d0f14;border:1px solid #2a2e3d;color:#e8e8ee;padding:6px 8px;border-radius:4px;font-family:inherit;box-sizing:border-box;width:100%}\n    .row2{display:flex;gap:8px}\n    .row2 .field{flex:1}\n    .toolgrid{display:grid;grid-template-columns:1fr 1fr;gap:6px}\n    .toolbtn{display:flex;align-items:center;gap:6px;background:#1d2030;border:1px solid #2a2e3d;color:#cfd2e0;padding:6px 8px;border-radius:4px;cursor:pointer;font-size:11px;font-family:inherit}\n    .toolbtn .sw{width:14px;height:14px;border-radius:3px;background:var(--c);flex:0 0 14px;display:inline-block}\n    .toolbtn.active{border-color:#f6e58d;background:#2a2410;color:#f6e58d}\n    .actions{display:flex;flex-direction:column;gap:6px;margin-top:4px}\n    .btn{background:#2a2e3d;border:1px solid #3a3f52;color:#e8e8ee;padding:8px 10px;border-radius:4px;cursor:pointer;font-family:inherit;font-size:12px}\n    .btn.primary{background:#f6e58d;color:#161822;font-weight:700;border-color:#f6e58d}\n    .msg{font-size:11px;color:#78e08f;min-height:14px}\n    .levelList{display:flex;flex-direction:column;gap:4px;font-size:11px;border-top:1px solid #2a2e3d;padding-top:10px}\n    .levelList label{color:#9aa0b4;text-transform:uppercase;font-size:10px;letter-spacing:1px}\n    .levelItem{background:#1d2030;border:1px solid #2a2e3d;border-radius:4px;padding:6px 8px;cursor:pointer}\n    .levelItem.active{border-color:#00e6e6;color:#00e6e6}\n    .canvasArea{flex:1;overflow:auto;padding:24px;box-sizing:border-box}\n    .canvasWrap{position:relative;box-shadow:0 0 0 2px #2a2e3d}\n    .grid{display:block;width:100%;height:100%;cursor:crosshair}\n  `,\n  render(d2) {\n    const s = d2.state\n    const width = s.width || 12\n    const height = s.height || 12\n    const tool = s.tool || '1'\n    const name = s.name != null ? s.name : 'Novo Nível'\n    const msg = s.msg || ''\n    const levelId = s.levelId || null\n    const ds = d2.data('levels')\n    const rows = ds && !ds.loading ? ds.records() : []\n    const listHtml = rows.length\n      ? rows.map((r) => `<div class=\"levelItem ${r._id === levelId ? 'active' : ''}\" data-key=\"${r._id}\" data-id=\"${r._id}\">${esc(r.name)} (${r.width}x${r.height})</div>`).join('')\n      : `<div style=\"color:#666\">Nenhum nível salvo ainda.</div>`\n    const wrapW = width * CELL, wrapH = height * CELL\n    return `<div class=\"app\">\n      <div class=\"sidebar\">\n        <h1>PixelDoom · Editor</h1>\n        <div class=\"field\"><label>Nome do nível</label><input class=\"nameIn\" value=\"${esc(name)}\"/></div>\n        <div class=\"row2\">\n          <div class=\"field\"><label>Largura</label><input class=\"widthIn\" type=\"number\" min=\"4\" max=\"40\" value=\"${width}\"/></div>\n          <div class=\"field\"><label>Altura</label><input class=\"heightIn\" type=\"number\" min=\"4\" max=\"40\" value=\"${height}\"/></div>\n        </div>\n        <div class=\"field\"><label>Ferramentas</label>\n          <div class=\"toolgrid\">\n            ${TOOLS.map((t) => `<button class=\"toolbtn ${tool === t.id ? 'active' : ''}\" data-tool=\"${t.id}\" style=\"--c:${t.sw}\"><span class=\"sw\"></span>${t.label}</button>`).join('')}\n          </div>\n        </div>\n        <div class=\"actions\">\n          <button class=\"btn newBtn\">＋ Novo Nível</button>\n          <button class=\"btn saveBtn\">💾 Salvar</button>\n          <button class=\"btn primary playBtn\">▶ Jogar</button>\n        </div>\n        <div class=\"msg\">${esc(msg)}</div>\n        <div class=\"levelList\">\n          <label>Níveis salvos</label>\n          ${listHtml}\n        </div>\n      </div>\n      <div class=\"canvasArea\">\n        <div class=\"canvasWrap\" style=\"width:${wrapW}px;height:${wrapH}px\">\n          <canvas class=\"grid\" data-d2-static></canvas>\n        </div>\n      </div>\n    </div>`\n  },\n  mounted(d2) {\n    if (d2.state.width === undefined) {\n      d2.setState({\n        levelId: null, name: 'Novo Nível', width: 12, height: 12,\n        grid: blankGrid(12, 12), things: [], playerStart: { x: 1.5, y: 1.5, angle: 0 },\n        tool: '1', msg: 'Escolha uma ferramenta e desenhe o nível.',\n      })\n    }\n\n    let painting = false\n    let draft = null\n\n    const cellFromEvent = (e, cv) => {\n      const rect = cv.getBoundingClientRect()\n      const w = d2.state.width, h = d2.state.height\n      const cx = Math.floor(((e.clientX - rect.left) / rect.width) * w)\n      const cy = Math.floor(((e.clientY - rect.top) / rect.height) * h)\n      return { cx: Math.max(0, Math.min(w - 1, cx)), cy: Math.max(0, Math.min(h - 1, cy)) }\n    }\n\n    const applyTool = (cx, cy, gridRef) => {\n      const s = d2.state\n      const idx = cy * s.width + cx\n      if (s.tool === 'player') {\n        s.playerStart = { x: cx + 0.5, y: cy + 0.5, angle: (s.playerStart && s.playerStart.angle) || 0 }\n        return null\n      }\n      if (s.tool === 'enemy') {\n        const things = (s.things || []).slice()\n        const at = things.findIndex((t) => t.type === 'enemy' && Math.floor(t.x) === cx && Math.floor(t.y) === cy)\n        if (at >= 0) things.splice(at, 1)\n        else things.push({ x: cx + 0.5, y: cy + 0.5, type: 'enemy' })\n        s.things = things\n        return null\n      }\n      const val = Number(s.tool)\n      const g = gridRef.slice()\n      if (val === 5) { for (let i = 0; i < g.length; i++) if (g[i] === 5) g[i] = 0 }\n      g[idx] = val\n      return g\n    }\n\n    d2.on('.grid', 'mousedown', (e, cv) => {\n      const s = d2.state\n      if (s.tool === 'player' || s.tool === 'enemy') {\n        const { cx, cy } = cellFromEvent(e, cv)\n        applyTool(cx, cy, s.grid)\n        drawGrid(cv, d2.state, null)\n        return\n      }\n      painting = true\n      draft = s.grid.slice()\n      const { cx, cy } = cellFromEvent(e, cv)\n      const g2 = applyTool(cx, cy, draft)\n      if (g2) draft = g2\n      drawGrid(cv, d2.state, draft)\n    })\n    d2.on(window, 'mousemove', (e) => {\n      if (!painting) return\n      const cv = d2.$('.grid'); if (!cv) return\n      const { cx, cy } = cellFromEvent(e, cv)\n      const g2 = applyTool(cx, cy, draft)\n      if (g2) draft = g2\n      drawGrid(cv, d2.state, draft)\n    })\n    d2.on(window, 'mouseup', () => {\n      if (painting && draft) d2.state.grid = draft\n      painting = false\n      draft = null\n    })\n\n    d2.on('.toolbtn', 'click', (e, el) => { d2.state.tool = el.dataset.tool })\n    d2.on('.nameIn', 'input', (e, el) => { d2.state.name = el.value })\n    d2.on('.widthIn', 'change', (e, el) => {\n      const nw = Math.max(4, Math.min(40, parseInt(el.value, 10) || 12))\n      const s = d2.state\n      const ng = resizeGrid(s.grid, s.width, s.height, nw, s.height)\n      d2.setState({ grid: ng, width: nw })\n    })\n    d2.on('.heightIn', 'change', (e, el) => {\n      const nh = Math.max(4, Math.min(40, parseInt(el.value, 10) || 12))\n      const s = d2.state\n      const ng = resizeGrid(s.grid, s.width, s.height, s.width, nh)\n      d2.setState({ grid: ng, height: nh })\n    })\n\n    d2.on('.newBtn', 'click', () => {\n      d2.setState({\n        levelId: null, name: 'Novo Nível', width: 12, height: 12,\n        grid: blankGrid(12, 12), things: [], playerStart: { x: 1.5, y: 1.5, angle: 0 },\n        tool: '1', msg: 'Novo nível — comece a desenhar as paredes.',\n      })\n    })\n\n    d2.on('.levelItem', 'click', (e, el) => {\n      const ds = d2.data('levels')\n      const rec = (ds.records() || []).find((r) => r._id === el.dataset.id)\n      if (!rec) return\n      d2.setState({\n        levelId: rec._id, name: rec.name, width: rec.width, height: rec.height,\n        grid: (rec.grid || []).slice(), things: (rec.things || []).map((t) => ({ ...t })),\n        playerStart: rec.playerStart ? { ...rec.playerStart } : { x: 1.5, y: 1.5, angle: 0 },\n        msg: `Carregado: ${rec.name}`,\n      })\n    })\n\n    const doSave = async () => {\n      const s = d2.state\n      const ds = d2.data('levels')\n      const payload = {\n        name: s.name || 'Nível sem nome', width: s.width, height: s.height,\n        grid: s.grid, things: s.things || [], playerStart: s.playerStart || { x: 1.5, y: 1.5, angle: 0 },\n        updatedAt: new Date().toISOString(),\n      }\n      d2.state.msg = 'Salvando…'\n      try {\n        if (s.levelId) {\n          await ds.update({ _id: s.levelId, ...payload })\n        } else {\n          const rec = await ds.create(payload)\n          let newId = (rec && rec._id) || (rec && rec.data && rec.data._id) || null\n          if (!newId) {\n            await ds.refresh()\n            const all = (ds.records() || []).slice().sort((a, b) => (a._id < b._id ? 1 : -1))\n            newId = all[0] && all[0]._id\n          }\n          d2.state.levelId = newId\n        }\n        d2.state.msg = 'Salvo!'\n      } catch (err) {\n        d2.state.msg = 'Erro ao salvar.'\n      }\n    }\n    d2.on('.saveBtn', 'click', () => { doSave() })\n    d2.on('.playBtn', 'click', async () => {\n      await doSave()\n      const store = d2.store('gameStore')\n      if (store) store.set('selectedLevelId', d2.state.levelId)\n      window.location.href = d2.param('playUrl', '/')\n    })\n  },\n  updated(d2) {\n    const cv = d2.$('.grid')\n    if (cv) drawGrid(cv, d2.state, null)\n  },\n}\n"}},"levels":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"levels"}}}},"gameStore":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"gameStore"}}}},"playUrl":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"playUrl"}}}}}}}}}},{"_id":"6a73ee81d5c61d2c1c9a94e7","component":"data2-core-types@Component","system":"6172b957976026c073ab9058","params":{"extends":"data2-core-primitives@Block","component":"pixeldoomPlay","package":"lucca-playground","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"const WALL_COLORS = {\n  1: [179, 57, 57],\n  2: [74, 105, 189],\n  3: [56, 173, 169],\n  4: [229, 142, 38],\n}\nconst FLOOR_COLOR = [26, 26, 26]\nconst SKY_COLOR = [40, 46, 66]\nconst RENDER_W = 320\nconst RENDER_H = 200\nconst FOV = (66 * Math.PI) / 180\nconst MINI = 160\n\nfunction isWallCell(v) { return v >= 1 && v <= 4 }\nfunction clamp(v, a, b) { return Math.max(a, Math.min(b, v)) }\n\nexport default {\n  css: `\n    .app{position:absolute;inset:0;background:#000;overflow:hidden;font-family:'Courier New',monospace;color:#e8e8ee}\n    .scene{position:absolute;inset:0;width:100%;height:100%;image-rendering:pixelated;cursor:crosshair}\n    .mini{position:absolute;top:16px;right:16px;width:160px;height:160px;border:2px solid #444;background:#0d0f14;opacity:0.92}\n    .hud{position:absolute;top:16px;left:16px;display:flex;flex-direction:column;gap:4px;pointer-events:none;text-shadow:0 1px 3px #000}\n    .hudName{font-size:14px;letter-spacing:2px;color:#f6e58d;text-transform:uppercase}\n    .hudKills{font-size:13px;color:#78e08f}\n    .crosshair{position:absolute;left:50%;top:50%;width:14px;height:14px;margin:-7px;pointer-events:none}\n    .crosshair::before,.crosshair::after{content:'';position:absolute;background:#fff;opacity:.85}\n    .crosshair::before{left:6px;top:0;width:2px;height:14px}\n    .crosshair::after{left:0;top:6px;width:14px;height:2px}\n    .backBtn{position:absolute;bottom:16px;left:16px;background:#1d2030;border:1px solid #3a3f52;color:#e8e8ee;padding:8px 14px;border-radius:4px;cursor:pointer;font-family:inherit;font-size:12px;z-index:5}\n    .loading{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:#0d0f14;color:#9aa0b4;font-size:14px;letter-spacing:2px;z-index:9}\n    .loading.hide{display:none}\n    .victory{position:absolute;inset:0;display:none;align-items:center;justify-content:center;flex-direction:column;gap:16px;background:rgba(10,12,18,0.9);z-index:10}\n    .victory.show{display:flex}\n    .victory h2{font-size:32px;color:#f6e58d;letter-spacing:4px;margin:0}\n    .victory button{background:#f6e58d;color:#161822;font-weight:700;border:0;padding:10px 20px;border-radius:4px;cursor:pointer;font-family:inherit;font-size:13px}\n    .hint{position:absolute;bottom:16px;right:16px;font-size:10px;color:#666;text-align:right;pointer-events:none}\n  `,\n  render() {\n    return `<div class=\"app\">\n      <canvas class=\"scene\" data-d2-static></canvas>\n      <canvas class=\"mini\" data-d2-static></canvas>\n      <div class=\"crosshair\" data-d2-static></div>\n      <div class=\"hud\">\n        <div class=\"hudName\">Carregando…</div>\n        <div class=\"hudKills\">Abates: 0/0</div>\n      </div>\n      <div class=\"hint\">WASD/Setas move · Q/E strafe · Espaço/Click atira</div>\n      <button class=\"backBtn\">← Voltar ao editor</button>\n      <div class=\"loading\">Carregando nível…</div>\n      <div class=\"victory\">\n        <h2>NÍVEL COMPLETO</h2>\n        <button class=\"victoryBtn\">Voltar ao editor</button>\n      </div>\n    </div>`\n  },\n  mounted(d2) {\n    const keys = {}\n    let level = null\n    let player = { x: 1.5, y: 1.5, angle: 0 }\n    let things = []\n    let kills = 0\n    let totalEnemies = 0\n    let gameWon = false\n    let ready = false\n    let lastT = null\n    const depthBuffer = new Array(RENDER_W).fill(1e9)\n\n    const goEditor = () => { window.location.href = d2.param('editorUrl', '/') }\n    d2.on('.backBtn', 'click', goEditor)\n    d2.on('.victoryBtn', 'click', goEditor)\n\n    function cellAt(gx, gy) {\n      if (gx < 0 || gy < 0 || gx >= level.width || gy >= level.height) return 1\n      return level.grid[gy * level.width + gx] || 0\n    }\n    function canStand(x, y) { return !isWallCell(cellAt(Math.floor(x), Math.floor(y))) }\n\n    function castRay(px, py, angle) {\n      const dirX = Math.cos(angle), dirY = Math.sin(angle)\n      let mapX = Math.floor(px), mapY = Math.floor(py)\n      const deltaDistX = dirX === 0 ? 1e30 : Math.abs(1 / dirX)\n      const deltaDistY = dirY === 0 ? 1e30 : Math.abs(1 / dirY)\n      let stepX, stepY, sideDistX, sideDistY\n      if (dirX < 0) { stepX = -1; sideDistX = (px - mapX) * deltaDistX } else { stepX = 1; sideDistX = (mapX + 1 - px) * deltaDistX }\n      if (dirY < 0) { stepY = -1; sideDistY = (py - mapY) * deltaDistY } else { stepY = 1; sideDistY = (mapY + 1 - py) * deltaDistY }\n      let side = 0, cellVal = 0\n      for (let i = 0; i < 64; i++) {\n        if (sideDistX < sideDistY) { sideDistX += deltaDistX; mapX += stepX; side = 0 } else { sideDistY += deltaDistY; mapY += stepY; side = 1 }\n        cellVal = cellAt(mapX, mapY)\n        if (isWallCell(cellVal)) break\n      }\n      let dist\n      if (side === 0) dist = (mapX - px + (1 - stepX) / 2) / (dirX || 1e-6)\n      else dist = (mapY - py + (1 - stepY) / 2) / (dirY || 1e-6)\n      return { dist: Math.max(dist, 0.0001), side, cellVal }\n    }\n\n    function shadeRGB(rgb, mult, fogT) {\n      const r = rgb[0] * mult, g = rgb[1] * mult, b = rgb[2] * mult\n      return [\n        r + (FLOOR_COLOR[0] - r) * fogT,\n        g + (FLOOR_COLOR[1] - g) * fogT,\n        b + (FLOOR_COLOR[2] - b) * fogT,\n      ]\n    }\n\n    function shoot() {\n      if (!ready || gameWon) return\n      const wallDist = castRay(player.x, player.y, player.angle).dist\n      let best = null, bestAngle = 999\n      for (const e of things) {\n        if (e.type !== 'enemy' || !e.alive) continue\n        const ex = e.x - player.x, ey = e.y - player.y\n        const dist = Math.hypot(ex, ey)\n        let ang = Math.atan2(ey, ex) - player.angle\n        while (ang > Math.PI) ang -= 2 * Math.PI\n        while (ang < -Math.PI) ang += 2 * Math.PI\n        if (Math.abs(ang) < 0.14 && dist < wallDist + 0.6 && Math.abs(ang) < bestAngle) { bestAngle = Math.abs(ang); best = e }\n      }\n      if (best) { best.alive = false; kills += 1 }\n    }\n    d2.on('.scene', 'click', shoot)\n    d2.on(window, 'keydown', (e) => {\n      const c = e.code\n      if (c === 'KeyW' || c === 'ArrowUp') keys.forward = true\n      else if (c === 'KeyS' || c === 'ArrowDown') keys.back = true\n      else if (c === 'KeyA' || c === 'ArrowLeft') keys.rotLeft = true\n      else if (c === 'KeyD' || c === 'ArrowRight') keys.rotRight = true\n      else if (c === 'KeyQ') keys.strafeLeft = true\n      else if (c === 'KeyE') keys.strafeRight = true\n      else if (c === 'Space') { e.preventDefault(); shoot() }\n    })\n    d2.on(window, 'keyup', (e) => {\n      const c = e.code\n      if (c === 'KeyW' || c === 'ArrowUp') keys.forward = false\n      else if (c === 'KeyS' || c === 'ArrowDown') keys.back = false\n      else if (c === 'KeyA' || c === 'ArrowLeft') keys.rotLeft = false\n      else if (c === 'KeyD' || c === 'ArrowRight') keys.rotRight = false\n      else if (c === 'KeyQ') keys.strafeLeft = false\n      else if (c === 'KeyE') keys.strafeRight = false\n    })\n\n    async function loadLevel() {\n      const store = d2.store('gameStore')\n      const ds = d2.data('levels')\n      await ds.refresh()\n      const selId = store ? store.get('selectedLevelId') : null\n      const rows = ds.records() || []\n      let rec = selId ? rows.find((r) => r._id === selId) : null\n      if (!rec) rec = rows[0]\n      if (!rec) {\n        const nameEl = d2.$('.hudName'); if (nameEl) nameEl.textContent = 'Nenhum nível encontrado'\n        return\n      }\n      level = { width: rec.width, height: rec.height, grid: rec.grid || [], name: rec.name || 'Nível' }\n      things = (rec.things || []).map((t) => ({ ...t, alive: true }))\n      const ps = rec.playerStart || { x: 1.5, y: 1.5, angle: 0 }\n      player = { x: ps.x, y: ps.y, angle: ps.angle || 0 }\n      kills = 0\n      totalEnemies = things.filter((t) => t.type === 'enemy').length\n      gameWon = false\n      const loadingEl = d2.$('.loading'); if (loadingEl) loadingEl.classList.add('hide')\n      const nameEl = d2.$('.hudName'); if (nameEl) nameEl.textContent = level.name\n      const killsEl = d2.$('.hudKills'); if (killsEl) killsEl.textContent = `Abates: 0/${totalEnemies}`\n      ready = true\n    }\n\n    function render3D(ctx) {\n      const img = ctx.createImageData(RENDER_W, RENDER_H)\n      for (let col = 0; col < RENDER_W; col++) {\n        const rayAngle = player.angle - FOV / 2 + (FOV * col) / RENDER_W\n        const { dist, side, cellVal } = castRay(player.x, player.y, rayAngle)\n        const perp = dist * Math.cos(rayAngle - player.angle)\n        depthBuffer[col] = perp\n        const lineH = Math.min(RENDER_H * 3, RENDER_H / Math.max(perp, 0.0001))\n        const drawStart = Math.floor(RENDER_H / 2 - lineH / 2)\n        const drawEnd = Math.floor(RENDER_H / 2 + lineH / 2)\n        const fogT = clamp(perp / 11, 0, 1)\n        const mult = side === 1 ? 1 : 0.7\n        const wc = shadeRGB(WALL_COLORS[cellVal] || WALL_COLORS[1], mult, fogT)\n        for (let y = 0; y < RENDER_H; y++) {\n          let color\n          if (y < drawStart) color = SKY_COLOR\n          else if (y > drawEnd) color = FLOOR_COLOR\n          else color = wc\n          const idx = (y * RENDER_W + col) * 4\n          img.data[idx] = color[0] | 0\n          img.data[idx + 1] = color[1] | 0\n          img.data[idx + 2] = color[2] | 0\n          img.data[idx + 3] = 255\n        }\n      }\n      ctx.putImageData(img, 0, 0)\n      const alive = things.filter((t) => t.type === 'enemy' && t.alive)\n      alive.sort((a, b) => {\n        const da = Math.hypot(a.x - player.x, a.y - player.y)\n        const db = Math.hypot(b.x - player.x, b.y - player.y)\n        return db - da\n      })\n      ctx.textAlign = 'center'\n      ctx.textBaseline = 'middle'\n      alive.forEach((e) => {\n        const ex = e.x - player.x, ey = e.y - player.y\n        const dist = Math.hypot(ex, ey)\n        let ang = Math.atan2(ey, ex) - player.angle\n        while (ang > Math.PI) ang -= 2 * Math.PI\n        while (ang < -Math.PI) ang += 2 * Math.PI\n        if (Math.abs(ang) > FOV / 2 + 0.4 || dist < 0.15) return\n        const screenX = (0.5 + Math.tan(ang) / Math.tan(FOV / 2) / 2) * RENDER_W\n        const col = Math.floor(screenX)\n        if (col < -20 || col > RENDER_W + 20) return\n        const cCol = clamp(col, 0, RENDER_W - 1)\n        if (dist > depthBuffer[cCol] + 0.35) return\n        const size = clamp(RENDER_H / dist, 10, RENDER_H * 1.6)\n        const fogT = clamp(dist / 11, 0, 1)\n        ctx.globalAlpha = 1 - fogT * 0.7\n        ctx.font = `${size}px sans-serif`\n        ctx.fillText('👹', screenX, RENDER_H / 2 + size * 0.08)\n        ctx.globalAlpha = 1\n      })\n    }\n\n    function renderMini(ctx) {\n      ctx.clearRect(0, 0, MINI, MINI)\n      ctx.fillStyle = '#0d0f14'\n      ctx.fillRect(0, 0, MINI, MINI)\n      if (!level) return\n      const cs = Math.min(MINI / level.width, MINI / level.height)\n      for (let y = 0; y < level.height; y++) {\n        for (let x = 0; x < level.width; x++) {\n          const v = level.grid[y * level.width + x] || 0\n          if (v === 0) continue\n          ctx.fillStyle = v === 5 ? 'rgb(246,229,141)' : '#666'\n          ctx.fillRect(x * cs, y * cs, cs, cs)\n        }\n      }\n      things.filter((t) => t.type === 'enemy' && t.alive).forEach((e) => {\n        ctx.fillStyle = '#e84118'\n        ctx.beginPath(); ctx.arc(e.x * cs, e.y * cs, Math.max(2, cs * 0.3), 0, 7); ctx.fill()\n      })\n      ctx.save()\n      ctx.translate(player.x * cs, player.y * cs)\n      ctx.rotate(player.angle)\n      ctx.fillStyle = '#00e6e6'\n      ctx.beginPath(); ctx.moveTo(6, 0); ctx.lineTo(-4, 4); ctx.lineTo(-4, -4); ctx.closePath(); ctx.fill()\n      ctx.restore()\n    }\n\n    function checkWin() {\n      if (gameWon || !ready) return\n      if (kills >= totalEnemies) {\n        const onExit = cellAt(Math.floor(player.x), Math.floor(player.y)) === 5\n        if (onExit) {\n          gameWon = true\n          const v = d2.$('.victory'); if (v) v.classList.add('show')\n        }\n      }\n    }\n\n    const scene = d2.$('.scene')\n    const mini = d2.$('.mini')\n    if (!scene || !mini) return\n    scene.width = RENDER_W\n    scene.height = RENDER_H\n    const sctx = scene.getContext('2d')\n    mini.width = MINI\n    mini.height = MINI\n    const mctx = mini.getContext('2d')\n\n    loadLevel()\n\n    d2.raf((t) => {\n      if (lastT == null) lastT = t\n      const dt = Math.min((t - lastT) / 1000, 0.05)\n      lastT = t\n      if (!ready) return\n      if (!gameWon) {\n        const moveSpeed = 3, rotSpeed = 2.4, strafeSpeed = 2.6\n        let dx = 0, dy = 0\n        if (keys.forward) { dx += Math.cos(player.angle) * moveSpeed * dt; dy += Math.sin(player.angle) * moveSpeed * dt }\n        if (keys.back) { dx -= Math.cos(player.angle) * moveSpeed * dt; dy -= Math.sin(player.angle) * moveSpeed * dt }\n        if (keys.strafeLeft) { dx += Math.cos(player.angle - Math.PI / 2) * strafeSpeed * dt; dy += Math.sin(player.angle - Math.PI / 2) * strafeSpeed * dt }\n        if (keys.strafeRight) { dx += Math.cos(player.angle + Math.PI / 2) * strafeSpeed * dt; dy += Math.sin(player.angle + Math.PI / 2) * strafeSpeed * dt }\n        if (keys.rotLeft) player.angle -= rotSpeed * dt\n        if (keys.rotRight) player.angle += rotSpeed * dt\n        if (dx !== 0) { const nx = player.x + dx; if (canStand(nx + Math.sign(dx) * 0.2, player.y)) player.x = nx }\n        if (dy !== 0) { const ny = player.y + dy; if (canStand(player.x, ny + Math.sign(dy) * 0.2)) player.y = ny }\n        checkWin()\n      }\n      render3D(sctx)\n      renderMini(mctx)\n      const killsEl = d2.$('.hudKills')\n      if (killsEl) killsEl.textContent = `Abates: ${kills}/${totalEnemies}`\n    })\n  },\n}\n"}},"levels":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"levels"}}}},"gameStore":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"gameStore"}}}},"editorUrl":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"editorUrl"}}}}}}}}}},{"_id":"6abeecc5ba309d7a9face034","component":"data2-core-types@Component","system":"6172b957976026c073ab9058","params":{"extends":"data2-core-primitives@Block","component":"Rubik_s_Cube","package":"lucca-playground","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"export default {\n  css: `\n    :host { display: block; width: 100%; height: 100%; }\n    .rb { position: relative; width: 100%; height: 100%; min-height: 480px; overflow: hidden;\n      background: radial-gradient(120% 80% at 50% 30%, #2a2f45 0%, #12141f 70%);\n      color: #f3f4f8; font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif;\n      touch-action: none; user-select: none; -webkit-user-select: none; -webkit-tap-highlight-color: transparent;\n      display: flex; flex-direction: column; }\n    .hud { display: flex; justify-content: space-between; align-items: center; padding: 16px 20px 0; }\n    .title { font-weight: 700; font-size: 18px; letter-spacing: .3px; }\n    .stats { display: flex; gap: 16px; font-variant-numeric: tabular-nums; font-size: 14px; opacity: .85; }\n    .stats b { font-weight: 600; }\n    .stage { flex: 1; position: relative; display: flex; align-items: center; justify-content: center; }\n    .cube { position: relative; width: 0; height: 0; transform-style: preserve-3d; }\n    .cubie { position: absolute; transform-style: preserve-3d; }\n    .f { position: absolute; left: 0; top: 0; width: var(--s); height: var(--s); margin: calc(var(--s) / -2) 0 0 calc(var(--s) / -2);\n      background: #0b0c10; backface-visibility: hidden; -webkit-backface-visibility: hidden; box-sizing: border-box; }\n    .f.c::after { content: \"\"; position: absolute; inset: 6%; border-radius: 14%; background: var(--c);\n      box-shadow: inset 0 -2px 4px rgba(0,0,0,.25), inset 0 2px 3px rgba(255,255,255,.25); }\n    .hint { text-align: center; font-size: 13px; opacity: .55; padding: 0 24px; min-height: 18px; }\n    .bar { display: flex; gap: 10px; justify-content: center; padding: 14px 16px calc(18px + env(safe-area-inset-bottom)); }\n    .bar button { flex: 1; max-width: 140px; height: 48px; border: 0; border-radius: 14px; font-size: 15px; font-weight: 600;\n      color: #f3f4f8; background: rgba(255,255,255,.1); touch-action: manipulation; }\n    .bar button.primary { background: #4f7cff; }\n    .bar button:active { transform: scale(.97); }\n    .win { position: absolute; inset: 0; display: none; align-items: center; justify-content: center; pointer-events: none; }\n    .win.show { display: flex; }\n    .win div { background: rgba(18,20,31,.85); padding: 18px 26px; border-radius: 18px; text-align: center; font-size: 15px;\n      box-shadow: 0 10px 40px rgba(0,0,0,.4); animation: pop .35s ease-out; }\n    .win strong { display: block; font-size: 22px; margin-bottom: 4px; }\n    @keyframes pop { from { transform: scale(.8); opacity: 0; } to { transform: scale(1); opacity: 1; } }\n  `,\n\n  render() {\n    return `\n      <div class=\"rb\" data-d2-static>\n        <div class=\"hud\">\n          <div class=\"title\">Cube</div>\n          <div class=\"stats\"><span>Moves <b class=\"moves\">0</b></span><span><b class=\"time\">0:00</b></span></div>\n        </div>\n        <div class=\"stage\"><div class=\"cube\"></div><div class=\"win\"><div><strong>Solved!</strong><span class=\"winmsg\"></span></div></div></div>\n        <div class=\"hint\">Drag the background to spin · swipe a face to turn it</div>\n        <div class=\"bar\">\n          <button class=\"undo\">Undo</button>\n          <button class=\"reset\">Reset</button>\n          <button class=\"scramble primary\">Scramble</button>\n        </div>\n      </div>`;\n  },\n\n  mounted(d2, root) {\n    const $ = (s) => root.querySelector(s);\n    const rb = $('.rb'), stage = $('.stage'), cubeEl = $('.cube');\n    if (!rb || rb.__init) return;\n    rb.__init = true;\n\n    // ---------- math (3x3 row-major, CSS coords: x right, y down, z toward viewer) ----------\n    const I = () => [[1,0,0],[0,1,0],[0,0,1]];\n    const mul = (A, B) => A.map((r) => [0,1,2].map((c) => r[0]*B[0][c] + r[1]*B[1][c] + r[2]*B[2][c]));\n    const mv = (A, v) => A.map((r) => r[0]*v[0] + r[1]*v[1] + r[2]*v[2]);\n    const cross = (a, b) => [a[1]*b[2]-a[2]*b[1], a[2]*b[0]-a[0]*b[2], a[0]*b[1]-a[1]*b[0]];\n    const dot = (a, b) => a[0]*b[0] + a[1]*b[1] + a[2]*b[2];\n    const rnd = (v) => v.map((x) => Math.round(x));\n    const rot = (a, t) => {\n      const [x, y, z] = a, c = Math.cos(t), s = Math.sin(t), C = 1 - c;\n      return [[c+x*x*C, x*y*C-z*s, x*z*C+y*s], [y*x*C+z*s, c+y*y*C, y*z*C-x*s], [z*x*C-y*s, z*y*C+x*s, c+z*z*C]];\n    };\n    const css = (M, t = [0,0,0]) =>\n      `matrix3d(${M[0][0]},${M[1][0]},${M[2][0]},0,${M[0][1]},${M[1][1]},${M[2][1]},0,${M[0][2]},${M[1][2]},${M[2][2]},0,${t[0]},${t[1]},${t[2]},1)`;\n\n    // ---------- cube model ----------\n    const FACES = [ // local normal, css placement, color, test on solved position\n      { n: [0,0,1],  t: 'translateZ(calc(var(--s)/2))',                  c: '#2ecc71', k: (p) => p[2] === 1 },\n      { n: [0,0,-1], t: 'rotateY(180deg) translateZ(calc(var(--s)/2))',  c: '#3498ff', k: (p) => p[2] === -1 },\n      { n: [1,0,0],  t: 'rotateY(90deg) translateZ(calc(var(--s)/2))',   c: '#ff4757', k: (p) => p[0] === 1 },\n      { n: [-1,0,0], t: 'rotateY(-90deg) translateZ(calc(var(--s)/2))',  c: '#ff9f1a', k: (p) => p[0] === -1 },\n      { n: [0,-1,0], t: 'rotateX(90deg) translateZ(calc(var(--s)/2))',   c: '#f5f6fa', k: (p) => p[1] === -1 },\n      { n: [0,1,0],  t: 'rotateX(-90deg) translateZ(calc(var(--s)/2))',  c: '#ffd32a', k: (p) => p[1] === 1 },\n    ];\n    let S = 60, cubies = [];\n    let view = mul(rot([1,0,0], -0.5), rot([0,1,0], -0.65));\n\n    const build = () => {\n      cubeEl.innerHTML = '';\n      cubies = [];\n      for (let x = -1; x <= 1; x++) for (let y = -1; y <= 1; y++) for (let z = -1; z <= 1; z++) {\n        if (!x && !y && !z) continue;\n        const el = document.createElement('div');\n        el.className = 'cubie';\n        const home = [x, y, z];\n        FACES.forEach((F, i) => {\n          const f = document.createElement('div');\n          const colored = F.k(home);\n          f.className = 'f' + (colored ? ' c' : '');\n          f.dataset.fi = i;\n          f.style.transform = F.t;\n          if (colored) f.style.setProperty('--c', F.c);\n          el.appendChild(f);\n        });\n        const cb = { pos: home.slice(), R: I(), el };\n        el.__cb = cb;\n        cubies.push(cb);\n        cubeEl.appendChild(el);\n      }\n      paintAll();\n    };\n    const paint = (cb, A = null) => {\n      const M = A ? mul(A, cb.R) : cb.R;\n      const p = A ? mv(A, cb.pos) : cb.pos;\n      cb.el.style.transform = css(M, p.map((v) => v * S));\n    };\n    const paintAll = () => cubies.forEach((c) => paint(c));\n    const paintView = () => { cubeEl.style.transform = css(view); };\n\n    const layout = () => {\n      const r = stage.getBoundingClientRect();\n      const span = Math.min(r.width * 0.62, r.height * 0.55, 420);\n      S = Math.max(24, span / 3);\n      rb.style.setProperty('--s', S + 'px');\n      stage.style.perspective = S * 14 + 'px';\n      paintAll();\n    };\n\n    // ---------- moves ----------\n    let busy = false, queue = [], history = [], moves = 0, startT = 0, timerId = 0, scrambled = false;\n    const movesEl = $('.moves'), timeEl = $('.time'), winEl = $('.win'), winMsg = $('.winmsg');\n    const fmt = (ms) => { const s = Math.floor(ms / 1000); return Math.floor(s / 60) + ':' + String(s % 60).padStart(2, '0'); };\n    const tick = () => { if (startT) timeEl.textContent = fmt(Date.now() - startT); };\n\n    const layerOf = (axis, idx) => cubies.filter((c) => Math.round(dot(c.pos, axis)) === idx);\n    const commit = (axis, idx, q) => {\n      const A = rot(axis, q * Math.PI / 2);\n      layerOf(axis, idx).forEach((c) => { c.pos = rnd(mv(A, c.pos)); c.R = mul(A, c.R).map(rnd); paint(c); });\n    };\n    const animate = (layer, axis, from, to, ms, done) => {\n      const t0 = performance.now();\n      const step = (now) => {\n        const k = Math.min(1, (now - t0) / ms), e = 1 - Math.pow(1 - k, 3);\n        const A = rot(axis, from + (to - from) * e);\n        layer.forEach((c) => paint(c, A));\n        if (k < 1) requestAnimationFrame(step); else done();\n      };\n      requestAnimationFrame(step);\n    };\n    // move = { axis, idx, q } ; q in {-2, -1, 1, 2}\n    const run = () => {\n      if (busy || !queue.length) return;\n      busy = true;\n      const m = queue.shift();\n      animate(layerOf(m.axis, m.idx), m.axis, m.from || 0, m.q * Math.PI / 2, m.ms || 200, () => {\n        commit(m.axis, m.idx, m.q);\n        busy = false;\n        if (m.user) onUserMove(m);\n        run();\n      });\n    };\n    const push = (m) => { queue.push(m); run(); };\n\n    const onUserMove = (m) => {\n      if (!m.undo) history.push(m);\n      moves += m.undo ? -1 : 1;\n      movesEl.textContent = Math.max(0, moves);\n      try { navigator.vibrate && navigator.vibrate(8); } catch (e) {}\n      if (scrambled && !startT && !m.undo) { startT = Date.now(); timerId = setInterval(tick, 250); }\n      checkSolved();\n    };\n    const isSolved = () => {\n      const seen = {};\n      for (const c of cubies) for (const [i, F] of FACES.entries()) {\n        if (!c.el.children[i].classList.contains('c')) continue;\n        const n = rnd(mv(c.R, F.n)).join(',');\n        (seen[n] = seen[n] || new Set()).add(F.c);\n      }\n      return Object.values(seen).every((s) => s.size === 1);\n    };\n    const checkSolved = () => {\n      if (!scrambled || !isSolved()) return;\n      scrambled = false;\n      clearInterval(timerId);\n      const took = startT ? Date.now() - startT : 0;\n      startT = 0;\n      winMsg.textContent = `${moves} moves · ${fmt(took)}`;\n      winEl.classList.add('show');\n      try { navigator.vibrate && navigator.vibrate([30, 40, 30]); } catch (e) {}\n      setTimeout(() => winEl.classList.remove('show'), 2600);\n    };\n\n    const AXES = [[1,0,0],[0,1,0],[0,0,1]];\n    const scramble = () => {\n      if (busy || queue.length) return;\n      let last = -1;\n      for (let i = 0; i < 22; i++) {\n        let a; do { a = Math.floor(Math.random() * 3); } while (a === last);\n        last = a;\n        const idx = [-1, 0, 1][Math.floor(Math.random() * 3)];\n        queue.push({ axis: AXES[a], idx, q: Math.random() < 0.5 ? 1 : -1, ms: 90 });\n      }\n      history = []; moves = 0; movesEl.textContent = '0';\n      clearInterval(timerId); startT = 0; timeEl.textContent = '0:00';\n      winEl.classList.remove('show');\n      scrambled = true;\n      run();\n    };\n    const reset = () => {\n      if (busy) return;\n      queue = []; history = []; moves = 0; scrambled = false;\n      clearInterval(timerId); startT = 0;\n      movesEl.textContent = '0'; timeEl.textContent = '0:00';\n      winEl.classList.remove('show');\n      build();\n    };\n    const undo = () => {\n      if (busy || queue.length || !history.length) return;\n      const m = history.pop();\n      push({ axis: m.axis, idx: m.idx, q: -m.q, ms: 180, user: true, undo: true });\n    };\n    $('.scramble').addEventListener('click', scramble);\n    $('.reset').addEventListener('click', reset);\n    $('.undo').addEventListener('click', undo);\n\n    // ---------- gestures ----------\n    let g = null;\n    const toScreen = (d) => { const v = mv(view, d); return [v[0], v[1]]; };\n\n    stage.addEventListener('pointerdown', (ev) => {\n      if (g) return;\n      const face = ev.target.closest && ev.target.closest('.f');\n      g = { id: ev.pointerId, x0: ev.clientX, y0: ev.clientY, x: ev.clientX, y: ev.clientY, mode: 'orbit' };\n      if (face && face.classList.contains('c') && !busy && !queue.length) {\n        const cb = face.parentElement.__cb;\n        const n = rnd(mv(cb.R, FACES[+face.dataset.fi].n));\n        g.mode = 'pending'; g.cb = cb; g.n = n;\n      }\n      try { stage.setPointerCapture(ev.pointerId); } catch (e) {}\n      ev.preventDefault();\n    });\n\n    stage.addEventListener('pointermove', (ev) => {\n      if (!g || ev.pointerId !== g.id) return;\n      const dx = ev.clientX - g.x, dy = ev.clientY - g.y;\n      g.x = ev.clientX; g.y = ev.clientY;\n      if (g.mode === 'orbit') {\n        const len = Math.hypot(dx, dy);\n        if (len > 0) { view = mul(rot([-dy / len, dx / len, 0], len * 0.009), view); paintView(); }\n        return;\n      }\n      const tx = ev.clientX - g.x0, ty = ev.clientY - g.y0;\n      if (g.mode === 'pending') {\n        if (Math.hypot(tx, ty) < 10) return;\n        // pick the in-face direction whose screen projection best matches the swipe\n        let best = null;\n        for (const d of AXES.concat(AXES.map((a) => a.map((v) => -v)))) {\n          if (Math.abs(dot(d, g.n)) > 0.5) continue;\n          const s = toScreen(d), sl = Math.hypot(s[0], s[1]) || 1;\n          const score = (tx * s[0] + ty * s[1]) / sl;\n          if (!best || score > best.score) best = { d, s: [s[0] / sl, s[1] / sl], sl, score };\n        }\n        const axis = cross(g.n, best.d); // rotating +θ about n×d moves the sticker toward +d\n        g.mode = 'turn';\n        g.axis = axis;\n        g.dir = best.s;\n        g.scale = best.sl; // screen shrink of that direction\n        g.idx = Math.round(dot(g.cb.pos, axis));\n        g.layer = layerOf(axis, g.idx);\n      }\n      if (g.mode === 'turn') {\n        const along = tx * g.dir[0] + ty * g.dir[1];\n        g.angle = along / (S * 2.2 * Math.max(g.scale, 0.35)) * (Math.PI / 2);\n        const A = rot(g.axis, g.angle);\n        g.layer.forEach((c) => paint(c, A));\n      }\n    });\n\n    const end = (ev) => {\n      if (!g || ev.pointerId !== g.id) return;\n      const cur = g; g = null;\n      try { stage.releasePointerCapture(ev.pointerId); } catch (e) {}\n      if (cur.mode !== 'turn') return;\n      const quarter = cur.angle / (Math.PI / 2);\n      let q = Math.round(quarter);\n      if (q === 0 && Math.abs(quarter) > 0.28) q = Math.sign(quarter); // a short flick still turns\n      q = Math.max(-2, Math.min(2, q));\n      if (q === 0) {\n        busy = true;\n        animate(cur.layer, cur.axis, cur.angle, 0, 140, () => { busy = false; paintAll(); run(); });\n        return;\n      }\n      queue.unshift({ axis: cur.axis, idx: cur.idx, q, from: cur.angle, ms: 160, user: true });\n      run();\n    };\n    stage.addEventListener('pointerup', end);\n    stage.addEventListener('pointercancel', end);\n\n    build();\n    paintView();\n    layout();\n    if (typeof ResizeObserver !== 'undefined') new ResizeObserver(layout).observe(stage);\n    else window.addEventListener('resize', layout);\n  },\n};\n"}}}}}}}},{"_id":"6abf00de492457561460ce5d","component":"data2-core-types@Component","system":"6172b957976026c073ab9058","params":{"extends":"data2-core-primitives@Block","component":"Star_Fox_Mobile_Player","package":"lucca-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":"// Mobile SNES player for Star Fox: the player picks their own ROM on the device (it never\n// leaves the browser), EmulatorJS + snes9x (Super FX) run from the \"starfox-emulator\" Drive,\n// the phone's tilt steers the D-pad and touching the screen fires.\nconst MEDIA = 'https://lucca-playground.data2.media/file/';\nconst FILES = {\n  'loader.js': 'sjn4NtjRSdexz2quJ5Icu',\n  'emulator.js': 'H9sfJ9h2M4sL9l0V8lTRF',\n  'GameManager.js': '7-WiYcktgqc2iE805n4WB',\n  'compression.js': 'Z_xhOCdoDAvuDa0e8_MoL',\n  'gamepad.js': 'eQ7YsA53xxlQhATIzf1Lk',\n  'nipplejs.js': 'tQ3GRf6D_m2B_DgJ5FwCv',\n  'shaders.js': 'QSnBt1zhdtx9pOlWWit7k',\n  'storage.js': 'D59vCRKTT6jeJNnN2g0UB',\n  'socket.io.min.js': 'UUZqWxMwZg2qimoR3Nw8b',\n  'emulator.css': 'MNqXIt8RLPj-pB2WuqG7Q',\n  'snes9x-wasm.data': 'pVUBC-nZEN82gyuTpPsRw',\n  'snes9x-legacy-wasm.data': 'myfEzZFCCUes_kFsjUUCd',\n  'snes9x.json': 'YNbaUeVsno2mDYsEOtp39',\n  // the core .data files are 7z archives; these workers unpack cores and zipped ROMs\n  'extract7z.js': 'CPfp383FjI0u02qrXlbzl',\n  'extractzip.js': 'iqumdLN_ZjOfnsttklMob',\n  'libunrar.js': 'chOV7VBt3LvWNwsFSwxGk',\n  'libunrar.wasm': '8ftfUbTkQF-wO5oIGWIt1',\n};\n\n// libretro joypad ids as EmulatorJS's simulateInput expects them\nconst BTN = { B: 0, Y: 1, SELECT: 2, START: 3, UP: 4, DOWN: 5, LEFT: 6, RIGHT: 7, A: 8, X: 9, L: 10, R: 11 };\n\nconst DEAD_ZONE = 4;   // degrees of tilt ignored around neutral\nconst FULL_TILT = 20;  // degrees of tilt that hold the direction permanently\nconst YAW_GAIN = 1.0;      // degrees of steering per degree of yaw (turning the phone left/right)\nconst YAW_RECENTER = 1.2;  // s: yaw leaks back to neutral, so you never have to hold your body turned\nconst STEER_SMOOTH = 0.06; // s: low-pass time constant on the combined steering angle\nconst INVERT_STEER = true; // horizontal axis flipped: rolling/turning right steers left\nconst INVERT_YAW = false;  // flip yaw alone if it ever disagrees with roll on a device\nconst INVERT_ROLL = true;  // flip roll alone (tilting like a steering wheel)\n\nconst SHOOT_OPTIONS = ['B', 'Y', 'A', 'X'];\n\nfunction emulatorPage(romUrl, romName) {\n  const paths = {};\n  for (const [name, ref] of Object.entries(FILES)) paths[name] = MEDIA + ref;\n  const cfg = {\n    EJS_player: '#game',\n    EJS_core: 'snes',\n    EJS_gameName: romName.replace(/\\.[^.]+$/, ''),\n    EJS_gameUrl: romUrl,\n    EJS_pathtodata: MEDIA,\n    EJS_paths: paths,\n    EJS_DEBUG_XX: true,          // load the unminified sources individually (that's what the Drive holds)\n    EJS_startOnLoaded: true,\n    EJS_language: 'en-US',\n    EJS_disableAutoLang: false,  // loader's flag is inverted: false skips the localization fetch\n    EJS_threads: false,\n    EJS_color: '#ff6a2b',\n    EJS_backgroundColor: '#000',\n    EJS_Buttons: { netplay: false, saveState: false, loadState: false, screenRecord: false, screenshot: false, cacheManager: false, gamepad: false },\n  };\n  const assigns = Object.entries(cfg).map(([k, v]) => `window.${k}=${JSON.stringify(v)};`).join('');\n  return `<!doctype html><html><head><meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no\">\n<style>\nhtml,body{margin:0;height:100%;background:#000;overflow:hidden;touch-action:none}\n#game{width:100%;height:100%}\n.ejs_virtualGamepad_parent,.ejs_menu_bar,.ejs_context_menu{display:none!important}\n</style></head><body><div id=\"game\"></div>\n<script>${assigns}</script>\n<script src=\"${paths['loader.js']}\"></script>\n</body></html>`;\n}\n\nexport default {\n  css: `\n    :host{display:block;width:100%;height:100%}\n    .sf{position:relative;width:100%;height:100%;min-height:320px;overflow:hidden;background:#05060d;color:#e9ecff;\n      font-family:ui-sans-serif,system-ui,-apple-system,\"Segoe UI\",Roboto,sans-serif;touch-action:none;user-select:none;-webkit-user-select:none}\n    .sf:fullscreen{width:100vw;height:100vh}\n    .stage{position:absolute;inset:0}\n    .emu{position:absolute;inset:0;width:100%;height:100%;border:0;display:block;background:#000}\n    .touch{position:absolute;inset:0;z-index:2;display:none}\n    .playing .touch{display:block}\n    .hud{position:absolute;inset:0;z-index:3;pointer-events:none;display:none}\n    .playing .hud{display:block}\n    .hb{pointer-events:auto;position:absolute;border:1px solid rgba(255,255,255,.35);background:rgba(10,12,30,.45);color:#fff;\n      font-family:inherit;font-size:12px;font-weight:600;line-height:1;letter-spacing:.08em;border-radius:999px;padding:10px 14px;-webkit-tap-highlight-color:transparent}\n    .hb:active,.hb.on{background:rgba(255,106,43,.75);border-color:#ff6a2b}\n    .hb.start{top:max(10px,env(safe-area-inset-top));left:50%;transform:translateX(-50%)}\n    .hb.cal{top:max(10px,env(safe-area-inset-top));left:max(10px,env(safe-area-inset-left))}\n    .hb.bomb{bottom:max(14px,env(safe-area-inset-bottom));right:max(14px,env(safe-area-inset-right));width:68px;height:68px;padding:0;border-radius:50%}\n    .tilt{position:absolute;bottom:max(14px,env(safe-area-inset-bottom));left:max(14px,env(safe-area-inset-left));width:64px;height:64px;\n      border-radius:50%;border:1px solid rgba(255,255,255,.3);background:rgba(10,12,30,.35)}\n    .tilt i{position:absolute;left:50%;top:50%;width:14px;height:14px;margin:-7px 0 0 -7px;border-radius:50%;background:#ff6a2b;\n      box-shadow:0 0 10px #ff6a2b;transform:translate(0,0)}\n    .intro{position:absolute;inset:0;z-index:4;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:14px;\n      padding:24px;text-align:center;background:radial-gradient(ellipse at 50% 120%,#2a1240 0%,#0b0b22 45%,#05060d 75%)}\n    .intro::before{content:\"\";position:absolute;inset:0;pointer-events:none;opacity:.7;\n      background-image:radial-gradient(1px 1px at 12% 22%,#fff 50%,transparent 51%),radial-gradient(1px 1px at 72% 14%,#fff 50%,transparent 51%),\n      radial-gradient(1.5px 1.5px at 38% 68%,#cfd6ff 50%,transparent 51%),radial-gradient(1px 1px at 86% 58%,#fff 50%,transparent 51%),\n      radial-gradient(1px 1px at 22% 84%,#fff 50%,transparent 51%),radial-gradient(1.5px 1.5px at 58% 36%,#ffd9c7 50%,transparent 51%),\n      radial-gradient(1px 1px at 92% 88%,#fff 50%,transparent 51%),radial-gradient(1px 1px at 6% 52%,#cfd6ff 50%,transparent 51%)}\n    .playing .intro{display:none}\n    .intro > *{position:relative}\n    h1{margin:0;font-size:26px;letter-spacing:.18em;font-weight:800}\n    .sub{margin:0;font-size:13px;opacity:.7;max-width:340px;line-height:1.45}\n    .btn{appearance:none;border:0;border-radius:12px;padding:14px 22px;min-width:220px;font-family:inherit;font-size:15px;font-weight:700;line-height:1;letter-spacing:.06em;cursor:pointer;\n      -webkit-tap-highlight-color:transparent}\n    .pick{background:#1b1f3f;color:#fff;border:1px solid rgba(255,255,255,.25)}\n    .go{background:#ff6a2b;color:#140700}\n    .go[disabled]{opacity:.35}\n    .msg{min-height:18px;font-size:13px;color:#ffb38f;max-width:340px}\n    .help{list-style:none;margin:6px 0 0;padding:0;font-size:12px;opacity:.65;line-height:1.7}\n    .file{display:none}\n    .toast{position:absolute;left:50%;bottom:max(14px,env(safe-area-inset-bottom));transform:translateX(-50%);max-width:60%;font-size:12px;\n      text-align:center;color:#ffb38f;background:rgba(5,6,13,.7);border-radius:8px;padding:6px 10px;pointer-events:none}\n    .toast:empty{display:none}\n  `,\n\n  render() {\n    return `<div class=\"sf\">\n      <div class=\"stage\" data-d2-static></div>\n      <div class=\"touch\" data-d2-static></div>\n      <div class=\"hud\" data-d2-static>\n        <button class=\"hb cal\" type=\"button\">LEVEL</button>\n        <button class=\"hb start\" type=\"button\">START</button>\n        <button class=\"hb bomb\" type=\"button\">BOMB</button>\n        <div class=\"tilt\"><i></i></div>\n        <div class=\"toast\"></div>\n      </div>\n      <div class=\"intro\" data-d2-static>\n        <h1>FLIGHT DECK</h1>\n        <p class=\"sub\">Load your own Star Fox (SNES) ROM. The file stays on this device and is never uploaded.</p>\n        <input class=\"file\" type=\"file\" accept=\".sfc,.smc,.zip,.7z,.bin\">\n        <button class=\"btn pick\" type=\"button\">Choose ROM</button>\n        <button class=\"btn go\" type=\"button\" disabled>Launch</button>\n        <p class=\"msg\"></p>\n        <ul class=\"help\">\n          <li>Tilt the phone to steer — hold it the way you like, it levels on launch</li>\n          <li>Touch anywhere to shoot (hold for rapid fire)</li>\n          <li>START pauses &middot; BOMB fires a bomb &middot; LEVEL re-centres the tilt</li>\n        </ul>\n      </div>\n    </div>`;\n  },\n\n  mounted(d2) {\n    const sf = d2.$('.sf');\n    const stage = d2.$('.stage');\n    const touch = d2.$('.touch');\n    const fileInput = d2.$('.file');\n    const goBtn = d2.$('.go');\n    const msg = d2.$('.msg');\n    const dot = d2.$('.tilt i');\n    const toast = d2.$('.toast');\n\n    const shootName = String(d2.param('shootButton') || 'Y').toUpperCase();\n    const SHOOT = BTN[SHOOT_OPTIONS.includes(shootName) ? shootName : 'Y'];\n\n    let rom = null;          // { url, name }\n    let frame = null;\n    let firing = 0;          // active touches on the play surface\n    let fireTick = 0;\n    const held = new Set();  // buttons currently pressed by this component\n    const tilt = { roll: 0, rollWeight: 0, pitch: 0, yaw: 0, ok: false };\n    const neutral = { roll: 0, pitch: 0 };\n    let prevR = null, prevT = 0;\n    let steer = 0, lastFrame = 0;\n    let needsLevel = true;\n    const acc = { x: 0, y: 0 };\n\n    const say = (t) => { msg.textContent = t || ''; toast.textContent = sf.classList.contains('playing') ? (t || '') : ''; };\n\n    const gm = () => {\n      try { return frame && frame.contentWindow && frame.contentWindow.EJS_emulator && frame.contentWindow.EJS_emulator.gameManager; }\n      catch (e) { return null; }\n    };\n    const press = (id, down) => {\n      const g = gm();\n      if (!g) return;\n      if (down === held.has(id)) return;\n      down ? held.add(id) : held.delete(id);\n      try { g.simulateInput(0, id, down ? 1 : 0); } catch (e) {}\n    };\n    const resumeAudio = () => {\n      try {\n        const ctx = frame.contentWindow.EJS_emulator.Module.AL.currentCtx.audioCtx;\n        if (ctx && ctx.state !== 'running') ctx.resume();\n      } catch (e) {}\n    };\n\n    // --- ROM selection: read locally, hand the emulator a blob: URL ---\n    d2.on('.pick', 'click', () => fileInput.click());\n    fileInput.addEventListener('change', () => {\n      const f = fileInput.files && fileInput.files[0];\n      if (!f) return;\n      if (rom) URL.revokeObjectURL(rom.url);\n      rom = { url: URL.createObjectURL(f), name: f.name };\n      goBtn.disabled = false;\n      say(f.name + ' ready');\n    });\n\n    // --- Tilt: device orientation -> gravity in screen coordinates ---\n    const screenAngle = () => {\n      const a = (screen.orientation && typeof screen.orientation.angle === 'number') ? screen.orientation.angle : (window.orientation || 0);\n      return ((a % 360) + 360) % 360;\n    };\n    const wrap = (deg) => ((deg + 540) % 360) - 180;\n    // W3C device->earth rotation matrix (Z-X'-Y'' Euler: alpha, beta, gamma)\n    const rotation = (a, b, g) => {\n      const cA = Math.cos(a), sA = Math.sin(a), cB = Math.cos(b), sB = Math.sin(b), cG = Math.cos(g), sG = Math.sin(g);\n      return [\n        [cA * cG - sA * sB * sG, -cB * sA, cA * sG + cG * sA * sB],\n        [cG * sA + cA * sB * sG, cA * cB, sA * sG - cA * cG * sB],\n        [-cB * sG, sB, cB * cG],\n      ];\n    };\n    const onOrient = (e) => {\n      if (e.beta == null || e.gamma == null) return;\n      const r = Math.PI / 180;\n      const R = rotation((e.alpha || 0) * r, e.beta * r, e.gamma * r);\n      // \"up\" in device coordinates is the earth-z row of R\n      const ux = R[2][0], uy = R[2][1];\n\n      // Roll = steering-wheel rotation about the screen normal, read from where gravity points in the\n      // screen plane. It does not depend on the reported screen angle, and fades out as the phone goes\n      // flat (gravity leaves the screen plane), where yaw takes over the same motion.\n      const inPlane = Math.hypot(ux, uy);\n      const wheel = Math.atan2(uy, ux) / r;   // grows when the phone turns clockwise, as seen by the player\n      tilt.rollWeight = Math.max(0, Math.min(1, (inPlane - 0.2) / 0.3));\n\n      // Pitch = elevation of the screen's top edge (unchanged behaviour)\n      const t = screenAngle() * r;\n      const sy = ux * Math.sin(t) + uy * Math.cos(t);\n      tilt.pitch = Math.asin(Math.max(-1, Math.min(1, sy))) / r;\n\n      // Yaw = rotation about the earth's vertical between two samples, from the incremental rotation\n      // R_t * R_(t-1)^T. Integrated with a leak so compass drift and a turned body both re-centre.\n      const now = e.timeStamp || performance.now();\n      if (prevR && e.alpha != null) {\n        const dt = Math.max(0, Math.min(0.25, (now - prevT) / 1000));\n        let d10 = 0, d01 = 0, d00 = 0, d11 = 0;\n        for (let k = 0; k < 3; k++) {\n          d10 += R[1][k] * prevR[0][k]; d01 += R[0][k] * prevR[1][k];\n          d00 += R[0][k] * prevR[0][k]; d11 += R[1][k] * prevR[1][k];\n        }\n        const step = Math.atan2(d10 - d01, d00 + d11) / r;  // + = counter-clockwise seen from above (turning left)\n        if (Math.abs(step) < 30) tilt.yaw = (tilt.yaw - step) * Math.exp(-dt / YAW_RECENTER);  // + = turned right\n        tilt.yaw = Math.max(-45, Math.min(45, tilt.yaw));\n      }\n      prevR = R; prevT = now;\n\n      if (needsLevel) {\n        neutral.roll = wheel; neutral.pitch = tilt.pitch; tilt.yaw = 0; steer = 0; needsLevel = false;\n      }\n      tilt.roll = wrap(wheel - neutral.roll);\n      tilt.ok = true;\n    };\n    window.addEventListener('deviceorientation', onOrient);\n\n    const axis = (deg) => {\n      const m = Math.abs(deg);\n      if (m < DEAD_ZONE) return 0;\n      return Math.sign(deg) * Math.min(1, (m - DEAD_ZONE) / (FULL_TILT - DEAD_ZONE));\n    };\n    // The SNES pad is digital: pulse the direction with a duty cycle proportional to the tilt.\n    const pulse = (v, k) => {\n      const m = Math.abs(v);\n      if (m === 0) { acc[k] = 0; return 0; }\n      if (m >= 0.97) return Math.sign(v);\n      acc[k] += m;\n      if (acc[k] >= 1) { acc[k] -= 1; return Math.sign(v); }\n      return 0;\n    };\n\n    d2.raf(() => {\n      if (!sf.isConnected) return;\n      if (!gm()) return;\n      const invert = !!d2.param('invertPitch');\n      // horizontal: roll + yaw, low-passed so the pulses into the digital pad follow a smooth curve\n      const nowMs = performance.now();\n      const dt = lastFrame ? Math.min(0.1, (nowMs - lastFrame) / 1000) : 1 / 60;\n      lastFrame = nowMs;\n      const yawSign = INVERT_YAW ? -1 : 1;\n      const rollSign = INVERT_ROLL ? -1 : 1;\n      const target = tilt.ok ? rollSign * tilt.roll * tilt.rollWeight + YAW_GAIN * yawSign * tilt.yaw : 0;\n      steer += (target - steer) * (1 - Math.exp(-dt / STEER_SMOOTH));\n      const x = axis(INVERT_STEER ? -steer : steer);\n      // tilting the top edge away (nose forward) presses UP, like pushing a flight stick\n      let y = tilt.ok ? axis(neutral.pitch - tilt.pitch) : 0;\n      if (invert) y = -y;\n      dot.style.transform = `translate(${(x * 22).toFixed(1)}px,${(-y * 22).toFixed(1)}px)`;\n      const px = pulse(x, 'x'), py = pulse(y, 'y');\n      press(BTN.RIGHT, px > 0); press(BTN.LEFT, px < 0);\n      press(BTN.UP, py > 0); press(BTN.DOWN, py < 0);\n      // shooting: held touch = rapid fire (press 4 frames, release 4 frames)\n      if (firing > 0) {\n        const auto = d2.param('autoFire') !== false;\n        fireTick++;\n        press(SHOOT, auto ? (fireTick % 8) < 4 : true);\n      } else {\n        fireTick = 0;\n        press(SHOOT, false);\n      }\n    });\n\n    // --- Touch surface: any touch shoots ---\n    const touchDown = (e) => { e.preventDefault(); resumeAudio(); firing = e.touches ? e.touches.length : 1; };\n    const touchUp = (e) => { e.preventDefault(); firing = e.touches ? e.touches.length : 0; };\n    touch.addEventListener('touchstart', touchDown, { passive: false });\n    touch.addEventListener('touchend', touchUp, { passive: false });\n    touch.addEventListener('touchcancel', touchUp, { passive: false });\n    touch.addEventListener('mousedown', (e) => { resumeAudio(); firing = 1; e.preventDefault(); });\n    window.addEventListener('mouseup', () => { if (firing && !('ontouchstart' in window)) firing = 0; });\n\n    // --- HUD buttons: held for as long as the finger is down ---\n    const holdButton = (sel, id) => {\n      const el = d2.$(sel);\n      const down = (e) => { e.preventDefault(); e.stopPropagation(); resumeAudio(); el.classList.add('on'); press(id, true); };\n      const up = (e) => { e.preventDefault(); e.stopPropagation(); el.classList.remove('on'); press(id, false); };\n      el.addEventListener('touchstart', down, { passive: false });\n      el.addEventListener('touchend', up, { passive: false });\n      el.addEventListener('touchcancel', up, { passive: false });\n      el.addEventListener('mousedown', down);\n      el.addEventListener('mouseup', up);\n      el.addEventListener('mouseleave', up);\n    };\n    holdButton('.hb.start', BTN.START);\n    holdButton('.hb.bomb', BTN.A);\n    d2.on('.hb.cal', 'click', () => { needsLevel = true; });\n\n    // --- Launch: permissions need this user gesture, then boot the emulator in an iframe ---\n    d2.on('.go', 'click', () => {\n      if (!rom) return;\n      const DOE = window.DeviceOrientationEvent;\n      if (DOE && typeof DOE.requestPermission === 'function') {\n        DOE.requestPermission().then((s) => { if (s !== 'granted') say('Motion access denied — steering is disabled. Reload to try again.'); }).catch(() => {});\n      }\n      try { if (sf.requestFullscreen) sf.requestFullscreen({ navigationUI: 'hide' }).catch(() => {}); } catch (e) {}\n      try { if (screen.orientation && screen.orientation.lock) screen.orientation.lock('landscape').catch(() => {}); } catch (e) {}\n\n      stage.innerHTML = '';\n      frame = document.createElement('iframe');\n      frame.className = 'emu';\n      frame.title = 'SNES emulator';\n      frame.setAttribute('allow', 'autoplay; fullscreen; gamepad; accelerometer; gyroscope');\n      frame.srcdoc = emulatorPage(rom.url, rom.name);\n      stage.appendChild(frame);\n      needsLevel = true;\n      sf.classList.add('playing');\n      setTimeout(() => { if (!tilt.ok) say('No tilt sensor reported — steering needs a phone with a gyroscope.'); }, 3000);\n    });\n  },\n};\n"}},"autoFire":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"autoFire"}}}},"invertPitch":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"invertPitch"}}}},"shootButton":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"shootButton"}}}}}}}}}},{"_id":"6abff75fce21b725b3165a70","component":"data2-core-types@Component","system":"6172b957976026c073ab9058","params":{"extends":"data2-core-primitives@Block","component":"dataFox2_Player","package":"lucca-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":"// dataFox2 — runs the Starship (SF64 PC port) WebAssembly build in a WebGL2 canvas,\n// with phone controls: gyroscope steering, a touch stick, and touch buttons.\n//\n// Input reaches the engine as a *virtual gamepad*: navigator.getGamepads() is wrapped so\n// SDL's emscripten joystick backend sees one \"standard\"-mapped pad whose axes come from the\n// gyro / touch stick and whose buttons come from the on-screen controls. Starship's default\n// SDL mapping then turns that into N64 input (A, B, Z, R, Start, C-buttons, stick).\n\nconst BUTTON = { A: 0, CDOWN: 1, B: 2, CLEFT: 3, Z: 4, R: 5, BACK: 8, START: 9, DUP: 12, DDOWN: 13, DLEFT: 14, DRIGHT: 15 }\n\nfunction makePad() {\n  const buttons = Array.from({ length: 17 }, () => ({ pressed: false, touched: false, value: 0 }))\n  return {\n    id: 'dataFox2 virtual pad (standard)',\n    index: 0,\n    connected: true,\n    mapping: 'standard',\n    axes: [0, 0, 0, 0],\n    buttons,\n    timestamp: performance.now(),\n    vibrationActuator: null,\n  }\n}\n\nfunction installVirtualPad(pad) {\n  const nav = window.navigator\n  const original = nav.getGamepads ? nav.getGamepads.bind(nav) : () => []\n  const patched = () => {\n    pad.timestamp = performance.now()\n    const real = Array.from(original() || []).filter(Boolean)\n    return [pad, ...real.map((g, i) => g && { ...g, index: i + 1 })]\n  }\n  try {\n    Object.defineProperty(nav, 'getGamepads', { configurable: true, value: patched })\n  } catch (e) {\n    nav.getGamepads = patched\n  }\n  const ev = new Event('gamepadconnected')\n  ev.gamepad = pad\n  window.dispatchEvent(ev)\n  return () => {\n    try { Object.defineProperty(nav, 'getGamepads', { configurable: true, value: original }) } catch (e) {}\n  }\n}\n\nconst clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v))\n\nexport default {\n  css: `\n    .wrap { position: relative; display: block; width: 100%; height: 100%; background: #05060a;\n      overflow: hidden; touch-action: none; user-select: none; -webkit-user-select: none;\n      font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif; color: #e8ecf4; }\n    /* !important: SDL writes inline css sizes on resize; the canvas must always fill the component. */\n    canvas.game { position: absolute; inset: 0; width: 100% !important; height: 100% !important; display: block; outline: none; }\n    .crash { position: absolute; inset: 0; z-index: 9; display: flex; flex-direction: column; align-items: center;\n      justify-content: center; gap: 12px; padding: 24px; box-sizing: border-box; background: #05060af2; text-align: center; }\n    .crash[hidden] { display: none; }\n    .crash pre { max-width: 92%; max-height: 40%; overflow: auto; margin: 0; padding: 10px; border-radius: 8px;\n      background: #0c1222; color: #ffb4b4; font: 11px/1.4 ui-monospace, Menlo, monospace; text-align: left; white-space: pre-wrap; }\n    .overlay { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center;\n      justify-content: center; gap: 14px; background: radial-gradient(circle at 50% 40%, #16203a 0%, #05060a 70%);\n      text-align: center; padding: 24px; box-sizing: border-box; z-index: 5; }\n    .overlay[hidden] { display: none; }\n    .title { font-size: 28px; font-weight: 800; letter-spacing: 0.08em; }\n    .sub { font-size: 14px; opacity: 0.75; max-width: 420px; line-height: 1.4; }\n    .bar { width: min(320px, 70vw); height: 6px; border-radius: 3px; background: #1d2740; overflow: hidden; }\n    .bar > i { display: block; height: 100%; width: 0%; background: #5aa2ff; transition: width 0.2s; }\n    .go { appearance: none; border: 0; border-radius: 999px; padding: 14px 34px; font-size: 17px; font-weight: 700;\n      background: #5aa2ff; color: #05060a; cursor: pointer; }\n    .go[disabled] { opacity: 0.4; }\n    .err { color: #ff8a8a; font-size: 13px; max-width: 460px; white-space: pre-wrap; }\n    .login { display: flex; flex-direction: column; gap: 10px; width: min(300px, 80vw); }\n    .login[hidden] { display: none; }\n    .login input { appearance: none; border: 1px solid #2c3a5c; background: #0c1222; color: #e8ecf4; border-radius: 10px;\n      padding: 12px 14px; font-size: 16px; outline: none; }\n    .login input:focus { border-color: #5aa2ff; }\n    .login .go { width: 100%; }\n\n    .hud { position: absolute; inset: 0; z-index: 3; pointer-events: none; }\n    .hud[hidden] { display: none; }\n    .btn { position: absolute; pointer-events: auto; display: flex; align-items: center; justify-content: center;\n      border-radius: 50%; background: rgba(255,255,255,0.14); border: 2px solid rgba(255,255,255,0.35);\n      color: #fff; font-weight: 800; font-size: 18px; box-sizing: border-box; backdrop-filter: blur(2px); }\n    .btn.on { background: rgba(90,162,255,0.55); border-color: #9cc6ff; }\n    .btn.pill { border-radius: 14px; font-size: 13px; letter-spacing: 0.05em; }\n    .a { width: 74px; height: 74px; right: calc(26px + env(safe-area-inset-right)); bottom: 34px; }\n    .b { width: 60px; height: 60px; right: calc(112px + env(safe-area-inset-right)); bottom: 22px; }\n    .r { width: 64px; height: 46px; right: calc(28px + env(safe-area-inset-right)); bottom: 124px; }\n    .z { width: 64px; height: 46px; left: calc(22px + env(safe-area-inset-left)); bottom: 186px; }\n    .boost { width: 68px; height: 68px; left: calc(22px + env(safe-area-inset-left)); bottom: 104px; font-size: 12px; }\n    .brake { width: 68px; height: 68px; left: calc(22px + env(safe-area-inset-left)); bottom: 24px; font-size: 12px; }\n    .start { width: 78px; height: 34px; left: 50%; transform: translateX(-50%); bottom: 14px; }\n    .top { position: absolute; top: calc(8px + env(safe-area-inset-top)); right: calc(10px + env(safe-area-inset-right));\n      display: flex; gap: 8px; pointer-events: auto; }\n    .chip { pointer-events: auto; appearance: none; border: 1px solid rgba(255,255,255,0.35); background: rgba(0,0,0,0.45);\n      color: #fff; border-radius: 999px; padding: 6px 12px; font-size: 12px; font-weight: 600; }\n    .chip.on { background: rgba(90,162,255,0.55); }\n    .stickzone { position: absolute; left: calc(104px + env(safe-area-inset-left)); bottom: 0; width: 34%; height: 62%; pointer-events: auto; }\n    .knob-base { position: absolute; width: 120px; height: 120px; margin: -60px 0 0 -60px; border-radius: 50%;\n      border: 2px solid rgba(255,255,255,0.3); background: rgba(255,255,255,0.06); display: none; }\n    .knob { position: absolute; width: 54px; height: 54px; margin: -27px 0 0 -27px; border-radius: 50%;\n      background: rgba(255,255,255,0.35); display: none; }\n    .rotate { position: absolute; inset: 0; z-index: 8; display: none; align-items: center; justify-content: center;\n      background: #05060aee; font-size: 16px; text-align: center; padding: 24px; }\n    @media (orientation: portrait) and (pointer: coarse) { .rotate.armed { display: flex; } }\n  `,\n\n  // Render once; everything after mount is imperative (canvas + engine) so re-renders never touch it.\n  render() {\n    return `<div class=\"wrap\" data-d2-static>\n      <canvas class=\"game\" id=\"canvas\" tabindex=\"-1\"></canvas>\n      <div class=\"hud\" hidden>\n        <div class=\"stickzone\"><div class=\"knob-base\"></div><div class=\"knob\"></div></div>\n        <div class=\"btn a\" data-b=\"A\">A</div>\n        <div class=\"btn b\" data-b=\"B\">B</div>\n        <div class=\"btn pill r\" data-b=\"R\">R</div>\n        <div class=\"btn pill z\" data-b=\"Z\">Z</div>\n        <div class=\"btn boost\" data-b=\"CLEFT\">BOOST</div>\n        <div class=\"btn brake\" data-b=\"CDOWN\">BRAKE</div>\n        <div class=\"btn pill start\" data-b=\"START\">START</div>\n        <div class=\"top\">\n          <button class=\"chip gyro on\" data-act=\"gyro\">Gyro</button>\n          <button class=\"chip\" data-act=\"sens\">Tilt: Med</button>\n          <button class=\"chip\" data-act=\"center\">Center</button>\n          <button class=\"chip\" data-act=\"hud\">Hide</button>\n        </div>\n      </div>\n      <div class=\"overlay\">\n        <div class=\"title\">dataFox2</div>\n        <div class=\"sub status\">Preparing…</div>\n        <div class=\"bar\"><i></i></div>\n        <form class=\"login\" hidden autocomplete=\"on\">\n          <input name=\"email\" type=\"email\" placeholder=\"Email\" autocomplete=\"username\" required>\n          <input name=\"password\" type=\"password\" placeholder=\"Password\" autocomplete=\"current-password\" required>\n          <button class=\"go signin\" type=\"submit\">Sign in</button>\n        </form>\n        <button class=\"go start-btn\" disabled>Start</button>\n        <div class=\"err\"></div>\n      </div>\n      <div class=\"rotate\">Rotate your phone to landscape</div>\n      <div class=\"crash\" hidden>\n        <div class=\"title\">The game stopped</div>\n        <div class=\"sub crash-why\"></div>\n        <pre class=\"crash-log\"></pre>\n        <button class=\"go restart\">Restart</button>\n      </div>\n    </div>`\n  },\n\n  mounted(d2, root) {\n    const $ = (s) => root.querySelector(s) || d2.$(s)\n    const canvas = $('canvas.game')\n    const overlay = $('.overlay')\n    const statusEl = $('.status')\n    const barEl = $('.bar > i')\n    const goBtn = $('.start-btn')\n    const loginForm = $('.login')\n    const errEl = $('.err')\n    const hud = $('.hud')\n\n    const filesBase = String(d2.param('filesBase', '')).replace(/\\/$/, '')\n    const refs = {\n      js: d2.param('engineJs', ''),\n      wasm: d2.param('engineWasm', ''),\n      main: d2.param('mainO2r', ''),\n      port: d2.param('portO2r', ''),\n    }\n\n    const setStatus = (t) => { statusEl.textContent = t }\n    const setProgress = (f) => { barEl.style.width = `${Math.round(clamp(f, 0, 1) * 100)}%` }\n    const fail = (msg) => { errEl.textContent = msg; setStatus('Could not start'); goBtn.disabled = true }\n\n    // Two ways in: a builder session (the Drive's builder-only policy) or a player session from\n    // the login strategy. Both live in localStorage; each request carries whichever exist.\n    const systemDomain = String(d2.param('systemDomain', 'lucca-playground'))\n    const readKey = (k) => { try { return localStorage.getItem(k) || '' } catch (e) { return '' } }\n    const authHeaders = () => {\n      const h = {}\n      const builderToken = readKey('__data2-session-data2')\n      const sessionToken = readKey(`__data2-session-${systemDomain}`)\n      if (builderToken) h['X-Data2-Builder-Token'] = builderToken\n      if (sessionToken) h.Authorization = `Bearer ${sessionToken}`\n      return h\n    }\n    class Refused extends Error {}\n\n    const progress = { total: 0, loaded: {} }\n    // When a download dies at the network level ('Load failed' / 'Failed to fetch'), find out which\n    // hop broke: the authorised request to the server, or the redirect it answers with.\n    const diagnose = async (ref) => {\n      try {\n        const r = await fetch(`${filesBase}/file/${ref}`, { headers: authHeaders(), redirect: 'manual' })\n        if (r.type === 'opaqueredirect') return 'server accepted the session; the browser blocked the redirect to the file'\n        return `server answered HTTP ${r.status} without redirecting`\n      } catch (e) {\n        return `the request to the server itself failed (${e && e.message})`\n      }\n    }\n\n    // Downloads go out as plain GETs with the key in the query string (the Drive has a policy that\n    // accepts it). A request with no custom headers needs no CORS preflight, and WebKit (every\n    // iPhone browser) refuses to follow the server's redirect to the signed file URL after one.\n    // The key itself is read through the session-gated keys DataSet, which involves no redirect.\n    const keysDataset = String(d2.param('keysDataset', 'datafox2keysDataSet'))\n    const getDownloadKey = async () => {\n      const res = await fetch(`${filesBase}/dataset/${keysDataset}?$limit=1`, { headers: authHeaders() })\n      if (res.status === 401 || res.status === 403) throw new Refused(`keys: HTTP ${res.status}`)\n      if (!res.ok) throw new Error(`keys: HTTP ${res.status}`)\n      const body = await res.json()\n      const rows = Array.isArray(body) ? body : (body && body.data) || []\n      const k = rows[0] && rows[0].key\n      if (!k) throw new Refused('keys: no key readable for this session')\n      return k\n    }\n    let downloadKey = ''\n\n    const fetchBytes = async (key, ref) => {\n      if (!ref) throw new Error(`Missing file reference for ${key}`)\n      let res\n      try {\n        res = await fetch(`${filesBase}/file/${ref}?t=${encodeURIComponent(downloadKey)}`)\n      } catch (e) {\n        const ua = (navigator.userAgent.match(/(iPhone|iPad|Android|Macintosh|Windows)[^;)]*/) || [''])[0]\n        throw new Error(`${key}: ${e && e.message} | ${await diagnose(ref)} | ${ua}`)\n      }\n      if (res.status === 401 || res.status === 403) throw new Refused(`${key}: HTTP ${res.status}`)\n      if (!res.ok) throw new Error(`${key}: HTTP ${res.status}`)\n      const len = Number(res.headers.get('content-length')) || 0\n      progress.total += len\n      if (!res.body || !len) return new Uint8Array(await res.arrayBuffer())\n      const reader = res.body.getReader()\n      const chunks = []\n      let got = 0\n      for (;;) {\n        const { done, value } = await reader.read()\n        if (done) break\n        chunks.push(value)\n        got += value.length\n        progress.loaded[key] = got\n        const sum = Object.values(progress.loaded).reduce((a, b) => a + b, 0)\n        setProgress(progress.total ? sum / progress.total : 0)\n      }\n      const out = new Uint8Array(got)\n      let o = 0\n      for (const c of chunks) { out.set(c, o); o += c.length }\n      return out\n    }\n\n    // ---- input state ---------------------------------------------------------------------\n    const pad = makePad()\n    let uninstallPad = null\n    const input = { gyroOn: true, stickActive: false, stick: [0, 0], gyro: [0, 0], neutral: null, lastOri: null }\n\n    const syncAxes = () => {\n      const src = input.stickActive ? input.stick : (input.gyroOn ? input.gyro : [0, 0])\n      pad.axes[0] = src[0]\n      pad.axes[1] = src[1]\n    }\n\n    const setButton = (name, down) => {\n      const i = BUTTON[name]\n      if (i == null) return\n      pad.buttons[i] = { pressed: down, touched: down, value: down ? 1 : 0 }\n    }\n\n    // Buttons: multi-touch, each pointer tracked to the button it started on.\n    // The engine samples the pad once per frame, so a quick tap is held for MIN_HOLD_MS —\n    // otherwise down+up can both land between two polls and the press is never seen.\n    const MIN_HOLD_MS = 80\n    const held = new Map()\n    d2.on('.btn', 'pointerdown', (e, el) => {\n      e.preventDefault()\n      el.setPointerCapture?.(e.pointerId)\n      held.set(e.pointerId, { el, at: performance.now() })\n      el.classList.add('on')\n      setButton(el.dataset.b, true)\n      if (navigator.vibrate) navigator.vibrate(8)\n    })\n    const release = (e) => {\n      const h = held.get(e.pointerId)\n      if (!h) return\n      held.delete(e.pointerId)\n      const up = () => {\n        h.el.classList.remove('on')\n        setButton(h.el.dataset.b, false)\n      }\n      const left = MIN_HOLD_MS - (performance.now() - h.at)\n      if (left > 0) setTimeout(up, left)\n      else up()\n    }\n    d2.on(root, 'pointerup', release)\n    d2.on(root, 'pointercancel', release)\n\n    // Touch stick: overrides the gyro while a finger is on the left zone (good for menus).\n    const zone = $('.stickzone')\n    const base = $('.knob-base')\n    const knob = $('.knob')\n    let stickId = null\n    let origin = [0, 0]\n    const R = 56\n    d2.on(zone, 'pointerdown', (e) => {\n      e.preventDefault()\n      stickId = e.pointerId\n      zone.setPointerCapture?.(e.pointerId)\n      const r = zone.getBoundingClientRect()\n      origin = [e.clientX - r.left, e.clientY - r.top]\n      base.style.left = knob.style.left = `${origin[0]}px`\n      base.style.top = knob.style.top = `${origin[1]}px`\n      base.style.display = knob.style.display = 'block'\n      input.stickActive = true\n      input.stick = [0, 0]\n      syncAxes()\n    })\n    d2.on(zone, 'pointermove', (e) => {\n      if (e.pointerId !== stickId) return\n      const r = zone.getBoundingClientRect()\n      let dx = e.clientX - r.left - origin[0]\n      let dy = e.clientY - r.top - origin[1]\n      const m = Math.hypot(dx, dy)\n      if (m > R) { dx = (dx / m) * R; dy = (dy / m) * R }\n      knob.style.left = `${origin[0] + dx}px`\n      knob.style.top = `${origin[1] + dy}px`\n      input.stick = [dx / R, dy / R]\n      syncAxes()\n    })\n    const stickUp = (e) => {\n      if (e.pointerId !== stickId) return\n      stickId = null\n      base.style.display = knob.style.display = 'none'\n      input.stickActive = false\n      input.stick = [0, 0]\n      syncAxes()\n    }\n    d2.on(zone, 'pointerup', stickUp)\n    d2.on(zone, 'pointercancel', stickUp)\n\n    // Tilt steering from the gravity vector (devicemotion), not Euler angles: deviceorientation's\n    // beta/gamma degrade badly in the pose phones are actually held in for games (landscape, screen\n    // tipped toward the player, near beta's gimbal point). Each axis is an ANGLE OF ROTATION of\n    // gravity measured against a captured neutral pose:\n    //   steer  = rotation of gravity in the screen plane   (turning the phone like a wheel)\n    //   pitch  = rotation of gravity about the screen's horizontal axis (tipping the top away/toward)\n    // Angle deltas are unchanged if a platform reports gravity with the opposite sign (iOS vs\n    // Android), so no per-platform sign handling is needed.\n    const SENS = [{ label: 'Low', deg: 28 }, { label: 'Med', deg: 20 }, { label: 'High', deg: 14 }]\n    let sensIdx = 1\n    // The Tilt chip owns sensitivity; the instance's gyroRangeDeg is only a hint for the start level.\n    const hinted = Number(d2.param('gyroRangeDeg', 0)) || 0\n    if (hinted) sensIdx = SENS.reduce((best, s, i) => (Math.abs(s.deg - hinted) < Math.abs(SENS[best].deg - hinted) ? i : best), 0)\n    const rangeDeg = () => SENS[sensIdx].deg\n    const DEAD = 0.04\n    // libultraship ignores the inner 20% of stick travel; start our output just past it so the\n    // smallest tilt outside our own deadzone already moves the ship.\n    const LUS_DEAD = 0.21\n    const SMOOTH = 0.45 // one-pole low-pass weight of the newest sample (higher = snappier)\n\n    const screenAngle = () => {\n      const a = screen.orientation && typeof screen.orientation.angle === 'number' ? screen.orientation.angle : (window.orientation || 0)\n      return ((a % 360) + 360) % 360\n    }\n    // Device x/y (portrait frame) -> screen x (right) / y (up) for the current rotation.\n    const toScreen = (x, y) => {\n      switch (screenAngle()) {\n        case 90: return [y, -x]\n        case 180: return [-x, -y]\n        case 270: return [-y, x]\n        default: return [x, y]\n      }\n    }\n    const wrapDeg = (d) => ((d + 540) % 360) - 180\n    const shape = (deltaDeg) => {\n      const n = clamp(deltaDeg / rangeDeg(), -1, 1)\n      const a = Math.abs(n)\n      if (a < DEAD) return 0\n      const live = (a - DEAD) / (1 - DEAD)\n      return Math.sign(n) * (LUS_DEAD + (1 - LUS_DEAD) * live)\n    }\n    const smoothed = [0, 0]\n    const applyTilt = (steerDeg, pitchDeg) => {\n      input.lastOri = [steerDeg, pitchDeg]\n      if (!input.neutral) input.neutral = [steerDeg, pitchDeg]\n      const invertY = !!d2.param('invertY', false)\n      const sx = shape(wrapDeg(steerDeg - input.neutral[0]))\n      // Default: tipping the top edge away climbs (flight-sim style); invertY flips it back.\n      const sy = shape(wrapDeg(pitchDeg - input.neutral[1])) * (invertY ? 1 : -1)\n      smoothed[0] += SMOOTH * (sx - smoothed[0])\n      smoothed[1] += SMOOTH * (sy - smoothed[1])\n      input.gyro = [smoothed[0], smoothed[1]]\n      syncAxes()\n    }\n\n    let gotMotion = false\n    d2.on(window, 'devicemotion', (e) => {\n      const g = e.accelerationIncludingGravity\n      if (!g || g.x == null || g.y == null || g.z == null) return\n      gotMotion = true\n      const [gx, gy] = toScreen(g.x, g.y)\n      const deg = 180 / Math.PI\n      // Treat g as pointing down; with the opposite convention both angles shift by 180 degrees,\n      // which the neutral-relative, wrapped deltas cancel out.\n      applyTilt(Math.atan2(gx, -gy) * deg, Math.atan2(g.z, -gy) * deg)\n    })\n    // Fallback for browsers that only expose orientation angles.\n    d2.on(window, 'deviceorientation', (e) => {\n      if (gotMotion || e.beta == null || e.gamma == null) return\n      const [x, y] = toScreen(e.gamma, -e.beta)\n      applyTilt(x, -y)\n    })\n    d2.on(window, 'orientationchange', () => { input.neutral = null })\n\n    d2.on('.chip', 'click', (e, el) => {\n      const act = el.dataset.act\n      if (act === 'gyro') {\n        input.gyroOn = !input.gyroOn\n        el.classList.toggle('on', input.gyroOn)\n        syncAxes()\n      } else if (act === 'sens') {\n        sensIdx = (sensIdx + 1) % SENS.length\n        el.textContent = `Tilt: ${SENS[sensIdx].label}`\n      } else if (act === 'center') {\n        input.neutral = input.lastOri ? [...input.lastOri] : null\n      } else if (act === 'hud') {\n        const hidden = root.querySelectorAll('.btn, .stickzone')\n        const now = el.textContent === 'Hide'\n        hidden.forEach((n) => { n.style.visibility = now ? 'hidden' : '' })\n        el.textContent = now ? 'Show' : 'Hide'\n      }\n    })\n\n    // ---- engine boot ---------------------------------------------------------------------\n    let blobs = []\n    let started = false\n    let files = null\n    let engine = null\n    let crashed = false\n\n    // Last engine log lines, shown on the crash screen so a failure can be reported.\n    const logTail = []\n    const remember = (line) => { logTail.push(String(line).slice(0, 300)); if (logTail.length > 40) logTail.shift() }\n\n    // SDL's web audio lives on Module.SDL2.audioContext. When the engine dies, its audio callback\n    // keeps replaying the last buffer (the stuck shooting sound), so close the context outright.\n    const audioContext = () => (engine && engine.SDL2 && engine.SDL2.audioContext) || null\n    const stopAudio = () => { try { const ac = audioContext(); if (ac && ac.state !== 'closed') ac.close() } catch (e) {} }\n\n    const crash = (why) => {\n      if (crashed) return\n      crashed = true\n      stopAudio()\n      try { engine && engine.pauseMainLoop && engine.pauseMainLoop() } catch (e) {}\n      const box = $('.crash')\n      box.querySelector('.crash-why').textContent = String(why || 'Unknown error').slice(0, 300)\n      box.querySelector('.crash-log').textContent = logTail.slice(-25).join('\\n')\n      box.hidden = false\n      console.error('[dataFox2] crashed:', why)\n    }\n    d2.on('.restart', 'click', () => location.reload())\n\n    // Only engine failures count: the host page can raise its own unrelated errors.\n    const isEngineError = (err, filename) =>\n      String(filename || '').startsWith('blob:') ||\n      (err && (err instanceof WebAssembly.RuntimeError || /Aborted|RuntimeError|wasm|unreachable|memory access out of bounds/i.test(String(err.message || err))))\n    d2.on(window, 'error', (e) => { if (engine && isEngineError(e.error || e.message, e.filename)) crash(e.message || e.error) })\n    d2.on(window, 'unhandledrejection', (e) => { if (engine && isEngineError(e.reason)) crash(e.reason && (e.reason.message || e.reason)) })\n    d2.on(canvas, 'webglcontextlost', (e) => {\n      e.preventDefault()\n      crash('The browser dropped the graphics context (usually low memory on the phone).')\n    })\n    // Hidden tab: silence it; closing/leaving: shut audio down so nothing outlives the page.\n    d2.on(document, 'visibilitychange', () => {\n      const ac = audioContext()\n      if (!ac || crashed) return\n      try { document.hidden ? ac.suspend() : ac.resume() } catch (e) {}\n    })\n    d2.on(window, 'pagehide', stopAudio)\n    d2.onUnmount(stopAudio)\n\n    const showLogin = (msg) => {\n      loginForm.hidden = false\n      goBtn.hidden = true\n      errEl.textContent = msg || ''\n      setStatus('Sign in to play')\n    }\n\n    d2.on(loginForm, 'submit', async (e) => {\n      e.preventDefault()\n      const fd = new FormData(loginForm)\n      const btn = loginForm.querySelector('.signin')\n      btn.disabled = true\n      errEl.textContent = ''\n      try {\n        const auth = d2.auth(d2.param('auth'))\n        if (!auth) throw new Error('Login is not configured')\n        // AuthenticationStrategy.login takes { credentials, metadata? } and merges credentials in.\n        await auth.login({\n          credentials: { email: String(fd.get('email') || '').trim(), password: String(fd.get('password') || '') },\n        })\n        loginForm.hidden = true\n        goBtn.hidden = false\n        progress.total = 0\n        progress.loaded = {}\n        preload()\n      } catch (err) {\n        errEl.textContent = 'Sign-in failed. Check your email and password.'\n      } finally {\n        btn.disabled = false\n      }\n    })\n\n    const preload = async () => {\n      try {\n        if (!window.WebGL2RenderingContext) throw new Error('This browser has no WebGL2.')\n        setStatus('Downloading game files…')\n        errEl.textContent = ''\n        downloadKey = await getDownloadKey()\n        const [js, wasm, main, port] = await Promise.all([\n          fetchBytes('js', refs.js), fetchBytes('wasm', refs.wasm),\n          fetchBytes('main', refs.main), fetchBytes('port', refs.port),\n        ])\n        files = { js, wasm, main, port }\n        setProgress(1)\n        setStatus('Ready. Tap Start (sound and motion need a tap).')\n        goBtn.disabled = false\n      } catch (e) {\n        if (e instanceof Refused) showLogin()\n        else fail(String(e && e.message || e))\n      }\n    }\n\n    const askMotionPermission = async () => {\n      // iOS gates motion behind a prompt that must come from a tap; ask for both event types.\n      for (const Ev of [window.DeviceMotionEvent, window.DeviceOrientationEvent]) {\n        if (Ev && typeof Ev.requestPermission === 'function') {\n          try { await Ev.requestPermission() } catch (e) { /* declined; the touch stick still works */ }\n        }\n      }\n    }\n\n    const enterFullscreen = async () => {\n      const el = d2.root.host || root\n      try { if (el.requestFullscreen && !document.fullscreenElement) await el.requestFullscreen({ navigationUI: 'hide' }) } catch (e) {}\n      try { await screen.orientation?.lock?.('landscape') } catch (e) {}\n      try { await navigator.wakeLock?.request?.('screen') } catch (e) {}\n    }\n\n    const boot = async () => {\n      if (started || !files) return\n      started = true\n      goBtn.disabled = true\n      setStatus('Starting engine…')\n      await Promise.all([askMotionPermission(), enterFullscreen()])\n      $('.rotate').classList.add('armed')\n\n      const jsUrl = URL.createObjectURL(new Blob([files.js], { type: 'text/javascript' }))\n      blobs.push(jsUrl)\n      try {\n        if (typeof window.createStarship !== 'function') await new Promise((resolve, reject) => {\n          // A private blob, not a CDN library — d2.lib does not apply here.\n          const s = document.createElement('script')\n          s.src = jsUrl\n          s.onload = resolve\n          s.onerror = () => reject(new Error('Engine script failed to load'))\n          document.head.appendChild(s)\n        })\n        const factory = window.createStarship\n        if (typeof factory !== 'function') throw new Error('Engine factory missing')\n\n        const Module = await factory({\n          canvas,\n          noInitialRun: true,\n          wasmBinary: files.wasm,\n          print: (t) => { remember(t); console.log('[starship]', t) },\n          printErr: (t) => { remember(t); console.warn('[starship]', t) },\n          onAbort: (what) => crash(`Engine aborted: ${what}`),\n          preRun: [(M) => {\n            // SDL addresses its canvas as '#canvas'; the canvas lives in a shadow root that\n            // document.querySelector cannot see, so map the selector to the element directly.\n            if (M.specialHTMLTargets) M.specialHTMLTargets['#canvas'] = canvas\n            M.FS.writeFile('/sf64.o2r', files.main)\n            M.FS.writeFile('/starship.o2r', files.port)\n          }],\n        })\n        engine = Module\n        files = null // let the download buffers go; the engine holds its own copies\n        overlay.hidden = true\n        hud.hidden = false\n        // Install before main so SDL's joystick enumeration at init already sees the pad.\n        uninstallPad = installVirtualPad(pad)\n        canvas.focus()\n        try {\n          Module.callMain([])\n        } catch (e) {\n          // emscripten_set_main_loop(..., simulate_infinite_loop=1) unwinds out of main.\n          if (!(e === 'unwind' || (e && e.name === 'ExitStatus'))) throw e\n        }\n        // And announce it again now that SDL's gamepadconnected listener is registered.\n        const ev = new Event('gamepadconnected')\n        ev.gamepad = pad\n        window.dispatchEvent(ev)\n      } catch (e) {\n        if (e && (e.name === 'ExitStatus' || e === 'unwind')) return\n        overlay.hidden = false\n        fail(String(e && e.message || e))\n      }\n    }\n\n    d2.on('.start-btn', 'click', boot)\n\n    // SDL owns the canvas size: it follows the canvas's CSS box on every window resize.\n\n    d2.onUnmount(() => {\n      uninstallPad?.()\n      blobs.forEach((u) => URL.revokeObjectURL(u))\n    })\n\n    preload()\n  },\n}\n"}},"auth":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"auth"}}}},"engineJs":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"engineJs"}}}},"engineWasm":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"engineWasm"}}}},"filesBase":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"filesBase"}}}},"gyroRangeDeg":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"gyroRangeDeg"}}}},"invertY":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"invertY"}}}},"mainO2r":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"mainO2r"}}}},"portO2r":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"portO2r"}}}},"systemDomain":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"systemDomain"}}}}}}}}}},{"_id":"67d5e1e8698690841856d023","component":"data2-core-types@Component","system":"6172b957976026c073ab9058","params":{"component":"JfsOzWC5Bl9CZyvsPoJgN","package":"lucca-playground","definition":{"params":{"portions":{"params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"transform-bricks"}}},"component":"data2-core-types@Reference"}},"component":"data2-core-functions@StringConcat"},"inlineParamsControl":{"component":"data2-core-primitives@Boolean","params":{"content":false}}}},{"_id":"5ed9bed7b9e0891ce913e884","component":"data2-core-types@Component","params":{"package":"data2-core","component":"DataSource","renderable":{"dimensions":{"disabled":false,"component":"data2-core-types@Dimensions","params":{"width":1366,"exactWidth":false,"exactHeight":false,"height":768}}}}},{"_id":"5f472ae463c680de4ff296e9","component":"data2-core-types@Component","params":{"package":"data2-core","component":"Data2TableDataSource","extends":"data2-core@DataSource","renderable":{"dimensions":{"disabled":false,"component":"data2-core-types@Dimensions","params":{"width":1366,"exactWidth":false,"exactHeight":false,"height":768}}}}}]}},{"reference":"ExternalComponentInstancesTableDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[]}},{"_id":"5f060d9b1ca96def42f3c40a","displayName":"Builder AuthStrategy","component":"data2-core@AuthenticationStrategy","system":"5e3b796d6228227ca637d0f4","params":{"credentials":[{"component":"data2-core-types@DataSetColumnReference","params":{"column":"email"}},{"component":"data2-core-types@DataSetColumnReference","params":{"column":"password"}},{"component":"data2-core-types@DataSetColumnReference","params":{"column":"status"}}],"verifier":{"component":"data2-core-types@DataSetReference","params":{"reference":"UsersDataSet"}},"sessionShared":{"component":"data2-core-primitives@Boolean","params":{"content":false}},"password":{"disabled":false,"component":"data2-core-types@DataSetColumnReference","params":{"column":"password"}},"email":{"disabled":false,"component":"data2-core-types@DataSetColumnReference","params":{"column":"email"}},"resetPassword":{"resetEmail":{"artboard":{"params":{"preventEvaluation":true,"reference":"nR6Q9d7qdaPtZhJ28Q8Dy"},"component":"data2-core-types@ArtboardReference"}},"resetSuccessEmail":{"artboard":{"params":{"preventEvaluation":true,"reference":"yPCJBTKo8q1Mw0B_3Kpwl"},"component":"data2-core-types@ArtboardReference"}}},"session":[{"component":"data2-core-types@DataSetColumnReference","params":{"column":"name"}},{"component":"data2-core-types@DataSetColumnReference","params":{"column":"email"}},{"component":"data2-core-types@DataSetColumnReference","params":{"column":"password"}},{"component":"data2-core-types@DataSetColumnReference","params":{"column":"status"}}],"sessionMetadata":{"params":{"actions":[{"params":{"compare":{"params":{"subject":{"params":{"components":[{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"$user._id"}}}},{"params":{"comparisonSubject":{"component":"data2-core-primitives@String","params":{"content":"active"}},"subject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"$user.status"}}}}},"component":"data2-core-comparison-operators@Equal","displayName":"is active user"}]},"component":"data2-core-logical-operators@And","displayName":"is active and has _id"}},"component":"data2-core-logical-operators@Not"},"then":{"params":{"message":{"component":"data2-core-primitives@String","params":{"content":"Invalid Credentials"}},"code":{"component":"data2-core-primitives@String","params":{"content":"401"}}},"component":"data2-core-types@Error"}},"component":"data2-core-flow@If","displayName":"check $user"},{"params":{"action":{"params":{"pattern":{"component":"data2-core-primitives@String","params":{"content":"@data2.app"}},"subject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"$user.email"}}}}},"component":"data2-core-functions@Includes","displayName":"isData2User"},"resultName":{"component":"data2-core-primitives@String","params":{"content":"is_data2_user"}}},"component":"data2-core-flow@Step"},{"params":{"action":{"params":{"pattern":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"$metadata.d2_app"}}}},"subject":[{"component":"data2-core-primitives@String","params":{"content":"management"}}]},"component":"data2-core-functions@Includes","displayName":"isData2App Exception"},"resultName":{"component":"data2-core-primitives@String","params":{"content":"is_data2_app_exception"}}},"component":"data2-core-flow@Step"},{"params":{"resultName":{"component":"data2-core-primitives@String","params":{"content":"has_access"}},"action":{"params":{"compare":{"params":{"components":[{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"is_data2_user"}}}},{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"is_data2_app_exception"}}}}]},"component":"data2-core-logical-operators@Or"},"else":{"params":{"actions":[{"params":{"actions":[{"params":{"compare":{"params":{"subject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"$metadata.system"}}}}},"component":"data2-core-logical-operators@Not"},"then":{"params":{"message":{"component":"data2-core-primitives@String","params":{"content":"Invalid Credentials"}},"code":{"component":"data2-core-primitives@String","params":{"content":"401"}}},"component":"data2-core-types@Error"}},"component":"data2-core-flow@If","displayName":"check $metadata.system"},{"params":{"module":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"WuUa-VKHhW9uYU0E3-mJ8"}},"prop":"filter","args":[{"_id":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"$metadata.system"}}}}}]},"component":"data2-core-types@Interaction"},{"params":{"compare":{"params":{"subject":{"component":"data2-core-types@Getter","params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"WuUa-VKHhW9uYU0E3-mJ8"}},"prop":{"component":"data2-core-primitives@String","params":{"content":"data.records.length"}}}}},"component":"data2-core-logical-operators@Not","displayName":"systems not found"},"then":{"params":{"message":{"component":"data2-core-primitives@String","params":{"content":"Invalid Credentials"}},"code":{"component":"data2-core-primitives@String","params":{"content":"401"}}},"component":"data2-core-types@Error"}},"component":"data2-core-flow@If","displayName":"check system found"},{"params":{"actions":[{"params":{"module":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"Ok6Tsu5XkS7r8PyW_7TyW"}},"prop":"filter","args":[{"user":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"$user._id"}}}},"organization":{"component":"data2-core-types@Getter","params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"WuUa-VKHhW9uYU0E3-mJ8"}},"prop":{"component":"data2-core-primitives@String","params":{"content":"cursor.current.organization._id"}}}}}]},"component":"data2-core-types@Interaction"},{"params":{"module":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"NqKAY34n2ffFL64zamp7t"}},"prop":"filter","args":[{"user":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"$user._id"}}}},"system":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"$metadata.system"}}}}}]},"component":"data2-core-types@Interaction"}]},"component":"data2-core-flow@Parallel"}]},"component":"data2-core-flow@Series","displayName":"common setup"},{"params":{"then":{"params":{"content":true},"component":"data2-core-primitives@Boolean"},"else":{"params":{"content":false},"component":"data2-core-primitives@Boolean"},"compare":{"params":{"components":{"params":{"array":{"params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"WuUa-VKHhW9uYU0E3-mJ8"}},"prop":{"component":"data2-core-primitives@String","params":{"content":"data.records"}}},"component":"data2-core-types@Getter"},"mapper":{"disabled":false,"component":"data2-core-functions@Function","params":{"parameters":[{"component":"data2-core-primitives@String","params":{"content":"system"}}],"body":{"params":{"expression":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"system.visibility"}}}},"defaultCase":{"params":{"content":false},"component":"data2-core-primitives@Boolean"},"cases":[{"component":"data2-core-flow@Case","params":{"expression":{"component":"data2-core-primitives@String","params":{"content":"public"}},"value":{"params":{"components":[{"params":{"array":{"params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"Ok6Tsu5XkS7r8PyW_7TyW"}},"prop":{"component":"data2-core-primitives@String","params":{"content":"data.records"}}},"component":"data2-core-types@Getter"},"filter":{"disabled":false,"component":"data2-core-functions@Function","params":{"parameters":[{"component":"data2-core-primitives@String","params":{"content":"member"}}],"body":{"params":{"components":[{"params":{"subject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"member.organization._id"}}}},"comparisonSubject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"system.organization._id"}}}}},"component":"data2-core-comparison-operators@Equal","displayName":"is org member"},{"params":{"pattern":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"member.role"}}}},"subject":[{"component":"data2-core-primitives@String","params":{"content":"owner"}},{"component":"data2-core-primitives@String","params":{"content":"billing-owner"}},{"component":"data2-core-primitives@String","params":{"content":"admin"}},{"component":"data2-core-primitives@String","params":{"content":"member"}}]},"component":"data2-core-functions@Includes","displayName":"has role with access"}]},"component":"data2-core-logical-operators@And","displayName":"has org member role with access"}}}},"component":"data2-core-functions@ArrayFind","displayName":"is org member"},{"params":{"components":[{"params":{"array":{"params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"Ok6Tsu5XkS7r8PyW_7TyW"}},"prop":{"component":"data2-core-primitives@String","params":{"content":"data.records"}}},"component":"data2-core-types@Getter"},"filter":{"disabled":false,"component":"data2-core-functions@Function","params":{"parameters":[{"component":"data2-core-primitives@String","params":{"content":"member"}}],"body":{"params":{"components":[{"params":{"subject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"member.organization._id"}}}},"comparisonSubject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"system.organization._id"}}}}},"component":"data2-core-comparison-operators@Equal","displayName":"is org member"},{"params":{"subject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"member.role"}}}},"comparisonSubject":{"component":"data2-core-primitives@String","params":{"content":"collaborator"}}},"component":"data2-core-comparison-operators@Equal","displayName":"is collaborator"}]},"component":"data2-core-logical-operators@And","displayName":"is org member and collaborator"}}}},"component":"data2-core-functions@ArrayFind","displayName":"is collaborator"},{"params":{"array":{"params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"NqKAY34n2ffFL64zamp7t"}},"prop":{"component":"data2-core-primitives@String","params":{"content":"data.records"}}},"component":"data2-core-types@Getter"},"filter":{"disabled":false,"component":"data2-core-functions@Function","params":{"parameters":[{"component":"data2-core-primitives@String","params":{"content":"access"}}],"body":{"params":{"subject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"access.system._id"}}}},"comparisonSubject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"system._id"}}}}},"component":"data2-core-comparison-operators@Equal"}}}},"component":"data2-core-functions@ArrayFind","displayName":"has system access"}]},"component":"data2-core-logical-operators@And","displayName":"is collaborator and has system access"}]},"component":"data2-core-logical-operators@Or"}}},{"component":"data2-core-flow@Case","params":{"expression":{"component":"data2-core-primitives@String","params":{"content":"private"}},"value":{"params":{"components":[{"params":{"array":{"params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"Ok6Tsu5XkS7r8PyW_7TyW"}},"prop":{"component":"data2-core-primitives@String","params":{"content":"data.records"}}},"component":"data2-core-types@Getter"},"filter":{"disabled":false,"component":"data2-core-functions@Function","params":{"parameters":[{"component":"data2-core-primitives@String","params":{"content":"member"}}],"body":{"params":{"components":[{"params":{"subject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"member.organization._id"}}}},"comparisonSubject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"system.organization._id"}}}}},"component":"data2-core-comparison-operators@Equal","displayName":"is org member"},{"params":{"pattern":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"member.role"}}}},"subject":[{"component":"data2-core-primitives@String","params":{"content":"owner"}},{"component":"data2-core-primitives@String","params":{"content":"admin"}}]},"component":"data2-core-functions@Includes","displayName":"is owner/admin"}]},"component":"data2-core-logical-operators@And","displayName":"has org member role with access"}}}},"component":"data2-core-functions@ArrayFind","displayName":"is owner/admin"},{"params":{"components":[{"params":{"array":{"params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"Ok6Tsu5XkS7r8PyW_7TyW"}},"prop":{"component":"data2-core-primitives@String","params":{"content":"data.records"}}},"component":"data2-core-types@Getter"},"filter":{"disabled":false,"component":"data2-core-functions@Function","params":{"parameters":[{"component":"data2-core-primitives@String","params":{"content":"member"}}],"body":{"params":{"subject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"member.organization._id"}}}},"comparisonSubject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"system.organization._id"}}}}},"component":"data2-core-comparison-operators@Equal"}}}},"component":"data2-core-functions@ArrayFind","displayName":"is org member"},{"params":{"array":{"params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"NqKAY34n2ffFL64zamp7t"}},"prop":{"component":"data2-core-primitives@String","params":{"content":"data.records"}}},"component":"data2-core-types@Getter"},"filter":{"disabled":false,"component":"data2-core-functions@Function","params":{"parameters":[{"component":"data2-core-primitives@String","params":{"content":"access"}}],"body":{"params":{"subject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"access.system._id"}}}},"comparisonSubject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"system._id"}}}}},"component":"data2-core-comparison-operators@Equal"}}}},"component":"data2-core-functions@ArrayFind","displayName":"has system access"}]},"component":"data2-core-logical-operators@And","displayName":"is member and has system access"}]},"component":"data2-core-logical-operators@Or","displayName":"is owner/admin or has system access"}}}]},"component":"data2-core-flow@Switch"}}}},"component":"data2-core-functions@ArrayMap"}},"component":"data2-core-logical-operators@And","displayName":"has org member for all systems in request"}},"component":"data2-core-flow@If"}]},"component":"data2-core-flow@Series"},"then":{"params":{"content":true},"component":"data2-core-primitives@Boolean"}},"component":"data2-core-flow@If"}},"component":"data2-core-flow@Step"},{"params":{"then":{"system":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"$metadata.system"}}}}},"else":{"params":{"message":{"component":"data2-core-primitives@String","params":{"content":"Invalid Credentials"}},"code":{"component":"data2-core-primitives@String","params":{"content":"401"}}},"component":"data2-core-types@Error"},"compare":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"has_access"}}}}},"component":"data2-core-flow@If"}]},"component":"data2-core-flow@Series"},"sessionExpiresIn":{"component":"data2-core-primitives@Number","params":{"content":604800}},"sessionRenewal":{"component":"data2-core-primitives@Boolean","params":{"content":true}},"identityProviders":[{"provider":{"component":"data2-core-primitives@String","params":{"content":"GOOGLE"}},"clientId":{"component":"data2-core-primitives@String","params":{"content":"55470861603-22pvfc1t49lp8cjnbfk71s5ct9hp1q1v.apps.googleusercontent.com"}}}]},"reference":"Data2BuilderAuthentication","updatedAt":"2026-08-06T19:31:23.557Z"},{"_id":"5f060ee51ca96def42f3fbd1","component":"data2-core@DataSet","system":"5e3b796d6228227ca637d0f4","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"UsersTableDataSource"}},"pagination":{"size":{"component":"data2-core-primitives@Number","params":{"content":1}}}},"reference":"UsersDataSet","updatedAt":"2026-03-04T21:16:40.291Z"},{"_id":"68f938cf5fb002e33752a43d","displayName":"InternalApplicationsAuthenticationStrategy","component":"data2-core@AuthenticationStrategy","system":"5e3b796d6228227ca637d0f4","params":{"resetPassword":{"resetEmail":{"artboard":{"params":{"preventEvaluation":true},"component":"data2-core-types@ArtboardReference"}},"resetSuccessEmail":{"artboard":{"params":{"preventEvaluation":true},"component":"data2-core-types@ArtboardReference"}}},"verifier":{"disabled":false,"component":"data2-core-types@DataSetReference","params":{"reference":"InternalApplicationsVerifierDataSet"}},"password":{"disabled":false,"component":"data2-core-types@DataSetColumnReference","params":{"column":"secret"}},"credentials":[{"component":"data2-core-types@FieldReference","params":{"key":"application"}},{"component":"data2-core-types@FieldReference","params":{"key":"secret"}},{"component":"data2-core-types@FieldReference","params":{"key":"status"}}],"session":[{"component":"data2-core-types@FieldReference","params":{"key":"application"}},{"component":"data2-core-types@FieldReference","params":{"key":"secret"}},{"component":"data2-core-types@FieldReference","params":{"key":"status"}}]},"reference":"InternalApplicationsAuthenticationStrategy","updatedAt":"2025-10-22T20:08:54.442Z","createdAt":"2025-10-22T20:04:31.198Z"},{"component":"data2-core@DataSet","reference":"SessionsDataSet","params":{"requiresConfirmation":false,"pk":"key","datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"data2sessionsdatasource"}}}},{"reference":"UsersTableDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[]}},{"component":"data2-core@ServerSessionsDataSource","reference":"data2sessionsdatasource","params":{}},{"_id":"5f1600b70528b92601890f75","component":"data2-core@DataSet","system":"5e3b796d6228227ca637d0f4","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"ServiceComponentInstancesTableDataSource"}},"pk":"_id","autoEdit":true,"master":{"params":{"dataset":{"component":"data2-core-types@DataSetReference","params":{"reference":"SystemsDataSet"}},"localField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"system"}},"masterField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"_id"}}},"component":"data2-core-types@DataSetMaster"},"autoSelect":{"component":"data2-core-primitives@Boolean","params":{"content":false}},"policies":{"params":{"allowGet":{"component":"data2-core-primitives@Boolean","params":{"content":true}}},"component":"data2@hkXy-UhMjhyfptdE6_fpW"}},"reference":"ServiceComponentInstancesDataSet","updatedAt":"2025-11-06T21:07:20.208Z","createdAt":"2020-07-20T20:38:15.436Z"},{"_id":"5f1602190528b92601890f7d","component":"data2-core@DataSet","system":"5e3b796d6228227ca637d0f4","params":{"pagination":{"size":40000},"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"ComponentInstancesTableDataSource"}},"sort":{"_id":1},"pk":"_id","autoEdit":true,"master":{"dataset":{"component":"data2-core-types@DataSetReference","params":{"reference":"SystemsDataSet"}},"localField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"system"}},"masterField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"_id"}}},"autoSelect":{"component":"data2-core-primitives@Boolean","params":{"content":false}},"policies":{"params":{"allowGet":{"component":"data2-core-primitives@Boolean","params":{"content":true}}},"component":"data2@hkXy-UhMjhyfptdE6_fpW"}},"reference":"ComponentInstancesDataSet","updatedAt":"2025-11-06T21:07:07.089Z","createdAt":"2020-07-20T20:44:09.350Z"},{"_id":"5f16017f0528b92601890f7a","component":"data2-core@DataSet","system":"5e3b796d6228227ca637d0f4","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"FileComponentInstancesTableDataSource"}},"pk":"_id","autoEdit":true,"filter":{"domain":{"component":"data2-core-types@Reference","params":{"reference":"clientApplicationDomain"}}},"master":{"params":{"dataset":{"component":"data2-core-types@DataSetReference","params":{"reference":"SystemsDataSet"}},"localField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"system"}},"masterField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"_id"}}},"component":"data2-core-types@DataSetMaster"},"autoSelect":{"component":"data2-core-primitives@Boolean","params":{"content":false}}},"reference":"FileComponentInstancesDataSet","updatedAt":"2023-08-03T16:08:18.193Z","createdAt":"2020-07-20T20:41:35.797Z"},{"_id":"5f1601990528b92601890f7b","component":"data2-core@DataSet","system":"5e3b796d6228227ca637d0f4","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"ApplicationsTableDataSource"}},"pk":"_id","autoEdit":{"component":"data2-core-primitives@Boolean","params":{"content":true}},"master":{"dataset":{"component":"data2-core-types@DataSetReference","params":{"reference":"SystemsDataSet"}},"localField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"system"}},"masterField":{"params":{"column":"_id"},"component":"data2-core-types@DataSetColumnReference"}},"autoSelect":{"component":"data2-core-primitives@Boolean","params":{"content":false}}},"reference":"ApplicationsDataSet","updatedAt":"2022-12-13T21:51:13.995Z","createdAt":"2020-07-20T20:42:01.657Z"},{"_id":"5f16172a0528b92601890f85","component":"data2-core@DataSet","system":"5e3b796d6228227ca637d0f4","params":{"pagination":{"size":{"component":"data2-core-primitives@Number","params":{"content":10000}}},"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"DataComponentInstancesTableDataSource"}},"pk":"_id","autoEdit":true,"master":{"params":{"dataset":{"component":"data2-core-types@DataSetReference","params":{"reference":"SystemsDataSet"}},"localField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"system"}},"masterField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"_id"}}},"component":"data2-core-types@DataSetMaster"},"autoSelect":{"component":"data2-core-primitives@Boolean","params":{"content":false}},"policies":{"params":{"allowGet":{"component":"data2-core-primitives@Boolean","params":{"content":true}}},"component":"data2@hkXy-UhMjhyfptdE6_fpW"}},"reference":"DataComponentInstancesDataSet","updatedAt":"2025-11-06T21:07:12.831Z","createdAt":"2020-07-20T22:14:02.097Z"},{"_id":"60ddc1bbda3861ca65407c5a","component":"data2-core@DataSet","system":"5e3b796d6228227ca637d0f4","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"FileComponentInstancesTableDataSource"}},"pk":"_id","autoEdit":true,"filter":{"domain":{"component":"data2-core-types@Reference","params":{"reference":"clientApplicationDomain"}}},"pagination":{"size":1},"master":{"params":{"dataset":{"component":"data2-core-types@DataSetReference","params":{"reference":"SystemsDataSet"}},"localField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"system"}},"masterField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"_id"}}},"component":"data2-core-types@DataSetMaster"}},"reference":"OneFileComponentInstancesDataSet","updatedAt":"2022-02-09T19:44:38.036Z","createdAt":"2020-07-20T20:41:35.797Z"},{"_id":"63ee3d38b8c9838c62fb05fb","displayName":"DriveFileComponentInstancesDataSet","component":"data2-core@DataSet","system":"5e3b796d6228227ca637d0f4","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"FileComponentInstancesTableDataSource"}},"pk":"_id","autoEdit":true,"filter":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core@Drive"}}},"master":{"params":{"dataset":{"component":"data2-core-types@DataSetReference","params":{"reference":"SystemsDataSet"}},"localField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"system"}},"masterField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"_id"}}},"component":"data2-core-types@DataSetMaster"},"autoSelect":{"component":"data2-core-primitives@Boolean","params":{"content":false}}},"reference":"DriveFileComponentInstancesDataSet","updatedAt":"2025-12-04T21:58:32.367Z","createdAt":"2023-02-16T14:27:07.797Z"},{"reference":"ServiceComponentInstancesTableDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[]}},{"reference":"ComponentInstancesTableDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[{"_id":"69483e4605714c8c39688670","parent":"69483e4605714c8c3968866c","component":"data2-core-primitives@Block","system":"6172b957976026c073ab9058","displayName":"1","params":{"border":{"component":"data2-core-types@Border","params":{"color":"#808080","radius":{},"width":{"left":"0","top":"0","right":"0","bottom":"0"}}},"fill":{"params":{"backgrounds":[{"params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"rgba(103,83,209,1)"}}},"component":"data2-core-types@Background"}]},"component":"data2-core-types@Fill"},"dimensions":{"component":"data2-core-types@Dimensions","params":{"height":90,"exactHeight":90,"exactWidth":90,"width":90}},"position":{"component":"data2-core-types@Position","params":{"freeTransform":true,"exactX":false,"exactY":false,"x":96,"y":2}},"opacity":{"disabled":false,"component":"data2-core-types@Opacity","params":{"value":100}},"transitions":[{"component":"data2-core-types@Transition","params":{"scope":"ALL","duration":"100","delay":"0","easing":"cubic-bezier(0.7368421052631579,0.21437499999999998,0.7828947368421053,0.262375)"}}]},"index":0,"reference":"1"},{"_id":"69483e4605714c8c3968866d","parent":"69483e4605714c8c3968866c","component":"data2-core-primitives@Block","system":"6172b957976026c073ab9058","displayName":"2","params":{"border":{"component":"data2-core-types@Border","params":{"color":"#808080","radius":{},"width":{"left":"0","top":"0","right":"0","bottom":"0"}}},"fill":{"params":{"backgrounds":[{"params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"rgba(103,83,209,1)"}}},"component":"data2-core-types@Background"}]},"component":"data2-core-types@Fill"},"dimensions":{"component":"data2-core-types@Dimensions","params":{"height":90,"exactHeight":90,"exactWidth":90,"width":90}},"position":{"component":"data2-core-types@Position","params":{"freeTransform":true,"exactX":1.8199999999999998,"exactY":1.63,"x":2,"y":2}},"opacity":{"disabled":false,"component":"data2-core-types@Opacity","params":{"value":100}},"transitions":[{"component":"data2-core-types@Transition","params":{"scope":"ALL","duration":"100","delay":"0","easing":"cubic-bezier(0.7368421052631579,0.21437499999999998,0.7828947368421053,0.262375)"}}]},"index":1,"reference":"2"},{"_id":"69483e4605714c8c3968866e","parent":"69483e4605714c8c3968866c","component":"data2-core-primitives@Block","system":"6172b957976026c073ab9058","displayName":"3","params":{"border":{"component":"data2-core-types@Border","params":{"color":"#808080","radius":{},"width":{"left":"0","top":"0","right":"0","bottom":"0"}}},"fill":{"params":{"backgrounds":[{"params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"rgba(103,83,209,1)"}}},"component":"data2-core-types@Background"}]},"component":"data2-core-types@Fill"},"dimensions":{"component":"data2-core-types@Dimensions","params":{"height":90,"exactHeight":90,"exactWidth":90,"width":90}},"position":{"component":"data2-core-types@Position","params":{"freeTransform":true,"exactX":1.96,"exactY":96.42,"x":2,"y":96}},"opacity":{"disabled":false,"component":"data2-core-types@Opacity","params":{"value":100}},"transitions":[{"component":"data2-core-types@Transition","params":{"scope":"ALL","duration":"100","delay":"0","easing":"cubic-bezier(0.7368421052631579,0.21437499999999998,0.7828947368421053,0.262375)"}}]},"index":2,"reference":"3"},{"_id":"69483e4605714c8c3968866f","parent":"69483e4605714c8c3968866c","component":"data2-core-primitives@Block","system":"6172b957976026c073ab9058","displayName":"4","params":{"border":{"component":"data2-core-types@Border","params":{"color":"#808080","radius":{},"width":{"left":"0","top":"0","right":"0","bottom":"0"}}},"fill":{"params":{"backgrounds":[{"params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"rgba(103,83,209,1)"}}},"component":"data2-core-types@Background"}]},"component":"data2-core-types@Fill"},"dimensions":{"component":"data2-core-types@Dimensions","params":{"height":90,"exactHeight":90,"exactWidth":90,"width":90}},"position":{"component":"data2-core-types@Position","params":{"freeTransform":true,"exactX":1.75,"exactY":189.75,"x":2,"y":190}},"text":"","opacity":{"disabled":false,"component":"data2-core-types@Opacity","params":{"value":100}},"transitions":[{"component":"data2-core-types@Transition","params":{"scope":"ALL","duration":"100","delay":"0","easing":"cubic-bezier(0.7368421052631579,0.21437499999999998,0.7828947368421053,0.262375)"}}]},"index":3,"reference":"4"},{"_id":"69483e4605714c8c3968865f","parent":"69483e4605714c8c3968865c","component":"data2-core-primitives@Block","system":"6172b957976026c073ab9058","displayName":"5","params":{"border":{"component":"data2-core-types@Border","params":{"color":"#808080","radius":{},"width":{"left":"0","top":"0","right":"0","bottom":"0"}}},"fill":{"params":{"backgrounds":[{"params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"rgba(103,83,209,1)"}}},"component":"data2-core-types@Background"}]},"component":"data2-core-types@Fill"},"dimensions":{"component":"data2-core-types@Dimensions","params":{"height":90,"exactHeight":90,"exactWidth":90,"width":90}},"position":{"component":"data2-core-types@Position","params":{"freeTransform":true,"exactX":1.75,"exactY":189.75,"x":2,"y":190}},"text":"","opacity":{"disabled":false,"component":"data2-core-types@Opacity","params":{"value":100}},"transitions":[{"component":"data2-core-types@Transition","params":{"scope":"ALL","duration":"100","delay":"0","easing":"cubic-bezier(0.7368421052631579,0.21437499999999998,0.7828947368421053,0.262375)"}}]},"index":0,"reference":"5"},{"_id":"69483e4605714c8c3968865c","parent":"69483e4605714c8c3968865b","component":"data2-core-primitives@Block","system":"6172b957976026c073ab9058","displayName":"x","params":{"border":{"component":"data2-core-types@Border","params":{"color":"#808080","radius":{},"width":{"left":"0","top":"0","right":"0","bottom":"0"}}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":282,"height":282,"exactWidth":282,"exactHeight":282}},"position":{"component":"data2-core-types@Position","params":{"freeTransform":true,"x":0,"y":0,"exactX":0,"exactY":0}},"style":{"transform":{"component":"data2-core-primitives@String","params":{"content":"rotatex(90deg) translatey(141px)  translatez(-141px)"}},"transform-origin":{"component":"data2-core-primitives@String","params":{"content":"center center"}},"transform-style":{"component":"data2-core-primitives@String","params":{"content":"preserve-3d"}}},"childrenOverflow":{"component":"data2-core-primitives@Boolean","params":{"content":false}},"opacity":{"disabled":true,"component":"data2-core-types@Opacity","params":{"value":100}},"transitions":[{"component":"data2-core-types@Transition","params":{"scope":"ALL","duration":"100","delay":"0","easing":"cubic-bezier(0.7368421052631579,0.21437499999999998,0.7828947368421053,0.262375)"}}]},"index":2,"reference":"x"},{"_id":"68e47144c6e2258edb0bd7c4","parent":"68e4710ac6e2258edb0bd7bb","component":"data2-core-primitives@Block","system":"6172b957976026c073ab9058","displayName":"","params":{"border":{"component":"data2-core-types@Border","params":{"color":"#808080","radius":{"leftTop":"18"},"width":{"left":"0","top":"0","right":"0","bottom":"0"}}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":343,"height":511}},"position":{"component":"data2-core-types@Position","params":{"freeTransform":true,"x":0,"y":0,"exactX":0,"exactY":0}},"style":{"transform":{"params":{"portions":[{"params":{"transform-bricks":[{"type":"perspective","value":{"component":"data2-core-primitives@Number","params":{"content":1800}}},{"type":"rotateX","value":{"component":"data2-core-primitives@Number","params":{"content":0}}},{"type":"rotateY","value":{"component":"data2-core-primitives@Number","params":{"content":0}}},{"type":"rotateZ","value":{"component":"data2-core-primitives@Number","params":{"content":0}}}]},"component":"lucca-playground@JfsOzWC5Bl9CZyvsPoJgN"},{"component":"data2-core-primitives@String","params":{"content":" scale(1)"}}]},"component":"data2-core-functions@StringConcat"},"transform-origin":{"component":"data2-core-primitives@String","params":{"content":"center center"}}},"transitions":[{"component":"data2-core-types@Transition","params":{"scope":"OPACITY","easing":"cubic-bezier(0.42105263157894735,0.982375,0.9407894736842105,1.070375)","duration":"200"}},{"component":"data2-core-types@Transition","params":{"scope":"TRANSFORM","duration":"360","easing":"cubic-bezier(0.13815789473684212,1.074375,0.40789473684210525,0.882375)"}}],"opacity":{"disabled":false,"component":"data2-core-types@Opacity","params":{"value":100}},"backdropFilter":[{"component":"data2-core-types@BitmapFilter","params":{"type":"brightness","unit":"%","value":40}}],"shadow":[{"component":"data2-core-types@Shadow","params":{"type":"inset","spread":"1","color":{"component":"data2-core-types@Color","params":{"rgba":"rgba(255,0,0,1)"}}}}],"fill":{"disabled":false,"component":"data2-core-types@Fill","params":{"backgrounds":[{"params":{"color":{"disabled":false,"component":"data2-core-types@Color","params":{"rgba":"rgba(255,0,0,1)"}},"gradient":{"disabled":true,"component":"data2-core-types@ColorGradient","params":{}},"image":{"disabled":true,"component":"data2-core-types@String","params":{}}},"component":"data2-core-types@Background"}]}}},"reference":"main"},{"_id":"6abff76fce21b725b3165aa9","component":"data2-core-primitives@Artboard","system":"6172b957976026c073ab9058","displayName":"dataFox2","params":{"title":{"component":"data2-core-primitives@String","params":{"content":"dataFox2"}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":844,"height":390}},"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0}}},"reference":"mndAi_6v8WVRsCcx7Fvxp"},{"_id":"69483e4605714c8c3968865e","parent":"69483e4605714c8c3968865c","component":"data2-core-primitives@Block","system":"6172b957976026c073ab9058","displayName":"6","params":{"border":{"component":"data2-core-types@Border","params":{"color":"#808080","radius":{},"width":{"left":"0","top":"0","right":"0","bottom":"0"}}},"fill":{"params":{"backgrounds":[{"params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"rgba(103,83,209,1)"}}},"component":"data2-core-types@Background"}]},"component":"data2-core-types@Fill"},"dimensions":{"component":"data2-core-types@Dimensions","params":{"height":90,"exactHeight":90,"exactWidth":90,"width":90}},"position":{"component":"data2-core-types@Position","params":{"freeTransform":true,"exactX":1.96,"exactY":96.42,"x":2,"y":96}},"opacity":{"disabled":false,"component":"data2-core-types@Opacity","params":{"value":100}},"transitions":[{"component":"data2-core-types@Transition","params":{"scope":"ALL","duration":"100","delay":"0","easing":"cubic-bezier(0.7368421052631579,0.21437499999999998,0.7828947368421053,0.262375)"}}]},"index":1,"reference":"6"},{"_id":"69483e4605714c8c3968865d","parent":"69483e4605714c8c3968865c","component":"data2-core-primitives@Block","system":"6172b957976026c073ab9058","displayName":"7","params":{"border":{"component":"data2-core-types@Border","params":{"color":"#808080","radius":{},"width":{"left":"0","top":"0","right":"0","bottom":"0"}}},"fill":{"params":{"backgrounds":[{"params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"rgba(103,83,209,1)"}}},"component":"data2-core-types@Background"}]},"component":"data2-core-types@Fill"},"dimensions":{"component":"data2-core-types@Dimensions","params":{"height":90,"exactHeight":90,"exactWidth":90,"width":90}},"position":{"component":"data2-core-types@Position","params":{"freeTransform":true,"exactX":1.8199999999999998,"exactY":1.63,"x":2,"y":2}},"opacity":{"disabled":false,"component":"data2-core-types@Opacity","params":{"value":100}},"transitions":[{"component":"data2-core-types@Transition","params":{"scope":"ALL","duration":"100","delay":"0","easing":"cubic-bezier(0.7368421052631579,0.21437499999999998,0.7828947368421053,0.262375)"}}]},"index":2,"reference":"7"},{"_id":"69483e4605714c8c39688660","parent":"69483e4605714c8c3968865c","component":"data2-core-primitives@Block","system":"6172b957976026c073ab9058","displayName":"8","params":{"border":{"component":"data2-core-types@Border","params":{"color":"#808080","radius":{},"width":{"left":"0","top":"0","right":"0","bottom":"0"}}},"fill":{"params":{"backgrounds":[{"params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"rgba(103,83,209,1)"}}},"component":"data2-core-types@Background"}]},"component":"data2-core-types@Fill"},"dimensions":{"component":"data2-core-types@Dimensions","params":{"height":90,"exactHeight":90,"exactWidth":90,"width":90}},"position":{"component":"data2-core-types@Position","params":{"freeTransform":true,"exactX":false,"exactY":false,"y":2,"x":96}},"opacity":{"disabled":false,"component":"data2-core-types@Opacity","params":{"value":100}},"transitions":[{"component":"data2-core-types@Transition","params":{"scope":"ALL","duration":"100","delay":"0","easing":"cubic-bezier(0.7368421052631579,0.21437499999999998,0.7828947368421053,0.262375)"}}]},"index":3,"reference":"8"},{"_id":"69483e4605714c8c39688661","parent":"69483e4605714c8c3968865c","component":"data2-core-primitives@Block","system":"6172b957976026c073ab9058","displayName":"9","params":{"border":{"component":"data2-core-types@Border","params":{"color":"#808080","radius":{},"width":{"left":"0","top":"0","right":"0","bottom":"0"}}},"fill":{"params":{"backgrounds":[{"params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"rgba(103,83,209,1)"}}},"component":"data2-core-types@Background"}]},"component":"data2-core-types@Fill"},"dimensions":{"component":"data2-core-types@Dimensions","params":{"height":90,"exactHeight":90,"exactWidth":90,"width":90}},"position":{"component":"data2-core-types@Position","params":{"freeTransform":true,"exactX":false,"exactY":false,"y":2,"x":190}},"opacity":{"disabled":false,"component":"data2-core-types@Opacity","params":{"value":100}},"transitions":[{"component":"data2-core-types@Transition","params":{"scope":"ALL","duration":"100","delay":"0","easing":"cubic-bezier(0.7368421052631579,0.21437499999999998,0.7828947368421053,0.262375)"}}]},"index":4,"reference":"9"},{"_id":"69483e4605714c8c39688662","parent":"69483e4605714c8c3968865c","component":"data2-core-primitives@Block","system":"6172b957976026c073ab9058","displayName":"shadow4","params":{"border":{"component":"data2-core-types@Border","params":{"color":"#808080","radius":{"leftTop":"99999"},"width":{"left":"0","top":"0","right":"0","bottom":"0"}}},"fill":{"params":{"backgrounds":[{"params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"rgba(205,168,255,0.84)"}}},"component":"data2-core-types@Background"}]},"component":"data2-core-types@Fill"},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":185,"height":159,"exactWidth":184.99,"exactHeight":158.98}},"position":{"component":"data2-core-types@Position","params":{"freeTransform":true,"x":156,"y":-24,"exactX":155.82,"exactY":-23.78}},"filter":[{"component":"data2-core-types@BitmapFilter","params":{"type":"blur","unit":"px","value":77}}],"opacity":{"disabled":false,"component":"data2-core-types@Opacity","params":{"value":0}},"transitions":[{"component":"data2-core-types@Transition","params":{"scope":"ALL","duration":"100","delay":"0","easing":"cubic-bezier(0.7368421052631579,0.21437499999999998,0.7828947368421053,0.262375)"}}]},"reference":"shadow4"},{"_id":"68e5c3efdbc11be40cc12a54","parent":"68e47144c6e2258edb0bd7c4","component":"data2-core-primitives@Block","system":"6172b957976026c073ab9058","params":{"border":{"component":"data2-core-types@Border","params":{"color":"#808080","radius":{},"width":{"left":"0","top":"0","right":"0","bottom":"0"}}},"fill":{"params":{"backgrounds":[{"params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"rgba(255,255,255,1)"},"disabled":true},"gradient":{"disabled":false,"component":"data2-core-types@ColorGradient","params":{"colors":[{"component":"data2-core-types@ColorGradientStop","params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"rgba(255,255,255,1)"}},"offset":"0%"}},{"component":"data2-core-types@ColorGradientStop","params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"rgba(207,207,207,1)"}},"offset":"18%"}},{"component":"data2-core-types@ColorGradientStop","params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"rgba(154,154,154,0.81)"}},"offset":"36%"}},{"component":"data2-core-types@ColorGradientStop","params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"rgba(95,95,95,0.83)"}},"offset":"56.00000000000001%"}},{"component":"data2-core-types@ColorGradientStop","params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"rgba(0,0,0,0.66)"}},"offset":"100%"}}],"angle":90}},"image":{"disabled":true,"component":"data2-core-types@String","params":{}}},"component":"data2-core-types@Background"}]},"component":"data2-core-types@Fill"},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":126,"height":559,"exactHeight":558.77,"exactWidth":126.2}},"position":{"component":"data2-core-types@Position","params":{"freeTransform":true,"x":-25,"y":-131,"exactX":-24.569999999999997,"exactY":-130.71}},"style":{"transform":{"component":"data2-core-primitives@String","params":{"content":"rotate(45deg) scale(0,2)"}},"transform-origin":{"component":"data2-core-primitives@String","params":{"content":"center center"}}},"transitions":[{"component":"data2-core-types@Transition","params":{"scope":"TRANSFORM","duration":"350","easing":"cubic-bezier(0,1.022375,0.32894736842105265,0.934375)","delay":"0"}},{"component":"data2-core-types@Transition","params":{"scope":"OPACITY","duration":"350","easing":"cubic-bezier(0.7105263157894737,0.01837500000000003,0.8618421052631579,0.354375)","delay":"0"}}],"filter":[{"component":"data2-core-types@BitmapFilter","params":{"type":"blur","unit":"px","value":20}}],"opacity":{"disabled":false,"component":"data2-core-types@Opacity","params":{"value":0}}},"index":0,"reference":"reflect2"},{"_id":"68e47144c6e2258edb0bd7c5","parent":"68e47144c6e2258edb0bd7c4","component":"data2-core-primitives@Block","system":"6172b957976026c073ab9058","params":{"border":{"component":"data2-core-types@Border","params":{"color":"#808080","radius":{},"width":{"left":"0","top":"0","right":"0","bottom":"0"}}},"fill":{"params":{"backgrounds":[{"params":{"color":{"component":"data2-core-types@Color","params":{"rgba":"rgba(255,255,255,1)"}}},"component":"data2-core-types@Background"}]},"component":"data2-core-types@Fill"},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":126,"height":559,"exactHeight":558.77,"exactWidth":126.2}},"position":{"component":"data2-core-types@Position","params":{"freeTransform":true,"x":-25,"y":-131,"exactX":-24.569999999999997,"exactY":-130.71}},"style":{"transform":{"component":"data2-core-primitives@String","params":{"content":"rotate(45deg) scale(0,1)"}},"transform-origin":{"component":"data2-core-primitives@String","params":{"content":"center center"}}},"transitions":[{"component":"data2-core-types@Transition","params":{"scope":"TRANSFORM","duration":"350","easing":"cubic-bezier(0,1.022375,0.32894736842105265,0.934375)","delay":"0"}},{"component":"data2-core-types@Transition","params":{"scope":"OPACITY","duration":"350","easing":"cubic-bezier(0.7105263157894737,0.01837500000000003,0.8618421052631579,0.354375)","delay":"0"}}],"filter":[{"component":"data2-core-types@BitmapFilter","params":{"type":"blur","unit":"px","value":20}}],"opacity":{"disabled":false,"component":"data2-core-types@Opacity","params":{"value":0}}},"index":1,"reference":"reflect"},{"_id":"339092a3ed1e771d5186bfc1","parent":"6abff76fce21b725b3165aa9","component":"lucca-playground@dataFox2_Player","system":"6172b957976026c073ab9058","displayName":"dataFox2 Player","params":{"filesBase":{"component":"data2-core-primitives@String","params":{"content":"https://lucca-playground.data2apis.com"}},"engineJs":{"component":"data2-core-primitives@String","params":{"content":"1BvjE9joA6OxZmsW9iGFt"}},"engineWasm":{"component":"data2-core-primitives@String","params":{"content":"Al4yTkzXld9mNgtFklDVN"}},"mainO2r":{"component":"data2-core-primitives@String","params":{"content":"zRHcOMOifpC5L2q-cguV0"}},"portO2r":{"component":"data2-core-primitives@String","params":{"content":"PbVE2ImYJNbRq3AHzba6u"}},"gyroRangeDeg":{"component":"data2-core-primitives@Number","params":{"content":0}},"invertY":{"component":"data2-core-primitives@Boolean","params":{"content":false}},"pin":"stretch","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":844,"height":390}}},"auth":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"datafox2Auth"}},"systemDomain":{"component":"data2-core-primitives@String","params":{"content":"lucca-playground"}}},"reference":"rvxDii7YLtnag6fPO_D1m"}]}},{"_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":"6abffa22c1d9b5fa79c9049d","displayName":"dataFox2","reference":"OgKXZd44xZuNQKUWduZBM","params":{"domain":{"component":"data2-core-primitives@String","params":{"content":"datafox2"}},"entryPoint":{"component":"data2-core-types@ComponentInstanceReference","params":{"component":"data2-core-primitives@Artboard","reference":"mndAi_6v8WVRsCcx7Fvxp"}}},"system":"6172b957976026c073ab9058","component":"data2-core-primitives@Application"}]}},{"reference":"DataComponentInstancesTableDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[{"_id":"6a73eb729b80399cc1ea52fb","component":"data2-core@Store","system":"6172b957976026c073ab9058","params":{"initialData":{"selectedLevelId":null}},"reference":"gameStore"},{"_id":"6ac000c7c1d9b5fa79c91a25","component":"data2-core@DataSet","system":"6172b957976026c073ab9058","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"datafox2keys"}}},"reference":"datafox2keysDataSet"},{"_id":"6ac000c7c5d15a59091cc155","component":"data2-core@Data2TableDataSource","system":"6172b957976026c073ab9058","params":{"table":"datafox2keys","schema":[{"component":"data2-core-types@Field","params":{"key":"key","title":"Key","component":"data2-core-primitives@String","required":true}}]},"reference":"datafox2keys"},{"_id":"6abff8f5fbaf586443c53fcc","component":"data2-core@AuthenticationStrategy","system":"6172b957976026c073ab9058","params":{"credentials":[{"component":"data2-core-types@FieldReference","params":{"key":"email"}},{"component":"data2-core-types@FieldReference","params":{"key":"password"}}],"verifier":{"component":"data2-core-types@DataSetReference","params":{"reference":"Mmyuk3H48xrQz3KwY-dop"}},"email":{"disabled":false,"component":"data2-core-types@DataSetColumnReference","params":{"column":"email"}},"password":{"disabled":false,"component":"data2-core-types@DataSetColumnReference","params":{"column":"password"}}},"reference":"datafox2Auth"},{"_id":"6abffa8fe742f4472e5ce0e4","component":"data2-core@DataSet","system":"6172b957976026c073ab9058","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"datafox2users"}},"autoSelect":{"component":"data2-core-primitives@Boolean","params":{"content":true}}},"reference":"Mmyuk3H48xrQz3KwY-dop"},{"_id":"6abff8d3e892b0a312cf9528","component":"data2-core@Data2TableDataSource","system":"6172b957976026c073ab9058","params":{"table":"datafox2users","schema":[{"component":"data2-core-types@Field","params":{"key":"email","title":"Email","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"password","title":"Password","component":"data2-core-types@Password","required":true}}]},"reference":"datafox2users"}]}},{"reference":"BundlesTableDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[]}}],"processesParams":{}}}}