modified: .next/server/server-reference-manifest.json

This commit is contained in:
Anderson 2026-01-17 21:32:45 +00:00 committed by GitHub
parent 600ab00231
commit 47bcdb8a7a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
61 changed files with 667 additions and 421 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,5 +1,5 @@
{
"node": {},
"edge": {},
"encryptionKey": "I9unzdbRRLqwqb7hcQEnv7QxcZzULlN4bxXrH1oZ2iM="
"encryptionKey": "VYpDsXx+DFgMNIl7qYDU5T6FaoktB8axrpFyUrcQB6g="
}

File diff suppressed because one or more lines are too long

View file

@ -35,7 +35,7 @@ eval(__webpack_require__.ts("var __dirname = \"/\";\n(()=>{\"use strict\";var e=
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
eval(__webpack_require__.ts("__webpack_require__.r(__webpack_exports__);\n/* harmony default export */ __webpack_exports__[\"default\"] = (\"8fa583b02a3c\");\nif (true) { module.hot.accept() }\n//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiKGFwcC1wYWdlcy1icm93c2VyKS8uL2FwcC9nbG9iYWxzLmNzcyIsIm1hcHBpbmdzIjoiO0FBQUEsK0RBQWUsY0FBYztBQUM3QixJQUFJLElBQVUsSUFBSSxpQkFBaUIiLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9fTl9FLy4vYXBwL2dsb2JhbHMuY3NzP2Q4MWUiXSwic291cmNlc0NvbnRlbnQiOlsiZXhwb3J0IGRlZmF1bHQgXCI4ZmE1ODNiMDJhM2NcIlxuaWYgKG1vZHVsZS5ob3QpIHsgbW9kdWxlLmhvdC5hY2NlcHQoKSB9XG4iXSwibmFtZXMiOltdLCJzb3VyY2VSb290IjoiIn0=\n//# sourceURL=webpack-internal:///(app-pages-browser)/./app/globals.css\n"));
eval(__webpack_require__.ts("__webpack_require__.r(__webpack_exports__);\n/* harmony default export */ __webpack_exports__[\"default\"] = (\"6a657676f9a6\");\nif (true) { module.hot.accept() }\n//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiKGFwcC1wYWdlcy1icm93c2VyKS8uL2FwcC9nbG9iYWxzLmNzcyIsIm1hcHBpbmdzIjoiO0FBQUEsK0RBQWUsY0FBYztBQUM3QixJQUFJLElBQVUsSUFBSSxpQkFBaUIiLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9fTl9FLy4vYXBwL2dsb2JhbHMuY3NzP2Q4MWUiXSwic291cmNlc0NvbnRlbnQiOlsiZXhwb3J0IGRlZmF1bHQgXCI2YTY1NzY3NmY5YTZcIlxuaWYgKG1vZHVsZS5ob3QpIHsgbW9kdWxlLmhvdC5hY2NlcHQoKSB9XG4iXSwibmFtZXMiOltdLCJzb3VyY2VSb290IjoiIn0=\n//# sourceURL=webpack-internal:///(app-pages-browser)/./app/globals.css\n"));
/***/ }),
@ -45,7 +45,7 @@ eval(__webpack_require__.ts("__webpack_require__.r(__webpack_exports__);\n/* har
\**********************************************************************************************************************************************************************************/
/***/ (function(module, __unused_webpack_exports, __webpack_require__) {
eval(__webpack_require__.ts("// extracted by mini-css-extract-plugin\nmodule.exports = {\"style\":{\"fontFamily\":\"'__Inter_f367f3', '__Inter_Fallback_f367f3'\",\"fontStyle\":\"normal\"},\"className\":\"__className_f367f3\",\"variable\":\"__variable_f367f3\"};\n if(true) {\n // 1768625170093\n var cssReload = __webpack_require__(/*! ./node_modules/next/dist/compiled/mini-css-extract-plugin/hmr/hotModuleReplacement.js */ \"(app-pages-browser)/./node_modules/next/dist/compiled/mini-css-extract-plugin/hmr/hotModuleReplacement.js\")(module.id, {\"publicPath\":\"/_next/\",\"esModule\":false,\"locals\":true});\n module.hot.dispose(cssReload);\n \n }\n //# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiKGFwcC1wYWdlcy1icm93c2VyKS8uL25vZGVfbW9kdWxlcy9uZXh0L2ZvbnQvZ29vZ2xlL3RhcmdldC5jc3M/e1wicGF0aFwiOlwiYXBwL2xheW91dC50c3hcIixcImltcG9ydFwiOlwiSW50ZXJcIixcImFyZ3VtZW50c1wiOlt7XCJzdWJzZXRzXCI6W1wibGF0aW5cIl0sXCJ2YXJpYWJsZVwiOlwiLS1mb250LWludGVyXCJ9XSxcInZhcmlhYmxlTmFtZVwiOlwiaW50ZXJcIn0iLCJtYXBwaW5ncyI6IkFBQUE7QUFDQSxrQkFBa0IsU0FBUyxnRkFBZ0Y7QUFDM0csT0FBTyxJQUFVO0FBQ2pCO0FBQ0Esc0JBQXNCLG1CQUFPLENBQUMsd01BQStHLGNBQWMsc0RBQXNEO0FBQ2pOLE1BQU0sVUFBVTtBQUNoQjtBQUNBO0FBQ0EiLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9fTl9FLy4vbm9kZV9tb2R1bGVzL25leHQvZm9udC9nb29nbGUvdGFyZ2V0LmNzcz82NzU0Il0sInNvdXJjZXNDb250ZW50IjpbIi8vIGV4dHJhY3RlZCBieSBtaW5pLWNzcy1leHRyYWN0LXBsdWdpblxubW9kdWxlLmV4cG9ydHMgPSB7XCJzdHlsZVwiOntcImZvbnRGYW1pbHlcIjpcIidfX0ludGVyX2YzNjdmMycsICdfX0ludGVyX0ZhbGxiYWNrX2YzNjdmMydcIixcImZvbnRTdHlsZVwiOlwibm9ybWFsXCJ9LFwiY2xhc3NOYW1lXCI6XCJfX2NsYXNzTmFtZV9mMzY3ZjNcIixcInZhcmlhYmxlXCI6XCJfX3ZhcmlhYmxlX2YzNjdmM1wifTtcbiAgICBpZihtb2R1bGUuaG90KSB7XG4gICAgICAvLyAxNzY4NjI1MTcwMDkzXG4gICAgICB2YXIgY3NzUmVsb2FkID0gcmVxdWlyZShcIi93b3Jrc3BhY2VzL2FldGhleC1zdHVkaW8vbm9kZV9tb2R1bGVzL25leHQvZGlzdC9jb21waWxlZC9taW5pLWNzcy1leHRyYWN0LXBsdWdpbi9obXIvaG90TW9kdWxlUmVwbGFjZW1lbnQuanNcIikobW9kdWxlLmlkLCB7XCJwdWJsaWNQYXRoXCI6XCIvX25leHQvXCIsXCJlc01vZHVsZVwiOmZhbHNlLFwibG9jYWxzXCI6dHJ1ZX0pO1xuICAgICAgbW9kdWxlLmhvdC5kaXNwb3NlKGNzc1JlbG9hZCk7XG4gICAgICBcbiAgICB9XG4gICJdLCJuYW1lcyI6W10sInNvdXJjZVJvb3QiOiIifQ==\n//# sourceURL=webpack-internal:///(app-pages-browser)/./node_modules/next/font/google/target.css?{\"path\":\"app/layout.tsx\",\"import\":\"Inter\",\"arguments\":[{\"subsets\":[\"latin\"],\"variable\":\"--font-inter\"}],\"variableName\":\"inter\"}\n"));
eval(__webpack_require__.ts("// extracted by mini-css-extract-plugin\nmodule.exports = {\"style\":{\"fontFamily\":\"'__Inter_f367f3', '__Inter_Fallback_f367f3'\",\"fontStyle\":\"normal\"},\"className\":\"__className_f367f3\",\"variable\":\"__variable_f367f3\"};\n if(true) {\n // 1768684906210\n var cssReload = __webpack_require__(/*! ./node_modules/next/dist/compiled/mini-css-extract-plugin/hmr/hotModuleReplacement.js */ \"(app-pages-browser)/./node_modules/next/dist/compiled/mini-css-extract-plugin/hmr/hotModuleReplacement.js\")(module.id, {\"publicPath\":\"/_next/\",\"esModule\":false,\"locals\":true});\n module.hot.dispose(cssReload);\n \n }\n //# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiKGFwcC1wYWdlcy1icm93c2VyKS8uL25vZGVfbW9kdWxlcy9uZXh0L2ZvbnQvZ29vZ2xlL3RhcmdldC5jc3M/e1wicGF0aFwiOlwiYXBwL2xheW91dC50c3hcIixcImltcG9ydFwiOlwiSW50ZXJcIixcImFyZ3VtZW50c1wiOlt7XCJzdWJzZXRzXCI6W1wibGF0aW5cIl0sXCJ2YXJpYWJsZVwiOlwiLS1mb250LWludGVyXCJ9XSxcInZhcmlhYmxlTmFtZVwiOlwiaW50ZXJcIn0iLCJtYXBwaW5ncyI6IkFBQUE7QUFDQSxrQkFBa0IsU0FBUyxnRkFBZ0Y7QUFDM0csT0FBTyxJQUFVO0FBQ2pCO0FBQ0Esc0JBQXNCLG1CQUFPLENBQUMsd01BQStHLGNBQWMsc0RBQXNEO0FBQ2pOLE1BQU0sVUFBVTtBQUNoQjtBQUNBO0FBQ0EiLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9fTl9FLy4vbm9kZV9tb2R1bGVzL25leHQvZm9udC9nb29nbGUvdGFyZ2V0LmNzcz82NzU0Il0sInNvdXJjZXNDb250ZW50IjpbIi8vIGV4dHJhY3RlZCBieSBtaW5pLWNzcy1leHRyYWN0LXBsdWdpblxubW9kdWxlLmV4cG9ydHMgPSB7XCJzdHlsZVwiOntcImZvbnRGYW1pbHlcIjpcIidfX0ludGVyX2YzNjdmMycsICdfX0ludGVyX0ZhbGxiYWNrX2YzNjdmMydcIixcImZvbnRTdHlsZVwiOlwibm9ybWFsXCJ9LFwiY2xhc3NOYW1lXCI6XCJfX2NsYXNzTmFtZV9mMzY3ZjNcIixcInZhcmlhYmxlXCI6XCJfX3ZhcmlhYmxlX2YzNjdmM1wifTtcbiAgICBpZihtb2R1bGUuaG90KSB7XG4gICAgICAvLyAxNzY4Njg0OTA2MjEwXG4gICAgICB2YXIgY3NzUmVsb2FkID0gcmVxdWlyZShcIi93b3Jrc3BhY2VzL2FldGhleC1zdHVkaW8vbm9kZV9tb2R1bGVzL25leHQvZGlzdC9jb21waWxlZC9taW5pLWNzcy1leHRyYWN0LXBsdWdpbi9obXIvaG90TW9kdWxlUmVwbGFjZW1lbnQuanNcIikobW9kdWxlLmlkLCB7XCJwdWJsaWNQYXRoXCI6XCIvX25leHQvXCIsXCJlc01vZHVsZVwiOmZhbHNlLFwibG9jYWxzXCI6dHJ1ZX0pO1xuICAgICAgbW9kdWxlLmhvdC5kaXNwb3NlKGNzc1JlbG9hZCk7XG4gICAgICBcbiAgICB9XG4gICJdLCJuYW1lcyI6W10sInNvdXJjZVJvb3QiOiIifQ==\n//# sourceURL=webpack-internal:///(app-pages-browser)/./node_modules/next/font/google/target.css?{\"path\":\"app/layout.tsx\",\"import\":\"Inter\",\"arguments\":[{\"subsets\":[\"latin\"],\"variable\":\"--font-inter\"}],\"variableName\":\"inter\"}\n"));
/***/ }),
@ -55,7 +55,7 @@ eval(__webpack_require__.ts("// extracted by mini-css-extract-plugin\nmodule.exp
\************************************************************************************************************************************************************************************************************/
/***/ (function(module, __unused_webpack_exports, __webpack_require__) {
eval(__webpack_require__.ts("// extracted by mini-css-extract-plugin\nmodule.exports = {\"style\":{\"fontFamily\":\"'__JetBrains_Mono_3c557b', '__JetBrains_Mono_Fallback_3c557b'\",\"fontStyle\":\"normal\"},\"className\":\"__className_3c557b\",\"variable\":\"__variable_3c557b\"};\n if(true) {\n // 1768625170095\n var cssReload = __webpack_require__(/*! ./node_modules/next/dist/compiled/mini-css-extract-plugin/hmr/hotModuleReplacement.js */ \"(app-pages-browser)/./node_modules/next/dist/compiled/mini-css-extract-plugin/hmr/hotModuleReplacement.js\")(module.id, {\"publicPath\":\"/_next/\",\"esModule\":false,\"locals\":true});\n module.hot.dispose(cssReload);\n \n }\n //# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiKGFwcC1wYWdlcy1icm93c2VyKS8uL25vZGVfbW9kdWxlcy9uZXh0L2ZvbnQvZ29vZ2xlL3RhcmdldC5jc3M/e1wicGF0aFwiOlwiYXBwL2xheW91dC50c3hcIixcImltcG9ydFwiOlwiSmV0QnJhaW5zX01vbm9cIixcImFyZ3VtZW50c1wiOlt7XCJzdWJzZXRzXCI6W1wibGF0aW5cIl0sXCJ2YXJpYWJsZVwiOlwiLS1mb250LWpldGJyYWlucy1tb25vXCJ9XSxcInZhcmlhYmxlTmFtZVwiOlwiamV0YnJhaW5zTW9ub1wifSIsIm1hcHBpbmdzIjoiQUFBQTtBQUNBLGtCQUFrQixTQUFTLGtHQUFrRztBQUM3SCxPQUFPLElBQVU7QUFDakI7QUFDQSxzQkFBc0IsbUJBQU8sQ0FBQyx3TUFBK0csY0FBYyxzREFBc0Q7QUFDak4sTUFBTSxVQUFVO0FBQ2hCO0FBQ0E7QUFDQSIsInNvdXJjZXMiOlsid2VicGFjazovL19OX0UvLi9ub2RlX21vZHVsZXMvbmV4dC9mb250L2dvb2dsZS90YXJnZXQuY3NzPzUxNGYiXSwic291cmNlc0NvbnRlbnQiOlsiLy8gZXh0cmFjdGVkIGJ5IG1pbmktY3NzLWV4dHJhY3QtcGx1Z2luXG5tb2R1bGUuZXhwb3J0cyA9IHtcInN0eWxlXCI6e1wiZm9udEZhbWlseVwiOlwiJ19fSmV0QnJhaW5zX01vbm9fM2M1NTdiJywgJ19fSmV0QnJhaW5zX01vbm9fRmFsbGJhY2tfM2M1NTdiJ1wiLFwiZm9udFN0eWxlXCI6XCJub3JtYWxcIn0sXCJjbGFzc05hbWVcIjpcIl9fY2xhc3NOYW1lXzNjNTU3YlwiLFwidmFyaWFibGVcIjpcIl9fdmFyaWFibGVfM2M1NTdiXCJ9O1xuICAgIGlmKG1vZHVsZS5ob3QpIHtcbiAgICAgIC8vIDE3Njg2MjUxNzAwOTVcbiAgICAgIHZhciBjc3NSZWxvYWQgPSByZXF1aXJlKFwiL3dvcmtzcGFjZXMvYWV0aGV4LXN0dWRpby9ub2RlX21vZHVsZXMvbmV4dC9kaXN0L2NvbXBpbGVkL21pbmktY3NzLWV4dHJhY3QtcGx1Z2luL2htci9ob3RNb2R1bGVSZXBsYWNlbWVudC5qc1wiKShtb2R1bGUuaWQsIHtcInB1YmxpY1BhdGhcIjpcIi9fbmV4dC9cIixcImVzTW9kdWxlXCI6ZmFsc2UsXCJsb2NhbHNcIjp0cnVlfSk7XG4gICAgICBtb2R1bGUuaG90LmRpc3Bvc2UoY3NzUmVsb2FkKTtcbiAgICAgIFxuICAgIH1cbiAgIl0sIm5hbWVzIjpbXSwic291cmNlUm9vdCI6IiJ9\n//# sourceURL=webpack-internal:///(app-pages-browser)/./node_modules/next/font/google/target.css?{\"path\":\"app/layout.tsx\",\"import\":\"JetBrains_Mono\",\"arguments\":[{\"subsets\":[\"latin\"],\"variable\":\"--font-jetbrains-mono\"}],\"variableName\":\"jetbrainsMono\"}\n"));
eval(__webpack_require__.ts("// extracted by mini-css-extract-plugin\nmodule.exports = {\"style\":{\"fontFamily\":\"'__JetBrains_Mono_3c557b', '__JetBrains_Mono_Fallback_3c557b'\",\"fontStyle\":\"normal\"},\"className\":\"__className_3c557b\",\"variable\":\"__variable_3c557b\"};\n if(true) {\n // 1768684906212\n var cssReload = __webpack_require__(/*! ./node_modules/next/dist/compiled/mini-css-extract-plugin/hmr/hotModuleReplacement.js */ \"(app-pages-browser)/./node_modules/next/dist/compiled/mini-css-extract-plugin/hmr/hotModuleReplacement.js\")(module.id, {\"publicPath\":\"/_next/\",\"esModule\":false,\"locals\":true});\n module.hot.dispose(cssReload);\n \n }\n //# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiKGFwcC1wYWdlcy1icm93c2VyKS8uL25vZGVfbW9kdWxlcy9uZXh0L2ZvbnQvZ29vZ2xlL3RhcmdldC5jc3M/e1wicGF0aFwiOlwiYXBwL2xheW91dC50c3hcIixcImltcG9ydFwiOlwiSmV0QnJhaW5zX01vbm9cIixcImFyZ3VtZW50c1wiOlt7XCJzdWJzZXRzXCI6W1wibGF0aW5cIl0sXCJ2YXJpYWJsZVwiOlwiLS1mb250LWpldGJyYWlucy1tb25vXCJ9XSxcInZhcmlhYmxlTmFtZVwiOlwiamV0YnJhaW5zTW9ub1wifSIsIm1hcHBpbmdzIjoiQUFBQTtBQUNBLGtCQUFrQixTQUFTLGtHQUFrRztBQUM3SCxPQUFPLElBQVU7QUFDakI7QUFDQSxzQkFBc0IsbUJBQU8sQ0FBQyx3TUFBK0csY0FBYyxzREFBc0Q7QUFDak4sTUFBTSxVQUFVO0FBQ2hCO0FBQ0E7QUFDQSIsInNvdXJjZXMiOlsid2VicGFjazovL19OX0UvLi9ub2RlX21vZHVsZXMvbmV4dC9mb250L2dvb2dsZS90YXJnZXQuY3NzPzUxNGYiXSwic291cmNlc0NvbnRlbnQiOlsiLy8gZXh0cmFjdGVkIGJ5IG1pbmktY3NzLWV4dHJhY3QtcGx1Z2luXG5tb2R1bGUuZXhwb3J0cyA9IHtcInN0eWxlXCI6e1wiZm9udEZhbWlseVwiOlwiJ19fSmV0QnJhaW5zX01vbm9fM2M1NTdiJywgJ19fSmV0QnJhaW5zX01vbm9fRmFsbGJhY2tfM2M1NTdiJ1wiLFwiZm9udFN0eWxlXCI6XCJub3JtYWxcIn0sXCJjbGFzc05hbWVcIjpcIl9fY2xhc3NOYW1lXzNjNTU3YlwiLFwidmFyaWFibGVcIjpcIl9fdmFyaWFibGVfM2M1NTdiXCJ9O1xuICAgIGlmKG1vZHVsZS5ob3QpIHtcbiAgICAgIC8vIDE3Njg2ODQ5MDYyMTJcbiAgICAgIHZhciBjc3NSZWxvYWQgPSByZXF1aXJlKFwiL3dvcmtzcGFjZXMvYWV0aGV4LXN0dWRpby9ub2RlX21vZHVsZXMvbmV4dC9kaXN0L2NvbXBpbGVkL21pbmktY3NzLWV4dHJhY3QtcGx1Z2luL2htci9ob3RNb2R1bGVSZXBsYWNlbWVudC5qc1wiKShtb2R1bGUuaWQsIHtcInB1YmxpY1BhdGhcIjpcIi9fbmV4dC9cIixcImVzTW9kdWxlXCI6ZmFsc2UsXCJsb2NhbHNcIjp0cnVlfSk7XG4gICAgICBtb2R1bGUuaG90LmRpc3Bvc2UoY3NzUmVsb2FkKTtcbiAgICAgIFxuICAgIH1cbiAgIl0sIm5hbWVzIjpbXSwic291cmNlUm9vdCI6IiJ9\n//# sourceURL=webpack-internal:///(app-pages-browser)/./node_modules/next/font/google/target.css?{\"path\":\"app/layout.tsx\",\"import\":\"JetBrains_Mono\",\"arguments\":[{\"subsets\":[\"latin\"],\"variable\":\"--font-jetbrains-mono\"}],\"variableName\":\"jetbrainsMono\"}\n"));
/***/ })

View file

@ -192,7 +192,7 @@
/******/
/******/ /* webpack/runtime/getFullHash */
/******/ !function() {
/******/ __webpack_require__.h = function() { return "0ee802ff49f919d5"; }
/******/ __webpack_require__.h = function() { return "30c0bebc24642af0"; }
/******/ }();
/******/
/******/ /* webpack/runtime/global */

View file

@ -751,6 +751,9 @@ code, pre {
.mt-6{
margin-top: var(--size-6);
}
.mt-8{
margin-top: var(--size-8);
}
.mt-auto{
margin-top: auto;
}
@ -763,6 +766,9 @@ code, pre {
.block{
display: block;
}
.inline{
display: inline;
}
.flex{
display: flex;
}
@ -841,6 +847,9 @@ code, pre {
.h-12{
height: var(--size-12);
}
.h-16{
height: var(--size-16);
}
.h-2{
height: var(--size-2);
}
@ -907,6 +916,9 @@ code, pre {
.h-svh{
height: 100svh;
}
.max-h-24{
max-height: var(--size-24);
}
.max-h-32{
max-height: var(--size-32);
}
@ -919,9 +931,6 @@ code, pre {
.max-h-\[80vh\]{
max-height: 80vh;
}
.max-h-24{
max-height: var(--size-24);
}
.min-h-0{
min-height: var(--size-0);
}
@ -934,8 +943,11 @@ code, pre {
.min-h-8{
min-height: var(--size-8);
}
.min-h-\[60px\]{
min-height: 60px;
.min-h-\[36px\]{
min-height: 36px;
}
.min-h-\[38px\]{
min-height: 38px;
}
.min-h-screen{
min-height: 100vh;
@ -943,9 +955,6 @@ code, pre {
.min-h-svh{
min-height: 100svh;
}
.min-h-\[36px\]{
min-height: 36px;
}
.w-0{
width: var(--size-0);
}
@ -976,6 +985,9 @@ code, pre {
.w-4{
width: var(--size-4);
}
.w-40{
width: var(--size-40);
}
.w-5{
width: var(--size-5);
}
@ -985,6 +997,9 @@ code, pre {
.w-64{
width: var(--size-64);
}
.w-7{
width: var(--size-7);
}
.w-72{
width: var(--size-72);
}
@ -1041,15 +1056,18 @@ code, pre {
.min-w-\[180px\]{
min-width: 180px;
}
.min-w-\[260px\]{
min-width: 260px;
}
.min-w-\[320px\]{
min-width: 320px;
}
.min-w-\[8rem\]{
min-width: 8rem;
}
.min-w-\[var\(--radix-select-trigger-width\)\]{
min-width: var(--radix-select-trigger-width);
}
.min-w-\[260px\]{
min-width: 260px;
}
.max-w-3xl{
max-width: 48rem;
}
@ -1062,6 +1080,15 @@ code, pre {
.max-w-\[260px\]{
max-width: 260px;
}
.max-w-\[340px\]{
max-width: 340px;
}
.max-w-\[400px\]{
max-width: 400px;
}
.max-w-\[420px\]{
max-width: 420px;
}
.max-w-\[85\%\]{
max-width: 85%;
}
@ -1078,12 +1105,12 @@ code, pre {
.max-w-md{
max-width: 28rem;
}
.max-w-none{
max-width: none;
}
.max-w-xs{
max-width: 20rem;
}
.max-w-\[340px\]{
max-width: 340px;
}
.flex-1{
flex: 1 1 0%;
}
@ -1480,6 +1507,9 @@ code, pre {
.p-1{
padding: var(--size-1);
}
.p-1\.5{
padding: var(--size-1-5);
}
.p-2{
padding: var(--size-2);
}
@ -1545,10 +1575,6 @@ code, pre {
padding-top: var(--size-2);
padding-bottom: var(--size-2);
}
.py-2\.5{
padding-top: var(--size-2-5);
padding-bottom: var(--size-2-5);
}
.py-3{
padding-top: var(--size-3);
padding-bottom: var(--size-3);
@ -1638,6 +1664,9 @@ code, pre {
.text-\[0\.8rem\]{
font-size: 0.8rem;
}
.text-\[10px\]{
font-size: 10px;
}
.text-base{
font-size: 1rem;
line-height: 1.5rem;
@ -1658,9 +1687,6 @@ code, pre {
font-size: 0.75rem;
line-height: 1rem;
}
.text-\[10px\]{
font-size: 10px;
}
.font-bold{
font-weight: 700;
}
@ -2600,10 +2626,6 @@ code, pre {
display: block;
}
.sm\:inline{
display: inline;
}
.sm\:flex{
display: flex;
}

View file

@ -1 +0,0 @@
{"c":["webpack"],"r":[],"m":[]}

View file

@ -1 +0,0 @@
{"c":["app/layout","webpack"],"r":[],"m":[]}

View file

@ -1 +0,0 @@
{"c":["app/layout","webpack"],"r":[],"m":[]}

View file

@ -1,22 +0,0 @@
"use strict";
/*
* ATTENTION: An "eval-source-map" devtool has been used.
* This devtool is neither made for production nor for readable output files.
* It uses "eval()" calls to create a separate source file with attached SourceMaps in the browser devtools.
* If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)
* or disable the default devtool with "devtool: false".
* If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).
*/
self["webpackHotUpdate_N_E"]("app/layout",{
/***/ "(app-pages-browser)/./app/globals.css":
/*!*************************!*\
!*** ./app/globals.css ***!
\*************************/
/***/ (function(module, __webpack_exports__, __webpack_require__) {
eval(__webpack_require__.ts("__webpack_require__.r(__webpack_exports__);\n/* harmony default export */ __webpack_exports__[\"default\"] = (\"8fa583b02a3c\");\nif (true) { module.hot.accept() }\n//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiKGFwcC1wYWdlcy1icm93c2VyKS8uL2FwcC9nbG9iYWxzLmNzcyIsIm1hcHBpbmdzIjoiO0FBQUEsK0RBQWUsY0FBYztBQUM3QixJQUFJLElBQVUsSUFBSSxpQkFBaUIiLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9fTl9FLy4vYXBwL2dsb2JhbHMuY3NzP2Q4MWUiXSwic291cmNlc0NvbnRlbnQiOlsiZXhwb3J0IGRlZmF1bHQgXCI4ZmE1ODNiMDJhM2NcIlxuaWYgKG1vZHVsZS5ob3QpIHsgbW9kdWxlLmhvdC5hY2NlcHQoKSB9XG4iXSwibmFtZXMiOltdLCJzb3VyY2VSb290IjoiIn0=\n//# sourceURL=webpack-internal:///(app-pages-browser)/./app/globals.css\n"));
/***/ })
});

View file

@ -1,22 +0,0 @@
"use strict";
/*
* ATTENTION: An "eval-source-map" devtool has been used.
* This devtool is neither made for production nor for readable output files.
* It uses "eval()" calls to create a separate source file with attached SourceMaps in the browser devtools.
* If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)
* or disable the default devtool with "devtool: false".
* If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).
*/
self["webpackHotUpdate_N_E"]("app/layout",{
/***/ "(app-pages-browser)/./app/globals.css":
/*!*************************!*\
!*** ./app/globals.css ***!
\*************************/
/***/ (function(module, __webpack_exports__, __webpack_require__) {
eval(__webpack_require__.ts("__webpack_require__.r(__webpack_exports__);\n/* harmony default export */ __webpack_exports__[\"default\"] = (\"d132112bbecc\");\nif (true) { module.hot.accept() }\n//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiKGFwcC1wYWdlcy1icm93c2VyKS8uL2FwcC9nbG9iYWxzLmNzcyIsIm1hcHBpbmdzIjoiO0FBQUEsK0RBQWUsY0FBYztBQUM3QixJQUFJLElBQVUsSUFBSSxpQkFBaUIiLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9fTl9FLy4vYXBwL2dsb2JhbHMuY3NzP2Q4MWUiXSwic291cmNlc0NvbnRlbnQiOlsiZXhwb3J0IGRlZmF1bHQgXCJkMTMyMTEyYmJlY2NcIlxuaWYgKG1vZHVsZS5ob3QpIHsgbW9kdWxlLmhvdC5hY2NlcHQoKSB9XG4iXSwibmFtZXMiOltdLCJzb3VyY2VSb290IjoiIn0=\n//# sourceURL=webpack-internal:///(app-pages-browser)/./app/globals.css\n"));
/***/ })
});

View file

@ -1,22 +0,0 @@
"use strict";
/*
* ATTENTION: An "eval-source-map" devtool has been used.
* This devtool is neither made for production nor for readable output files.
* It uses "eval()" calls to create a separate source file with attached SourceMaps in the browser devtools.
* If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)
* or disable the default devtool with "devtool: false".
* If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).
*/
self["webpackHotUpdate_N_E"]("app/layout",{
/***/ "(app-pages-browser)/./app/globals.css":
/*!*************************!*\
!*** ./app/globals.css ***!
\*************************/
/***/ (function(module, __webpack_exports__, __webpack_require__) {
eval(__webpack_require__.ts("__webpack_require__.r(__webpack_exports__);\n/* harmony default export */ __webpack_exports__[\"default\"] = (\"529ad35ab23a\");\nif (true) { module.hot.accept() }\n//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiKGFwcC1wYWdlcy1icm93c2VyKS8uL2FwcC9nbG9iYWxzLmNzcyIsIm1hcHBpbmdzIjoiO0FBQUEsK0RBQWUsY0FBYztBQUM3QixJQUFJLElBQVUsSUFBSSxpQkFBaUIiLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9fTl9FLy4vYXBwL2dsb2JhbHMuY3NzP2Q4MWUiXSwic291cmNlc0NvbnRlbnQiOlsiZXhwb3J0IGRlZmF1bHQgXCI1MjlhZDM1YWIyM2FcIlxuaWYgKG1vZHVsZS5ob3QpIHsgbW9kdWxlLmhvdC5hY2NlcHQoKSB9XG4iXSwibmFtZXMiOltdLCJzb3VyY2VSb290IjoiIn0=\n//# sourceURL=webpack-internal:///(app-pages-browser)/./app/globals.css\n"));
/***/ })
});

View file

@ -15,7 +15,7 @@ self["webpackHotUpdate_N_E"]("app/layout",{
\*************************/
/***/ (function(module, __webpack_exports__, __webpack_require__) {
eval(__webpack_require__.ts("__webpack_require__.r(__webpack_exports__);\n/* harmony default export */ __webpack_exports__[\"default\"] = (\"3619bd783801\");\nif (true) { module.hot.accept() }\n//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiKGFwcC1wYWdlcy1icm93c2VyKS8uL2FwcC9nbG9iYWxzLmNzcyIsIm1hcHBpbmdzIjoiO0FBQUEsK0RBQWUsY0FBYztBQUM3QixJQUFJLElBQVUsSUFBSSxpQkFBaUIiLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9fTl9FLy4vYXBwL2dsb2JhbHMuY3NzP2Q4MWUiXSwic291cmNlc0NvbnRlbnQiOlsiZXhwb3J0IGRlZmF1bHQgXCIzNjE5YmQ3ODM4MDFcIlxuaWYgKG1vZHVsZS5ob3QpIHsgbW9kdWxlLmhvdC5hY2NlcHQoKSB9XG4iXSwibmFtZXMiOltdLCJzb3VyY2VSb290IjoiIn0=\n//# sourceURL=webpack-internal:///(app-pages-browser)/./app/globals.css\n"));
eval(__webpack_require__.ts("__webpack_require__.r(__webpack_exports__);\n/* harmony default export */ __webpack_exports__[\"default\"] = (\"6a657676f9a6\");\nif (true) { module.hot.accept() }\n//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiKGFwcC1wYWdlcy1icm93c2VyKS8uL2FwcC9nbG9iYWxzLmNzcyIsIm1hcHBpbmdzIjoiO0FBQUEsK0RBQWUsY0FBYztBQUM3QixJQUFJLElBQVUsSUFBSSxpQkFBaUIiLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9fTl9FLy4vYXBwL2dsb2JhbHMuY3NzP2Q4MWUiXSwic291cmNlc0NvbnRlbnQiOlsiZXhwb3J0IGRlZmF1bHQgXCI2YTY1NzY3NmY5YTZcIlxuaWYgKG1vZHVsZS5ob3QpIHsgbW9kdWxlLmhvdC5hY2NlcHQoKSB9XG4iXSwibmFtZXMiOltdLCJzb3VyY2VSb290IjoiIn0=\n//# sourceURL=webpack-internal:///(app-pages-browser)/./app/globals.css\n"));
/***/ })

View file

@ -1 +0,0 @@
{"c":["app/layout","webpack"],"r":["app/_not-found/page"],"m":["(app-pages-browser)/./node_modules/next/dist/build/webpack/loaders/next-client-pages-loader.js?absolutePagePath=%2Fworkspaces%2Faethex-studio%2Fnode_modules%2Fnext%2Fdist%2Fclient%2Fcomponents%2Fnot-found-error.js&page=%2F_not-found%2Fpage!","(app-pages-browser)/./node_modules/next/dist/client/components/not-found-error.js"]}

View file

@ -1,18 +0,0 @@
"use strict";
/*
* ATTENTION: An "eval-source-map" devtool has been used.
* This devtool is neither made for production nor for readable output files.
* It uses "eval()" calls to create a separate source file with attached SourceMaps in the browser devtools.
* If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)
* or disable the default devtool with "devtool: false".
* If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).
*/
self["webpackHotUpdate_N_E"]("webpack",{},
/******/ function(__webpack_require__) { // webpackRuntimeModules
/******/ /* webpack/runtime/getFullHash */
/******/ !function() {
/******/ __webpack_require__.h = function() { return "3f5b70011611c9fa"; }
/******/ }();
/******/
/******/ }
);

View file

@ -1,18 +0,0 @@
"use strict";
/*
* ATTENTION: An "eval-source-map" devtool has been used.
* This devtool is neither made for production nor for readable output files.
* It uses "eval()" calls to create a separate source file with attached SourceMaps in the browser devtools.
* If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)
* or disable the default devtool with "devtool: false".
* If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).
*/
self["webpackHotUpdate_N_E"]("webpack",{},
/******/ function(__webpack_require__) { // webpackRuntimeModules
/******/ /* webpack/runtime/getFullHash */
/******/ !function() {
/******/ __webpack_require__.h = function() { return "0ee802ff49f919d5"; }
/******/ }();
/******/
/******/ }
);

View file

@ -1,18 +0,0 @@
"use strict";
/*
* ATTENTION: An "eval-source-map" devtool has been used.
* This devtool is neither made for production nor for readable output files.
* It uses "eval()" calls to create a separate source file with attached SourceMaps in the browser devtools.
* If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)
* or disable the default devtool with "devtool: false".
* If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).
*/
self["webpackHotUpdate_N_E"]("webpack",{},
/******/ function(__webpack_require__) { // webpackRuntimeModules
/******/ /* webpack/runtime/getFullHash */
/******/ !function() {
/******/ __webpack_require__.h = function() { return "68e3ebbde764c022"; }
/******/ }();
/******/
/******/ }
);

View file

@ -1,18 +0,0 @@
"use strict";
/*
* ATTENTION: An "eval-source-map" devtool has been used.
* This devtool is neither made for production nor for readable output files.
* It uses "eval()" calls to create a separate source file with attached SourceMaps in the browser devtools.
* If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)
* or disable the default devtool with "devtool: false".
* If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).
*/
self["webpackHotUpdate_N_E"]("webpack",{},
/******/ function(__webpack_require__) { // webpackRuntimeModules
/******/ /* webpack/runtime/getFullHash */
/******/ !function() {
/******/ __webpack_require__.h = function() { return "2429eb7975bb05d5"; }
/******/ }();
/******/
/******/ }
);

View file

@ -11,7 +11,7 @@ self["webpackHotUpdate_N_E"]("webpack",{},
/******/ function(__webpack_require__) { // webpackRuntimeModules
/******/ /* webpack/runtime/getFullHash */
/******/ !function() {
/******/ __webpack_require__.h = function() { return "b9ea7f6737e528e9"; }
/******/ __webpack_require__.h = function() { return "30c0bebc24642af0"; }
/******/ }();
/******/
/******/ }

File diff suppressed because one or more lines are too long

View file

@ -16,8 +16,5 @@ import { useIsMobile } from '@/hooks/use-mobile';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { toast } from 'sonner';
function App() {
// ...existing code from your provided App component...
}
export default App;
export { default } from '../src/App';

View file

@ -10,7 +10,7 @@ import { NewProjectModal } from '@/components/NewProjectModal';
import { useAppStore } from '@/store/app-store';
import { ChevronLeft, ChevronRight, ChevronDown } from 'lucide-react';
import App from './App';
import App from '../src/App';
export default function Home() {
return <App />;

23
package-lock.json generated
View file

@ -35,7 +35,9 @@
"next": "14.2.15",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-error-boundary": "^6.1.0",
"socket.io-client": "^4.8.1",
"sonner": "^2.0.7",
"tailwind-merge": "^2.6.0",
"tailwindcss-animate": "^1.0.7",
"zustand": "^5.0.3"
@ -2180,6 +2182,7 @@
"integrity": "sha512-jCzKdm/QK0Kg4V4IK/oMlRZlY+QOcdjv89U2NgKHZk1CYTj82/RVSx1mV/0gqCVMJ/DA+Zf/S4NBWNF8GQ+eqQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.48.0",
"@typescript-eslint/types": "8.48.0",
@ -4123,6 +4126,7 @@
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@rtsao/scc": "^1.1.0",
"array-includes": "^3.1.9",
@ -6571,6 +6575,15 @@
"react": "^18.3.1"
}
},
"node_modules/react-error-boundary": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-6.1.0.tgz",
"integrity": "sha512-02k9WQ/mUhdbXir0tC1NiMesGzRPaCsJEWU/4bcFrbY1YMZOtHShtZP6zw0SJrBWA/31H0KT9/FgdL8+sPKgHA==",
"license": "MIT",
"peerDependencies": {
"react": "^18.0.0 || ^19.0.0"
}
},
"node_modules/react-remove-scroll": {
"version": "2.7.2",
"resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz",
@ -7141,6 +7154,16 @@
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/sonner": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.7.tgz",
"integrity": "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==",
"license": "MIT",
"peerDependencies": {
"react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc",
"react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc"
}
},
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",

View file

@ -9,9 +9,6 @@
"lint": "next lint"
},
"dependencies": {
"next": "14.2.15",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"@monaco-editor/react": "^4.6.0",
"@radix-ui/react-accordion": "^1.2.2",
"@radix-ui/react-alert-dialog": "^1.1.4",
@ -36,7 +33,12 @@
"framer-motion": "^11.15.0",
"lucide-react": "^0.462.0",
"monaco-editor": "^0.52.2",
"next": "14.2.15",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-error-boundary": "^6.1.0",
"socket.io-client": "^4.8.1",
"sonner": "^2.0.7",
"tailwind-merge": "^2.6.0",
"tailwindcss-animate": "^1.0.7",
"zustand": "^5.0.3"

0
public/favicon.ico Normal file
View file

View file

@ -1,30 +1,57 @@
import { useState } from 'react';
import { Toaster } from '@/components/ui/sonner';
import { CodeEditor } from '@/components/CodeEditor';
import { AIChat } from '@/components/AIChat';
import { Toolbar } from '@/components/Toolbar';
import { TemplatesDrawer } from '@/components/TemplatesDrawer';
import { WelcomeDialog } from '@/components/WelcomeDialog';
import { FileTree, FileNode } from '@/components/FileTree';
import { FileTabs } from '@/components/FileTabs';
import { PreviewModal } from '@/components/PreviewModal';
import { NewProjectModal, ProjectConfig } from '@/components/NewProjectModal';
import { ConsolePanel } from '@/components/ConsolePanel';
import { ResizablePanelGroup, ResizablePanel, ResizableHandle } from '@/components/ui/resizable';
import { useKV } from '@github/spark/hooks';
import { useIsMobile } from '@/hooks/use-mobile';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import React, { useState } from 'react';
import { Toaster } from './components/ui/sonner';
import { CodeEditor } from './components/CodeEditor';
import { AIChat } from './components/AIChat';
import { Toolbar } from './components/Toolbar';
import { TemplatesDrawer } from './components/TemplatesDrawer';
import { WelcomeDialog } from './components/WelcomeDialog';
import { FileTree, FileNode } from './components/FileTree';
import { FileTabs } from './components/FileTabs';
import { PreviewModal } from './components/PreviewModal';
import { NewProjectModal, ProjectConfig } from './components/NewProjectModal';
import { ConsolePanel } from './components/ConsolePanel';
import { ResizablePanelGroup, ResizablePanel, ResizableHandle } from './components/ui/resizable';
import { useIsMobile } from './hooks/use-mobile';
import { Tabs, TabsContent, TabsList, TabsTrigger } from './components/ui/tabs';
import { toast } from 'sonner';
import { EducationPanel } from './components/EducationPanel';
import { ExtraTabs } from './components/ui/tabs-extra';
import { PassportLogin } from './components/PassportLogin';
import { Button } from './components/ui/button';
import { initPostHog, captureEvent } from './lib/posthog';
import { initSentry, captureError } from './lib/sentry';
function App() {
const [currentCode, setCurrentCode] = useState('');
const [showTemplates, setShowTemplates] = useState(false);
const [showPreview, setShowPreview] = useState(false);
const [showNewProject, setShowNewProject] = useState(false);
const [code, setCode] = useKV('aethex-current-code', '');
const [code, setCode] = useState('');
const isMobile = useIsMobile();
const [files, setFiles] = useKV<FileNode[]>('aethex-files', [
const [showPassportLogin, setShowPassportLogin] = useState(false);
const [user, setUser] = useState<{ login: string; avatarUrl: string; email: string } | null>(() => {
const stored = typeof window !== 'undefined' ? localStorage.getItem('aethex-user') : null;
return stored ? JSON.parse(stored) : null;
});
React.useEffect(() => {
initPostHog();
initSentry();
}, []);
const handleLoginSuccess = (user: { login: string; avatarUrl: string; email: string }) => {
setUser(user);
localStorage.setItem('aethex-user', JSON.stringify(user));
captureEvent('login', { user });
};
const handleSignOut = () => {
setUser(null);
localStorage.removeItem('aethex-user');
};
const [files, setFiles] = useState<FileNode[]>([
{
id: 'root',
name: 'src',
@ -56,8 +83,8 @@ end)`,
},
]);
const [openFiles, setOpenFiles] = useKV<FileNode[]>('aethex-open-files', []);
const [activeFileId, setActiveFileId] = useKV<string>('aethex-active-file', 'file-1');
const [openFiles, setOpenFiles] = useState<FileNode[]>([]);
const [activeFileId, setActiveFileId] = useState<string>('file-1');
const handleTemplateSelect = (templateCode: string) => {
setCode(templateCode);
@ -73,6 +100,7 @@ end)`,
setCode(file.content || '');
setCurrentCode(file.content || '');
}
captureEvent('file_select', { fileId: file.id });
};
const handleFileCreate = (name: string, parentId?: string) => {
@ -107,6 +135,7 @@ end)`,
return addToFolder(prev || []);
});
captureEvent('file_create', { name, parentId });
toast.success(`Created ${newFile.name}`);
};
@ -145,6 +174,7 @@ end)`,
if (activeFileId === id) {
setActiveFileId((openFiles || [])[0]?.id || '');
}
captureEvent('file_delete', { id });
};
const handleFileClose = (id: string) => {
@ -177,6 +207,12 @@ end)`,
setActiveFileId('');
};
// Example user stub for profile
const demoUser = user || {
login: 'demo-user',
avatarUrl: 'https://avatars.githubusercontent.com/u/1?v=4',
email: 'demo@aethex.com',
};
return (
<div className="h-screen flex flex-col bg-background text-foreground">
<Toolbar
@ -193,6 +229,7 @@ end)`,
<TabsTrigger value="files" className="flex-1">Files</TabsTrigger>
<TabsTrigger value="editor" className="flex-1">Editor</TabsTrigger>
<TabsTrigger value="ai" className="flex-1">AI</TabsTrigger>
<TabsTrigger value="education" className="flex-1">Learn</TabsTrigger>
</TabsList>
<TabsContent value="files" className="flex-1 m-0">
<FileTree
@ -218,6 +255,9 @@ end)`,
<TabsContent value="ai" className="flex-1 m-0">
<AIChat currentCode={currentCode} />
</TabsContent>
<TabsContent value="education" className="flex-1 m-0">
<EducationPanel />
</TabsContent>
</Tabs>
) : (
<>
@ -252,7 +292,11 @@ end)`,
<ResizableHandle className="w-1 bg-border hover:bg-accent transition-colors" />
<ResizablePanel defaultSize={30} minSize={20}>
<ResizablePanel defaultSize={20} minSize={15}>
<EducationPanel />
</ResizablePanel>
<ResizablePanel defaultSize={15} minSize={10}>
<AIChat currentCode={currentCode} />
</ResizablePanel>
</ResizablePanelGroup>
@ -260,6 +304,10 @@ end)`,
<ConsolePanel />
</>
)}
{/* Unified feature tabs for all major panels */}
<div className="w-full border-t border-border mt-4">
<ExtraTabs user={demoUser} />
</div>
</div>
{showTemplates && (
@ -282,6 +330,29 @@ end)`,
/>
<WelcomeDialog />
{!user && (
<Button
variant="secondary"
className="fixed top-4 right-4 z-50"
onClick={() => setShowPassportLogin(true)}
>
Sign In
</Button>
)}
{user && (
<div className="fixed top-4 right-4 z-50 flex items-center gap-2 bg-card px-3 py-1 rounded shadow">
<img src={user.avatarUrl} alt={user.login} className="h-6 w-6 rounded-full" />
<span className="text-xs font-medium">{user.login}</span>
<Button variant="ghost" size="sm" onClick={handleSignOut}>
Sign Out
</Button>
</div>
)}
<PassportLogin
open={showPassportLogin}
onClose={() => setShowPassportLogin(false)}
onLoginSuccess={handleLoginSuccess}
/>
<Toaster position="bottom-right" theme="dark" />
</div>
);

View file

@ -33,18 +33,21 @@ export function AIChat({ currentCode }: AIChatProps) {
setIsLoading(true);
try {
const promptText = `You are an expert Roblox Lua developer helping a user with their code. The user is working on this code:
\`\`\`lua
${currentCode}
\`\`\`
User question: ${userMessage}
Provide helpful, concise answers. Include code examples when relevant. Keep responses friendly and encouraging.`;
// Context-aware prompt: include active code, file name, and platform
const promptText = `You are an expert Roblox Lua developer and code assistant.\n\nUser's active code:\n\n\`\`\`lua\n${currentCode}\n\`\`\`\n\nUser question: ${userMessage}\n\nIf the user asks for code completion, suggest the next line(s) of code.\nIf the user asks for an explanation, explain the code in simple terms.\nIf the user asks for platform-specific help, provide Roblox Lua answers.\n\nRespond with concise, friendly, and actionable advice. Include code examples inline when relevant.`;
const response = await window.spark.llm(promptText, 'gpt-4o-mini');
setMessages((prev) => [...prev, { role: 'assistant', content: response }]);
// If the response contains code, show it in a highlighted block
const codeMatch = response.match(/```lua([\s\S]*?)```/);
if (codeMatch) {
setMessages((prev) => [
...prev,
{ role: 'assistant', content: response.replace(/```lua([\s\S]*?)```/, '') },
{ role: 'assistant', content: `<pre class='bg-muted p-2 rounded text-xs font-mono'>${codeMatch[1].trim()}</pre>` },
]);
} else {
setMessages((prev) => [...prev, { role: 'assistant', content: response }]);
}
} catch (error) {
console.error('AI Error:', error);
toast.error('Failed to get AI response. Please try again.');
@ -82,7 +85,11 @@ Provide helpful, concise answers. Include code examples when relevant. Keep resp
: 'bg-muted text-foreground'
}`}
>
<p className="whitespace-pre-wrap leading-relaxed">{message.content}</p>
{message.content.startsWith('<pre') ? (
<span dangerouslySetInnerHTML={{ __html: message.content }} />
) : (
<p className="whitespace-pre-wrap leading-relaxed">{message.content}</p>
)}
</div>
</div>
))}

View file

@ -0,0 +1,11 @@
import React from 'react';
import { Card } from './ui/card';
export function AssetLibraryPanel() {
return (
<Card className="p-4">
<h2 className="font-bold text-lg mb-2">Asset Library</h2>
<p>Manage cross-platform assets for your projects. (stub)</p>
</Card>
);
}

View file

@ -0,0 +1,11 @@
import React from 'react';
import { Card } from './ui/card';
export function CertificationPanel() {
return (
<Card className="p-4">
<h2 className="font-bold text-lg mb-2">Certification & Progress</h2>
<p>Track curriculum completion and earn certificates. (stub)</p>
</Card>
);
}

View file

@ -0,0 +1,11 @@
import React from 'react';
import { Card } from './ui/card';
export function DesktopAppPanel() {
return (
<Card className="p-4">
<h2 className="font-bold text-lg mb-2">Desktop App</h2>
<p>Download and use the AeThex Studio desktop app. (stub)</p>
</Card>
);
}

View file

@ -0,0 +1,64 @@
import React, { useState } from 'react';
import { chapters } from '../education/chapters';
import { Card } from './ui/card';
import { ScrollArea } from './ui/scroll-area';
import { Button } from './ui/button';
export function EducationPanel() {
const [selected, setSelected] = useState<number>(chapters[0].id);
const [completed, setCompleted] = useState<number[]>([]);
const chapter = chapters.find(c => c.id === selected);
const markComplete = (id: number) => {
if (!completed.includes(id)) {
setCompleted([...completed, id]);
}
};
return (
<div className="flex h-full bg-card border-l border-border min-w-[320px] max-w-[420px] flex-col">
<div className="flex items-center gap-2 px-4 py-2 border-b border-border bg-card/80">
<h2 className="font-semibold text-xs tracking-wide uppercase text-muted-foreground">Foundation Curriculum</h2>
<span className="ml-auto text-xs text-muted-foreground">{completed.length}/{chapters.length} Complete</span>
</div>
<div className="flex flex-row flex-1 min-h-0">
<div className="w-40 border-r border-border bg-card/60">
<ScrollArea className="h-full">
<ul className="py-2">
{chapters.map(c => (
<li key={c.id} className="flex items-center">
<button
className={`w-full text-left px-3 py-2 text-xs font-medium rounded hover:bg-muted/60 transition-colors ${selected === c.id ? 'bg-muted/80' : ''}`}
onClick={() => setSelected(c.id)}
>
{c.title}
</button>
{completed.includes(c.id) && (
<span className="ml-2 text-green-500 text-xs"></span>
)}
</li>
))}
</ul>
</ScrollArea>
</div>
<div className="flex-1 min-w-0">
<ScrollArea className="h-full px-4 py-4">
<Card className="p-4">
<div className="prose prose-sm max-w-none" dangerouslySetInnerHTML={{ __html: chapter ? chapter.content.replace(/\n/g, '<br/>') : '' }} />
<div className="mt-4 flex justify-end">
<Button
variant={completed.includes(selected) ? 'outline' : 'accent'}
size="sm"
disabled={completed.includes(selected)}
onClick={() => markComplete(selected)}
>
{completed.includes(selected) ? 'Completed' : 'Mark Complete'}
</Button>
</div>
</Card>
</ScrollArea>
</div>
</div>
</div>
);
}

View file

@ -0,0 +1,11 @@
import React from 'react';
import { Card } from './ui/card';
export function EnterpriseAnalyticsPanel() {
return (
<Card className="p-4">
<h2 className="font-bold text-lg mb-2">Enterprise Analytics</h2>
<p>Advanced analytics and enterprise features. (stub)</p>
</Card>
);
}

View file

@ -0,0 +1,11 @@
import React from 'react';
import { Card } from './ui/card';
export function GamePreviewPanel() {
return (
<Card className="p-4">
<h2 className="font-bold text-lg mb-2">Game Preview</h2>
<p>Preview your game in the browser using WebGL or sandboxed Lua. (stub)</p>
</Card>
);
}

View file

@ -0,0 +1,48 @@
import React, { useState } from 'react';
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from './ui/dialog';
import { Button } from './ui/button';
const steps = [
{
title: 'Welcome to AeThex Studio',
description: 'Your cross-platform game IDE. Lets get started!',
},
{
title: 'Code Editor',
description: 'Write Roblox Lua and Verse code with Monaco editor.',
},
{
title: 'AI Assistant',
description: 'Get instant code help and explanations.',
},
{
title: 'Curriculum',
description: 'Learn game development with interactive lessons.',
},
{
title: 'Projects & Assets',
description: 'Manage files, templates, and cross-platform assets.',
},
];
export function OnboardingDialog({ open, onClose }: { open: boolean; onClose: () => void }) {
const [step, setStep] = useState(0);
return (
<Dialog open={open} onOpenChange={onClose}>
<DialogContent>
<DialogHeader>
<DialogTitle>{steps[step].title}</DialogTitle>
<DialogDescription>{steps[step].description}</DialogDescription>
</DialogHeader>
<div className="flex justify-between mt-6">
<Button disabled={step === 0} onClick={() => setStep(step - 1)}>Back</Button>
{step < steps.length - 1 ? (
<Button onClick={() => setStep(step + 1)}>Next</Button>
) : (
<Button onClick={onClose}>Finish</Button>
)}
</div>
</DialogContent>
</Dialog>
);
}

View file

@ -0,0 +1,42 @@
import React from 'react';
import { Dialog, DialogContent, DialogHeader, DialogTitle } from './ui/dialog';
import { Button } from './ui/button';
import { Card } from './ui/card';
import { Sparkle } from '@phosphor-icons/react';
interface PassportLoginProps {
open: boolean;
onClose: () => void;
onLoginSuccess: (user: { login: string; avatarUrl: string; email: string }) => void;
}
export function PassportLogin({ open, onClose, onLoginSuccess }: PassportLoginProps) {
const handleLogin = async () => {
// Stub: Replace with real OAuth flow
const user = {
login: 'demo_user',
avatarUrl: 'https://avatars.githubusercontent.com/u/1?v=4',
email: 'demo@aethex.io',
};
onLoginSuccess(user);
onClose();
};
return (
<Dialog open={open} onOpenChange={onClose}>
<DialogContent className="max-w-[400px]">
<DialogHeader>
<DialogTitle className="text-xl flex items-center gap-2">
<Sparkle className="text-accent" size={20} /> AeThex Passport Login
</DialogTitle>
</DialogHeader>
<Card className="p-4 mt-2">
<p className="text-sm mb-4">Sign in with your AeThex Passport account to access private projects and sync your progress.</p>
<Button variant="accent" className="w-full" onClick={handleLogin}>
Sign in with Passport
</Button>
</Card>
</DialogContent>
</Dialog>
);
}

View file

@ -0,0 +1,11 @@
import React from 'react';
import { Card } from './ui/card';
export function SpatialPanel() {
return (
<Card className="p-4">
<h2 className="font-bold text-lg mb-2">Spatial Export</h2>
<p>Export your project to Spatial world format. (stub)</p>
</Card>
);
}

View file

@ -0,0 +1,11 @@
import React from 'react';
import { Card } from './ui/card';
export function TeacherDashboard() {
return (
<Card className="p-4">
<h2 className="font-bold text-lg mb-2">Teacher Dashboard</h2>
<p>Manage classrooms, assignments, and student progress. (stub)</p>
</Card>
);
}

View file

@ -0,0 +1,11 @@
import React from 'react';
import { Card } from './ui/card';
export function TeamCollabPanel() {
return (
<Card className="p-4">
<h2 className="font-bold text-lg mb-2">Team Collaboration</h2>
<p>Collaborate in real-time with your team. (stub)</p>
</Card>
);
}

View file

@ -52,11 +52,11 @@ export function Toolbar({ code, onTemplatesClick, onPreviewClick, onNewProjectCl
return (
<>
<div className="flex items-center gap-2 px-4 py-3 bg-card border-b border-border">
<h1 className="text-2xl font-bold tracking-tight">
<div className="flex items-center gap-1 px-2 py-1.5 bg-card border-b border-border min-h-[38px]">
<h1 className="text-lg font-bold tracking-tight leading-none">
Ae<span className="text-accent">Thex</span>
</h1>
<span className="text-sm text-muted-foreground ml-1">Studio</span>
<span className="text-xs text-muted-foreground ml-1 leading-none">Studio</span>
<div className="flex-1" />
@ -64,84 +64,83 @@ export function Toolbar({ code, onTemplatesClick, onPreviewClick, onNewProjectCl
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="default"
size="sm"
variant="ghost"
size="icon"
onClick={onNewProjectClick}
className="bg-accent text-accent-foreground hover:bg-accent/90"
className="p-1.5 rounded hover:bg-accent/10"
aria-label="New Project"
>
<FolderPlus />
<span className="ml-2 hidden sm:inline">New Project</span>
<FolderPlus size={18} />
</Button>
</TooltipTrigger>
<TooltipContent>Create a new project</TooltipContent>
<TooltipContent>New Project</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="sm"
size="icon"
onClick={onPreviewClick}
className="p-1.5 rounded hover:bg-accent/10"
aria-label="Preview"
>
<Play />
<span className="ml-2 hidden sm:inline">Preview</span>
<Play size={18} />
</Button>
</TooltipTrigger>
<TooltipContent>Preview all platforms</TooltipContent>
<TooltipContent>Preview</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button variant="ghost" size="sm" onClick={onTemplatesClick}>
<FileCode />
<span className="ml-2 hidden sm:inline">Templates</span>
<Button variant="ghost" size="icon" onClick={onTemplatesClick} className="p-1.5 rounded hover:bg-accent/10" aria-label="Templates">
<FileCode size={18} />
</Button>
</TooltipTrigger>
<TooltipContent>Browse script templates</TooltipContent>
<TooltipContent>Templates</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button variant="ghost" size="sm" onClick={handleCopy}>
<Copy />
<span className="ml-2 hidden sm:inline">Copy</span>
<Button variant="ghost" size="icon" onClick={handleCopy} className="p-1.5 rounded hover:bg-accent/10" aria-label="Copy">
<Copy size={18} />
</Button>
</TooltipTrigger>
<TooltipContent>Copy code to clipboard</TooltipContent>
<TooltipContent>Copy</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="sm"
size="icon"
onClick={handleExport}
className="hidden sm:flex"
className="p-1.5 rounded hover:bg-accent/10 hidden sm:flex"
aria-label="Export"
>
<Download />
<span className="ml-2">Export</span>
<Download size={18} />
</Button>
</TooltipTrigger>
<TooltipContent>Download as .lua file</TooltipContent>
<TooltipContent>Export</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button variant="ghost" size="sm" onClick={() => setShowInfo(true)}>
<Info />
<Button variant="ghost" size="icon" onClick={() => setShowInfo(true)} className="p-1.5 rounded hover:bg-accent/10" aria-label="About">
<Info size={18} />
</Button>
</TooltipTrigger>
<TooltipContent>About AeThex Studio</TooltipContent>
<TooltipContent>About</TooltipContent>
</Tooltip>
</TooltipProvider>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon" className="rounded-full">
<Avatar className="h-8 w-8">
<Button variant="ghost" size="icon" className="rounded-full p-0">
<Avatar className="h-7 w-7">
<AvatarImage src={user?.avatarUrl} alt={user?.login || 'User'} />
<AvatarFallback>
<User />
<User size={16} />
</AvatarFallback>
</Avatar>
</Button>
@ -150,19 +149,19 @@ export function Toolbar({ code, onTemplatesClick, onPreviewClick, onNewProjectCl
{user && (
<>
<div className="px-2 py-1.5">
<p className="text-sm font-semibold">{user.login}</p>
<p className="text-xs text-muted-foreground">{user.email}</p>
<p className="text-xs font-semibold">{user.login}</p>
<p className="text-[10px] text-muted-foreground">{user.email}</p>
</div>
<DropdownMenuSeparator />
</>
)}
<DropdownMenuItem onClick={() => toast.info('Profile coming soon!')}>
<User className="mr-2" />
Profile
<User className="mr-2" size={14} />
<span className="text-xs">Profile</span>
</DropdownMenuItem>
<DropdownMenuItem onClick={() => toast.info('Sign out feature coming soon!')}>
<SignOut className="mr-2" />
Sign Out
<SignOut className="mr-2" size={14} />
<span className="text-xs">Sign Out</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>

View file

@ -0,0 +1,11 @@
import React from 'react';
import { Card } from './ui/card';
export function TranslationPanel() {
return (
<Card className="p-4">
<h2 className="font-bold text-lg mb-2">Cross-Platform Translation</h2>
<p>Translate GameForge Script to Roblox Lua, Verse, and more. (stub)</p>
</Card>
);
}

View file

@ -0,0 +1,11 @@
import React from 'react';
import { Card } from './ui/card';
export function UEFNPanel() {
return (
<Card className="p-4">
<h2 className="font-bold text-lg mb-2">UEFN/Verse Support</h2>
<p>Export your project to Verse format and manage UEFN templates. (stub)</p>
</Card>
);
}

View file

@ -0,0 +1,21 @@
import React from 'react';
import { Card } from './ui/card';
export function UserProfile({ user }: { user: { login: string; avatarUrl: string; email: string } }) {
return (
<Card className="max-w-md mx-auto p-6 mt-8">
<div className="flex items-center gap-4">
<img src={user.avatarUrl} alt={user.login} className="h-16 w-16 rounded-full" />
<div>
<h2 className="text-lg font-bold">{user.login}</h2>
<p className="text-xs text-muted-foreground">{user.email}</p>
</div>
</div>
<div className="mt-6">
<h3 className="font-semibold mb-2">Progress</h3>
<p>Curriculum chapters completed: <span className="font-bold">(stub)</span></p>
<p>Recent projects: <span className="font-bold">(stub)</span></p>
</div>
</Card>
);
}

View file

@ -0,0 +1,47 @@
import React from 'react';
import { Tabs, TabsList, TabsTrigger, TabsContent } from './tabs';
import { OnboardingDialog } from '../OnboardingDialog';
import { UserProfile } from '../UserProfile';
import { UEFNPanel } from '../UEFNPanel';
import { GamePreviewPanel } from '../GamePreviewPanel';
import { TranslationPanel } from '../TranslationPanel';
import { SpatialPanel } from '../SpatialPanel';
import { AssetLibraryPanel } from '../AssetLibraryPanel';
import { TeacherDashboard } from '../TeacherDashboard';
import { CertificationPanel } from '../CertificationPanel';
import { DesktopAppPanel } from '../DesktopAppPanel';
import { TeamCollabPanel } from '../TeamCollabPanel';
import { EnterpriseAnalyticsPanel } from '../EnterpriseAnalyticsPanel';
export function ExtraTabs({ user }: { user: any }) {
return (
<Tabs defaultValue="onboarding" className="h-full flex flex-col">
<TabsList className="w-full rounded-none border-b border-border">
<TabsTrigger value="onboarding">Onboarding</TabsTrigger>
<TabsTrigger value="profile">Profile</TabsTrigger>
<TabsTrigger value="uefn">UEFN</TabsTrigger>
<TabsTrigger value="preview">Preview</TabsTrigger>
<TabsTrigger value="translation">Translation</TabsTrigger>
<TabsTrigger value="spatial">Spatial</TabsTrigger>
<TabsTrigger value="assets">Assets</TabsTrigger>
<TabsTrigger value="teacher">Teacher</TabsTrigger>
<TabsTrigger value="certification">Certification</TabsTrigger>
<TabsTrigger value="desktop">Desktop App</TabsTrigger>
<TabsTrigger value="team">Team</TabsTrigger>
<TabsTrigger value="enterprise">Enterprise</TabsTrigger>
</TabsList>
<TabsContent value="onboarding"><OnboardingDialog open={true} onClose={() => {}} /></TabsContent>
<TabsContent value="profile"><UserProfile user={user} /></TabsContent>
<TabsContent value="uefn"><UEFNPanel /></TabsContent>
<TabsContent value="preview"><GamePreviewPanel /></TabsContent>
<TabsContent value="translation"><TranslationPanel /></TabsContent>
<TabsContent value="spatial"><SpatialPanel /></TabsContent>
<TabsContent value="assets"><AssetLibraryPanel /></TabsContent>
<TabsContent value="teacher"><TeacherDashboard /></TabsContent>
<TabsContent value="certification"><CertificationPanel /></TabsContent>
<TabsContent value="desktop"><DesktopAppPanel /></TabsContent>
<TabsContent value="team"><TeamCollabPanel /></TabsContent>
<TabsContent value="enterprise"><EnterpriseAnalyticsPanel /></TabsContent>
</Tabs>
);
}

53
src/education/chapters.ts Normal file
View file

@ -0,0 +1,53 @@
// Foundation Chapters 1-10 (stub)
export const chapters = [
{
id: 1,
title: 'Introduction to Roblox Scripting',
content: `# Chapter 1: Introduction to Roblox Scripting\n\nWelcome to Roblox development!\n\n**Learning Objectives:**\n- Understand what Roblox Studio is\n- Learn why Lua is used\n- Write your first script\n\n**What is Roblox Studio?**\nRoblox Studio is the official IDE for creating Roblox games. It lets you build worlds, write scripts, and publish games.\n\n**Why Lua?**\nLua is a lightweight scripting language used for game logic in Roblox.\n\n**Your First Script:**\nPaste this in a Script object:\n\n\n```lua\nprint('Hello, Roblox!')\n```\n\nTry running your game to see the output!`
},
{
id: 2,
title: 'Variables and Data Types',
content: `# Chapter 2: Variables and Data Types\n\n**Learning Objectives:**\n- Use variables to store data\n- Understand numbers, strings, tables\n\n**Variables:**\n```lua\nlocal score = 10\nlocal playerName = "Alex"\n```\n\n**Tables:**\n```lua\nlocal inventory = {"Sword", "Shield"}\nprint(inventory[1]) -- Sword\n```\n`
},
{
id: 3,
title: 'Functions and Events',
content: `# Chapter 3: Functions and Events\n\n**Learning Objectives:**\n- Write reusable functions\n- Respond to game events\n\n**Functions:**\n```lua\nfunction greet(name)\n print("Hello, " .. name)\nend\ngreet("Alex")\n```\n\n**Events:**\n```lua\nlocal Players = game:GetService("Players")\nPlayers.PlayerAdded:Connect(function(player)\n print(player.Name .. " joined!")\nend)\n```\n`
},
{
id: 4,
title: 'Game Objects and Hierarchy',
content: `# Chapter 4: Game Objects and Hierarchy\n\n**Learning Objectives:**\n- Explore Roblox object hierarchy\n- Access and modify game objects\n\n**Example:**\n```lua\nlocal workspace = game.Workspace\nlocal part = workspace.Part\npart.BrickColor = BrickColor.new("Bright red")\n```\n`
},
{
id: 5,
title: 'Player Interaction',
content: `# Chapter 5: Player Interaction\n\n**Learning Objectives:**\n- Detect player actions\n- Respond to input\n\n**Example:**\n```lua\nlocal Players = game:GetService("Players")\nPlayers.PlayerAdded:Connect(function(player)\n player.Chatted:Connect(function(message)\n print(player.Name .. " said: " .. message)\n end)\nend)\n```\n`
},
{
id: 6,
title: 'Building Game Logic',
content: `# Chapter 6: Building Game Logic\n\n**Learning Objectives:**\n- Create rules and mechanics\n- Use conditions and loops\n\n**Example:**\n```lua\nlocal score = 0\nwhile score < 10 do\n score = score + 1\n print("Score:", score)\nend\n```\n`
},
{
id: 7,
title: 'Saving Data',
content: `# Chapter 7: Saving Data\n\n**Learning Objectives:**\n- Store player progress\n- Use DataStore service\n\n**Example:**\n```lua\nlocal DataStoreService = game:GetService("DataStoreService")\nlocal scoreStore = DataStoreService:GetDataStore("Scores")\n\n-- Save score\nscoreStore:SetAsync("player_123", 100)\n-- Load score\nlocal score = scoreStore:GetAsync("player_123")\nprint(score)\n```\n`
},
{
id: 8,
title: 'Deploying Your Game',
content: `# Chapter 8: Deploying Your Game\n\n**Learning Objectives:**\n- Publish your game to Roblox\n- Update and manage releases\n\n**Steps:**\n1. Click "File > Publish to Roblox As..."\n2. Choose a name and description\n3. Set permissions and publish\n`
},
{
id: 9,
title: 'Debugging and Testing',
content: `# Chapter 9: Debugging and Testing\n\n**Learning Objectives:**\n- Find and fix bugs\n- Use print statements and breakpoints\n\n**Example:**\n```lua\nprint("Debug: Player joined")\n-- Use breakpoints in Studio to pause and inspect\n```\n`
},
{
id: 10,
title: 'Next Steps and Resources',
content: `# Chapter 10: Next Steps and Resources\n\n**Continue Learning:**\n- Join the [Roblox Developer Forum](https://devforum.roblox.com/)\n- Explore [Roblox Education](https://education.roblox.com/)\n- Try building your own game!\n`
}
];

14
src/lib/analytics.ts Normal file
View file

@ -0,0 +1,14 @@
// PostHog/Sentry analytics integration stub
export function trackEvent(event: string, properties?: Record<string, any>) {
// TODO: Integrate with PostHog or Sentry
// Example: window.posthog?.capture(event, properties)
// Example: window.Sentry?.captureMessage(event, properties)
console.log('[Analytics]', event, properties);
}
export function trackError(error: Error, context?: Record<string, any>) {
// TODO: Integrate with Sentry
// Example: window.Sentry?.captureException(error, context)
console.error('[Error]', error, context);
}

10
src/lib/posthog.ts Normal file
View file

@ -0,0 +1,10 @@
// PostHog analytics integration
import posthog from 'posthog-js';
export function initPostHog() {
posthog.init('ph_project_key', { api_host: 'https://app.posthog.com' });
}
export function captureEvent(event: string, properties?: Record<string, any>) {
posthog.capture(event, properties);
}

10
src/lib/sentry.ts Normal file
View file

@ -0,0 +1,10 @@
// Sentry error tracking integration
import * as Sentry from '@sentry/browser';
export function initSentry() {
Sentry.init({ dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0' });
}
export function captureError(error: Error, context?: Record<string, any>) {
Sentry.captureException(error, { extra: context });
}

View file

@ -2,8 +2,8 @@ import { createRoot } from 'react-dom/client'
import { ErrorBoundary } from "react-error-boundary";
import "@github/spark/spark"
import App from './App.tsx'
import { ErrorFallback } from './ErrorFallback.tsx'
import App from './App'
import { ErrorFallback } from './ErrorFallback'
import "./main.css"
import "./styles/theme.css"