diff --git a/index.html b/index.html
index dde16aa..d9ec90d 100644
--- a/index.html
+++ b/index.html
@@ -3,8 +3,9 @@
-
- Vite + Vue + TS
+
+
+ I LOVE YOU
diff --git a/package-lock.json b/package-lock.json
index 109a970..75f6987 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -17,6 +17,8 @@
"vue-router": "^4.5.0"
},
"devDependencies": {
+ "@types/node": "^24.1.0",
+ "@types/three": "^0.178.1",
"@vitejs/plugin-vue": "^6.0.0",
"@vue/eslint-config-prettier": "^10.2.0",
"@vue/eslint-config-typescript": "^14.5.0",
@@ -540,6 +542,13 @@
"node": ">=6.9.0"
}
},
+ "node_modules/@dimforge/rapier3d-compat": {
+ "version": "0.12.0",
+ "resolved": "https://registry.npmjs.org/@dimforge/rapier3d-compat/-/rapier3d-compat-0.12.0.tgz",
+ "integrity": "sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
"node_modules/@esbuild/aix-ppc64": {
"version": "0.25.8",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz",
@@ -1875,6 +1884,13 @@
"vite": "^5.2.0 || ^6 || ^7"
}
},
+ "node_modules/@tweenjs/tween.js": {
+ "version": "23.1.3",
+ "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-23.1.3.tgz",
+ "integrity": "sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/draco3d": {
"version": "1.4.10",
"resolved": "https://registry.npmjs.org/@types/draco3d/-/draco3d-1.4.10.tgz",
@@ -1894,12 +1910,52 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/node": {
+ "version": "24.1.0",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.1.0.tgz",
+ "integrity": "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~7.8.0"
+ }
+ },
"node_modules/@types/offscreencanvas": {
"version": "2019.7.3",
"resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.3.tgz",
"integrity": "sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A==",
"license": "MIT"
},
+ "node_modules/@types/stats.js": {
+ "version": "0.17.4",
+ "resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.4.tgz",
+ "integrity": "sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/three": {
+ "version": "0.178.1",
+ "resolved": "https://registry.npmjs.org/@types/three/-/three-0.178.1.tgz",
+ "integrity": "sha512-WSabew1mgWgRx2RfLfKY+9h4wyg6U94JfLbZEGU245j/WY2kXqU0MUfghS+3AYMV5ET1VlILAgpy77cB6a3Itw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@dimforge/rapier3d-compat": "~0.12.0",
+ "@tweenjs/tween.js": "~23.1.3",
+ "@types/stats.js": "*",
+ "@types/webxr": "*",
+ "@webgpu/types": "*",
+ "fflate": "~0.8.2",
+ "meshoptimizer": "~0.18.1"
+ }
+ },
+ "node_modules/@types/three/node_modules/fflate": {
+ "version": "0.8.2",
+ "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
+ "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/webxr": {
"version": "0.5.22",
"resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.22.tgz",
@@ -2515,6 +2571,13 @@
}
}
},
+ "node_modules/@webgpu/types": {
+ "version": "0.1.64",
+ "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.64.tgz",
+ "integrity": "sha512-84kRIAGV46LJTlJZWxShiOrNL30A+9KokD7RB3dRCIqODFjodS5tCD5yyiZ8kIReGVZSDfA3XkkwyyOIF6K62A==",
+ "dev": true,
+ "license": "BSD-3-Clause"
+ },
"node_modules/acorn": {
"version": "8.15.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
@@ -4503,6 +4566,13 @@
"node": ">= 8"
}
},
+ "node_modules/meshoptimizer": {
+ "version": "0.18.1",
+ "resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-0.18.1.tgz",
+ "integrity": "sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/micromatch": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
@@ -5544,6 +5614,13 @@
"typescript": ">=4.8.4 <5.9.0"
}
},
+ "node_modules/undici-types": {
+ "version": "7.8.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz",
+ "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==",
+ "devOptional": true,
+ "license": "MIT"
+ },
"node_modules/unicorn-magic": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz",
diff --git a/package.json b/package.json
index 09f0974..1006c8d 100644
--- a/package.json
+++ b/package.json
@@ -21,6 +21,8 @@
"vue-router": "^4.5.0"
},
"devDependencies": {
+ "@types/node": "^24.1.0",
+ "@types/three": "^0.178.1",
"@vitejs/plugin-vue": "^6.0.0",
"@vue/eslint-config-prettier": "^10.2.0",
"@vue/eslint-config-typescript": "^14.5.0",
diff --git a/src/models/Heart.glb b/public/models/heart.glb
similarity index 100%
rename from src/models/Heart.glb
rename to public/models/heart.glb
diff --git a/src/App.vue b/src/App.vue
index 9691f5b..c89ff2a 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -1,27 +1,10 @@
-
-
-
+
+
+
-
+
diff --git a/src/components/typography/UiHeadering.vue b/src/components/typography/UiHeadering.vue
new file mode 100644
index 0000000..3c91ff9
--- /dev/null
+++ b/src/components/typography/UiHeadering.vue
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
diff --git a/src/components/typography/UiParagraph.vue b/src/components/typography/UiParagraph.vue
new file mode 100644
index 0000000..105854e
--- /dev/null
+++ b/src/components/typography/UiParagraph.vue
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
diff --git a/src/constants/heart-section.ts b/src/constants/heart-section.ts
new file mode 100644
index 0000000..88a2b3d
--- /dev/null
+++ b/src/constants/heart-section.ts
@@ -0,0 +1,5 @@
+export default {
+ SCALE: 1,
+ DIRECTION: 1,
+ ANIMATION_ID: 0,
+}
diff --git a/src/constants/index.ts b/src/constants/index.ts
new file mode 100644
index 0000000..f65eebc
--- /dev/null
+++ b/src/constants/index.ts
@@ -0,0 +1 @@
+export { default as heartSectionConst } from './heart-section.ts'
diff --git a/src/declarations.d.ts b/src/declarations.d.ts
new file mode 100644
index 0000000..bcce09b
--- /dev/null
+++ b/src/declarations.d.ts
@@ -0,0 +1,66 @@
+declare module 'three/examples/jsm/loaders/GLTFLoader' {
+ import { Loader, LoadingManager, Group, AnimationClip, Object3D } from 'three'
+
+ export interface GLTF {
+ scene: Group
+ scenes: Group[]
+ animations: AnimationClip[]
+ cameras: Object3D[]
+ asset: Record
+ }
+
+ export class GLTFLoader extends Loader {
+ constructor(manager?: LoadingManager)
+ load(
+ url: string,
+ onLoad: (gltf: GLTF) => void,
+ onProgress?: (event: ProgressEvent) => void,
+ onError?: (event: ErrorEvent | unknown) => void
+ ): void
+ }
+}
+
+declare module 'three/examples/jsm/controls/OrbitControls' {
+ import { Camera, EventDispatcher, MOUSE, TOUCH, Vector3 } from 'three'
+
+ export class OrbitControls extends EventDispatcher {
+ constructor(object: Camera, domElement?: HTMLElement)
+ object: Camera
+ target: Vector3
+ update(): boolean
+ dispose(): void
+
+ enabled: boolean
+ autoRotate: boolean
+ autoRotateSpeed: number
+
+ minDistance: number
+ maxDistance: number
+
+ minZoom: number
+ maxZoom: number
+
+ minPolarAngle: number
+ maxPolarAngle: number
+
+ minAzimuthAngle: number
+ maxAzimuthAngle: number
+
+ enableDamping: boolean
+ dampingFactor: number
+
+ enableZoom: boolean
+ zoomSpeed: number
+
+ enableRotate: boolean
+ rotateSpeed: number
+
+ enablePan: boolean
+ panSpeed: number
+ screenSpacePanning: boolean
+ keyPanSpeed: number
+
+ mouseButtons: { LEFT: MOUSE; MIDDLE: MOUSE; RIGHT: MOUSE }
+ touches: { ONE: TOUCH; TWO: TOUCH }
+ }
+}
diff --git a/src/layout/UiDefault.vue b/src/layout/UiDefault.vue
new file mode 100644
index 0000000..8f71aa8
--- /dev/null
+++ b/src/layout/UiDefault.vue
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/layout/UiFooter.vue b/src/layout/UiFooter.vue
new file mode 100644
index 0000000..967ca28
--- /dev/null
+++ b/src/layout/UiFooter.vue
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/src/layout/UiHeader.vue b/src/layout/UiHeader.vue
new file mode 100644
index 0000000..94cbc6e
--- /dev/null
+++ b/src/layout/UiHeader.vue
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/src/layout/UiMain.vue b/src/layout/UiMain.vue
new file mode 100644
index 0000000..55b5129
--- /dev/null
+++ b/src/layout/UiMain.vue
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/src/main.ts b/src/main.ts
index 6a27733..2b047f0 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -1,5 +1,8 @@
import { createApp } from 'vue'
import '../src/assets/main.css'
import App from './App.vue'
+import router from './router'
-createApp(App).mount('#app')
+const app = createApp(App)
+app.use(router)
+app.mount('#app')
diff --git a/src/pages/index/_ui/heartSection.vue b/src/pages/index/_ui/heartSection.vue
new file mode 100644
index 0000000..41beff5
--- /dev/null
+++ b/src/pages/index/_ui/heartSection.vue
@@ -0,0 +1,88 @@
+
+
+
+
+
diff --git a/src/pages/index/rootPage.vue b/src/pages/index/rootPage.vue
new file mode 100644
index 0000000..bc9bbb1
--- /dev/null
+++ b/src/pages/index/rootPage.vue
@@ -0,0 +1,9 @@
+
+
+
+
+
diff --git a/src/router/config/_type/TRoutes.ts b/src/router/config/_type/TRoutes.ts
new file mode 100644
index 0000000..fcadd8d
--- /dev/null
+++ b/src/router/config/_type/TRoutes.ts
@@ -0,0 +1,7 @@
+import type { Component } from 'vue'
+
+export type TRoute = {
+ path: string
+ name: string
+ component: () => Promise
+}
diff --git a/src/router/config/routes.ts b/src/router/config/routes.ts
new file mode 100644
index 0000000..7101618
--- /dev/null
+++ b/src/router/config/routes.ts
@@ -0,0 +1,10 @@
+import type { TRoute } from './_type/TRoutes.ts'
+
+const routes: TRoute[] = [
+ {
+ path: '/',
+ name: '/',
+ component: () => import('../../pages/index/rootPage.vue'),
+ },
+]
+export default routes
diff --git a/src/router/index.ts b/src/router/index.ts
new file mode 100644
index 0000000..cef1576
--- /dev/null
+++ b/src/router/index.ts
@@ -0,0 +1,8 @@
+import { createRouter, createWebHistory } from 'vue-router'
+import routes from './config/routes.ts'
+
+const router = createRouter({
+ history: createWebHistory(import.meta.env.BASE_URL),
+ routes,
+})
+export default router
diff --git a/tsconfig.app.json b/tsconfig.app.json
index 70fe34f..31dad50 100644
--- a/tsconfig.app.json
+++ b/tsconfig.app.json
@@ -1,7 +1,11 @@
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
+ "include": ["./src/**/*", "./src/**/*.vue"],
+ "exclude": ["./src/**/__tests__/*"],
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
+ "@/*": ["./src/*"],
+ "~": ["../src/*"],
/* Linting */
"strict": true,
@@ -11,8 +15,4 @@
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
- "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"],
- "paths": {
- "@/*": ["./src/*"]
- }
}