clean code

This commit is contained in:
MuslemRahimi 2025-04-01 15:14:21 +02:00
parent f8161d5cd8
commit fbab3bb01e
17 changed files with 95 additions and 3143 deletions

327
package-lock.json generated
View File

@ -38,15 +38,11 @@
"date-fns": "^3.6.0", "date-fns": "^3.6.0",
"date-fns-tz": "^3.1.3", "date-fns-tz": "^3.1.3",
"date-picker-svelte": "^2.12.0", "date-picker-svelte": "^2.12.0",
"echarts": "^5.5.1",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"flowbite-svelte": "^0.46.15",
"got": "^14.4.2", "got": "^14.4.2",
"html2canvas": "^1.4.1",
"html2canvas-pro": "^1.5.8", "html2canvas-pro": "^1.5.8",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"katex": "^0.16.11", "katex": "^0.16.11",
"lightweight-charts": "^4.1.3",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"lucide-svelte": "^0.438.0", "lucide-svelte": "^0.438.0",
"luxon": "^3.5.0", "luxon": "^3.5.0",
@ -58,8 +54,6 @@
"postcss": "^8.4.38", "postcss": "^8.4.38",
"prettier": "^3.3.3", "prettier": "^3.3.3",
"prettier-plugin-svelte": "^3.2.7", "prettier-plugin-svelte": "^3.2.7",
"quill": "^2.0.2",
"quill-delta-to-html": "^0.12.1",
"rollup-plugin-visualizer": "^5.12.0", "rollup-plugin-visualizer": "^5.12.0",
"rollup-plugin-web-worker-loader": "^1.6.1", "rollup-plugin-web-worker-loader": "^1.6.1",
"sass": "^1.75.0", "sass": "^1.75.0",
@ -67,10 +61,8 @@
"string-similarity": "^4.0.4", "string-similarity": "^4.0.4",
"svelte": "^4.2.15", "svelte": "^4.2.15",
"svelte-check": "^3.6.9", "svelte-check": "^3.6.9",
"svelte-echarts": "^1.0.0-rc3",
"svelte-inview": "^4.0.2", "svelte-inview": "^4.0.2",
"svelte-lazy": "^1.2.11", "svelte-lazy": "^1.2.11",
"svelte-lightweight-charts": "^2.2.0",
"svelte-loading-spinners": "^0.3.6", "svelte-loading-spinners": "^0.3.6",
"svelte-preprocess": "^5.1.4", "svelte-preprocess": "^5.1.4",
"svelte-sonner": "^0.3.27", "svelte-sonner": "^0.3.27",
@ -1280,16 +1272,6 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@popperjs/core": {
"version": "2.11.8",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
"dev": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/popperjs"
}
},
"node_modules/@rollup/plugin-commonjs": { "node_modules/@rollup/plugin-commonjs": {
"version": "28.0.1", "version": "28.0.1",
"resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-28.0.1.tgz", "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-28.0.1.tgz",
@ -2822,12 +2804,6 @@
"license": "Apache-2.0", "license": "Apache-2.0",
"peer": true "peer": true
}, },
"node_modules/@yr/monotone-cubic-spline": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@yr/monotone-cubic-spline/-/monotone-cubic-spline-1.0.3.tgz",
"integrity": "sha512-FQXkOta0XBSUPHndIKON2Y9JeQz5ZeMqLYZVVK93FliNBFm7LNMIZmY6FrMEB9XPcDbE2bekMbZD6kzDkxwYjA==",
"dev": true
},
"node_modules/abs-svg-path": { "node_modules/abs-svg-path": {
"version": "0.1.1", "version": "0.1.1",
"resolved": "https://registry.npmjs.org/abs-svg-path/-/abs-svg-path-0.1.1.tgz", "resolved": "https://registry.npmjs.org/abs-svg-path/-/abs-svg-path-0.1.1.tgz",
@ -2989,21 +2965,6 @@
"node": ">= 8" "node": ">= 8"
} }
}, },
"node_modules/apexcharts": {
"version": "3.53.0",
"resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.53.0.tgz",
"integrity": "sha512-QESZHZY3w9LPQ64PGh1gEdfjYjJ5Jp+Dfy0D/CLjsLOPTpXzdxwlNMqRj+vPbTcP0nAHgjWv1maDqcEq6u5olw==",
"dev": true,
"dependencies": {
"@yr/monotone-cubic-spline": "^1.0.3",
"svg.draggable.js": "^2.2.2",
"svg.easing.js": "^2.0.0",
"svg.filter.js": "^2.0.2",
"svg.pathmorphing.js": "^0.1.3",
"svg.resize.js": "^1.4.3",
"svg.select.js": "^3.0.1"
}
},
"node_modules/aria-query": { "node_modules/aria-query": {
"version": "5.3.0", "version": "5.3.0",
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
@ -4331,22 +4292,6 @@
"safe-buffer": "^5.0.1" "safe-buffer": "^5.0.1"
} }
}, },
"node_modules/echarts": {
"version": "5.5.1",
"resolved": "https://registry.npmjs.org/echarts/-/echarts-5.5.1.tgz",
"integrity": "sha512-Fce8upazaAXUVUVsjgV6mBnGuqgO+JNDlcgF79Dksy4+wgGpQB2lmYoO4TSweFg/mZITdpGHomw/cNBJZj1icA==",
"dev": true,
"dependencies": {
"tslib": "2.3.0",
"zrender": "5.6.0"
}
},
"node_modules/echarts/node_modules/tslib": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz",
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==",
"dev": true
},
"node_modules/electron-to-chromium": { "node_modules/electron-to-chromium": {
"version": "1.5.79", "version": "1.5.79",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.79.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.79.tgz",
@ -4688,12 +4633,6 @@
"es5-ext": "~0.10.14" "es5-ext": "~0.10.14"
} }
}, },
"node_modules/eventemitter3": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
"integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==",
"dev": true
},
"node_modules/events": { "node_modules/events": {
"version": "3.3.0", "version": "3.3.0",
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
@ -4772,12 +4711,6 @@
"node": ">=0.4.0" "node": ">=0.4.0"
} }
}, },
"node_modules/fancy-canvas": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fancy-canvas/-/fancy-canvas-2.1.0.tgz",
"integrity": "sha512-nifxXJ95JNLFR2NgRV4/MxVP45G9909wJTEKz5fg/TZS20JJZA6hfgRVh/bC9bwl2zBtBNcYPjiBE4njQHVBwQ==",
"dev": true
},
"node_modules/fast-deep-equal": { "node_modules/fast-deep-equal": {
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@ -4785,12 +4718,6 @@
"license": "MIT", "license": "MIT",
"peer": true "peer": true
}, },
"node_modules/fast-diff": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz",
"integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==",
"dev": true
},
"node_modules/fast-glob": { "node_modules/fast-glob": {
"version": "3.3.2", "version": "3.3.2",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
@ -4876,46 +4803,6 @@
"dtype": "^2.0.0" "dtype": "^2.0.0"
} }
}, },
"node_modules/flowbite": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/flowbite/-/flowbite-2.5.1.tgz",
"integrity": "sha512-7jP1jy9c3QP7y+KU9lc8ueMkTyUdMDvRP+lteSWgY5TigSZjf9K1kqZxmqjhbx2gBnFQxMl1GAjVThCa8cEpKA==",
"dev": true,
"dependencies": {
"@popperjs/core": "^2.9.3",
"flowbite-datepicker": "^1.3.0",
"mini-svg-data-uri": "^1.4.3"
}
},
"node_modules/flowbite-datepicker": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/flowbite-datepicker/-/flowbite-datepicker-1.3.0.tgz",
"integrity": "sha512-CLVqzuoE2vkUvWYK/lJ6GzT0be5dlTbH3uuhVwyB67+PjqJWABm2wv68xhBf5BqjpBxvTSQ3mrmLHpPJ2tvrSQ==",
"dev": true,
"dependencies": {
"@rollup/plugin-node-resolve": "^15.2.3",
"flowbite": "^2.0.0"
}
},
"node_modules/flowbite-svelte": {
"version": "0.46.15",
"resolved": "https://registry.npmjs.org/flowbite-svelte/-/flowbite-svelte-0.46.15.tgz",
"integrity": "sha512-xWhyLDez/gafTAmQayPMPKmWj1BAoq80SoA48yHZ12Wk3Vu3hFrELLUZf0UksxnjQL9hMOKRUlYl3/IH6pHwnQ==",
"dev": true,
"dependencies": {
"@floating-ui/dom": "^1.6.7",
"apexcharts": "^3.49.2",
"flowbite": "^2.4.1",
"tailwind-merge": "^2.3.0"
},
"engines": {
"node": ">=18.0.0",
"pnpm": ">=8.0.0"
},
"peerDependencies": {
"svelte": "^3.55.1 || ^4.0.0 || ^5.0.0"
}
},
"node_modules/focus-trap": { "node_modules/focus-trap": {
"version": "7.5.4", "version": "7.5.4",
"resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.5.4.tgz", "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.5.4.tgz",
@ -5578,20 +5465,6 @@
"integrity": "sha512-08iL2VyCRbkQKBySkSh6m8zMUa3sADAxGVWs3Z1aPcUkTJeK0ETG4Fc27tEmQBGUAXZjIsXOZqBvacuVNSC/fQ==", "integrity": "sha512-08iL2VyCRbkQKBySkSh6m8zMUa3sADAxGVWs3Z1aPcUkTJeK0ETG4Fc27tEmQBGUAXZjIsXOZqBvacuVNSC/fQ==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/html2canvas": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz",
"integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
"dev": true,
"license": "MIT",
"dependencies": {
"css-line-break": "^2.1.0",
"text-segmentation": "^1.0.3"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/html2canvas-pro": { "node_modules/html2canvas-pro": {
"version": "1.5.8", "version": "1.5.8",
"resolved": "https://registry.npmjs.org/html2canvas-pro/-/html2canvas-pro-1.5.8.tgz", "resolved": "https://registry.npmjs.org/html2canvas-pro/-/html2canvas-pro-1.5.8.tgz",
@ -6455,15 +6328,6 @@
"url": "https://opencollective.com/parcel" "url": "https://opencollective.com/parcel"
} }
}, },
"node_modules/lightweight-charts": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/lightweight-charts/-/lightweight-charts-4.1.3.tgz",
"integrity": "sha512-SJacmEyx3LmT2Qsc7Kq7cEX7nEHtQv0MOlujhRlcDxhW62pG6nkBlcM52/jNqkq8B28KQeVmgOQ7zrdJ4BCPDw==",
"dev": true,
"dependencies": {
"fancy-canvas": "2.1.0"
}
},
"node_modules/lilconfig": { "node_modules/lilconfig": {
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
@ -6515,12 +6379,6 @@
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
}, },
"node_modules/lodash.clonedeep": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
"integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==",
"dev": true
},
"node_modules/lodash.includes": { "node_modules/lodash.includes": {
"version": "4.3.0", "version": "4.3.0",
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
@ -6533,12 +6391,6 @@
"integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==",
"dev": true "dev": true
}, },
"node_modules/lodash.isequal": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
"integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==",
"dev": true
},
"node_modules/lodash.isinteger": { "node_modules/lodash.isinteger": {
"version": "4.0.4", "version": "4.0.4",
"resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
@ -6875,15 +6727,6 @@
"node": ">=4" "node": ">=4"
} }
}, },
"node_modules/mini-svg-data-uri": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz",
"integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==",
"dev": true,
"bin": {
"mini-svg-data-uri": "cli.js"
}
},
"node_modules/minimalistic-assert": { "node_modules/minimalistic-assert": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
@ -7250,12 +7093,6 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/parchment": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/parchment/-/parchment-3.0.0.tgz",
"integrity": "sha512-HUrJFQ/StvgmXRcQ1ftY6VEZUq3jA2t9ncFN4F84J/vN0/FPpQF+8FKXb3l6fLces6q0uOHj6NJn+2xvZnxO6A==",
"dev": true
},
"node_modules/parenthesis": { "node_modules/parenthesis": {
"version": "3.1.8", "version": "3.1.8",
"resolved": "https://registry.npmjs.org/parenthesis/-/parenthesis-3.1.8.tgz", "resolved": "https://registry.npmjs.org/parenthesis/-/parenthesis-3.1.8.tgz",
@ -7830,44 +7667,6 @@
"integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==", "integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==",
"license": "ISC" "license": "ISC"
}, },
"node_modules/quill": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/quill/-/quill-2.0.2.tgz",
"integrity": "sha512-QfazNrhMakEdRG57IoYFwffUIr04LWJxbS/ZkidRFXYCQt63c1gK6Z7IHUXMx/Vh25WgPBU42oBaNzQ0K1R/xw==",
"dev": true,
"dependencies": {
"eventemitter3": "^5.0.1",
"lodash-es": "^4.17.21",
"parchment": "^3.0.0",
"quill-delta": "^5.1.0"
},
"engines": {
"npm": ">=8.2.3"
}
},
"node_modules/quill-delta": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/quill-delta/-/quill-delta-5.1.0.tgz",
"integrity": "sha512-X74oCeRI4/p0ucjb5Ma8adTXd9Scumz367kkMK5V/IatcX6A0vlgLgKbzXWy5nZmCGeNJm2oQX0d2Eqj+ZIlCA==",
"dev": true,
"dependencies": {
"fast-diff": "^1.3.0",
"lodash.clonedeep": "^4.5.0",
"lodash.isequal": "^4.5.0"
},
"engines": {
"node": ">= 12.0.0"
}
},
"node_modules/quill-delta-to-html": {
"version": "0.12.1",
"resolved": "https://registry.npmjs.org/quill-delta-to-html/-/quill-delta-to-html-0.12.1.tgz",
"integrity": "sha512-QhpeMk9+5ge3HYbL5A0Ewz3pXCsbemqGvIF/kw5D6D4V68AtcUp7yt9xNUkzOk/0IQz43hKy3IkzBzRhLIE+oA==",
"dev": true,
"dependencies": {
"lodash.isequal": "^4.5.0"
}
},
"node_modules/raf": { "node_modules/raf": {
"version": "3.4.1", "version": "3.4.1",
"resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz",
@ -8693,16 +8492,6 @@
"svelte": "^3.55.0 || ^4.0.0-next.0 || ^4.0.0 || ^5.0.0-next.0" "svelte": "^3.55.0 || ^4.0.0-next.0 || ^4.0.0 || ^5.0.0-next.0"
} }
}, },
"node_modules/svelte-echarts": {
"version": "1.0.0-rc3",
"resolved": "https://registry.npmjs.org/svelte-echarts/-/svelte-echarts-1.0.0-rc3.tgz",
"integrity": "sha512-jM9EU4m7yFJaISCXhnGgtYirMD1VP2DzoI3Lhbl2jsvGzZFIAg8wsn/5n8GccZxm7wHgzcEL48EVJVlrKPU7gQ==",
"dev": true,
"peerDependencies": {
"echarts": "^5.0.0",
"svelte": "^4.0.0"
}
},
"node_modules/svelte-hmr": { "node_modules/svelte-hmr": {
"version": "0.16.0", "version": "0.16.0",
"resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.16.0.tgz", "resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.16.0.tgz",
@ -8734,16 +8523,6 @@
"svelte": "^3.0.0 || ^4.0.0 || ^5.0.0" "svelte": "^3.0.0 || ^4.0.0 || ^5.0.0"
} }
}, },
"node_modules/svelte-lightweight-charts": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/svelte-lightweight-charts/-/svelte-lightweight-charts-2.2.0.tgz",
"integrity": "sha512-LXdha4vfLMuOPc0Yyetu+DLSDJkPryGkufUQgpCkfguCscSQUcrLiI9MdqKwQk6Fkm6AZbg8SQ5qDIYxcyC+Dg==",
"dev": true,
"peerDependencies": {
"lightweight-charts": ">=4.0.0",
"svelte": ">=3.44.0"
}
},
"node_modules/svelte-loading-spinners": { "node_modules/svelte-loading-spinners": {
"version": "0.3.6", "version": "0.3.6",
"resolved": "https://registry.npmjs.org/svelte-loading-spinners/-/svelte-loading-spinners-0.3.6.tgz", "resolved": "https://registry.npmjs.org/svelte-loading-spinners/-/svelte-loading-spinners-0.3.6.tgz",
@ -8891,97 +8670,6 @@
"svg-path-bounds": "^1.0.1" "svg-path-bounds": "^1.0.1"
} }
}, },
"node_modules/svg.draggable.js": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/svg.draggable.js/-/svg.draggable.js-2.2.2.tgz",
"integrity": "sha512-JzNHBc2fLQMzYCZ90KZHN2ohXL0BQJGQimK1kGk6AvSeibuKcIdDX9Kr0dT9+UJ5O8nYA0RB839Lhvk4CY4MZw==",
"dev": true,
"dependencies": {
"svg.js": "^2.0.1"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/svg.easing.js": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/svg.easing.js/-/svg.easing.js-2.0.0.tgz",
"integrity": "sha512-//ctPdJMGy22YoYGV+3HEfHbm6/69LJUTAqI2/5qBvaNHZ9uUFVC82B0Pl299HzgH13rKrBgi4+XyXXyVWWthA==",
"dev": true,
"dependencies": {
"svg.js": ">=2.3.x"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/svg.filter.js": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/svg.filter.js/-/svg.filter.js-2.0.2.tgz",
"integrity": "sha512-xkGBwU+dKBzqg5PtilaTb0EYPqPfJ9Q6saVldX+5vCRy31P6TlRCP3U9NxH3HEufkKkpNgdTLBJnmhDHeTqAkw==",
"dev": true,
"dependencies": {
"svg.js": "^2.2.5"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/svg.js": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/svg.js/-/svg.js-2.7.1.tgz",
"integrity": "sha512-ycbxpizEQktk3FYvn/8BH+6/EuWXg7ZpQREJvgacqn46gIddG24tNNe4Son6omdXCnSOaApnpZw6MPCBA1dODA==",
"dev": true
},
"node_modules/svg.pathmorphing.js": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/svg.pathmorphing.js/-/svg.pathmorphing.js-0.1.3.tgz",
"integrity": "sha512-49HWI9X4XQR/JG1qXkSDV8xViuTLIWm/B/7YuQELV5KMOPtXjiwH4XPJvr/ghEDibmLQ9Oc22dpWpG0vUDDNww==",
"dev": true,
"dependencies": {
"svg.js": "^2.4.0"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/svg.resize.js": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/svg.resize.js/-/svg.resize.js-1.4.3.tgz",
"integrity": "sha512-9k5sXJuPKp+mVzXNvxz7U0uC9oVMQrrf7cFsETznzUDDm0x8+77dtZkWdMfRlmbkEEYvUn9btKuZ3n41oNA+uw==",
"dev": true,
"dependencies": {
"svg.js": "^2.6.5",
"svg.select.js": "^2.1.2"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/svg.resize.js/node_modules/svg.select.js": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-2.1.2.tgz",
"integrity": "sha512-tH6ABEyJsAOVAhwcCjF8mw4crjXSI1aa7j2VQR8ZuJ37H2MBUbyeqYr5nEO7sSN3cy9AR9DUwNg0t/962HlDbQ==",
"dev": true,
"dependencies": {
"svg.js": "^2.2.5"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/svg.select.js": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-3.0.1.tgz",
"integrity": "sha512-h5IS/hKkuVCbKSieR9uQCj9w+zLHoPh+ce19bBYyqF53g6mnPB8sAtIbe1s9dh2S2fCmYX2xel1Ln3PJBbK4kw==",
"dev": true,
"dependencies": {
"svg.js": "^2.6.5"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/tabbable": { "node_modules/tabbable": {
"version": "6.2.0", "version": "6.2.0",
"resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz",
@ -10025,21 +9713,6 @@
"funding": { "funding": {
"url": "https://github.com/sponsors/colinhacks" "url": "https://github.com/sponsors/colinhacks"
} }
},
"node_modules/zrender": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/zrender/-/zrender-5.6.0.tgz",
"integrity": "sha512-uzgraf4njmmHAbEUxMJ8Oxg+P3fT04O+9p7gY+wJRVxo8Ge+KmYv0WJev945EH4wFuc4OY2NLXz46FZrWS9xJg==",
"dev": true,
"dependencies": {
"tslib": "2.3.0"
}
},
"node_modules/zrender/node_modules/tslib": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz",
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==",
"dev": true
} }
} }
} }

View File

@ -38,15 +38,11 @@
"date-fns": "^3.6.0", "date-fns": "^3.6.0",
"date-fns-tz": "^3.1.3", "date-fns-tz": "^3.1.3",
"date-picker-svelte": "^2.12.0", "date-picker-svelte": "^2.12.0",
"echarts": "^5.5.1",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"flowbite-svelte": "^0.46.15",
"got": "^14.4.2", "got": "^14.4.2",
"html2canvas": "^1.4.1",
"html2canvas-pro": "^1.5.8", "html2canvas-pro": "^1.5.8",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"katex": "^0.16.11", "katex": "^0.16.11",
"lightweight-charts": "^4.1.3",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"lucide-svelte": "^0.438.0", "lucide-svelte": "^0.438.0",
"luxon": "^3.5.0", "luxon": "^3.5.0",
@ -58,8 +54,6 @@
"postcss": "^8.4.38", "postcss": "^8.4.38",
"prettier": "^3.3.3", "prettier": "^3.3.3",
"prettier-plugin-svelte": "^3.2.7", "prettier-plugin-svelte": "^3.2.7",
"quill": "^2.0.2",
"quill-delta-to-html": "^0.12.1",
"rollup-plugin-visualizer": "^5.12.0", "rollup-plugin-visualizer": "^5.12.0",
"rollup-plugin-web-worker-loader": "^1.6.1", "rollup-plugin-web-worker-loader": "^1.6.1",
"sass": "^1.75.0", "sass": "^1.75.0",
@ -67,10 +61,8 @@
"string-similarity": "^4.0.4", "string-similarity": "^4.0.4",
"svelte": "^4.2.15", "svelte": "^4.2.15",
"svelte-check": "^3.6.9", "svelte-check": "^3.6.9",
"svelte-echarts": "^1.0.0-rc3",
"svelte-inview": "^4.0.2", "svelte-inview": "^4.0.2",
"svelte-lazy": "^1.2.11", "svelte-lazy": "^1.2.11",
"svelte-lightweight-charts": "^2.2.0",
"svelte-loading-spinners": "^0.3.6", "svelte-loading-spinners": "^0.3.6",
"svelte-preprocess": "^5.1.4", "svelte-preprocess": "^5.1.4",
"svelte-sonner": "^0.3.27", "svelte-sonner": "^0.3.27",

View File

@ -1,411 +0,0 @@
<script lang="ts">
import { Chart } from "svelte-echarts";
import InfoModal from "$lib/components/InfoModal.svelte";
import {
dcfComponent,
stockTicker,
screenWidth,
getCache,
setCache,
} from "$lib/store";
import Lazy from "svelte-lazy";
//export let quantData;
export let data;
let fairPrice;
let isLoaded = false;
let lastPrice: Number;
let optionsBarChart;
let change: Number;
const contentModal = `<span class="text-white">
Discounted Cash Flow (DCF) is a core method for valuing a company's true worth. It starts by predicting the company's growth and how it affects its future cash flow.
<br>
<br>
Then, it adjusts these future cash flows to their present value using a discount rate. This considers the risk associated with future cash flow predictions. Simply put, higher discount rates signal greater risk.
</span>`;
const plotBarChart = () => {
const options = {
grid: {
left: "0%",
right: "0%",
top: "0%",
bottom: "0%",
containLabel: true,
},
animation: false,
silent: true,
xAxis: {
type: "value",
axisLabel: {
show: false, // Hide the x-axis labels
},
axisLine: {
show: false, // Hide the y-axis lines
},
splitLine: {
show: false, // Hide the grid lines on the y-axis
},
},
yAxis: {
type: "category",
axisLabel: {
show: false, // Hide the x-axis labels
},
axisLine: {
show: true, // Hide the y-axis lines
},
splitLine: {
show: false, // Hide the grid lines on the y-axis
},
},
series: [
{
name: "Current Price",
type: "bar",
barWidth: $screenWidth < 640 ? "30%" : "30%",
smooth: true,
stack: change < 0 ? "lastPriceStack" : "",
data: [lastPrice],
label: {
show: true,
position: "inside",
formatter: function (params) {
return [
"{a|Current Price}",
"{b|" + "$" + params.value + "}",
].join("\n");
},
rich: {
a: {
color: "white",
fontSize: $screenWidth < 640 ? 15 : 20,
fontWeight: "bold",
},
b: {
color: "white",
fontSize: $screenWidth < 640 ? 24 : 30,
fontWeight: "bold",
},
},
},
markLine: {
symbol: "none",
label: {
position: "middle",
formatter: "{b}",
},
lineStyle: {
color: "white",
fontWeight: "bold", // Make the mark line bold
type: "dashed",
width: 2, // Increase the dashed line width
},
data: [{ type: "average", name: "" }],
},
},
{
name: "Fair Price",
type: "bar",
barWidth: $screenWidth < 640 ? "30%" : "30%",
smooth: true,
stack: change > 0 ? "fairPriceStack" : "",
data: [fairPrice],
itemStyle: {
color: "#2DC97E",
},
label: {
show: true,
position: lastPrice > fairPrice ? "outside" : "inside",
formatter: function (params) {
return ["{a|Fair Price}", "{b|" + "$" + params.value + "}"].join(
"\n",
);
},
rich: {
a: {
color: "white",
fontSize: $screenWidth < 640 ? 15 : 20,
fontWeight: "bold",
},
b: {
color: "white",
fontSize: $screenWidth < 640 ? 24 : 30,
fontWeight: "bold",
},
},
},
markLine: {
symbol: "none",
label: {
position: "middle",
formatter: "{b}",
},
lineStyle: {
color: "white",
fontWeight: "bold", // Make the mark line bold
type: "dashed",
width: 2, // Increase the dashed line width
},
data: [{ type: "average", name: "" }],
},
},
// Add the new bar chart on top
{
name: "Difference",
type: "bar",
barWidth: "100%", // Set the width to cover the other bars
smooth: true,
stack: change > 0 ? "fairPriceStack" : "lastPriceStack", // Stack this bar on top of the others
data: change > 0 ? [lastPrice - fairPrice] : [fairPrice - lastPrice], // Set the value to 200
itemStyle: {
color: "#FF2F1F",
},
},
],
aria: {
enabled: true,
decal: {
show: true,
},
},
};
return options;
};
async function getFairPrice(ticker) {
const cachedData = getCache(ticker, "getFairPrice");
if (cachedData) {
fairPrice = cachedData;
} else {
try {
const response = await fetch("/api/ticker-data", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ ticker: ticker, path: "fair-price" }),
});
fairPrice = await response.json();
setCache(ticker, fairPrice, "getFairPrice");
} catch (error) {
console.error("Failed to fetch swap data:", error);
fairPrice = null;
}
}
if (fairPrice !== null && fairPrice >= 0) {
$dcfComponent = true;
} else {
$dcfComponent = false;
}
}
$: {
if ($stockTicker && typeof window !== "undefined") {
isLoaded = false;
getFairPrice($stockTicker).then(() => {
if (fairPrice !== null) {
lastPrice = data?.getStockQuote?.price?.toFixed(2);
change = ((1 - fairPrice / lastPrice) * 100)?.toFixed(2);
optionsBarChart = plotBarChart();
}
isLoaded = true;
});
}
}
</script>
<section class="overflow-hidden text-white h-full pb-8">
<main class="overflow-hidden">
<div class="flex flex-row items-center">
<label
for="dcfInfo"
class="mr-1 cursor-pointer flex flex-row items-center text-white text-xl sm:text-3xl font-bold"
>
Discounted Cashflow Model
</label>
<InfoModal
title={"Discounted Cashflow Model"}
content={contentModal}
id={"dcfInfo"}
/>
</div>
{#if ["Pro", "Plus"]?.includes(data?.user?.tier)}
{#if isLoaded}
{#if fairPrice !== null}
<div
class="p-3 sm:p-0 mt-2 pb-8 sm:pb-2 rounded-md bg-default sm:bg-default"
>
<div
class="mt-4 text-white text-[1rem] sm:text-xl pb-4 sm:pb-0 m-auto text-start"
>
The DCF model signals a
{#if change < -3}
<span class="text-green-700 dark:text-[#00FC50]">
<svg
class="w-6 h-6 sm:w-7 sm:h-7 inline-block"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
><g
fill="none"
stroke="#00FC50"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2.5"
><path d="m3 17l6-6l4 4l8-8" /><path d="M17 7h4v4" /></g
></svg
>
Buy
</span>
{:else if change > 3}
<span class="text-[#E57C34]">
<svg
class="w-6 h-6 sm:w-7 sm:h-7 inline-block"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 256 256"
><path
fill="#ff2f1f"
d="M244 136v64a12 12 0 0 1-12 12h-64a12 12 0 0 1 0-24h35l-67-67l-31.51 31.52a12 12 0 0 1-17 0l-72-72a12 12 0 0 1 17-17L96 127l31.51-31.52a12 12 0 0 1 17 0L220 171v-35a12 12 0 0 1 24 0Z"
/></svg
>
Sell
</span>
{:else}
<span class="text-red-700 dark:text-[#FF2F1F]">
<svg
class="w-6 h-6 sm:w-7 sm:h-7 inline-block"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
><path
fill="#e57c34"
d="m22 12l-4-4v3H3v2h15v3l4-4Z"
/></svg
>
Hold
</span>
{/if}
</div>
{#if change > 0}
<div class="text-white">
The Stock Price is
<span class="text-[#FF2F1F] sm:text-lg"
>{Math?.abs(change)}% overvalued</span
>.
</div>
{:else if change < 0}
<div class="text-white">
The Stock Price is
<span class="text-green-700 dark:text-[#00FC50]"
>{Math?.abs(change)}% undervalued</span
>.
</div>
{/if}
<div class="text-white text-md mt-2">
<span class="text-blue-400">${$stockTicker}</span>
(${lastPrice}) is trading {change < 0 ? "below" : "above"}
our estimate of fair value (${fairPrice}).
</div>
<br />
<div class="text-white text-md mb-10 -mt-3">
What is the Fair Price of <span class="font-normal text-blue-400"
>${$stockTicker}</span
> when looking at its future cash flows? For this estimate we use a
Discounted Cash Flow model (DCF).
</div>
<Lazy
height={300}
fadeOption={{ delay: 100, duration: 500 }}
keep={true}
>
<div class="app w-full m-auto mb-5">
<Chart options={optionsBarChart} class="chart w-full" />
</div>
</Lazy>
{#if Math?.abs(change) > 30}
<div
class=" mb-5 text-gray-100 text-sm sm:text-[1rem] sm:rounded-md h-auto border border-gray-600 p-4"
>
<svg
class="w-5 h-5 inline-block mr-0.5 shrink-0"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 256 256"
><path
fill="#fff"
d="M128 24a104 104 0 1 0 104 104A104.11 104.11 0 0 0 128 24m-4 48a12 12 0 1 1-12 12a12 12 0 0 1 12-12m12 112a16 16 0 0 1-16-16v-40a8 8 0 0 1 0-16a16 16 0 0 1 16 16v40a8 8 0 0 1 0 16"
/></svg
>
Caution: The DCF model may not be reliable for
<span class="text-blue-400">${$stockTicker}</span> due to significant
deviation between intrinsic value and current price.
</div>
{/if}
</div>
{/if}
{:else}
<div class="flex justify-center items-center h-80">
<div class="relative">
<label
class="bg-secondary rounded-md h-14 w-14 flex justify-center items-center absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2"
>
<span class="loading loading-spinner loading-md text-gray-400"
></span>
</label>
</div>
</div>
{/if}
{:else}
<div
class="shadow-lg shadow-bg-[#000] bg-[#111112] sm:bg-opacity-[0.5] text-sm sm:text-[1rem] rounded-md w-full p-4 min-h-24 mt-4 text-white m-auto flex justify-center items-center text-center font-semibold"
>
<svg
class="mr-1.5 w-5 h-5 inline-block"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
><path
fill="#A3A3A3"
d="M17 9V7c0-2.8-2.2-5-5-5S7 4.2 7 7v2c-1.7 0-3 1.3-3 3v7c0 1.7 1.3 3 3 3h10c1.7 0 3-1.3 3-3v-7c0-1.7-1.3-3-3-3M9 7c0-1.7 1.3-3 3-3s3 1.3 3 3v2H9z"
/></svg
>
Unlock content with
<a
class="inline-block ml-2 text-blue-400 sm:hover:text-white"
href="/pricing">Pro Subscription</a
>
</div>
{/if}
</main>
</section>
<style>
.app {
height: 300px;
max-width: 100%; /* Ensure chart width doesn't exceed the container */
}
@media (max-width: 640px) {
.app {
height: 180px;
}
}
.chart {
width: 100%;
}
</style>

View File

@ -277,7 +277,7 @@
</div> </div>
<div <div
class="chart mt-5 sm:mt-0 border border-gray-300 dark:border-gray-800 rounded" class="shadow-sm mt-5 sm:mt-0 border border-gray-300 dark:border-gray-800 rounded"
use:highcharts={config} use:highcharts={config}
></div> ></div>

View File

@ -1,57 +1,8 @@
<script lang="ts"> <script lang="ts">
import * as HoverCard from "$lib/components/shadcn/hover-card/index.js";
import { ColorType } from "lightweight-charts";
import { Chart, BaselineSeries, PriceLine } from "svelte-lightweight-charts";
import { screenWidth, getCache, setCache } from "$lib/store";
import { abbreviateNumber } from "$lib/utils";
import { afterUpdate } from "svelte";
export let symbol; export let symbol;
export let assetType = "stock"; export let assetType = "stock";
export let link = null; export let link = null;
let priceData = [];
let changesPercentage = 0;
let change = 0;
let stockChartData;
async function getStockData(ticker: string) {
const cachedData = getCache(ticker, "hoverStockChart");
if (cachedData) {
stockChartData = cachedData;
} else {
const postData = { ticker: ticker };
const response = await fetch("/api/hover-stock-chart", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(postData),
});
stockChartData = (await response?.json()) ?? {};
setCache(ticker, stockChartData, "hoverStockChart");
}
changesPercentage = stockChartData?.changesPercentage;
change = stockChartData?.change;
priceData = stockChartData?.history;
priceData = priceData
?.map((item) => ({
time: Date?.parse(item?.time), // Assuming 'time' is the correct property to parse
value: item?.close ?? null,
}))
?.filter(
(item) =>
item?.value !== 0 &&
item?.value !== null &&
item?.value !== undefined,
);
}
function getHref(symbol: string) { function getHref(symbol: string) {
let path = ""; let path = "";
if (symbol?.length !== 0) { if (symbol?.length !== 0) {
@ -67,208 +18,10 @@
} }
return path; return path;
} }
$: topLineColor = changesPercentage >= 0 ? "#71CA96" : "#FF7070";
let width = $screenWidth < 640 ? 80 : 150; //= ($screenWidth <= 1200 && $screenWidth > 900) ? 360 : ($screenWidth <= 900 && $screenWidth > 700) ? 260 : ($screenWidth <= 700 && $screenWidth >=600 ) ? 200 : ($screenWidth < 600 && $screenWidth >=500 ) ? 150 : 80;
//Initial height of graph
let height = $screenWidth < 640 ? 50 : 60;
let observer;
let ref;
let chart;
ref = (element) => {
if (observer) {
observer?.disconnect();
}
if (!element) {
return;
}
observer = new ResizeObserver(([entry]) => {
width = entry.contentRect.width;
height = entry.contentRect.height;
});
observer.observe(element);
};
const THEMES = {
Dark: {
chart: {
layout: {
background: {
type: ColorType.Solid,
color: "#11151D",
},
lineColor: "#2B2B43",
textColor: "#D9D9D9",
},
crosshair: {
mode: 2,
},
grid: {
vertLines: {
visible: false,
},
horzLines: {
visible: false,
},
},
},
series: {
baseValue: { type: "price", price: priceData?.at(0)?.value },
priceLineVisible: false,
baseLineVisible: true,
baseLineColor: "#B2B5BE",
baseLineWidth: 1,
baseLineStyle: 1,
lineWidth: 1.5,
},
},
};
const AVAILABLE_THEMES = Object?.keys(THEMES);
let selected = AVAILABLE_THEMES[0];
$: theme = THEMES[selected];
const options = {
width: width,
height: height,
rightPriceScale: {
visible: false,
},
timeScale: {
borderColor: "#FFFFFF",
textColor: "#FFFFFF",
visible: false,
fixLeftEdge: true,
fixRightEdge: true,
},
handleScale: {
mouseWheel: false,
},
handleScroll: {
mouseWheel: false,
horzTouchDrag: false,
vertTouchDrag: false,
pressedMouseMove: false,
},
};
afterUpdate(async () => {
if (
$screenWidth &&
stockChartData &&
chart &&
typeof window !== "undefined"
) {
chart?.timeScale()?.fitContent();
}
});
$: charNumber = 20;
</script> </script>
<div on:mouseover={() => getStockData(symbol)} class="inline-block"> <a
<HoverCard.Root>
<div on:mouseover>
<HoverCard.Trigger
class="rounded-sm underline-offset-4 hover:underline focus-visible:outline-2 focus-visible:outline-offset-8 focus-visible:outline-black"
>
<a
href={getHref(symbol)} href={getHref(symbol)}
class="sm:hover:text-muted dark:sm:hover:text-white text-blue-700 dark:text-blue-400" class="sm:hover:text-muted dark:sm:hover:text-white text-blue-700 dark:text-blue-400"
>{symbol?.length !== 0 ? symbol : "-"}</a >{symbol?.length !== 0 ? symbol : "-"}</a
> >
</HoverCard.Trigger>
</div>
<HoverCard.Content class=" w-96 bg-[#11151D] border border-gray-600">
<div class="flex justify-between space-x-4 w-full text-white">
<div class="space-y-1 w-full">
<!--Hover Stock Chart-->
<label
class=" text-sm flex flex-row items-center justify-start bg-[#11151D]"
>
<div class="flex flex-col items-start w-full">
<div class=" flex flex-col items-start pb-1">
<h4 class="text-sm text-blue-400 inline-block">
{symbol}
</h4>
<h5 class="text-sm inline-block">
{stockChartData?.name?.length > charNumber
? stockChartData?.name?.slice(0, charNumber) + "..."
: stockChartData?.name}
</h5>
</div>
<p>
Current Price: {stockChartData?.price?.toFixed(2)} (<span
class="text-xs {change >= 0
? "before:content-['+'] text-green-700 dark:text-[#00FC50]"
: 'text-red-700 dark:text-[#FF2F1F]'}"
>{change?.toFixed(2)}</span
>)
</p>
<p>
<svg
class="-ml-1 inline-block w-4 h-4 {changesPercentage >= 0
? ''
: 'rotate-180'}"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
>
<path
fill={changesPercentage >= 0 ? "#00FC50" : "#FF2F1F"}
d="M12.884 5.116a1.253 1.253 0 0 0-1.768 0l-5 5a1.25 1.25 0 0 0 1.768 1.768l2.866-2.866V18a1.25 1.25 0 1 0 2.5 0V9.018l2.866 2.866a1.25 1.25 0 1 0 1.768-1.768z"
/>
</svg>
{#if changesPercentage != null}
<span
class="-ml-1"
style="color: {changesPercentage >= 0
? '#00FC50'
: '#FF2F1F'}"
>
{changesPercentage >= 1000 || changesPercentage <= -1000
? abbreviateNumber(changesPercentage)
: changesPercentage?.toFixed(2)}%
</span>
{:else}
-
{/if}
today
</p>
</div>
{#if priceData?.length > 0}
<div class="w-[40%]">
<Chart
{...options}
{...theme.chart}
autoSize={true}
ref={(ref) => (chart = ref)}
>
<BaselineSeries
data={priceData}
{...theme.series}
{topLineColor}
>
<PriceLine
price={priceData?.at(0)?.value}
lineWidth={1}
color="#fff"
/>
</BaselineSeries>
</Chart>
</div>
{/if}
</label>
<!--Hover Stock Chart-->
</div>
</div>
</HoverCard.Content>
</HoverCard.Root>
</div>

View File

@ -1,195 +0,0 @@
<script>
import { ColorType } from "lightweight-charts";
import { Chart, BaselineSeries, PriceLine } from "svelte-lightweight-charts";
import { screenWidth, etfTicker } from "$lib/store";
import { goto } from "$app/navigation";
export let title;
export let priceData;
export let changesPercentage;
export let previousClose;
const topLineColor = "#71CA96";
const topFillColor1 = "rgba( 38, 166, 154, 0.2)";
const bottomLineColor = "#FF7070";
const bottomFillColor1 = "rgba(239, 83, 80, 0.2)";
let width = $screenWidth < 640 ? 80 : $screenWidth < 1500 ? 150 : 220; //= ($screenWidth <= 1200 && $screenWidth > 900) ? 360 : ($screenWidth <= 900 && $screenWidth > 700) ? 260 : ($screenWidth <= 700 && $screenWidth >=600 ) ? 200 : ($screenWidth < 600 && $screenWidth >=500 ) ? 150 : 80;
//Initial height of graph
let height = $screenWidth < 640 ? 50 : 60;
let observer;
let ref;
let chart;
ref = (element) => {
if (observer) {
observer?.disconnect();
}
if (!element) {
return;
}
observer = new ResizeObserver(([entry]) => {
width = entry.contentRect.width;
height = entry.contentRect.height;
});
observer.observe(element);
};
const THEMES = {
Dark: {
chart: {
layout: {
background: {
type: ColorType.Solid,
color: "#09090B",
},
lineColor: "#2B2B43",
textColor: "#D9D9D9",
},
crosshair: {
mode: 2,
},
grid: {
vertLines: {
visible: false,
},
horzLines: {
visible: false,
},
},
},
series: {
baseValue: { type: "price", price: priceData?.at(0)?.value },
priceLineVisible: false,
baseLineVisible: true,
baseLineColor: "#B2B5BE",
baseLineWidth: 1,
baseLineStyle: 1,
lineWidth: 1.5,
},
},
};
const AVAILABLE_THEMES = Object?.keys(THEMES);
let selected = AVAILABLE_THEMES[0];
$: theme = THEMES[selected];
const options = {
width: width,
height: height,
rightPriceScale: {
visible: false,
},
timeScale: {
borderColor: "#FFFFFF",
textColor: "#FFFFFF",
visible: false,
fixLeftEdge: true,
fixRightEdge: true,
},
handleScale: {
mouseWheel: false,
},
handleScroll: {
mouseWheel: false,
horzTouchDrag: false,
vertTouchDrag: false,
pressedMouseMove: false,
},
};
function etfSelector() {
if (title === "S&P500") {
etfTicker.update((value) => "SPY");
goto("/etf/SPY");
} else if (title === "Nasdaq") {
etfTicker.update((value) => "QQQ");
goto("/etf/QQQ");
} else if (title === "Dow") {
etfTicker.update((value) => "DIA");
goto("/etf/DIA");
} else if (title === "Russel") {
etfTicker.update((value) => "IWM");
goto("/etf/IWM");
}
}
$: {
if (chart && typeof window !== "undefined") {
chart?.timeScale()?.fitContent();
}
}
</script>
<label
on:click={etfSelector}
class="sm:hover:border-[#3C74D4] duration-100 transition ease-in-out cursor-pointer flex flex-row items-center rounded-md shadow-lg border border-gray-600 bg-default"
>
<div class="flex flex-col items-center lg:mr-5">
<span
class="text-white font-semibold text-xs w-20 text-start pl-3 uppercase"
>{title}</span
>
<div class="flex flex-row mt-1 items-center">
{#if changesPercentage >= 0}
<svg
class="w-4 h-4 -mr-0.5"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
><g id="evaArrowUpFill0"
><g id="evaArrowUpFill1"
><path
id="evaArrowUpFill2"
fill="#00FC50"
d="M16.21 16H7.79a1.76 1.76 0 0 1-1.59-1a2.1 2.1 0 0 1 .26-2.21l4.21-5.1a1.76 1.76 0 0 1 2.66 0l4.21 5.1A2.1 2.1 0 0 1 17.8 15a1.76 1.76 0 0 1-1.59 1Z"
/></g
></g
></svg
>
<span class="text-[#00FC50] text-xs"
>+{changesPercentage?.toFixed(2)}%</span
>
{:else}
<svg
class="w-4 h-4 -mr-0.5 rotate-180"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
><g id="evaArrowUpFill0"
><g id="evaArrowUpFill1"
><path
id="evaArrowUpFill2"
fill="#FF2F1F"
d="M16.21 16H7.79a1.76 1.76 0 0 1-1.59-1a2.1 2.1 0 0 1 .26-2.21l4.21-5.1a1.76 1.76 0 0 1 2.66 0l4.21 5.1A2.1 2.1 0 0 1 17.8 15a1.76 1.76 0 0 1-1.59 1Z"
/></g
></g
></svg
>
<span class="text-[#FF2F1F] text-xs"
>{changesPercentage?.toFixed(2)}%
</span>
{/if}
</div>
</div>
<Chart
{...options}
{...theme.chart}
autoSize={true}
ref={(ref) => (chart = ref)}
>
<BaselineSeries
data={priceData}
{...theme.series}
{topLineColor}
{topFillColor1}
{bottomLineColor}
{bottomFillColor1}
>
<PriceLine price={priceData?.at(0)?.value} lineWidth={1} color="#fff" />
</BaselineSeries>
</Chart>
</label>

View File

@ -1,344 +0,0 @@
<script lang="ts">
import { displayCompanyName, screenWidth } from "$lib/store";
import InfoModal from "$lib/components/InfoModal.svelte";
import { Chart } from "svelte-echarts";
import { onMount } from "svelte";
import { init, use } from "echarts/core";
import { LineChart } from "echarts/charts";
import { GridComponent, TooltipComponent } from "echarts/components";
import { CanvasRenderer } from "echarts/renderers";
use([LineChart, GridComponent, TooltipComponent, CanvasRenderer]);
export let data;
let isLoaded = false;
let priceAnalysisDict = data?.getPriceAnalysis;
let lastPrice = data?.getStockQuote?.price ?? "n/a";
const modalContent = `
Our AI model, employing a Bayesian approach, predicts future prices by breaking down trends, seasonality, and holiday effects. It integrates uncertainty to offer forecasts with intervals.<br><br>
<span class="font-semibold underline"><span class="italic">R</span><sup>2</sup> Score</span>: How well the regression model fits the data. A high score (close to 100%) is good, indicating a strong fit, while a low score (close to 0%) is bad, suggesting poor fit.
<br><br>
<span class="font-semibold underline">Mean Absolute Percentage Error (MAPE)</span>: Measures the average percentage difference between predicted and actual values. A lower MAPE indicates better accuracy, while a higher MAPE suggests less accurate predictions.
`;
const monthNames = [
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec",
];
let r2Score;
let mape;
let priceSentiment = "n/a";
let oneYearPricePrediction = "n/a";
let optionsData;
function getPlotOptions() {
const predictionDate = priceAnalysisDict?.predictionDate;
const upperBand = priceAnalysisDict?.upperBand;
const lowerBand = priceAnalysisDict?.lowerBand?.map((value) =>
value < 0 ? 0 : value,
);
const historicalPrice = priceAnalysisDict?.historicalPrice;
const option = {
silent: true,
animation: false,
grid: {
left: screenWidth < 640 ? "0%" : "2%",
right: screenWidth < 640 ? "5%" : "2%",
bottom: screenWidth < 640 ? "0%" : "5%",
containLabel: true,
},
tooltip: {
trigger: "axis",
hideDelay: 100,
},
xAxis: {
type: "category",
boundaryGap: false,
data: predictionDate,
axisLabel: {
color: "#fff",
formatter(value) {
const dateParts = value?.split("-");
const year = dateParts[0]?.substring(2);
const monthIndex = parseInt(dateParts[1], 10) - 1;
return `${monthNames[monthIndex]} '${year}`;
},
},
},
yAxis: [
{
type: "value",
splitLine: { show: false },
axisLabel: { show: false },
},
],
series: [
{
name: "Stock Price",
data: historicalPrice,
showSymbol: false,
smooth: true,
type: "line",
itemStyle: { color: "#fff" },
},
{
name: "Upperband",
data: upperBand,
showSymbol: false,
smooth: true,
type: "line",
areaStyle: { color: "rgba(60, 116, 212, 0.4)" },
lineStyle: { color: "rgb(60, 116, 212)" },
},
{
name: "Lowerband",
data: lowerBand,
showSymbol: false,
smooth: true,
type: "line",
areaStyle: { color: "#09090B", opacity: 1 },
lineStyle: { color: "rgb(60, 116, 212)" },
},
],
};
return option;
}
onMount(async () => {
oneYearPricePrediction = priceAnalysisDict?.meanResult?.slice(-1)?.at(0);
mape = priceAnalysisDict?.mape;
r2Score = priceAnalysisDict?.r2Score;
priceSentiment = lastPrice < oneYearPricePrediction ? "Bullish" : "Bearish";
optionsData = await getPlotOptions();
isLoaded = true;
});
</script>
<section class="overflow-hidden text-white h-full pb-8 sm:pb-2">
<main class="overflow-hidden">
<div class="flex flex-row items-center">
<label
for="priceAnalysisInfo"
class="mr-1 cursor-pointer flex flex-row items-center text-white text-xl sm:text-3xl font-bold"
>
AI Price Analysis
</label>
<InfoModal
title={"Price Analysis"}
content={modalContent}
id={"priceAnalysisInfo"}
/>
</div>
{#if isLoaded}
{#if Object?.keys(priceAnalysisDict)?.length !== 0}
<div class="w-full flex flex-col items-start">
<div class="text-white text-[1rem] mt-1 sm:mt-3 mb-1 w-full">
Our model predicts future prices by analyzing trends, seasonal
variations, and holiday impacts. Here are the stats of the model for {$displayCompanyName}
to ensure transparency and reliability.
</div>
</div>
<div class="w-full mt-5 mb-5 flex justify-start items-center">
<div
class="w-full grid grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4 gap-y-3 gap-x-3"
>
<!--Start Flow Sentiment-->
<div
class="flex flex-row items-center flex-wrap w-full px-3 sm:px-5 bg-primary shadow-lg rounded-md h-20"
>
<div class="flex flex-col items-start">
<span class=" text-gray-200 text-sm">Price Sentiment</span>
<span
class="text-start text-[1rem] sm:text-lg font-semibold {priceSentiment ===
'Bullish'
? 'text-[#37C97D]'
: 'text-red-700 dark:text-[#FF2F1F]'}"
>{priceSentiment}</span
>
</div>
</div>
<!--End Flow Sentiment-->
<!--Start Put/Call-->
<div
class="flex flex-row items-center flex-wrap w-full px-3 sm:px-5 bg-primary shadow-lg rounded-md h-20"
>
<div class="flex flex-col items-start">
<span class=" text-gray-200 text-sm"
><span class="italic">R</span><sup>2</sup> Score</span
>
<span class="text-start text-sm sm:text-[1rem] text-white">
{r2Score >= 65 ? "Good" : r2Score >= 50 ? "Moderate" : "Bad"}
</span>
</div>
<!-- Circular Progress -->
<div class="relative size-14 ml-auto">
<svg
class="size-full w-14 h-14"
viewBox="0 0 36 36"
xmlns="http://www.w3.org/2000/svg"
>
<!-- Background Circle -->
<circle
cx="18"
cy="18"
r="16"
fill="none"
class="stroke-current text-[#3E3E3E]"
stroke-width="3"
></circle>
<!-- Progress Circle inside a group with rotation -->
<g class="origin-center -rotate-90 transform">
<circle
cx="18"
cy="18"
r="16"
fill="none"
class="stroke-current {r2Score >= 65
? 'text-green-700 dark:text-[#00FC50]'
: r2Score >= 50
? 'text-[#F8901E]'
: 'text-red-700 dark:text-[#FF2F1F]'}"
stroke-width="3"
stroke-dasharray="100"
stroke-dashoffset={100 - r2Score}
></circle>
</g>
</svg>
<!-- Percentage Text -->
<div
class="absolute top-1/2 start-1/2 transform -translate-y-1/2 -translate-x-1/2"
>
<span class="text-center text-white text-sm">{r2Score}%</span>
</div>
</div>
<!-- End Circular Progress -->
</div>
<!--End Put/Call-->
<!--Start mape-->
<div
class="flex flex-row items-center flex-wrap w-full px-3 sm:px-5 bg-primary shadow-lg rounded-md h-20"
>
<div class="flex flex-col items-start">
<span class=" text-gray-200 text-sm">MAPE</span>
<span class="text-start text-sm sm:text-[1rem] text-white">
{mape <= 15 ? "Good" : mape <= 35 ? "Moderate" : "Bad"}
</span>
</div>
<!-- Circular Progress -->
<div class="relative size-14 ml-auto">
<svg
class="size-full w-14 h-14"
viewBox="0 0 36 36"
xmlns="http://www.w3.org/2000/svg"
>
<!-- Background Circle -->
<circle
cx="18"
cy="18"
r="16"
fill="none"
class="stroke-current text-[#3E3E3E]"
stroke-width="3"
></circle>
<!-- Progress Circle inside a group with rotation -->
<g class="origin-center -rotate-90 transform">
<circle
cx="18"
cy="18"
r="16"
fill="none"
class="stroke-current {mape <= 15
? 'text-green-700 dark:text-[#00FC50]'
: mape <= 35
? 'text-[#F8901E]'
: 'text-red-700 dark:text-[#FF2F1F]'}"
stroke-width="3"
stroke-dasharray="100"
stroke-dashoffset={100 - mape > 0 ? 100 - mape : 1}
></circle>
</g>
</svg>
<!-- Percentage Text -->
<div
class="absolute top-1/2 start-1/2 transform -translate-y-1/2 -translate-x-1/2"
>
<span class="text-center text-white text-sm">{mape}%</span>
</div>
</div>
<!-- End Circular Progress -->
</div>
<!--End mape-->
</div>
</div>
<div class="app w-full h-[300px]">
<Chart {init} options={optionsData} class="chart" />
</div>
<div class="text-white text-[1rem] mt-4 sm:mt-7 ml-1">
Over the next 12 months, the model predicts a
<span
class="font-semibold {priceSentiment === 'Bullish'
? 'text-[#37C97D]'
: 'text-red-700 dark:text-[#FF2F1F]'}">{priceSentiment}</span
>
trend, suggesting that the future price is expected to {priceSentiment ===
"Bullish"
? "surpass"
: "to be less than"} the previous price of
<span class="font-semibold">${lastPrice?.toFixed(2) ?? "n/a"}</span>,
with a mean value of
<span class="font-semibold">${oneYearPricePrediction}</span>.
</div>
{/if}
{:else}
<div class="flex justify-center items-center h-80">
<div class="relative">
<label
class="bg-secondary rounded-md h-14 w-14 flex justify-center items-center absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2"
>
<span class="loading loading-spinner loading-md text-gray-400"
></span>
</label>
</div>
</div>
{/if}
</main>
</section>
<style>
.app {
height: 300px;
max-width: 100%; /* Ensure chart width doesn't exceed the container */
}
@media (max-width: 640px) {
.app {
height: 230px;
}
}
.chart {
width: 100%;
}
</style>

View File

@ -69,9 +69,9 @@
animation: false, animation: false,
}, },
title: { title: {
text: `<div class="text-muted dark:text-gray-200 mt-3 text-center font-normal text-2xl">Price Target: <span class="${changesPercentage >= 0 ? "text-green-700 dark:text-[#00FC50]" : "text-[#FF2F1F]"}">$${priceTarget}</span></div> text: `<div class="text-muted dark:text-gray-200 mt-3 text-center font-normal text-2xl">Price Target: <span class="${changesPercentage >= 0 ? "text-green-700 dark:text-[#00FC50]" : "text-red-700 dark:text-[#FF2F1F]"}">$${priceTarget}</span></div>
<div class="text-muted dark:text-gray-200 mb-2 text-center font-normal text-xl">(${changesPercentage}% ${changesPercentage >= 0 ? "upside" : "downside"})</div> <div class="text-muted dark:text-gray-200 mb-2 text-center font-normal text-xl">(${changesPercentage}% ${changesPercentage >= 0 ? "upside" : "downside"})</div>
<div class="text-muted dark:text-gray-200 text-center font-normal text-xl flex justify-center items-center">Analyst Consensus: <span class="ml-1 ${consensusRating === "Buy" ? "text-green-700 dark:text-[#00FC50]" : consensusRating === "Sell" ? "text-red-500 dark:text-[#FF2F1F]" : consensusRating === "Hold" ? "text-orange-500 dark:text-[#D5AB31]" : "text-muted dark:text-white"}">${consensusRating ?? "n/a"}</span></div>`, <div class="text-muted dark:text-gray-200 text-center font-normal text-xl flex justify-center items-center">Analyst Consensus: <span class="ml-1 ${consensusRating === "Buy" ? "text-green-700 dark:text-[#00FC50]" : consensusRating === "Sell" ? "text-red-700 dark:text-[#FF2F1F]" : consensusRating === "Hold" ? "text-orange-500 dark:text-[#D5AB31]" : "text-muted dark:text-white"}">${consensusRating ?? "n/a"}</span></div>`,
style: { style: {
color: "white", color: "white",
// Using inline CSS for margin-top and margin-bottom // Using inline CSS for margin-top and margin-bottom
@ -299,7 +299,7 @@
</p> </p>
<div <div
class="mt-3 border border-gray-300 dark:border-gray-700 rounded" class="mt-3 border border-gray-300 dark:border-gray-800 rounded"
use:highcharts={configAnalyst} use:highcharts={configAnalyst}
></div> ></div>

View File

@ -1,253 +0,0 @@
<script lang="ts">
import {
varComponent,
displayCompanyName,
stockTicker,
etfTicker,
assetType,
getCache,
setCache,
} from "$lib/store";
import InfoModal from "$lib/components/InfoModal.svelte";
import Infobox from "$lib/components/Infobox.svelte";
import { Chart } from "svelte-echarts";
import { init, use } from "echarts/core";
import { LineChart } from "echarts/charts";
import { GridComponent, TooltipComponent } from "echarts/components";
import { CanvasRenderer } from "echarts/renderers";
export let data;
export let rawData = {};
use([LineChart, GridComponent, TooltipComponent, CanvasRenderer]);
let isLoaded = false;
let rating: string | undefined;
let outlook: string | undefined;
let valueAtRisk: number | string | undefined;
let optionsData: any;
let monthlyVarAvg: string | undefined;
function getPlotOptions() {
const dates: string[] = [];
const varList: number[] = [];
const priceList = [];
rawData?.history?.forEach((item: { date: string; var: number }) => {
dates.push(item?.date);
varList.push(item?.var);
priceList.push(item?.price);
});
const sum = varList.reduce((acc, curr) => acc + curr, 0);
monthlyVarAvg = (sum / varList.length)?.toFixed(2);
const option = {
silent: true,
tooltip: {
trigger: "axis",
hideDelay: 100,
},
animation: false,
grid: {
left: "2%",
right: "2%",
bottom: "2%",
top: "5%",
containLabel: true,
},
xAxis: {
type: "category",
boundaryGap: false,
data: dates,
axisLabel: {
color: "#fff",
formatter: (value: string) => {
const date = new Date(value + "-01");
return new Intl.DateTimeFormat("en-US", {
year: "numeric",
month: "short",
}).format(date);
},
},
},
yAxis: [
{
type: "value",
splitLine: { show: false },
axisLabel: { show: false },
},
{
type: "value",
splitLine: { show: false },
axisLabel: { show: false },
},
],
series: [
{
name: "VaR",
data: varList,
type: "line",
itemStyle: { color: "#E11D48" },
showSymbol: false,
},
{
name: "Stock Price",
data: priceList,
type: "line",
yAxisIndex: 1,
itemStyle: { color: "#fff" },
showSymbol: false,
},
],
};
return option;
}
$: {
const ticker = $assetType === "stock" ? $stockTicker : "etf";
if (ticker) {
isLoaded = false;
rating = rawData.rating;
outlook = rawData.outlook;
valueAtRisk = rawData.history?.slice(-1)?.at(0)?.var ?? "n/a";
optionsData = getPlotOptions();
}
}
</script>
<section class="overflow-hidden text-white h-full pb-10 sm:pb-0">
<main class="overflow-hidden">
<div class="flex flex-row items-center">
<label
for="varInfo"
class="mr-1 cursor-pointer flex flex-row items-center text-white text-xl sm:text-2xl font-bold"
>
Value at Risk
</label>
<InfoModal
title={"Value at Risk"}
content={`Value at Risk (VaR) quantifies the potential loss of investment or capital within a specified time frame (N days) under typical market conditions, providing an estimate of potential losses with a given probability for ${$displayCompanyName}.`}
id={"varInfo"}
/>
</div>
{#if Object?.keys(rawData)?.length !== 0}
<div class="pb-4 w-full mt-5">
<div
class="w-auto p-4 sm:p-6 bg-default sm:bg-default rounded-md relative"
>
<div class="flex flex-row items-center justify-between">
<div class="relative size-[60px] sm:size-[90px] ml-auto">
<svg
class="size-full w-[60px] h-[60px] sm:w-[90px] sm:h-[90px]"
viewBox="0 0 36 36"
xmlns="http://www.w3.org/2000/svg"
>
<!-- Background Circle -->
<circle
cx="18"
cy="18"
r="16"
fill="none"
class="stroke-current text-[#303030]"
stroke-width="4"
></circle>
<!-- Progress Circle inside a group with rotation -->
<g class="origin-center -rotate-90 transform">
<circle
cx="18"
cy="18"
r="16"
fill="none"
class="stroke-current {rating > 5
? 'text-green-700 dark:text-[#00FC50]'
: rating < 5
? 'text-red-700 dark:text-[#FF2F1F]'
: 'text-white'} "
stroke-width="4"
stroke-dasharray="100"
stroke-dashoffset={100 - rating * 10}
></circle>
</g>
</svg>
<!-- Percentage Text -->
<div
class="absolute top-1/2 start-1/2 transform -translate-y-1/2 -translate-x-1/2"
>
<span
class="text-center text-white text-xl sm:text-2xl font-semibold"
>
{rating}
</span>
</div>
</div>
<div
class="flex flex-col items-start ml-4 sm:ml-10 mr-auto sm:-top-3 sm:relative"
>
<h3
class="hidden sm:block text-gray-300 text-[1rem] sm:text-lg font-semibold"
>
<span
class={outlook === "Minimum Risk"
? "text-[#10BC09]"
: outlook === "Risky"
? "text-red-500"
: "text-white"}>{outlook}</span
> outlook:
</h3>
<span class="text-gray-200 text-sm sm:text-lg mt-1">
Under typical market conditions, there is a <span
class="font-semibold">95%</span
>
probability that
{$assetType === "stock" ? $stockTicker : $etfTicker}
will incur a maximum loss of
<span class="text-[#FF2F1F] font-semibold">{valueAtRisk}%</span>
in the upcoming week.
</span>
</div>
</div>
</div>
</div>
<h2 class="text-white text-xl sm:text-2xl font-semibold mt-5">
Historical VaR
</h2>
<div class="text-white text-[1rem] mt-3">
Based on historical price data, the company experienced an average
monthly Value-at-Risk <span class="font-semibold">{monthlyVarAvg}%</span
>.
</div>
<div class="app w-full h-[300px] mt-5">
<Chart {init} options={optionsData} class="chart" />
</div>
{:else}
<Infobox text="No data available" />
{/if}
</main>
</section>
<style>
.app {
height: 300px;
max-width: 100%; /* Ensure chart width doesn't exceed the container */
}
@media (max-width: 640px) {
.app {
height: 210px;
}
}
.chart {
width: 100%;
}
</style>

View File

@ -1,22 +0,0 @@
export const load = async ({ locals, setHeaders }) => {
const getEconomicIndicator = async () => {
const { apiKey, apiURL } = locals;
const response = await fetch(apiURL + "/economic-indicator", {
method: "GET",
headers: {
"Content-Type": "application/json",
"X-API-KEY": apiKey,
},
});
const output = await response.json();
return output;
};
// Make sure to return a promise
return {
getEconomicIndicator: await getEconomicIndicator(),
};
};

File diff suppressed because it is too large Load Diff

View File

@ -1,19 +1,13 @@
<script lang="ts"> <script lang="ts">
import { displayCompanyName, screenWidth, indexTicker } from "$lib/store"; import { displayCompanyName, indexTicker } from "$lib/store";
import DailyStats from "$lib/components/Options/DailyStats.svelte"; import DailyStats from "$lib/components/Options/DailyStats.svelte";
import { Chart } from "svelte-echarts"; import { abbreviateNumber } from "$lib/utils";
import { abbreviateNumberWithColor, monthNames } from "$lib/utils";
import { onMount } from "svelte"; import { onMount } from "svelte";
import UpgradeToPro from "$lib/components/UpgradeToPro.svelte"; import UpgradeToPro from "$lib/components/UpgradeToPro.svelte";
import { init, use } from "echarts/core";
import { BarChart, LineChart } from "echarts/charts";
import { GridComponent, TooltipComponent } from "echarts/components";
import { CanvasRenderer } from "echarts/renderers";
import Infobox from "$lib/components/Infobox.svelte"; import Infobox from "$lib/components/Infobox.svelte";
import SEO from "$lib/components/SEO.svelte"; import SEO from "$lib/components/SEO.svelte";
use([BarChart, LineChart, GridComponent, TooltipComponent, CanvasRenderer]);
export let data; export let data;
let dailyStats = data?.getDailyStats; let dailyStats = data?.getDailyStats;
@ -56,145 +50,6 @@
displayData = event.target.value; displayData = event.target.value;
} }
function plotData(callData, putData, priceList) {
const options = {
animation: false,
tooltip: {
trigger: "axis",
hideDelay: 100,
borderColor: "#969696", // Black border color
borderWidth: 1, // Border width of 1px
backgroundColor: "#313131", // Optional: Set background color for contrast
textStyle: {
color: "#fff", // Optional: Text color for better visibility
},
formatter: function (params) {
// Get the timestamp from the first parameter
const timestamp = params[0].axisValue;
// Initialize result with timestamp
let result = timestamp + "<br/>";
// Sort params to ensure Vol appears last
params.sort((a, b) => {
if (a.seriesName === "Vol") return 1;
if (b.seriesName === "Vol") return -1;
return 0;
});
// Add each series data
params?.forEach((param) => {
const marker =
'<span style="display:inline-block;margin-right:4px;' +
"border-radius:10px;width:10px;height:10px;background-color:" +
param.color +
'"></span>';
result +=
marker +
param.seriesName +
": " +
abbreviateNumberWithColor(param.value, false, true) +
"<br/>";
});
return result;
},
axisPointer: {
lineStyle: {
color: "#fff",
},
},
},
silent: true,
grid: {
left: $screenWidth < 640 ? "5%" : "2%",
right: $screenWidth < 640 ? "5%" : "2%",
bottom: "10%",
containLabel: true,
},
xAxis: [
{
type: "category",
data: dateList,
axisLabel: {
color: "#fff",
formatter: function (value) {
// Assuming dates are in the format 'yyyy-mm-dd'
const dateParts = value.split("-");
const monthIndex = parseInt(dateParts[1]) - 1; // Months are zero-indexed in JavaScript Date objects
const year = parseInt(dateParts[0]);
const day = parseInt(dateParts[2]);
return `${day} ${monthNames[monthIndex]} ${year}`;
},
},
},
],
yAxis: [
{
type: "value",
splitLine: {
show: false, // Disable x-axis grid lines
},
axisLabel: {
show: false, // Hide y-axis labels
},
},
{
type: "value",
splitLine: {
show: false, // Disable x-axis grid lines
},
position: "right",
axisLabel: {
show: false, // Hide y-axis labels
},
},
],
series: [
{
name: "Call",
type: "bar",
stack: "Put-Call Ratio",
emphasis: {
focus: "series",
},
data: callData,
itemStyle: {
color: "#00FC50",
},
},
{
name: "Put",
type: "bar",
stack: "Put-Call Ratio",
emphasis: {
focus: "series",
},
data: putData,
itemStyle: {
color: "#EE5365", //'#7A1C16'
},
},
{
name: "Price", // Name for the line chart
type: "line", // Type of the chart (line)
yAxisIndex: 1, // Use the second y-axis on the right
data: priceList, // iv60Data (assumed to be passed as priceList)
itemStyle: {
color: "#fff", // Choose a color for the line (gold in this case)
},
lineStyle: {
width: 2, // Set the width of the line
},
smooth: true, // Optional: make the line smooth
showSymbol: false,
},
],
};
return options;
}
function filterDate(filteredList, displayTimePeriod) { function filterDate(filteredList, displayTimePeriod) {
const now = Date.now(); const now = Date.now();
let cutoffDate; let cutoffDate;
@ -244,11 +99,13 @@
putOpenInterestList = filteredList?.map((item) => item?.put_open_interest); putOpenInterestList = filteredList?.map((item) => item?.put_open_interest);
// Determine the type of plot data to generate based on displayData // Determine the type of plot data to generate based on displayData
/*
if (displayData === "volume") { if (displayData === "volume") {
options = plotData(callVolumeList, putVolumeList, priceList); options = plotData(callVolumeList, putVolumeList, priceList);
} else if (displayData === "openInterest") { } else if (displayData === "openInterest") {
options = plotData(callOpenInterestList, putOpenInterestList, priceList); options = plotData(callOpenInterestList, putOpenInterestList, priceList);
} }
*/
} }
async function handleScroll() { async function handleScroll() {
@ -290,7 +147,7 @@
<div <div
class="w-full relative flex justify-center items-center overflow-hidden" class="w-full relative flex justify-center items-center overflow-hidden"
> >
<div class="sm:pl-7 sm:pb-7 sm:pt-7 w-full m-auto mt-2 sm:mt-0"> <div class="sm:pl-5 sm:pb-7 sm:pt-7 w-full m-auto mt-2 sm:mt-0">
{#if Object?.keys(dailyStats)?.length === 0 && rawData?.length === 0} {#if Object?.keys(dailyStats)?.length === 0 && rawData?.length === 0}
<Infobox text="No Options data available" /> <Infobox text="No Options data available" />
{/if} {/if}
@ -302,56 +159,6 @@
{/if} {/if}
{#if rawData?.length > 0} {#if rawData?.length > 0}
<div class="flex flex-row items-center w-full mt-10">
<!--
<select
class="ml-1 w-40 select select-bordered select-sm p-0 pl-5 bg-secondary"
on:change={changeTimePeriod}
>
<option disabled>Choose a time period</option>
<option value="oneWeek">1 Week</option>
<option value="oneMonth">1 Month</option>
<option value="threeMonths">3 Months</option>
<option value="sixMonths">6 Months</option>
<option value="oneYear" selected>1 Year</option>
</select>
-->
<select
class=" w-40 select select-bordered select-sm p-0 pl-5 bg-secondary"
on:change={changeVolumeOI}
>
<option disabled>Choose a category</option>
<option value="volume" selected>Volume</option>
<option value="openInterest">Open Interest</option>
</select>
</div>
<div class="app w-full bg-default">
{#if filteredList?.length !== 0}
<Chart {init} {options} class="chart" />
{:else}
<span
class="text-xl text-white m-auto flex justify-center items-center h-full"
>
<div
class="text-white text-sm sm:text-[1rem] sm:rounded-md h-auto border border-gray-600 p-4"
>
<svg
class="w-5 h-5 inline-block sm:mr-2 shrink-0"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 256 256"
><path
fill="#fff"
d="M128 24a104 104 0 1 0 104 104A104.11 104.11 0 0 0 128 24m-4 48a12 12 0 1 1-12 12a12 12 0 0 1 12-12m12 112a16 16 0 0 1-16-16v-40a8 8 0 0 1 0-16a16 16 0 0 1 16 16v40a8 8 0 0 1 0 16"
/></svg
>
No Options activity found
</div>
</span>
{/if}
</div>
{#if optionList?.length !== 0} {#if optionList?.length !== 0}
<h3 class="text-xl sm:text-2xl text-white font-bold text-start"> <h3 class="text-xl sm:text-2xl text-white font-bold text-start">
Historical {$indexTicker} Data Historical {$indexTicker} Data
@ -359,92 +166,66 @@
<div class="flex justify-start items-center m-auto overflow-x-auto"> <div class="flex justify-start items-center m-auto overflow-x-auto">
<table <table
class="w-full table table-sm table-compact bg-table border border-gray-800 rounded-none sm:rounded-md m-auto mt-4 overflow-x-auto" class="table table-sm table-compact no-scrollbar rounded-none sm:rounded-md w-full bg-white dark:bg-table border border-gray-300 dark:border-gray-800 m-auto mt-3"
> >
<thead class="bg-default"> <thead class="text-muted dark:text-white dark:bg-default">
<tr class=""> <tr class="">
<td class="text-white font-semibold text-sm text-start" <td class=" font-semibold text-sm text-start">Date</td>
>Date</td <td class=" font-semibold text-sm text-end">% Change</td>
> <td class=" font-semibold text-sm text-end">P/C</td>
<td class="text-white font-semibold text-sm text-end" <td class=" font-semibold text-sm text-center">Volume</td>
>% Change</td <td class=" font-semibold text-sm text-center">C Volume</td>
> <td class=" font-semibold text-sm text-center">P Volume</td>
<td class="text-white font-semibold text-sm text-end"
>P/C</td
>
<td class="text-white font-semibold text-sm text-center"
>Volume</td
>
<td class="text-white font-semibold text-sm text-center"
>C Volume</td
>
<td class="text-white font-semibold text-sm text-center"
>P Volume</td
>
<!-- <!--
<td class="text-white font-semibold text-sm text-end" <td class=" font-semibold text-sm text-end"
>Vol/30D</td >Vol/30D</td
> >
--> -->
<!-- <!--
<td class="text-white font-semibold text-sm text-end" <td class=" font-semibold text-sm text-end"
>🐻/🐂 Prem</td >🐻/🐂 Prem</td
> >
--> -->
<td class="text-white font-semibold text-sm text-end" <td class=" font-semibold text-sm text-end">Total OI</td>
>Total OI</td <td class=" font-semibold text-sm text-end">OI Change</td>
> <td class=" font-semibold text-sm text-end">% OI Change</td>
<td class="text-white font-semibold text-sm text-end" <td class=" font-semibold text-sm text-end">C Prem</td>
>OI Change</td <td class=" font-semibold text-sm text-end">P Prem</td>
>
<td class="text-white font-semibold text-sm text-end"
>% OI Change</td
>
<td class="text-white font-semibold text-sm text-end"
>C Prem</td
>
<td class="text-white font-semibold text-sm text-end"
>P Prem</td
>
<!-- <!--
<td class="text-white font-semibold text-sm text-end" <td class=" font-semibold text-sm text-end"
>Net Prem</td >Net Prem</td
> >
<td class="text-white font-semibold text-sm text-end" <td class=" font-semibold text-sm text-end"
>Total Prem</td >Total Prem</td
> >
--> -->
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{#each ["Pro"]?.includes(data?.user?.tier) ? optionList : optionList?.slice(0, 3) as item, index} {#each data?.user?.tier === "Pro" ? optionList : optionList?.slice(0, 3) as item, index}
<tr <tr
class="dark:sm:hover:bg-[#245073]/10 odd:bg-[#F6F7F8] dark:odd:bg-oddborder-b border-gray-800 {index + class="dark:sm:hover:bg-[#245073]/10 odd:bg-[#F6F7F8] dark:odd:bg-odd {index +
1 === 1 ===
optionList?.slice(0, 3)?.length && optionList?.slice(0, 3)?.length &&
!['Pro']?.includes(data?.user?.tier) !['Pro']?.includes(data?.user?.tier)
? 'opacity-[0.1]' ? 'opacity-[0.1]'
: ''}" : ''}"
> >
<td class="text-white text-sm sm:text-[1rem] text-start"> <td class=" text-sm sm:text-[1rem] text-start">
{formatDate(item?.date)} {formatDate(item?.date)}
</td> </td>
<td class="text-white text-sm sm:text-[1rem] text-end"> <td class=" text-sm sm:text-[1rem] text-end">
{#if item?.changesPercentage >= 0 && item?.changesPercentage !== null} {#if item?.changesPercentage >= 0 && item?.changesPercentage !== null}
<span class="text-green-700 dark:text-[#00FC50]" <span class="text-green-700 dark:text-[#00FC50]"
>+{item?.changesPercentage >= 1000 >+{item?.changesPercentage >= 1000
? abbreviateNumberWithColor( ? abbreviateNumber(item?.changesPercentage)
item?.changesPercentage,
)
: item?.changesPercentage?.toFixed(2)}%</span : item?.changesPercentage?.toFixed(2)}%</span
> >
{:else if item?.changesPercentage < 0 && item?.changesPercentage !== null} {:else if item?.changesPercentage < 0 && item?.changesPercentage !== null}
<span class="text-red-700 dark:text-[#FF2F1F]" <span class="text-red-700 dark:text-[#FF2F1F]"
>{item?.changesPercentage <= -1000 >{item?.changesPercentage <= -1000
? abbreviateNumberWithColor( ? abbreviateNumber(item?.changesPercentage)
item?.changesPercentage,
)
: item?.changesPercentage?.toFixed(2)}% : item?.changesPercentage?.toFixed(2)}%
</span> </span>
{:else} {:else}
@ -452,11 +233,11 @@
{/if} {/if}
</td> </td>
<td class="text-sm sm:text-[1rem] text-end text-white"> <td class="text-sm sm:text-[1rem] text-end">
{item?.putCallRatio} {item?.putCallRatio}
</td> </td>
<td class="text-sm sm:text-[1rem] text-end text-white"> <td class="text-sm sm:text-[1rem] text-end">
{item?.volume?.toLocaleString("en-US")} {item?.volume?.toLocaleString("en-US")}
</td> </td>
@ -472,7 +253,7 @@
{item?.put_volume?.toLocaleString("en-US")} {item?.put_volume?.toLocaleString("en-US")}
</td> </td>
<!-- <!--
<td class="text-sm sm:text-[1rem] text-white text-end"> <td class="text-sm sm:text-[1rem] text-end">
{item?.avgVolumeRatio?.toFixed(2)} {item?.avgVolumeRatio?.toFixed(2)}
</td> </td>
--> -->
@ -520,24 +301,24 @@
> >
<div class="flex justify-between space-x-4"> <div class="flex justify-between space-x-4">
<div <div
class="space-y-1 flex flex-col items-start text-white" class="space-y-1 flex flex-col items-start "
> >
<div> <div>
Bearish: {@html abbreviateNumberWithColor( Bearish: {@html abbreviateNumber(
item?.premium_ratio[0], item?.premium_ratio[0],
false, false,
true, true,
)} )}
</div> </div>
<div> <div>
Neutral: {@html abbreviateNumberWithColor( Neutral: {@html abbreviateNumber(
item?.premium_ratio[1], item?.premium_ratio[1],
false, false,
true, true,
)} )}
</div> </div>
<div> <div>
Bullish: {@html abbreviateNumberWithColor( Bullish: {@html abbreviateNumber(
item?.premium_ratio[2], item?.premium_ratio[2],
false, false,
true, true,
@ -550,15 +331,15 @@
</td> </td>
--> -->
<td class="text-sm sm:text-[1rem] text-end text-white"> <td class="text-sm sm:text-[1rem] text-end">
{@html abbreviateNumberWithColor( {@html abbreviateNumber(
item?.total_open_interest, item?.total_open_interest,
false, false,
true, true,
)} )}
</td> </td>
<td class="text-white text-sm sm:text-[1rem] text-end"> <td class=" text-sm sm:text-[1rem] text-end">
{#if item?.changeOI >= 0} {#if item?.changeOI >= 0}
<span class="text-green-700 dark:text-[#00FC50]" <span class="text-green-700 dark:text-[#00FC50]"
>+{item?.changeOI?.toLocaleString("en-US")}</span >+{item?.changeOI?.toLocaleString("en-US")}</span
@ -568,58 +349,50 @@
>{item?.changeOI?.toLocaleString("en-US")} >{item?.changeOI?.toLocaleString("en-US")}
</span> </span>
{:else} {:else}
<span class="text-white"> n/a </span> <span class=""> n/a </span>
{/if} {/if}
</td> </td>
<td class="text-white text-sm sm:text-[1rem] text-end"> <td class=" text-sm sm:text-[1rem] text-end">
{#if item?.changesPercentageOI >= 0} {#if item?.changesPercentageOI >= 0}
<span class="text-green-700 dark:text-[#00FC50]" <span class="text-green-700 dark:text-[#00FC50]"
>+{item?.changesPercentageOI >= 1000 >+{item?.changesPercentageOI >= 1000
? abbreviateNumberWithColor( ? abbreviateNumber(item?.changesPercentageOI)
item?.changesPercentageOI,
)
: item?.changesPercentageOI?.toFixed(2)}%</span : item?.changesPercentageOI?.toFixed(2)}%</span
> >
{:else if item?.changesPercentageOI < 0} {:else if item?.changesPercentageOI < 0}
<span class="text-red-700 dark:text-[#FF2F1F]" <span class="text-red-700 dark:text-[#FF2F1F]"
>{item?.changesPercentageOI <= -1000 >{item?.changesPercentageOI <= -1000
? abbreviateNumberWithColor( ? abbreviateNumber(item?.changesPercentageOI)
item?.changesPercentageOI,
)
: item?.changesPercentageOI?.toFixed(2)}% : item?.changesPercentageOI?.toFixed(2)}%
</span> </span>
{:else} {:else}
<span class="text-white"> n/a </span> <span class=""> n/a </span>
{/if} {/if}
</td> </td>
<td class="text-sm sm:text-[1rem] text-end text-white"> <td class="text-sm sm:text-[1rem] text-end">
{@html abbreviateNumberWithColor( {@html abbreviateNumber(
item?.call_premium, item?.call_premium,
false, false,
true, true,
)} )}
</td> </td>
<td class="text-sm sm:text-[1rem] text-end text-white"> <td class="text-sm sm:text-[1rem] text-end">
{@html abbreviateNumberWithColor( {@html abbreviateNumber(item?.put_premium, false, true)}
item?.put_premium,
false,
true,
)}
</td> </td>
<!-- <!--
<td class="text-sm sm:text-[1rem] text-end text-white"> <td class="text-sm sm:text-[1rem] text-end ">
{@html abbreviateNumberWithColor( {@html abbreviateNumber(
item?.net_premium, item?.net_premium,
false, false,
true, true,
)} )}
</td> </td>
<td class="text-sm sm:text-[1rem] text-end text-white"> <td class="text-sm sm:text-[1rem] text-end ">
{@html abbreviateNumberWithColor( {@html abbreviateNumber(
item?.total_premium, item?.total_premium,
false, false,
true, true,

View File

@ -1166,7 +1166,7 @@
<div class="flex flex-row items-center justify-between"> <div class="flex flex-row items-center justify-between">
<h3 class="text-2xl md:text-3xl font-bold">Lifetime</h3> <h3 class="text-2xl md:text-3xl font-bold">Lifetime</h3>
<div> <div>
<span class="text-3xl md:text-4xl font-bold">$599</span> <span class="text-3xl md:text-4xl font-bold">$799</span>
</div> </div>
</div> </div>
<p class=" md:text-lg mt-4 lg:mt-2"> <p class=" md:text-lg mt-4 lg:mt-2">
@ -1196,24 +1196,30 @@
</div> </div>
</div> </div>
</div> </div>
<div class="relative flex flex-col items-center">
<div
class="h-4 w-4 rounded-full bg-blue-500 relative z-10"
></div>
<div class="mt-2 text-center">
<div class="text-lg sm:text-xl font-semibold">
<span class="text-zinc-900 dark:text-white">$599</span>
</div>
<div class="text-xs sm:text-sm">First 100 users</div>
</div>
</div>
<div class="relative flex flex-col items-center"> <div class="relative flex flex-col items-center">
<div <div
class="h-4 w-4 rounded-full bg-zinc-500 dark:bg-gray-600 relative z-10" class="h-4 w-4 rounded-full bg-zinc-500 dark:bg-gray-600 relative z-10"
></div> ></div>
<div class="mt-2 text-center"> <div class="mt-2 text-center">
<div class="text-lg sm:text-xl font-semibold"> <div class="text-lg sm:text-xl font-semibold">
<span class="text-zinc-500 dark:text-zinc-400">$799</span> <span class="text-gray-800 dark:text-gray-300 line-through"
>$599</span
>
</div>
<div
class="text-xs sm:text-sm text-gray-800 dark:text-gray-300"
>
First 100 users
</div>
</div>
</div>
<div class="relative flex flex-col items-center">
<div
class="h-4 w-4 rounded-full bg-blue-500 relative z-10"
></div>
<div class="mt-2 text-center">
<div class="text-lg sm:text-xl font-semibold">
<span class="">$799</span>
</div> </div>
<div <div
class="text-xs sm:text-sm text-gray-800 dark:text-gray-300" class="text-xs sm:text-sm text-gray-800 dark:text-gray-300"

View File

@ -788,11 +788,16 @@
class="flex flex-col border-b border-gray-300 dark:border-gray-800 py-1 sm:table-row sm:py-0" class="flex flex-col border-b border-gray-300 dark:border-gray-800 py-1 sm:table-row sm:py-0"
><td ><td
class="whitespace-nowrap px-0.5 py-[1px] xs:px-1 text-sm sm:text-[1rem]" class="whitespace-nowrap px-0.5 py-[1px] xs:px-1 text-sm sm:text-[1rem]"
>Revenue (ttm)</td ><a
href={`/stocks/${$stockTicker}/statistics/revenue`}
class="sm:hover:text-muted dark:sm:hover:text-blue-400 underline underline-offset-4"
>Revenue (ttm)</a
></td
> >
<td <td
class="whitespace-nowrap px-0.5 py-[1px] text-left text-sm text-[1rem] font-semibold dark:font-normal xs:px-1 sm:text-right" class="whitespace-nowrap px-0.5 py-[1px] text-left text-sm text-[1rem] font-semibold dark:font-normal xs:px-1 sm:text-right"
>{@html stockDeck?.revenueTTM !== null && >
{@html stockDeck?.revenueTTM !== null &&
stockDeck?.revenueTTM !== 0 stockDeck?.revenueTTM !== 0
? abbreviateNumber(stockDeck?.revenueTTM, false, true) ? abbreviateNumber(stockDeck?.revenueTTM, false, true)
: "n/a"}</td : "n/a"}</td

View File

@ -237,7 +237,7 @@
<span>Revenue (ttm)</span> <span>Revenue (ttm)</span>
</div> </div>
<div class="flex items-baseline"> <div class="flex items-baseline">
<span class="text-xl font-bold"> <span class="text-xl font-semibold">
{abbreviateNumber(rawData?.revenue, true)}</span {abbreviateNumber(rawData?.revenue, true)}</span
> >
</div> </div>
@ -250,7 +250,7 @@
<span>Revenue Growth</span> <span>Revenue Growth</span>
</div> </div>
<div class="flex items-baseline"> <div class="flex items-baseline">
<span class="text-xl font-bold" <span class="text-xl font-semibold"
>{rawData?.growthRevenue}%</span >{rawData?.growthRevenue}%</span
> >
</div> </div>
@ -263,7 +263,7 @@
<span>Price / Sales Ratio</span> <span>Price / Sales Ratio</span>
</div> </div>
<div class="flex items-baseline"> <div class="flex items-baseline">
<span class="text-xl font-bold" <span class="text-xl font-semibold"
>{rawData?.priceToSalesRatio}</span >{rawData?.priceToSalesRatio}</span
> >
</div> </div>
@ -276,7 +276,7 @@
<span>Revenue / Employee </span> <span>Revenue / Employee </span>
</div> </div>
<div class="flex items-baseline"> <div class="flex items-baseline">
<span class="text-xl font-bold" <span class="text-xl font-semibold"
>{abbreviateNumber( >{abbreviateNumber(
rawData?.revenuePerEmployee, rawData?.revenuePerEmployee,
true, true,
@ -292,7 +292,7 @@
<span>Employees </span> <span>Employees </span>
</div> </div>
<div class="flex items-baseline"> <div class="flex items-baseline">
<span class="text-xl font-bold" <span class="text-xl font-semibold"
>{rawData?.employees?.toLocaleString("en-US")}</span >{rawData?.employees?.toLocaleString("en-US")}</span
> >
</div> </div>
@ -304,7 +304,7 @@
<span>Market Cap </span> <span>Market Cap </span>
</div> </div>
<div class="flex items-baseline"> <div class="flex items-baseline">
<span class="text-xl font-bold" <span class="text-xl font-semibold"
>{abbreviateNumber(data?.getStockQuote?.marketCap)}</span >{abbreviateNumber(data?.getStockQuote?.marketCap)}</span
> >
</div> </div>

View File

@ -1377,9 +1377,9 @@
on:input={handleInput} on:input={handleInput}
autocomplete="off" autocomplete="off"
autofocus="" autofocus=""
class="text-sm w-full bg-white dark:bg-default border-0 focus:border-gray-200 focus:ring-0 placeholder:text-gray-300 pr-8" class="text-sm w-full bg-white dark:bg-default border-0 focus:border-gray-200 focus:ring-0 focus:outline-none placeholder:text-gray-400 dark:placeholder:text-gray-600 pr-8"
type="text" type="text"
placeholder="" placeholder="Search indicators..."
/> />
<!-- Clear Button - Shown only when searchQuery has input --> <!-- Clear Button - Shown only when searchQuery has input -->

View File

@ -5,7 +5,6 @@ module.exports = {
safelist: ["dark"], safelist: ["dark"],
content: [ content: [
"./src/**/*.{html,js,svelte,ts}", "./src/**/*.{html,js,svelte,ts}",
"./node_modules/flowbite-svelte/**/*.{html,js,svelte,ts}",
], ],
theme: { theme: {
container: { container: {
@ -194,5 +193,4 @@ module.exports = {
}, },
}, },
}, },
plugins: [require("flowbite/plugin")],
}; };