diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 0000000..ce60f10
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,2 @@
+@AGENTS.md
+
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..bd2f312
--- /dev/null
+++ b/README.md
@@ -0,0 +1,25 @@
+# sky_phone
+
+FiveM phone scaffold for Sky-Systems, using Vue 3 and Konsta UI.
+
+## Development
+
+```powershell
+cd frontend
+pnpm install
+pnpm dev
+```
+
+The development command starts Vite and a small mock server for NUI callbacks. The phone opens
+automatically in browser development mode.
+
+## Build
+
+```powershell
+cd frontend
+pnpm build
+```
+
+The production build is published to `sky_phone/source/html/`. In FiveM, ensure `sky_base` before
+`sky_phone` and use `/phone` to toggle the UI.
+
diff --git a/frontend/.editorconfig b/frontend/.editorconfig
new file mode 100644
index 0000000..4a036b9
--- /dev/null
+++ b/frontend/.editorconfig
@@ -0,0 +1,9 @@
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+insert_final_newline = true
+indent_style = space
+indent_size = 2
+
diff --git a/frontend/.gitignore b/frontend/.gitignore
new file mode 100644
index 0000000..5d3c6e3
--- /dev/null
+++ b/frontend/.gitignore
@@ -0,0 +1,4 @@
+node_modules/
+dist/
+*.local
+
diff --git a/frontend/.prettierrc.json b/frontend/.prettierrc.json
new file mode 100644
index 0000000..5690cce
--- /dev/null
+++ b/frontend/.prettierrc.json
@@ -0,0 +1,6 @@
+{
+ "semi": false,
+ "singleQuote": true,
+ "trailingComma": "all"
+}
+
diff --git a/frontend/build.cjs b/frontend/build.cjs
new file mode 100644
index 0000000..c896e3c
--- /dev/null
+++ b/frontend/build.cjs
@@ -0,0 +1,31 @@
+const fs = require('node:fs')
+const path = require('node:path')
+
+const frontendRoot = __dirname
+const sourceDirectory = path.join(frontendRoot, 'dist')
+const targetDirectory = path.join(
+ frontendRoot,
+ '..',
+ 'sky_phone',
+ 'source',
+ 'html',
+)
+
+if (!fs.existsSync(sourceDirectory)) {
+ throw new Error(
+ 'Missing frontend/dist. Run vite build before publishing the NUI.',
+ )
+}
+
+fs.rmSync(targetDirectory, { force: true, recursive: true })
+fs.mkdirSync(targetDirectory, { recursive: true })
+fs.cpSync(sourceDirectory, targetDirectory, { recursive: true })
+
+const targetIndex = path.join(targetDirectory, 'index.html')
+const normalizedIndex = fs
+ .readFileSync(targetIndex, 'utf8')
+ .replace(/\r\n?/g, '\n')
+ .replace(/\n[ \t]*\n([ \t]*<\/body>)/g, '\n$1')
+fs.writeFileSync(targetIndex, normalizedIndex)
+
+console.log(`Published NUI to ${targetDirectory}`)
diff --git a/frontend/env.d.ts b/frontend/env.d.ts
new file mode 100644
index 0000000..5bdd12d
--- /dev/null
+++ b/frontend/env.d.ts
@@ -0,0 +1,6 @@
+///
+ {{ phone.t('Phone.readyBody') }} +
+