diff --git a/package-lock.json b/package-lock.json
index ab8755124decc7449562e17c3f6fe85531d220d2..e033404d9e73b55448ecc76c9f34f9b993e8682d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,6 +8,7 @@
       "name": "idatt2106_2024_02_frontend",
       "version": "0.0.0",
       "dependencies": {
+        "animejs": "^3.2.2",
         "pinia": "^2.1.7",
         "vue": "^3.4.21",
         "vue-router": "^4.3.1"
@@ -15,8 +16,11 @@
       "devDependencies": {
         "@rushstack/eslint-patch": "^1.8.0",
         "@tsconfig/node20": "^20.1.4",
+        "@types/animejs": "^3.1.12",
         "@types/jsdom": "^21.1.6",
         "@types/node": "^20.12.5",
+        "@typescript-eslint/eslint-plugin": "^7.7.0",
+        "@typescript-eslint/parser": "^7.7.0",
         "@vitejs/plugin-vue": "^5.0.4",
         "@vitest/coverage-v8": "^1.5.0",
         "@vue/eslint-config-prettier": "^9.0.0",
@@ -1143,6 +1147,12 @@
       "integrity": "sha512-sqgsT69YFeLWf5NtJ4Xq/xAF8p4ZQHlmGW74Nu2tD4+g5fAsposc4ZfaaPixVu4y01BEiDCWLRDCvDM5JOsRxg==",
       "dev": true
     },
+    "node_modules/@types/animejs": {
+      "version": "3.1.12",
+      "resolved": "https://registry.npmjs.org/@types/animejs/-/animejs-3.1.12.tgz",
+      "integrity": "sha512-fpdH+ZtlO0kqjTOqRaBdsEmvpRNOayI8k4EVkEtitL5l6wducDOXk0rgQgfZqWf/ZX9DzXrHf257S5i9xTcISQ==",
+      "dev": true
+    },
     "node_modules/@types/estree": {
       "version": "1.0.5",
       "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
@@ -1210,16 +1220,16 @@
       }
     },
     "node_modules/@typescript-eslint/eslint-plugin": {
-      "version": "7.6.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.6.0.tgz",
-      "integrity": "sha512-gKmTNwZnblUdnTIJu3e9kmeRRzV2j1a/LUO27KNNAnIC5zjy1aSvXSRp4rVNlmAoHlQ7HzX42NbKpcSr4jF80A==",
+      "version": "7.7.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.7.0.tgz",
+      "integrity": "sha512-GJWR0YnfrKnsRoluVO3PRb9r5aMZriiMMM/RHj5nnTrBy1/wIgk76XCtCKcnXGjpZQJQRFtGV9/0JJ6n30uwpQ==",
       "dev": true,
       "dependencies": {
         "@eslint-community/regexpp": "^4.10.0",
-        "@typescript-eslint/scope-manager": "7.6.0",
-        "@typescript-eslint/type-utils": "7.6.0",
-        "@typescript-eslint/utils": "7.6.0",
-        "@typescript-eslint/visitor-keys": "7.6.0",
+        "@typescript-eslint/scope-manager": "7.7.0",
+        "@typescript-eslint/type-utils": "7.7.0",
+        "@typescript-eslint/utils": "7.7.0",
+        "@typescript-eslint/visitor-keys": "7.7.0",
         "debug": "^4.3.4",
         "graphemer": "^1.4.0",
         "ignore": "^5.3.1",
@@ -1245,15 +1255,15 @@
       }
     },
     "node_modules/@typescript-eslint/parser": {
-      "version": "7.6.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.6.0.tgz",
-      "integrity": "sha512-usPMPHcwX3ZoPWnBnhhorc14NJw9J4HpSXQX4urF2TPKG0au0XhJoZyX62fmvdHONUkmyUe74Hzm1//XA+BoYg==",
+      "version": "7.7.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.7.0.tgz",
+      "integrity": "sha512-fNcDm3wSwVM8QYL4HKVBggdIPAy9Q41vcvC/GtDobw3c4ndVT3K6cqudUmjHPw8EAp4ufax0o58/xvWaP2FmTg==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/scope-manager": "7.6.0",
-        "@typescript-eslint/types": "7.6.0",
-        "@typescript-eslint/typescript-estree": "7.6.0",
-        "@typescript-eslint/visitor-keys": "7.6.0",
+        "@typescript-eslint/scope-manager": "7.7.0",
+        "@typescript-eslint/types": "7.7.0",
+        "@typescript-eslint/typescript-estree": "7.7.0",
+        "@typescript-eslint/visitor-keys": "7.7.0",
         "debug": "^4.3.4"
       },
       "engines": {
@@ -1273,13 +1283,13 @@
       }
     },
     "node_modules/@typescript-eslint/scope-manager": {
-      "version": "7.6.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.6.0.tgz",
-      "integrity": "sha512-ngttyfExA5PsHSx0rdFgnADMYQi+Zkeiv4/ZxGYUWd0nLs63Ha0ksmp8VMxAIC0wtCFxMos7Lt3PszJssG/E6w==",
+      "version": "7.7.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.7.0.tgz",
+      "integrity": "sha512-/8INDn0YLInbe9Wt7dK4cXLDYp0fNHP5xKLHvZl3mOT5X17rK/YShXaiNmorl+/U4VKCVIjJnx4Ri5b0y+HClw==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "7.6.0",
-        "@typescript-eslint/visitor-keys": "7.6.0"
+        "@typescript-eslint/types": "7.7.0",
+        "@typescript-eslint/visitor-keys": "7.7.0"
       },
       "engines": {
         "node": "^18.18.0 || >=20.0.0"
@@ -1290,13 +1300,13 @@
       }
     },
     "node_modules/@typescript-eslint/type-utils": {
-      "version": "7.6.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.6.0.tgz",
-      "integrity": "sha512-NxAfqAPNLG6LTmy7uZgpK8KcuiS2NZD/HlThPXQRGwz6u7MDBWRVliEEl1Gj6U7++kVJTpehkhZzCJLMK66Scw==",
+      "version": "7.7.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.7.0.tgz",
+      "integrity": "sha512-bOp3ejoRYrhAlnT/bozNQi3nio9tIgv3U5C0mVDdZC7cpcQEDZXvq8inrHYghLVwuNABRqrMW5tzAv88Vy77Sg==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/typescript-estree": "7.6.0",
-        "@typescript-eslint/utils": "7.6.0",
+        "@typescript-eslint/typescript-estree": "7.7.0",
+        "@typescript-eslint/utils": "7.7.0",
         "debug": "^4.3.4",
         "ts-api-utils": "^1.3.0"
       },
@@ -1317,9 +1327,9 @@
       }
     },
     "node_modules/@typescript-eslint/types": {
-      "version": "7.6.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.6.0.tgz",
-      "integrity": "sha512-h02rYQn8J+MureCvHVVzhl69/GAfQGPQZmOMjG1KfCl7o3HtMSlPaPUAPu6lLctXI5ySRGIYk94clD/AUMCUgQ==",
+      "version": "7.7.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.7.0.tgz",
+      "integrity": "sha512-G01YPZ1Bd2hn+KPpIbrAhEWOn5lQBrjxkzHkWvP6NucMXFtfXoevK82hzQdpfuQYuhkvFDeQYbzXCjR1z9Z03w==",
       "dev": true,
       "engines": {
         "node": "^18.18.0 || >=20.0.0"
@@ -1330,13 +1340,13 @@
       }
     },
     "node_modules/@typescript-eslint/typescript-estree": {
-      "version": "7.6.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.6.0.tgz",
-      "integrity": "sha512-+7Y/GP9VuYibecrCQWSKgl3GvUM5cILRttpWtnAu8GNL9j11e4tbuGZmZjJ8ejnKYyBRb2ddGQ3rEFCq3QjMJw==",
+      "version": "7.7.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.7.0.tgz",
+      "integrity": "sha512-8p71HQPE6CbxIBy2kWHqM1KGrC07pk6RJn40n0DSc6bMOBBREZxSDJ+BmRzc8B5OdaMh1ty3mkuWRg4sCFiDQQ==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "7.6.0",
-        "@typescript-eslint/visitor-keys": "7.6.0",
+        "@typescript-eslint/types": "7.7.0",
+        "@typescript-eslint/visitor-keys": "7.7.0",
         "debug": "^4.3.4",
         "globby": "^11.1.0",
         "is-glob": "^4.0.3",
@@ -1358,17 +1368,17 @@
       }
     },
     "node_modules/@typescript-eslint/utils": {
-      "version": "7.6.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.6.0.tgz",
-      "integrity": "sha512-x54gaSsRRI+Nwz59TXpCsr6harB98qjXYzsRxGqvA5Ue3kQH+FxS7FYU81g/omn22ML2pZJkisy6Q+ElK8pBCA==",
+      "version": "7.7.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.7.0.tgz",
+      "integrity": "sha512-LKGAXMPQs8U/zMRFXDZOzmMKgFv3COlxUQ+2NMPhbqgVm6R1w+nU1i4836Pmxu9jZAuIeyySNrN/6Rc657ggig==",
       "dev": true,
       "dependencies": {
         "@eslint-community/eslint-utils": "^4.4.0",
         "@types/json-schema": "^7.0.15",
         "@types/semver": "^7.5.8",
-        "@typescript-eslint/scope-manager": "7.6.0",
-        "@typescript-eslint/types": "7.6.0",
-        "@typescript-eslint/typescript-estree": "7.6.0",
+        "@typescript-eslint/scope-manager": "7.7.0",
+        "@typescript-eslint/types": "7.7.0",
+        "@typescript-eslint/typescript-estree": "7.7.0",
         "semver": "^7.6.0"
       },
       "engines": {
@@ -1383,12 +1393,12 @@
       }
     },
     "node_modules/@typescript-eslint/visitor-keys": {
-      "version": "7.6.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.6.0.tgz",
-      "integrity": "sha512-4eLB7t+LlNUmXzfOu1VAIAdkjbu5xNSerURS9X/S5TUKWFRpXRQZbmtPqgKmYx8bj3J0irtQXSiWAOY82v+cgw==",
+      "version": "7.7.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.7.0.tgz",
+      "integrity": "sha512-h0WHOj8MhdhY8YWkzIF30R379y0NqyOHExI9N9KCzvmu05EgG4FumeYa3ccfKUSphyWkWQE1ybVrgz/Pbam6YA==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "7.6.0",
+        "@typescript-eslint/types": "7.7.0",
         "eslint-visitor-keys": "^3.4.3"
       },
       "engines": {
@@ -1831,6 +1841,11 @@
         "url": "https://github.com/sponsors/epoberezkin"
       }
     },
+    "node_modules/animejs": {
+      "version": "3.2.2",
+      "resolved": "https://registry.npmjs.org/animejs/-/animejs-3.2.2.tgz",
+      "integrity": "sha512-Ao95qWLpDPXXM+WrmwcKbl6uNlC5tjnowlaRYtuVDHHoygjtIPfDUoK9NthrlZsQSKjZXlmji2TrBUAVbiH0LQ=="
+    },
     "node_modules/ansi-colors": {
       "version": "4.1.3",
       "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz",
diff --git a/package.json b/package.json
index 5fcdb2e2afb3b02c395000e730ffc39b72207653..53e12f251ec26cadb44ba81d493081cb723e8159 100644
--- a/package.json
+++ b/package.json
@@ -18,6 +18,7 @@
     "format-test": "prettier --check src/"
   },
   "dependencies": {
+    "animejs": "^3.2.2",
     "pinia": "^2.1.7",
     "vue": "^3.4.21",
     "vue-router": "^4.3.1"
@@ -25,8 +26,11 @@
   "devDependencies": {
     "@rushstack/eslint-patch": "^1.8.0",
     "@tsconfig/node20": "^20.1.4",
+    "@types/animejs": "^3.1.12",
     "@types/jsdom": "^21.1.6",
     "@types/node": "^20.12.5",
+    "@typescript-eslint/eslint-plugin": "^7.7.0",
+    "@typescript-eslint/parser": "^7.7.0",
     "@vitejs/plugin-vue": "^5.0.4",
     "@vitest/coverage-v8": "^1.5.0",
     "@vue/eslint-config-prettier": "^9.0.0",
diff --git a/src/assets/coffee.png b/src/assets/coffee.png
new file mode 100644
index 0000000000000000000000000000000000000000..4862babd365d69b653cd83415f00520ce3bf3684
Binary files /dev/null and b/src/assets/coffee.png differ
diff --git a/src/assets/completed.png b/src/assets/completed.png
new file mode 100644
index 0000000000000000000000000000000000000000..3b3e629dc3b97bfc0eb09aa27cc520579ddc322b
Binary files /dev/null and b/src/assets/completed.png differ
diff --git a/src/assets/finishLine.png b/src/assets/finishLine.png
new file mode 100644
index 0000000000000000000000000000000000000000..a0da9ecd3be1e39e70d342a2c580d09ce80015d0
Binary files /dev/null and b/src/assets/finishLine.png differ
diff --git a/src/assets/frozenStreak.png b/src/assets/frozenStreak.png
new file mode 100644
index 0000000000000000000000000000000000000000..9d60b296b4432f7ea2f469b0309c1ebae9b5699f
Binary files /dev/null and b/src/assets/frozenStreak.png differ
diff --git a/src/assets/gaming.png b/src/assets/gaming.png
new file mode 100644
index 0000000000000000000000000000000000000000..b02ffa01a3b3a7267925a1eeb8084ac70c2f132c
Binary files /dev/null and b/src/assets/gaming.png differ
diff --git a/src/assets/lock.png b/src/assets/lock.png
new file mode 100644
index 0000000000000000000000000000000000000000..d4598c4c7e310202edd3b81af5edc13fcecdaf76
Binary files /dev/null and b/src/assets/lock.png differ
diff --git a/src/assets/pending.png b/src/assets/pending.png
new file mode 100644
index 0000000000000000000000000000000000000000..734d180e43d50e93d315ff8c56f1d54c1cb05224
Binary files /dev/null and b/src/assets/pending.png differ
diff --git a/src/assets/pigSteps.png b/src/assets/pigSteps.png
new file mode 100644
index 0000000000000000000000000000000000000000..45309466a7ec3d8b163f5ca4e74671750fc628f2
Binary files /dev/null and b/src/assets/pigSteps.png differ
diff --git a/src/assets/snacks.png b/src/assets/snacks.png
new file mode 100644
index 0000000000000000000000000000000000000000..45ad39383c62ca25334634bd5747bccab7a4dac3
Binary files /dev/null and b/src/assets/snacks.png differ
diff --git a/src/assets/spare.png b/src/assets/spare.png
new file mode 100644
index 0000000000000000000000000000000000000000..8e652fe5646673f24da26874beb8b7bd109b6760
Binary files /dev/null and b/src/assets/spare.png differ
diff --git a/src/assets/streak.png b/src/assets/streak.png
new file mode 100644
index 0000000000000000000000000000000000000000..16a0e93231f89c033cfb25938f0a97b1fccbeae6
Binary files /dev/null and b/src/assets/streak.png differ
diff --git a/src/components/ButtonAddGoalOrChallange.vue b/src/components/ButtonAddGoalOrChallange.vue
new file mode 100644
index 0000000000000000000000000000000000000000..820233b39e4ef56f96b58303cc965e33444f9fde
--- /dev/null
+++ b/src/components/ButtonAddGoalOrChallange.vue
@@ -0,0 +1,33 @@
+<template>
+    <button
+        class="w-full max-w-60 max-h-12 bg-green-500 text-white font-bold py-2 rounded-full flex items-center justify-start pl-4 space-x-2 hover:bg-green-600 drop-shadow-lg focus:outline-none focus:ring-2 focus:ring-green-700 focus:ring-opacity-50 shadow-md transition duration-300 ease-in-out text-xs md:text-sm lg:text-base"
+    >
+        <svg
+            xmlns="http://www.w3.org/2000/svg"
+            fill="none"
+            viewBox="0 0 24 24"
+            stroke="currentColor"
+            class="w-6 h-6"
+        >
+            <path
+                stroke-linecap="round"
+                stroke-linejoin="round"
+                stroke-width="2"
+                d="M12 4v16m8-8H4"
+            />
+        </svg>
+        <span class="truncate">{{ btnText }}</span>
+    </button>
+</template>
+
+<script setup lang="ts">
+import { defineProps, ref } from 'vue'
+
+interface Props {
+    buttonText: string
+}
+
+const props = defineProps<Props>()
+
+const btnText = ref(props.buttonText)
+</script>
diff --git a/src/components/ButtonDIsplayStreak.vue b/src/components/ButtonDIsplayStreak.vue
new file mode 100644
index 0000000000000000000000000000000000000000..ec0276559164c1ec544453a4c8e5c880b94281cf
--- /dev/null
+++ b/src/components/ButtonDIsplayStreak.vue
@@ -0,0 +1,76 @@
+<template>
+    <div class="flex flex-col items-center">
+        <Span class="text-sm text-bold">STREAK</Span>
+        <button @click="toggleStreakCard" class="bg-transparent">
+            <img src="@/assets/streak.png" alt="streak" class="mx-auto w-12 h-12" />
+        </button>
+
+        <div
+            v-if="displayStreakCard"
+            class="w-96 h-64 duration-500 group overflow-hidden relative rounded bg-white-800 text-neutral-50 p-4 flex flex-col justify-evenly"
+        >
+            <div
+                class="absolute blur opacity-40 duration-500 group-hover:blur-none w-72 h-72 rounded-full group-hover:translate-x-12 group-hover:translate-y-12 bg-green-100 right-1 -bottom-24"
+            ></div>
+            <div
+                class="absolute blur opacity-40 duration-500 group-hover:blur-none w-12 h-12 rounded-full group-hover:translate-x-12 group-hover:translate-y-2 bg-green-300 right-12 bottom-12"
+            ></div>
+            <div
+                class="absolute blur opacity-40 duration-500 group-hover:blur-none w-36 h-36 rounded-full group-hover:translate-x-12 group-hover:-translate-y-12 bg-green-500 right-1 -top-12"
+            ></div>
+            <div
+                class="absolute blur opacity-40 duration-500 group-hover:blur-none w-24 h-24 bg-green-400 rounded-full group-hover:-translate-x-12"
+            ></div>
+            <div class="z-10 flex flex-col justify-evenly w-full h-full px-4">
+                <span class="text-2xl font-bold text-black"
+                    >{{ currentStreak }}{{ currentStreak === 1 ? ' dag' : ' dager' }} streak</span
+                >
+                <p class="text-black text-1xl font-bold">
+                    {{
+                        currentStreak > 0
+                            ? 'Bra jobba du har spart i ' + currentStreak + ' dager!'
+                            : 'Du har ikke gjort noe i dag. Gjør noe nå for å starte en streak!'
+                    }}
+                </p>
+                <!-- Row component with horizontal padding and auto margins for centering -->
+                <div
+                    class="flex flex-row justify-content-center items-center h-20 w-full mx-auto bg-black-400 gap-4"
+                >
+                    <div class="flex flex-1 overflow-x-auto">
+                        <div v-for="index in 6" :key="index" class="min-w-max mx-auto">
+                            <div class="flex flex-col items-center">
+                                <span class="text-black"
+                                    >Dag {{ currentStreak - ((currentStreak % 7) - index) }}</span
+                                >
+                                <!-- Conditional rendering for streak images -->
+                                <img
+                                    v-if="index - 1 < currentStreak % 7"
+                                    src="@/assets/streak.png"
+                                    alt="challenge completed"
+                                    class="max-h-8 max-w-8"
+                                />
+                                <img
+                                    v-else
+                                    src="@/assets/streak.png"
+                                    alt="challenge not completed"
+                                    class="max-h-8 max-w-8 grayscale"
+                                />
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</template>
+
+<script setup lang="ts">
+import { ref } from 'vue'
+
+const displayStreakCard = ref(false)
+const currentStreak = ref(5)
+
+function toggleStreakCard() {
+    displayStreakCard.value = !displayStreakCard.value
+}
+</script>
diff --git a/src/components/InteractiveSpare.vue b/src/components/InteractiveSpare.vue
new file mode 100644
index 0000000000000000000000000000000000000000..5d50f1e4a45eb8fc7a748b4d10209f6a309de4b9
--- /dev/null
+++ b/src/components/InteractiveSpare.vue
@@ -0,0 +1,62 @@
+<template>
+    <div
+        class="flex items-center max-w-80 w-full"
+        :class="{ 'flex-row': direction === 'right', 'flex-row-reverse': direction === 'left' }"
+    >
+        <!-- Image -->
+        <img
+            :src="spareImageSrc"
+            :class="['w-' + pngSize, 'h-' + pngSize, imageClass]"
+            alt="Sparemannen"
+            @click="nextSpeech"
+        />
+        <!-- Speech Bubble -->
+        <div
+            v-if="currentSpeech"
+            class="rounded-lg bg-white p-4 text-black border-black border-2 min-h-16 max-w-48"
+        >
+            <span>{{ currentSpeech }}</span>
+        </div>
+    </div>
+</template>
+
+<script setup lang="ts">
+import { ref, defineProps, computed } from 'vue'
+import spareImageSrc from '@/assets/spare.png'
+
+interface Props {
+    speech?: string[] // Using TypeScript's type for speech as an array of strings
+    direction: 'left' | 'right' // This restricts direction to either 'left' or 'right'
+    pngSize: number // Just declaring the type directly since it's simple
+}
+
+const props = defineProps<Props>()
+
+const speech = ref<String[]>(props.speech || [])
+
+const currentSpeechIndex = ref(0)
+const currentSpeech = computed(() => speech.value[currentSpeechIndex.value])
+
+const nextSpeech = () => {
+    if (speech.value.length > 0) {
+        // Remove the currently displayed speech first
+        speech.value.splice(currentSpeechIndex.value, 1)
+
+        // Check if there are any speeches left after removal
+        if (speech.value.length > 0) {
+            // Move to the next speech or reset to the beginning if the current index is out of range
+            currentSpeechIndex.value = currentSpeechIndex.value % speech.value.length
+        } else {
+            // If no speeches left, reset index to indicate no available speech
+            currentSpeechIndex.value = -1
+        }
+    }
+}
+
+const imageClass = computed(() => {
+    return [
+        'transform',
+        props.direction === 'right' ? 'scale-x-[-1]' : '' // Flip image if right
+    ]
+})
+</script>
diff --git a/src/components/__tests__/ButtonAddGoalOrChallengeTest.spec.ts b/src/components/__tests__/ButtonAddGoalOrChallengeTest.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..144e004d9fca598adc9c71298004944d5721c8af
--- /dev/null
+++ b/src/components/__tests__/ButtonAddGoalOrChallengeTest.spec.ts
@@ -0,0 +1,64 @@
+import { describe, it, expect } from 'vitest'
+import { mount } from '@vue/test-utils'
+import ButtonComponent from '@/components/ButtonAddGoalOrChallange.vue' // Adjust the import path as needed.
+
+describe('ButtonComponent', () => {
+    it('renders correctly', () => {
+        const wrapper = mount(ButtonComponent, {
+            props: {
+                buttonText: 'Click me'
+            }
+        })
+        expect(wrapper.exists()).toBe(true)
+    })
+
+    it('has the correct classes', () => {
+        const wrapper = mount(ButtonComponent, {
+            props: {
+                buttonText: 'Click me'
+            }
+        })
+        const button = wrapper.find('button')
+        const expectedClasses = [
+            'w-full',
+            'max-w-60',
+            'max-h-12',
+            'bg-green-500',
+            'text-white',
+            'font-bold',
+            'py-2',
+            'rounded-full',
+            'flex',
+            'items-center',
+            'justify-start',
+            'pl-4',
+            'space-x-2',
+            'hover:bg-green-600',
+            'drop-shadow-lg',
+            'focus:outline-none',
+            'focus:ring-2',
+            'focus:ring-green-700',
+            'focus:ring-opacity-50',
+            'shadow-md',
+            'transition',
+            'duration-300',
+            'ease-in-out',
+            'text-xs',
+            'md:text-sm',
+            'lg:text-base'
+        ]
+        expectedClasses.forEach((cls) => {
+            expect(button.classes()).toContain(cls)
+        })
+    })
+
+    it('displays the correct button text', () => {
+        const wrapper = mount(ButtonComponent, {
+            props: {
+                buttonText: 'Submit'
+            }
+        })
+        const buttonText = wrapper.find('span.truncate')
+        expect(buttonText.text()).toBe('Submit')
+    })
+})
diff --git a/src/components/__tests__/HomeViewTest.spec.ts b/src/components/__tests__/HomeViewTest.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8d51d5d7d72355f883b1dabc6cbb4a44816800b9
--- /dev/null
+++ b/src/components/__tests__/HomeViewTest.spec.ts
@@ -0,0 +1,168 @@
+import { describe, it, expect, vi, beforeEach } from 'vitest'
+import { mount } from '@vue/test-utils'
+import HomeView from '@/views/HomeView.vue' // Adjust the import path as needed.
+import anime from 'animejs'
+import type { Challenge } from '../../types/challenge'
+import type { Goal } from '../../types/goal'
+
+// Setup localStorage mock
+const localStorageMock = (function () {
+    let store = {} as { [key: string]: string }
+    return {
+        getItem: vi.fn((key: string) => store[key] || null),
+        setItem: vi.fn((key: string, value: any) => {
+            store[key] = value.toString()
+        }),
+        clear: vi.fn(() => {
+            store = {}
+        }),
+        removeItem: vi.fn((key: string) => {
+            delete store[key]
+        }),
+        get length() {
+            return Object.keys(store).length
+        },
+        key: vi.fn((index: number): string | null => {
+            const keys = Object.keys(store)
+            return keys[index] || null
+        }),
+        __store: store // expose store for assertions
+    }
+})()
+Object.defineProperty(global, 'localStorage', {
+    value: localStorageMock,
+    writable: true
+})
+
+// Mocking animejs with a default export
+vi.mock('animejs', () => ({
+    default: {
+        // Ensuring the mock includes a 'default' export
+        timeline: vi.fn(() => ({
+            add: vi.fn().mockReturnThis()
+        }))
+    }
+}))
+
+describe('HomeView', () => {
+    let wrapper: any
+
+    beforeEach(() => {
+        // Clear localStorage and reset all mocks
+        localStorage.clear()
+        vi.clearAllMocks()
+        wrapper = mount(HomeView, {
+            global: {
+                mocks: {
+                    $router: {
+                        push: vi.fn()
+                    }
+                }
+            }
+        })
+    })
+
+    it('renders correctly and initializes data', () => {
+        expect(wrapper.find('.no-scrollbar').exists()).toBe(true)
+        expect(wrapper.vm.challenges.length).toBeGreaterThan(0)
+    })
+
+    it('handles incrementSaved correctly', async () => {
+        const challenge: Challenge = {
+            createdOn: new Date(),
+            description: '',
+            title: 'Kaffe',
+            saved: 90,
+            target: 100,
+            completion: 90
+        }
+        wrapper.vm.incrementSaved(challenge)
+        expect(challenge.saved).toBe(100)
+        expect(challenge.completion).toBe(100)
+    })
+
+    it('animates on challenge completion', async () => {
+        const challenge: Challenge = {
+            createdOn: new Date(),
+            description: '',
+            title: 'Mat og Drikke',
+            type: 'SNACKS',
+            saved: 100,
+            target: 100,
+            completion: 100
+        }
+        wrapper.vm.animateChallenge(challenge)
+        await wrapper.vm.$nextTick()
+        expect(anime.timeline).toHaveBeenCalled()
+    })
+
+    it('persists animated challenges to localStorage', async () => {
+        const challenge = {
+            title: 'Gaming',
+            challengeType: 'GAMING',
+            saved: 100,
+            target: 100,
+            completion: 100
+        }
+        await wrapper.vm.animateChallenge(challenge)
+        expect(localStorage.setItem).toHaveBeenCalled()
+        expect(localStorage.getItem('animatedChallenges')).toContain('Gaming')
+    })
+
+    it('correctly computes currentGoal based on goals', async () => {
+        const goal: Goal = {
+            id: 1,
+            due: new Date(),
+            createdOn: new Date(),
+            title: 'Vacation',
+            saved: 500,
+            target: 1500,
+            description: 'Summer vacation',
+            priority: 1,
+            completion: 33
+        }
+        await wrapper.vm.$nextTick()
+        expect(goal.title).toBe('Vacation')
+    })
+
+    it('responds to changes in challenges and updates animation states', async () => {
+        wrapper.vm.challenges.push({
+            title: 'New Challenge',
+            challengeType: 'COFFEE',
+            saved: 50,
+            target: 100,
+            completion: 50
+        })
+        await wrapper.vm.$nextTick()
+        wrapper.vm.challenges[wrapper.vm.challenges.length - 1].completion = 100 // Directly modify the data
+        await wrapper.vm.$nextTick()
+        expect(wrapper.vm.animatedChallenges.has('New Challenge')).toBe(true)
+    })
+
+    it('triggers animation on completion', async () => {
+        const challenge = {
+            title: 'Test Challenge',
+            challengeType: 'TEST',
+            saved: 100,
+            target: 100,
+            completion: 100
+        }
+        await wrapper.vm.animateChallenge(challenge)
+        await wrapper.vm.$nextTick() // Wait for all nextTick callbacks to resolve
+
+        expect(anime.timeline).toHaveBeenCalled() // Check if anime.timeline was called
+    })
+
+    // Test other methods like animateIcon, getChallengeIcon, etc.
+    it('returns correct icon path based on challenge type', () => {
+        const challenge: Challenge = {
+            createdOn: new Date(),
+            description: '',
+            saved: 0,
+            target: 0,
+            title: 'Coffee',
+            type: 'COFFEE'
+        }
+        expect(wrapper.vm.getChallengeIcon(challenge)).toBe('src/assets/coffee.png')
+    })
+})
diff --git a/src/components/__tests__/InteractiveSpareTest.spec.ts b/src/components/__tests__/InteractiveSpareTest.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..50351141febb49f6af92d3310d41b4de1ead3dfd
--- /dev/null
+++ b/src/components/__tests__/InteractiveSpareTest.spec.ts
@@ -0,0 +1,59 @@
+import { describe, it, expect } from 'vitest'
+import { mount } from '@vue/test-utils'
+import SpeechBubbleComponent from '@/components/InteractiveSpare.vue' // Adjust the import path as needed.
+
+describe('SpeechBubbleComponent', () => {
+    it('renders correctly with default props', () => {
+        const wrapper: any = mount(SpeechBubbleComponent, {
+            props: {
+                direction: 'left',
+                speech: ['Hello', 'World'],
+                pngSize: 100
+            }
+        })
+        expect(wrapper.exists()).toBeTruthy()
+    })
+
+    it('applies dynamic classes based on direction prop', () => {
+        const wrapper = mount(SpeechBubbleComponent, {
+            props: {
+                direction: 'right',
+                speech: ['Hello', 'World'],
+                pngSize: 100
+            }
+        })
+        expect(wrapper.find('div').classes()).toContain('flex-row')
+        const wrapperReverse = mount(SpeechBubbleComponent, {
+            props: {
+                direction: 'left',
+                speech: ['Hello', 'World'],
+                pngSize: 100
+            }
+        })
+        expect(wrapperReverse.find('div').classes()).toContain('flex-row-reverse')
+    })
+
+    it('image class is computed based on direction', () => {
+        const wrapper = mount(SpeechBubbleComponent, {
+            props: {
+                direction: 'right',
+                speech: ['Hello', 'World'],
+                pngSize: 100
+            }
+        })
+        expect(wrapper.find('img').classes()).toContain('scale-x-[-1]')
+    })
+
+    it('updates speech text on image click', async () => {
+        const wrapper = mount(SpeechBubbleComponent, {
+            props: {
+                direction: 'left',
+                speech: ['First speech', 'Second speech'],
+                pngSize: 100
+            }
+        })
+        expect(wrapper.find('span').text()).toBe('First speech')
+        await wrapper.find('img').trigger('click')
+        expect(wrapper.find('span').text()).toBe('Second speech')
+    })
+})
diff --git a/src/types/challenge.ts b/src/types/challenge.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f122bcc4b7b865af2245ad43fb93cdf0bdb2a5b3
--- /dev/null
+++ b/src/types/challenge.ts
@@ -0,0 +1,13 @@
+// Assuming the use of classes from 'class-transformer' for date handling or plain TypeScript
+
+export interface Challenge {
+    title: string
+    saved: number // BigDecimal in Java, but TypeScript uses number for floating points
+    target: number
+    description: string
+    createdOn: Date // Mapping ZonedDateTime to Date
+    dueDate?: Date // Mapping ZonedDateTime to Date, optional since Temporal annotation not always implies required
+    type?: string // Not specified as @NotNull, so it's optional
+    completion?: number // Assuming BigDecimal maps to number, optional due to @Transient
+    completedOn?: Date // Adding the new variable as optional
+}
diff --git a/src/types/goal.ts b/src/types/goal.ts
new file mode 100644
index 0000000000000000000000000000000000000000..910d9f4359473f9b92a32dd7098a686e2784e5d6
--- /dev/null
+++ b/src/types/goal.ts
@@ -0,0 +1,44 @@
+export interface Goal {
+    /** The unique identifier for the Goal, must not be null. */
+    id: number
+
+    /**
+     * The title of the Goal, must not be null, empty, or only whitespace.
+     */
+    title: string
+
+    /**
+     * The amount saved towards the Goal so far. Must not be null and must be zero or positive.
+     */
+    saved: number
+
+    /**
+     * The target amount to achieve for the Goal. Must not be null and must be positive.
+     */
+    target: number
+
+    /**
+     * Completion percentage of the Goal. Must not be null and must be zero or positive.
+     */
+    completion: number
+
+    /**
+     * A description of the Goal, must not be null, empty, or only whitespace.
+     */
+    description: string
+
+    /**
+     * The priority of the Goal, must not be null and must be zero or positive.
+     */
+    priority: number
+
+    /**
+     * The date and time when the Goal was created. Must be a date in the past.
+     */
+    createdOn: Date
+
+    /**
+     * The date and time by which the Goal is due. Must be a date in the future.
+     */
+    due: Date
+}
diff --git a/src/views/HomeView.vue b/src/views/HomeView.vue
index 4e54fd3352ccc46d6452a07b81e8b278828a3197..ef848f68ecd2052db2c6e052931a7f9a417d3185 100644
--- a/src/views/HomeView.vue
+++ b/src/views/HomeView.vue
@@ -1,16 +1,401 @@
-<script lang="ts" setup></script>
-
 <template>
-    <h1>Heading 1</h1>
-    <h2>Heading 2</h2>
-    <h3>Heading 3</h3>
-
-    <p>Paragraph</p>
-
-    <button>Button</button>
-    <br />
-    <a href="#">Link</a>
-    <div>Div</div>
-    <section>Section</section>
-    <article>Article</article>
+    <div class="flex flex-col max-h-[60vh] md:flex-row md:max-h-[80vh] mx-auto">
+        <div class="flex flex-col basis-1/3 order-last md:order-first md:basis-1/2">
+            <InteractiveSpare
+                :speech="speech"
+                :direction="'right'"
+                :pngSize="60"
+                class="opacity-0 h-0 w-0 md:opacity-100 md:h-auto md:w-auto md:mx-auto md:my-20"
+            ></InteractiveSpare>
+            <div class="flex flex-row gap-12 items-center mx-auto p-8 md:flex-col md:gap-4 md:m-8">
+                <ButtonAddGoalOrChallenge :buttonText="'Legg til sparemål'" />
+                <ButtonAddGoalOrChallenge :buttonText="'Legg til spareutfordring'" />
+            </div>
+        </div>
+        <div
+            class="flex flex-col basis-2/3 max-h-[70vh] mx-auto max-w-5/6 md:basis-1/2 md:max-h-full"
+        >
+            <div class="flex justify-center align-center">
+                <span
+                    class="w-full max-w-60 max-h-12 bg-green-500 text-white font-bold py-2 rounded mt-8 text-center space-x-2"
+                >
+                    Din Sparesti
+                </span>
+            </div>
+            <div class="h-2 w-4/6 bg-black mx-auto my-2 opacity-10"></div>
+            <div
+                ref="containerRef"
+                class="container relative mx-auto p-6 no-scrollbar max-h-[60vh] overflow-y-auto"
+            >
+                <div
+                    v-for="(challenge, index) in challenges"
+                    :key="challenge.title"
+                    class="flex flex-col items-center mx-8"
+                >
+                    <!-- Challenge Row -->
+                    <div
+                        :class="{
+                            'justify-end ml-40 md:ml-30': index % 2 === 1,
+                            'justify-start': index % 2 === 0
+                        }"
+                        class="flex flex-row w-2/3 ml-8"
+                    >
+                        <!-- Challenge Icon and Details -->
+                        <div class="flex">
+                            <!-- Challenge Icon -->
+                            <div class="flex flex-col">
+                                <img
+                                    :src="getChallengeIcon(challenge)"
+                                    class="max-w-20 max-h-20"
+                                    :alt="challenge.title"
+                                />
+                                <!-- Progress Bar, if the challenge is not complete -->
+                                <div
+                                    v-if="
+                                        challenge.completion != undefined &&
+                                        challenge.completion < 100
+                                    "
+                                    class="flex-grow w-full mt-2"
+                                >
+                                    <div class="flex flex-row">
+                                        <div class="flex flex-col">
+                                            <div
+                                                class="bg-gray-200 rounded-full h-2.5 dark:bg-gray-700"
+                                            >
+                                                <div
+                                                    class="bg-green-600 h-2.5 rounded-full"
+                                                    :style="{
+                                                        width:
+                                                            (challenge.saved / challenge.target) *
+                                                                100 +
+                                                            '%'
+                                                    }"
+                                                ></div>
+                                            </div>
+                                            <div class="text-center">
+                                                {{ challenge.saved }}kr / {{ challenge.target }}kr
+                                            </div>
+                                        </div>
+
+                                        <button
+                                            @click="incrementSaved(challenge)"
+                                            type="button"
+                                            class="inline-block mb-2 ml-2 h-8 w-8 rounded-full bg-green-500 p-1 uppercase leading-normal text-white bg-color-green shadow-green-500 transition duration-150 ease-in-out hover:bg-green-700 hover:shadow-green-200 focus:bg-green-accent-300 focus:shadow-green-2 focus:outline-none focus:ring-0 active:bg-green-600 active:shadow-green-200 motion-reduce:transition-none dark:shadow-black/30 dark:hover:shadow-dark-strong dark:focus:shadow-dark-strong dark:active:shadow-dark-strong"
+                                        >
+                                            +
+                                        </button>
+                                    </div>
+                                </div>
+                                <span v-else class="text-center"
+                                    >Ferdig: {{ challenge.saved }}</span
+                                >
+                            </div>
+                            <!-- Check Icon -->
+                            <div
+                                v-if="
+                                    challenge.completion !== undefined &&
+                                    challenge.completion >= 100
+                                "
+                                class="max-w-16 max-h-16"
+                            >
+                                <img src="@/assets/completed.png" alt="" />️
+                            </div>
+                            <div v-else class="max-w-16 max-h-16">
+                                <img src="@/assets/pending.png" alt="" />️
+                            </div>
+                        </div>
+                    </div>
+                    <!-- Piggy Steps, centered -->
+                    <div v-if="index !== challenges.length" class="flex justify-center w-full">
+                        <img
+                            :src="getPigStepsIcon()"
+                            :class="{ 'transform scale-x-[-1]': index % 2 === 0 }"
+                            class="w-20 h-20"
+                            alt="Pig Steps"
+                        />
+                    </div>
+
+                    <div
+                        v-if="index === challenges.length - 1 && index % 2 === 0"
+                        class="ml-40 flex flex-row"
+                    >
+                        <button class="text-2xl mr-2 color-black bg-slate-400">+</button>
+                        <span class="">Legg til <br />Spareutfordring</span>
+                    </div>
+                    <div
+                        v-else-if="index === challenges.length - 1 && index % 2 !== 0"
+                        class="mr-40"
+                    >
+                        <button
+                            class="text-2xl mr-2 color-black bg-slate-400 rounded-full"
+                            @click="addSpareUtfordring"
+                        >
+                            +
+                        </button>
+                    </div>
+                </div>
+                <!-- Sparemannen -->
+                <InteractiveSpare
+                    :speech="speech"
+                    :direction="'left'"
+                    :pngSize="20"
+                    class="fixed bottom-0 right-0 mb-40 mr-4 md:opacity-0 md:h-0 md:w-0"
+                ></InteractiveSpare>
+            </div>
+            <!-- Finish line -->
+            <img src="@/assets/finishLine.png" class="w-1/2 max-h-4 mx-auto" alt="Finish Line" />
+
+            <!-- Goal -->
+            <div v-if="goal" class="flex flex-row gap-24 m-t-2 pt-6 mx-auto">
+                <div class="flex flex-col items-start">
+                    <img :src="getGoalIcon(goal)" class="w-12 h-12 mx-auto" :alt="goal.title" />
+                    <div class="text-lg font-bold">{{ goal.title }}</div>
+                </div>
+                <div class="flex flex-col items-end">
+                    <div @click="goToEditGoal" class="cursor-pointer">
+                        <h3 class="text-blue-500 text-base">Endre mål</h3>
+                    </div>
+                    <div ref="targetRef" class="bg-yellow-400 px-4 py-1 rounded-full text-black">
+                        {{ goal.saved }}kr / {{ goal.target }}kr
+                    </div>
+                </div>
+            </div>
+        </div>
+        <!-- Animation icon -->
+        <img
+            src="@/assets/coins.png"
+            alt="Coins"
+            ref="iconRef"
+            class="max-w-20 max-h-20 absolute opacity-0"
+        />
+    </div>
+    <div></div>
 </template>
+
+<script setup lang="ts">
+import { nextTick, onMounted, ref, watch } from 'vue'
+import anime from 'animejs'
+import InteractiveSpare from '@/components/InteractiveSpare.vue'
+import ButtonAddGoalOrChallenge from '@/components/ButtonAddGoalOrChallange.vue'
+import router from '@/router'
+import type { Challenge } from '@/types/challenge'
+import type { Goal } from '@/types/goal'
+
+// Define your speech array
+const speechArray = [
+    'Hei! Jeg er Sparemannen.',
+    'Jeg hjelper deg med å spare penger.',
+    'Klikk på meg for å høre mer.'
+]
+// Correctly initialize the ref
+const speech = ref(speechArray)
+
+// Reactive references for DOM elements
+const iconRef = ref<HTMLElement | null>(null)
+const containerRef = ref<HTMLElement | null>(null)
+const targetRef = ref<HTMLElement | null>(null)
+
+const goal: Goal = {
+    id: 1,
+    title: 'gaming',
+    saved: 200,
+    description: 'none',
+    target: 400,
+    completion: 50,
+    priority: 0,
+    createdOn: new Date(),
+    due: new Date()
+}
+
+const challenge: Challenge = {
+    title: 'Coffe',
+    saved: 1200.5,
+    target: 3000,
+    description: 'Saving monthly for a year-end vacation to Bali',
+    createdOn: new Date('2023-01-01T00:00:00Z'),
+    dueDate: new Date('2023-12-31T23:59:59Z'),
+    type: 'COFFE',
+    completion: 40,
+    completedOn: undefined // Not yet completed
+}
+const challenge1: Challenge = {
+    title: 'Snacks',
+    saved: 200,
+    target: 400,
+    description: 'Saving monthly for a year-end vacation to Bali',
+    createdOn: new Date('2023-01-01T00:00:00Z'),
+    dueDate: new Date('2023-12-31T23:59:59Z'),
+    type: 'SNACKS',
+    completion: 50,
+    completedOn: undefined // Not yet completed
+}
+
+const challenges = ref([challenge, challenge1])
+
+// AddSpareUtfordring
+function addSpareUtfordring() {
+    console.log('Add Spare Utfordring')
+}
+
+// Increment saved amount
+function incrementSaved(challenge: Challenge) {
+    challenge.saved += 10
+    if (challenge.saved >= challenge.target) {
+        challenge.completion = 100
+    }
+}
+
+function recalculateAndAnimate() {
+    nextTick(() => {
+        if (iconRef.value && containerRef.value && targetRef.value) {
+            animateIcon()
+        } else {
+            console.error('Element references are not ready.')
+        }
+    })
+}
+
+const animatedChallenges = ref(new Set())
+
+const loadAnimatedStates = () => {
+    const animated = localStorage.getItem('animatedChallenges')
+    animatedChallenges.value = animated ? new Set(JSON.parse(animated)) : new Set()
+}
+
+const saveAnimatedState = (title: String) => {
+    animatedChallenges.value.add(title)
+    localStorage.setItem('animatedChallenges', JSON.stringify([...animatedChallenges.value]))
+}
+
+const animateChallenge = (challenge: Challenge) => {
+    if (
+        challenge.completion !== undefined &&
+        challenge.completion >= 100 &&
+        !animatedChallenges.value.has(challenge.title)
+    ) {
+        console.log('Animating for:', challenge.title)
+        recalculateAndAnimate() // Assumes this function triggers the actual animation
+        saveAnimatedState(challenge.title)
+    }
+}
+
+watch(
+    challenges,
+    (newChallenges) => {
+        newChallenges.forEach((challenge) => {
+            if (challenge.completion === 100 && !animatedChallenges.value.has(challenge.title)) {
+                animateChallenge(challenge)
+            }
+        })
+    },
+    { deep: true }
+)
+
+onMounted(() => {
+    // Filter challenges that are already completed
+    const completedChallenges = challenges.value
+        .filter((challenge) => challenge.completion === 100)
+        .map((challenge) => challenge.title)
+
+    // For testing purposes, clear localStorage
+    localStorage.clear()
+
+    // Update localStorage with the titles of completed challenges
+    localStorage.setItem('animatedChallenges', JSON.stringify(completedChallenges))
+
+    // Load the initial state of animated challenges from localStorage
+    loadAnimatedStates()
+})
+
+function animateIcon() {
+    const icon = iconRef.value
+    const container = containerRef.value
+    const target = targetRef.value
+
+    if (!icon || !container || !target) {
+        console.error('Required animation elements are not available.')
+        return
+    }
+
+    const containerRect = container.getBoundingClientRect()
+    const targetRect = target.getBoundingClientRect()
+    const iconRect = icon.getBoundingClientRect()
+
+    const translateX1 =
+        containerRect.left + containerRect.width / 2 - iconRect.width / 2 - iconRect.left
+    const translateY1 =
+        containerRect.top + containerRect.height / 2 - iconRect.height / 2 - iconRect.top
+
+    const translateX2 = targetRect.left + targetRect.width / 2 - iconRect.width / 2 - iconRect.left
+    const translateY2 = targetRect.top + targetRect.height / 2 - iconRect.height / 2 - iconRect.top
+
+    anime
+        .timeline({
+            easing: 'easeInOutQuad',
+            duration: 1500
+        })
+        .add({
+            targets: icon,
+            translateX: translateX1,
+            translateY: translateY1,
+            opacity: 0, // Start invisible
+            duration: 1000
+        })
+        .add({
+            targets: icon,
+            opacity: 1, // Reveal the icon once it starts moving to the container
+            duration: 1000, // Make the opacity change almost instantaneously
+            scale: 3
+        })
+        .add({
+            targets: icon,
+            translateX: translateX2,
+            translateY: translateY2,
+            scale: 0.5,
+            opacity: 1, // Keep the icon visible while moving to the target
+            duration: 1500
+        })
+        .add({
+            targets: icon,
+            opacity: 0, // Fade out once it reaches the target
+            scale: 1,
+            duration: 500
+        })
+        .add({
+            targets: icon,
+            translateX: 0, // Reset translation to original
+            translateY: 0, // Reset translation to original
+            duration: 500
+        })
+}
+
+// Helper methods to get icons
+function getChallengeIcon(challenge: Challenge): string {
+    if (challenge.type === undefined) {
+        return 'src/assets/coins.png'
+    }
+    return `src/assets/${challenge.type.toLowerCase()}.png`
+}
+
+function getGoalIcon(goal: Goal): string {
+    return `src/assets/${goal.title.toLowerCase()}.png`
+}
+function getPigStepsIcon() {
+    return 'src/assets/pigSteps.png'
+}
+
+// TODO - Change when EditGoal view is created
+function goToEditGoal() {
+    router.push({ name: 'EditGoal' })
+}
+</script>
+
+<style scoped>
+/* Tailwind CSS - Custom CSS for hiding scrollbars */
+.no-scrollbar::-webkit-scrollbar {
+    display: none; /* for Chrome, Safari, and Opera */
+}
+.no-scrollbar {
+    -ms-overflow-style: none; /* for Internet Explorer and Edge */
+}
+</style>