From 4ce4bdf1aab4f53e1cf8d6ed54c734d3ed9a86c0 Mon Sep 17 00:00:00 2001 From: obchain Date: Tue, 26 May 2026 18:05:23 +0530 Subject: [PATCH 1/4] chore(frontend): swap lightweight-charts for echarts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit lightweight-charts ships a mandatory TradingView attribution that the license forbids removing. Replace with echarts (MIT, tree-shakable, native candlestick + line + bar). Bundle weight grows from ~52 kB to ~200 kB on the chart route only — the trade for richer chart chrome and no third-party branding sitting on every screenshot. --- frontend/package.json | 16 ++++++++-------- frontend/pnpm-lock.yaml | 38 +++++++++++++++++++++++--------------- 2 files changed, 31 insertions(+), 23 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index f9b6fea..fdfd13a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -16,22 +16,22 @@ "format:check": "prettier --check \"**/*.{ts,tsx,js,jsx,json,md,css}\"" }, "dependencies": { + "echarts": "^6.1.0", "next": "15.1.6", "react": "19.0.0", - "react-dom": "19.0.0", - "lightweight-charts": "4.2.2" + "react-dom": "19.0.0" }, "devDependencies": { + "@eslint/eslintrc": "3.2.0", + "@tailwindcss/postcss": "4.0.17", "@types/node": "22.10.7", "@types/react": "19.0.7", "@types/react-dom": "19.0.3", - "@tailwindcss/postcss": "4.0.17", - "tailwindcss": "4.0.17", - "postcss": "8.5.1", - "typescript": "5.7.3", "eslint": "9.18.0", "eslint-config-next": "15.1.6", - "@eslint/eslintrc": "3.2.0", - "prettier": "3.4.2" + "postcss": "8.5.1", + "prettier": "3.4.2", + "tailwindcss": "4.0.17", + "typescript": "5.7.3" } } diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 47735e2..eb11966 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -8,9 +8,9 @@ importers: .: dependencies: - lightweight-charts: - specifier: 4.2.2 - version: 4.2.2 + echarts: + specifier: ^6.1.0 + version: 6.1.0 next: specifier: 15.1.6 version: 15.1.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -791,6 +791,9 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} + echarts@6.1.0: + resolution: {integrity: sha512-q0yaFPggC9FUdsWH4blavRWFmxdrIodbkoKNAjJudAI6CA9gNPxHtV2RcZNEepZVlk4yvBYkOkbk6HIVpIyHZA==} + emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} @@ -954,9 +957,6 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} - fancy-canvas@2.1.0: - resolution: {integrity: sha512-nifxXJ95JNLFR2NgRV4/MxVP45G9909wJTEKz5fg/TZS20JJZA6hfgRVh/bC9bwl2zBtBNcYPjiBE4njQHVBwQ==} - fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -1326,9 +1326,6 @@ packages: resolution: {integrity: sha512-6b6gd/RUXKaw5keVdSEtqFVdzWnU5jMxTUjA2bVcMNPLwSQ08Sv/UodBVtETLCn7k4S1Ibxwh7k68IwLZPgKaA==} engines: {node: '>= 12.0.0'} - lightweight-charts@4.2.2: - resolution: {integrity: sha512-H5u9BfUeWzOA90QaqAlGjz1tePa7kUihKSOMiE4538/xMuM3k3n6bWYxuZSj9zwIj1PhgY/J5HTA/4lMk/lYYQ==} - locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} @@ -1706,6 +1703,9 @@ packages: tsconfig-paths@3.15.0: resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} + tslib@2.3.0: + resolution: {integrity: sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==} + tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} @@ -1776,6 +1776,9 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + zrender@6.1.0: + resolution: {integrity: sha512-oEGMDB6pOP2S6OwRR4PdVv610zrjnA3Bh+JnSG12fYJlBKjtNAoEb5fSUoCOOINlH96I2fU38/A2UpRKs67xYQ==} + snapshots: '@alloc/quick-lru@5.2.0': {} @@ -2471,6 +2474,11 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 + echarts@6.1.0: + dependencies: + tslib: 2.3.0 + zrender: 6.1.0 + emoji-regex@9.2.2: {} enhanced-resolve@5.22.0: @@ -2779,8 +2787,6 @@ snapshots: esutils@2.0.3: {} - fancy-canvas@2.1.0: {} - fast-deep-equal@3.1.3: {} fast-glob@3.3.1: @@ -3141,10 +3147,6 @@ snapshots: lightningcss-win32-arm64-msvc: 1.29.2 lightningcss-win32-x64-msvc: 1.29.2 - lightweight-charts@4.2.2: - dependencies: - fancy-canvas: 2.1.0 - locate-path@6.0.0: dependencies: p-locate: 5.0.0 @@ -3584,6 +3586,8 @@ snapshots: minimist: 1.2.8 strip-bom: 3.0.0 + tslib@2.3.0: {} + tslib@2.8.1: {} type-check@0.4.0: @@ -3713,3 +3717,7 @@ snapshots: word-wrap@1.2.5: {} yocto-queue@0.1.0: {} + + zrender@6.1.0: + dependencies: + tslib: 2.3.0 From 9b374d5e6464b1d143c0d7cd3f0cd8bfc2777f27 Mon Sep 17 00:00:00 2001 From: obchain Date: Tue, 26 May 2026 18:05:42 +0530 Subject: [PATCH 2/4] feat(frontend): dark/light theme + landing redesign MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CSS variable palette per [data-theme="dark|light"] (background, surface, border, up/down, soft/muted text). Tailwind v4 @theme inline binds them so utilities like bg-surface, text-muted, text-up resolve via the active theme. - ThemeProvider holds the React state, syncs to localStorage and writes data-theme on ; an inline pre-paint script in reads the stored / system value before hydration so the page never flashes the wrong palette. - ThemeToggle button sits in the corner of both landing and chart pages. - Landing: gradient hero, badge above title, link-card per index that routes to /chart/{id} on click with a "chart →" hover affordance. - Sparkline strokes via var(--up) / var(--down) so it flips with the theme automatically. --- frontend/app/docs/page.tsx | 2 +- frontend/app/globals.css | 69 +++++++++++++++++++++++++++- frontend/app/layout.tsx | 10 ++++- frontend/app/methodology/page.tsx | 2 +- frontend/app/page.tsx | 2 +- frontend/components/Dashboard.tsx | 27 ++++++----- frontend/components/Footer.tsx | 6 +-- frontend/components/IndexCard.tsx | 43 ++++++++++++------ frontend/components/Sparkline.tsx | 2 +- frontend/components/ThemeToggle.tsx | 56 +++++++++++++++++++++++ frontend/lib/theme.tsx | 70 +++++++++++++++++++++++++++++ 11 files changed, 254 insertions(+), 35 deletions(-) create mode 100644 frontend/components/ThemeToggle.tsx create mode 100644 frontend/lib/theme.tsx diff --git a/frontend/app/docs/page.tsx b/frontend/app/docs/page.tsx index 3e9a978..7a86690 100644 --- a/frontend/app/docs/page.tsx +++ b/frontend/app/docs/page.tsx @@ -1,7 +1,7 @@ export default function DocsPage() { return (
-

api docs — placeholder

+

api docs — placeholder

); } diff --git a/frontend/app/globals.css b/frontend/app/globals.css index 349718b..5588bd2 100644 --- a/frontend/app/globals.css +++ b/frontend/app/globals.css @@ -1,18 +1,78 @@ @import "tailwindcss"; -:root { - --background: #0a0a0a; +@custom-variant dark (&:where([data-theme="dark"], [data-theme="dark"] *)); +@custom-variant light (&:where([data-theme="light"], [data-theme="light"] *)); + +:root, +[data-theme="dark"] { + --background: #07070a; --foreground: #ededed; + --surface: rgba(255, 255, 255, 0.02); + --surface-2: rgba(255, 255, 255, 0.04); + --border-subtle: rgba(255, 255, 255, 0.06); + --border: rgba(255, 255, 255, 0.1); + --border-strong: rgba(255, 255, 255, 0.18); + --muted: rgba(237, 237, 237, 0.55); + --soft: rgba(237, 237, 237, 0.35); + --soft-2: rgba(237, 237, 237, 0.22); + --up: #4ade80; + --down: #f87171; + --up-soft: rgba(74, 222, 128, 0.12); + --down-soft: rgba(248, 113, 113, 0.12); + --bg-radial-1: rgba(74, 222, 128, 0.06); + --bg-radial-2: rgba(248, 113, 113, 0.05); + --tooltip-bg: rgba(20, 20, 20, 0.95); + --grid: rgba(255, 255, 255, 0.04); +} + +[data-theme="light"] { + --background: #fafafa; + --foreground: #0a0a0c; + --surface: rgba(10, 10, 12, 0.03); + --surface-2: rgba(10, 10, 12, 0.05); + --border-subtle: rgba(10, 10, 12, 0.06); + --border: rgba(10, 10, 12, 0.1); + --border-strong: rgba(10, 10, 12, 0.2); + --muted: rgba(10, 10, 12, 0.65); + --soft: rgba(10, 10, 12, 0.45); + --soft-2: rgba(10, 10, 12, 0.28); + --up: #16a34a; + --down: #dc2626; + --up-soft: rgba(22, 163, 74, 0.1); + --down-soft: rgba(220, 38, 38, 0.1); + --bg-radial-1: rgba(22, 163, 74, 0.05); + --bg-radial-2: rgba(220, 38, 38, 0.04); + --tooltip-bg: rgba(252, 252, 252, 0.98); + --grid: rgba(10, 10, 12, 0.07); } @theme inline { --color-background: var(--background); --color-foreground: var(--foreground); + --color-surface: var(--surface); + --color-surface-2: var(--surface-2); + --color-border-subtle: var(--border-subtle); + --color-border: var(--border); + --color-border-strong: var(--border-strong); + --color-muted: var(--muted); + --color-soft: var(--soft); + --color-soft-2: var(--soft-2); + --color-up: var(--up); + --color-down: var(--down); + --color-up-soft: var(--up-soft); + --color-down-soft: var(--down-soft); } +html, body { background: var(--background); color: var(--foreground); +} + +body { + background-image: radial-gradient(at 20% 0%, var(--bg-radial-1), transparent 50%), + radial-gradient(at 80% 0%, var(--bg-radial-2), transparent 50%); + background-attachment: fixed; font-family: ui-sans-serif, system-ui, @@ -21,4 +81,9 @@ body { Roboto, sans-serif; -webkit-font-smoothing: antialiased; + color-scheme: dark; +} + +[data-theme="light"] body { + color-scheme: light; } diff --git a/frontend/app/layout.tsx b/frontend/app/layout.tsx index 1276c0d..019a6c8 100644 --- a/frontend/app/layout.tsx +++ b/frontend/app/layout.tsx @@ -1,4 +1,5 @@ import type { Metadata } from "next"; +import { ThemeProvider, themeInitScript } from "@/lib/theme"; import "./globals.css"; export const metadata: Metadata = { @@ -12,8 +13,13 @@ export default function RootLayout({ children: React.ReactNode; }>) { return ( - - {children} + + +