diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/LICENSE b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/LICENSE
new file mode 100644
index 0000000..db9ad82
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2026 uixmat
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/README.md b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/README.md
new file mode 100644
index 0000000..066a873
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/README.md
@@ -0,0 +1,192 @@
+
+
+
+
+
+
+
+
+
+ A collection of Open Source charts and utility components that you can customize and extend.
+
+
+
+
+
+
+
+
+
+
+
+## Premium Sponsors
+
+Bklit UI is supported by our premium sponsors.
+
+
+
+
+
+
+**[OpenPanel](https://openpanel.dev)** — OpenPanel is an open source analytics platform that combines web analytics and product analytics in one privacy-first tool. Track pageviews, events, funnels, retention, and user journeys — all without cookies.
+
+## Charts
+
+
+
+## Studio
+
+Studio is Bklit's interactive chart playground. Pick a chart, tweak styling and animation controls in real time, then copy the generated React code or export a registry JSON for your project.
+
+
+
+
+
+[Open Studio →](https://ui.bklit.com/studio)
+
+## Getting Started
+
+Bklit UI is a shadcn registry. Initialize shadcn in your project, then add a chart:
+
+```bash
+npx shadcn@latest init
+npx shadcn@latest add @bklit/line-chart
+```
+
+See [Installation](https://ui.bklit.com/docs/installation) for registry setup and all components.
+
+## Repo activity
+
+
+
+## License
+
+**Chart components** (`packages/ui`, shadcn registry) are [MIT licensed](./LICENSE).
+
+**Bklit Studio** (`packages/studio`, hosted at [ui.bklit.com/studio](https://ui.bklit.com/studio))
+is proprietary. See [LICENSE-STUDIO.md](./LICENSE-STUDIO.md). You may not reuse,
+resell, or redistribute Studio without written permission.
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/UPSTREAM.md b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/UPSTREAM.md
new file mode 100644
index 0000000..d6ad130
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/UPSTREAM.md
@@ -0,0 +1,35 @@
+# Bklit UI Vendor Snapshot
+
+Source: https://github.com/bklit/bklit-ui
+Commit: 86149e76b509b661aca26af95461b18952eda646
+Snapshot date: 2026-06-28
+
+Included:
+
+- `LICENSE`
+- `README.md`
+- `packages/ui`
+- `apps/web/public/svgs`
+
+Excluded:
+
+- `.git`
+- `node_modules`
+- `apps/studio`
+- `packages/studio`
+
+License notes:
+
+- The root Bklit UI repository is MIT licensed.
+- Bklit Studio has a separate restrictive license and is intentionally not vendored here.
+- Treat this directory as a local source/reference snapshot. Do not import it into the runtime directly until the selected chart components and their dependencies are copied into the app layer intentionally.
+
+Candidate SEO module uses:
+
+- `funnel-chart`: pipeline/readiness from context review to market demand, SEO plan, rewrite, validation.
+- `bar-chart`: top clusters, pages, intents, and demand buckets.
+- `scatter-chart`: opportunity map by demand, relevance, confidence, and implementation effort.
+- `sankey-chart`: context/intent to keyword/page/field flow for dev-mode explainability.
+- `gauge-chart` or `ring-chart`: compact quality/readiness scores.
+- `area-chart`, `line-chart`, `composed-chart`: historical demand, forecast, and validation trend screens after time-series data exists.
+- `heatmap-chart`: keyword/page coverage matrix if the table becomes too dense.
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/apps/web/public/svgs/area-chart.svg b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/apps/web/public/svgs/area-chart.svg
new file mode 100644
index 0000000..82646ea
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/apps/web/public/svgs/area-chart.svg
@@ -0,0 +1,44 @@
+Jan 1
Jan 3
Jan 6
Jan 9
Jan 12
\ No newline at end of file
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/apps/web/public/svgs/bar-chart.svg b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/apps/web/public/svgs/bar-chart.svg
new file mode 100644
index 0000000..8f86808
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/apps/web/public/svgs/bar-chart.svg
@@ -0,0 +1,44 @@
+Jan
Feb
Mar
Apr
May
Jun
Jul
Aug
Sep
Oct
Nov
Dec
\ No newline at end of file
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/apps/web/public/svgs/candlestick-chart.svg b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/apps/web/public/svgs/candlestick-chart.svg
new file mode 100644
index 0000000..64c7264
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/apps/web/public/svgs/candlestick-chart.svg
@@ -0,0 +1,44 @@
+Jan 1
Jan 8
Jan 15
Jan 22
Jan 30
\ No newline at end of file
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/apps/web/public/svgs/choropleth-chart.svg b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/apps/web/public/svgs/choropleth-chart.svg
new file mode 100644
index 0000000..2ba32d9
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/apps/web/public/svgs/choropleth-chart.svg
@@ -0,0 +1,23 @@
+
\ No newline at end of file
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/apps/web/public/svgs/composed-chart.svg b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/apps/web/public/svgs/composed-chart.svg
new file mode 100644
index 0000000..49aa48e
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/apps/web/public/svgs/composed-chart.svg
@@ -0,0 +1,44 @@
+Jan 1
Jan 5
Jan 10
Jan 15
Jan 20
\ No newline at end of file
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/apps/web/public/svgs/funnel-chart.svg b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/apps/web/public/svgs/funnel-chart.svg
new file mode 100644
index 0000000..e660491
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/apps/web/public/svgs/funnel-chart.svg
@@ -0,0 +1,44 @@
+
\ No newline at end of file
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/apps/web/public/svgs/gauge-chart.svg b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/apps/web/public/svgs/gauge-chart.svg
new file mode 100644
index 0000000..48e2c2e
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/apps/web/public/svgs/gauge-chart.svg
@@ -0,0 +1,44 @@
+
\ No newline at end of file
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/apps/web/public/svgs/line-chart.svg b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/apps/web/public/svgs/line-chart.svg
new file mode 100644
index 0000000..115c257
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/apps/web/public/svgs/line-chart.svg
@@ -0,0 +1,44 @@
+Jan 1
Jan 3
Jan 6
Jan 9
Jan 12
\ No newline at end of file
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/apps/web/public/svgs/live-line-chart.svg b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/apps/web/public/svgs/live-line-chart.svg
new file mode 100644
index 0000000..907fac9
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/apps/web/public/svgs/live-line-chart.svg
@@ -0,0 +1,44 @@
+12:18:52
12:19:00
12:19:07
12:19:15
12:19:22
87.50
90.00
92.50
95.00
97.50
100.00
102.50
\ No newline at end of file
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/apps/web/public/svgs/pie-chart.svg b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/apps/web/public/svgs/pie-chart.svg
new file mode 100644
index 0000000..a0900ad
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/apps/web/public/svgs/pie-chart.svg
@@ -0,0 +1,44 @@
+
\ No newline at end of file
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/apps/web/public/svgs/radar-chart.svg b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/apps/web/public/svgs/radar-chart.svg
new file mode 100644
index 0000000..8495524
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/apps/web/public/svgs/radar-chart.svg
@@ -0,0 +1,23 @@
+20 40 60 80 100 Speed Reliability Comfort Efficiency Safety
\ No newline at end of file
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/apps/web/public/svgs/ring-chart.svg b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/apps/web/public/svgs/ring-chart.svg
new file mode 100644
index 0000000..164fcac
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/apps/web/public/svgs/ring-chart.svg
@@ -0,0 +1,44 @@
+
\ No newline at end of file
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/apps/web/public/svgs/sankey-chart.svg b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/apps/web/public/svgs/sankey-chart.svg
new file mode 100644
index 0000000..5c32eb4
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/apps/web/public/svgs/sankey-chart.svg
@@ -0,0 +1,23 @@
+A 60 sessions B 65 sessions X 70 sessions Y 55 sessions Z 125 sessions
\ No newline at end of file
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/apps/web/public/svgs/scatter-chart.svg b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/apps/web/public/svgs/scatter-chart.svg
new file mode 100644
index 0000000..5208203
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/apps/web/public/svgs/scatter-chart.svg
@@ -0,0 +1,44 @@
+Jan 1
Jan 8
Jan 15
Jan 22
Jan 30
\ No newline at end of file
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/typescript-config/react-library.json b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/typescript-config/react-library.json
new file mode 100644
index 0000000..c90a0f3
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/typescript-config/react-library.json
@@ -0,0 +1,17 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "useDefineForClassFields": true,
+ "module": "ESNext",
+ "moduleResolution": "Bundler",
+ "jsx": "react-jsx",
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
+ "strict": true,
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "allowSyntheticDefaultImports": true,
+ "esModuleInterop": true
+ }
+}
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/eslint.config.mjs b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/eslint.config.mjs
new file mode 100644
index 0000000..0a63d82
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/eslint.config.mjs
@@ -0,0 +1,2 @@
+// biome-ignore lint/performance/noBarrelFile: ESLint config re-export
+export { config as default } from "@bklitui/eslint-config/react-internal";
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/package.json b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/package.json
new file mode 100644
index 0000000..b86f0ea
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/package.json
@@ -0,0 +1,81 @@
+{
+ "name": "@bklitui/ui",
+ "version": "0.0.0",
+ "license": "MIT",
+ "private": true,
+ "sideEffects": false,
+ "exports": {
+ "./charts": "./src/charts/index.ts",
+ "./lib/utils": "./src/lib/utils.ts",
+ "./components/shimmering-text": "./src/components/shimmering-text.tsx",
+ "./button": "./src/button.tsx",
+ "./card": "./src/card.tsx",
+ "./code": "./src/code.tsx",
+ "./chart-grid": "./src/chart-grid.tsx",
+ "./chart-marker": "./src/chart-marker.tsx",
+ "./line-chart": "./src/line-chart.tsx",
+ "./tooltip": "./src/tooltip.tsx",
+ "./*": "./src/*.tsx"
+ },
+ "scripts": {
+ "lint": "eslint . --max-warnings 0",
+ "check-types": "tsc --noEmit",
+ "test": "node scripts/run-tests.mjs",
+ "registry:build": "node scripts/verify-registry-files.mjs && node scripts/verify-registry-deps.mjs && node scripts/generate-v0-examples.mjs && node scripts/generate-block-registry.mjs && shadcn build --output ../../apps/web/public/r && cp registry.json ../../apps/web/public/r/registry.json",
+ "registry:verify": "node scripts/verify-registry-files.mjs && node scripts/verify-registry-deps.mjs && node scripts/verify-registry-sync.mjs"
+ },
+ "devDependencies": {
+ "@bklitui/eslint-config": "workspace:*",
+ "@bklitui/typescript-config": "workspace:*",
+ "@types/d3-array": "^3.2.1",
+ "@types/d3-geo": "^3.1.0",
+ "@types/d3-sankey": "^0.12.5",
+ "@types/d3-scale": "^4.0.9",
+ "@types/d3-shape": "^3.1.6",
+ "@types/geojson": "^7946.0.16",
+ "@types/node": "^22.15.3",
+ "@types/react": "19.2.2",
+ "@types/react-dom": "19.2.2",
+ "@types/topojson-client": "^3.1.5",
+ "@types/topojson-specification": "^1.0.5",
+ "eslint": "^9.39.1",
+ "shadcn": "^2.5.0",
+ "tailwindcss": "^4.1.8",
+ "tsx": "^4.19.4",
+ "typescript": "5.9.2"
+ },
+ "peerDependencies": {
+ "react": "^18.0.0 || ^19.0.0",
+ "react-dom": "^18.0.0 || ^19.0.0"
+ },
+ "dependencies": {
+ "@bklitui/icons": "workspace:*",
+ "@base-ui/react": "^1.0.0-alpha.8",
+ "@number-flow/react": "^0.5.4",
+ "@visx/brush": "3.12.0",
+ "@visx/curve": "4.0.1-alpha.0",
+ "@visx/event": "4.0.1-alpha.0",
+ "@visx/geo": "4.0.1-alpha.0",
+ "@visx/gradient": "4.0.1-alpha.0",
+ "@visx/grid": "4.0.1-alpha.0",
+ "@visx/group": "4.0.1-alpha.0",
+ "@visx/heatmap": "4.0.1-alpha.0",
+ "@visx/pattern": "4.0.1-alpha.0",
+ "@visx/responsive": "4.0.1-alpha.0",
+ "@visx/sankey": "4.0.1-alpha.0",
+ "@visx/scale": "4.0.1-alpha.0",
+ "@visx/shape": "4.0.1-alpha.0",
+ "@visx/zoom": "4.0.1-alpha.0",
+ "clsx": "^2.1.1",
+ "d3-array": "3.2.4",
+ "d3-geo": "3.1.0",
+ "d3-scale": "4.0.2",
+ "d3-shape": "^3.2.0",
+ "motion": "^12.27.0",
+ "react": "^19.2.0",
+ "react-dom": "^19.2.0",
+ "react-use-measure": "^2.1.7",
+ "tailwind-merge": "^2.6.0",
+ "topojson-client": "3.1.0"
+ }
+}
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry.json b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry.json
new file mode 100644
index 0000000..ceb52d1
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry.json
@@ -0,0 +1,2307 @@
+{
+ "$schema": "https://ui.shadcn.com/schema/registry.json",
+ "name": "bklit-ui",
+ "homepage": "https://ui.bklit.com",
+ "items": [
+ {
+ "name": "utils",
+ "type": "registry:lib",
+ "title": "Utilities",
+ "description": "Utility functions for className merging with clsx and tailwind-merge",
+ "dependencies": [
+ "clsx",
+ "tailwind-merge"
+ ],
+ "files": [
+ {
+ "path": "src/lib/utils.ts",
+ "type": "registry:lib",
+ "target": "lib/utils.ts"
+ }
+ ]
+ },
+ {
+ "name": "shimmering-text",
+ "type": "registry:component",
+ "title": "Shimmering Text",
+ "description": "Per-character shimmer animation for loading labels and badges",
+ "registryDependencies": [
+ "@bklit/utils"
+ ],
+ "dependencies": [
+ "motion"
+ ],
+ "files": [
+ {
+ "path": "src/components/shimmering-text.tsx",
+ "type": "registry:component",
+ "target": "components/shimmering-text.tsx"
+ }
+ ]
+ },
+ {
+ "name": "chart-utils",
+ "type": "registry:lib",
+ "title": "Chart Utilities",
+ "description": "Shared Intl formatters, time-series decimation, and RAF tooltip scheduling for Bklit charts",
+ "files": [
+ {
+ "path": "src/charts/chart-formatters.ts",
+ "type": "registry:lib",
+ "target": "components/charts/chart-formatters.ts"
+ },
+ {
+ "path": "src/charts/decimate-time-series.ts",
+ "type": "registry:lib",
+ "target": "components/charts/decimate-time-series.ts"
+ },
+ {
+ "path": "src/charts/use-scheduled-tooltip.ts",
+ "type": "registry:lib",
+ "target": "components/charts/use-scheduled-tooltip.ts"
+ }
+ ]
+ },
+ {
+ "name": "chart-context",
+ "type": "registry:component",
+ "title": "Chart Context",
+ "description": "Shared context and hooks for Bklit chart components",
+ "registryDependencies": [
+ "@bklit/utils",
+ "@bklit/chart-utils"
+ ],
+ "dependencies": [
+ "@visx/event@4.0.1-alpha.0",
+ "@visx/responsive@4.0.1-alpha.0",
+ "@visx/scale@4.0.1-alpha.0",
+ "d3-array",
+ "motion"
+ ],
+ "cssVars": {
+ "light": {
+ "--chart-1": "oklch(0.32 0 none)",
+ "--chart-2": "oklch(0.41 0 none)",
+ "--chart-3": "oklch(0.54 0 none)",
+ "--chart-4": "oklch(0.71 0 none)",
+ "--chart-5": "oklch(0.89 0 none)",
+ "--chart-background": "oklch(1 0 0)",
+ "--chart-foreground": "oklch(0.145 0.004 285)",
+ "--chart-foreground-muted": "oklch(0.55 0.014 260)",
+ "--chart-line-primary": "var(--chart-1)",
+ "--chart-line-secondary": "var(--chart-2)",
+ "--chart-crosshair": "oklch(0.4 0.1828 274.34)",
+ "--chart-grid": "oklch(0.9 0 0)",
+ "--chart-brush-border": "var(--chart-grid)",
+ "--chart-tooltip-background": "oklch(0.21 0.006 285 / 0.8)",
+ "--chart-tooltip-foreground": "oklch(0.985 0 0)",
+ "--chart-tooltip-muted": "oklch(0.65 0.01 260)",
+ "--chart-marker-background": "oklch(0.97 0.005 260)",
+ "--chart-marker-border": "oklch(0.85 0.01 260)",
+ "--chart-marker-foreground": "oklch(0.3 0.01 260)",
+ "--chart-label": "oklch(0.45 0.01 260)",
+ "--chart-scale-01": "oklch(0.98 0.003 106)",
+ "--chart-scale-02": "oklch(0.92 0.008 106)",
+ "--chart-scale-03": "oklch(0.82 0.015 106)",
+ "--chart-scale-04": "oklch(0.68 0.02 106)",
+ "--chart-scale-05": "oklch(0.55 0.025 106)",
+ "--chart-scale-pattern-color": "oklch(0.96 0.005 106)"
+ },
+ "dark": {
+ "--chart-1": "oklch(1 0 none)",
+ "--chart-2": "oklch(0.73 0 none)",
+ "--chart-3": "oklch(0.51 0 none)",
+ "--chart-4": "oklch(0.39 0 none)",
+ "--chart-5": "oklch(0.32 0 none)",
+ "--chart-background": "oklch(0.145 0 0)",
+ "--chart-foreground": "oklch(0.45 0 0)",
+ "--chart-foreground-muted": "oklch(0.65 0.01 260)",
+ "--chart-crosshair": "oklch(0.45 0 0)",
+ "--chart-grid": "oklch(0.25 0 0)",
+ "--chart-brush-border": "var(--chart-grid)",
+ "--chart-marker-background": "oklch(0.25 0.01 260)",
+ "--chart-marker-border": "oklch(0.4 0.01 260)",
+ "--chart-marker-foreground": "oklch(0.9 0 0)",
+ "--chart-label": "oklch(0.75 0.01 260)",
+ "--chart-scale-01": "oklch(0.28 0.03 270)",
+ "--chart-scale-02": "oklch(0.38 0.04 270)",
+ "--chart-scale-03": "oklch(0.48 0.05 270)",
+ "--chart-scale-04": "oklch(0.58 0.06 270)",
+ "--chart-scale-05": "oklch(0.68 0.07 270)",
+ "--chart-scale-pattern-color": "oklch(0.145 0 0)"
+ }
+ },
+ "files": [
+ {
+ "path": "src/charts/chart-context.tsx",
+ "type": "registry:component",
+ "target": "components/charts/chart-context.tsx"
+ },
+ {
+ "path": "src/charts/reference-area-config.ts",
+ "type": "registry:lib",
+ "target": "components/charts/reference-area-config.ts"
+ },
+ {
+ "path": "src/charts/use-chart-interaction.ts",
+ "type": "registry:component",
+ "target": "components/charts/use-chart-interaction.ts"
+ },
+ {
+ "path": "src/charts/y-axis-scales.ts",
+ "type": "registry:lib",
+ "target": "components/charts/y-axis-scales.ts"
+ },
+ {
+ "path": "src/charts/y-axis-ticks.ts",
+ "type": "registry:lib",
+ "target": "components/charts/y-axis-ticks.ts"
+ },
+ {
+ "path": "src/charts/chart-phase.ts",
+ "type": "registry:component",
+ "target": "components/charts/chart-phase.ts"
+ },
+ {
+ "path": "src/charts/y-domain-utils.ts",
+ "type": "registry:lib",
+ "target": "components/charts/y-domain-utils.ts"
+ },
+ {
+ "path": "src/charts/filter-data-by-x-domain.ts",
+ "type": "registry:lib",
+ "target": "components/charts/filter-data-by-x-domain.ts"
+ },
+ {
+ "path": "src/charts/generate-chart-skeleton-data.ts",
+ "type": "registry:lib",
+ "target": "components/charts/generate-chart-skeleton-data.ts"
+ },
+ {
+ "path": "src/charts/use-animated-y-domains.ts",
+ "type": "registry:component",
+ "target": "components/charts/use-animated-y-domains.ts"
+ },
+ {
+ "path": "src/charts/use-chart-phase-orchestrator.ts",
+ "type": "registry:component",
+ "target": "components/charts/use-chart-phase-orchestrator.ts"
+ },
+ {
+ "path": "src/charts/line-loading-timing.ts",
+ "type": "registry:lib",
+ "target": "components/charts/line-loading-timing.ts"
+ }
+ ]
+ },
+ {
+ "name": "chart-animation",
+ "type": "registry:component",
+ "title": "Chart Animation",
+ "description": "Shared motion helpers for chart enter transitions, clip reveals, and staggered animations",
+ "dependencies": [
+ "motion"
+ ],
+ "files": [
+ {
+ "path": "src/charts/animation.ts",
+ "type": "registry:component",
+ "target": "components/charts/animation.ts"
+ },
+ {
+ "path": "src/charts/motion-utils.ts",
+ "type": "registry:component",
+ "target": "components/charts/motion-utils.ts"
+ },
+ {
+ "path": "src/charts/use-mount-progress.ts",
+ "type": "registry:component",
+ "target": "components/charts/use-mount-progress.ts"
+ },
+ {
+ "path": "src/charts/use-enter-complete.ts",
+ "type": "registry:component",
+ "target": "components/charts/use-enter-complete.ts"
+ },
+ {
+ "path": "src/charts/chart-reveal-clip.tsx",
+ "type": "registry:component",
+ "target": "components/charts/chart-reveal-clip.tsx"
+ },
+ {
+ "path": "src/charts/static-chart-preview-context.tsx",
+ "type": "registry:component",
+ "target": "components/charts/static-chart-preview-context.tsx"
+ },
+ {
+ "path": "src/charts/chart-defs.ts",
+ "type": "registry:component",
+ "target": "components/charts/chart-defs.ts"
+ }
+ ]
+ },
+ {
+ "name": "chart-series",
+ "type": "registry:component",
+ "title": "Chart Series Layer",
+ "description": "Shared line and area series rendering helpers (paths, markers, highlights, dash tails)",
+ "registryDependencies": [
+ "@bklit/chart-context",
+ "@bklit/chart-animation",
+ "@bklit/chart-tooltip"
+ ],
+ "dependencies": [
+ "@visx/shape@4.0.1-alpha.0",
+ "motion"
+ ],
+ "files": [
+ {
+ "path": "src/charts/path-stroke-utils.ts",
+ "type": "registry:component",
+ "target": "components/charts/path-stroke-utils.ts"
+ },
+ {
+ "path": "src/charts/highlight-segment-bounds.ts",
+ "type": "registry:component",
+ "target": "components/charts/highlight-segment-bounds.ts"
+ },
+ {
+ "path": "src/charts/highlight-segment.tsx",
+ "type": "registry:component",
+ "target": "components/charts/highlight-segment.tsx"
+ },
+ {
+ "path": "src/charts/dash-tail-stroke.tsx",
+ "type": "registry:component",
+ "target": "components/charts/dash-tail-stroke.tsx"
+ },
+ {
+ "path": "src/charts/series-dash-tail-overlay.tsx",
+ "type": "registry:component",
+ "target": "components/charts/series-dash-tail-overlay.tsx"
+ },
+ {
+ "path": "src/charts/series-highlight-layer.tsx",
+ "type": "registry:component",
+ "target": "components/charts/series-highlight-layer.tsx"
+ },
+ {
+ "path": "src/charts/chart-legend-hover.tsx",
+ "type": "registry:component",
+ "target": "components/charts/chart-legend-hover.tsx"
+ },
+ {
+ "path": "src/charts/series-hover-dim.tsx",
+ "type": "registry:component",
+ "target": "components/charts/series-hover-dim.tsx"
+ },
+ {
+ "path": "src/charts/series-point-marker.tsx",
+ "type": "registry:component",
+ "target": "components/charts/series-point-marker.tsx"
+ },
+ {
+ "path": "src/charts/series-markers.tsx",
+ "type": "registry:component",
+ "target": "components/charts/series-markers.tsx"
+ },
+ {
+ "path": "src/charts/use-highlight-segment.ts",
+ "type": "registry:component",
+ "target": "components/charts/use-highlight-segment.ts"
+ },
+ {
+ "path": "src/charts/area-gradient-defs.tsx",
+ "type": "registry:component",
+ "target": "components/charts/area-gradient-defs.tsx"
+ },
+ {
+ "path": "src/charts/fade-edges.ts",
+ "type": "registry:component",
+ "target": "components/charts/fade-edges.ts"
+ }
+ ]
+ },
+ {
+ "name": "grid",
+ "type": "registry:component",
+ "title": "Chart Grid",
+ "description": "Grid lines for charts",
+ "registryDependencies": [
+ "@bklit/chart-context"
+ ],
+ "dependencies": [
+ "@visx/grid@4.0.1-alpha.0"
+ ],
+ "files": [
+ {
+ "path": "src/charts/grid.tsx",
+ "type": "registry:component",
+ "target": "components/charts/grid.tsx"
+ },
+ {
+ "path": "src/charts/use-grid-shimmer.ts",
+ "type": "registry:component",
+ "target": "components/charts/use-grid-shimmer.ts"
+ }
+ ]
+ },
+ {
+ "name": "reference-area",
+ "type": "registry:component",
+ "title": "Reference Area",
+ "description": "Shaded data-coordinate band for target ranges and thresholds on time-series charts",
+ "registryDependencies": [
+ "@bklit/chart-context",
+ "@bklit/background"
+ ],
+ "dependencies": [
+ "motion"
+ ],
+ "files": [
+ {
+ "path": "src/charts/reference-area.tsx",
+ "type": "registry:component",
+ "target": "components/charts/reference-area.tsx"
+ },
+ {
+ "path": "src/charts/reference-area-geometry.ts",
+ "type": "registry:component",
+ "target": "components/charts/reference-area-geometry.ts"
+ },
+ {
+ "path": "src/charts/reference-area-registration-context.tsx",
+ "type": "registry:component",
+ "target": "components/charts/reference-area-registration-context.tsx"
+ }
+ ]
+ },
+ {
+ "name": "projection-line",
+ "type": "registry:component",
+ "title": "Projection Line",
+ "description": "Forecast segment extending a line series past the last data point",
+ "registryDependencies": [
+ "@bklit/chart-context",
+ "@bklit/line-chart"
+ ],
+ "dependencies": [
+ "@visx/shape@4.0.1-alpha.0",
+ "motion"
+ ],
+ "files": [
+ {
+ "path": "src/charts/projection-line.tsx",
+ "type": "registry:component",
+ "target": "components/charts/projection-line.tsx"
+ },
+ {
+ "path": "src/charts/projection-line-end-marker.tsx",
+ "type": "registry:component",
+ "target": "components/charts/projection-line-end-marker.tsx"
+ },
+ {
+ "path": "src/charts/projection-utils.ts",
+ "type": "registry:component",
+ "target": "components/charts/projection-utils.ts"
+ },
+ {
+ "path": "src/charts/line-series-terminal-marker.tsx",
+ "type": "registry:component",
+ "target": "components/charts/line-series-terminal-marker.tsx"
+ }
+ ]
+ },
+ {
+ "name": "background",
+ "type": "registry:component",
+ "title": "Chart Background",
+ "description": "Pattern fill for the plot area when grid lines are hidden",
+ "registryDependencies": [
+ "@bklit/chart-context"
+ ],
+ "dependencies": [
+ "@visx/pattern@4.0.1-alpha.0",
+ "motion"
+ ],
+ "files": [
+ {
+ "path": "src/charts/background.tsx",
+ "type": "registry:component",
+ "target": "components/charts/background.tsx"
+ },
+ {
+ "path": "src/charts/pattern-preset.tsx",
+ "type": "registry:component",
+ "target": "components/charts/pattern-preset.tsx"
+ },
+ {
+ "path": "src/charts/visx-pattern.tsx",
+ "type": "registry:component",
+ "target": "components/charts/visx-pattern.tsx"
+ }
+ ]
+ },
+ {
+ "name": "x-axis",
+ "type": "registry:component",
+ "title": "X Axis",
+ "description": "X-axis component for time-series charts",
+ "registryDependencies": [
+ "@bklit/chart-context",
+ "@bklit/utils"
+ ],
+ "files": [
+ {
+ "path": "src/charts/x-axis.tsx",
+ "type": "registry:component",
+ "target": "components/charts/x-axis.tsx"
+ }
+ ]
+ },
+ {
+ "name": "y-axis",
+ "type": "registry:component",
+ "title": "Y Axis",
+ "description": "Y-axis component for value labels in line and area charts",
+ "registryDependencies": [
+ "@bklit/chart-context",
+ "@bklit/utils"
+ ],
+ "files": [
+ {
+ "path": "src/charts/y-axis.tsx",
+ "type": "registry:component",
+ "target": "components/charts/y-axis.tsx"
+ },
+ {
+ "path": "src/charts/reference-area-geometry.ts",
+ "type": "registry:lib",
+ "target": "components/charts/reference-area-geometry.ts"
+ }
+ ]
+ },
+ {
+ "name": "chart-tooltip",
+ "type": "registry:component",
+ "title": "Chart Tooltip",
+ "description": "Composable tooltip components for charts",
+ "registryDependencies": [
+ "@bklit/chart-context",
+ "@bklit/utils"
+ ],
+ "dependencies": [
+ "@number-flow/react",
+ "motion"
+ ],
+ "files": [
+ {
+ "path": "src/charts/chart-config-context.tsx",
+ "type": "registry:component",
+ "target": "components/charts/chart-config-context.tsx"
+ },
+ {
+ "path": "src/charts/indicator-fade.ts",
+ "type": "registry:lib",
+ "target": "components/charts/indicator-fade.ts"
+ },
+ {
+ "path": "src/charts/tooltip/chart-tooltip.tsx",
+ "type": "registry:component",
+ "target": "components/charts/tooltip/chart-tooltip.tsx"
+ },
+ {
+ "path": "src/charts/tooltip/tooltip-box.tsx",
+ "type": "registry:component",
+ "target": "components/charts/tooltip/tooltip-box.tsx"
+ },
+ {
+ "path": "src/charts/tooltip/tooltip-content.tsx",
+ "type": "registry:component",
+ "target": "components/charts/tooltip/tooltip-content.tsx"
+ },
+ {
+ "path": "src/charts/tooltip/tooltip-dot.tsx",
+ "type": "registry:component",
+ "target": "components/charts/tooltip/tooltip-dot.tsx"
+ },
+ {
+ "path": "src/charts/tooltip/tooltip-indicator.tsx",
+ "type": "registry:component",
+ "target": "components/charts/tooltip/tooltip-indicator.tsx"
+ },
+ {
+ "path": "src/charts/tooltip/date-ticker.tsx",
+ "type": "registry:component",
+ "target": "components/charts/tooltip/date-ticker.tsx"
+ },
+ {
+ "path": "src/charts/tooltip/index.ts",
+ "type": "registry:component",
+ "target": "components/charts/tooltip/index.ts"
+ }
+ ]
+ },
+ {
+ "name": "legend",
+ "type": "registry:component",
+ "title": "Chart Legend",
+ "description": "Composable legend components for charts",
+ "registryDependencies": [
+ "@bklit/utils",
+ "@bklit/chart-utils"
+ ],
+ "dependencies": [
+ "@base-ui/react",
+ "@number-flow/react"
+ ],
+ "cssVars": {
+ "light": {
+ "--legend": "oklch(1 0 0)",
+ "--legend-foreground": "oklch(0.141 0.005 285.823)",
+ "--legend-muted": "oklch(0.967 0.001 286.375)",
+ "--legend-muted-foreground": "oklch(0.552 0.016 285.938)",
+ "--legend-track": "oklch(0.92 0.004 286.32)"
+ },
+ "dark": {
+ "--legend": "oklch(0.21 0.006 285.885)",
+ "--legend-foreground": "oklch(0.985 0 0)",
+ "--legend-muted": "oklch(0.274 0.006 286.033)",
+ "--legend-muted-foreground": "oklch(0.705 0.015 286.067)",
+ "--legend-track": "oklch(0.274 0.006 286.033)"
+ }
+ },
+ "files": [
+ {
+ "path": "src/charts/legend/legend.tsx",
+ "type": "registry:component",
+ "target": "components/charts/legend/legend.tsx"
+ },
+ {
+ "path": "src/charts/legend/legend-context.tsx",
+ "type": "registry:component",
+ "target": "components/charts/legend/legend-context.tsx"
+ },
+ {
+ "path": "src/charts/legend/legend-item.tsx",
+ "type": "registry:component",
+ "target": "components/charts/legend/legend-item.tsx"
+ },
+ {
+ "path": "src/charts/legend/legend-label.tsx",
+ "type": "registry:component",
+ "target": "components/charts/legend/legend-label.tsx"
+ },
+ {
+ "path": "src/charts/legend/legend-marker.tsx",
+ "type": "registry:component",
+ "target": "components/charts/legend/legend-marker.tsx"
+ },
+ {
+ "path": "src/charts/legend/legend-progress.tsx",
+ "type": "registry:component",
+ "target": "components/charts/legend/legend-progress.tsx"
+ },
+ {
+ "path": "src/charts/legend/legend-value.tsx",
+ "type": "registry:component",
+ "target": "components/charts/legend/legend-value.tsx"
+ },
+ {
+ "path": "src/charts/legend/index.ts",
+ "type": "registry:component",
+ "target": "components/charts/legend/index.ts"
+ }
+ ]
+ },
+ {
+ "name": "markers",
+ "type": "registry:component",
+ "title": "Chart Markers",
+ "description": "Marker components for highlighting data points on charts",
+ "registryDependencies": [
+ "@bklit/chart-context",
+ "@bklit/chart-tooltip",
+ "@bklit/utils"
+ ],
+ "files": [
+ {
+ "path": "src/charts/markers/chart-markers.tsx",
+ "type": "registry:component",
+ "target": "components/charts/markers/chart-markers.tsx"
+ },
+ {
+ "path": "src/charts/markers/marker-group.tsx",
+ "type": "registry:component",
+ "target": "components/charts/markers/marker-group.tsx"
+ },
+ {
+ "path": "src/charts/markers/index.ts",
+ "type": "registry:component",
+ "target": "components/charts/markers/index.ts"
+ }
+ ]
+ },
+ {
+ "name": "area-chart",
+ "type": "registry:component",
+ "title": "Area Chart",
+ "description": "A composable area chart with gradient fills and animations",
+ "registryDependencies": [
+ "@bklit/chart-context",
+ "@bklit/chart-animation",
+ "@bklit/chart-series",
+ "@bklit/grid",
+ "@bklit/x-axis",
+ "@bklit/chart-tooltip",
+ "@bklit/shimmering-text",
+ "@bklit/utils"
+ ],
+ "dependencies": [
+ "@visx/curve@4.0.1-alpha.0",
+ "@visx/gradient@4.0.1-alpha.0",
+ "@visx/shape@4.0.1-alpha.0",
+ "motion"
+ ],
+ "files": [
+ {
+ "path": "src/charts/area-chart.tsx",
+ "type": "registry:component",
+ "target": "components/charts/area-chart.tsx"
+ },
+ {
+ "path": "src/charts/time-series-chart-shell.tsx",
+ "type": "registry:component",
+ "target": "components/charts/time-series-chart-shell.tsx"
+ },
+ {
+ "path": "src/charts/reference-area-registration-context.tsx",
+ "type": "registry:component",
+ "target": "components/charts/reference-area-registration-context.tsx"
+ },
+ {
+ "path": "src/charts/projection-config.ts",
+ "type": "registry:component",
+ "target": "components/charts/projection-config.ts"
+ },
+ {
+ "path": "src/charts/projection-utils.ts",
+ "type": "registry:component",
+ "target": "components/charts/projection-utils.ts"
+ },
+ {
+ "path": "src/charts/chart-child-passthrough.ts",
+ "type": "registry:component",
+ "target": "components/charts/chart-child-passthrough.ts"
+ },
+ {
+ "path": "src/charts/series-bar-layout.ts",
+ "type": "registry:component",
+ "target": "components/charts/series-bar-layout.ts"
+ },
+ {
+ "path": "src/charts/area.tsx",
+ "type": "registry:component",
+ "target": "components/charts/area.tsx"
+ },
+ {
+ "path": "src/charts/area-chart-loading.tsx",
+ "type": "registry:component",
+ "target": "components/charts/area-chart-loading.tsx"
+ },
+ {
+ "path": "src/charts/line-loading-pulse.tsx",
+ "type": "registry:component",
+ "target": "components/charts/line-loading-pulse.tsx"
+ },
+ {
+ "path": "src/charts/loading-sweep.tsx",
+ "type": "registry:component",
+ "target": "components/charts/loading-sweep.tsx"
+ },
+ {
+ "path": "src/charts/line-loading-timing.ts",
+ "type": "registry:component",
+ "target": "components/charts/line-loading-timing.ts"
+ },
+ {
+ "path": "src/charts/chart-loading-label.tsx",
+ "type": "registry:component",
+ "target": "components/charts/chart-loading-label.tsx"
+ },
+ {
+ "path": "src/charts/pattern-area.tsx",
+ "type": "registry:component",
+ "target": "components/charts/pattern-area.tsx"
+ }
+ ]
+ },
+ {
+ "name": "composed-chart",
+ "type": "registry:component",
+ "title": "Composed Chart",
+ "description": "Time-series chart composing Line, Area, and SeriesBar on one shared scale (Recharts ComposedChart-style)",
+ "registryDependencies": [
+ "@bklit/chart-context",
+ "@bklit/grid",
+ "@bklit/x-axis",
+ "@bklit/y-axis",
+ "@bklit/chart-tooltip",
+ "@bklit/utils",
+ "@bklit/line-chart",
+ "@bklit/area-chart"
+ ],
+ "dependencies": [
+ "@visx/curve@4.0.1-alpha.0",
+ "@visx/scale@4.0.1-alpha.0",
+ "@visx/shape@4.0.1-alpha.0",
+ "@visx/responsive@4.0.1-alpha.0",
+ "d3-array",
+ "motion"
+ ],
+ "files": [
+ {
+ "path": "src/charts/composed-chart.tsx",
+ "type": "registry:component",
+ "target": "components/charts/composed-chart.tsx"
+ },
+ {
+ "path": "src/charts/series-bar.tsx",
+ "type": "registry:component",
+ "target": "components/charts/series-bar.tsx"
+ },
+ {
+ "path": "src/charts/series-bar-layout.ts",
+ "type": "registry:component",
+ "target": "components/charts/series-bar-layout.ts"
+ },
+ {
+ "path": "src/charts/time-series-chart-shell.tsx",
+ "type": "registry:component",
+ "target": "components/charts/time-series-chart-shell.tsx"
+ },
+ {
+ "path": "src/charts/reference-area-registration-context.tsx",
+ "type": "registry:component",
+ "target": "components/charts/reference-area-registration-context.tsx"
+ },
+ {
+ "path": "src/charts/projection-config.ts",
+ "type": "registry:component",
+ "target": "components/charts/projection-config.ts"
+ },
+ {
+ "path": "src/charts/projection-utils.ts",
+ "type": "registry:component",
+ "target": "components/charts/projection-utils.ts"
+ },
+ {
+ "path": "src/charts/chart-child-passthrough.ts",
+ "type": "registry:component",
+ "target": "components/charts/chart-child-passthrough.ts"
+ }
+ ]
+ },
+ {
+ "name": "bar-chart",
+ "type": "registry:component",
+ "title": "Bar Chart",
+ "description": "A composable bar chart with horizontal/vertical orientations and animations",
+ "registryDependencies": [
+ "@bklit/chart-context",
+ "@bklit/chart-animation",
+ "@bklit/grid",
+ "@bklit/chart-tooltip",
+ "@bklit/utils"
+ ],
+ "dependencies": [
+ "@visx/gradient@4.0.1-alpha.0",
+ "@visx/pattern@4.0.1-alpha.0",
+ "@visx/shape@4.0.1-alpha.0",
+ "motion"
+ ],
+ "files": [
+ {
+ "path": "src/charts/bar-chart.tsx",
+ "type": "registry:component",
+ "target": "components/charts/bar-chart.tsx"
+ },
+ {
+ "path": "src/charts/chart-child-passthrough.ts",
+ "type": "registry:lib",
+ "target": "components/charts/chart-child-passthrough.ts"
+ },
+ {
+ "path": "src/charts/chart-legend-hover.tsx",
+ "type": "registry:component",
+ "target": "components/charts/chart-legend-hover.tsx"
+ },
+ {
+ "path": "src/charts/bar.tsx",
+ "type": "registry:component",
+ "target": "components/charts/bar.tsx"
+ },
+ {
+ "path": "src/charts/bar-depth-geometry.ts",
+ "type": "registry:lib",
+ "target": "components/charts/bar-depth-geometry.ts"
+ },
+ {
+ "path": "src/charts/bar-x-axis.tsx",
+ "type": "registry:component",
+ "target": "components/charts/bar-x-axis.tsx"
+ },
+ {
+ "path": "src/charts/bar-y-axis.tsx",
+ "type": "registry:component",
+ "target": "components/charts/bar-y-axis.tsx"
+ },
+ {
+ "path": "src/charts/loading-sweep.tsx",
+ "type": "registry:component",
+ "target": "components/charts/loading-sweep.tsx"
+ },
+ {
+ "path": "src/charts/bar-chart-loading.tsx",
+ "type": "registry:component",
+ "target": "components/charts/bar-chart-loading.tsx"
+ }
+ ]
+ },
+ {
+ "name": "line-chart",
+ "type": "registry:component",
+ "title": "Line Chart",
+ "description": "A composable line chart with multiple series support",
+ "registryDependencies": [
+ "@bklit/chart-context",
+ "@bklit/chart-animation",
+ "@bklit/chart-series",
+ "@bklit/grid",
+ "@bklit/x-axis",
+ "@bklit/chart-tooltip",
+ "@bklit/shimmering-text",
+ "@bklit/utils"
+ ],
+ "dependencies": [
+ "@visx/curve@4.0.1-alpha.0",
+ "@visx/shape@4.0.1-alpha.0",
+ "motion"
+ ],
+ "files": [
+ {
+ "path": "src/charts/line-chart.tsx",
+ "type": "registry:component",
+ "target": "components/charts/line-chart.tsx"
+ },
+ {
+ "path": "src/charts/time-series-chart-shell.tsx",
+ "type": "registry:component",
+ "target": "components/charts/time-series-chart-shell.tsx"
+ },
+ {
+ "path": "src/charts/reference-area-registration-context.tsx",
+ "type": "registry:component",
+ "target": "components/charts/reference-area-registration-context.tsx"
+ },
+ {
+ "path": "src/charts/projection-config.ts",
+ "type": "registry:component",
+ "target": "components/charts/projection-config.ts"
+ },
+ {
+ "path": "src/charts/projection-utils.ts",
+ "type": "registry:component",
+ "target": "components/charts/projection-utils.ts"
+ },
+ {
+ "path": "src/charts/projection-line.tsx",
+ "type": "registry:component",
+ "target": "components/charts/projection-line.tsx"
+ },
+ {
+ "path": "src/charts/projection-line-end-marker.tsx",
+ "type": "registry:component",
+ "target": "components/charts/projection-line-end-marker.tsx"
+ },
+ {
+ "path": "src/charts/line-series-terminal-marker.tsx",
+ "type": "registry:component",
+ "target": "components/charts/line-series-terminal-marker.tsx"
+ },
+ {
+ "path": "src/charts/chart-child-passthrough.ts",
+ "type": "registry:component",
+ "target": "components/charts/chart-child-passthrough.ts"
+ },
+ {
+ "path": "src/charts/series-bar-layout.ts",
+ "type": "registry:component",
+ "target": "components/charts/series-bar-layout.ts"
+ },
+ {
+ "path": "src/charts/line.tsx",
+ "type": "registry:component",
+ "target": "components/charts/line.tsx"
+ },
+ {
+ "path": "src/charts/line-chart-loading.tsx",
+ "type": "registry:component",
+ "target": "components/charts/line-chart-loading.tsx"
+ },
+ {
+ "path": "src/charts/line-loading-pulse.tsx",
+ "type": "registry:component",
+ "target": "components/charts/line-loading-pulse.tsx"
+ },
+ {
+ "path": "src/charts/loading-sweep.tsx",
+ "type": "registry:component",
+ "target": "components/charts/loading-sweep.tsx"
+ },
+ {
+ "path": "src/charts/line-loading-timing.ts",
+ "type": "registry:component",
+ "target": "components/charts/line-loading-timing.ts"
+ },
+ {
+ "path": "src/charts/chart-loading-label.tsx",
+ "type": "registry:component",
+ "target": "components/charts/chart-loading-label.tsx"
+ }
+ ]
+ },
+ {
+ "name": "profit-loss-line",
+ "type": "registry:component",
+ "title": "Profit/Loss Line",
+ "description": "Sign-colored line segments for profit and loss on LineChart",
+ "registryDependencies": [
+ "@bklit/line-chart",
+ "@bklit/grid",
+ "@bklit/x-axis",
+ "@bklit/chart-tooltip",
+ "@bklit/legend",
+ "@bklit/utils"
+ ],
+ "dependencies": [
+ "@visx/curve@4.0.1-alpha.0",
+ "@visx/shape@4.0.1-alpha.0"
+ ],
+ "files": [
+ {
+ "path": "src/charts/profit-loss-line.tsx",
+ "type": "registry:component",
+ "target": "components/charts/profit-loss-line.tsx"
+ },
+ {
+ "path": "src/charts/profit-loss-segments.ts",
+ "type": "registry:lib",
+ "target": "lib/profit-loss-segments.ts"
+ },
+ {
+ "path": "src/charts/profit-loss-legend-hover.tsx",
+ "type": "registry:component",
+ "target": "components/charts/profit-loss-legend-hover.tsx"
+ },
+ {
+ "path": "src/charts/profit-loss-legend.tsx",
+ "type": "registry:component",
+ "target": "components/charts/profit-loss-legend.tsx"
+ }
+ ]
+ },
+ {
+ "name": "bar-depth",
+ "type": "registry:component",
+ "title": "Bar Depth",
+ "description": "3D depth and glossy glass surfaces for BarChart bars",
+ "registryDependencies": [
+ "@bklit/bar-chart"
+ ],
+ "dependencies": [
+ "motion"
+ ],
+ "files": [
+ {
+ "path": "src/charts/bar-depth.tsx",
+ "type": "registry:component",
+ "target": "components/charts/bar-depth.tsx"
+ }
+ ]
+ },
+ {
+ "name": "scatter-chart",
+ "type": "registry:component",
+ "title": "Scatter Chart",
+ "description": "A composable time-series scatter chart with offset rings, hover dimming, and animated enter",
+ "registryDependencies": [
+ "@bklit/chart-context",
+ "@bklit/chart-animation",
+ "@bklit/chart-series",
+ "@bklit/grid",
+ "@bklit/x-axis",
+ "@bklit/chart-tooltip",
+ "@bklit/utils"
+ ],
+ "dependencies": [
+ "d3-array",
+ "d3-scale",
+ "motion",
+ "react-use-measure"
+ ],
+ "files": [
+ {
+ "path": "src/charts/scatter-chart.tsx",
+ "type": "registry:component",
+ "target": "components/charts/scatter-chart.tsx"
+ },
+ {
+ "path": "src/charts/scatter-chart-shell.tsx",
+ "type": "registry:component",
+ "target": "components/charts/scatter-chart-shell.tsx"
+ },
+ {
+ "path": "src/charts/time-series-chart-shell.tsx",
+ "type": "registry:component",
+ "target": "components/charts/time-series-chart-shell.tsx"
+ },
+ {
+ "path": "src/charts/reference-area-registration-context.tsx",
+ "type": "registry:component",
+ "target": "components/charts/reference-area-registration-context.tsx"
+ },
+ {
+ "path": "src/charts/projection-config.ts",
+ "type": "registry:component",
+ "target": "components/charts/projection-config.ts"
+ },
+ {
+ "path": "src/charts/projection-utils.ts",
+ "type": "registry:component",
+ "target": "components/charts/projection-utils.ts"
+ },
+ {
+ "path": "src/charts/chart-child-passthrough.ts",
+ "type": "registry:component",
+ "target": "components/charts/chart-child-passthrough.ts"
+ },
+ {
+ "path": "src/charts/series-bar-layout.ts",
+ "type": "registry:component",
+ "target": "components/charts/series-bar-layout.ts"
+ },
+ {
+ "path": "src/charts/scatter.tsx",
+ "type": "registry:component",
+ "target": "components/charts/scatter.tsx"
+ },
+ {
+ "path": "src/charts/use-scatter-chart-interaction.ts",
+ "type": "registry:component",
+ "target": "components/charts/use-scatter-chart-interaction.ts"
+ },
+ {
+ "path": "src/charts/scatter-svg.ts",
+ "type": "registry:component",
+ "target": "components/charts/scatter-svg.ts"
+ }
+ ]
+ },
+ {
+ "name": "live-line-chart",
+ "type": "registry:component",
+ "title": "Live Line Chart",
+ "description": "Real-time streaming line chart with smooth scrolling, crosshair, and animated axes",
+ "registryDependencies": [
+ "@bklit/chart-context",
+ "@bklit/chart-tooltip",
+ "@bklit/utils"
+ ],
+ "dependencies": [
+ "@visx/curve@4.0.1-alpha.0",
+ "@visx/scale@4.0.1-alpha.0",
+ "@visx/shape@4.0.1-alpha.0",
+ "@visx/responsive@4.0.1-alpha.0",
+ "@visx/event@4.0.1-alpha.0",
+ "d3-array",
+ "motion"
+ ],
+ "files": [
+ {
+ "path": "src/charts/live-line-chart.tsx",
+ "type": "registry:component",
+ "target": "components/charts/live-line-chart.tsx"
+ },
+ {
+ "path": "src/charts/chart-child-passthrough.ts",
+ "type": "registry:lib",
+ "target": "components/charts/chart-child-passthrough.ts"
+ },
+ {
+ "path": "src/charts/live-line.tsx",
+ "type": "registry:component",
+ "target": "components/charts/live-line.tsx"
+ },
+ {
+ "path": "src/charts/live-x-axis.tsx",
+ "type": "registry:component",
+ "target": "components/charts/live-x-axis.tsx"
+ },
+ {
+ "path": "src/charts/live-y-axis.tsx",
+ "type": "registry:component",
+ "target": "components/charts/live-y-axis.tsx"
+ }
+ ]
+ },
+ {
+ "name": "candlestick-chart",
+ "type": "registry:component",
+ "title": "Candlestick Chart",
+ "description": "A composable OHLC candlestick chart with gradients, patterns, tooltips, and hover interactions",
+ "registryDependencies": [
+ "@bklit/chart-context",
+ "@bklit/chart-animation",
+ "@bklit/grid",
+ "@bklit/x-axis",
+ "@bklit/y-axis",
+ "@bklit/chart-tooltip",
+ "@bklit/utils"
+ ],
+ "dependencies": [
+ "@visx/scale@4.0.1-alpha.0",
+ "@visx/responsive@4.0.1-alpha.0",
+ "d3-array",
+ "motion"
+ ],
+ "files": [
+ {
+ "path": "src/charts/candlestick-chart.tsx",
+ "type": "registry:component",
+ "target": "components/charts/candlestick-chart.tsx"
+ },
+ {
+ "path": "src/charts/chart-child-passthrough.ts",
+ "type": "registry:lib",
+ "target": "components/charts/chart-child-passthrough.ts"
+ },
+ {
+ "path": "src/charts/chart-legend-hover.tsx",
+ "type": "registry:component",
+ "target": "components/charts/chart-legend-hover.tsx"
+ },
+ {
+ "path": "src/charts/candlestick.tsx",
+ "type": "registry:component",
+ "target": "components/charts/candlestick.tsx"
+ }
+ ]
+ },
+ {
+ "name": "pie-chart",
+ "type": "registry:component",
+ "title": "Pie Chart",
+ "description": "A composable pie chart with animations and customizable slices",
+ "registryDependencies": [
+ "@bklit/chart-animation",
+ "@bklit/utils"
+ ],
+ "dependencies": [
+ "@number-flow/react",
+ "@visx/group@4.0.1-alpha.0",
+ "@visx/responsive@4.0.1-alpha.0",
+ "@visx/shape@4.0.1-alpha.0",
+ "d3-shape",
+ "motion"
+ ],
+ "files": [
+ {
+ "path": "src/charts/pie-chart.tsx",
+ "type": "registry:component",
+ "target": "components/charts/pie-chart.tsx"
+ },
+ {
+ "path": "src/charts/pie-context.tsx",
+ "type": "registry:component",
+ "target": "components/charts/pie-context.tsx"
+ },
+ {
+ "path": "src/charts/pie-slice.tsx",
+ "type": "registry:component",
+ "target": "components/charts/pie-slice.tsx"
+ },
+ {
+ "path": "src/charts/chart-stat-flow.tsx",
+ "type": "registry:component",
+ "target": "components/charts/chart-stat-flow.tsx"
+ },
+ {
+ "path": "src/charts/pie-center-shell.tsx",
+ "type": "registry:component",
+ "target": "components/charts/pie-center-shell.tsx"
+ },
+ {
+ "path": "src/charts/pie-center.tsx",
+ "type": "registry:component",
+ "target": "components/charts/pie-center.tsx"
+ },
+ {
+ "path": "src/charts/chart-center-typography.ts",
+ "type": "registry:component",
+ "target": "components/charts/chart-center-typography.ts"
+ }
+ ]
+ },
+ {
+ "name": "radar-chart",
+ "type": "registry:component",
+ "title": "Radar Chart",
+ "description": "A composable radar/spider chart with multiple data series",
+ "registryDependencies": [
+ "@bklit/chart-animation",
+ "@bklit/utils"
+ ],
+ "dependencies": [
+ "@visx/group@4.0.1-alpha.0",
+ "@visx/responsive@4.0.1-alpha.0",
+ "@visx/scale@4.0.1-alpha.0",
+ "@visx/shape@4.0.1-alpha.0",
+ "d3-shape",
+ "motion"
+ ],
+ "files": [
+ {
+ "path": "src/charts/radar-chart.tsx",
+ "type": "registry:component",
+ "target": "components/charts/radar-chart.tsx"
+ },
+ {
+ "path": "src/charts/radar-context.tsx",
+ "type": "registry:component",
+ "target": "components/charts/radar-context.tsx"
+ },
+ {
+ "path": "src/charts/radar-area.tsx",
+ "type": "registry:component",
+ "target": "components/charts/radar-area.tsx"
+ },
+ {
+ "path": "src/charts/radar-axis.tsx",
+ "type": "registry:component",
+ "target": "components/charts/radar-axis.tsx"
+ },
+ {
+ "path": "src/charts/radar-grid.tsx",
+ "type": "registry:component",
+ "target": "components/charts/radar-grid.tsx"
+ },
+ {
+ "path": "src/charts/radar-labels.tsx",
+ "type": "registry:component",
+ "target": "components/charts/radar-labels.tsx"
+ }
+ ]
+ },
+ {
+ "name": "ring-chart",
+ "type": "registry:component",
+ "title": "Ring Chart",
+ "description": "A composable donut/ring chart with progress indicators",
+ "registryDependencies": [
+ "@bklit/chart-animation",
+ "@bklit/utils"
+ ],
+ "dependencies": [
+ "@visx/group@4.0.1-alpha.0",
+ "@visx/responsive@4.0.1-alpha.0",
+ "@visx/shape@4.0.1-alpha.0",
+ "@number-flow/react",
+ "motion"
+ ],
+ "files": [
+ {
+ "path": "src/charts/ring-chart.tsx",
+ "type": "registry:component",
+ "target": "components/charts/ring-chart.tsx"
+ },
+ {
+ "path": "src/charts/ring-context.tsx",
+ "type": "registry:component",
+ "target": "components/charts/ring-context.tsx"
+ },
+ {
+ "path": "src/charts/ring.tsx",
+ "type": "registry:component",
+ "target": "components/charts/ring.tsx"
+ },
+ {
+ "path": "src/charts/chart-stat-flow.tsx",
+ "type": "registry:component",
+ "target": "components/charts/chart-stat-flow.tsx"
+ },
+ {
+ "path": "src/charts/ring-center.tsx",
+ "type": "registry:component",
+ "target": "components/charts/ring-center.tsx"
+ },
+ {
+ "path": "src/charts/chart-center-typography.ts",
+ "type": "registry:component",
+ "target": "components/charts/chart-center-typography.ts"
+ }
+ ]
+ },
+ {
+ "name": "gauge-chart",
+ "type": "registry:component",
+ "title": "Gauge",
+ "description": "Notch-based radial gauge with PieCenter-style center, patterns, and optional arc gradients",
+ "registryDependencies": [
+ "@bklit/utils"
+ ],
+ "dependencies": [
+ "@visx/responsive@4.0.1-alpha.0",
+ "@visx/pattern@4.0.1-alpha.0",
+ "@number-flow/react",
+ "motion",
+ "d3-shape"
+ ],
+ "files": [
+ {
+ "path": "src/charts/chart-stat-flow.tsx",
+ "type": "registry:component",
+ "target": "components/charts/chart-stat-flow.tsx"
+ },
+ {
+ "path": "src/charts/pie-context.tsx",
+ "type": "registry:component",
+ "target": "components/charts/pie-context.tsx"
+ },
+ {
+ "path": "src/charts/pie-center-shell.tsx",
+ "type": "registry:component",
+ "target": "components/charts/pie-center-shell.tsx"
+ },
+ {
+ "path": "src/charts/pie-center.tsx",
+ "type": "registry:component",
+ "target": "components/charts/pie-center.tsx"
+ },
+ {
+ "path": "src/charts/gauge.tsx",
+ "type": "registry:component",
+ "target": "components/charts/gauge.tsx"
+ },
+ {
+ "path": "src/charts/chart-center-typography.ts",
+ "type": "registry:component",
+ "target": "components/charts/chart-center-typography.ts"
+ }
+ ]
+ },
+ {
+ "name": "heatmap-chart",
+ "type": "registry:component",
+ "title": "Heatmap Chart",
+ "description": "A contribution heatmap with animated cells and configurable level colors",
+ "registryDependencies": [
+ "@bklit/chart-context",
+ "@bklit/chart-animation",
+ "@bklit/utils",
+ "@bklit/chart-utils",
+ "@bklit/chart-tooltip",
+ "@bklit/background",
+ "@bklit/shimmering-text"
+ ],
+ "dependencies": [
+ "@visx/group@4.0.1-alpha.0",
+ "@visx/heatmap@4.0.1-alpha.0",
+ "@visx/pattern@4.0.1-alpha.0",
+ "@visx/responsive@4.0.1-alpha.0",
+ "@visx/scale@4.0.1-alpha.0",
+ "motion"
+ ],
+ "files": [
+ {
+ "path": "src/charts/heatmap/heatmap-chart.tsx",
+ "type": "registry:component",
+ "target": "components/charts/heatmap/heatmap-chart.tsx"
+ },
+ {
+ "path": "src/charts/heatmap/heatmap-context.tsx",
+ "type": "registry:component",
+ "target": "components/charts/heatmap/heatmap-context.tsx"
+ },
+ {
+ "path": "src/charts/heatmap/heatmap-cells.tsx",
+ "type": "registry:component",
+ "target": "components/charts/heatmap/heatmap-cells.tsx"
+ },
+ {
+ "path": "src/charts/heatmap/heatmap-x-axis.tsx",
+ "type": "registry:component",
+ "target": "components/charts/heatmap/heatmap-x-axis.tsx"
+ },
+ {
+ "path": "src/charts/heatmap/heatmap-y-axis.tsx",
+ "type": "registry:component",
+ "target": "components/charts/heatmap/heatmap-y-axis.tsx"
+ },
+ {
+ "path": "src/charts/heatmap/heatmap-legend.tsx",
+ "type": "registry:component",
+ "target": "components/charts/heatmap/heatmap-legend.tsx"
+ },
+ {
+ "path": "src/charts/heatmap/heatmap-legend-swatch.tsx",
+ "type": "registry:component",
+ "target": "components/charts/heatmap/heatmap-legend-swatch.tsx"
+ },
+ {
+ "path": "src/charts/heatmap/heatmap-tooltip.tsx",
+ "type": "registry:component",
+ "target": "components/charts/heatmap/heatmap-tooltip.tsx"
+ },
+ {
+ "path": "src/charts/heatmap/heatmap-colors.ts",
+ "type": "registry:component",
+ "target": "components/charts/heatmap/heatmap-colors.ts"
+ },
+ {
+ "path": "src/charts/chart-scale.ts",
+ "type": "registry:component",
+ "target": "components/charts/chart-scale.ts"
+ },
+ {
+ "path": "src/charts/heatmap/heatmap-utils.ts",
+ "type": "registry:component",
+ "target": "components/charts/heatmap/heatmap-utils.ts"
+ },
+ {
+ "path": "src/charts/heatmap/heatmap-animation.ts",
+ "type": "registry:component",
+ "target": "components/charts/heatmap/heatmap-animation.ts"
+ },
+ {
+ "path": "src/charts/heatmap/heatmap-pattern-defs.tsx",
+ "type": "registry:component",
+ "target": "components/charts/heatmap/heatmap-pattern-defs.tsx"
+ },
+ {
+ "path": "src/charts/heatmap/heatmap-chart-loading.tsx",
+ "type": "registry:component",
+ "target": "components/charts/heatmap/heatmap-chart-loading.tsx"
+ },
+ {
+ "path": "src/charts/heatmap/generate-heatmap-skeleton-data.ts",
+ "type": "registry:component",
+ "target": "components/charts/heatmap/generate-heatmap-skeleton-data.ts"
+ },
+ {
+ "path": "src/charts/heatmap/index.ts",
+ "type": "registry:component",
+ "target": "components/charts/heatmap/index.ts"
+ },
+ {
+ "path": "src/charts/chart-loading-label.tsx",
+ "type": "registry:component",
+ "target": "components/charts/chart-loading-label.tsx"
+ }
+ ]
+ },
+ {
+ "name": "choropleth-chart",
+ "type": "registry:component",
+ "title": "Choropleth Map",
+ "description": "A geographic map chart with zoom, pan, and data visualization",
+ "registryDependencies": [
+ "@bklit/chart-animation",
+ "@bklit/utils",
+ "@bklit/chart-utils",
+ "@bklit/chart-tooltip"
+ ],
+ "dependencies": [
+ "@types/geojson",
+ "@visx/geo@4.0.1-alpha.0",
+ "@visx/responsive@4.0.1-alpha.0",
+ "@visx/zoom@4.0.1-alpha.0",
+ "d3-geo",
+ "topojson-client",
+ "motion"
+ ],
+ "files": [
+ {
+ "path": "src/charts/choropleth/choropleth-chart.tsx",
+ "type": "registry:component",
+ "target": "components/charts/choropleth/choropleth-chart.tsx"
+ },
+ {
+ "path": "src/charts/choropleth/choropleth-context.tsx",
+ "type": "registry:component",
+ "target": "components/charts/choropleth/choropleth-context.tsx"
+ },
+ {
+ "path": "src/charts/chart-scale.ts",
+ "type": "registry:component",
+ "target": "components/charts/chart-scale.ts"
+ },
+ {
+ "path": "src/charts/choropleth/choropleth-feature.tsx",
+ "type": "registry:component",
+ "target": "components/charts/choropleth/choropleth-feature.tsx"
+ },
+ {
+ "path": "src/charts/choropleth/choropleth-graticule.tsx",
+ "type": "registry:component",
+ "target": "components/charts/choropleth/choropleth-graticule.tsx"
+ },
+ {
+ "path": "src/charts/choropleth/choropleth-tooltip.tsx",
+ "type": "registry:component",
+ "target": "components/charts/choropleth/choropleth-tooltip.tsx"
+ },
+ {
+ "path": "src/charts/choropleth/index.ts",
+ "type": "registry:component",
+ "target": "components/charts/choropleth/index.ts"
+ }
+ ]
+ },
+ {
+ "name": "funnel-chart",
+ "type": "registry:component",
+ "title": "Funnel Chart",
+ "description": "An animated funnel chart with multi-layer halo rings, hover interactions, and staggered entrance animations",
+ "registryDependencies": [
+ "@bklit/chart-animation",
+ "@bklit/utils",
+ "@bklit/chart-utils"
+ ],
+ "dependencies": [
+ "motion"
+ ],
+ "files": [
+ {
+ "path": "src/charts/funnel-chart.tsx",
+ "type": "registry:component",
+ "target": "components/charts/funnel-chart.tsx"
+ }
+ ]
+ },
+ {
+ "name": "sankey-chart",
+ "type": "registry:component",
+ "title": "Sankey Diagram",
+ "description": "A flow diagram for visualizing data transfers between nodes",
+ "registryDependencies": [
+ "@bklit/chart-animation",
+ "@bklit/utils",
+ "@bklit/chart-utils",
+ "@bklit/chart-tooltip"
+ ],
+ "dependencies": [
+ "@visx/event@4.0.1-alpha.0",
+ "@visx/gradient@4.0.1-alpha.0",
+ "@visx/pattern@4.0.1-alpha.0",
+ "@visx/responsive@4.0.1-alpha.0",
+ "@visx/sankey@4.0.1-alpha.0",
+ "d3-sankey",
+ "motion"
+ ],
+ "files": [
+ {
+ "path": "src/charts/sankey/sankey-chart.tsx",
+ "type": "registry:component",
+ "target": "components/charts/sankey/sankey-chart.tsx"
+ },
+ {
+ "path": "src/charts/sankey/sankey-context.tsx",
+ "type": "registry:component",
+ "target": "components/charts/sankey/sankey-context.tsx"
+ },
+ {
+ "path": "src/charts/sankey/sankey-node.tsx",
+ "type": "registry:component",
+ "target": "components/charts/sankey/sankey-node.tsx"
+ },
+ {
+ "path": "src/charts/sankey/sankey-link.tsx",
+ "type": "registry:component",
+ "target": "components/charts/sankey/sankey-link.tsx"
+ },
+ {
+ "path": "src/charts/sankey/sankey-tooltip.tsx",
+ "type": "registry:component",
+ "target": "components/charts/sankey/sankey-tooltip.tsx"
+ },
+ {
+ "path": "src/charts/sankey/index.ts",
+ "type": "registry:component",
+ "target": "components/charts/sankey/index.ts"
+ }
+ ]
+ },
+ {
+ "name": "area-chart-example",
+ "type": "registry:example",
+ "title": "Area Chart Example",
+ "description": "Composable area-chart demo for Open in v0",
+ "registryDependencies": [
+ "@bklit/area-chart",
+ "@bklit/grid",
+ "@bklit/x-axis",
+ "@bklit/chart-tooltip"
+ ],
+ "dependencies": [
+ "@visx/curve@4.0.1-alpha.0",
+ "@visx/shape@4.0.1-alpha.0",
+ "motion"
+ ],
+ "files": [
+ {
+ "path": "registry/examples/area-chart.tsx",
+ "type": "registry:page",
+ "target": "app/page.tsx"
+ },
+ {
+ "path": "registry/examples/area-chart-index.ts",
+ "type": "registry:lib",
+ "target": "components/charts/index.ts"
+ }
+ ]
+ },
+ {
+ "name": "bar-chart-example",
+ "type": "registry:example",
+ "title": "Bar Chart Example",
+ "description": "Composable bar-chart demo for Open in v0",
+ "registryDependencies": [
+ "@bklit/bar-chart",
+ "@bklit/grid",
+ "@bklit/chart-tooltip"
+ ],
+ "dependencies": [
+ "@visx/gradient@4.0.1-alpha.0",
+ "@visx/pattern@4.0.1-alpha.0",
+ "@visx/shape@4.0.1-alpha.0",
+ "motion"
+ ],
+ "files": [
+ {
+ "path": "registry/examples/bar-chart.tsx",
+ "type": "registry:page",
+ "target": "app/page.tsx"
+ },
+ {
+ "path": "registry/examples/bar-chart-index.ts",
+ "type": "registry:lib",
+ "target": "components/charts/index.ts"
+ }
+ ]
+ },
+ {
+ "name": "line-chart-example",
+ "type": "registry:example",
+ "title": "Line Chart Example",
+ "description": "Composable line-chart demo for Open in v0",
+ "registryDependencies": [
+ "@bklit/line-chart",
+ "@bklit/grid",
+ "@bklit/x-axis",
+ "@bklit/chart-tooltip"
+ ],
+ "dependencies": [
+ "@visx/curve@4.0.1-alpha.0",
+ "@visx/shape@4.0.1-alpha.0",
+ "motion"
+ ],
+ "files": [
+ {
+ "path": "registry/examples/line-chart.tsx",
+ "type": "registry:page",
+ "target": "app/page.tsx"
+ },
+ {
+ "path": "registry/examples/line-chart-index.ts",
+ "type": "registry:lib",
+ "target": "components/charts/index.ts"
+ }
+ ]
+ },
+ {
+ "name": "scatter-chart-example",
+ "type": "registry:example",
+ "title": "Scatter Chart Example",
+ "description": "Composable scatter-chart demo for Open in v0",
+ "registryDependencies": [
+ "@bklit/scatter-chart",
+ "@bklit/grid",
+ "@bklit/x-axis",
+ "@bklit/chart-tooltip"
+ ],
+ "dependencies": [
+ "d3-scale",
+ "d3-array",
+ "motion",
+ "react-use-measure"
+ ],
+ "files": [
+ {
+ "path": "registry/examples/scatter-chart.tsx",
+ "type": "registry:page",
+ "target": "app/page.tsx"
+ },
+ {
+ "path": "registry/examples/scatter-chart-index.ts",
+ "type": "registry:lib",
+ "target": "components/charts/index.ts"
+ }
+ ]
+ },
+ {
+ "name": "pie-chart-example",
+ "type": "registry:example",
+ "title": "Pie Chart Example",
+ "description": "Composable pie-chart demo for Open in v0",
+ "registryDependencies": [
+ "@bklit/pie-chart"
+ ],
+ "dependencies": [
+ "@visx/shape@4.0.1-alpha.0",
+ "motion"
+ ],
+ "files": [
+ {
+ "path": "registry/examples/pie-chart.tsx",
+ "type": "registry:page",
+ "target": "app/page.tsx"
+ },
+ {
+ "path": "registry/examples/pie-chart-index.ts",
+ "type": "registry:lib",
+ "target": "components/charts/index.ts"
+ }
+ ]
+ },
+ {
+ "name": "gauge-chart-example",
+ "type": "registry:example",
+ "title": "Gauge Chart Example",
+ "description": "Composable gauge-chart demo for Open in v0",
+ "registryDependencies": [
+ "@bklit/gauge-chart"
+ ],
+ "dependencies": [
+ "@visx/shape@4.0.1-alpha.0",
+ "motion"
+ ],
+ "files": [
+ {
+ "path": "registry/examples/gauge-chart.tsx",
+ "type": "registry:page",
+ "target": "app/page.tsx"
+ },
+ {
+ "path": "registry/examples/gauge-chart-index.ts",
+ "type": "registry:lib",
+ "target": "components/charts/index.ts"
+ }
+ ]
+ },
+ {
+ "name": "heatmap-chart-example",
+ "type": "registry:example",
+ "title": "Heatmap Chart Example",
+ "description": "Composable heatmap-chart demo for Open in v0",
+ "registryDependencies": [
+ "@bklit/heatmap-chart"
+ ],
+ "dependencies": [
+ "@visx/heatmap@4.0.1-alpha.0",
+ "@visx/shape@4.0.1-alpha.0",
+ "motion"
+ ],
+ "files": [
+ {
+ "path": "registry/examples/heatmap-chart.tsx",
+ "type": "registry:page",
+ "target": "app/page.tsx"
+ },
+ {
+ "path": "registry/examples/heatmap-chart-index.ts",
+ "type": "registry:lib",
+ "target": "components/charts/index.ts"
+ }
+ ]
+ },
+ {
+ "name": "ring-chart-example",
+ "type": "registry:example",
+ "title": "Ring Chart Example",
+ "description": "Composable ring-chart demo for Open in v0",
+ "registryDependencies": [
+ "@bklit/ring-chart"
+ ],
+ "dependencies": [
+ "@visx/shape@4.0.1-alpha.0",
+ "motion"
+ ],
+ "files": [
+ {
+ "path": "registry/examples/ring-chart.tsx",
+ "type": "registry:page",
+ "target": "app/page.tsx"
+ },
+ {
+ "path": "registry/examples/ring-chart-index.ts",
+ "type": "registry:lib",
+ "target": "components/charts/index.ts"
+ }
+ ]
+ },
+ {
+ "name": "radar-chart-example",
+ "type": "registry:example",
+ "title": "Radar Chart Example",
+ "description": "Composable radar-chart demo for Open in v0",
+ "registryDependencies": [
+ "@bklit/radar-chart"
+ ],
+ "dependencies": [
+ "@visx/shape@4.0.1-alpha.0",
+ "motion"
+ ],
+ "files": [
+ {
+ "path": "registry/examples/radar-chart.tsx",
+ "type": "registry:page",
+ "target": "app/page.tsx"
+ },
+ {
+ "path": "registry/examples/radar-chart-index.ts",
+ "type": "registry:lib",
+ "target": "components/charts/index.ts"
+ }
+ ]
+ },
+ {
+ "name": "composed-chart-example",
+ "type": "registry:example",
+ "title": "Composed Chart Example",
+ "description": "Composable composed-chart demo for Open in v0",
+ "registryDependencies": [
+ "@bklit/composed-chart",
+ "@bklit/grid",
+ "@bklit/x-axis",
+ "@bklit/chart-tooltip"
+ ],
+ "dependencies": [
+ "@visx/curve@4.0.1-alpha.0",
+ "@visx/shape@4.0.1-alpha.0",
+ "motion"
+ ],
+ "files": [
+ {
+ "path": "registry/examples/composed-chart.tsx",
+ "type": "registry:page",
+ "target": "app/page.tsx"
+ },
+ {
+ "path": "registry/examples/composed-chart-index.ts",
+ "type": "registry:lib",
+ "target": "components/charts/index.ts"
+ }
+ ]
+ },
+ {
+ "name": "funnel-chart-example",
+ "type": "registry:example",
+ "title": "Funnel Chart Example",
+ "description": "Composable funnel-chart demo for Open in v0",
+ "registryDependencies": [
+ "@bklit/funnel-chart"
+ ],
+ "dependencies": [
+ "@visx/shape@4.0.1-alpha.0",
+ "motion"
+ ],
+ "files": [
+ {
+ "path": "registry/examples/funnel-chart.tsx",
+ "type": "registry:page",
+ "target": "app/page.tsx"
+ },
+ {
+ "path": "registry/examples/funnel-chart-index.ts",
+ "type": "registry:lib",
+ "target": "components/charts/index.ts"
+ }
+ ]
+ },
+ {
+ "name": "sankey-chart-example",
+ "type": "registry:example",
+ "title": "Sankey Chart Example",
+ "description": "Composable sankey-chart demo for Open in v0",
+ "registryDependencies": [
+ "@bklit/sankey-chart"
+ ],
+ "dependencies": [
+ "@visx/sankey@4.0.1-alpha.0",
+ "@visx/shape@4.0.1-alpha.0",
+ "motion"
+ ],
+ "files": [
+ {
+ "path": "registry/examples/sankey-chart.tsx",
+ "type": "registry:page",
+ "target": "app/page.tsx"
+ },
+ {
+ "path": "registry/examples/sankey-chart-index.ts",
+ "type": "registry:lib",
+ "target": "components/charts/index.ts"
+ }
+ ]
+ },
+ {
+ "name": "candlestick-chart-example",
+ "type": "registry:example",
+ "title": "Candlestick Chart Example",
+ "description": "Composable candlestick-chart demo for Open in v0",
+ "registryDependencies": [
+ "@bklit/candlestick-chart",
+ "@bklit/grid",
+ "@bklit/x-axis",
+ "@bklit/y-axis",
+ "@bklit/chart-tooltip"
+ ],
+ "dependencies": [
+ "@visx/shape@4.0.1-alpha.0",
+ "motion"
+ ],
+ "files": [
+ {
+ "path": "registry/examples/candlestick-chart.tsx",
+ "type": "registry:page",
+ "target": "app/page.tsx"
+ },
+ {
+ "path": "registry/examples/candlestick-chart-index.ts",
+ "type": "registry:lib",
+ "target": "components/charts/index.ts"
+ }
+ ]
+ },
+ {
+ "name": "choropleth-chart-example",
+ "type": "registry:example",
+ "title": "Choropleth Chart Example",
+ "description": "Composable choropleth-chart demo for Open in v0",
+ "registryDependencies": [
+ "@bklit/choropleth-chart"
+ ],
+ "dependencies": [
+ "@visx/geo@4.0.1-alpha.0",
+ "d3-geo",
+ "topojson-client"
+ ],
+ "files": [
+ {
+ "path": "registry/examples/choropleth-chart.tsx",
+ "type": "registry:page",
+ "target": "app/page.tsx"
+ },
+ {
+ "path": "registry/examples/choropleth-chart-index.ts",
+ "type": "registry:lib",
+ "target": "components/charts/index.ts"
+ }
+ ]
+ },
+ {
+ "name": "live-line-chart-example",
+ "type": "registry:example",
+ "title": "Live Line Chart Example",
+ "description": "Composable live-line-chart demo for Open in v0",
+ "registryDependencies": [
+ "@bklit/live-line-chart",
+ "@bklit/chart-tooltip"
+ ],
+ "dependencies": [
+ "@visx/curve@4.0.1-alpha.0",
+ "@visx/shape@4.0.1-alpha.0",
+ "motion"
+ ],
+ "files": [
+ {
+ "path": "registry/examples/live-line-chart.tsx",
+ "type": "registry:page",
+ "target": "app/page.tsx"
+ },
+ {
+ "path": "registry/examples/live-line-chart-index.ts",
+ "type": "registry:lib",
+ "target": "components/charts/index.ts"
+ }
+ ]
+ },
+ {
+ "name": "chart-stat-flow",
+ "type": "registry:component",
+ "title": "Chart Stat Flow",
+ "description": "Animated stat value and label for chart overlays",
+ "registryDependencies": [
+ "@bklit/utils"
+ ],
+ "dependencies": [
+ "@number-flow/react",
+ "motion"
+ ],
+ "files": [
+ {
+ "path": "src/charts/chart-stat-flow.tsx",
+ "type": "registry:component",
+ "target": "components/charts/chart-stat-flow.tsx"
+ }
+ ]
+ },
+ {
+ "name": "stat-card-area-01",
+ "type": "registry:block",
+ "title": "Stat Card Area",
+ "description": "Revenue stat card with gradient area sparkline, NumberFlow, and trend badge",
+ "registryDependencies": [
+ "@bklit/area-chart",
+ "@bklit/chart-stat-flow",
+ "card",
+ "badge"
+ ],
+ "dependencies": [
+ "@visx/curve@4.0.1-alpha.0",
+ "@visx/gradient@4.0.1-alpha.0",
+ "@number-flow/react",
+ "@central-icons-react/all"
+ ],
+ "files": [
+ {
+ "path": "registry/blocks/stat-card-area-01/components/stat-card-area.tsx",
+ "type": "registry:component",
+ "target": "components/stat-card-area.tsx"
+ },
+ {
+ "path": "registry/blocks/stat-card-area-01/components/stat-card-chart.tsx",
+ "type": "registry:component",
+ "target": "components/stat-card-chart.tsx"
+ },
+ {
+ "path": "registry/blocks/stat-card-area-01/components/stat-card-hover-bridge.tsx",
+ "type": "registry:component",
+ "target": "components/stat-card-hover-bridge.tsx"
+ },
+ {
+ "path": "registry/blocks/stat-card-area-01/components/trend-badge.tsx",
+ "type": "registry:component",
+ "target": "components/trend-badge.tsx"
+ },
+ {
+ "path": "registry/blocks/stat-card-area-01/data/revenue-series.ts",
+ "type": "registry:lib",
+ "target": "data/revenue-series.ts"
+ },
+ {
+ "path": "registry/examples/stat-card-area-01-index.ts",
+ "type": "registry:lib",
+ "target": "components/charts/index.ts"
+ }
+ ]
+ },
+ {
+ "name": "stat-card-area-01-example",
+ "type": "registry:example",
+ "title": "Stat Card Area Example",
+ "description": "Revenue stat card with gradient area sparkline, NumberFlow, and trend badge — demo for Open in v0",
+ "registryDependencies": [
+ "@bklit/stat-card-area-01"
+ ],
+ "dependencies": [
+ "@visx/curve@4.0.1-alpha.0",
+ "@visx/gradient@4.0.1-alpha.0",
+ "@number-flow/react",
+ "@central-icons-react/all"
+ ],
+ "files": [
+ {
+ "path": "registry/examples/stat-card-area-01.tsx",
+ "type": "registry:page",
+ "target": "app/page.tsx"
+ },
+ {
+ "path": "registry/examples/stat-card-area-01-index.ts",
+ "type": "registry:lib",
+ "target": "components/charts/index.ts"
+ }
+ ]
+ },
+ {
+ "name": "stat-card-line-01",
+ "type": "registry:block",
+ "title": "Stat Card Line",
+ "description": "Sessions stat card with line sparkline, overlaid NumberFlow, and trend badge",
+ "registryDependencies": [
+ "@bklit/line-chart",
+ "@bklit/chart-stat-flow",
+ "card",
+ "badge"
+ ],
+ "dependencies": [
+ "@visx/curve@4.0.1-alpha.0",
+ "@number-flow/react",
+ "@central-icons-react/all"
+ ],
+ "files": [
+ {
+ "path": "registry/blocks/stat-card-line-01/components/stat-card-line.tsx",
+ "type": "registry:component",
+ "target": "components/stat-card-line.tsx"
+ },
+ {
+ "path": "registry/blocks/stat-card-line-01/components/stat-card-chart.tsx",
+ "type": "registry:component",
+ "target": "components/stat-card-chart.tsx"
+ },
+ {
+ "path": "registry/blocks/stat-card-line-01/components/stat-card-hover-bridge.tsx",
+ "type": "registry:component",
+ "target": "components/stat-card-hover-bridge.tsx"
+ },
+ {
+ "path": "registry/blocks/stat-card-line-01/components/trend-badge.tsx",
+ "type": "registry:component",
+ "target": "components/trend-badge.tsx"
+ },
+ {
+ "path": "registry/blocks/stat-card-line-01/data/sessions-series.ts",
+ "type": "registry:lib",
+ "target": "data/sessions-series.ts"
+ },
+ {
+ "path": "registry/examples/stat-card-line-01-index.ts",
+ "type": "registry:lib",
+ "target": "components/charts/index.ts"
+ }
+ ]
+ },
+ {
+ "name": "stat-card-line-01-example",
+ "type": "registry:example",
+ "title": "Stat Card Line Example",
+ "description": "Sessions stat card with line sparkline, overlaid NumberFlow, and trend badge — demo for Open in v0",
+ "registryDependencies": [
+ "@bklit/stat-card-line-01"
+ ],
+ "dependencies": [
+ "@visx/curve@4.0.1-alpha.0",
+ "@number-flow/react",
+ "@central-icons-react/all"
+ ],
+ "files": [
+ {
+ "path": "registry/examples/stat-card-line-01.tsx",
+ "type": "registry:page",
+ "target": "app/page.tsx"
+ },
+ {
+ "path": "registry/examples/stat-card-line-01-index.ts",
+ "type": "registry:lib",
+ "target": "components/charts/index.ts"
+ }
+ ]
+ },
+ {
+ "name": "stat-card-choropleth-01",
+ "type": "registry:block",
+ "title": "Stat Card Choropleth",
+ "description": "Visitor map stat card with choropleth sparkline, NumberFlow, and trend badge",
+ "registryDependencies": [
+ "@bklit/choropleth-chart",
+ "@bklit/chart-stat-flow",
+ "card",
+ "badge"
+ ],
+ "dependencies": [
+ "@number-flow/react",
+ "@types/geojson",
+ "@types/topojson-specification",
+ "@central-icons-react/all",
+ "topojson-client"
+ ],
+ "files": [
+ {
+ "path": "registry/blocks/stat-card-choropleth-01/components/stat-card-choropleth.tsx",
+ "type": "registry:component",
+ "target": "components/stat-card-choropleth.tsx"
+ },
+ {
+ "path": "registry/blocks/stat-card-choropleth-01/components/stat-card-choropleth-hover-bridge.tsx",
+ "type": "registry:component",
+ "target": "components/stat-card-choropleth-hover-bridge.tsx"
+ },
+ {
+ "path": "registry/blocks/stat-card-choropleth-01/components/stat-card-chart.tsx",
+ "type": "registry:component",
+ "target": "components/stat-card-chart.tsx"
+ },
+ {
+ "path": "registry/blocks/stat-card-choropleth-01/components/trend-badge.tsx",
+ "type": "registry:component",
+ "target": "components/trend-badge.tsx"
+ },
+ {
+ "path": "registry/blocks/stat-card-choropleth-01/data/visitors.ts",
+ "type": "registry:lib",
+ "target": "data/visitors.ts"
+ },
+ {
+ "path": "registry/blocks/stat-card-choropleth-01/lib/use-world-data.tsx",
+ "type": "registry:component",
+ "target": "lib/use-world-data.tsx"
+ },
+ {
+ "path": "registry/examples/stat-card-choropleth-01-index.ts",
+ "type": "registry:lib",
+ "target": "components/charts/index.ts"
+ }
+ ]
+ },
+ {
+ "name": "stat-card-choropleth-01-example",
+ "type": "registry:example",
+ "title": "Stat Card Choropleth Example",
+ "description": "Visitor map stat card with choropleth sparkline, NumberFlow, and trend badge — demo for Open in v0",
+ "registryDependencies": [
+ "@bklit/stat-card-choropleth-01"
+ ],
+ "dependencies": [
+ "@number-flow/react",
+ "@types/geojson",
+ "@types/topojson-specification",
+ "@central-icons-react/all",
+ "topojson-client"
+ ],
+ "files": [
+ {
+ "path": "registry/examples/stat-card-choropleth-01.tsx",
+ "type": "registry:page",
+ "target": "app/page.tsx"
+ },
+ {
+ "path": "registry/examples/stat-card-choropleth-01-index.ts",
+ "type": "registry:lib",
+ "target": "components/charts/index.ts"
+ }
+ ]
+ }
+ ]
+}
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/blocks/stat-card-area-01/components/stat-card-area.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/blocks/stat-card-area-01/components/stat-card-area.tsx
new file mode 100644
index 0000000..3fcaebd
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/blocks/stat-card-area-01/components/stat-card-area.tsx
@@ -0,0 +1,97 @@
+"use client";
+
+import {
+ Area,
+ AreaChart,
+ ChartStatFlow,
+ LinearGradient,
+} from "@/components/charts";
+import { curveCardinal } from "@visx/curve";
+import { useState } from "react";
+import {
+ Card,
+ CardAction,
+ CardContent,
+ CardHeader,
+ CardTitle,
+} from "@/components/ui/card";
+import { revenueSeries, revenueStats } from "../data/revenue-series";
+import {
+ StatCardChart,
+ statCardLabelClassName,
+ statCardValueClassName,
+} from "./stat-card-chart";
+import {
+ formatStatCardMonth,
+ StatCardHoverBridge,
+ type StatCardHoverState,
+} from "./stat-card-hover-bridge";
+import { TrendBadge } from "./trend-badge";
+
+export function StatCardArea() {
+ const [hover, setHover] = useState({
+ value: null,
+ label: null,
+ trend: null,
+ });
+ const displayValue = hover.value ?? revenueStats.average;
+ const displayLabel = hover.label ?? "Avg";
+ const displayTrend = hover.trend ?? revenueStats.trend;
+
+ return (
+
+
+ Total Revenue
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/blocks/stat-card-area-01/components/stat-card-chart.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/blocks/stat-card-area-01/components/stat-card-chart.tsx
new file mode 100644
index 0000000..2d3d90d
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/blocks/stat-card-area-01/components/stat-card-chart.tsx
@@ -0,0 +1,45 @@
+"use client";
+
+import type { ReactNode } from "react";
+import { cn } from "@/lib/utils";
+
+export interface StatCardHoverState {
+ value: number | null;
+ label: string | null;
+ trend: number | null;
+}
+
+export const statCardValueClassName =
+ "text-3xl font-semibold leading-none tracking-tight";
+
+export const statCardLabelClassName = "mt-0 text-xs";
+
+export const statCardChartHeights = {
+ sm: "[--stat-card-chart-h:96px]",
+ md: "[--stat-card-chart-h:190px]",
+ lg: "[--stat-card-chart-h:420px]",
+} as const;
+
+/** Bleeds charts edge-to-edge inside stat card content padding. */
+export function StatCardChart({
+ children,
+ className,
+ size = "sm",
+}: {
+ children: ReactNode;
+ className?: string;
+ size?: keyof typeof statCardChartHeights;
+}) {
+ return (
+
+ {children}
+
+ );
+}
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/blocks/stat-card-area-01/components/stat-card-hover-bridge.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/blocks/stat-card-area-01/components/stat-card-hover-bridge.tsx
new file mode 100644
index 0000000..4bb3bec
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/blocks/stat-card-area-01/components/stat-card-hover-bridge.tsx
@@ -0,0 +1,80 @@
+"use client";
+
+import { useChart } from "@/components/charts";
+import { useEffect } from "react";
+import type { StatCardHoverState } from "./stat-card-chart";
+
+export type { StatCardHoverState } from "./stat-card-chart";
+
+export function formatStatCardMonth(date: Date) {
+ return date.toLocaleDateString("en-US", { month: "short" });
+}
+
+export function formatStatCardWeekday(date: Date) {
+ return date.toLocaleDateString("en-US", { weekday: "long" });
+}
+
+function parsePointDate(raw: unknown): Date | null {
+ if (raw instanceof Date) {
+ return raw;
+ }
+ if (typeof raw === "string") {
+ return new Date(raw);
+ }
+ return null;
+}
+
+function computePeriodTrend(
+ data: Record[],
+ index: number,
+ dataKey: string
+): number | null {
+ if (index <= 0) {
+ return null;
+ }
+
+ const current = data[index]?.[dataKey];
+ const previous = data[index - 1]?.[dataKey];
+
+ if (
+ typeof current !== "number" ||
+ typeof previous !== "number" ||
+ previous === 0
+ ) {
+ return null;
+ }
+
+ return ((current - previous) / previous) * 100;
+}
+
+/** Syncs hovered chart values, labels, and trend into stat card UI. */
+export function StatCardHoverBridge({
+ dataKey,
+ dateKey = "date",
+ formatLabel,
+ onHoverChange,
+}: {
+ dataKey: string;
+ dateKey?: string;
+ formatLabel: (date: Date) => string;
+ onHoverChange: (state: StatCardHoverState) => void;
+}) {
+ const { data, tooltipData } = useChart();
+
+ useEffect(() => {
+ if (!tooltipData?.point) {
+ onHoverChange({ value: null, label: null, trend: null });
+ return;
+ }
+
+ const raw = tooltipData.point[dataKey];
+ const value = typeof raw === "number" ? raw : null;
+ const date = parsePointDate(tooltipData.point[dateKey]);
+ const label = date ? formatLabel(date) : null;
+ const trend = computePeriodTrend(data, tooltipData.index, dataKey);
+
+ onHoverChange({ value, label, trend });
+ }, [data, dataKey, dateKey, formatLabel, onHoverChange, tooltipData]);
+
+ return null;
+}
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/blocks/stat-card-area-01/components/trend-badge.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/blocks/stat-card-area-01/components/trend-badge.tsx
new file mode 100644
index 0000000..eb800e9
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/blocks/stat-card-area-01/components/trend-badge.tsx
@@ -0,0 +1,38 @@
+"use client";
+
+import { CentralIcon } from "@central-icons-react/all";
+import { Badge } from "@/components/ui/badge";
+import { cn } from "@/lib/utils";
+
+export function TrendBadge({
+ value,
+ className,
+}: {
+ value: number;
+ className?: string;
+}) {
+ const positive = value >= 0;
+
+ return (
+
+
+ {positive ? "+" : ""}
+ {value.toFixed(1)}%
+
+ );
+}
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/blocks/stat-card-choropleth-01/components/stat-card-chart.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/blocks/stat-card-choropleth-01/components/stat-card-chart.tsx
new file mode 100644
index 0000000..2d3d90d
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/blocks/stat-card-choropleth-01/components/stat-card-chart.tsx
@@ -0,0 +1,45 @@
+"use client";
+
+import type { ReactNode } from "react";
+import { cn } from "@/lib/utils";
+
+export interface StatCardHoverState {
+ value: number | null;
+ label: string | null;
+ trend: number | null;
+}
+
+export const statCardValueClassName =
+ "text-3xl font-semibold leading-none tracking-tight";
+
+export const statCardLabelClassName = "mt-0 text-xs";
+
+export const statCardChartHeights = {
+ sm: "[--stat-card-chart-h:96px]",
+ md: "[--stat-card-chart-h:190px]",
+ lg: "[--stat-card-chart-h:420px]",
+} as const;
+
+/** Bleeds charts edge-to-edge inside stat card content padding. */
+export function StatCardChart({
+ children,
+ className,
+ size = "sm",
+}: {
+ children: ReactNode;
+ className?: string;
+ size?: keyof typeof statCardChartHeights;
+}) {
+ return (
+
+ {children}
+
+ );
+}
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/blocks/stat-card-choropleth-01/components/stat-card-choropleth-hover-bridge.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/blocks/stat-card-choropleth-01/components/stat-card-choropleth-hover-bridge.tsx
new file mode 100644
index 0000000..3a17690
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/blocks/stat-card-choropleth-01/components/stat-card-choropleth-hover-bridge.tsx
@@ -0,0 +1,32 @@
+"use client";
+
+import { useChoropleth } from "@/components/charts";
+import { useEffect } from "react";
+import { computeVisitorTrend, getVisitorValue } from "../data/visitors";
+import type { StatCardHoverState } from "./stat-card-chart";
+
+/** Syncs hovered choropleth feature into stat card NumberFlow and trend badge. */
+export function StatCardChoroplethHoverBridge({
+ onHoverChange,
+}: {
+ onHoverChange: (state: StatCardHoverState) => void;
+}) {
+ const { tooltipData } = useChoropleth();
+
+ useEffect(() => {
+ if (!tooltipData?.feature) {
+ onHoverChange({ value: null, label: null, trend: null });
+ return;
+ }
+
+ const feature = tooltipData.feature;
+ const label = (feature.properties?.name as string | undefined) ?? "Unknown";
+ const visitors = getVisitorValue(feature);
+ const value = visitors ?? 0;
+ const trend = visitors === undefined ? null : computeVisitorTrend(visitors);
+
+ onHoverChange({ value, label, trend });
+ }, [onHoverChange, tooltipData]);
+
+ return null;
+}
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/blocks/stat-card-choropleth-01/components/stat-card-choropleth.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/blocks/stat-card-choropleth-01/components/stat-card-choropleth.tsx
new file mode 100644
index 0000000..37fc413
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/blocks/stat-card-choropleth-01/components/stat-card-choropleth.tsx
@@ -0,0 +1,91 @@
+"use client";
+
+import type { ChoroplethFeature } from "@/components/charts";
+import {
+ ChartStatFlow,
+ ChoroplethChart,
+ ChoroplethFeatureComponent,
+ ChoroplethTooltip,
+} from "@/components/charts";
+import { useState } from "react";
+import { useWorldDataStandalone } from "@/lib/use-world-data";
+import {
+ Card,
+ CardAction,
+ CardContent,
+ CardHeader,
+ CardTitle,
+} from "@/components/ui/card";
+import {
+ getVisitorColor,
+ getVisitorValue,
+ visitorStats,
+} from "../data/visitors";
+import {
+ StatCardChart,
+ type StatCardHoverState,
+ statCardLabelClassName,
+ statCardValueClassName,
+} from "./stat-card-chart";
+import { StatCardChoroplethHoverBridge } from "./stat-card-choropleth-hover-bridge";
+import { TrendBadge } from "./trend-badge";
+
+export function StatCardChoropleth() {
+ const { worldData, isLoading } = useWorldDataStandalone();
+ const [hover, setHover] = useState({
+ value: null,
+ label: null,
+ trend: null,
+ });
+ const displayValue = hover.value ?? visitorStats.total;
+ const displayLabel = hover.label ?? "Total";
+ const displayTrend = hover.trend ?? visitorStats.trend;
+
+ return (
+
+
+
+ Unique Visitors
+
+
+
+
+
+
+
+
+ {isLoading || !worldData ? (
+
+
+ Loading map…
+
+
+ ) : (
+
+
+
+
+ getVisitorColor(feature)
+ }
+ />
+
+
+
+ )}
+
+
+ );
+}
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/blocks/stat-card-choropleth-01/components/trend-badge.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/blocks/stat-card-choropleth-01/components/trend-badge.tsx
new file mode 100644
index 0000000..eb800e9
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/blocks/stat-card-choropleth-01/components/trend-badge.tsx
@@ -0,0 +1,38 @@
+"use client";
+
+import { CentralIcon } from "@central-icons-react/all";
+import { Badge } from "@/components/ui/badge";
+import { cn } from "@/lib/utils";
+
+export function TrendBadge({
+ value,
+ className,
+}: {
+ value: number;
+ className?: string;
+}) {
+ const positive = value >= 0;
+
+ return (
+
+
+ {positive ? "+" : ""}
+ {value.toFixed(1)}%
+
+ );
+}
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/blocks/stat-card-choropleth-01/lib/use-world-data.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/blocks/stat-card-choropleth-01/lib/use-world-data.tsx
new file mode 100644
index 0000000..f4763b7
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/blocks/stat-card-choropleth-01/lib/use-world-data.tsx
@@ -0,0 +1,143 @@
+"use client";
+
+import type { FeatureCollection, Geometry } from "geojson";
+import {
+ createContext,
+ type ReactNode,
+ useContext,
+ useEffect,
+ useState,
+} from "react";
+import { feature } from "topojson-client";
+import type { GeometryCollection, Topology } from "topojson-specification";
+
+interface CountryProperties {
+ name: string;
+ [key: string]: unknown;
+}
+
+interface WorldTopology extends Topology {
+ objects: {
+ [key: string]: GeometryCollection;
+ };
+}
+
+const WORLD_DATA_URL =
+ "https://raw.githubusercontent.com/subyfly/topojson/refs/heads/master/world-countries.json";
+
+// Global cache to avoid refetching across component mounts
+let globalWorldDataCache: FeatureCollection<
+ Geometry,
+ CountryProperties
+> | null = null;
+let globalFetchPromise: Promise | null> | null = null;
+
+function fetchWorldData(): Promise | null> {
+ // Return cached data if available
+ if (globalWorldDataCache) {
+ return Promise.resolve(globalWorldDataCache);
+ }
+
+ // Return existing promise if fetch is in progress
+ if (globalFetchPromise) {
+ return globalFetchPromise;
+ }
+
+ // Start new fetch
+ globalFetchPromise = (async () => {
+ try {
+ const response = await fetch(WORLD_DATA_URL);
+ const topology = (await response.json()) as WorldTopology;
+ const objectKey = Object.keys(topology.objects)[0];
+ if (!objectKey) {
+ throw new Error("No objects found in topology");
+ }
+ const geoObject = topology.objects[objectKey];
+ if (!geoObject) {
+ throw new Error("Object not found in topology");
+ }
+ const geojson = feature(
+ topology,
+ geoObject
+ ) as unknown as FeatureCollection;
+ globalWorldDataCache = geojson;
+ return geojson;
+ } catch (error) {
+ console.error("Failed to fetch world data:", error);
+ return null;
+ }
+ })();
+
+ return globalFetchPromise;
+}
+
+interface WorldDataContextValue {
+ worldData: FeatureCollection | null;
+ isLoading: boolean;
+}
+
+const WorldDataContext = createContext({
+ worldData: null,
+ isLoading: true,
+});
+
+export function WorldDataProvider({ children }: { children: ReactNode }) {
+ const [worldData, setWorldData] = useState | null>(globalWorldDataCache);
+ const [isLoading, setIsLoading] = useState(!globalWorldDataCache);
+
+ useEffect(() => {
+ if (globalWorldDataCache) {
+ setWorldData(globalWorldDataCache);
+ setIsLoading(false);
+ return;
+ }
+
+ fetchWorldData().then((data) => {
+ setWorldData(data);
+ setIsLoading(false);
+ });
+ }, []);
+
+ return (
+
+ {children}
+
+ );
+}
+
+export function useWorldData() {
+ return useContext(WorldDataContext);
+}
+
+// Standalone hook for components that don't have the provider
+export function useWorldDataStandalone() {
+ const [worldData, setWorldData] = useState | null>(globalWorldDataCache);
+ const [isLoading, setIsLoading] = useState(!globalWorldDataCache);
+
+ useEffect(() => {
+ if (globalWorldDataCache) {
+ setWorldData(globalWorldDataCache);
+ setIsLoading(false);
+ return;
+ }
+
+ fetchWorldData().then((data) => {
+ setWorldData(data);
+ setIsLoading(false);
+ });
+ }, []);
+
+ return { worldData, isLoading };
+}
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/blocks/stat-card-line-01/components/stat-card-chart.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/blocks/stat-card-line-01/components/stat-card-chart.tsx
new file mode 100644
index 0000000..2d3d90d
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/blocks/stat-card-line-01/components/stat-card-chart.tsx
@@ -0,0 +1,45 @@
+"use client";
+
+import type { ReactNode } from "react";
+import { cn } from "@/lib/utils";
+
+export interface StatCardHoverState {
+ value: number | null;
+ label: string | null;
+ trend: number | null;
+}
+
+export const statCardValueClassName =
+ "text-3xl font-semibold leading-none tracking-tight";
+
+export const statCardLabelClassName = "mt-0 text-xs";
+
+export const statCardChartHeights = {
+ sm: "[--stat-card-chart-h:96px]",
+ md: "[--stat-card-chart-h:190px]",
+ lg: "[--stat-card-chart-h:420px]",
+} as const;
+
+/** Bleeds charts edge-to-edge inside stat card content padding. */
+export function StatCardChart({
+ children,
+ className,
+ size = "sm",
+}: {
+ children: ReactNode;
+ className?: string;
+ size?: keyof typeof statCardChartHeights;
+}) {
+ return (
+
+ {children}
+
+ );
+}
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/blocks/stat-card-line-01/components/stat-card-hover-bridge.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/blocks/stat-card-line-01/components/stat-card-hover-bridge.tsx
new file mode 100644
index 0000000..4bb3bec
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/blocks/stat-card-line-01/components/stat-card-hover-bridge.tsx
@@ -0,0 +1,80 @@
+"use client";
+
+import { useChart } from "@/components/charts";
+import { useEffect } from "react";
+import type { StatCardHoverState } from "./stat-card-chart";
+
+export type { StatCardHoverState } from "./stat-card-chart";
+
+export function formatStatCardMonth(date: Date) {
+ return date.toLocaleDateString("en-US", { month: "short" });
+}
+
+export function formatStatCardWeekday(date: Date) {
+ return date.toLocaleDateString("en-US", { weekday: "long" });
+}
+
+function parsePointDate(raw: unknown): Date | null {
+ if (raw instanceof Date) {
+ return raw;
+ }
+ if (typeof raw === "string") {
+ return new Date(raw);
+ }
+ return null;
+}
+
+function computePeriodTrend(
+ data: Record[],
+ index: number,
+ dataKey: string
+): number | null {
+ if (index <= 0) {
+ return null;
+ }
+
+ const current = data[index]?.[dataKey];
+ const previous = data[index - 1]?.[dataKey];
+
+ if (
+ typeof current !== "number" ||
+ typeof previous !== "number" ||
+ previous === 0
+ ) {
+ return null;
+ }
+
+ return ((current - previous) / previous) * 100;
+}
+
+/** Syncs hovered chart values, labels, and trend into stat card UI. */
+export function StatCardHoverBridge({
+ dataKey,
+ dateKey = "date",
+ formatLabel,
+ onHoverChange,
+}: {
+ dataKey: string;
+ dateKey?: string;
+ formatLabel: (date: Date) => string;
+ onHoverChange: (state: StatCardHoverState) => void;
+}) {
+ const { data, tooltipData } = useChart();
+
+ useEffect(() => {
+ if (!tooltipData?.point) {
+ onHoverChange({ value: null, label: null, trend: null });
+ return;
+ }
+
+ const raw = tooltipData.point[dataKey];
+ const value = typeof raw === "number" ? raw : null;
+ const date = parsePointDate(tooltipData.point[dateKey]);
+ const label = date ? formatLabel(date) : null;
+ const trend = computePeriodTrend(data, tooltipData.index, dataKey);
+
+ onHoverChange({ value, label, trend });
+ }, [data, dataKey, dateKey, formatLabel, onHoverChange, tooltipData]);
+
+ return null;
+}
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/blocks/stat-card-line-01/components/stat-card-line.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/blocks/stat-card-line-01/components/stat-card-line.tsx
new file mode 100644
index 0000000..1a4f757
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/blocks/stat-card-line-01/components/stat-card-line.tsx
@@ -0,0 +1,80 @@
+"use client";
+
+import { ChartStatFlow, Line, LineChart } from "@/components/charts";
+import { curveBasis } from "@visx/curve";
+import { useState } from "react";
+import {
+ Card,
+ CardAction,
+ CardContent,
+ CardHeader,
+ CardTitle,
+} from "@/components/ui/card";
+import { sessionsSeries, sessionsStats } from "../data/sessions-series";
+import {
+ StatCardChart,
+ statCardLabelClassName,
+ statCardValueClassName,
+} from "./stat-card-chart";
+import {
+ formatStatCardWeekday,
+ StatCardHoverBridge,
+ type StatCardHoverState,
+} from "./stat-card-hover-bridge";
+import { TrendBadge } from "./trend-badge";
+
+export function StatCardLine() {
+ const [hover, setHover] = useState({
+ value: null,
+ label: null,
+ trend: null,
+ });
+ const average = Math.round(sessionsStats.average);
+ const displayValue = hover.value === null ? average : Math.round(hover.value);
+ const displayLabel = hover.label ?? "Avg";
+ const displayTrend = hover.trend ?? sessionsStats.trend;
+
+ return (
+
+
+ Active Sessions
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/blocks/stat-card-line-01/components/trend-badge.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/blocks/stat-card-line-01/components/trend-badge.tsx
new file mode 100644
index 0000000..eb800e9
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/blocks/stat-card-line-01/components/trend-badge.tsx
@@ -0,0 +1,38 @@
+"use client";
+
+import { CentralIcon } from "@central-icons-react/all";
+import { Badge } from "@/components/ui/badge";
+import { cn } from "@/lib/utils";
+
+export function TrendBadge({
+ value,
+ className,
+}: {
+ value: number;
+ className?: string;
+}) {
+ const positive = value >= 0;
+
+ return (
+
+
+ {positive ? "+" : ""}
+ {value.toFixed(1)}%
+
+ );
+}
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/area-chart-index.ts b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/area-chart-index.ts
new file mode 100644
index 0000000..3f27eb7
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/area-chart-index.ts
@@ -0,0 +1,6 @@
+// biome-ignore-all lint/performance/noBarrelFile: v0 registry example barrel for shadcn install
+export { AreaChart } from "./area-chart";
+export { Area } from "./area";
+export { Grid } from "./grid";
+export { XAxis } from "./x-axis";
+export { ChartTooltip } from "./tooltip";
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/area-chart.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/area-chart.tsx
new file mode 100644
index 0000000..682be4d
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/area-chart.tsx
@@ -0,0 +1,30 @@
+"use client"
+
+// In your app (monorepo/npm): import { AreaChart, Area, Grid, XAxis, ChartTooltip } from "@bklitui/ui/charts"
+import { AreaChart, Area, Grid, XAxis, ChartTooltip } from "@/components/charts"
+
+import { curveNatural } from "@visx/curve";
+
+const chartData = [
+ { date: new Date("2024-01-01"), desktop: 186 },
+ { date: new Date("2024-02-01"), desktop: 305 },
+ { date: new Date("2024-03-01"), desktop: 237 },
+ { date: new Date("2024-04-01"), desktop: 73 },
+ { date: new Date("2024-05-01"), desktop: 209 },
+ { date: new Date("2024-06-01"), desktop: 214 },
+];
+
+export default function Component() {
+ return (
+
+
+
+ )
+}
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/bar-chart-index.ts b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/bar-chart-index.ts
new file mode 100644
index 0000000..7e8fa70
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/bar-chart-index.ts
@@ -0,0 +1,6 @@
+// biome-ignore-all lint/performance/noBarrelFile: v0 registry example barrel for shadcn install
+export { BarChart } from "./bar-chart";
+export { Bar } from "./bar";
+export { BarXAxis } from "./bar-x-axis";
+export { Grid } from "./grid";
+export { ChartTooltip } from "./tooltip";
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/bar-chart.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/bar-chart.tsx
new file mode 100644
index 0000000..2fe5a50
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/bar-chart.tsx
@@ -0,0 +1,29 @@
+"use client"
+
+// In your app (monorepo/npm): import { BarChart, Bar, BarXAxis, Grid, ChartTooltip } from "@bklitui/ui/charts"
+import { BarChart, Bar, BarXAxis, Grid, ChartTooltip } from "@/components/charts"
+
+const data = [
+ { month: "Jan", revenue: 12000, profit: 4500 },
+ { month: "Feb", revenue: 15500, profit: 5200 },
+ { month: "Mar", revenue: 11000, profit: 3800 },
+ { month: "Apr", revenue: 18500, profit: 7100 },
+ { month: "May", revenue: 16800, profit: 5400 },
+ { month: "Jun", revenue: 21200, profit: 8800 },
+];
+
+export default function Component() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/candlestick-chart-index.ts b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/candlestick-chart-index.ts
new file mode 100644
index 0000000..143206b
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/candlestick-chart-index.ts
@@ -0,0 +1,7 @@
+// biome-ignore-all lint/performance/noBarrelFile: v0 registry example barrel for shadcn install
+export { CandlestickChart } from "./candlestick-chart";
+export { Candlestick } from "./candlestick";
+export { Grid } from "./grid";
+export { XAxis } from "./x-axis";
+export { YAxis } from "./y-axis";
+export { ChartTooltip } from "./tooltip";
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/candlestick-chart.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/candlestick-chart.tsx
new file mode 100644
index 0000000..619194e
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/candlestick-chart.tsx
@@ -0,0 +1,27 @@
+"use client"
+
+// In your app (monorepo/npm): import { CandlestickChart, Candlestick, Grid, XAxis, YAxis, ChartTooltip } from "@bklitui/ui/charts"
+import { CandlestickChart, Candlestick, Grid, XAxis, YAxis, ChartTooltip } from "@/components/charts"
+
+const ohlcData = [
+ { date: new Date("2024-01-02"), open: 100, high: 108, low: 98, close: 105 },
+ { date: new Date("2024-01-03"), open: 105, high: 110, low: 102, close: 103 },
+ { date: new Date("2024-01-04"), open: 103, high: 112, low: 101, close: 110 },
+ { date: new Date("2024-01-05"), open: 110, high: 115, low: 107, close: 108 },
+];
+
+export default function Component() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/choropleth-chart-index.ts b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/choropleth-chart-index.ts
new file mode 100644
index 0000000..d161d0a
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/choropleth-chart-index.ts
@@ -0,0 +1,2 @@
+// biome-ignore-all lint/performance/noBarrelFile: v0 registry example barrel for shadcn install
+export { ChoroplethChart, ChoroplethFeatureComponent, ChoroplethTooltip } from "./choropleth";
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/choropleth-chart.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/choropleth-chart.tsx
new file mode 100644
index 0000000..3eb65c9
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/choropleth-chart.tsx
@@ -0,0 +1,23 @@
+"use client"
+
+// In your app (monorepo/npm): import { ChoroplethChart, ChoroplethFeatureComponent, ChoroplethTooltip } from "@bklitui/ui/charts"
+import { ChoroplethChart, ChoroplethFeatureComponent, ChoroplethTooltip } from "@/components/charts"
+
+const features = [
+ { id: "US", name: "United States", value: 120 },
+ { id: "CA", name: "Canada", value: 45 },
+ { id: "GB", name: "United Kingdom", value: 62 },
+];
+
+export default function Component() {
+ return (
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/composed-chart-index.ts b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/composed-chart-index.ts
new file mode 100644
index 0000000..0b65b3c
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/composed-chart-index.ts
@@ -0,0 +1,8 @@
+// biome-ignore-all lint/performance/noBarrelFile: v0 registry example barrel for shadcn install
+export { ComposedChart } from "./composed-chart";
+export { SeriesBar } from "./series-bar";
+export { Area } from "./area";
+export { Line } from "./line";
+export { Grid } from "./grid";
+export { XAxis } from "./x-axis";
+export { ChartTooltip } from "./tooltip";
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/composed-chart.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/composed-chart.tsx
new file mode 100644
index 0000000..9e6f796
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/composed-chart.tsx
@@ -0,0 +1,30 @@
+"use client"
+
+// In your app (monorepo/npm): import { ComposedChart, SeriesBar, Area, Line, Grid, XAxis, ChartTooltip } from "@bklitui/ui/charts"
+import { ComposedChart, SeriesBar, Area, Line, Grid, XAxis, ChartTooltip } from "@/components/charts"
+
+import { curveNatural } from "@visx/curve";
+
+const chartData = [
+ { date: new Date("2024-01-01"), revenue: 4200, runRate: 3800 },
+ { date: new Date("2024-02-01"), revenue: 5100, runRate: 4600 },
+ { date: new Date("2024-03-01"), revenue: 4800, runRate: 5200 },
+ { date: new Date("2024-04-01"), revenue: 5500, runRate: 5000 },
+];
+
+export default function Component() {
+ return (
+
+
+
+ )
+}
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/funnel-chart-index.ts b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/funnel-chart-index.ts
new file mode 100644
index 0000000..24608cb
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/funnel-chart-index.ts
@@ -0,0 +1,2 @@
+// biome-ignore-all lint/performance/noBarrelFile: v0 registry example barrel for shadcn install
+export { FunnelChart } from "./funnel-chart";
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/funnel-chart.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/funnel-chart.tsx
new file mode 100644
index 0000000..7275420
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/funnel-chart.tsx
@@ -0,0 +1,21 @@
+"use client"
+
+// In your app (monorepo/npm): import { FunnelChart } from "@bklitui/ui/charts"
+import { FunnelChart } from "@/components/charts"
+
+const funnelData = [
+ { label: "Visitors", value: 12000 },
+ { label: "Signups", value: 4800 },
+ { label: "Activated", value: 2100 },
+ { label: "Paid", value: 840 },
+];
+
+export default function Component() {
+ return (
+
+
+
+
+
+ )
+}
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/gauge-chart-index.ts b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/gauge-chart-index.ts
new file mode 100644
index 0000000..540287e
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/gauge-chart-index.ts
@@ -0,0 +1,2 @@
+// biome-ignore-all lint/performance/noBarrelFile: v0 registry example barrel for shadcn install
+export { Gauge } from "./gauge";
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/gauge-chart.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/gauge-chart.tsx
new file mode 100644
index 0000000..4fdb1ce
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/gauge-chart.tsx
@@ -0,0 +1,22 @@
+"use client"
+
+// In your app (monorepo/npm): import { Gauge } from "@bklitui/ui/charts"
+import { Gauge } from "@/components/charts"
+
+
+
+export default function Component() {
+ return (
+
+
+
+
+
+ )
+}
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/heatmap-chart-index.ts b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/heatmap-chart-index.ts
new file mode 100644
index 0000000..a489340
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/heatmap-chart-index.ts
@@ -0,0 +1,11 @@
+// biome-ignore-all lint/performance/noBarrelFile: v0 registry example barrel for shadcn install
+export {
+ HeatmapCells,
+ HeatmapChart,
+ HeatmapInteractionBoundary,
+ HeatmapInteractionProvider,
+ HeatmapLegend,
+ HeatmapTooltip,
+ HeatmapXAxis,
+ HeatmapYAxis,
+} from "./heatmap";
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/heatmap-chart.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/heatmap-chart.tsx
new file mode 100644
index 0000000..38c4321
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/heatmap-chart.tsx
@@ -0,0 +1,53 @@
+"use client"
+
+// In your app (monorepo/npm): import { HeatmapCells, HeatmapChart, HeatmapInteractionBoundary, HeatmapInteractionProvider, HeatmapLegend, HeatmapTooltip, HeatmapXAxis, HeatmapYAxis } from "@bklitui/ui/charts"
+import { HeatmapCells, HeatmapChart, HeatmapInteractionBoundary, HeatmapInteractionProvider, HeatmapLegend, HeatmapTooltip, HeatmapXAxis, HeatmapYAxis } from "@/components/charts"
+
+const data = [
+ {
+ bin: 0,
+ bins: [
+ { bin: 0, count: 2, date: new Date(2024, 0, 1) },
+ { bin: 1, count: 0, date: new Date(2024, 0, 2) },
+ { bin: 2, count: 3, date: new Date(2024, 0, 3) },
+ { bin: 3, count: 1, date: new Date(2024, 0, 4) },
+ { bin: 4, count: 4, date: new Date(2024, 0, 5) },
+ { bin: 5, count: 0, date: new Date(2024, 0, 6) },
+ { bin: 6, count: 1, date: new Date(2024, 0, 7) },
+ ],
+ },
+ {
+ bin: 1,
+ bins: [
+ { bin: 0, count: 1, date: new Date(2024, 0, 8) },
+ { bin: 1, count: 2, date: new Date(2024, 0, 9) },
+ { bin: 2, count: 0, date: new Date(2024, 0, 10) },
+ { bin: 3, count: 3, date: new Date(2024, 0, 11) },
+ { bin: 4, count: 2, date: new Date(2024, 0, 12) },
+ { bin: 5, count: 1, date: new Date(2024, 0, 13) },
+ { bin: 6, count: 0, date: new Date(2024, 0, 14) },
+ ],
+ },
+];
+
+export default function Component() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/line-chart-index.ts b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/line-chart-index.ts
new file mode 100644
index 0000000..73f33e2
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/line-chart-index.ts
@@ -0,0 +1,6 @@
+// biome-ignore-all lint/performance/noBarrelFile: v0 registry example barrel for shadcn install
+export { LineChart } from "./line-chart";
+export { Line } from "./line";
+export { Grid } from "./grid";
+export { XAxis } from "./x-axis";
+export { ChartTooltip } from "./tooltip";
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/line-chart.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/line-chart.tsx
new file mode 100644
index 0000000..c2c83a7
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/line-chart.tsx
@@ -0,0 +1,30 @@
+"use client"
+
+// In your app (monorepo/npm): import { LineChart, Line, Grid, XAxis, ChartTooltip } from "@bklitui/ui/charts"
+import { LineChart, Line, Grid, XAxis, ChartTooltip } from "@/components/charts"
+
+import { curveNatural } from "@visx/curve";
+
+const chartData = [
+ { date: new Date("2024-01-01"), users: 1200 },
+ { date: new Date("2024-02-01"), users: 1350 },
+ { date: new Date("2024-03-01"), users: 1100 },
+ { date: new Date("2024-04-01"), users: 1450 },
+ { date: new Date("2024-05-01"), users: 1380 },
+ { date: new Date("2024-06-01"), users: 1520 },
+];
+
+export default function Component() {
+ return (
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/live-line-chart-index.ts b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/live-line-chart-index.ts
new file mode 100644
index 0000000..42993cf
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/live-line-chart-index.ts
@@ -0,0 +1,6 @@
+// biome-ignore-all lint/performance/noBarrelFile: v0 registry example barrel for shadcn install
+export { LiveLineChart } from "./live-line-chart";
+export { LiveLine } from "./live-line";
+export { LiveXAxis } from "./live-x-axis";
+export { LiveYAxis } from "./live-y-axis";
+export { ChartTooltip } from "./tooltip";
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/live-line-chart.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/live-line-chart.tsx
new file mode 100644
index 0000000..7a5f5c6
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/live-line-chart.tsx
@@ -0,0 +1,32 @@
+"use client"
+
+// In your app (monorepo/npm): import { LiveLineChart, LiveLine, LiveXAxis, LiveYAxis, ChartTooltip } from "@bklitui/ui/charts"
+import { LiveLineChart, LiveLine, LiveXAxis, LiveYAxis, ChartTooltip } from "@/components/charts"
+
+import { curveNatural } from "@visx/curve";
+
+const initialData = Array.from({ length: 24 }, (_, i) => ({
+ time: Date.now() - (23 - i) * 60_000,
+ value: 50 + Math.sin(i / 3) * 20,
+}));
+
+export default function Component() {
+ return (
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/pie-chart-index.ts b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/pie-chart-index.ts
new file mode 100644
index 0000000..fc94e92
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/pie-chart-index.ts
@@ -0,0 +1,4 @@
+// biome-ignore-all lint/performance/noBarrelFile: v0 registry example barrel for shadcn install
+export { PieChart } from "./pie-chart";
+export { PieSlice } from "./pie-slice";
+export { PieCenter } from "./pie-center";
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/pie-chart.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/pie-chart.tsx
new file mode 100644
index 0000000..a63f233
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/pie-chart.tsx
@@ -0,0 +1,26 @@
+"use client"
+
+// In your app (monorepo/npm): import { PieChart, PieSlice, PieCenter } from "@bklitui/ui/charts"
+import { PieChart, PieSlice, PieCenter } from "@/components/charts"
+
+const pieData = [
+ { label: "Direct", value: 320 },
+ { label: "Organic", value: 280 },
+ { label: "Referral", value: 190 },
+ { label: "Social", value: 140 },
+];
+
+export default function Component() {
+ return (
+
+
+
+ {pieData.map((item, i) => (
+
+ ))}
+
+
+
+
+ )
+}
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/profit-loss-line-index.ts b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/profit-loss-line-index.ts
new file mode 100644
index 0000000..a5538cb
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/profit-loss-line-index.ts
@@ -0,0 +1,13 @@
+// biome-ignore-all lint/performance/noBarrelFile: v0 registry example barrel for shadcn install
+export { LineChart } from "./line-chart";
+export { Line } from "./line";
+export { Grid } from "./grid";
+export { XAxis } from "./x-axis";
+export { ChartTooltip } from "./tooltip";
+export { ProfitLossLine } from "./profit-loss-line";
+export { ProfitLossLegend } from "./profit-loss-legend";
+export { ProfitLossLegendHoverProvider } from "./profit-loss-legend-hover";
+export {
+ profitLossColor,
+ resolveProfitLossTooltipLabel,
+} from "./profit-loss-line";
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/profit-loss-line.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/profit-loss-line.tsx
new file mode 100644
index 0000000..972c7b5
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/profit-loss-line.tsx
@@ -0,0 +1,72 @@
+"use client"
+
+// In your app (monorepo/npm): import from "@bklitui/ui/charts"
+import {
+ ChartTooltip,
+ Grid,
+ Line,
+ LineChart,
+ ProfitLossLegend,
+ ProfitLossLegendHoverProvider,
+ ProfitLossLine,
+ profitLossColor,
+ resolveProfitLossTooltipLabel,
+ XAxis,
+} from "@/components/charts"
+import { curveLinear } from "@visx/curve";
+import { useState } from "react";
+
+const chartData = [
+ { date: new Date("2024-01-01"), pnl: 420 },
+ { date: new Date("2024-01-05"), pnl: 180 },
+ { date: new Date("2024-01-10"), pnl: -240 },
+ { date: new Date("2024-01-15"), pnl: -90 },
+ { date: new Date("2024-01-20"), pnl: 310 },
+ { date: new Date("2024-01-25"), pnl: 520 },
+];
+
+export default function Component() {
+ const [legendHoveredIndex, setLegendHoveredIndex] = useState(
+ null
+ );
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ profitLossColor((point.pnl as number) ?? 0)}
+ rows={(point) => {
+ const value = (point.pnl as number) ?? 0;
+ return [
+ {
+ color: profitLossColor(value),
+ label: resolveProfitLossTooltipLabel(""),
+ value,
+ },
+ ];
+ }}
+ />
+
+
+
+ )
+}
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/radar-chart-index.ts b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/radar-chart-index.ts
new file mode 100644
index 0000000..d0b73a8
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/radar-chart-index.ts
@@ -0,0 +1,6 @@
+// biome-ignore-all lint/performance/noBarrelFile: v0 registry example barrel for shadcn install
+export { RadarChart } from "./radar-chart";
+export { RadarGrid } from "./radar-grid";
+export { RadarAxis } from "./radar-axis";
+export { RadarLabels } from "./radar-labels";
+export { RadarArea } from "./radar-area";
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/radar-chart.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/radar-chart.tsx
new file mode 100644
index 0000000..43f2d90
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/radar-chart.tsx
@@ -0,0 +1,32 @@
+"use client"
+
+// In your app (monorepo/npm): import { RadarChart, RadarGrid, RadarAxis, RadarLabels, RadarArea } from "@bklitui/ui/charts"
+import { RadarChart, RadarGrid, RadarAxis, RadarLabels, RadarArea } from "@/components/charts"
+
+const metrics = [
+ { key: "speed", label: "Speed" },
+ { key: "reliability", label: "Reliability" },
+ { key: "comfort", label: "Comfort" },
+ { key: "safety", label: "Safety" },
+ { key: "efficiency", label: "Efficiency" },
+];
+const data = [
+ { id: "a", speed: 80, reliability: 70, comfort: 60, safety: 90, efficiency: 75 },
+];
+
+export default function Component() {
+ return (
+
+
+
+
+
+
+ {data.map((row, i) => (
+
+ ))}
+
+
+
+ )
+}
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/ring-chart-index.ts b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/ring-chart-index.ts
new file mode 100644
index 0000000..7d478dc
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/ring-chart-index.ts
@@ -0,0 +1,4 @@
+// biome-ignore-all lint/performance/noBarrelFile: v0 registry example barrel for shadcn install
+export { RingChart } from "./ring-chart";
+export { Ring } from "./ring";
+export { RingCenter } from "./ring-center";
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/ring-chart.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/ring-chart.tsx
new file mode 100644
index 0000000..bf74009
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/ring-chart.tsx
@@ -0,0 +1,26 @@
+"use client"
+
+// In your app (monorepo/npm): import { RingChart, Ring, RingCenter } from "@bklitui/ui/charts"
+import { RingChart, Ring, RingCenter } from "@/components/charts"
+
+const ringData = [
+ { label: "Email", value: 42 },
+ { label: "Social", value: 28 },
+ { label: "Direct", value: 18 },
+ { label: "Other", value: 12 },
+];
+
+export default function Component() {
+ return (
+
+
+
+ {ringData.map((item, i) => (
+
+ ))}
+
+
+
+
+ )
+}
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/sankey-chart-index.ts b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/sankey-chart-index.ts
new file mode 100644
index 0000000..d6be2f3
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/sankey-chart-index.ts
@@ -0,0 +1,2 @@
+// biome-ignore-all lint/performance/noBarrelFile: v0 registry example barrel for shadcn install
+export { SankeyChart, SankeyLink, SankeyNode, SankeyTooltip } from "./sankey";
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/sankey-chart.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/sankey-chart.tsx
new file mode 100644
index 0000000..3b87a56
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/sankey-chart.tsx
@@ -0,0 +1,34 @@
+"use client"
+
+// In your app (monorepo/npm): import { SankeyChart, SankeyLink, SankeyNode, SankeyTooltip } from "@bklitui/ui/charts"
+import { SankeyChart, SankeyLink, SankeyNode, SankeyTooltip } from "@/components/charts"
+
+const data = {
+ nodes: [
+ { name: "Ads" },
+ { name: "Organic" },
+ { name: "Landing" },
+ { name: "Product" },
+ { name: "Checkout" },
+ ],
+ links: [
+ { source: 0, target: 2, value: 40 },
+ { source: 1, target: 2, value: 30 },
+ { source: 2, target: 3, value: 50 },
+ { source: 3, target: 4, value: 35 },
+ ],
+};
+
+export default function Component() {
+ return (
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/scatter-chart-index.ts b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/scatter-chart-index.ts
new file mode 100644
index 0000000..bfa680b
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/scatter-chart-index.ts
@@ -0,0 +1,6 @@
+// biome-ignore-all lint/performance/noBarrelFile: v0 registry example barrel for shadcn install
+export { ScatterChart } from "./scatter-chart";
+export { Scatter } from "./scatter";
+export { Grid } from "./grid";
+export { XAxis } from "./x-axis";
+export { ChartTooltip } from "./tooltip";
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/scatter-chart.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/scatter-chart.tsx
new file mode 100644
index 0000000..84da754
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/scatter-chart.tsx
@@ -0,0 +1,29 @@
+"use client"
+
+// In your app (monorepo/npm): import { ScatterChart, Scatter, Grid, XAxis, ChartTooltip } from "@bklitui/ui/charts"
+import { ScatterChart, Scatter, Grid, XAxis, ChartTooltip } from "@/components/charts"
+
+const chartData = [
+ { date: new Date("2024-01-01"), sessions: 420, conversions: 28 },
+ { date: new Date("2024-02-01"), sessions: 510, conversions: 34 },
+ { date: new Date("2024-03-01"), sessions: 390, conversions: 22 },
+ { date: new Date("2024-04-01"), sessions: 580, conversions: 41 },
+ { date: new Date("2024-05-01"), sessions: 620, conversions: 38 },
+ { date: new Date("2024-06-01"), sessions: 710, conversions: 52 },
+];
+
+export default function Component() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/stat-card-area-01-index.ts b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/stat-card-area-01-index.ts
new file mode 100644
index 0000000..b226fda
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/stat-card-area-01-index.ts
@@ -0,0 +1,6 @@
+/** biome-ignore-all lint/performance/noBarrelFile: v0 registry example barrel for shadcn install */
+export { AreaChart } from "./area-chart";
+export { Area } from "./area";
+export { ChartStatFlow } from "./chart-stat-flow";
+export { useChart } from "./chart-context";
+export { LinearGradient } from "@visx/gradient";
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/stat-card-area-01.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/stat-card-area-01.tsx
new file mode 100644
index 0000000..4ce5c9c
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/stat-card-area-01.tsx
@@ -0,0 +1,13 @@
+"use client";
+
+import { StatCardArea } from "@/components/stat-card-area";
+
+export default function Component() {
+ return (
+
+
+
+
+
+ );
+}
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/stat-card-choropleth-01-index.ts b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/stat-card-choropleth-01-index.ts
new file mode 100644
index 0000000..7f9e635
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/stat-card-choropleth-01-index.ts
@@ -0,0 +1,3 @@
+/** biome-ignore-all lint/performance/noBarrelFile: v0 registry example barrel for shadcn install */
+export { type ChoroplethFeature, ChoroplethChart, ChoroplethFeatureComponent, ChoroplethTooltip, useChoropleth } from "./choropleth";
+export { ChartStatFlow } from "./chart-stat-flow";
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/stat-card-choropleth-01.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/stat-card-choropleth-01.tsx
new file mode 100644
index 0000000..c819a92
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/stat-card-choropleth-01.tsx
@@ -0,0 +1,13 @@
+"use client";
+
+import { StatCardChoropleth } from "@/components/stat-card-choropleth";
+
+export default function Component() {
+ return (
+
+
+
+
+
+ );
+}
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/stat-card-line-01-index.ts b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/stat-card-line-01-index.ts
new file mode 100644
index 0000000..d8b5f8b
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/stat-card-line-01-index.ts
@@ -0,0 +1,5 @@
+/** biome-ignore-all lint/performance/noBarrelFile: v0 registry example barrel for shadcn install */
+export { LineChart } from "./line-chart";
+export { Line } from "./line";
+export { ChartStatFlow } from "./chart-stat-flow";
+export { useChart } from "./chart-context";
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/stat-card-line-01.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/stat-card-line-01.tsx
new file mode 100644
index 0000000..4642050
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/registry/examples/stat-card-line-01.tsx
@@ -0,0 +1,13 @@
+"use client";
+
+import { StatCardLine } from "@/components/stat-card-line";
+
+export default function Component() {
+ return (
+
+
+
+
+
+ );
+}
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/scripts/generate-block-registry.mjs b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/scripts/generate-block-registry.mjs
new file mode 100644
index 0000000..6f5c0f3
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/scripts/generate-block-registry.mjs
@@ -0,0 +1,328 @@
+/**
+ * Syncs stat-card blocks from apps/web/blocks into registry sources and registry.json
+ * (shadcn add + Open in v0). Run from packages/ui after generate-v0-examples.mjs.
+ */
+import {
+ cpSync,
+ existsSync,
+ mkdirSync,
+ readFileSync,
+ writeFileSync,
+} from "node:fs";
+import { dirname, join } from "node:path";
+import { fileURLToPath } from "node:url";
+
+const __dirname = dirname(fileURLToPath(import.meta.url));
+const uiRoot = join(__dirname, "..");
+const webBlocksRoot = join(uiRoot, "../../apps/web/blocks");
+const registryBlocksRoot = join(uiRoot, "registry/blocks");
+const examplesDir = join(uiRoot, "registry/examples");
+const registryPath = join(uiRoot, "registry.json");
+
+const TREND_BADGE_REGISTRY = `"use client";
+
+import { CentralIcon } from "@central-icons-react/all";
+import { Badge } from "@/components/ui/badge";
+import { cn } from "@/lib/utils";
+
+export function TrendBadge({
+ value,
+ className,
+}: {
+ value: number;
+ className?: string;
+}) {
+ const positive = value >= 0;
+
+ return (
+
+
+ {positive ? "+" : ""}
+ {value.toFixed(1)}%
+
+ );
+}
+`;
+
+const BLOCK_CHART_INDEX = {
+ "stat-card-area-01": `export { AreaChart } from "./area-chart";
+export { Area } from "./area";
+export { ChartStatFlow } from "./chart-stat-flow";
+export { useChart } from "./chart-context";
+export { LinearGradient } from "@visx/gradient";`,
+ "stat-card-line-01": `export { LineChart } from "./line-chart";
+export { Line } from "./line";
+export { ChartStatFlow } from "./chart-stat-flow";
+export { useChart } from "./chart-context";`,
+ "stat-card-choropleth-01": `export { type ChoroplethFeature, ChoroplethChart, ChoroplethFeatureComponent, ChoroplethTooltip, useChoropleth } from "./choropleth";
+export { ChartStatFlow } from "./chart-stat-flow";`,
+};
+
+/** @type {Record} */
+const BLOCKS = {
+ "stat-card-area-01": {
+ title: "Stat Card Area",
+ description:
+ "Revenue stat card with gradient area sparkline, NumberFlow, and trend badge",
+ previewMaxWidth: "max-w-md",
+ registryDependencies: [
+ "@bklit/area-chart",
+ "@bklit/chart-stat-flow",
+ "card",
+ "badge",
+ ],
+ dependencies: [
+ "@visx/curve@4.0.1-alpha.0",
+ "@visx/gradient@4.0.1-alpha.0",
+ "@number-flow/react",
+ "@central-icons-react/all",
+ ],
+ files: [
+ "components/stat-card-area.tsx",
+ "components/stat-card-chart.tsx",
+ "components/stat-card-hover-bridge.tsx",
+ "components/trend-badge.tsx",
+ "data/revenue-series.ts",
+ ],
+ },
+ "stat-card-line-01": {
+ title: "Stat Card Line",
+ description:
+ "Sessions stat card with line sparkline, overlaid NumberFlow, and trend badge",
+ previewMaxWidth: "max-w-md",
+ registryDependencies: [
+ "@bklit/line-chart",
+ "@bklit/chart-stat-flow",
+ "card",
+ "badge",
+ ],
+ dependencies: [
+ "@visx/curve@4.0.1-alpha.0",
+ "@number-flow/react",
+ "@central-icons-react/all",
+ ],
+ files: [
+ "components/stat-card-line.tsx",
+ "components/stat-card-chart.tsx",
+ "components/stat-card-hover-bridge.tsx",
+ "components/trend-badge.tsx",
+ "data/sessions-series.ts",
+ ],
+ },
+ "stat-card-choropleth-01": {
+ title: "Stat Card Choropleth",
+ description:
+ "Visitor map stat card with choropleth sparkline, NumberFlow, and trend badge",
+ previewMaxWidth: "max-w-3xl",
+ registryDependencies: [
+ "@bklit/choropleth-chart",
+ "@bklit/chart-stat-flow",
+ "card",
+ "badge",
+ ],
+ dependencies: [
+ "@number-flow/react",
+ "@types/geojson",
+ "@types/topojson-specification",
+ "@central-icons-react/all",
+ "topojson-client",
+ ],
+ files: [
+ "components/stat-card-choropleth.tsx",
+ "components/stat-card-choropleth-hover-bridge.tsx",
+ "components/stat-card-chart.tsx",
+ "components/trend-badge.tsx",
+ "data/visitors.ts",
+ "lib/use-world-data.tsx",
+ ],
+ },
+};
+
+const COMPONENT_EXPORTS = {
+ "stat-card-area-01": "StatCardArea",
+ "stat-card-line-01": "StatCardLine",
+ "stat-card-choropleth-01": "StatCardChoropleth",
+};
+
+function transformForRegistry(content, filePath) {
+ if (filePath.endsWith("trend-badge.tsx")) {
+ return TREND_BADGE_REGISTRY;
+ }
+
+ return content
+ .replaceAll("@bklitui/ui/charts", "@/components/charts")
+ .replaceAll("@/components/docs/use-world-data", "@/lib/use-world-data");
+}
+
+function copyBlockFile(blockId, relativePath) {
+ const sourcePath = join(webBlocksRoot, blockId, "files", relativePath);
+ const targetPath = join(registryBlocksRoot, blockId, relativePath);
+
+ if (relativePath === "lib/use-world-data.tsx") {
+ const sharedSource = join(
+ uiRoot,
+ "../../apps/web/components/docs/use-world-data.tsx"
+ );
+ mkdirSync(dirname(targetPath), { recursive: true });
+ cpSync(sharedSource, targetPath);
+ return;
+ }
+
+ if (!existsSync(sourcePath)) {
+ throw new Error(`Block source not found: ${sourcePath}`);
+ }
+
+ mkdirSync(dirname(targetPath), { recursive: true });
+ const content = transformForRegistry(
+ readFileSync(sourcePath, "utf8"),
+ relativePath
+ );
+ writeFileSync(targetPath, content);
+}
+
+function blockRegistryFiles(blockId, relativePaths) {
+ return relativePaths.map((relativePath) => ({
+ path: `registry/blocks/${blockId}/${relativePath}`,
+ type: relativePath.endsWith(".ts") ? "registry:lib" : "registry:component",
+ target: relativePath,
+ }));
+}
+
+function blockChartIndexFile(blockId) {
+ if (!BLOCK_CHART_INDEX[blockId]) {
+ return null;
+ }
+
+ return {
+ path: `registry/examples/${blockId}-index.ts`,
+ type: "registry:lib",
+ target: "components/charts/index.ts",
+ };
+}
+
+const chartStatFlowItem = {
+ name: "chart-stat-flow",
+ type: "registry:component",
+ title: "Chart Stat Flow",
+ description: "Animated stat value and label for chart overlays",
+ registryDependencies: ["@bklit/utils"],
+ dependencies: ["@number-flow/react", "motion"],
+ files: [
+ {
+ path: "src/charts/chart-stat-flow.tsx",
+ type: "registry:component",
+ target: "components/charts/chart-stat-flow.tsx",
+ },
+ ],
+};
+
+mkdirSync(examplesDir, { recursive: true });
+mkdirSync(registryBlocksRoot, { recursive: true });
+
+for (const [blockId, spec] of Object.entries(BLOCKS)) {
+ for (const relativePath of spec.files) {
+ copyBlockFile(blockId, relativePath);
+ }
+
+ const exportName = COMPONENT_EXPORTS[blockId];
+ const mainComponentPath = spec.files.find((file) =>
+ file.startsWith("components/stat-card-")
+ );
+ const importPath = `@/components/${mainComponentPath
+ ?.replace("components/", "")
+ .replace(".tsx", "")}`;
+
+ const exampleContent = `"use client";
+
+import { ${exportName} } from "${importPath}";
+
+export default function Component() {
+ return (
+
+
+ <${exportName} />
+
+
+ );
+}
+`;
+
+ writeFileSync(join(examplesDir, `${blockId}.tsx`), exampleContent);
+
+ const chartIndex = BLOCK_CHART_INDEX[blockId];
+ if (chartIndex) {
+ writeFileSync(
+ join(examplesDir, `${blockId}-index.ts`),
+ `/** biome-ignore-all lint/performance/noBarrelFile: v0 registry example barrel for shadcn install */\n${chartIndex}\n`
+ );
+ }
+}
+
+const registry = JSON.parse(readFileSync(registryPath, "utf8"));
+
+registry.items = registry.items.filter(
+ (item) =>
+ item.name !== "chart-stat-flow" && !item.name.startsWith("stat-card-")
+);
+
+registry.items.push(chartStatFlowItem);
+
+for (const [blockId, spec] of Object.entries(BLOCKS)) {
+ const blockFiles = blockRegistryFiles(blockId, spec.files);
+ const chartIndexFile = blockChartIndexFile(blockId);
+ if (chartIndexFile) {
+ blockFiles.push(chartIndexFile);
+ }
+
+ registry.items.push({
+ name: blockId,
+ type: "registry:block",
+ title: spec.title,
+ description: spec.description,
+ registryDependencies: spec.registryDependencies,
+ dependencies: spec.dependencies,
+ files: blockFiles,
+ });
+
+ const exampleFiles = [
+ {
+ path: `registry/examples/${blockId}.tsx`,
+ type: "registry:page",
+ target: "app/page.tsx",
+ },
+ ];
+
+ if (chartIndexFile) {
+ exampleFiles.push(chartIndexFile);
+ }
+
+ registry.items.push({
+ name: `${blockId}-example`,
+ type: "registry:example",
+ title: `${spec.title} Example`,
+ description: `${spec.description} — demo for Open in v0`,
+ registryDependencies: [`@bklit/${blockId}`],
+ dependencies: spec.dependencies,
+ files: exampleFiles,
+ });
+}
+
+writeFileSync(registryPath, `${JSON.stringify(registry, null, 2)}\n`);
+console.log(
+ `Synced ${Object.keys(BLOCKS).length} stat-card blocks into registry (${Object.keys(BLOCKS).length * 2 + 1} items).`
+);
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/scripts/generate-v0-examples.mjs b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/scripts/generate-v0-examples.mjs
new file mode 100644
index 0000000..c07437b
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/scripts/generate-v0-examples.mjs
@@ -0,0 +1,520 @@
+/**
+ * Generates registry example entries for Open in v0 (registry:example + registry:page).
+ * Run from packages/ui: node scripts/generate-v0-examples.mjs
+ */
+import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
+import { dirname, join } from "node:path";
+import { fileURLToPath } from "node:url";
+
+const __dirname = dirname(fileURLToPath(import.meta.url));
+const root = join(__dirname, "..");
+const examplesDir = join(root, "registry/examples");
+
+/** Per-chart barrel for installed @/components/charts/* paths in v0. */
+const CHART_INDEX_EXPORTS = {
+ "area-chart": `export { AreaChart } from "./area-chart";
+export { Area } from "./area";
+export { Grid } from "./grid";
+export { XAxis } from "./x-axis";
+export { ChartTooltip } from "./tooltip";`,
+ "bar-chart": `export { BarChart } from "./bar-chart";
+export { Bar } from "./bar";
+export { BarXAxis } from "./bar-x-axis";
+export { Grid } from "./grid";
+export { ChartTooltip } from "./tooltip";`,
+ "line-chart": `export { LineChart } from "./line-chart";
+export { Line } from "./line";
+export { Grid } from "./grid";
+export { XAxis } from "./x-axis";
+export { ChartTooltip } from "./tooltip";`,
+ "scatter-chart": `export { ScatterChart } from "./scatter-chart";
+export { Scatter } from "./scatter";
+export { Grid } from "./grid";
+export { XAxis } from "./x-axis";
+export { ChartTooltip } from "./tooltip";`,
+ "pie-chart": `export { PieChart } from "./pie-chart";
+export { PieSlice } from "./pie-slice";
+export { PieCenter } from "./pie-center";`,
+ "gauge-chart": `export { Gauge } from "./gauge";`,
+ "heatmap-chart": `export {
+ HeatmapCells,
+ HeatmapChart,
+ HeatmapInteractionBoundary,
+ HeatmapInteractionProvider,
+ HeatmapLegend,
+ HeatmapTooltip,
+ HeatmapXAxis,
+ HeatmapYAxis,
+} from "./heatmap";`,
+ "ring-chart": `export { RingChart } from "./ring-chart";
+export { Ring } from "./ring";
+export { RingCenter } from "./ring-center";`,
+ "radar-chart": `export { RadarChart } from "./radar-chart";
+export { RadarGrid } from "./radar-grid";
+export { RadarAxis } from "./radar-axis";
+export { RadarLabels } from "./radar-labels";
+export { RadarArea } from "./radar-area";`,
+ "composed-chart": `export { ComposedChart } from "./composed-chart";
+export { SeriesBar } from "./series-bar";
+export { Area } from "./area";
+export { Line } from "./line";
+export { Grid } from "./grid";
+export { XAxis } from "./x-axis";
+export { ChartTooltip } from "./tooltip";`,
+ "funnel-chart": `export { FunnelChart } from "./funnel-chart";`,
+ "sankey-chart": `export { SankeyChart, SankeyLink, SankeyNode, SankeyTooltip } from "./sankey";`,
+ "candlestick-chart": `export { CandlestickChart } from "./candlestick-chart";
+export { Candlestick } from "./candlestick";
+export { Grid } from "./grid";
+export { XAxis } from "./x-axis";
+export { YAxis } from "./y-axis";
+export { ChartTooltip } from "./tooltip";`,
+ "choropleth-chart": `export { ChoroplethChart, ChoroplethFeatureComponent, ChoroplethTooltip } from "./choropleth";`,
+ "live-line-chart": `export { LiveLineChart } from "./live-line-chart";
+export { LiveLine } from "./live-line";
+export { LiveXAxis } from "./live-x-axis";
+export { LiveYAxis } from "./live-y-axis";
+export { ChartTooltip } from "./tooltip";`,
+};
+
+/** @type {Record} */
+const EXAMPLES = {
+ "area-chart": {
+ registryDependencies: [
+ "@bklit/area-chart",
+ "@bklit/grid",
+ "@bklit/x-axis",
+ "@bklit/chart-tooltip",
+ ],
+ dependencies: [
+ "@visx/curve@4.0.1-alpha.0",
+ "@visx/shape@4.0.1-alpha.0",
+ "motion",
+ ],
+ importFrom: "AreaChart, Area, Grid, XAxis, ChartTooltip",
+ data: `const chartData = [
+ { date: new Date("2024-01-01"), desktop: 186 },
+ { date: new Date("2024-02-01"), desktop: 305 },
+ { date: new Date("2024-03-01"), desktop: 237 },
+ { date: new Date("2024-04-01"), desktop: 73 },
+ { date: new Date("2024-05-01"), desktop: 209 },
+ { date: new Date("2024-06-01"), desktop: 214 },
+];`,
+ body: `
+
+
+
+
+ `,
+ extraImports: `import { curveNatural } from "@visx/curve";`,
+ },
+ "bar-chart": {
+ registryDependencies: [
+ "@bklit/bar-chart",
+ "@bklit/grid",
+ "@bklit/chart-tooltip",
+ ],
+ dependencies: [
+ "@visx/gradient@4.0.1-alpha.0",
+ "@visx/pattern@4.0.1-alpha.0",
+ "@visx/shape@4.0.1-alpha.0",
+ "motion",
+ ],
+ importFrom: "BarChart, Bar, BarXAxis, Grid, ChartTooltip",
+ data: `const data = [
+ { month: "Jan", revenue: 12000, profit: 4500 },
+ { month: "Feb", revenue: 15500, profit: 5200 },
+ { month: "Mar", revenue: 11000, profit: 3800 },
+ { month: "Apr", revenue: 18500, profit: 7100 },
+ { month: "May", revenue: 16800, profit: 5400 },
+ { month: "Jun", revenue: 21200, profit: 8800 },
+];`,
+ body: `
+
+
+
+
+
+ `,
+ },
+ "line-chart": {
+ registryDependencies: [
+ "@bklit/line-chart",
+ "@bklit/grid",
+ "@bklit/x-axis",
+ "@bklit/chart-tooltip",
+ ],
+ dependencies: [
+ "@visx/curve@4.0.1-alpha.0",
+ "@visx/shape@4.0.1-alpha.0",
+ "motion",
+ ],
+ importFrom: "LineChart, Line, Grid, XAxis, ChartTooltip",
+ data: `const chartData = [
+ { date: new Date("2024-01-01"), users: 1200 },
+ { date: new Date("2024-02-01"), users: 1350 },
+ { date: new Date("2024-03-01"), users: 1100 },
+ { date: new Date("2024-04-01"), users: 1450 },
+ { date: new Date("2024-05-01"), users: 1380 },
+ { date: new Date("2024-06-01"), users: 1520 },
+];`,
+ body: `
+
+
+
+
+ `,
+ extraImports: `import { curveNatural } from "@visx/curve";`,
+ },
+ "scatter-chart": {
+ registryDependencies: [
+ "@bklit/scatter-chart",
+ "@bklit/grid",
+ "@bklit/x-axis",
+ "@bklit/chart-tooltip",
+ ],
+ dependencies: ["d3-scale", "d3-array", "motion", "react-use-measure"],
+ importFrom: "ScatterChart, Scatter, Grid, XAxis, ChartTooltip",
+ data: `const chartData = [
+ { date: new Date("2024-01-01"), sessions: 420, conversions: 28 },
+ { date: new Date("2024-02-01"), sessions: 510, conversions: 34 },
+ { date: new Date("2024-03-01"), sessions: 390, conversions: 22 },
+ { date: new Date("2024-04-01"), sessions: 580, conversions: 41 },
+ { date: new Date("2024-05-01"), sessions: 620, conversions: 38 },
+ { date: new Date("2024-06-01"), sessions: 710, conversions: 52 },
+];`,
+ body: `
+
+
+
+
+
+ `,
+ },
+ "pie-chart": {
+ registryDependencies: ["@bklit/pie-chart"],
+ dependencies: ["@visx/shape@4.0.1-alpha.0", "motion"],
+ importFrom: "PieChart, PieSlice, PieCenter",
+ data: `const pieData = [
+ { label: "Direct", value: 320 },
+ { label: "Organic", value: 280 },
+ { label: "Referral", value: 190 },
+ { label: "Social", value: 140 },
+];`,
+ body: `
+ {pieData.map((item, i) => (
+
+ ))}
+
+ `,
+ },
+ "gauge-chart": {
+ registryDependencies: ["@bklit/gauge-chart"],
+ dependencies: ["@visx/shape@4.0.1-alpha.0", "motion"],
+ importFrom: "Gauge",
+ data: "",
+ body: ` `,
+ },
+ "heatmap-chart": {
+ registryDependencies: ["@bklit/heatmap-chart"],
+ dependencies: [
+ "@visx/heatmap@4.0.1-alpha.0",
+ "@visx/shape@4.0.1-alpha.0",
+ "motion",
+ ],
+ importFrom:
+ "HeatmapCells, HeatmapChart, HeatmapInteractionBoundary, HeatmapInteractionProvider, HeatmapLegend, HeatmapTooltip, HeatmapXAxis, HeatmapYAxis",
+ data: `const data = [
+ {
+ bin: 0,
+ bins: [
+ { bin: 0, count: 2, date: new Date(2024, 0, 1) },
+ { bin: 1, count: 0, date: new Date(2024, 0, 2) },
+ { bin: 2, count: 3, date: new Date(2024, 0, 3) },
+ { bin: 3, count: 1, date: new Date(2024, 0, 4) },
+ { bin: 4, count: 4, date: new Date(2024, 0, 5) },
+ { bin: 5, count: 0, date: new Date(2024, 0, 6) },
+ { bin: 6, count: 1, date: new Date(2024, 0, 7) },
+ ],
+ },
+ {
+ bin: 1,
+ bins: [
+ { bin: 0, count: 1, date: new Date(2024, 0, 8) },
+ { bin: 1, count: 2, date: new Date(2024, 0, 9) },
+ { bin: 2, count: 0, date: new Date(2024, 0, 10) },
+ { bin: 3, count: 3, date: new Date(2024, 0, 11) },
+ { bin: 4, count: 2, date: new Date(2024, 0, 12) },
+ { bin: 5, count: 1, date: new Date(2024, 0, 13) },
+ { bin: 6, count: 0, date: new Date(2024, 0, 14) },
+ ],
+ },
+];`,
+ body: `
+
+
+
+
+
+
+
+
+
+
+
+ `,
+ },
+ "ring-chart": {
+ registryDependencies: ["@bklit/ring-chart"],
+ dependencies: ["@visx/shape@4.0.1-alpha.0", "motion"],
+ importFrom: "RingChart, Ring, RingCenter",
+ data: `const ringData = [
+ { label: "Email", value: 42 },
+ { label: "Social", value: 28 },
+ { label: "Direct", value: 18 },
+ { label: "Other", value: 12 },
+];`,
+ body: `
+ {ringData.map((item, i) => (
+
+ ))}
+
+ `,
+ },
+ "radar-chart": {
+ registryDependencies: ["@bklit/radar-chart"],
+ dependencies: ["@visx/shape@4.0.1-alpha.0", "motion"],
+ importFrom: "RadarChart, RadarGrid, RadarAxis, RadarLabels, RadarArea",
+ data: `const metrics = [
+ { key: "speed", label: "Speed" },
+ { key: "reliability", label: "Reliability" },
+ { key: "comfort", label: "Comfort" },
+ { key: "safety", label: "Safety" },
+ { key: "efficiency", label: "Efficiency" },
+];
+const data = [
+ { id: "a", speed: 80, reliability: 70, comfort: 60, safety: 90, efficiency: 75 },
+];`,
+ body: `
+
+
+
+ {data.map((row, i) => (
+
+ ))}
+ `,
+ },
+ "composed-chart": {
+ registryDependencies: [
+ "@bklit/composed-chart",
+ "@bklit/grid",
+ "@bklit/x-axis",
+ "@bklit/chart-tooltip",
+ ],
+ dependencies: [
+ "@visx/curve@4.0.1-alpha.0",
+ "@visx/shape@4.0.1-alpha.0",
+ "motion",
+ ],
+ importFrom:
+ "ComposedChart, SeriesBar, Area, Line, Grid, XAxis, ChartTooltip",
+ data: `const chartData = [
+ { date: new Date("2024-01-01"), revenue: 4200, runRate: 3800 },
+ { date: new Date("2024-02-01"), revenue: 5100, runRate: 4600 },
+ { date: new Date("2024-03-01"), revenue: 4800, runRate: 5200 },
+ { date: new Date("2024-04-01"), revenue: 5500, runRate: 5000 },
+];`,
+ body: `
+
+
+
+
+
+
+ `,
+ extraImports: `import { curveNatural } from "@visx/curve";`,
+ },
+ "funnel-chart": {
+ registryDependencies: ["@bklit/funnel-chart"],
+ dependencies: ["@visx/shape@4.0.1-alpha.0", "motion"],
+ importFrom: "FunnelChart",
+ data: `const funnelData = [
+ { label: "Visitors", value: 12000 },
+ { label: "Signups", value: 4800 },
+ { label: "Activated", value: 2100 },
+ { label: "Paid", value: 840 },
+];`,
+ body: ` `,
+ },
+ "sankey-chart": {
+ registryDependencies: ["@bklit/sankey-chart"],
+ dependencies: [
+ "@visx/sankey@4.0.1-alpha.0",
+ "@visx/shape@4.0.1-alpha.0",
+ "motion",
+ ],
+ importFrom: "SankeyChart, SankeyLink, SankeyNode, SankeyTooltip",
+ data: `const data = {
+ nodes: [
+ { name: "Ads" },
+ { name: "Organic" },
+ { name: "Landing" },
+ { name: "Product" },
+ { name: "Checkout" },
+ ],
+ links: [
+ { source: 0, target: 2, value: 40 },
+ { source: 1, target: 2, value: 30 },
+ { source: 2, target: 3, value: 50 },
+ { source: 3, target: 4, value: 35 },
+ ],
+};`,
+ body: `
+
+
+
+ `,
+ },
+ "candlestick-chart": {
+ registryDependencies: [
+ "@bklit/candlestick-chart",
+ "@bklit/grid",
+ "@bklit/x-axis",
+ "@bklit/y-axis",
+ "@bklit/chart-tooltip",
+ ],
+ dependencies: ["@visx/shape@4.0.1-alpha.0", "motion"],
+ importFrom:
+ "CandlestickChart, Candlestick, Grid, XAxis, YAxis, ChartTooltip",
+ data: `const ohlcData = [
+ { date: new Date("2024-01-02"), open: 100, high: 108, low: 98, close: 105 },
+ { date: new Date("2024-01-03"), open: 105, high: 110, low: 102, close: 103 },
+ { date: new Date("2024-01-04"), open: 103, high: 112, low: 101, close: 110 },
+ { date: new Date("2024-01-05"), open: 110, high: 115, low: 107, close: 108 },
+];`,
+ body: `
+
+
+
+
+
+ `,
+ },
+ "choropleth-chart": {
+ registryDependencies: ["@bklit/choropleth-chart"],
+ dependencies: ["@visx/geo@4.0.1-alpha.0", "d3-geo", "topojson-client"],
+ importFrom:
+ "ChoroplethChart, ChoroplethFeatureComponent, ChoroplethTooltip",
+ data: `const features = [
+ { id: "US", name: "United States", value: 120 },
+ { id: "CA", name: "Canada", value: 45 },
+ { id: "GB", name: "United Kingdom", value: 62 },
+];`,
+ body: `
+
+
+ `,
+ },
+ "live-line-chart": {
+ registryDependencies: ["@bklit/live-line-chart", "@bklit/chart-tooltip"],
+ dependencies: [
+ "@visx/curve@4.0.1-alpha.0",
+ "@visx/shape@4.0.1-alpha.0",
+ "motion",
+ ],
+ importFrom: "LiveLineChart, LiveLine, LiveXAxis, LiveYAxis, ChartTooltip",
+ data: `const initialData = Array.from({ length: 24 }, (_, i) => ({
+ time: Date.now() - (23 - i) * 60_000,
+ value: 50 + Math.sin(i / 3) * 20,
+}));`,
+ body: `
+
+
+
+
+ `,
+ extraImports: `import { curveNatural } from "@visx/curve";`,
+ },
+};
+
+mkdirSync(examplesDir, { recursive: true });
+
+for (const [slug, spec] of Object.entries(EXAMPLES)) {
+ const extra = spec.extraImports ? `${spec.extraImports}\n\n` : "";
+ const content = `"use client"
+
+// In your app (monorepo/npm): import { ${spec.importFrom} } from "@bklitui/ui/charts"
+import { ${spec.importFrom} } from "@/components/charts"
+
+${extra}${spec.data}
+
+export default function Component() {
+ return (
+
+
+ ${spec.body}
+
+
+ )
+}
+`;
+ writeFileSync(join(examplesDir, `${slug}.tsx`), content);
+
+ const indexExports = CHART_INDEX_EXPORTS[slug];
+ if (indexExports) {
+ writeFileSync(
+ join(examplesDir, `${slug}-index.ts`),
+ `// biome-ignore-all lint/performance/noBarrelFile: v0 registry example barrel for shadcn install\n${indexExports}\n`
+ );
+ }
+}
+
+const registryPath = join(root, "registry.json");
+const registry = JSON.parse(readFileSync(registryPath, "utf8"));
+
+registry.items = registry.items.filter(
+ (item) => !item.name.endsWith("-example")
+);
+
+const exampleItems = Object.entries(EXAMPLES).map(([slug, spec]) => {
+ const files = [
+ {
+ path: `registry/examples/${slug}.tsx`,
+ type: "registry:page",
+ target: "app/page.tsx",
+ },
+ ];
+ if (CHART_INDEX_EXPORTS[slug]) {
+ files.push({
+ path: `registry/examples/${slug}-index.ts`,
+ type: "registry:lib",
+ target: "components/charts/index.ts",
+ });
+ }
+ return {
+ name: `${slug}-example`,
+ type: "registry:example",
+ title: `${slug.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase())} Example`,
+ description: `Composable ${slug} demo for Open in v0`,
+ registryDependencies: spec.registryDependencies,
+ dependencies: spec.dependencies,
+ files,
+ };
+});
+
+registry.items.push(...exampleItems);
+
+writeFileSync(registryPath, `${JSON.stringify(registry, null, 2)}\n`);
+console.log(`Wrote ${Object.keys(EXAMPLES).length} v0 example registry items`);
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/scripts/run-tests.mjs b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/scripts/run-tests.mjs
new file mode 100644
index 0000000..ad965a5
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/scripts/run-tests.mjs
@@ -0,0 +1,24 @@
+import { spawnSync } from "node:child_process";
+import { readdirSync } from "node:fs";
+import { dirname, join } from "node:path";
+import { fileURLToPath } from "node:url";
+
+const packageRoot = join(dirname(fileURLToPath(import.meta.url)), "..");
+const testDir = join(packageRoot, "src/charts/__tests__");
+
+const tests = readdirSync(testDir)
+ .filter((name) => name.endsWith(".test.ts"))
+ .map((name) => join(testDir, name));
+
+if (tests.length === 0) {
+ console.error(`No test files found in ${testDir}`);
+ process.exit(1);
+}
+
+const result = spawnSync(
+ process.execPath,
+ ["--import", "tsx", "--test", ...tests],
+ { stdio: "inherit", cwd: packageRoot }
+);
+
+process.exit(result.status ?? 1);
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/scripts/verify-registry-deps.mjs b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/scripts/verify-registry-deps.mjs
new file mode 100644
index 0000000..5acf555
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/scripts/verify-registry-deps.mjs
@@ -0,0 +1,169 @@
+#!/usr/bin/env node
+/**
+ * Verifies registry item `dependencies` cover all npm imports in the item's
+ * file tree plus transitive @bklit registryDependencies (shadcn / v0 install closure).
+ */
+import fs from "node:fs";
+import path from "node:path";
+import { fileURLToPath } from "node:url";
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
+const uiRoot = path.join(__dirname, "..");
+const registryPath = path.join(uiRoot, "registry.json");
+
+const registry = JSON.parse(fs.readFileSync(registryPath, "utf8"));
+const byName = Object.fromEntries(
+ registry.items.map((item) => [item.name, item])
+);
+
+const importRe =
+ /(?:import|export)\s+(?:type\s+)?(?:[^'"]+\s+from\s+)?['"]([^'"]+)['"]/g;
+const DEP_VERSION_SUFFIX_RE = /@[\d.]+(-[\w.]+)?$/;
+const BKLIT_REGISTRY_DEP_RE = /^@bklit\/(.+)$/;
+
+function getPackageName(spec) {
+ if (spec.startsWith(".") || spec.startsWith("@/")) {
+ return null;
+ }
+ if (spec.startsWith("@")) {
+ const parts = spec.split("/");
+ return `${parts[0]}/${parts[1]}`;
+ }
+ return spec.split("/")[0];
+}
+
+function normalizeDep(dep) {
+ return dep.replace(DEP_VERSION_SUFFIX_RE, "");
+}
+
+/** Import specifiers that are satisfied by @types/* registry dependencies. */
+const TYPES_ALIASES = {
+ geojson: ["geojson", "@types/geojson"],
+};
+
+function isDeclared(pkg, declared) {
+ if (declared.has(pkg)) {
+ return true;
+ }
+ const aliases = TYPES_ALIASES[pkg];
+ if (aliases) {
+ return aliases.some((name) => declared.has(name));
+ }
+ return false;
+}
+
+function scanFiles(files) {
+ const used = new Set();
+ for (const file of files ?? []) {
+ const fullPath = path.join(uiRoot, file.path);
+ if (!fs.existsSync(fullPath)) {
+ throw new Error(`Registry file not found: ${file.path}`);
+ }
+ const content = fs.readFileSync(fullPath, "utf8");
+ importRe.lastIndex = 0;
+ let match = importRe.exec(content);
+ while (match) {
+ const pkg = getPackageName(match[1]);
+ if (pkg && pkg !== "react" && pkg !== "react-dom") {
+ used.add(pkg);
+ }
+ match = importRe.exec(content);
+ }
+ }
+ return used;
+}
+
+function getRegistryDepName(dep) {
+ const m = dep.match(BKLIT_REGISTRY_DEP_RE);
+ return m ? m[1] : null;
+}
+
+function collectDeclared(name, visited = new Set()) {
+ const declared = new Set();
+ const visit = (itemName) => {
+ if (visited.has(itemName)) {
+ return;
+ }
+ visited.add(itemName);
+ const item = byName[itemName];
+ if (!item) {
+ return;
+ }
+ for (const dep of item.dependencies ?? []) {
+ declared.add(normalizeDep(dep));
+ }
+ for (const regDep of item.registryDependencies ?? []) {
+ const nested = getRegistryDepName(regDep);
+ if (nested) {
+ visit(nested);
+ }
+ }
+ };
+ visit(name);
+ return declared;
+}
+
+function collectUsed(name, visited = new Set()) {
+ const used = new Set();
+ const visit = (itemName) => {
+ if (visited.has(itemName)) {
+ return;
+ }
+ visited.add(itemName);
+ const item = byName[itemName];
+ if (!item) {
+ return;
+ }
+ for (const pkg of scanFiles(item.files)) {
+ used.add(pkg);
+ }
+ for (const regDep of item.registryDependencies ?? []) {
+ const nested = getRegistryDepName(regDep);
+ if (nested) {
+ visit(nested);
+ }
+ }
+ };
+ visit(name);
+ return used;
+}
+
+const chartItems = registry.items.filter(
+ (item) =>
+ item.type === "registry:component" &&
+ (item.name.endsWith("-chart") ||
+ item.name === "chart-context" ||
+ item.name === "chart-animation")
+);
+
+const utilityItems = registry.items.filter((item) =>
+ ["grid", "x-axis", "y-axis", "chart-tooltip", "legend", "markers"].includes(
+ item.name
+ )
+);
+
+const failures = [];
+
+for (const item of [...chartItems, ...utilityItems]) {
+ const declared = collectDeclared(item.name);
+ const used = collectUsed(item.name);
+ const missing = [...used].filter((pkg) => !isDeclared(pkg, declared)).sort();
+ if (missing.length > 0) {
+ failures.push({ name: item.name, missing });
+ }
+}
+
+if (failures.length > 0) {
+ console.error("Registry dependency closure check failed:\n");
+ for (const { name, missing } of failures) {
+ console.error(` ${name}: missing ${missing.join(", ")}`);
+ }
+ console.error(
+ "\nAdd missing packages to `dependencies` in packages/ui/registry.json (on the item or a registryDependency)."
+ );
+ process.exit(1);
+}
+
+console.log(
+ `Registry dependency closure OK (${chartItems.length + utilityItems.length} items checked).`
+);
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/scripts/verify-registry-files.mjs b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/scripts/verify-registry-files.mjs
new file mode 100644
index 0000000..c3c7b5c
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/scripts/verify-registry-files.mjs
@@ -0,0 +1,178 @@
+#!/usr/bin/env node
+/**
+ * Verifies every installable registry item ships all local `./` imports in its
+ * transitive file closure (item files + registryDependencies).
+ *
+ * Also rejects CommonJS `require("react-dom")` in registry source files — it
+ * breaks Vite/ESM consumers even though Next.js/webpack may tolerate it.
+ */
+import fs from "node:fs";
+import path from "node:path";
+import { fileURLToPath } from "node:url";
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
+const uiRoot = path.join(__dirname, "..");
+const registryPath = path.join(uiRoot, "registry.json");
+
+const registry = JSON.parse(fs.readFileSync(registryPath, "utf8"));
+const byName = Object.fromEntries(
+ registry.items.map((item) => [item.name, item])
+);
+
+const importRe =
+ /(?:import|export)\s+(?:type\s+)?(?:[^'"]+\s+from\s+)?['"](\.[^'"]+)['"]/g;
+const BKLIT_REGISTRY_DEP_RE = /^@bklit\/(.+)$/;
+
+const RESOLVE_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx"];
+
+function resolveRelativeImport(fromFilePath, spec) {
+ const dir = path.dirname(fromFilePath);
+ const base = path.normalize(path.join(dir, spec));
+ const candidates = [
+ base,
+ ...RESOLVE_EXTENSIONS.map((ext) => base + ext),
+ ...RESOLVE_EXTENSIONS.map((ext) => path.join(base, `index${ext}`)),
+ ];
+
+ for (const candidate of candidates) {
+ const fullPath = path.join(uiRoot, candidate);
+ if (fs.existsSync(fullPath)) {
+ return candidate;
+ }
+ }
+
+ return null;
+}
+
+function collectItemFiles(itemName, visited = new Set()) {
+ const files = new Set();
+ const visit = (name) => {
+ if (visited.has(name)) {
+ return;
+ }
+ visited.add(name);
+ const item = byName[name];
+ if (!item) {
+ return;
+ }
+ for (const file of item.files ?? []) {
+ files.add(file.path);
+ }
+ for (const regDep of item.registryDependencies ?? []) {
+ const nested = regDep.match(BKLIT_REGISTRY_DEP_RE)?.[1];
+ if (nested) {
+ visit(nested);
+ }
+ }
+ };
+ visit(itemName);
+ return files;
+}
+
+function scanRelativeImports(filePath) {
+ // v0/shadcn example barrels re-export co-installed chart files from the
+ // target `components/charts/` directory — not from registry source paths.
+ if (
+ filePath.startsWith("registry/examples/") &&
+ filePath.endsWith("-index.ts")
+ ) {
+ return { imports: [], content: "" };
+ }
+
+ const fullPath = path.join(uiRoot, filePath);
+ if (!fs.existsSync(fullPath)) {
+ throw new Error(`Registry file not found: ${filePath}`);
+ }
+ const content = fs.readFileSync(fullPath, "utf8");
+ const imports = [];
+ importRe.lastIndex = 0;
+ let match = importRe.exec(content);
+ while (match) {
+ imports.push(match[1]);
+ match = importRe.exec(content);
+ }
+ return { imports, content };
+}
+
+const INSTALLABLE_TYPES = new Set([
+ "registry:component",
+ "registry:lib",
+ "registry:block",
+]);
+
+const installableItems = registry.items.filter((item) =>
+ INSTALLABLE_TYPES.has(item.type)
+);
+
+const closureFailures = [];
+const requireFailures = [];
+
+for (const item of installableItems) {
+ const closure = collectItemFiles(item.name);
+ const missing = new Map();
+
+ for (const filePath of closure) {
+ const { imports, content } = scanRelativeImports(filePath);
+
+ if (content.includes('require("react-dom")')) {
+ requireFailures.push({ item: item.name, file: filePath });
+ }
+
+ for (const spec of imports) {
+ const resolved = resolveRelativeImport(filePath, spec);
+ if (!resolved) {
+ const key = `${filePath} -> ${spec}`;
+ if (!missing.has(key)) {
+ missing.set(key, "missing on disk");
+ }
+ continue;
+ }
+ if (!closure.has(resolved)) {
+ const key = `${filePath} -> ${spec} (${resolved})`;
+ if (!missing.has(key)) {
+ missing.set(key, "not in install closure");
+ }
+ }
+ }
+ }
+
+ if (missing.size > 0) {
+ closureFailures.push({ name: item.name, missing: [...missing.entries()] });
+ }
+}
+
+let failed = false;
+
+if (closureFailures.length > 0) {
+ failed = true;
+ console.error("Registry local import closure check failed:\n");
+ for (const { name, missing } of closureFailures) {
+ console.error(` ${name}:`);
+ for (const [ref, reason] of missing) {
+ console.error(` - ${ref} (${reason})`);
+ }
+ console.error("");
+ }
+ console.error(
+ "Add missing files to packages/ui/registry.json (on the item or a registryDependency)."
+ );
+}
+
+if (requireFailures.length > 0) {
+ failed = true;
+ console.error('Registry source must not use require("react-dom"):\n');
+ for (const { item, file } of requireFailures) {
+ console.error(` ${item}: ${file}`);
+ }
+ console.error(
+ '\nUse `import { createPortal } from "react-dom"` in "use client" components instead.'
+ );
+}
+
+if (failed) {
+ process.exit(1);
+}
+
+console.log(
+ `Registry file closure OK (${installableItems.length} installable items checked).`
+);
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/scripts/verify-registry-sync.mjs b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/scripts/verify-registry-sync.mjs
new file mode 100644
index 0000000..68c3f0d
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/scripts/verify-registry-sync.mjs
@@ -0,0 +1,139 @@
+#!/usr/bin/env node
+/**
+ * Ensures shadcn registry output matches chart source when packages/ui/src/charts changes.
+ * Run via `pnpm registry:verify` (locally or in CI).
+ */
+import { execSync } from "node:child_process";
+import fs from "node:fs";
+import { dirname, join } from "node:path";
+import { fileURLToPath } from "node:url";
+
+const __dirname = dirname(fileURLToPath(import.meta.url));
+const repoRoot = join(__dirname, "../../..");
+
+const CHARTS_SRC = "packages/ui/src/charts";
+const REGISTRY_PATHS = [
+ "apps/web/public/r",
+ "packages/ui/registry.json",
+ "packages/ui/registry/examples",
+ "packages/ui/registry/blocks",
+];
+
+/** Build scripts may reformat these; compare parsed JSON, not raw text. */
+const SEMANTIC_JSON_PATHS = [
+ "packages/ui/registry.json",
+ "apps/web/public/r/registry.json",
+];
+
+function run(cmd, { inherit = false } = {}) {
+ return execSync(cmd, {
+ cwd: repoRoot,
+ encoding: "utf8",
+ stdio: inherit ? "inherit" : "pipe",
+ });
+}
+
+function readJson(relativePath) {
+ return JSON.parse(fs.readFileSync(join(repoRoot, relativePath), "utf8"));
+}
+
+function jsonEqual(a, b) {
+ return JSON.stringify(a) === JSON.stringify(b);
+}
+
+function resolveDiffRange() {
+ if (process.env.REGISTRY_SYNC_DIFF_RANGE) {
+ return process.env.REGISTRY_SYNC_DIFF_RANGE;
+ }
+
+ if (process.env.GITHUB_EVENT_NAME === "pull_request") {
+ const base = process.env.GITHUB_BASE_REF || "main";
+ return `origin/${base}...HEAD`;
+ }
+
+ if (
+ process.env.GITHUB_EVENT_NAME === "push" &&
+ process.env.GITHUB_EVENT_BEFORE &&
+ process.env.GITHUB_SHA
+ ) {
+ return `${process.env.GITHUB_EVENT_BEFORE}...${process.env.GITHUB_SHA}`;
+ }
+
+ return "origin/main...HEAD";
+}
+
+function chartsChanged(diffRange) {
+ const changed = run(
+ `git diff --name-only ${diffRange} -- ${CHARTS_SRC}`
+ ).trim();
+ return changed.length > 0;
+}
+
+function listDirtyRegistryFiles() {
+ const excludeArgs = SEMANTIC_JSON_PATHS.map((p) => `':!${p}'`).join(" ");
+ const paths = REGISTRY_PATHS.join(" ");
+ const status = run(
+ `git status --porcelain -- ${paths} ${excludeArgs}`
+ ).trim();
+ if (!status) {
+ return [];
+ }
+ return status.split("\n").filter(Boolean);
+}
+
+function listSemanticRegistryDrift(beforeByPath) {
+ const drift = [];
+ for (const relativePath of SEMANTIC_JSON_PATHS) {
+ const after = readJson(relativePath);
+ if (!jsonEqual(beforeByPath[relativePath], after)) {
+ drift.push(relativePath);
+ }
+ }
+ return drift;
+}
+
+function main() {
+ const diffRange = resolveDiffRange();
+
+ if (!chartsChanged(diffRange)) {
+ console.log(
+ `No changes in ${CHARTS_SRC} (${diffRange}); registry sync check skipped.`
+ );
+ process.exit(0);
+ }
+
+ console.log(
+ `Chart source changed (${diffRange}); rebuilding registry to verify sync...`
+ );
+
+ const registryBefore = Object.fromEntries(
+ SEMANTIC_JSON_PATHS.map((relativePath) => [
+ relativePath,
+ readJson(relativePath),
+ ])
+ );
+
+ run("pnpm registry:build", { inherit: true });
+
+ const semanticDrift = listSemanticRegistryDrift(registryBefore);
+ const dirty = listDirtyRegistryFiles();
+ const failures = [
+ ...semanticDrift.map((path) => `${path} (content changed)`),
+ ...dirty,
+ ];
+
+ if (failures.length > 0) {
+ console.error("\nRegistry is out of sync with chart source changes.\n");
+ console.error(
+ "Run `pnpm registry:build` from the repo root and commit the updated registry files:\n"
+ );
+ for (const line of failures) {
+ console.error(` ${line}`);
+ }
+ process.exit(1);
+ }
+
+ console.log("Registry is in sync with chart source.");
+}
+
+main();
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/button.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/button.tsx
new file mode 100644
index 0000000..9c8e325
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/button.tsx
@@ -0,0 +1,21 @@
+"use client";
+
+import type { ReactNode } from "react";
+
+interface ButtonProps {
+ children: ReactNode;
+ className?: string;
+ appName: string;
+}
+
+export const Button = ({ children, className, appName }: ButtonProps) => {
+ return (
+ console.log(`Hello from your ${appName} app!`)}
+ type="button"
+ >
+ {children}
+
+ );
+};
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/card.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/card.tsx
new file mode 100644
index 0000000..3f99a89
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/card.tsx
@@ -0,0 +1,27 @@
+import type { JSX } from "react";
+
+export function Card({
+ className,
+ title,
+ children,
+ href,
+}: {
+ className?: string;
+ title: string;
+ children: React.ReactNode;
+ href: string;
+}): JSX.Element {
+ return (
+
+
+ {title} ->
+
+ {children}
+
+ );
+}
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/chart-grid.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/chart-grid.tsx
new file mode 100644
index 0000000..2b29a4c
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/chart-grid.tsx
@@ -0,0 +1,116 @@
+"use client";
+
+import { GridColumns, GridRows } from "@visx/grid";
+import type { scaleLinear, scaleTime } from "@visx/scale";
+import { useId } from "react";
+
+type ScaleLinear = ReturnType<
+ typeof scaleLinear
+>;
+type ScaleTime = ReturnType<
+ typeof scaleTime
+>;
+
+export interface ChartGridProps {
+ /** Width of the grid area */
+ width: number;
+ /** Height of the grid area */
+ height: number;
+ /** X scale (time scale) */
+ xScale: ScaleTime;
+ /** Y scale (linear scale) */
+ yScale: ScaleLinear;
+ /** Show horizontal grid lines. Default: true */
+ showRows?: boolean;
+ /** Show vertical grid lines. Default: false */
+ showColumns?: boolean;
+ /** Number of horizontal grid lines. Default: 5 */
+ numTicksRows?: number;
+ /** Number of vertical grid lines. Default: 10 */
+ numTicksColumns?: number;
+ /** Grid line stroke color. Default: var(--chart-grid) */
+ stroke?: string;
+ /** Grid line stroke opacity. Default: 1 */
+ strokeOpacity?: number;
+ /** Grid line stroke width. Default: 1 */
+ strokeWidth?: number;
+ /** Grid line dash array. Default: "4,4" for dashed lines */
+ strokeDasharray?: string;
+ /** Enable horizontal fade effect on grid rows. Default: true */
+ fadeRows?: boolean;
+}
+
+export function ChartGrid({
+ width,
+ height,
+ xScale,
+ yScale,
+ showRows = true,
+ showColumns = false,
+ numTicksRows = 5,
+ numTicksColumns = 10,
+ stroke = "var(--chart-grid)",
+ strokeOpacity = 1,
+ strokeWidth = 1,
+ strokeDasharray = "4,4",
+ fadeRows = true,
+}: ChartGridProps) {
+ const uniqueId = useId();
+ const maskId = `chart-grid-rows-fade-${uniqueId}`;
+ const gradientId = `${maskId}-gradient`;
+
+ return (
+
+ {/* Gradient mask for horizontal grid lines - fades at both ends */}
+ {showRows && fadeRows && (
+
+
+
+
+
+
+
+
+
+
+
+ )}
+
+ {showRows && (
+
+
+
+ )}
+ {showColumns && (
+
+ )}
+
+ );
+}
+
+export default ChartGrid;
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/__tests__/animation.test.ts b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/__tests__/animation.test.ts
new file mode 100644
index 0000000..5286c59
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/__tests__/animation.test.ts
@@ -0,0 +1,38 @@
+import assert from "node:assert/strict";
+import { describe, it } from "node:test";
+import {
+ clipRevealTransition,
+ DEFAULT_CHART_ENTER_TRANSITION,
+} from "../animation";
+
+describe("clipRevealTransition", () => {
+ it("preserves explicit tween ease", () => {
+ const tween = {
+ type: "tween" as const,
+ duration: 0.9,
+ ease: [0.1, 0.2, 0.3, 0.4] as [number, number, number, number],
+ };
+ assert.deepEqual(clipRevealTransition(tween), tween);
+ });
+
+ it("applies default ease when tween omits ease", () => {
+ const result = clipRevealTransition({
+ type: "tween",
+ duration: 0.9,
+ });
+ assert.equal(result.type, "tween");
+ assert.equal(result.duration, 0.9);
+ assert.deepEqual(result.ease, DEFAULT_CHART_ENTER_TRANSITION.ease);
+ });
+
+ it("converts spring to tween for svg width reveal", () => {
+ const result = clipRevealTransition({
+ type: "spring",
+ duration: 1.2,
+ bounce: 0.5,
+ });
+ assert.equal(result.type, "tween");
+ assert.equal(result.duration, 1.2);
+ assert.deepEqual(result.ease, DEFAULT_CHART_ENTER_TRANSITION.ease);
+ });
+});
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/__tests__/bar-depth-geometry.test.ts b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/__tests__/bar-depth-geometry.test.ts
new file mode 100644
index 0000000..4a0195e
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/__tests__/bar-depth-geometry.test.ts
@@ -0,0 +1,54 @@
+import assert from "node:assert/strict";
+import { describe, it } from "node:test";
+import {
+ BAR_DEPTH_MAX_PX,
+ BAR_DEPTH_PERSPECTIVE_RATIO,
+ barDepthAndRise,
+ barDepthMaxDepth,
+} from "../bar-depth-geometry";
+
+describe("barDepthMaxDepth", () => {
+ it("caps at BAR_DEPTH_MAX_PX when the gap is wide", () => {
+ // step 100, band 90 → gap 10, gap-1=9; band*0.22=19.8 → min(19.8, 9, 7) = 7
+ assert.equal(barDepthMaxDepth(100, 90), BAR_DEPTH_MAX_PX);
+ });
+
+ it("is bounded by the inter-bar gap on dense charts", () => {
+ // step 50, band 48 → gap 2, gap-1=1; band*0.22=10.56 → min(10.56, 1, 7) = 1
+ assert.equal(barDepthMaxDepth(50, 48), 1);
+ });
+
+ it("is 0 when there is no gap between bars", () => {
+ assert.equal(barDepthMaxDepth(50, 50), 0);
+ });
+});
+
+describe("barDepthAndRise", () => {
+ it("returns no depth for a dead-center bar (absOffset 0)", () => {
+ assert.deepEqual(barDepthAndRise(0, 100, 7), {
+ depth: 0,
+ perspectiveRise: 0,
+ });
+ });
+
+ it("scales depth with offset, capped by maxDepth at the edge", () => {
+ assert.equal(barDepthAndRise(1, 100, 7).depth, 7);
+ assert.equal(barDepthAndRise(0.5, 100, 7).depth, 3.5);
+ });
+
+ it("caps depth by the bar height so short bars stay proportional", () => {
+ // naturalHeight 4 < maxDepth 7 → depth capped at 4
+ assert.equal(barDepthAndRise(1, 4, 7).depth, 4);
+ });
+
+ it("derives the rise from depth via the perspective ratio", () => {
+ const { depth, perspectiveRise } = barDepthAndRise(0.5, 100, 7);
+ assert.equal(perspectiveRise, depth * BAR_DEPTH_PERSPECTIVE_RATIO);
+ });
+
+ it("clamps out-of-range inputs defensively", () => {
+ assert.equal(barDepthAndRise(2, 100, 7).depth, 7); // absOffset clamped to 1
+ assert.equal(barDepthAndRise(-1, 100, 7).depth, 0); // clamped to 0
+ assert.equal(barDepthAndRise(1, -5, 7).depth, 0); // negative height → 0
+ });
+});
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/__tests__/chart-formatters.test.ts b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/__tests__/chart-formatters.test.ts
new file mode 100644
index 0000000..0a2ae15
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/__tests__/chart-formatters.test.ts
@@ -0,0 +1,76 @@
+import assert from "node:assert/strict";
+import { describe, it } from "node:test";
+import {
+ hmsTimeFmt,
+ intFmt,
+ shortDateFmt,
+ weekdayDateFmt,
+} from "../chart-formatters";
+
+const sampleDates = [
+ new Date(2025, 0, 5, 9, 8, 7),
+ new Date(2024, 11, 31, 23, 59, 59),
+ new Date(2026, 6, 4, 12, 0, 0),
+];
+
+const sampleNumbers = [0, 42, 1234, 1_234_567, -9876.5];
+
+describe("chart-formatters", () => {
+ describe("shortDateFmt", () => {
+ for (const date of sampleDates) {
+ it(`matches toLocaleDateString for ${date.toISOString()}`, () => {
+ assert.equal(
+ shortDateFmt.format(date),
+ date.toLocaleDateString("en-US", {
+ month: "short",
+ day: "numeric",
+ })
+ );
+ });
+ }
+ });
+
+ describe("weekdayDateFmt", () => {
+ for (const date of sampleDates) {
+ it(`matches toLocaleDateString for ${date.toISOString()}`, () => {
+ assert.equal(
+ weekdayDateFmt.format(date),
+ date.toLocaleDateString("en-US", {
+ weekday: "short",
+ month: "short",
+ day: "numeric",
+ })
+ );
+ });
+ }
+ });
+
+ describe("hmsTimeFmt", () => {
+ for (const date of sampleDates) {
+ it(`matches toLocaleTimeString for ${date.toISOString()}`, () => {
+ assert.equal(
+ hmsTimeFmt.format(date),
+ date.toLocaleTimeString("en-US", {
+ hour: "2-digit",
+ minute: "2-digit",
+ second: "2-digit",
+ hour12: false,
+ })
+ );
+ });
+ }
+ });
+
+ describe("intFmt", () => {
+ for (const value of sampleNumbers) {
+ it(`matches toLocaleString for ${value}`, () => {
+ assert.equal(intFmt(value), value.toLocaleString("en-US"));
+ });
+ }
+
+ it("is a reusable formatter function", () => {
+ const formatValue = intFmt;
+ assert.equal(formatValue(1000), (1000).toLocaleString("en-US"));
+ });
+ });
+});
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/__tests__/decimate-time-series.test.ts b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/__tests__/decimate-time-series.test.ts
new file mode 100644
index 0000000..d7cc192
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/__tests__/decimate-time-series.test.ts
@@ -0,0 +1,56 @@
+import assert from "node:assert/strict";
+import { describe, it } from "node:test";
+import {
+ decimateOhlcData,
+ decimateTimeSeries,
+ maxRenderPointsForWidth,
+} from "../decimate-time-series";
+
+describe("decimateTimeSeries", () => {
+ it("returns the original array when under the point budget", () => {
+ const data = [{ v: 1 }, { v: 2 }, { v: 3 }];
+ assert.equal(decimateTimeSeries(data, 10), data);
+ });
+
+ it("always keeps the first and last points", () => {
+ const data = Array.from({ length: 100 }, (_, i) => ({ v: i }));
+ const sampled = decimateTimeSeries(data, 20, ["v"]);
+ assert.equal(sampled[0]?.v, 0);
+ assert.equal(sampled.at(-1)?.v, 99);
+ assert.equal(sampled.length, 20);
+ });
+
+ it("preserves spikes in the series", () => {
+ const data = Array.from({ length: 50 }, (_, i) => ({
+ v: i === 25 ? 1000 : i,
+ }));
+ const sampled = decimateTimeSeries(data, 10, ["v"]);
+ assert(sampled.some((point) => point.v === 1000));
+ });
+});
+
+describe("decimateOhlcData", () => {
+ it("preserves bucket high/low extremes", () => {
+ const data = Array.from({ length: 100 }, (_, i) => ({
+ date: new Date(i),
+ open: i,
+ high: i === 50 ? 999 : i + 5,
+ low: i === 50 ? 1 : i - 5,
+ close: i + 1,
+ }));
+ const sampled = decimateOhlcData(data, 10);
+ assert(sampled.some((row) => row.high === 999));
+ assert(sampled.some((row) => row.low === 1));
+ assert.equal(sampled.length, 10);
+ });
+});
+
+describe("maxRenderPointsForWidth", () => {
+ it("returns at least 64 points", () => {
+ assert.equal(maxRenderPointsForWidth(10), 64);
+ });
+
+ it("scales with chart width", () => {
+ assert.equal(maxRenderPointsForWidth(400), 600);
+ });
+});
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/__tests__/highlight-segment-bounds.test.ts b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/__tests__/highlight-segment-bounds.test.ts
new file mode 100644
index 0000000..285f2e9
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/__tests__/highlight-segment-bounds.test.ts
@@ -0,0 +1,69 @@
+import assert from "node:assert/strict";
+import { describe, it } from "node:test";
+import { computeSegmentBounds } from "../highlight-segment-bounds";
+
+// Data points at t = 0,10,20,30 ms; a linear pixel xScale (1px per ms) keeps the
+// arithmetic obvious — the band is the pixel x-range one data point either side.
+const data = [{ t: 0 }, { t: 10 }, { t: 20 }, { t: 30 }];
+const xAccessor = (d: Record) => new Date(d.t as number);
+const xScale = (value: Date) => value.getTime();
+
+describe("computeSegmentBounds", () => {
+ it("is inactive for empty data", () => {
+ assert.deepEqual(
+ computeSegmentBounds([], xScale, xAccessor, { index: 1 }, null),
+ { x: 0, width: 0, isActive: false }
+ );
+ });
+
+ it("is inactive with no hover and no selection", () => {
+ assert.equal(
+ computeSegmentBounds(data, xScale, xAccessor, null, null).isActive,
+ false
+ );
+ });
+
+ it("spans one data point either side of the hovered index", () => {
+ // idx 1 → [t 0, t 20] → x 0, width 20
+ assert.deepEqual(
+ computeSegmentBounds(data, xScale, xAccessor, { index: 1 }, null),
+ { x: 0, width: 20, isActive: true }
+ );
+ });
+
+ it("clamps the start at the first index", () => {
+ // idx 0 → [t 0, t 10] → x 0, width 10
+ assert.deepEqual(
+ computeSegmentBounds(data, xScale, xAccessor, { index: 0 }, null),
+ { x: 0, width: 10, isActive: true }
+ );
+ });
+
+ it("clamps the end at the last index", () => {
+ // idx 3 (last) → [t 20, t 30] → x 20, width 10
+ assert.deepEqual(
+ computeSegmentBounds(data, xScale, xAccessor, { index: 3 }, null),
+ { x: 20, width: 10, isActive: true }
+ );
+ });
+
+ it("uses the dragged pixel range for an active selection (takes priority)", () => {
+ const bounds = computeSegmentBounds(
+ data,
+ xScale,
+ xAccessor,
+ { index: 1 }, // ignored — selection wins
+ { active: true, startX: 5, endX: 25 }
+ );
+ assert.deepEqual(bounds, { x: 5, width: 20, isActive: true });
+ });
+
+ it("normalizes a reversed (right-to-left) selection drag", () => {
+ const bounds = computeSegmentBounds(data, xScale, xAccessor, null, {
+ active: true,
+ startX: 25,
+ endX: 5,
+ });
+ assert.deepEqual(bounds, { x: 5, width: 20, isActive: true });
+ });
+});
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/__tests__/line-loading-pulse.test.ts b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/__tests__/line-loading-pulse.test.ts
new file mode 100644
index 0000000..04402b3
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/__tests__/line-loading-pulse.test.ts
@@ -0,0 +1,17 @@
+import assert from "node:assert/strict";
+import { describe, it } from "node:test";
+import { resolveLineLoadingPulseMode } from "../line-loading-pulse";
+
+describe("resolveLineLoadingPulseMode", () => {
+ it("maps loading phases to pulse modes", () => {
+ assert.equal(resolveLineLoadingPulseMode("loading"), "loop");
+ assert.equal(resolveLineLoadingPulseMode("exiting"), "exit");
+ assert.equal(resolveLineLoadingPulseMode("revealingLoading"), "enter");
+ });
+
+ it("returns null for non-loading phases", () => {
+ assert.equal(resolveLineLoadingPulseMode("ready"), null);
+ assert.equal(resolveLineLoadingPulseMode("revealing"), null);
+ assert.equal(resolveLineLoadingPulseMode("exitingReady"), null);
+ });
+});
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/__tests__/loading-sweep.test.ts b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/__tests__/loading-sweep.test.ts
new file mode 100644
index 0000000..be85a68
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/__tests__/loading-sweep.test.ts
@@ -0,0 +1,30 @@
+import assert from "node:assert/strict";
+import { describe, it } from "node:test";
+import { getSkeletonHeights } from "../loading-sweep";
+
+describe("getSkeletonHeights", () => {
+ it("returns the requested number of heights", () => {
+ assert.equal(getSkeletonHeights(12).length, 12);
+ assert.equal(getSkeletonHeights(0).length, 0);
+ });
+
+ it("is deterministic for the same (count, seed) — SSR-safe", () => {
+ assert.deepEqual(getSkeletonHeights(8, 3), getSkeletonHeights(8, 3));
+ });
+
+ it("re-rolls when the seed changes", () => {
+ assert.notDeepEqual(getSkeletonHeights(8, 0), getSkeletonHeights(8, 1));
+ });
+
+ it("stays within the default [20, 80) range", () => {
+ for (const h of getSkeletonHeights(50, 7)) {
+ assert.ok(h >= 20 && h < 80, `height ${h} out of default range`);
+ }
+ });
+
+ it("respects a custom range", () => {
+ for (const h of getSkeletonHeights(50, 2, 40, 60)) {
+ assert.ok(h >= 40 && h < 60, `height ${h} out of [40, 60)`);
+ }
+ });
+});
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/__tests__/profit-loss-segments.test.ts b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/__tests__/profit-loss-segments.test.ts
new file mode 100644
index 0000000..c8a50f5
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/__tests__/profit-loss-segments.test.ts
@@ -0,0 +1,55 @@
+import assert from "node:assert/strict";
+import { describe, it } from "node:test";
+import { splitProfitLossSegments } from "../profit-loss-segments";
+
+const xAccessor = (d: Record) => d.date as Date;
+
+describe("splitProfitLossSegments", () => {
+ it("returns empty array for no data", () => {
+ assert.deepEqual(
+ splitProfitLossSegments({
+ data: [],
+ dataKey: "pnl",
+ xAccessor,
+ }),
+ []
+ );
+ });
+
+ it("splits at zero crossings with interpolated crossing points", () => {
+ const data = [
+ { date: new Date("2024-01-01"), pnl: 100 },
+ { date: new Date("2024-01-02"), pnl: -50 },
+ { date: new Date("2024-01-03"), pnl: -20 },
+ ];
+
+ const segments = splitProfitLossSegments({
+ data,
+ dataKey: "pnl",
+ xAccessor,
+ });
+
+ assert.equal(segments.length, 2);
+ assert.equal(segments[0]?.isPositive, true);
+ assert.equal(segments[1]?.isPositive, false);
+ assert.equal(segments[0]?.data.at(-1)?.pnl, 0);
+ assert.equal(segments[1]?.data[0]?.pnl, 0);
+ });
+
+ it("keeps a single segment when values stay on one side of zero", () => {
+ const data = [
+ { date: new Date("2024-01-01"), pnl: 10 },
+ { date: new Date("2024-01-02"), pnl: 20 },
+ ];
+
+ const segments = splitProfitLossSegments({
+ data,
+ dataKey: "pnl",
+ xAccessor,
+ });
+
+ assert.equal(segments.length, 1);
+ assert.equal(segments[0]?.isPositive, true);
+ assert.equal(segments[0]?.data.length, 2);
+ });
+});
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/__tests__/projection-utils.test.ts b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/__tests__/projection-utils.test.ts
new file mode 100644
index 0000000..6afb328
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/__tests__/projection-utils.test.ts
@@ -0,0 +1,50 @@
+import assert from "node:assert/strict";
+import { describe, it } from "node:test";
+import {
+ buildHorizontalTangentBezierPath,
+ buildProjectionPath,
+} from "../projection-utils";
+
+const sourceData = Array.from({ length: 6 }, (_, index) => ({
+ date: new Date(2024, 0, index + 1),
+ desktop: 100 + index * 10,
+}));
+
+describe("buildProjectionPath", () => {
+ it("extends auto projections from the last point to the horizon", () => {
+ const path = buildProjectionPath({
+ sourceData,
+ seriesKey: "desktop",
+ mode: "auto",
+ autoMethod: "linearRegression",
+ horizonPoints: 3,
+ });
+
+ assert.equal(path.length, 2);
+ assert.equal(path[0]?.value, 150);
+ assert.ok((path.at(-1)?.value ?? 0) > 150);
+ });
+
+ it("builds a target projection to an end value", () => {
+ const path = buildProjectionPath({
+ sourceData,
+ seriesKey: "desktop",
+ mode: "target",
+ horizonPoints: 3,
+ endValue: 220,
+ });
+
+ assert.equal(path.length, 2);
+ assert.equal(path[0]?.value, 150);
+ assert.equal(path[1]?.value, 220);
+ });
+});
+
+const HORIZONTAL_TANGENT_BEZIER_PATH = /^M 0,100 C 90,100 110,50 200,50$/;
+
+describe("buildHorizontalTangentBezierPath", () => {
+ it("uses horizontal control points at start and end y", () => {
+ const path = buildHorizontalTangentBezierPath(0, 100, 200, 50);
+ assert.match(path, HORIZONTAL_TANGENT_BEZIER_PATH);
+ });
+});
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/__tests__/reference-area-geometry.test.ts b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/__tests__/reference-area-geometry.test.ts
new file mode 100644
index 0000000..98a8e7b
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/__tests__/reference-area-geometry.test.ts
@@ -0,0 +1,102 @@
+import assert from "node:assert/strict";
+import { describe, it } from "node:test";
+import {
+ computeReferenceAreaRect,
+ resolveReferenceDataRange,
+} from "../reference-area-geometry";
+
+const innerWidth = 400;
+const innerHeight = 200;
+
+const xScale = (date: Date) => date.getTime() / 10;
+const yScale = (value: number) => innerHeight - value;
+
+function baseOptions(
+ overrides: Partial[0]> = {}
+) {
+ return {
+ innerWidth,
+ innerHeight,
+ xScale,
+ yScale,
+ ...overrides,
+ };
+}
+
+describe("computeReferenceAreaRect", () => {
+ it("maps a full-width horizontal band between y1 and y2", () => {
+ const rect = computeReferenceAreaRect(baseOptions({ y1: 40, y2: 80 }));
+ assert.deepEqual(rect, { x: 0, y: 120, width: 400, height: 40 });
+ });
+
+ it("maps a partial x-range when x1 and x2 are set", () => {
+ const rect = computeReferenceAreaRect(
+ baseOptions({
+ x1: new Date(1000),
+ x2: new Date(2000),
+ y1: 50,
+ y2: 100,
+ })
+ );
+ assert.deepEqual(rect, { x: 100, y: 100, width: 100, height: 50 });
+ });
+
+ it("clamps to the plot when ifOverflow is hidden", () => {
+ const rect = computeReferenceAreaRect(
+ baseOptions({
+ y1: 150,
+ y2: 250,
+ ifOverflow: "hidden",
+ })
+ );
+ assert.deepEqual(rect, { x: 0, y: 0, width: 400, height: 50 });
+ });
+
+ it("returns null when discard and partly outside the plot", () => {
+ const rect = computeReferenceAreaRect(
+ baseOptions({
+ y1: 150,
+ y2: 250,
+ ifOverflow: "discard",
+ })
+ );
+ assert.equal(rect, null);
+ });
+
+ it("does not clamp when ifOverflow is visible", () => {
+ const rect = computeReferenceAreaRect(
+ baseOptions({
+ y1: 150,
+ y2: 250,
+ ifOverflow: "visible",
+ })
+ );
+ assert.deepEqual(rect, { x: 0, y: -50, width: 400, height: 100 });
+ });
+
+ it("returns null for zero plot size", () => {
+ assert.equal(
+ computeReferenceAreaRect(
+ baseOptions({ innerWidth: 0, innerHeight: 200, y1: 10, y2: 20 })
+ ),
+ null
+ );
+ });
+});
+
+describe("resolveReferenceDataRange", () => {
+ it("returns inclusive bounds between y1 and y2", () => {
+ assert.deepEqual(resolveReferenceDataRange(160, 220, [0, 300]), [160, 220]);
+ });
+
+ it("extends to domain edges when y bounds are omitted", () => {
+ assert.deepEqual(
+ resolveReferenceDataRange(undefined, 220, [0, 300]),
+ [0, 220]
+ );
+ assert.deepEqual(
+ resolveReferenceDataRange(160, undefined, [0, 300]),
+ [160, 300]
+ );
+ });
+});
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/__tests__/series-bar-layout.test.ts b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/__tests__/series-bar-layout.test.ts
new file mode 100644
index 0000000..ad0258e
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/__tests__/series-bar-layout.test.ts
@@ -0,0 +1,44 @@
+import assert from "node:assert/strict";
+import { describe, it } from "node:test";
+import {
+ computeSeriesBarRevealClipPadding,
+ computeSeriesBarWidth,
+} from "../series-bar-layout";
+
+describe("computeSeriesBarWidth", () => {
+ it("caps grouped bar width to the slot", () => {
+ const width = computeSeriesBarWidth({
+ innerWidth: 400,
+ dataLength: 12,
+ columnWidth: 400 / 11,
+ seriesCount: 2,
+ composedBarGap: 4,
+ });
+ assert.ok(width >= 2);
+ assert.ok(width <= 40);
+ });
+});
+
+describe("computeSeriesBarRevealClipPadding", () => {
+ it("uses half the bar width for a single stacked column", () => {
+ assert.equal(
+ computeSeriesBarRevealClipPadding({
+ barWidth: 24,
+ seriesCount: 3,
+ stacked: true,
+ }),
+ 12
+ );
+ });
+
+ it("uses half the group width for grouped bars", () => {
+ assert.equal(
+ computeSeriesBarRevealClipPadding({
+ barWidth: 20,
+ seriesCount: 2,
+ gap: 4,
+ }),
+ 22
+ );
+ });
+});
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/__tests__/y-domain-utils.test.ts b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/__tests__/y-domain-utils.test.ts
new file mode 100644
index 0000000..06dc5ce
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/__tests__/y-domain-utils.test.ts
@@ -0,0 +1,65 @@
+import assert from "node:assert/strict";
+import { describe, it } from "node:test";
+import {
+ domainsEqual,
+ isReferenceAreaVisiblePhase,
+ mergeYDomainRecords,
+ niceYDomain,
+ shouldTweenYDomain,
+} from "../y-domain-utils";
+
+describe("shouldTweenYDomain", () => {
+ it("skips tween when both endpoints move less than 2% of span", () => {
+ assert.equal(shouldTweenYDomain([0, 100], [1, 101]), false);
+ });
+
+ it("tweens when min endpoint shifts enough", () => {
+ assert.equal(shouldTweenYDomain([0, 100], [5, 100]), true);
+ });
+
+ it("tweens when max endpoint shifts enough", () => {
+ assert.equal(shouldTweenYDomain([0, 100], [0, 110]), true);
+ });
+});
+
+describe("niceYDomain", () => {
+ it("expands raw domain to nice tick boundaries", () => {
+ const [min, max] = niceYDomain([13, 87]);
+ assert.ok(min <= 13);
+ assert.ok(max >= 87);
+ });
+});
+
+describe("mergeYDomainRecords", () => {
+ it("normalizes axis ids and merges maps", () => {
+ const merged = mergeYDomainRecords({ left: [0, 100] }, { right: [10, 50] });
+ assert.deepEqual(merged.left, [0, 100]);
+ assert.deepEqual(merged.right, [10, 50]);
+ });
+});
+
+describe("domainsEqual", () => {
+ it("returns true when axis domains match", () => {
+ assert.equal(domainsEqual({ left: [0, 100] }, { left: [0, 100] }), true);
+ });
+
+ it("returns false when any endpoint differs", () => {
+ assert.equal(domainsEqual({ left: [0, 100] }, { left: [0, 110] }), false);
+ });
+});
+
+describe("isReferenceAreaVisiblePhase", () => {
+ it("shows reference areas during ready reveal phases", () => {
+ assert.equal(isReferenceAreaVisiblePhase("ready"), true);
+ assert.equal(isReferenceAreaVisiblePhase("revealing"), true);
+ assert.equal(isReferenceAreaVisiblePhase("gridTweenReady"), true);
+ });
+
+ it("hides reference areas during loading and exit phases", () => {
+ assert.equal(isReferenceAreaVisiblePhase("loading"), false);
+ assert.equal(isReferenceAreaVisiblePhase("exiting"), false);
+ assert.equal(isReferenceAreaVisiblePhase("exitingReady"), false);
+ assert.equal(isReferenceAreaVisiblePhase("gridTweenLoading"), false);
+ assert.equal(isReferenceAreaVisiblePhase("revealingLoading"), false);
+ });
+});
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/animation.ts b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/animation.ts
new file mode 100644
index 0000000..0b5569b
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/animation.ts
@@ -0,0 +1,36 @@
+import type { Transition } from "motion/react";
+
+/** Default clip-reveal easing for cartesian charts. */
+export const DEFAULT_ANIMATION_EASING = "cubic-bezier(0.85, 0, 0.15, 1)";
+
+export const DEFAULT_ANIMATION_DURATION_MS = 1100;
+
+/** Default enter transition — matches the original line chart reveal. */
+export const DEFAULT_CHART_ENTER_TRANSITION: Transition = {
+ type: "tween",
+ duration: DEFAULT_ANIMATION_DURATION_MS / 1000,
+ ease: [0.85, 0, 0.15, 1],
+};
+
+/**
+ * Clip-path width reveal must use tween — spring does not reliably animate SVG width.
+ */
+export function clipRevealTransition(enterTransition?: Transition): Transition {
+ if (enterTransition?.type === "tween") {
+ return {
+ ...enterTransition,
+ ease: enterTransition.ease ?? DEFAULT_CHART_ENTER_TRANSITION.ease,
+ };
+ }
+
+ const duration =
+ typeof enterTransition?.duration === "number"
+ ? enterTransition.duration
+ : DEFAULT_ANIMATION_DURATION_MS / 1000;
+
+ return {
+ type: "tween",
+ duration,
+ ease: DEFAULT_CHART_ENTER_TRANSITION.ease,
+ };
+}
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/area-chart-loading.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/area-chart-loading.tsx
new file mode 100644
index 0000000..0e90530
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/area-chart-loading.tsx
@@ -0,0 +1,116 @@
+"use client";
+
+import { curveNatural } from "@visx/curve";
+import { useMemo } from "react";
+import { Area } from "./area";
+import { AreaChart } from "./area-chart";
+import type { Margin } from "./chart-context";
+import type { LoadingStyle } from "./chart-phase";
+import {
+ DEFAULT_SKELETON_DATA_KEY,
+ DEFAULT_SKELETON_POINT_COUNT,
+ generateChartSkeletonData,
+} from "./generate-chart-skeleton-data";
+import { Grid } from "./grid";
+
+const LOADING_DATA_KEY = DEFAULT_SKELETON_DATA_KEY;
+const DEFAULT_LOADING_STROKE = "var(--foreground)";
+const DEFAULT_LOADING_GRID_STROKE =
+ "color-mix(in oklch, var(--chart-grid) 50%, transparent)";
+const DEFAULT_LOADING_GRID_SHIMMER_STROKE =
+ "color-mix(in oklch, var(--foreground) 68%, transparent)";
+const DEFAULT_LOADING_STROKE_OPACITY = 0.5;
+
+export interface AreaChartLoadingProps {
+ /** Chart margins */
+ margin?: Partial;
+ /** Stroke color for the animated loading segment. */
+ stroke?: string;
+ /** Stroke opacity for the animated loading segment. Default: 0.5 */
+ strokeOpacity?: number;
+ /** Grid line stroke (color and opacity via color-mix or oklch alpha). */
+ gridStroke?: string;
+ /** Shimmer band stroke (color and opacity via color-mix or oklch alpha). */
+ gridShimmerStroke?: string;
+ /** Animate a shimmer band across grid lines. Default: true */
+ gridShimmer?: boolean;
+ /** Shimmer band width in pixels. Default: 140 */
+ gridShimmerLength?: number;
+ /** Shimmer speed multiplier (higher = faster). Default: 1 */
+ gridShimmerSpeed?: number;
+ /** Match shimmer loop to the loading line pulse (cycle + inter-loop pause). */
+ gridShimmerSync?: boolean;
+ /** Loading animation: `"pulse"` (default traveling pulse) or `"sweep"` (a
+ * diagonal shimmer across the skeleton area). Default: `"pulse"`. */
+ loadingStyle?: LoadingStyle;
+ /** Centered shimmer label text. Default: "Loading" */
+ label?: string;
+ /** Aspect ratio as "width / height". Default: "2 / 1" */
+ aspectRatio?: string;
+ /** Additional class name for the container */
+ className?: string;
+}
+
+export function AreaChartLoading({
+ margin,
+ stroke = DEFAULT_LOADING_STROKE,
+ strokeOpacity = DEFAULT_LOADING_STROKE_OPACITY,
+ gridStroke = DEFAULT_LOADING_GRID_STROKE,
+ gridShimmerStroke = DEFAULT_LOADING_GRID_SHIMMER_STROKE,
+ gridShimmer = true,
+ gridShimmerLength,
+ gridShimmerSpeed,
+ gridShimmerSync = false,
+ loadingStyle = "pulse",
+ label = "Loading",
+ aspectRatio = "2 / 1",
+ className = "",
+}: AreaChartLoadingProps) {
+ const data = useMemo(
+ () =>
+ generateChartSkeletonData({
+ dataKey: DEFAULT_SKELETON_DATA_KEY,
+ pointCount: DEFAULT_SKELETON_POINT_COUNT,
+ }),
+ []
+ );
+
+ return (
+
+
+
+
+ );
+}
+
+export default AreaChartLoading;
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/area-chart.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/area-chart.tsx
new file mode 100644
index 0000000..51b871e
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/area-chart.tsx
@@ -0,0 +1,272 @@
+"use client";
+
+import { ParentSize } from "@visx/responsive";
+import type { Transition } from "motion/react";
+import {
+ Children,
+ type CSSProperties,
+ isValidElement,
+ type ReactNode,
+ useCallback,
+ useMemo,
+ useRef,
+ useState,
+} from "react";
+import { cn } from "@/lib/utils";
+import { Area, type AreaProps } from "./area";
+import type { LineConfig, Margin } from "./chart-context";
+import { ChartLoadingLabel } from "./chart-loading-label";
+import {
+ type ChartPhase,
+ type ChartStatus,
+ DEFAULT_CHART_STATUS,
+ DEFAULT_Y_DOMAIN_TWEEN_MS,
+ resolveRestingChartPhase,
+} from "./chart-phase";
+import { PatternArea } from "./pattern-area";
+import { TimeSeriesChartInner } from "./time-series-chart-shell";
+
+export interface AreaChartProps {
+ /** Data array - each item should have a date field and numeric values */
+ data: Record[];
+ /** Key in data for the x-axis (date). Default: "date" */
+ xDataKey?: string;
+ /** Chart margins */
+ margin?: Partial;
+ /** Animation duration in milliseconds. Default: 1100 */
+ animationDuration?: number;
+ /** CSS easing for clip-reveal. Default: cubic-bezier(0.85, 0, 0.15, 1) */
+ animationEasing?: string;
+ /** Motion enter transition (spring or cubic-bezier tween). */
+ enterTransition?: Transition;
+ /** Signature of motion URL state — triggers reveal replay when it changes. */
+ revealSignature?: string;
+ /** Aspect ratio as "width / height". Default: "2 / 1" */
+ aspectRatio?: string;
+ /** Additional class name for the container */
+ className?: string;
+ /** Loading vs ready — drives chart phase and loading chrome. Default: `"ready"`. */
+ status?: ChartStatus;
+ /** Centered shimmer label while loading. */
+ loadingLabel?: string;
+ /** Animate y-domain over this duration (ms) on status transitions. Default: 500. */
+ yDomainTweenDuration?: number;
+ /** Animate y-domain when status or target domain changes. Default: true */
+ yDomainTween?: boolean;
+ /** Visible x-domain for brush zoom. */
+ xDomain?: [Date, Date];
+ /** Full dataset length for x-scale padding when `xDomain` is set. */
+ xDomainSlotCount?: number;
+ /** Tween y-domain when brush changes the visible x-range. Default: false */
+ tweenYDomainOnXDomainChange?: boolean;
+ /** Inline container styles (e.g. fixed height for brush strip). */
+ style?: CSSProperties;
+ /** Fires when the internal chart phase changes (e.g. OG capture readiness). */
+ onPhaseChange?: (phase: ChartPhase) => void;
+ /** Child components (Area, Grid, ChartTooltip, etc.) */
+ children: ReactNode;
+}
+
+const DEFAULT_MARGIN: Margin = { top: 40, right: 40, bottom: 40, left: 40 };
+
+function extractAreaConfigs(children: ReactNode): LineConfig[] {
+ const configs: LineConfig[] = [];
+
+ Children.forEach(children, (child) => {
+ if (!isValidElement(child)) {
+ return;
+ }
+
+ const childType = child.type as {
+ displayName?: string;
+ name?: string;
+ };
+ const componentName =
+ typeof child.type === "function"
+ ? childType.displayName || childType.name || ""
+ : "";
+
+ const props = child.props as AreaProps | undefined;
+ const isPatternArea =
+ componentName === "PatternArea" || child.type === PatternArea;
+ const isAreaComponent =
+ componentName === "Area" ||
+ child.type === Area ||
+ (props &&
+ typeof props.dataKey === "string" &&
+ props.dataKey.length > 0 &&
+ !isPatternArea);
+
+ if (isAreaComponent && props?.dataKey) {
+ configs.push({
+ dataKey: props.dataKey,
+ stroke: props.stroke || props.fill || "var(--chart-line-primary)",
+ strokeWidth: props.strokeWidth || 2,
+ yAxisId: props.yAxisId,
+ });
+ }
+ });
+
+ return configs;
+}
+
+interface ChartInnerProps {
+ width: number;
+ height: number;
+ data: Record[];
+ xDataKey: string;
+ margin: Margin;
+ animationDuration: number;
+ animationEasing?: string;
+ enterTransition?: Transition;
+ revealSignature?: string;
+ chartStatus: ChartStatus;
+ loadingLabel?: string;
+ yDomainTweenDuration: number;
+ yDomainTween: boolean;
+ xDomain?: [Date, Date];
+ xDomainSlotCount?: number;
+ tweenYDomainOnXDomainChange?: boolean;
+ children: ReactNode;
+ containerRef: React.RefObject;
+ onPhaseChange: (phase: ChartPhase) => void;
+}
+
+function ChartInner({
+ width,
+ height,
+ data,
+ xDataKey,
+ margin,
+ animationDuration,
+ animationEasing,
+ enterTransition,
+ revealSignature,
+ chartStatus,
+ loadingLabel,
+ yDomainTweenDuration,
+ yDomainTween,
+ xDomain,
+ xDomainSlotCount,
+ tweenYDomainOnXDomainChange,
+ children,
+ containerRef,
+ onPhaseChange,
+}: ChartInnerProps) {
+ const lines = useMemo(() => extractAreaConfigs(children), [children]);
+
+ return (
+
+ {children}
+
+ );
+}
+
+export function AreaChart({
+ data,
+ xDataKey = "date",
+ margin: marginProp,
+ animationDuration = 1100,
+ animationEasing,
+ enterTransition,
+ revealSignature,
+ aspectRatio = "2 / 1",
+ className = "",
+ status = DEFAULT_CHART_STATUS,
+ loadingLabel,
+ yDomainTweenDuration = DEFAULT_Y_DOMAIN_TWEEN_MS,
+ yDomainTween = true,
+ xDomain,
+ xDomainSlotCount,
+ tweenYDomainOnXDomainChange = false,
+ style,
+ onPhaseChange,
+ children,
+}: AreaChartProps) {
+ const containerRef = useRef(null);
+ const margin = { ...DEFAULT_MARGIN, ...marginProp };
+ const [chartPhase, setChartPhase] = useState(() =>
+ resolveRestingChartPhase(status)
+ );
+ const handlePhaseChange = useCallback(
+ (phase: ChartPhase) => {
+ setChartPhase(phase);
+ onPhaseChange?.(phase);
+ },
+ [onPhaseChange]
+ );
+
+ const showLoadingLabel = Boolean(
+ loadingLabel?.trim() &&
+ (chartPhase === "loading" ||
+ chartPhase === "exiting" ||
+ chartPhase === "gridTweenReady" ||
+ chartPhase === "revealingLoading")
+ );
+
+ return (
+
+
+ {({ width, height }) => (
+
+ {children}
+
+ )}
+
+ {showLoadingLabel ? (
+
+ ) : null}
+
+ );
+}
+
+export { Area, type AreaProps } from "./area";
+
+export default AreaChart;
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/area-gradient-defs.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/area-gradient-defs.tsx
new file mode 100644
index 0000000..cb142cb
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/area-gradient-defs.tsx
@@ -0,0 +1,113 @@
+import {
+ type FadeEdges,
+ fadeGradientStops,
+ resolveFadeSides,
+ viewportFadeGradientAttrs,
+} from "./fade-edges";
+
+interface AreaGradientDefsProps {
+ gradientId: string;
+ strokeGradientId: string;
+ edgeMaskId: string;
+ edgeGradientId: string;
+ fill: string;
+ fillOpacity: number;
+ gradientToOpacity: number;
+ /** 0–1: where the bottom stop sits (1 = full-height gradient). */
+ gradientSpan?: number;
+ resolvedStroke: string;
+ isPatternFill: boolean;
+ fadeEdges: FadeEdges;
+ innerWidth: number;
+ innerHeight: number;
+}
+
+export function AreaGradientDefs({
+ gradientId,
+ strokeGradientId,
+ edgeMaskId,
+ edgeGradientId,
+ fill,
+ fillOpacity,
+ gradientToOpacity,
+ gradientSpan = 1,
+ resolvedStroke,
+ isPatternFill,
+ fadeEdges,
+ innerWidth,
+ innerHeight,
+}: AreaGradientDefsProps) {
+ const sides = resolveFadeSides(fadeEdges);
+ // Stroke gradient mirrors the area's edge fade so the line doesn't pop in
+ // past the faded fill. Skip emitting it when neither edge fades — the line
+ // can then paint a solid stroke instead of an unnecessary url(#...) ref.
+ const strokeStops = sides.any ? fadeGradientStops(sides) : null;
+ const showEdgeMask = sides.any && !isPatternFill;
+ const edgeStops = showEdgeMask ? fadeGradientStops(sides) : null;
+ const span = Math.min(1, Math.max(0.01, gradientSpan));
+ const midOffset = `${span * 100}%`;
+
+ return (
+
+ {isPatternFill ? null : (
+
+
+
+ {span < 1 ? (
+
+ ) : null}
+
+ )}
+
+ {strokeStops ? (
+
+ {strokeStops.map((stop) => (
+
+ ))}
+
+ ) : null}
+
+ {edgeStops ? (
+ <>
+
+ {edgeStops.map((stop) => (
+
+ ))}
+
+
+
+
+ >
+ ) : null}
+
+ );
+}
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/area.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/area.tsx
new file mode 100644
index 0000000..1439a7d
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/area.tsx
@@ -0,0 +1,377 @@
+"use client";
+
+import { curveMonotoneX } from "@visx/curve";
+import { AreaClosed, LinePath } from "@visx/shape";
+
+// CurveFactory type - simplified version compatible with visx
+// biome-ignore lint/suspicious/noExplicitAny: d3 curve factory type
+type CurveFactory = any;
+
+import { useCallback, useId, useMemo, useRef, useState } from "react";
+import { AreaGradientDefs } from "./area-gradient-defs";
+import { chartCssVars, useChartStable, useYScale } from "./chart-context";
+import type { ChartPhase, LoadingStyle } from "./chart-phase";
+import { type FadeEdges, resolveFadeSides } from "./fade-edges";
+import {
+ type LineLoadingPulseMode,
+ LineLoadingPulseStroke,
+ resolveLineLoadingPulseMode,
+} from "./line-loading-pulse";
+import { LINE_LOADING_LOOP_PAUSE_MS } from "./line-loading-timing";
+import { LineLoadingSweep } from "./loading-sweep";
+import {
+ resolveDashTailBounds,
+ usePathStrokeMetrics,
+} from "./path-stroke-utils";
+import { SeriesDashTailOverlay } from "./series-dash-tail-overlay";
+import { SeriesHighlightLayer } from "./series-highlight-layer";
+import { SeriesHoverDim } from "./series-hover-dim";
+import { SeriesMarkers } from "./series-markers";
+import type { SeriesPointMarkerStyle } from "./series-point-marker";
+
+export interface AreaProps {
+ /** Key in data to use for y values */
+ dataKey: string;
+ /** Y-scale group id (Recharts `yAxisId`). Default: `"left"`. */
+ yAxisId?: string | number;
+ /** Fill color for the area gradient start. Default: var(--chart-line-primary) */
+ fill?: string;
+ /** Fill opacity at the top of the area. Default: 0.4 */
+ fillOpacity?: number;
+ /** Stroke color for the line. Default: same as fill */
+ stroke?: string;
+ /** Stroke width. Default: 2 */
+ strokeWidth?: number;
+ /** Curve function. Default: curveMonotoneX */
+ curve?: CurveFactory;
+ /** Whether to animate the area. Default: true */
+ animate?: boolean;
+ /** Whether to show the stroke line. Default: true */
+ showLine?: boolean;
+ /** Whether to show highlight segment on hover. Default: true */
+ showHighlight?: boolean;
+ /** Gradient opacity at bottom (0 = fully transparent). Default: 0 */
+ gradientToOpacity?: number;
+ /**
+ * Vertical extent of the fill gradient (0–1). `1` fades across the full
+ * height; lower values compress the gradient toward the top.
+ */
+ gradientSpan?: number;
+ /**
+ * Fade the area fill (and stroke) toward transparent at the chart edges.
+ * - `true` fades both edges, `false` disables the fade entirely.
+ * - `"left"` / `"right"` fades only that side — useful when the opposite
+ * edge butts up against another element you don't want to fade into.
+ * Default: false
+ */
+ fadeEdges?: FadeEdges;
+ /** Render scatter-style circle markers at each data point. Default: false */
+ showMarkers?: boolean;
+ /** Marker styling (same options as Scatter). */
+ markers?: SeriesPointMarkerStyle;
+ /**
+ * Data index from which the line stroke becomes dashed (inclusive).
+ * Useful for projecting incomplete periods, e.g. dashed from yesterday through today.
+ */
+ dashFromIndex?: number;
+ /** Dash pattern for the tail segment when `dashFromIndex` is set. Default: "6,4" */
+ dashArray?: string;
+ /** Pulse stroke color while chart is loading. Default: var(--foreground) */
+ loadingStroke?: string;
+ /** Pulse stroke opacity while chart is loading. Default: 0.5 */
+ loadingStrokeOpacity?: number;
+ /**
+ * Show the loading pulse overlay. Default: follows chart loading phase.
+ * Set `false` to disable even during loading.
+ */
+ loading?: boolean;
+ /** Override pulse animation mode (loop / exit / enter). */
+ loadingPulseMode?: LineLoadingPulseMode;
+ /**
+ * Loading animation while the chart is in loading status: the default
+ * traveling `"pulse"`, or a diagonal `"sweep"` shimmer across the skeleton
+ * area. Default: `"pulse"`.
+ */
+ loadingStyle?: LoadingStyle;
+}
+
+function useAreaLoadingPulseState(
+ chartPhase: ChartPhase,
+ loading: boolean | undefined,
+ loadingPulseMode: LineLoadingPulseMode | undefined,
+ notifyLoadingPulseComplete?: () => void
+) {
+ const phasePulseMode = resolveLineLoadingPulseMode(chartPhase);
+ const pulseMode =
+ loading === false
+ ? null
+ : (loadingPulseMode ?? (loading === true ? "loop" : phasePulseMode));
+ const showLoadingPulse = pulseMode != null;
+ const showSeriesContent =
+ chartPhase === "revealing" ||
+ chartPhase === "ready" ||
+ chartPhase === "exitingReady";
+ const [pulseEpoch, setPulseEpoch] = useState(0);
+
+ const handleLoadingPulseComplete = useCallback(() => {
+ if (pulseMode === "loop") {
+ window.setTimeout(() => {
+ setPulseEpoch((epoch) => epoch + 1);
+ }, LINE_LOADING_LOOP_PAUSE_MS);
+ return;
+ }
+ notifyLoadingPulseComplete?.();
+ }, [notifyLoadingPulseComplete, pulseMode]);
+
+ return {
+ handleLoadingPulseComplete,
+ pulseMode,
+ pulseEpoch,
+ showLoadingPulse,
+ showSeriesContent,
+ };
+}
+
+// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: mirrors Line series layout (fill, stroke, dash, markers, pulse)
+export function Area({
+ dataKey,
+ yAxisId,
+ fill = chartCssVars.linePrimary,
+ fillOpacity = 0.4,
+ stroke,
+ strokeWidth = 2,
+ curve = curveMonotoneX,
+ animate = true,
+ showLine = true,
+ showHighlight = true,
+ gradientToOpacity = 0,
+ gradientSpan = 1,
+ fadeEdges = false,
+ showMarkers = false,
+ markers,
+ dashFromIndex,
+ dashArray = "6,4",
+ loading,
+ loadingStroke = chartCssVars.foreground,
+ loadingStrokeOpacity = 0.5,
+ loadingPulseMode,
+ loadingStyle = "pulse",
+}: AreaProps) {
+ // Stable slice only: hover state lives inside `` and
+ // `` so this component (and its expensive
+ // child) does not re-render on cursor motion.
+ // The reveal-clip is now a single shared clipPath at the chart-shell
+ // level (`time-series-chart-shell.tsx`); we no longer render a per-area
+ // `` or read `revealEpoch` here.
+ const {
+ data,
+ renderData,
+ xScale,
+ innerHeight,
+ innerWidth,
+ xAccessor,
+ lines,
+ chartPhase,
+ notifyLoadingPulseComplete,
+ } = useChartStable();
+ const yScale = useYScale(yAxisId);
+ const {
+ handleLoadingPulseComplete,
+ pulseMode,
+ pulseEpoch,
+ showLoadingPulse,
+ showSeriesContent,
+ } = useAreaLoadingPulseState(
+ chartPhase,
+ loading,
+ loadingPulseMode,
+ notifyLoadingPulseComplete
+ );
+
+ const seriesIndex = useMemo(() => {
+ const index = lines.findIndex((line) => line.dataKey === dataKey);
+ return index >= 0 ? index : 0;
+ }, [lines, dataKey]);
+
+ const pathRef = useRef(null);
+ const { pathLength, pathD } = usePathStrokeMetrics(pathRef, [
+ renderData,
+ innerWidth,
+ dashFromIndex,
+ showLine,
+ showSeriesContent,
+ showLoadingPulse,
+ ]);
+
+ // Unique IDs for this area
+ const uniqueId = useId();
+ const gradientId = `area-gradient-${dataKey}-${uniqueId}`;
+ const strokeGradientId = `area-stroke-gradient-${dataKey}-${uniqueId}`;
+ const edgeMaskId = `area-edge-mask-${dataKey}-${uniqueId}`;
+ const edgeGradientId = `${edgeMaskId}-gradient`;
+
+ const isPatternFill = fill.startsWith("url(");
+ const showAreaFill = isPatternFill || fillOpacity > 0;
+ const areaFill = isPatternFill ? fill : `url(#${gradientId})`;
+
+ // Resolved stroke color (defaults to fill; pattern URLs need a real color)
+ const resolvedStroke =
+ stroke || (isPatternFill ? chartCssVars.linePrimary : fill);
+
+ const getY = useCallback(
+ (d: Record) => {
+ const value = d[dataKey];
+ return typeof value === "number" ? (yScale(value) ?? 0) : 0;
+ },
+ [dataKey, yScale]
+ );
+
+ const hasDashTail = resolveDashTailBounds(dashFromIndex, data.length);
+ // The stroke gradient is only emitted when at least one edge fades, so fall
+ // back to the resolved solid color otherwise — avoids an invalid url(#...).
+ const fadeSides = resolveFadeSides(fadeEdges);
+ const useViewportEdgeFade = fadeSides.any && !isPatternFill;
+ let strokePaint = resolvedStroke;
+ if (!useViewportEdgeFade && fadeSides.any) {
+ strokePaint = `url(#${strokeGradientId})`;
+ }
+ const highlightEnabled =
+ showHighlight && showLine && !showLoadingPulse && showSeriesContent;
+ const showSeriesStroke = showSeriesContent && showLine;
+ let visibleStroke = "transparent";
+ if (showSeriesStroke && !hasDashTail) {
+ visibleStroke = strokePaint;
+ }
+ const shouldMeasurePath = showLine && (showSeriesContent || showLoadingPulse);
+
+ const seriesLayers = (
+ <>
+ {showSeriesContent && showAreaFill ? (
+ xScale(xAccessor(d)) ?? 0}
+ y={getY}
+ yScale={yScale}
+ />
+ ) : null}
+
+ {shouldMeasurePath ? (
+ <>
+ xScale(xAccessor(d)) ?? 0}
+ y={getY}
+ />
+ {showSeriesStroke ? (
+
+ ) : null}
+ >
+ ) : null}
+ >
+ );
+
+ // Sweep style owns all loading modes (loop + the exit/enter transitions),
+ // drawing its own silhouette; the pulse covers the default style.
+ const sweepLoading =
+ showLoadingPulse && innerWidth > 0 && loadingStyle === "sweep";
+ const pulseLoading = showLoadingPulse && innerWidth > 0 && !sweepLoading;
+
+ return (
+ <>
+
+
+
+ {useViewportEdgeFade ? (
+ {seriesLayers}
+ ) : (
+ seriesLayers
+ )}
+
+
+ {/* Highlight segment on hover — isolated hover subscriber. */}
+
+
+ {showMarkers && showSeriesContent ? (
+
+ ) : null}
+
+ {sweepLoading ? (
+
+ ) : null}
+ {pulseLoading && pathD ? (
+
+ ) : null}
+ >
+ );
+}
+
+Area.displayName = "Area";
+
+export default Area;
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/background.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/background.tsx
new file mode 100644
index 0000000..e30dbec
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/background.tsx
@@ -0,0 +1,219 @@
+"use client";
+
+import { motion } from "motion/react";
+import { useId, useMemo } from "react";
+import { useChartStable } from "./chart-context";
+import {
+ type PatternPresetId,
+ type PatternPresetOptions,
+ renderPatternPreset,
+} from "./pattern-preset";
+
+export type BackgroundPatternPreset = PatternPresetId;
+
+export interface BackgroundProps extends PatternPresetOptions {
+ /** Pattern preset. `"none"` renders nothing. */
+ pattern?: BackgroundPatternPreset;
+ /** Pattern stroke color. Default: `var(--chart-grid)` */
+ color?: string;
+ /** Apply the pattern texture to the plot area. Default: true */
+ showFill?: boolean;
+ /** Pattern fill opacity. Default: 1 */
+ opacity?: number;
+ /** Fade pattern at the left and right chart edges. Default: true */
+ fadeHorizontal?: boolean;
+ /** Fade pattern at the top and bottom chart edges. Default: true */
+ fadeVertical?: boolean;
+ /** Horizontal fade zone as % of plot width per edge. Default: 10 */
+ fadeHorizontalLength?: number;
+ /** Vertical fade zone as % of plot height per edge. Default: 10 */
+ fadeVerticalLength?: number;
+}
+
+const BACKGROUND_ENTER_FADE_MS = 420;
+
+function clampFadeLength(length: number): number {
+ return Math.min(45, Math.max(0, length));
+}
+
+function fadeMaskStops(lengthPercent: number): Array<{
+ offset: string;
+ opacity: number;
+}> {
+ const edge = clampFadeLength(lengthPercent);
+ return [
+ { offset: "0%", opacity: 0 },
+ { offset: `${edge}%`, opacity: 1 },
+ { offset: `${100 - edge}%`, opacity: 1 },
+ { offset: "100%", opacity: 0 },
+ ];
+}
+
+/** Plot-area pattern fill for charts without a grid. Renders behind series layers. */
+export function Background({
+ pattern = "diagonal",
+ color = "var(--chart-grid)",
+ scale = 1,
+ strokeWidth,
+ radius,
+ complement,
+ fill,
+ dotFill,
+ tileBackground,
+ showFill = true,
+ opacity = 1,
+ fadeHorizontal = true,
+ fadeVertical = true,
+ fadeHorizontalLength = 10,
+ fadeVerticalLength = 10,
+}: BackgroundProps) {
+ const { innerWidth, innerHeight, isLoaded, enterTransition } =
+ useChartStable();
+ const uniqueId = useId();
+ const patternId = `chart-background-${uniqueId.replace(/:/g, "")}`;
+
+ const hStops = useMemo(
+ () => fadeMaskStops(fadeHorizontalLength),
+ [fadeHorizontalLength]
+ );
+ const vStops = useMemo(
+ () => fadeMaskStops(fadeVerticalLength),
+ [fadeVerticalLength]
+ );
+
+ if (pattern === "none" || !showFill || innerWidth <= 0 || innerHeight <= 0) {
+ return null;
+ }
+
+ const patternNode = renderPatternPreset(pattern, patternId, {
+ color,
+ scale,
+ strokeWidth,
+ radius,
+ complement,
+ fill,
+ dotFill,
+ tileBackground,
+ });
+ if (!patternNode) {
+ return null;
+ }
+
+ const fadeMask = fadeHorizontal || fadeVertical;
+ const hMaskId = `chart-background-fade-h-${uniqueId.replace(/:/g, "")}`;
+ const hGradientId = `${hMaskId}-gradient`;
+ const vMaskId = `chart-background-fade-v-${uniqueId.replace(/:/g, "")}`;
+ const vGradientId = `${vMaskId}-gradient`;
+ const combinedMaskId = `chart-background-fade-${uniqueId.replace(/:/g, "")}`;
+
+ let maskRef: string | undefined;
+ if (fadeHorizontal && fadeVertical) {
+ maskRef = `url(#${combinedMaskId})`;
+ } else if (fadeHorizontal) {
+ maskRef = `url(#${hMaskId})`;
+ } else if (fadeVertical) {
+ maskRef = `url(#${vMaskId})`;
+ }
+
+ const targetOpacity = opacity;
+ const revealOpacity = isLoaded ? targetOpacity : 0;
+ const enterFade =
+ enterTransition && typeof enterTransition === "object"
+ ? enterTransition
+ : { duration: BACKGROUND_ENTER_FADE_MS / 1000, ease: "easeOut" as const };
+
+ return (
+ // biome-ignore lint/a11y/noAriaHiddenOnFocusable: decorative plot-area pattern layer
+
+ {fadeMask ? (
+
+ {fadeHorizontal ? (
+ <>
+
+ {hStops.map((stop) => (
+
+ ))}
+
+
+
+
+ >
+ ) : null}
+ {fadeVertical ? (
+ <>
+
+ {vStops.map((stop) => (
+
+ ))}
+
+
+
+
+ >
+ ) : null}
+ {fadeHorizontal && fadeVertical ? (
+
+
+
+
+
+ ) : null}
+
+ ) : null}
+ {patternNode}
+
+
+ );
+}
+
+Background.displayName = "Background";
+
+export default Background;
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/bar-chart-loading.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/bar-chart-loading.tsx
new file mode 100644
index 0000000..8258273
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/bar-chart-loading.tsx
@@ -0,0 +1,38 @@
+"use client";
+
+import { BarChart } from "./bar-chart";
+import type { Margin } from "./chart-context";
+
+const EMPTY_DATA: Record[] = [];
+
+export interface BarChartLoadingProps {
+ /** Chart margins. */
+ margin?: Partial;
+ /** Aspect ratio as "width / height". Default: "2 / 1" */
+ aspectRatio?: string;
+ /** Additional class name for the container. */
+ className?: string;
+}
+
+/**
+ * Turnkey loading skeleton for bar charts, a thin shortcut for
+ * ` `. Renders shimmer-swept placeholder bars while
+ * data is fetching; swap in a real `` once it resolves.
+ */
+export function BarChartLoading({
+ margin,
+ aspectRatio = "2 / 1",
+ className = "",
+}: BarChartLoadingProps) {
+ return (
+
+ );
+}
+
+export default BarChartLoading;
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/bar-chart.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/bar-chart.tsx
new file mode 100644
index 0000000..60445d8
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/bar-chart.tsx
@@ -0,0 +1,706 @@
+"use client";
+
+import { localPoint } from "@visx/event";
+import { ParentSize } from "@visx/responsive";
+import { scaleBand, scaleLinear } from "@visx/scale";
+import type { Transition } from "motion/react";
+import {
+ memo,
+ type ReactElement,
+ type ReactNode,
+ useCallback,
+ useEffect,
+ useMemo,
+ useRef,
+ useState,
+} from "react";
+import { cn } from "@/lib/utils";
+import { DEFAULT_ANIMATION_EASING } from "./animation";
+import type { BarProps } from "./bar";
+import {
+ forEachChartChild,
+ isChartClipPassthrough,
+ isClipExcludedComponent,
+ isPostOverlayComponent,
+ isUnderlayComponent,
+ renderKeyedChartLayers,
+ resolveChartChildElement,
+} from "./chart-child-passthrough";
+import {
+ ChartProvider,
+ type LineConfig,
+ type Margin,
+ type TooltipData,
+} from "./chart-context";
+import { isGradientDefComponent, isPatternDefComponent } from "./chart-defs";
+import { shortDateFmt } from "./chart-formatters";
+import {
+ type ChartPhase,
+ type ChartStatus,
+ DEFAULT_CHART_LIFECYCLE,
+ resolveRestingChartPhase,
+} from "./chart-phase";
+import { BarLoadingSkeleton } from "./loading-sweep";
+import { extractReferenceAreaConfigs } from "./reference-area-config";
+import { useScheduledTooltip } from "./use-scheduled-tooltip";
+import {
+ buildYScalesForLines,
+ getPrimaryYScale,
+ normalizeYAxisId,
+ wrapSingleYScale,
+} from "./y-axis-scales";
+
+/** Skeleton bars to show when `status="loading"` and `data` is empty. */
+const FALLBACK_LOADING_BARS = 12;
+
+export type BarOrientation = "vertical" | "horizontal";
+
+export interface BarChartProps {
+ /** Data array - each item should have an x-axis key and numeric values */
+ data: Record[];
+ /** Key in data for the categorical axis. Default: "name" */
+ xDataKey?: string;
+ /** Chart margins */
+ margin?: Partial;
+ /** Animation duration in milliseconds. Default: 1100 */
+ animationDuration?: number;
+ /** CSS easing for bar grow transitions. */
+ animationEasing?: string;
+ /** Motion enter transition (spring or cubic-bezier tween). */
+ enterTransition?: Transition;
+ /** Signature of motion URL state — triggers enter replay when it changes. */
+ revealSignature?: string;
+ /** Aspect ratio as "width / height". Default: "2 / 1" */
+ aspectRatio?: string;
+ /** Additional class name for the container */
+ className?: string;
+ /** Gap between bar groups as a fraction of band width (0-1). Default: 0.2 */
+ barGap?: number;
+ /** Fixed bar width in pixels. If not set, bars auto-size to fill the band. */
+ barWidth?: number;
+ /** Bar chart orientation. Default: "vertical" */
+ orientation?: BarOrientation;
+ /** Whether to stack bars instead of grouping them. Default: false */
+ stacked?: boolean;
+ /** Gap between stacked bar segments in pixels. Default: 0 */
+ stackGap?: number;
+ /** Child components (Bar, Grid, ChartTooltip, etc.). Optional — omit for a
+ * pure `status="loading"` skeleton. */
+ children?: ReactNode;
+ /** Reports reveal lifecycle for OG screenshots and loading orchestration. */
+ onPhaseChange?: (phase: ChartPhase) => void;
+ /** Fetch / display status. When `"loading"`, a shimmer skeleton replaces the
+ * bars (no chart data required). Default: `"ready"`. */
+ status?: ChartStatus;
+}
+
+const DEFAULT_MARGIN: Margin = { top: 40, right: 40, bottom: 40, left: 40 };
+
+// Extract bar configs from children synchronously
+function extractBarConfigs(children: ReactNode): LineConfig[] {
+ const configs: LineConfig[] = [];
+
+ forEachChartChild(children, (child) => {
+ const childType = child.type as {
+ displayName?: string;
+ name?: string;
+ __isBarDepthLayer?: boolean;
+ };
+ // Bar-depth surface layers (BarDepthBack/Front, BarPulse) carry a
+ // `dataKey` to pair with a Bar but are not series themselves — skip them
+ // so they don't inflate the series count and shrink the real bars.
+ if (childType.__isBarDepthLayer) {
+ return;
+ }
+ const componentName =
+ typeof child.type === "function"
+ ? childType.displayName || childType.name || ""
+ : "";
+
+ const props = child.props as BarProps | undefined;
+ const isBarComponent =
+ componentName === "Bar" ||
+ (props && typeof props.dataKey === "string" && props.dataKey.length > 0);
+
+ if (isBarComponent && props?.dataKey) {
+ // Use stroke for tooltip dot color if provided, otherwise fall back to fill
+ // This allows gradient/pattern fills to have a solid dot color
+ const dotColor =
+ props.stroke || props.fill || "var(--chart-line-primary)";
+ configs.push({
+ dataKey: props.dataKey,
+ stroke: dotColor,
+ strokeWidth: 0,
+ yAxisId: props.yAxisId,
+ });
+ }
+ });
+
+ return configs;
+}
+
+interface ChartInnerProps {
+ width: number;
+ height: number;
+ data: Record[];
+ xDataKey: string;
+ margin: Margin;
+ animationDuration: number;
+ animationEasing: string;
+ enterTransition?: Transition;
+ revealSignature?: string;
+ barGap: number;
+ barWidthProp?: number;
+ orientation: BarOrientation;
+ stacked: boolean;
+ stackGap: number;
+ children: ReactNode;
+ containerRef: React.RefObject;
+ onPhaseChange?: (phase: ChartPhase) => void;
+ status: ChartStatus;
+}
+
+function ChartInner(props: ChartInnerProps) {
+ const { width, height } = props;
+ if (width < 10 || height < 10) {
+ return null;
+ }
+ return ;
+}
+
+const ChartCore = memo(function ChartCore({
+ width,
+ height,
+ data,
+ xDataKey,
+ margin,
+ animationDuration,
+ animationEasing,
+ enterTransition,
+ revealSignature = "",
+ barGap,
+ barWidthProp,
+ orientation,
+ stacked,
+ stackGap,
+ children,
+ containerRef,
+ onPhaseChange,
+ status,
+}: ChartInnerProps) {
+ const { tooltipData, setTooltipData, scheduleTooltip, clearTooltip } =
+ useScheduledTooltip();
+ const [isLoaded, setIsLoaded] = useState(false);
+ const [revealEpoch, setRevealEpoch] = useState(0);
+ const hoveredBarIndex = tooltipData?.index ?? null;
+
+ const isHorizontal = orientation === "horizontal";
+
+ // Extract bar configs synchronously from children
+ const lines = useMemo(() => extractBarConfigs(children), [children]);
+
+ const innerWidth = width - margin.left - margin.right;
+ const innerHeight = height - margin.top - margin.bottom;
+
+ // Category accessor function - returns string for categorical scale
+ const categoryAccessor = useCallback(
+ (d: Record): string => {
+ const value = d[xDataKey];
+ if (value instanceof Date) {
+ return shortDateFmt.format(value);
+ }
+ return String(value ?? "");
+ },
+ [xDataKey]
+ );
+
+ // For compatibility with ChartContext, provide a Date-based xAccessor
+ const xAccessorDate = useCallback(
+ (d: Record): Date => {
+ const value = d[xDataKey];
+ if (value instanceof Date) {
+ return value;
+ }
+ return new Date();
+ },
+ [xDataKey]
+ );
+
+ // Category scale (band) - for the categorical axis
+ const categoryScale = useMemo(() => {
+ const domain = data.map((d) => categoryAccessor(d));
+ const range: [number, number] = isHorizontal
+ ? [0, innerHeight]
+ : [0, innerWidth];
+ return scaleBand({
+ range,
+ domain,
+ padding: barGap,
+ });
+ }, [innerWidth, innerHeight, data, categoryAccessor, barGap, isHorizontal]);
+
+ // Band width for bars - use prop if provided, otherwise use scale's bandwidth
+ const bandWidth = barWidthProp ?? categoryScale.bandwidth();
+
+ // Compute max value considering stacking
+ const maxValue = useMemo(() => {
+ if (stacked) {
+ // For stacked bars, sum all values at each data point
+ let max = 0;
+ for (const d of data) {
+ let sum = 0;
+ for (const line of lines) {
+ const value = d[line.dataKey];
+ if (typeof value === "number") {
+ sum += value;
+ }
+ }
+ if (sum > max) {
+ max = sum;
+ }
+ }
+ return max || 100;
+ }
+ // For grouped bars, find max single value
+ let max = 0;
+ for (const line of lines) {
+ for (const d of data) {
+ const value = d[line.dataKey];
+ if (typeof value === "number" && value > max) {
+ max = value;
+ }
+ }
+ }
+ return max || 100;
+ }, [data, lines, stacked]);
+
+ // Value scale (linear) - for the value axis
+ const valueScale = useMemo(() => {
+ const range = isHorizontal ? [0, innerWidth] : [innerHeight, 0];
+ return scaleLinear({
+ range,
+ domain: [0, maxValue * 1.1],
+ nice: true,
+ });
+ }, [innerWidth, innerHeight, maxValue, isHorizontal]);
+
+ const yScales = useMemo(() => {
+ if (isHorizontal) {
+ return wrapSingleYScale(valueScale);
+ }
+ return buildYScalesForLines({
+ lines,
+ data,
+ innerHeight,
+ resolveDomain: (dataKeys) => {
+ let max = 0;
+ for (const d of data) {
+ for (const key of dataKeys) {
+ const value = d[key];
+ if (typeof value === "number" && value > max) {
+ max = value;
+ }
+ }
+ }
+ return [0, (max || 100) * 1.1];
+ },
+ });
+ }, [data, innerHeight, isHorizontal, lines, valueScale]);
+
+ const primaryYScale = getPrimaryYScale(yScales, valueScale);
+
+ // Compute stack offsets for stacked bars
+ const stackOffsets = useMemo(() => {
+ if (!stacked) {
+ return undefined;
+ }
+ const offsets = new Map>();
+ for (let i = 0; i < data.length; i++) {
+ const d = data[i];
+ if (!d) {
+ continue;
+ }
+ const pointOffsets = new Map();
+ let cumulative = 0;
+ for (const line of lines) {
+ pointOffsets.set(line.dataKey, cumulative);
+ const value = d[line.dataKey];
+ if (typeof value === "number") {
+ cumulative += value;
+ }
+ }
+ offsets.set(i, pointOffsets);
+ }
+ return offsets;
+ }, [data, lines, stacked]);
+
+ // Column width for tooltip indicator
+ const columnWidth = useMemo(() => {
+ if (data.length < 1) {
+ return 0;
+ }
+ return isHorizontal ? innerHeight / data.length : innerWidth / data.length;
+ }, [innerWidth, innerHeight, data.length, isHorizontal]);
+
+ // Pre-compute labels for ticker animation
+ const dateLabels = useMemo(
+ () => data.map((d) => categoryAccessor(d)),
+ [data, categoryAccessor]
+ );
+
+ // Create a fake time scale for compatibility with ChartContext
+ const fakeTimeScale = useMemo(() => {
+ const now = Date.now();
+ const start = now - data.length * 24 * 60 * 60 * 1000;
+ const scale = {
+ ...categoryScale,
+ domain: () => [new Date(start), new Date(now)],
+ range: () => [0, innerWidth] as [number, number],
+ invert: (x: number) => new Date(start + (x / innerWidth) * (now - start)),
+ copy: () => scale,
+ };
+ return scale;
+ }, [categoryScale, innerWidth, data.length]);
+
+ // Animation timing — replay when motion settings change
+ // biome-ignore lint/correctness/useExhaustiveDependencies: revealSignature
+ useEffect(() => {
+ setRevealEpoch((n) => n + 1);
+ setIsLoaded(false);
+ // While loading, hold the skeleton (no reveal, no interaction). When
+ // status flips to "ready" this effect re-runs and plays the grow reveal.
+ if (status === "loading") {
+ return;
+ }
+ const timer = setTimeout(() => {
+ setIsLoaded(true);
+ }, animationDuration);
+ return () => clearTimeout(timer);
+ }, [animationDuration, revealSignature, status]);
+
+ useEffect(() => {
+ onPhaseChange?.(isLoaded ? "ready" : "revealing");
+ }, [isLoaded, onPhaseChange]);
+
+ // Mouse move handler
+ const handleMouseMove = useCallback(
+ (event: React.MouseEvent) => {
+ const point = localPoint(event);
+ if (!point) {
+ return;
+ }
+
+ const pos = isHorizontal ? point.y - margin.top : point.x - margin.left;
+
+ // Find which band the mouse is over
+ const bandIndex = Math.floor(pos / columnWidth);
+ const clampedIndex = Math.max(0, Math.min(data.length - 1, bandIndex));
+ const d = data[clampedIndex];
+
+ if (!d) {
+ return;
+ }
+
+ // Calculate positions for each bar
+ const yPositions: Record = {};
+ const xPositions: Record = {};
+ const barPos = categoryScale(categoryAccessor(d)) ?? 0;
+
+ if (isHorizontal) {
+ // Horizontal bars: dots at end of bar (x = value), centered vertically in band
+ const seriesCount = lines.length;
+ const groupGap = seriesCount > 1 ? 4 : 0;
+ const individualBarHeight =
+ seriesCount > 0
+ ? (bandWidth - groupGap * (seriesCount - 1)) / seriesCount
+ : bandWidth;
+
+ if (stacked) {
+ // Stacked horizontal: all bars same y, x at cumulative end
+ let cumulative = 0;
+ for (const line of lines) {
+ const value = d[line.dataKey];
+ if (typeof value === "number") {
+ cumulative += value;
+ const axisScale =
+ yScales[normalizeYAxisId(line.yAxisId)] ?? valueScale;
+ xPositions[line.dataKey] = axisScale(cumulative) ?? 0;
+ yPositions[line.dataKey] = barPos + bandWidth / 2;
+ }
+ }
+ } else {
+ // Grouped horizontal: each bar at its own y position
+ lines.forEach((line, idx) => {
+ const value = d[line.dataKey];
+ if (typeof value === "number") {
+ const axisScale =
+ yScales[normalizeYAxisId(line.yAxisId)] ?? valueScale;
+ xPositions[line.dataKey] = axisScale(value) ?? 0;
+ yPositions[line.dataKey] =
+ barPos +
+ idx * (individualBarHeight + groupGap) +
+ individualBarHeight / 2;
+ }
+ });
+ }
+ } else if (stacked) {
+ // Vertical stacked bars
+ let cumulative = 0;
+ let seriesIdx = 0;
+ for (const line of lines) {
+ const value = d[line.dataKey];
+ if (typeof value === "number") {
+ cumulative += value;
+ const axisScale =
+ yScales[normalizeYAxisId(line.yAxisId)] ?? primaryYScale;
+ const gapOffset = seriesIdx * stackGap;
+ yPositions[line.dataKey] = (axisScale(cumulative) ?? 0) - gapOffset;
+ seriesIdx++;
+ }
+ }
+ } else {
+ // Vertical grouped bars
+ const seriesCount = lines.length;
+ const groupGap = seriesCount > 1 ? 4 : 0;
+ const individualBarWidth =
+ seriesCount > 0
+ ? (bandWidth - groupGap * (seriesCount - 1)) / seriesCount
+ : bandWidth;
+
+ lines.forEach((line, idx) => {
+ const value = d[line.dataKey];
+ if (typeof value === "number") {
+ const axisScale =
+ yScales[normalizeYAxisId(line.yAxisId)] ?? primaryYScale;
+ yPositions[line.dataKey] = axisScale(value) ?? 0;
+ xPositions[line.dataKey] =
+ barPos +
+ idx * (individualBarWidth + groupGap) +
+ individualBarWidth / 2;
+ }
+ });
+ }
+
+ // Tooltip position: for horizontal, position at max bar end; for vertical, center of band
+ let tooltipX: number;
+ if (isHorizontal) {
+ // Position tooltip at the end of the longest bar
+ const maxX = Math.max(...Object.values(xPositions), 0);
+ tooltipX = maxX;
+ } else {
+ tooltipX = barPos + bandWidth / 2;
+ }
+
+ scheduleTooltip({
+ point: d,
+ index: clampedIndex,
+ x: tooltipX,
+ yPositions,
+ xPositions: Object.keys(xPositions).length > 0 ? xPositions : undefined,
+ });
+ },
+ [
+ categoryScale,
+ valueScale,
+ data,
+ lines,
+ margin.left,
+ margin.top,
+ categoryAccessor,
+ columnWidth,
+ bandWidth,
+ isHorizontal,
+ stacked,
+ stackGap,
+ scheduleTooltip,
+ yScales,
+ primaryYScale,
+ ]
+ );
+
+ const handleMouseLeave = useCallback(() => {
+ clearTooltip();
+ }, [clearTooltip]);
+
+ const canInteract = isLoaded;
+
+ // Separate children into defs, pre-overlay, and post-overlay
+ const defsChildren: ReactElement[] = [];
+ const clipExcludedChildren: ReactElement[] = [];
+ const underlayChildren: ReactElement[] = [];
+ const preOverlayChildren: ReactElement[] = [];
+ const postOverlayChildren: ReactElement[] = [];
+
+ forEachChartChild(children, (child) => {
+ const resolvedChild = resolveChartChildElement(child);
+
+ if (isGradientDefComponent(child)) {
+ defsChildren.push(child);
+ } else if (isPatternDefComponent(child)) {
+ preOverlayChildren.push(child);
+ } else if (isPostOverlayComponent(resolvedChild)) {
+ postOverlayChildren.push(resolvedChild);
+ } else if (isClipExcludedComponent(resolvedChild)) {
+ clipExcludedChildren.push(
+ isChartClipPassthrough(child.type) ? resolvedChild : child
+ );
+ } else if (isUnderlayComponent(resolvedChild)) {
+ underlayChildren.push(resolvedChild);
+ } else {
+ preOverlayChildren.push(child);
+ }
+ });
+
+ const referenceAreas = useMemo(
+ () => extractReferenceAreaConfigs(children),
+ [children]
+ );
+
+ const contextValue = {
+ ...DEFAULT_CHART_LIFECYCLE,
+ chartPhase: resolveRestingChartPhase(status),
+ chartStatus: status,
+ data,
+ renderData: data,
+ xScale: fakeTimeScale as unknown as ReturnType<
+ typeof import("@visx/scale").scaleTime
+ >,
+ yScale: isHorizontal ? valueScale : primaryYScale,
+ yScales,
+ width,
+ height,
+ innerWidth,
+ innerHeight,
+ margin,
+ columnWidth,
+ tooltipData,
+ setTooltipData,
+ containerRef,
+ lines,
+ referenceAreas,
+ isLoaded,
+ animationDuration,
+ animationEasing,
+ enterTransition,
+ revealEpoch,
+ xAccessor: xAccessorDate,
+ dateLabels,
+ // Bar-specific properties
+ barScale: categoryScale,
+ bandWidth,
+ hoveredBarIndex,
+ barXAccessor: categoryAccessor,
+ orientation,
+ stacked,
+ stackOffsets,
+ };
+
+ return (
+
+
+ {/* Gradient and pattern definitions */}
+ {defsChildren.length > 0 && {defsChildren} }
+
+
+
+ {/* biome-ignore lint/a11y/noStaticElementInteractions: Chart interaction area */}
+
+ {/* Background rect for mouse event detection */}
+
+
+ {renderKeyedChartLayers(clipExcludedChildren)}
+ {renderKeyedChartLayers(underlayChildren)}
+ {status === "loading" ? (
+
+ ) : (
+ renderKeyedChartLayers(preOverlayChildren)
+ )}
+
+ {/* Markers rendered last so they're on top for interaction */}
+ {renderKeyedChartLayers(postOverlayChildren)}
+
+
+
+ );
+});
+
+export function BarChart({
+ data,
+ xDataKey = "name",
+ margin: marginProp,
+ animationDuration = 1100,
+ animationEasing = DEFAULT_ANIMATION_EASING,
+ enterTransition,
+ revealSignature,
+ aspectRatio = "2 / 1",
+ className = "",
+ barGap = 0.2,
+ barWidth,
+ orientation = "vertical",
+ stacked = false,
+ stackGap = 0,
+ children,
+ onPhaseChange,
+ status = "ready",
+}: BarChartProps) {
+ const containerRef = useRef(null);
+ const margin = { ...DEFAULT_MARGIN, ...marginProp };
+
+ return (
+
+
+ {({ width, height }) => (
+
+ {children}
+
+ )}
+
+
+ );
+}
+
+BarChart.displayName = "BarChart";
+
+export default BarChart;
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/bar-depth-geometry.ts b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/bar-depth-geometry.ts
new file mode 100644
index 0000000..91f76f7
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/bar-depth-geometry.ts
@@ -0,0 +1,44 @@
+/**
+ * Shared 3D bar-depth geometry — the single source of truth for the
+ * perspective math used by BOTH `` (which shrinks a
+ * bar's front-face top) AND the bar-depth surfaces (``'s side +
+ * lid). Keeping the formula here means the front face and the 3D lid can never
+ * drift out of alignment.
+ */
+
+/** Hard ceiling on side-face thickness in px. Capped further by the column gap
+ * so depth never bleeds past the next bar's leading edge. */
+export const BAR_DEPTH_MAX_PX = 7;
+/** The side parallelogram's back edge lifts by `depth * this ratio`, giving a
+ * subtle head-on perspective slope. */
+export const BAR_DEPTH_PERSPECTIVE_RATIO = 0.45;
+
+/**
+ * Maximum side-face depth in px for a chart, clamped so depth never spills past
+ * the gap between bars. `stepWidth` is d3-scaleBand's `step()` (bandwidth +
+ * gap); `bandWidth` is a single bar's width. Returns 0 for gapless/dense charts.
+ */
+export function barDepthMaxDepth(stepWidth: number, bandWidth: number): number {
+ const gap = Math.max(0, stepWidth - bandWidth);
+ return Math.min(bandWidth * 0.22, Math.max(0, gap - 1), BAR_DEPTH_MAX_PX);
+}
+
+/**
+ * Per-bar side-face depth + perspective rise.
+ *
+ * - `absOffset` ∈ [0, 1]: the bar's normalized distance from the chart's
+ * horizontal center. 0 = dead center (no depth); 1 = chart edge (full depth).
+ * - `naturalHeight`: the bar's pixel height. Depth is capped by it so a short
+ * bar's side never reads wider than the bar is tall.
+ * - `maxDepth`: from `barDepthMaxDepth`.
+ */
+export function barDepthAndRise(
+ absOffset: number,
+ naturalHeight: number,
+ maxDepth: number
+): { depth: number; perspectiveRise: number } {
+ const offset = Math.min(1, Math.max(0, absOffset));
+ const cappedMaxDepth = Math.min(maxDepth, Math.max(0, naturalHeight));
+ const depth = offset * cappedMaxDepth;
+ return { depth, perspectiveRise: depth * BAR_DEPTH_PERSPECTIVE_RATIO };
+}
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/bar-depth.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/bar-depth.tsx
new file mode 100644
index 0000000..fa77f71
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/bar-depth.tsx
@@ -0,0 +1,1074 @@
+"use client";
+
+/**
+ * Bar 3D depth + glass surfaces — drop-in layers for ``.
+ *
+ * Adds a head-on perspective and a glossy glass-block sheen to the bars in
+ * any ``. Bars left of chart center expose their RIGHT side face;
+ * bars right of center expose their LEFT — the vanishing point sits at the
+ * chart's horizontal middle, so the row reads like vertical glass blocks
+ * viewed straight on.
+ *
+ * Three surfaces per bar:
+ * 1. Side face — a parallelogram on the depth side, lit at the front edge
+ * with a horizontal black gradient and shaded toward the back.
+ * 2. Top face — a parallelogram lid above the bar's top edge, with a
+ * bright catch at the front lip fading to soft white at the back.
+ * 3. Glass overlay — a vertical white-to-dark gradient on the bar's front
+ * face (sharp top reflection, transparent middle, faint bottom shadow).
+ *
+ * Usage (purely additive — no changes to ``):
+ *
+ *
+ *
+ * // before
+ *
+ * // after
+ *
+ *
+ * Both depth layers read geometry from `useChart()` and figure out the
+ * perspective themselves. The only configuration is the bar color
+ * (`color` / `colorAccessor`) and, for stacked bars, a `segmentsAccessor`
+ * supplied via ``.
+ *
+ * An optional `` paints a looping vertical sweep over a single
+ * "active" bar (selected by `activeIndex`) — useful for highlighting a live
+ * or in-progress value. Render it AFTER ``.
+ *
+ * Behavior notes:
+ * - Negative values are supported: a bar whose value is below zero grows
+ * downward from the baseline, with the lid sitting on the baseline.
+ * (This requires the host `` to use a value scale whose
+ * domain includes negatives.)
+ * - Zero-value bars are skipped by default; set `minBarHeight` on
+ * `` (and ``) to floor them to a short visible bar.
+ *
+ * Performance:
+ * - Three shared SVG gradients per layer instance (ID-collision-safe via
+ * `useId()`).
+ * - A per-bar `` for the side face animates from the baseline so
+ * the static side path is revealed bottom-up in lockstep with the bar.
+ * Only used where depth ≥ 0.5px.
+ * - At high bar density the column gap clamps depth below the 0.5px
+ * threshold; the back surfaces skip rendering and only the cheap glass
+ * rect remains.
+ * - `useBarDepthEntries` is memoized, so the Back/Front/Pulse layers
+ * compute geometry at most once per render between them.
+ */
+
+import { motion, type Transition } from "motion/react";
+import {
+ createContext,
+ type ReactNode,
+ useContext,
+ useId,
+ useMemo,
+} from "react";
+import { barDepthAndRise, barDepthMaxDepth } from "./bar-depth-geometry";
+import { useChartHover, useChartStable } from "./chart-context";
+import { transitionWithDelay } from "./motion-utils";
+
+// ─── Constants ───────────────────────────────────────────────────────
+
+/** Below this depth the back surfaces don't render — saves elements for
+ * dead-center bars and dense charts where perspective is sub-pixel. */
+const BAR_DEPTH_MIN_PX = 0.5;
+/** Mirrors `Bar`'s default `fadedOpacity` so depth/glass dim in step with
+ * their bar when a sibling bar is hovered. */
+const BAR_FADED_OPACITY = 0.3;
+/** Default bar color when neither `color` nor `colorAccessor` is provided. */
+const DEFAULT_BAR_COLOR = "var(--chart-line-primary)";
+/** Default opacity of the baseline "contact shadow" that grounds each bar at
+ * the zero line. Configurable via ``. */
+const DEFAULT_GROUND_SHADOW = 0.26;
+/** Peak opacity of the white reflection catch at each bar's tip. Kept here so
+ * the front/side glass and the solid lid highlight all use the same value. */
+const GLASS_TIP_OPACITY = 0.2;
+
+// `BarPulse` — vertical sweep wave constants. The wave is a tall translucent
+// rect with a centered bright band; it translates from below the bar's bottom
+// edge to above the lid's back edge on a loop, clipped to the bar's full 3D
+// silhouette.
+/** Wave height = max(barHeight × this, PULSE_WAVE_HEIGHT_MIN_PX). 0.55 puts
+ * the bright band's peak in the middle of the bar at mid-travel. */
+const PULSE_WAVE_HEIGHT_RATIO = 0.55;
+/** Minimum wave height in px so short bars still pulse on a steady cadence —
+ * the wave is taller than the bar so the bright band sweeps through cleanly
+ * instead of flashing for one frame. */
+const PULSE_WAVE_HEIGHT_MIN_PX = 36;
+/** Seconds per sweep (bottom → above the lid). 2.4s reads as a deliberate
+ * heartbeat; faster feels frantic, slower feels laggy. */
+const PULSE_WAVE_DURATION_S = 2.4;
+/** Peak opacity of the wave's brightest gradient stop. White at 0.85 is
+ * bright enough to register over saturated bar colors while the bell-curve
+ * stops around it keep the falloff soft so the bar's hue still dominates. */
+const PULSE_WAVE_PEAK_OPACITY = 0.85;
+
+const BAR_HOVER_TRANSITION = { duration: 0.15, ease: "easeOut" as const };
+
+// ─── Types ───────────────────────────────────────────────────────────
+
+export interface BarDepthEntry {
+ label: string;
+ /** Index in the original `data` array — used for stagger calc. NOT the
+ * index in this entries list. */
+ dataIndex: number;
+ /** Original datum reference — handed back to `colorAccessor` /
+ * `segmentsAccessor` so consumers can color or split bars without
+ * bar-depth knowing the data shape. */
+ datum: Record;
+ /** True when this bar is the active bar (`dataIndex === activeIndex`).
+ * Used only by ``; geometry is identical regardless. */
+ isActive: boolean;
+ /** True when the bar's value is negative — the bar extends downward from
+ * the baseline. The lid still sits at the baseline; the side parallelogram
+ * grows downward instead of upward. */
+ isNegative: boolean;
+ /** y of the chart's baseline (= `yScale(0)`). For positive bars this equals
+ * `bottomY`; for negative bars it equals `topY`. Used as the grow-from
+ * anchor for both the clipPath (side face) and the glass rect. */
+ baselineY: number;
+ bandX: number;
+ bandWidth: number;
+ /** Visual top of the bar in screen Y (smaller y = higher on screen). */
+ topY: number;
+ /** Visual bottom of the bar in screen Y. */
+ bottomY: number;
+ barHeight: number;
+ /** Un-trimmed natural pixel height of the bar (= `|baselineY - yScale(value)|`).
+ * Segment heights are scaled against this so they line up with ``. */
+ naturalHeight: number;
+ /** Pixels subtracted from the bar's visual top (and added to `topY`) so the
+ * lid's back edge lands at `yScale(value)` regardless of depth. */
+ topYTrim: number;
+ /** Side-face thickness in px (0 = dead-center bar / dense chart). */
+ depth: number;
+ perspectiveRise: number;
+ isRightOfCenter: boolean;
+}
+
+/** One segment of a stacked bar, in stack order bottom→top. The values across
+ * all segments should sum to the bar's value; bar-depth uses the proportions
+ * to pick segment boundaries that line up with the front face's stacking. */
+export interface BarDepthSegment {
+ value: number;
+ color: string;
+}
+
+/** Resolve a per-datum color, falling back to the shared `color` default. */
+type ColorAccessor = (datum: Record, index: number) => string;
+
+function resolveColor(
+ datum: Record,
+ index: number,
+ color: string,
+ colorAccessor?: ColorAccessor
+): string {
+ return colorAccessor?.(datum, index) ?? color;
+}
+
+/** Side-face parallelogram between two screen-Y edges, on the depth side of
+ * the bar (left of the bar for right-of-center bars, right for left-of). */
+function sideFacePath(
+ e: BarDepthEntry,
+ topEdge: number,
+ bottomEdge: number
+): string {
+ const rise = e.perspectiveRise;
+ if (e.isRightOfCenter) {
+ const x = e.bandX;
+ return `M ${x} ${topEdge} L ${x - e.depth} ${topEdge - rise} L ${x - e.depth} ${bottomEdge - rise} L ${x} ${bottomEdge} Z`;
+ }
+ const x = e.bandX + e.bandWidth;
+ return `M ${x} ${topEdge} L ${x + e.depth} ${topEdge - rise} L ${x + e.depth} ${bottomEdge - rise} L ${x} ${bottomEdge} Z`;
+}
+
+/** Lid (top face) parallelogram above the bar's top edge. */
+function lidFacePath(e: BarDepthEntry): string {
+ const rise = e.perspectiveRise;
+ const left = e.bandX;
+ const right = e.bandX + e.bandWidth;
+ if (e.isRightOfCenter) {
+ return `M ${left} ${e.topY} L ${right} ${e.topY} L ${right - e.depth} ${e.topY - rise} L ${left - e.depth} ${e.topY - rise} Z`;
+ }
+ return `M ${left} ${e.topY} L ${right} ${e.topY} L ${right + e.depth} ${e.topY - rise} L ${left + e.depth} ${e.topY - rise} Z`;
+}
+
+/**
+ * Build the side-face parallelograms for a bar. Without segments it's a single
+ * piece in `baseColor`; with segments each gets its natural scaled height
+ * `(seg.value/total) * naturalHeight` and only the topmost has `topYTrim`
+ * removed — matching exactly how `` stacks segments, so the side seams
+ * line up with the front face.
+ */
+function buildSidePieces(
+ e: BarDepthEntry,
+ visibleSegments: BarDepthSegment[] | null,
+ baseColor: string
+): { path: string; color: string }[] {
+ if (!(visibleSegments && visibleSegments.length > 0)) {
+ return [{ path: sideFacePath(e, e.topY, e.bottomY), color: baseColor }];
+ }
+ const totalValue = visibleSegments.reduce((sum, s) => sum + s.value, 0);
+ const pieces: { path: string; color: string }[] = [];
+ let cursorY = e.bottomY;
+ for (let i = 0; i < visibleSegments.length; i++) {
+ const seg = visibleSegments[i];
+ if (!seg) {
+ continue;
+ }
+ const isTopmost = i === visibleSegments.length - 1;
+ const segScaledHeight = (seg.value / totalValue) * e.naturalHeight;
+ const segHeight = isTopmost
+ ? Math.max(0, segScaledHeight - e.topYTrim)
+ : segScaledHeight;
+ const segBottomY = cursorY;
+ const segTopY = cursorY - segHeight;
+ cursorY = segTopY;
+ pieces.push({
+ path: sideFacePath(e, segTopY, segBottomY),
+ color: seg.color,
+ });
+ }
+ return pieces;
+}
+
+// ─── Context ──────────────────────────────────────────────────────────
+// `BarDepthProvider` is the single source of truth for `segmentsAccessor`,
+// shared by every depth surface (``, ``,
+// ``) AND by the entries hook those surfaces consume.
+//
+// Why context instead of per-component props: `useBarDepthEntries` computes
+// `topYTrim` from the topmost segment's scaled height, so all three layers
+// need the *same* `segmentsAccessor` to produce the same `topY`. A single
+// provider above the chart makes it structurally impossible for them to
+// disagree.
+//
+// `BarChart` reads its `` children with `React.Children.forEach`, which
+// doesn't recurse into a provider. Wrap the provider *around* the
+// ``, not inside it.
+
+interface BarDepthContextValue {
+ segmentsAccessor?: (
+ datum: Record
+ ) => BarDepthSegment[] | null | undefined;
+ /** Opacity (0–1) of the baseline contact shadow. 0 removes it. */
+ groundShadow?: number;
+ /** Minimum bar height in px — floors short/zero bars so they stay visible.
+ * Match the value passed to ``. */
+ minBarHeight?: number;
+}
+
+const BarDepthContext = createContext({});
+
+export interface BarDepthProviderProps extends BarDepthContextValue {
+ children: ReactNode;
+}
+
+/**
+ * Provides shared bar-depth configuration to every depth surface beneath it.
+ * Wrap a `` whose children include `` /
+ * `` / `` with this provider to:
+ * - split stacked bars into per-segment side faces (`segmentsAccessor`),
+ * - tune the baseline contact shadow (`groundShadow`, default 0.26; 0 = off),
+ * - floor short/zero bars so they stay visible (`minBarHeight`; pair with the
+ * same value on ``).
+ *
+ * Because the glass gradient is chart-wide (one Y-anchored ramp shared by the
+ * front face and the side glass), the provider is the single source of truth —
+ * so the surfaces can never disagree.
+ */
+export function BarDepthProvider({
+ segmentsAccessor,
+ groundShadow,
+ minBarHeight,
+ children,
+}: BarDepthProviderProps) {
+ // Memoize so consumers don't re-render when the config is stable.
+ const value = useMemo(
+ () => ({ segmentsAccessor, groundShadow, minBarHeight }),
+ [segmentsAccessor, groundShadow, minBarHeight]
+ );
+ return (
+
+ {children}
+
+ );
+}
+
+BarDepthProvider.displayName = "BarDepthProvider";
+
+// ─── Per-bar glass gradient ──────────────────────────────────────────
+// The glass is anchored to EACH BAR's own height (objectBoundingBox: 0% = the
+// bar's tip, 100% = its baseline), not to the chart. So every bar — tall or
+// short — gets the identical proportional gloss. (Anchoring per-chart made a
+// tall bar's tip bright while a short bar's tip fell in the transparent middle
+// and read as matte.)
+
+interface GlassGradientStop {
+ offset: string;
+ color: string;
+ opacity: string;
+}
+
+/**
+ * Stop list for a POSITIVE bar's glass (0% = tip, 100% = baseline): a sharp
+ * white reflection catch at the tip falling off fast to transparent, then the
+ * contact shadow at the base.
+ */
+function buildPosBarStops(groundShadow: number): GlassGradientStop[] {
+ return [
+ { offset: "0%", color: "white", opacity: String(GLASS_TIP_OPACITY) },
+ { offset: "3%", color: "white", opacity: "0.09" },
+ { offset: "8%", color: "white", opacity: "0.02" },
+ { offset: "55%", color: "white", opacity: "0" },
+ { offset: "100%", color: "black", opacity: String(groundShadow) },
+ ];
+}
+
+/**
+ * Mirror of `buildPosBarStops` for NEGATIVE bars (which grow downward): the
+ * contact shadow sits at the top (the baseline) and the reflection catch at
+ * the bottom (the bar's tip).
+ */
+function buildNegBarStops(groundShadow: number): GlassGradientStop[] {
+ return [
+ { offset: "0%", color: "black", opacity: String(groundShadow) },
+ { offset: "45%", color: "white", opacity: "0" },
+ { offset: "92%", color: "white", opacity: "0.02" },
+ { offset: "97%", color: "white", opacity: "0.09" },
+ { offset: "100%", color: "white", opacity: String(GLASS_TIP_OPACITY) },
+ ];
+}
+
+/**
+ * Build both per-bar glass stop lists from the current `groundShadow`. Each
+ * depth surface picks `posStops` or `negStops` by the bar's direction and
+ * paints it objectBoundingBox, so the gloss is identical across bar heights.
+ */
+function useGlassStops() {
+ const { groundShadow = DEFAULT_GROUND_SHADOW } = useContext(BarDepthContext);
+ return useMemo(
+ () => ({
+ posStops: buildPosBarStops(groundShadow),
+ negStops: buildNegBarStops(groundShadow),
+ }),
+ [groundShadow]
+ );
+}
+
+function readBarValue(d: Record, key: string): number {
+ const v = key ? d[key] : undefined;
+ return typeof v === "number" ? v : 0;
+}
+
+// ─── Hooks ───────────────────────────────────────────────────────────
+
+/**
+ * Compute per-bar 3D depth geometry from chart context.
+ *
+ * Memoized on the chart's scales + data, so calling this from sibling layers
+ * within the same chart is essentially free after the first settles. Returns
+ * one entry per renderable bar; bars with no resolvable height are excluded.
+ *
+ * Negative-value bars are kept — `isNegative` flips the entry's geometry so
+ * the side parallelogram and lid render in the correct direction.
+ *
+ * Exported for advanced consumers that want to render custom layers aligned
+ * with the same geometry (e.g. labels above each bar's top face).
+ */
+export function useBarDepthEntries(
+ dataKey: string,
+ activeIndex?: number
+): BarDepthEntry[] {
+ const {
+ data,
+ barScale,
+ bandWidth,
+ yScale,
+ innerHeight,
+ innerWidth,
+ barXAccessor,
+ } = useChartStable();
+ // Read shared config from ``. Outside a provider these are
+ // the defaults (no segmentation, no floor) — correct for simple charts.
+ const { segmentsAccessor, minBarHeight = 0 } = useContext(BarDepthContext);
+
+ return useMemo(() => {
+ if (!(barScale && bandWidth && barXAccessor)) {
+ return [];
+ }
+
+ const centerX = innerWidth / 2;
+ const zeroY = yScale(0) ?? innerHeight;
+
+ // d3-scaleBand exposes step() (= bandwidth + gap); the shared helper caps
+ // depth below the gap so a side face never spills past the next bar.
+ const step =
+ (barScale as unknown as { step?: () => number }).step?.() ?? bandWidth;
+ const maxDepth = barDepthMaxDepth(step, bandWidth);
+
+ // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: per-bar 3D geometry (direction, perspective trim, segment scaling) reads clearest as one pass
+ return data.flatMap((d, dataIndex): BarDepthEntry[] => {
+ // Stacked bars: the bar's value is the SUM of its segments (so the depth
+ // height matches the host BarChart's stacked total). Single bars: the
+ // `dataKey` column. `segmentsAccessor` comes from ``.
+ const visibleSegs =
+ segmentsAccessor?.(d)?.filter((s) => s.value > 0) ?? null;
+ const value =
+ visibleSegs && visibleSegs.length > 0
+ ? visibleSegs.reduce((sum, s) => sum + s.value, 0)
+ : readBarValue(d, dataKey);
+ const isActive = activeIndex != null && dataIndex === activeIndex;
+ const isNegative = value < 0;
+ const label = barXAccessor(d);
+ const bandX = barScale(label) ?? 0;
+ const cx = bandX + bandWidth / 2;
+
+ // `Math.abs` so negative bars produce a positive height — direction is
+ // captured by `isNegative`. Non-negative bars are floored to
+ // `minBarHeight` so short/zero values stay visible (mirrors
+ // ``); a floored bar skips the perspective trim so its
+ // tiny front face and lid stay aligned. With the default floor of 0,
+ // zero-value bars produce no height and are skipped.
+ const valuePos = yScale(value) ?? innerHeight;
+ const rawHeight = Math.abs(zeroY - valuePos);
+ const naturalHeight = isNegative
+ ? rawHeight
+ : Math.max(rawHeight, minBarHeight);
+ if (naturalHeight <= 0) {
+ return [];
+ }
+ const isFloored = !isNegative && rawHeight < naturalHeight;
+
+ const offsetFromCenter = centerX > 0 ? (cx - centerX) / centerX : 0;
+ const isRightOfCenter = offsetFromCenter > 0;
+ const absOffset = Math.min(1, Math.abs(offsetFromCenter));
+ const { depth, perspectiveRise: rawRise } = barDepthAndRise(
+ absOffset,
+ naturalHeight,
+ maxDepth
+ );
+ // Perspective lift applies only to positive bars. For negative bars a
+ // lift would raise the lid's back edge above the baseline (a visible
+ // "lip" at the chart edges), so it's forced to 0 — degenerating the lid
+ // and flattening the side into a rectangle flush with the baseline.
+ const perspectiveRise = isNegative ? 0 : rawRise;
+
+ // Perspective trim — positive value-bearing bars only. The lid's back
+ // edge sits `perspectiveRise` above `topY`. Without trim the silhouette
+ // top would vary with depth (bars further from center look taller).
+ // Trim shifts `topY` DOWN by `perspectiveRise` so the lid's back edge
+ // lands on `yScale(value)` regardless of depth. The clamp denominator
+ // is the topmost segment's scaled height (for stacked bars) or the whole
+ // bar's natural height, matching how `` stacks segments.
+ let trimClampReference = naturalHeight;
+ if (visibleSegs && visibleSegs.length > 0) {
+ const segTotal = visibleSegs.reduce((sum, s) => sum + s.value, 0);
+ const topmost = visibleSegs.at(-1);
+ if (segTotal > 0 && topmost) {
+ trimClampReference = (topmost.value / segTotal) * naturalHeight;
+ }
+ }
+ const isValueBar = naturalHeight > 0 && !isNegative && !isFloored;
+ const topYTrim = isValueBar
+ ? Math.min(perspectiveRise, Math.max(0, trimClampReference - 1))
+ : 0;
+ // Direction-aware top/bottom: positive bars hang ABOVE the baseline;
+ // negative bars hang BELOW it. The lid always lives at `topY` so for
+ // negative bars it sits on the baseline.
+ const topY = isNegative ? zeroY : zeroY - naturalHeight + topYTrim;
+ const bottomY = isNegative ? zeroY + naturalHeight : zeroY;
+ const barHeight = bottomY - topY;
+
+ return [
+ {
+ label,
+ dataIndex,
+ datum: d,
+ isActive,
+ isNegative,
+ baselineY: zeroY,
+ bandX,
+ bandWidth,
+ topY,
+ bottomY,
+ barHeight,
+ naturalHeight,
+ topYTrim,
+ depth,
+ perspectiveRise,
+ isRightOfCenter,
+ },
+ ];
+ });
+ }, [
+ data,
+ barScale,
+ bandWidth,
+ yScale,
+ innerHeight,
+ innerWidth,
+ barXAccessor,
+ segmentsAccessor,
+ minBarHeight,
+ dataKey,
+ activeIndex,
+ ]);
+}
+
+function useBarDepthStagger() {
+ const { data, animationDuration } = useChartStable();
+ // Mirror `Bar`'s stagger formula so each depth surface enters in lockstep
+ // with its front face (`Bar` uses `animationDuration * 0.4 / count`).
+ const total = animationDuration || 1100;
+ return data.length > 1 ? (total * 0.4) / 1000 / data.length : 0;
+}
+
+// ─── Components ───────────────────────────────────────────────────────
+
+export interface BarDepthBackProps {
+ /** Key in data for the bar value. Match the sibling ``. */
+ dataKey: string;
+ /** Solid color for the side + top faces. Default: var(--chart-line-primary). */
+ color?: string;
+ /** Per-bar color override; takes precedence over `color` when provided. */
+ colorAccessor?: ColorAccessor;
+}
+
+/**
+ * Renders the back surfaces (side + top faces) of the 3D bar effect.
+ *
+ * Place BEFORE the `` elements in `` children so the bar's
+ * solid front face occludes any depth that would extend into an adjacent
+ * column. Skips bars at chart center (depth ≈ 0) and dense charts.
+ *
+ * For stacked bars, wrap the `` in a
+ * `` so the side face splits into
+ * per-segment parallelograms and the lid color matches the topmost segment.
+ */
+export function BarDepthBack({
+ dataKey,
+ color = DEFAULT_BAR_COLOR,
+ colorAccessor,
+}: BarDepthBackProps) {
+ const { isLoaded, enterTransition, revealEpoch = 0 } = useChartStable();
+ const { hoveredBarIndex } = useChartHover();
+ const { segmentsAccessor } = useContext(BarDepthContext);
+ const entries = useBarDepthEntries(dataKey);
+ const stagger = useBarDepthStagger();
+ const { posStops: glassStops, negStops: glassNegStops } = useGlassStops();
+ const idPrefix = useId().replace(/[^a-z0-9]/gi, "_");
+
+ // Two horizontal black gradients (one per depth direction) shade the side
+ // from front-edge (lit) to back-edge (in shadow). A single vertical white
+ // gradient on the lid: bright catch at the front lip → soft white at the
+ // back. The vertical glass gradient layered on top of the side adds the same
+ // bright-tip / dark-baseline curve as the front face for continuity.
+ const sideShadeRtl = `bdb-side-rtl-${idPrefix}`;
+ const sideShadeLtr = `bdb-side-ltr-${idPrefix}`;
+ const topShade = `bdb-top-${idPrefix}`;
+ const sideGlass = `bdb-glass-${idPrefix}`;
+ // Per-bar bbox variant used only for bars that extend below the baseline.
+ const sideGlassNeg = `bdb-glass-neg-${idPrefix}`;
+
+ return (
+
+
+ {/* Side shade — diagonal: lit corner at front-top, dark corner at
+ back-bottom. Right-of-center bars have the side on the LEFT
+ (gradient runs (1,0)→(0,1)); left-of-center bars mirror it. */}
+
+
+
+
+
+
+
+
+ {/* Lid 3D shade — mirrors the side face's directionality so the lid
+ never reads as a brighter "white-washed cap". Front lip at opacity
+ 0 (seamless with the front face), back edge picks up shadow as it
+ angles into perspective. */}
+
+
+
+
+ {/* Side glass — per-bar (objectBoundingBox): 0% = the bar's tip,
+ 100% = its baseline, same proportional gloss as the front face. */}
+
+ {glassStops.map((stop) => (
+
+ ))}
+
+ {/* Side glass (per-bar bbox variant for negative bars). The gradient
+ runs from the bar's baseline-edge (top of bbox) down to the bar's
+ value tip (bottom of bbox), lifting the tip glow onto each bar's
+ own value edge. */}
+
+ {glassNegStops.map((stop) => (
+
+ ))}
+
+
+
+ {entries.map((e) => {
+ if (e.depth < BAR_DEPTH_MIN_PX) {
+ return null;
+ }
+
+ // Resolve stacked segments for this bar. Filter to non-zero so a
+ // missing segment doesn't paint an invisible 0-height piece or quietly
+ // become the lid color.
+ const rawSegments = segmentsAccessor?.(e.datum);
+ const visibleSegments = rawSegments?.filter((s) => s.value > 0) ?? null;
+ const baseColor = resolveColor(
+ e.datum,
+ e.dataIndex,
+ color,
+ colorAccessor
+ );
+
+ const sidePieces = buildSidePieces(e, visibleSegments, baseColor);
+ // Full-side path for the vertical glass overlay — drawn once per bar
+ // so the brightness curve sweeps across the entire side as one layer.
+ const fullSidePath = sideFacePath(e, e.topY, e.bottomY);
+ // Lid is one parallelogram regardless of segmentation — colored by the
+ // topmost visible segment so it matches the front face's top section.
+ const topPath = lidFacePath(e);
+ const topColor = visibleSegments?.at(-1)?.color ?? baseColor;
+
+ const delay = e.dataIndex * stagger;
+ // Grow uses the SAME enter transition + stagger as `` so the side
+ // and lid rise at the exact speed of the front face. Post-load geometry
+ // changes get a quick settle instead.
+ const growTransition = isLoaded
+ ? { duration: 0.3, ease: "easeOut" as const }
+ : transitionWithDelay(enterTransition, delay);
+ const isFaded =
+ hoveredBarIndex != null && hoveredBarIndex !== e.dataIndex;
+ const sideShadeId = e.isRightOfCenter ? sideShadeRtl : sideShadeLtr;
+ const safeId = e.label.replace(/[^a-z0-9]/gi, "_");
+ const clipId = `bdb-clip-${idPrefix}-${safeId}`;
+ // Lid ENTRANCE only (hover-dim is owned by the wrapping group below, so
+ // the lid can never dim differently from the side). Positive lids
+ // translate up from the baseline riding the growing top edge; negative
+ // lids already sit at the baseline, so they fade in instead.
+ let lidEntrance: {
+ initial: false | { y?: number; opacity?: number };
+ animate: { y?: number; opacity?: number };
+ transition: Transition;
+ } = {
+ initial: false,
+ animate: { y: 0 },
+ transition: { y: growTransition },
+ };
+ if (!isLoaded && e.isNegative) {
+ lidEntrance = {
+ initial: { y: 0, opacity: 0 },
+ animate: { y: 0, opacity: 1 },
+ transition: { y: growTransition, opacity: growTransition },
+ };
+ } else if (!isLoaded) {
+ lidEntrance = {
+ initial: { y: e.barHeight },
+ animate: { y: 0 },
+ transition: { y: growTransition },
+ };
+ }
+
+ return (
+ // Single hover-dim group for the whole back surface — side AND lid
+ // share this one opacity so they always fade in lockstep (mirrors
+ // ``, which puts grow on the inner rect and hover-dim on a
+ // wrapping group). Entrance animations live on the inner groups.
+
+ {/* Side face — a clip-rect that grows from the baseline reveals the
+ static side path(s) bottom-up, in lockstep with the bar's grow
+ animation. ClipPath beats CSS scaleY because motion writes the
+ SVG `transform` directly. */}
+
+
+ {/* Keyed on revealEpoch AND isLoaded so it (a) replays on
+ every reveal and (b) snaps to final the instant ``
+ swaps to its static rect at isLoaded — so the side never
+ trails the face past the animation cutoff. */}
+
+
+
+
+ {/* Per-segment SOLID colors only. */}
+ {sidePieces.map((piece) => (
+
+ ))}
+ {/* Directional shade + Y-anchored glass painted ONCE over the
+ full side so both gradients stay continuous across segment
+ seams. Per-segment shading uses objectBoundingBox units, which
+ would restart the lit→shadowed ramp at every stacked boundary
+ and read as a notch/indent. The glass is the same global
+ Y-anchored curve as the front face; negatives swap to the
+ per-bar bbox variant so the tip catch lands on the bar's own
+ bottom edge. */}
+
+
+
+
+ {/* Top face — three paint passes: solid color → a flat tip-bright
+ highlight (the lid is the bar's top, so it gets the reflection
+ catch; it can't share the side's objectBoundingBox glass, which
+ would map the full ramp onto the lid's own tiny bbox) →
+ directional 3D shade darkening the back edge. */}
+
+
+
+
+
+
+ );
+ })}
+
+ );
+}
+
+BarDepthBack.displayName = "BarDepthBack";
+// Mark as a non-series layer so `BarChart` doesn't count it as a bar (it
+// carries a `dataKey` to pair with a Bar). Mirrors `ChartMarkers`'
+// `__isChartMarkers` flag — a static property is minification-safe, unlike
+// matching on `displayName`.
+(BarDepthBack as { __isBarDepthLayer?: boolean }).__isBarDepthLayer = true;
+
+export interface BarDepthFrontProps {
+ /** Key in data for the bar value. Match the sibling ``. */
+ dataKey: string;
+}
+
+/**
+ * Renders the front surface: a glossy glass sheen over the bar's solid fill.
+ * Place AFTER the `` elements in `` children so the gradient
+ * sits on top of the bar.
+ *
+ * Reads `segmentsAccessor` from the surrounding `` (if any)
+ * via `useBarDepthEntries`, so the glass rectangle's `topY` automatically
+ * matches the lid+side's trim. No prop wiring required.
+ */
+export function BarDepthFront({ dataKey }: BarDepthFrontProps) {
+ const { isLoaded, enterTransition, revealEpoch = 0 } = useChartStable();
+ const { hoveredBarIndex } = useChartHover();
+ const entries = useBarDepthEntries(dataKey);
+ const stagger = useBarDepthStagger();
+ const { posStops: glassStops, negStops: glassNegStops } = useGlassStops();
+ const idPrefix = useId().replace(/[^a-z0-9]/gi, "_");
+ const glassId = `bdf-glass-${idPrefix}`;
+ const glassIdNeg = `bdf-glass-neg-${idPrefix}`;
+
+ return (
+
+
+ {/* Per-bar (objectBoundingBox) glass for POSITIVE bars: 0% = the bar's
+ tip, 100% = its baseline — same proportional gloss on every bar. */}
+
+ {glassStops.map((stop) => (
+
+ ))}
+
+ {/* Per-bar bbox variant for negative bars — the bright catch always
+ sits on the value-bearing tip no matter how deep the bar reaches. */}
+
+ {glassNegStops.map((stop) => (
+
+ ))}
+
+
+
+ {entries.map((e) => {
+ const delay = e.dataIndex * stagger;
+ // Same enter transition + stagger as `` so the glass rises in
+ // lockstep with the front face.
+ const baseTransition = isLoaded
+ ? { duration: 0.3, ease: "easeOut" as const }
+ : transitionWithDelay(enterTransition, delay);
+ const isFaded =
+ hoveredBarIndex != null && hoveredBarIndex !== e.dataIndex;
+
+ return (
+ `.
+ initial={isLoaded ? false : { y: e.baselineY, height: 0 }}
+ key={`${e.label}-${revealEpoch}-${isLoaded}`}
+ transition={{ ...baseTransition, opacity: BAR_HOVER_TRANSITION }}
+ width={e.bandWidth}
+ x={e.bandX}
+ />
+ );
+ })}
+
+ );
+}
+
+BarDepthFront.displayName = "BarDepthFront";
+(BarDepthFront as { __isBarDepthLayer?: boolean }).__isBarDepthLayer = true;
+
+// ─── BarPulse ─────────────────────────────────────────────────────────
+
+/**
+ * Builds the SVG path tracing the silhouette of a 3D bar — the unioned
+ * outline of front face + visible side parallelogram + lid parallelogram.
+ * Used as the clip region for `BarPulse`'s wave so the gradient only paints
+ * inside the bar's visible 3D shape. For dead-center bars (`depth <= 0`) the
+ * silhouette degenerates to just the front rectangle.
+ */
+function buildBarSilhouettePath(e: BarDepthEntry): string {
+ const {
+ bandX,
+ bandWidth,
+ topY,
+ bottomY,
+ depth,
+ perspectiveRise,
+ isRightOfCenter,
+ } = e;
+ if (depth <= 0) {
+ return [
+ `M ${bandX} ${topY}`,
+ `L ${bandX + bandWidth} ${topY}`,
+ `L ${bandX + bandWidth} ${bottomY}`,
+ `L ${bandX} ${bottomY}`,
+ "Z",
+ ].join(" ");
+ }
+ if (isRightOfCenter) {
+ // Side face on LEFT — back shifted (-depth, -perspectiveRise).
+ return [
+ `M ${bandX - depth} ${topY - perspectiveRise}`,
+ `L ${bandX + bandWidth - depth} ${topY - perspectiveRise}`,
+ `L ${bandX + bandWidth} ${topY}`,
+ `L ${bandX + bandWidth} ${bottomY}`,
+ `L ${bandX} ${bottomY}`,
+ `L ${bandX - depth} ${bottomY - perspectiveRise}`,
+ "Z",
+ ].join(" ");
+ }
+ // Side face on RIGHT — back shifted (+depth, -perspectiveRise).
+ return [
+ `M ${bandX} ${topY}`,
+ `L ${bandX + depth} ${topY - perspectiveRise}`,
+ `L ${bandX + bandWidth + depth} ${topY - perspectiveRise}`,
+ `L ${bandX + bandWidth + depth} ${bottomY - perspectiveRise}`,
+ `L ${bandX + bandWidth} ${bottomY}`,
+ `L ${bandX} ${bottomY}`,
+ "Z",
+ ].join(" ");
+}
+
+export interface BarPulseProps {
+ /** Key in data for the bar value. Match the sibling ``. */
+ dataKey: string;
+ /** Index (in the data array) of the bar to pulse. */
+ activeIndex?: number;
+ /** Suppress the sweep while keeping the bar's 3D + glass treatment. */
+ pulsePaused?: boolean;
+}
+
+/**
+ * Vertical wave that sweeps from a bar's root (baseline) toward its tip on a
+ * continuous loop, painted across the bar's full 3D silhouette (front face +
+ * lid + visible side). Reads as a heartbeat pulse flowing up the column for
+ * positive bars and down for negative bars — useful for marking a live or
+ * in-progress value without adding any fixed element.
+ *
+ * Select the bar via `activeIndex`. Set `pulsePaused` to freeze it. Place
+ * AFTER ` ` so the wave brightens on top of the glass.
+ *
+ * Performance: at most one clipped `` with a single Y translate
+ * per frame (no per-frame paint of gradient stops, no SVG filters).
+ */
+export function BarPulse({ dataKey, activeIndex, pulsePaused }: BarPulseProps) {
+ const { isLoaded } = useChartStable();
+ const { hoveredBarIndex } = useChartHover();
+ const entries = useBarDepthEntries(dataKey, activeIndex);
+ const idPrefix = useId().replace(/[^a-z0-9]/gi, "_");
+ const activeEntries = useMemo(
+ () => entries.filter((e) => e.isActive),
+ [entries]
+ );
+
+ if (activeEntries.length === 0 || pulsePaused) {
+ return null;
+ }
+ // Hold the wave until bars finish growing — the silhouette clip is computed
+ // at the final geometry, so painting it mid-grow would reveal a wave inside
+ // an area the bar hasn't reached yet.
+ if (!isLoaded) {
+ return null;
+ }
+
+ return (
+
+
+ {activeEntries.flatMap((e) => {
+ const safeId = e.label.replace(/[^a-z0-9]/gi, "_");
+ const clipId = `bar-pulse-clip-${idPrefix}-${safeId}`;
+ const gradId = `bar-pulse-grad-${idPrefix}-${safeId}`;
+ return [
+
+
+ ,
+ // Vertical gradient with a centered bright band shaped into a soft
+ // bell curve. y1=1, y2=0 makes offset 50% the rect's center, so the
+ // brightest pixel sits mid-rect during travel.
+
+
+
+
+
+
+
+
+
+
+
+
+ ,
+ ];
+ })}
+
+
+ {activeEntries.map((e) => {
+ const safeId = e.label.replace(/[^a-z0-9]/gi, "_");
+ const clipId = `bar-pulse-clip-${idPrefix}-${safeId}`;
+ const gradId = `bar-pulse-grad-${idPrefix}-${safeId}`;
+
+ const waveHeight = Math.max(
+ e.barHeight * PULSE_WAVE_HEIGHT_RATIO,
+ PULSE_WAVE_HEIGHT_MIN_PX
+ );
+ // Travel always flows root → tip. Positive bars sweep up (root =
+ // bottomY); negative bars sweep down (root = topY, the baseline). The
+ // gradient is symmetric, so reversing the endpoints reverses direction.
+ const yAboveLid = e.topY - e.perspectiveRise - waveHeight;
+ const yBelowFloor = e.bottomY;
+ const yStart = e.isNegative ? yAboveLid : yBelowFloor;
+ const yEnd = e.isNegative ? yBelowFloor : yAboveLid;
+
+ const isFaded =
+ hoveredBarIndex != null && hoveredBarIndex !== e.dataIndex;
+
+ return (
+
+
+
+ );
+ })}
+
+ );
+}
+
+BarPulse.displayName = "BarPulse";
+(BarPulse as { __isBarDepthLayer?: boolean }).__isBarDepthLayer = true;
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/bar-x-axis.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/bar-x-axis.tsx
new file mode 100644
index 0000000..2fbfa5f
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/bar-x-axis.tsx
@@ -0,0 +1,153 @@
+"use client";
+
+import { motion } from "motion/react";
+import { memo, useEffect, useMemo, useState } from "react";
+import { createPortal } from "react-dom";
+import { cn } from "@/lib/utils";
+import { useChart, useChartStable } from "./chart-context";
+
+export interface BarXAxisProps {
+ /** Width of the date ticker box for fade calculation. Default: 50 */
+ tickerHalfWidth?: number;
+ /** Whether to show all labels or skip some for dense data. Default: false */
+ showAllLabels?: boolean;
+ /** Maximum number of labels to show. Default: 12 */
+ maxLabels?: number;
+}
+
+interface BarXAxisLabelProps {
+ label: string;
+ x: number;
+ crosshairX: number | null;
+ isHovering: boolean;
+ tickerHalfWidth: number;
+}
+
+function BarXAxisLabel({
+ label,
+ x,
+ crosshairX,
+ isHovering,
+ tickerHalfWidth,
+}: BarXAxisLabelProps) {
+ const fadeBuffer = 20;
+ const fadeRadius = tickerHalfWidth + fadeBuffer;
+
+ let opacity = 1;
+ if (isHovering && crosshairX !== null) {
+ const distance = Math.abs(x - crosshairX);
+ if (distance < tickerHalfWidth) {
+ opacity = 0;
+ } else if (distance < fadeRadius) {
+ opacity = (distance - tickerHalfWidth) / fadeBuffer;
+ }
+ }
+
+ // Zero-width container approach for perfect centering
+ return (
+
+
+ {label}
+
+
+ );
+}
+
+export function BarXAxis(props: BarXAxisProps) {
+ const { containerRef, barScale } = useChartStable();
+ const [mounted, setMounted] = useState(false);
+
+ useEffect(() => {
+ setMounted(true);
+ }, []);
+
+ const container = containerRef.current;
+ if (!(mounted && container)) {
+ return null;
+ }
+
+ if (!barScale) {
+ return null;
+ }
+
+ return ;
+}
+
+const BarXAxisInner = memo(function BarXAxisInner({
+ tickerHalfWidth = 50,
+ showAllLabels = false,
+ maxLabels = 12,
+ container,
+}: BarXAxisProps & { container: HTMLDivElement }) {
+ const { margin, tooltipData, barScale, bandWidth, barXAccessor, data } =
+ useChart();
+
+ // Generate labels for each bar
+ const labelsToShow = useMemo(() => {
+ if (!(barScale && bandWidth && barXAccessor)) {
+ return [];
+ }
+
+ const allLabels = data.map((d) => {
+ const label = barXAccessor(d);
+ const bandX = barScale(label) ?? 0;
+ // Center the label under the bar group
+ const x = bandX + bandWidth / 2 + margin.left;
+ return { label, x };
+ });
+
+ // If showAllLabels is true or we have fewer than maxLabels, show all
+ if (showAllLabels || allLabels.length <= maxLabels) {
+ return allLabels;
+ }
+
+ // Otherwise, skip some labels to avoid crowding
+ const step = Math.ceil(allLabels.length / maxLabels);
+ return allLabels.filter((_, i) => i % step === 0);
+ }, [
+ barScale,
+ bandWidth,
+ barXAccessor,
+ data,
+ margin.left,
+ showAllLabels,
+ maxLabels,
+ ]);
+
+ const isHovering = tooltipData !== null;
+ const crosshairX = tooltipData ? tooltipData.x + margin.left : null;
+
+ return createPortal(
+
+ {labelsToShow.map((item) => (
+
+ ))}
+
,
+ container
+ );
+});
+
+BarXAxis.displayName = "BarXAxis";
+
+export default BarXAxis;
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/bar-y-axis.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/bar-y-axis.tsx
new file mode 100644
index 0000000..95400e5
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/bar-y-axis.tsx
@@ -0,0 +1,142 @@
+"use client";
+
+import { motion } from "motion/react";
+import { memo, useEffect, useMemo, useState } from "react";
+import { createPortal } from "react-dom";
+import { cn } from "@/lib/utils";
+import { useChart, useChartStable } from "./chart-context";
+
+export interface BarYAxisProps {
+ /** Whether to show all labels or skip some for dense data. Default: true */
+ showAllLabels?: boolean;
+ /** Maximum number of labels to show. Default: 20 */
+ maxLabels?: number;
+}
+
+interface BarYAxisLabelProps {
+ label: string;
+ y: number;
+ bandHeight: number;
+ isHovered: boolean;
+}
+
+function BarYAxisLabel({
+ label,
+ y,
+ bandHeight,
+ isHovered,
+}: BarYAxisLabelProps) {
+ return (
+
+
+ {label}
+
+
+ );
+}
+
+export function BarYAxis(props: BarYAxisProps) {
+ const { containerRef, barScale } = useChartStable();
+ const [mounted, setMounted] = useState(false);
+
+ useEffect(() => {
+ setMounted(true);
+ }, []);
+
+ const container = containerRef.current;
+ if (!(mounted && container)) {
+ return null;
+ }
+
+ if (!barScale) {
+ return null;
+ }
+
+ return ;
+}
+
+const BarYAxisInner = memo(function BarYAxisInner({
+ showAllLabels = true,
+ maxLabels = 20,
+ container,
+}: BarYAxisProps & { container: HTMLDivElement }) {
+ const { margin, barScale, bandWidth, barXAccessor, data, hoveredBarIndex } =
+ useChart();
+
+ // Generate labels for each bar
+ const labelsToShow = useMemo(() => {
+ if (!(barScale && bandWidth && barXAccessor)) {
+ return [];
+ }
+
+ const allLabels = data.map((d, i) => {
+ const label = barXAccessor(d);
+ const bandY = barScale(label) ?? 0;
+ // Center the label vertically within the band
+ const y = bandY + margin.top;
+ return { label, y, bandHeight: bandWidth, index: i };
+ });
+
+ // If showAllLabels is true or we have fewer than maxLabels, show all
+ if (showAllLabels || allLabels.length <= maxLabels) {
+ return allLabels;
+ }
+
+ // Otherwise, skip some labels to avoid crowding
+ const step = Math.ceil(allLabels.length / maxLabels);
+ return allLabels.filter((_, i) => i % step === 0);
+ }, [
+ barScale,
+ bandWidth,
+ barXAccessor,
+ data,
+ margin.top,
+ showAllLabels,
+ maxLabels,
+ ]);
+
+ return createPortal(
+
+ {labelsToShow.map((item) => (
+
+ ))}
+
,
+ container
+ );
+});
+
+BarYAxis.displayName = "BarYAxis";
+
+export default BarYAxis;
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/bar.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/bar.tsx
new file mode 100644
index 0000000..6f3d815
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/bar.tsx
@@ -0,0 +1,466 @@
+"use client";
+
+import type { scaleBand } from "@visx/scale";
+import type { Transition } from "motion/react";
+import { motion } from "motion/react";
+import { memo, useId, useMemo } from "react";
+import { barDepthAndRise, barDepthMaxDepth } from "./bar-depth-geometry";
+import {
+ chartCssVars,
+ useChart,
+ useChartStable,
+ useYScale,
+} from "./chart-context";
+import { useChartLegendHover } from "./chart-legend-hover";
+import { transitionWithDelay } from "./motion-utils";
+
+type ScaleBand = ReturnType<
+ typeof scaleBand
+>;
+
+export type BarLineCap = "round" | "butt" | number;
+export type BarAnimationType = "grow" | "fade";
+
+// ── Bar-depth perspective trim ───────────────────────────────────────────
+// Uses the SHARED geometry (`bar-depth-geometry.ts`) so a
+// `` front face lines up exactly with
+// ``'s lid — the formula lives in one place for both.
+
+/** perspectiveRise for a positive bar whose visual top sits at `topY`.
+ * Returns 0 for a dead-center bar or a dense chart (degenerate depth). */
+function barDepthPerspectiveRise(
+ barScale: ScaleBand,
+ bandWidth: number,
+ barXAccessor: (d: Record) => string,
+ innerWidth: number,
+ datum: Record,
+ topY: number,
+ baselineY: number
+): number {
+ const centerX = innerWidth / 2;
+ if (centerX <= 0) {
+ return 0;
+ }
+ const step =
+ (barScale as unknown as { step?: () => number }).step?.() ?? bandWidth;
+ const maxDepth = barDepthMaxDepth(step, bandWidth);
+ const bandX = barScale(barXAccessor(datum)) ?? 0;
+ const cx = bandX + bandWidth / 2;
+ const absOffset = Math.min(1, Math.abs((cx - centerX) / centerX));
+ const naturalHeight = Math.abs(baselineY - topY);
+ return barDepthAndRise(absOffset, naturalHeight, maxDepth).perspectiveRise;
+}
+
+export interface BarProps {
+ /** Key in data to use for y values */
+ dataKey: string;
+ /** Y-scale group id for vertical bars (Recharts `yAxisId`). Default: `"left"`. */
+ yAxisId?: string | number;
+ /** Fill color for the bar. Can be a color, gradient url, or pattern url. Default: var(--chart-line-primary) */
+ fill?: string;
+ /** Color for tooltip dot. Use when fill is a gradient/pattern. Default: uses fill value */
+ stroke?: string;
+ /** Line cap style for bar ends: "round", "butt", or a number for custom radius. Default: "round" */
+ lineCap?: BarLineCap;
+ /** Whether to animate the bars. Default: true */
+ animate?: boolean;
+ /** Animation type: "grow" (height) or "fade" (opacity + blur). Default: "grow" */
+ animationType?: BarAnimationType;
+ /** Opacity when not hovered (when another bar is hovered). Default: 0.3 */
+ fadedOpacity?: number;
+ /** Stagger delay between bars in seconds. Auto-calculated if not provided. */
+ staggerDelay?: number;
+ /** Gap between stacked bars in pixels. Default: 0 */
+ stackGap?: number;
+ /** Gap between grouped bars in pixels. Default: 4 */
+ groupGap?: number;
+ /** Shrink each positive bar's top by its perspective rise so the front face
+ * lines up with ``'s lid (instead of the lid sitting above the
+ * front face). Pass `true` whenever the chart also renders the bar-depth 3D
+ * surfaces. Default: false */
+ perspective?: boolean;
+ /** Minimum rendered bar height in px (non-stacked, vertical). Floors short or
+ * zero-value bars so they stay visible. Pair with the same value on
+ * `` when using the 3D surfaces. Default: 0 */
+ minBarHeight?: number;
+}
+
+interface BarInnerProps extends BarProps {
+ barScale: ScaleBand;
+ bandWidth: number;
+ barXAccessor: (d: Record) => string;
+}
+
+interface AnimatedBarProps {
+ x: number;
+ y: number;
+ width: number;
+ height: number;
+ fill: string;
+ rx: number;
+ ry: number;
+ index: number;
+ isFaded: boolean;
+ animationType: BarAnimationType;
+ innerHeight: number;
+ fadedOpacity: number;
+ staggerDelay: number;
+ enterTransition?: Transition;
+ revealEpoch: number;
+ isHorizontal: boolean;
+}
+
+function AnimatedBar({
+ x,
+ y,
+ width,
+ height,
+ fill,
+ rx,
+ ry,
+ index,
+ isFaded,
+ animationType,
+ innerHeight,
+ fadedOpacity,
+ staggerDelay,
+ enterTransition,
+ revealEpoch,
+ isHorizontal,
+}: AnimatedBarProps) {
+ const enterAnim = transitionWithDelay(enterTransition, index * staggerDelay);
+
+ if (animationType === "fade") {
+ return (
+
+ );
+ }
+
+ const initial = isHorizontal
+ ? { width: 0, height, x: 0, y }
+ : { width, height: 0, x, y: innerHeight };
+ const target = isHorizontal
+ ? { width, height, x: 0, y }
+ : { width, height, x, y };
+
+ return (
+
+
+
+ );
+}
+
+const BarInner = memo(function BarInner({
+ dataKey,
+ yAxisId,
+ fill = chartCssVars.linePrimary,
+ lineCap = "round",
+ animate = true,
+ animationType = "grow",
+ fadedOpacity = 0.3,
+ staggerDelay,
+ stackGap = 0,
+ groupGap = 4,
+ perspective = false,
+ minBarHeight = 0,
+ barScale,
+ bandWidth,
+ barXAccessor,
+}: BarInnerProps) {
+ const {
+ data,
+ yScale: chartYScale,
+ innerHeight,
+ innerWidth,
+ isLoaded,
+ hoveredBarIndex,
+ lines,
+ orientation,
+ stacked,
+ stackOffsets,
+ animationDuration,
+ enterTransition,
+ revealEpoch = 0,
+ } = useChart();
+
+ // Calculate stagger delay automatically if not provided
+ // Total animation duration is ~1200ms, with 40% for stagger spread and 60% for bar animation
+ const totalAnimDuration = animationDuration || 1100;
+ const staggerSpread = totalAnimDuration * 0.4; // 40% of time for stagger spread
+ const calculatedStaggerDelay =
+ staggerDelay ?? (data.length > 1 ? staggerSpread / 1000 / data.length : 0);
+ const uniqueId = useId();
+
+ const isHorizontal = orientation === "horizontal";
+
+ // Find the index of this bar series among all bar series
+ const { hoveredIndex: legendHoveredIndex } = useChartLegendHover();
+
+ const seriesIndex = useMemo(() => {
+ const idx = lines.findIndex((l) => l.dataKey === dataKey);
+ return idx >= 0 ? idx : 0;
+ }, [lines, dataKey]);
+
+ const seriesConfig = lines[seriesIndex];
+ const valueScale = useYScale(yAxisId ?? seriesConfig?.yAxisId);
+
+ const isLegendDimmed =
+ legendHoveredIndex !== null && legendHoveredIndex !== seriesIndex;
+
+ const seriesCount = lines.length;
+ const isLastSeries = seriesIndex === seriesCount - 1;
+
+ // Calculate the width for each bar within a group (for non-stacked)
+ const barWidth = useMemo(() => {
+ if (!bandWidth || seriesCount === 0) {
+ return 0;
+ }
+ if (stacked) {
+ // Stacked bars use full band width
+ return bandWidth;
+ }
+ // Leave a gap between grouped bars (controlled by groupGap prop)
+ const effectiveGroupGap = seriesCount > 1 ? groupGap : 0;
+ return (bandWidth - effectiveGroupGap * (seriesCount - 1)) / seriesCount;
+ }, [bandWidth, seriesCount, stacked, groupGap]);
+
+ // Calculate corner radius based on lineCap. Perspective bars force a flat
+ // top (radius 0) so the 3D lid from `` meets the bar with no
+ // gap — rounded corners would leave a wedge, so `perspective` overrides it.
+ const cornerRadius = useMemo(() => {
+ if (perspective) {
+ return 0;
+ }
+ if (typeof lineCap === "number") {
+ return lineCap;
+ }
+ if (lineCap === "round" && barWidth) {
+ return Math.min(barWidth / 2, 8);
+ }
+ return 0;
+ }, [lineCap, barWidth, perspective]);
+
+ return (
+
+ {data.map((d, i) => {
+ const value = d[dataKey];
+ if (typeof value !== "number") {
+ return null;
+ }
+
+ const categoryValue = barXAccessor(d);
+ const bandPos = barScale(categoryValue) ?? 0;
+
+ let x: number;
+ let y: number;
+ let barHeight: number;
+ let barW: number;
+
+ const scale = isHorizontal ? chartYScale : valueScale;
+
+ if (isHorizontal) {
+ // Horizontal bars: category on y-axis, value on x-axis
+ const valuePos = scale(value) ?? 0;
+ barW = valuePos; // Width is the value position (grows from left)
+ barHeight = barWidth;
+
+ if (stacked && stackOffsets) {
+ const offset = stackOffsets.get(i)?.get(dataKey) ?? 0;
+ x = scale(offset) ?? 0;
+ barW = valuePos - x;
+ // Apply stack gap for horizontal: shift right and reduce width
+ const gapOffset = seriesIndex * stackGap;
+ x += gapOffset;
+ if (!isLastSeries && stackGap > 0) {
+ barW = Math.max(0, barW - stackGap);
+ }
+ } else {
+ x = 0;
+ // For grouped bars, offset y position
+ const effectiveGroupGap = seriesCount > 1 ? groupGap : 0;
+ y = bandPos + seriesIndex * (barWidth + effectiveGroupGap);
+ }
+ y = stacked
+ ? bandPos
+ : bandPos +
+ seriesIndex * (barWidth + (seriesCount > 1 ? groupGap : 0));
+ } else {
+ // Vertical bars: category on x-axis, value on y-axis
+ const valuePos = scale(value) ?? 0;
+ barHeight = innerHeight - valuePos;
+ barW = barWidth;
+
+ if (stacked && stackOffsets) {
+ const offset = stackOffsets.get(i)?.get(dataKey) ?? 0;
+ const offsetY = scale(offset) ?? innerHeight;
+ // Apply stack gap: shift up and reduce height
+ const gapOffset = seriesIndex * stackGap;
+ y = offsetY - barHeight - gapOffset;
+ // Reduce height slightly for non-last bars to create visual gap
+ if (!isLastSeries && stackGap > 0) {
+ barHeight = Math.max(0, barHeight - stackGap);
+ }
+ } else {
+ y = valuePos;
+ // For grouped bars, offset x position
+ const effectiveGroupGap = seriesCount > 1 ? groupGap : 0;
+ x = bandPos + seriesIndex * (barWidth + effectiveGroupGap);
+ }
+ x = stacked
+ ? bandPos
+ : bandPos +
+ seriesIndex * (barWidth + (seriesCount > 1 ? groupGap : 0));
+
+ // Minimum visible height — floor short/zero non-stacked bars so a
+ // zero-value data point still reads as a tiny bar instead of
+ // vanishing. Grows up from the baseline. Floored bars skip the
+ // perspective trim (sub-pixel on a 3px bar; keeps the front aligned
+ // with bar-depth, which also skips trim for floored bars).
+ let isFloored = false;
+ if (
+ !stacked &&
+ minBarHeight > 0 &&
+ value >= 0 &&
+ barHeight < minBarHeight
+ ) {
+ const baselineY = scale(0) ?? innerHeight;
+ barHeight = minBarHeight;
+ y = baselineY - minBarHeight;
+ isFloored = true;
+ }
+
+ // Perspective trim — shrink the topmost positive bar's front-face
+ // top down by its perspective rise so it meets ``'s
+ // lid back edge. Stacked: only the last (topmost) series; grouped or
+ // single: every positive bar. Clamped to `barHeight - 1` so very
+ // short bars keep a positive height (matches bar-depth's clamp).
+ if (
+ perspective &&
+ value > 0 &&
+ !isFloored &&
+ (!stacked || isLastSeries)
+ ) {
+ const baselineY = scale(0) ?? innerHeight;
+ const rise = barDepthPerspectiveRise(
+ barScale,
+ bandWidth,
+ barXAccessor,
+ innerWidth,
+ d,
+ y,
+ baselineY
+ );
+ const trim = Math.min(rise, Math.max(0, barHeight - 1));
+ y += trim;
+ barHeight -= trim;
+ }
+ }
+
+ const isFaded =
+ (hoveredBarIndex !== null && hoveredBarIndex !== i) || isLegendDimmed;
+
+ // Use categoryValue as key since it's the unique identifier from data
+ const barKey = `bar-${dataKey}-${categoryValue}`;
+
+ // Apply rounded corners:
+ // - For non-stacked: always apply
+ // - For stacked with gap: apply to all bars
+ // - For stacked without gap: only apply to the last series
+ const applyRounding = !stacked || stackGap > 0 || isLastSeries;
+ const effectiveRx = applyRounding ? cornerRadius : 0;
+ const effectiveRy = applyRounding ? cornerRadius : 0;
+
+ if (animate && !isLoaded) {
+ return (
+
+ );
+ }
+
+ // Static bar after animation completes
+ return (
+
+ );
+ })}
+
+ );
+});
+
+export function Bar(props: BarProps) {
+ const { barScale, bandWidth, barXAccessor } = useChartStable();
+
+ if (!(barScale && bandWidth && barXAccessor)) {
+ console.warn("Bar component must be used within a BarChart");
+ return null;
+ }
+
+ return (
+
+ );
+}
+
+Bar.displayName = "Bar";
+
+export default Bar;
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/candlestick-chart.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/candlestick-chart.tsx
new file mode 100644
index 0000000..1d49a91
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/candlestick-chart.tsx
@@ -0,0 +1,425 @@
+"use client";
+
+import { ParentSize } from "@visx/responsive";
+import { scaleLinear, scaleTime } from "@visx/scale";
+import { bisector } from "d3-array";
+import type { Transition } from "motion/react";
+import {
+ Children,
+ isValidElement,
+ memo,
+ type ReactElement,
+ type ReactNode,
+ useCallback,
+ useEffect,
+ useMemo,
+ useRef,
+ useState,
+} from "react";
+import { cn } from "@/lib/utils";
+import {
+ isClipExcludedComponent,
+ isPostOverlayComponent,
+ isUnderlayComponent,
+} from "./chart-child-passthrough";
+import { ChartProvider, type LineConfig, type Margin } from "./chart-context";
+import { shortDateFmt } from "./chart-formatters";
+import { DEFAULT_CHART_LIFECYCLE } from "./chart-phase";
+import {
+ decimateOhlcData,
+ maxRenderPointsForWidth,
+} from "./decimate-time-series";
+import { extractReferenceAreaConfigs } from "./reference-area-config";
+import { useChartInteraction } from "./use-chart-interaction";
+import { wrapSingleYScale } from "./y-axis-scales";
+
+export interface OHLCDataPoint {
+ date: Date;
+ open: number;
+ high: number;
+ low: number;
+ close: number;
+}
+
+export interface CandlestickChartProps {
+ /** OHLC data array */
+ data: OHLCDataPoint[];
+ /** Key in data for the x-axis (date). Default: "date" */
+ xDataKey?: string;
+ /** Chart margins */
+ margin?: Partial;
+ /** Animation duration in milliseconds. Default: 1500 */
+ animationDuration?: number;
+ /** Motion enter transition (spring or cubic-bezier tween). */
+ enterTransition?: Transition;
+ /** Signature of motion URL state — triggers enter replay when it changes. */
+ revealSignature?: string;
+ /** Aspect ratio as "width / height". Default: "2 / 1" */
+ aspectRatio?: string;
+ /** Additional class name for the container */
+ className?: string;
+ /** Inline styles for the container (e.g. { height: 320 }) */
+ style?: React.CSSProperties;
+ /** Gap between candles as fraction of slot width (0–1). Default: 0.2. Ignored when candleWidth is set. */
+ candleGap?: number;
+ /** Fixed candle body width in pixels. If set, overrides candleGap. */
+ candleWidth?: number;
+ /** When set, xScale uses this domain instead of deriving from data. Use with brush so main chart and strip share the same scale. */
+ xDomain?: [Date, Date];
+ /** When xDomain is set, use this as the number of slots for scale padding (e.g. full data length). */
+ xDomainSlotCount?: number;
+ /** Child components (Candlestick, Grid, XAxis, YAxis, ChartTooltip, etc.) */
+ children: ReactNode;
+}
+
+const DEFAULT_MARGIN: Margin = { top: 40, right: 40, bottom: 40, left: 40 };
+
+interface ChartInnerProps {
+ width: number;
+ height: number;
+ data: Record[];
+ xDataKey: string;
+ margin: Margin;
+ animationDuration: number;
+ enterTransition?: Transition;
+ revealSignature?: string;
+ candleGap: number;
+ candleWidthProp?: number;
+ xDomain?: [Date, Date];
+ xDomainSlotCount?: number;
+ children: ReactNode;
+ containerRef: React.RefObject;
+}
+
+function ChartInner(props: ChartInnerProps) {
+ const { width, height } = props;
+ if (width < 10 || height < 10) {
+ return null;
+ }
+ return ;
+}
+
+const ChartCore = memo(function ChartCore({
+ width,
+ height,
+ data,
+ xDataKey,
+ margin,
+ animationDuration,
+ enterTransition,
+ revealSignature = "",
+ candleGap,
+ candleWidthProp,
+ xDomain,
+ xDomainSlotCount,
+ children,
+ containerRef,
+}: ChartInnerProps) {
+ const [isLoaded, setIsLoaded] = useState(false);
+ const [revealEpoch, setRevealEpoch] = useState(0);
+
+ const innerWidth = width - margin.left - margin.right;
+ const innerHeight = height - margin.top - margin.bottom;
+
+ const xAccessor = useCallback(
+ (d: Record): Date => {
+ const value = d[xDataKey];
+ return value instanceof Date ? value : new Date(value as string | number);
+ },
+ [xDataKey]
+ );
+
+ const bisectDate = useMemo(
+ () => bisector, Date>((d) => xAccessor(d)).left,
+ [xAccessor]
+ );
+
+ const slotCount =
+ xDomain && xDomainSlotCount != null ? xDomainSlotCount : data.length;
+ const slotWidth = innerWidth / Math.max(slotCount, 1);
+ const xScale = useMemo(() => {
+ const minTime = xDomain
+ ? xDomain[0].getTime()
+ : Math.min(...data.map((d) => xAccessor(d).getTime()));
+ const maxTime = xDomain
+ ? xDomain[1].getTime()
+ : Math.max(...data.map((d) => xAccessor(d).getTime()));
+ const padding = slotWidth / 2;
+ return scaleTime({
+ range: [padding, innerWidth - padding],
+ domain: [minTime, maxTime],
+ });
+ }, [innerWidth, data, xAccessor, slotWidth, xDomain]);
+
+ const yScale = useMemo(() => {
+ let minVal = Number.POSITIVE_INFINITY;
+ let maxVal = Number.NEGATIVE_INFINITY;
+ for (const d of data) {
+ const low = d.low as number | undefined;
+ const high = d.high as number | undefined;
+ if (typeof low === "number" && low < minVal) {
+ minVal = low;
+ }
+ if (typeof high === "number" && high > maxVal) {
+ maxVal = high;
+ }
+ }
+ if (minVal === Number.POSITIVE_INFINITY) {
+ minVal = 0;
+ }
+ if (maxVal === Number.NEGATIVE_INFINITY) {
+ maxVal = 100;
+ }
+ const padding = (maxVal - minVal) * 0.05 || 1;
+ return scaleLinear({
+ range: [innerHeight, 0],
+ domain: [minVal - padding, maxVal + padding],
+ nice: true,
+ });
+ }, [innerHeight, data]);
+
+ const columnWidth = slotWidth;
+ const bandWidth = candleWidthProp ?? slotWidth * (1 - candleGap);
+
+ const lines: LineConfig[] = useMemo(
+ () => [
+ { dataKey: "close", stroke: "var(--chart-line-primary)", strokeWidth: 0 },
+ ],
+ []
+ );
+
+ const renderData = useMemo(
+ () => decimateOhlcData(data, maxRenderPointsForWidth(innerWidth)),
+ [data, innerWidth]
+ );
+
+ const dateLabels = useMemo(
+ () => data.map((d) => shortDateFmt.format(xAccessor(d))),
+ [data, xAccessor]
+ );
+
+ // biome-ignore lint/correctness/useExhaustiveDependencies: revealSignature
+ useEffect(() => {
+ setRevealEpoch((n) => n + 1);
+ setIsLoaded(false);
+ const timer = setTimeout(() => setIsLoaded(true), animationDuration);
+ return () => clearTimeout(timer);
+ }, [animationDuration, revealSignature]);
+
+ const {
+ tooltipData,
+ setTooltipData,
+ selection,
+ clearSelection,
+ interactionHandlers,
+ interactionStyle,
+ } = useChartInteraction({
+ xScale,
+ yScale,
+ yScales: wrapSingleYScale(yScale),
+ data,
+ lines,
+ margin,
+ xAccessor,
+ bisectDate,
+ canInteract: isLoaded,
+ });
+
+ const hoveredCandleIndex = tooltipData?.index ?? null;
+
+ const isDefsComponent = (child: ReactElement): boolean => {
+ const displayName =
+ (child.type as { displayName?: string })?.displayName ||
+ (child.type as { name?: string })?.name ||
+ "";
+ return (
+ displayName.includes("Gradient") ||
+ displayName.includes("Pattern") ||
+ displayName === "LinearGradient" ||
+ displayName === "RadialGradient" ||
+ displayName === "Lines" ||
+ displayName === "PatternLines"
+ );
+ };
+
+ const defsChildren: ReactElement[] = [];
+ const clipExcludedChildren: ReactElement[] = [];
+ const underlayChildren: ReactElement[] = [];
+ const preOverlayChildren: ReactElement[] = [];
+ const postOverlayChildren: ReactElement[] = [];
+ Children.forEach(children, (child) => {
+ if (!isValidElement(child)) {
+ return;
+ }
+ if (isDefsComponent(child)) {
+ defsChildren.push(child);
+ } else if (isPostOverlayComponent(child)) {
+ postOverlayChildren.push(child);
+ } else if (isClipExcludedComponent(child)) {
+ clipExcludedChildren.push(child);
+ } else if (isUnderlayComponent(child)) {
+ underlayChildren.push(child);
+ } else {
+ preOverlayChildren.push(child);
+ }
+ });
+
+ const referenceAreas = useMemo(
+ () => extractReferenceAreaConfigs(children),
+ [children]
+ );
+
+ const yScales = useMemo(() => wrapSingleYScale(yScale), [yScale]);
+
+ const contextValue = useMemo(
+ () => ({
+ ...DEFAULT_CHART_LIFECYCLE,
+ data,
+ renderData,
+ xScale,
+ yScale,
+ yScales,
+ width,
+ height,
+ innerWidth,
+ innerHeight,
+ margin,
+ columnWidth,
+ tooltipData,
+ setTooltipData,
+ containerRef,
+ lines,
+ referenceAreas,
+ isLoaded,
+ animationDuration,
+ enterTransition,
+ revealEpoch,
+ xAccessor,
+ dateLabels,
+ selection: selection ?? null,
+ clearSelection,
+ bandWidth,
+ hoveredCandleIndex,
+ }),
+ [
+ data,
+ renderData,
+ xScale,
+ yScale,
+ yScales,
+ width,
+ height,
+ innerWidth,
+ innerHeight,
+ margin,
+ columnWidth,
+ tooltipData,
+ setTooltipData,
+ containerRef,
+ lines,
+ referenceAreas,
+ isLoaded,
+ animationDuration,
+ enterTransition,
+ revealEpoch,
+ xAccessor,
+ dateLabels,
+ selection,
+ clearSelection,
+ bandWidth,
+ hoveredCandleIndex,
+ ]
+ );
+
+ return (
+
+
+
+ {/* Default vertical gradients for positive/negative candles (emerald / red) */}
+
+
+
+
+
+
+
+
+ {defsChildren}
+
+
+
+
+ {clipExcludedChildren}
+ {underlayChildren}
+ {preOverlayChildren}
+ {postOverlayChildren}
+
+
+
+ );
+});
+
+export function CandlestickChart({
+ data,
+ xDataKey = "date",
+ margin: marginProp,
+ animationDuration = 1100,
+ enterTransition,
+ revealSignature,
+ aspectRatio = "2 / 1",
+ className = "",
+ style,
+ candleGap = 0.2,
+ candleWidth,
+ xDomain,
+ xDomainSlotCount,
+ children,
+}: CandlestickChartProps) {
+ const containerRef = useRef(null);
+ const margin = { ...DEFAULT_MARGIN, ...marginProp };
+ const dataAsRecords = data as unknown as Record[];
+
+ return (
+
+
+ {({ width, height }) => (
+
+ {children}
+
+ )}
+
+
+ );
+}
+
+CandlestickChart.displayName = "CandlestickChart";
+
+export default CandlestickChart;
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/candlestick.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/candlestick.tsx
new file mode 100644
index 0000000..af6ec86
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/candlestick.tsx
@@ -0,0 +1,439 @@
+"use client";
+
+import type { Transition } from "motion/react";
+import { motion } from "motion/react";
+import { memo, useMemo } from "react";
+import { useChart } from "./chart-context";
+import { useChartLegendHover } from "./chart-legend-hover";
+import { transitionWithDelay } from "./motion-utils";
+
+const DEFAULT_POSITIVE = "url(#candlestick-positive)";
+const DEFAULT_NEGATIVE = "url(#candlestick-negative)";
+
+const SOLID_POSITIVE = "var(--color-emerald-500)";
+const SOLID_NEGATIVE = "var(--color-red-500)";
+const WICK_WIDTH = 1.5;
+
+export interface CandlestickProps {
+ /** Whether to animate the candlesticks. Default: true */
+ animate?: boolean;
+ /** Fill for positive (close >= open) candles. Color or url(#gradient). Default: emerald */
+ positiveFill?: string;
+ /** Fill for negative candles. Color or url(#gradient). Default: red */
+ negativeFill?: string;
+ /** Optional pattern URL for body only (e.g. url(#pattern)). When set, body is drawn solid first, then pattern overlaid and masked to the body rect. */
+ bodyPatternPositive?: string;
+ /** Optional pattern URL for negative candle body. */
+ bodyPatternNegative?: string;
+ /** Inner border width on the body (drawn inside so it does not expand the shape). Default: 0 (off). */
+ insideStrokeWidth?: number;
+ /** Opacity when another candle is hovered. Default: 0.3 */
+ fadedOpacity?: number;
+ /** Dim non-hovered candles on hover. Default: true */
+ showHoverFade?: boolean;
+}
+
+interface CandleGeometry {
+ time: number;
+ centerX: number;
+ bodyTop: number;
+ bodyHeight: number;
+ bodyLeft: number;
+ candleWidth: number;
+ wickTop: number;
+ wickHeight: number;
+ wickLeft: number;
+ bodySolidFill: string;
+ wickFill: string;
+ bodyPattern?: string;
+ insideStrokeWidth: number;
+ isPositive: boolean;
+}
+
+function getSolidColor(isPositive: boolean): string {
+ return isPositive ? SOLID_POSITIVE : SOLID_NEGATIVE;
+}
+
+function computeGeometries(
+ renderData: Record[],
+ xScale: (value: Date) => number | undefined,
+ yScale: (value: number) => number | undefined,
+ xAccessor: (d: Record) => Date,
+ candleWidth: number,
+ positiveFill: string,
+ negativeFill: string,
+ bodyPatternPositive: string | undefined,
+ bodyPatternNegative: string | undefined,
+ insideStrokeWidth: number
+): CandleGeometry[] {
+ return renderData.map((d) => {
+ const date = xAccessor(d);
+ const open = d.open as number;
+ const high = d.high as number;
+ const low = d.low as number;
+ const close = d.close as number;
+ const centerX = xScale(date) ?? 0;
+ const yHigh = yScale(high) ?? 0;
+ const yLow = yScale(low) ?? 0;
+ const yOpen = yScale(open) ?? 0;
+ const yClose = yScale(close) ?? 0;
+ const bodyTop = Math.min(yOpen, yClose);
+ const bodyHeight = Math.abs(yClose - yOpen) || 1;
+ const bodyLeft = centerX - candleWidth / 2;
+ const wickTop = Math.min(yHigh, yLow);
+ const wickHeight = Math.abs(yLow - yHigh) || 1;
+ const isPositive = close >= open;
+ const fill = isPositive ? positiveFill : negativeFill;
+ const bodyPattern = isPositive ? bodyPatternPositive : bodyPatternNegative;
+ const hasPatternOverlay = Boolean(bodyPattern);
+ const bodySolidFill = hasPatternOverlay ? getSolidColor(isPositive) : fill;
+
+ return {
+ time: date.getTime(),
+ centerX,
+ bodyTop,
+ bodyHeight,
+ bodyLeft,
+ candleWidth,
+ wickTop,
+ wickHeight,
+ wickLeft: centerX - WICK_WIDTH / 2,
+ bodySolidFill,
+ wickFill: hasPatternOverlay ? bodySolidFill : fill,
+ bodyPattern: hasPatternOverlay ? bodyPattern : undefined,
+ insideStrokeWidth,
+ isPositive,
+ };
+ });
+}
+
+function geometryDimOpacity(
+ geometry: CandleGeometry,
+ fadedOpacity: number,
+ legendHoveredIndex: number | null,
+ hoveredTime: number | null
+): number {
+ if (legendHoveredIndex !== null) {
+ const dimFromLegend =
+ (legendHoveredIndex === 0 && !geometry.isPositive) ||
+ (legendHoveredIndex === 1 && geometry.isPositive);
+ return dimFromLegend ? fadedOpacity : 1;
+ }
+ if (hoveredTime !== null && geometry.time !== hoveredTime) {
+ return fadedOpacity;
+ }
+ return 1;
+}
+
+const CandlestickBody = memo(function CandlestickBody({
+ geometry,
+}: {
+ geometry: CandleGeometry;
+}) {
+ const {
+ wickLeft,
+ wickTop,
+ wickHeight,
+ wickFill,
+ bodyLeft,
+ bodyTop,
+ bodyHeight,
+ candleWidth,
+ bodySolidFill,
+ bodyPattern,
+ insideStrokeWidth,
+ } = geometry;
+
+ return (
+ <>
+
+
+ {bodyPattern ? (
+
+ ) : null}
+ {insideStrokeWidth > 0 ? (
+
+ ) : null}
+ >
+ );
+});
+
+const CandlestickBodies = memo(function CandlestickBodies({
+ geometries,
+ fadedOpacity,
+ legendHoveredIndex,
+ hoveredTime,
+}: {
+ geometries: CandleGeometry[];
+ fadedOpacity: number;
+ legendHoveredIndex: number | null;
+ hoveredTime: number | null;
+}) {
+ return (
+ <>
+ {geometries.map((geometry) => (
+
+
+
+ ))}
+ >
+ );
+});
+
+interface AnimatedCandleProps {
+ geometry: CandleGeometry;
+ delay: number;
+ enterTransition: Transition;
+ revealEpoch: number;
+}
+
+function AnimatedCandle({
+ geometry,
+ delay,
+ enterTransition,
+ revealEpoch,
+}: AnimatedCandleProps) {
+ const t = transitionWithDelay(enterTransition, delay);
+ const bodyOrigin = `${geometry.centerX}px ${geometry.bodyTop + geometry.bodyHeight / 2}px`;
+ const wickCenterY = geometry.wickTop + geometry.wickHeight / 2;
+
+ return (
+
+
+
+ {geometry.bodyPattern ? (
+
+ ) : null}
+
+ );
+}
+
+export function Candlestick({
+ animate = true,
+ positiveFill = DEFAULT_POSITIVE,
+ negativeFill = DEFAULT_NEGATIVE,
+ bodyPatternPositive,
+ bodyPatternNegative,
+ insideStrokeWidth = 0,
+ fadedOpacity = 0.3,
+ showHoverFade = true,
+}: CandlestickProps) {
+ const {
+ data,
+ xScale,
+ yScale,
+ xAccessor,
+ animationDuration,
+ enterTransition,
+ revealEpoch = 0,
+ isLoaded,
+ bandWidth,
+ columnWidth,
+ hoveredCandleIndex,
+ } = useChart();
+ const { hoveredIndex: legendHoveredIndex } = useChartLegendHover();
+
+ const candleWidth = Math.min(bandWidth ?? columnWidth * 0.8, columnWidth);
+
+ const geometries = useMemo(
+ () =>
+ computeGeometries(
+ data,
+ xScale,
+ yScale,
+ xAccessor,
+ candleWidth,
+ positiveFill,
+ negativeFill,
+ bodyPatternPositive,
+ bodyPatternNegative,
+ insideStrokeWidth
+ ),
+ [
+ data,
+ xScale,
+ yScale,
+ xAccessor,
+ candleWidth,
+ positiveFill,
+ negativeFill,
+ bodyPatternPositive,
+ bodyPatternNegative,
+ insideStrokeWidth,
+ ]
+ );
+
+ const hoveredTime = useMemo(() => {
+ if (hoveredCandleIndex == null) {
+ return null;
+ }
+ const point = data[hoveredCandleIndex];
+ return point ? xAccessor(point).getTime() : null;
+ }, [hoveredCandleIndex, data, xAccessor]);
+
+ const highlightGeometry = useMemo(() => {
+ if (hoveredCandleIndex == null) {
+ return null;
+ }
+ const point = data[hoveredCandleIndex];
+ if (!point) {
+ return null;
+ }
+ return (
+ computeGeometries(
+ [point],
+ xScale,
+ yScale,
+ xAccessor,
+ candleWidth,
+ positiveFill,
+ negativeFill,
+ bodyPatternPositive,
+ bodyPatternNegative,
+ insideStrokeWidth
+ )[0] ?? null
+ );
+ }, [
+ hoveredCandleIndex,
+ data,
+ xScale,
+ yScale,
+ xAccessor,
+ candleWidth,
+ positiveFill,
+ negativeFill,
+ bodyPatternPositive,
+ bodyPatternNegative,
+ insideStrokeWidth,
+ ]);
+
+ const defaultEnter: Transition = {
+ type: "spring",
+ duration: 0.8,
+ bounce: 0.15,
+ };
+ const enter = enterTransition ?? defaultEnter;
+ const staggerDelayMs =
+ data.length > 0 ? (animationDuration * 0.6) / data.length : 0;
+
+ if (animate && !isLoaded) {
+ return (
+
+ {geometries.map((geometry, index) => (
+
+ ))}
+
+ );
+ }
+
+ return (
+
+
+ {highlightGeometry ? (
+
+
+
+ ) : null}
+
+ );
+}
+
+Candlestick.displayName = "Candlestick";
+
+export default Candlestick;
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/chart-brush-handle.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/chart-brush-handle.tsx
new file mode 100644
index 0000000..12e29e4
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/chart-brush-handle.tsx
@@ -0,0 +1,116 @@
+"use client";
+
+import type { BrushHandleRenderProps } from "@visx/brush/lib/BrushHandle";
+import { useEffect, useState } from "react";
+import { createPortal } from "react-dom";
+import type { ChartBrushOverlayHost } from "./chart-brush-track-overlay";
+import { useChartStable } from "./chart-context";
+
+/** Matches shadcn `ResizableHandle` withHandle pill (`h-6 w-1 rounded-lg`). */
+const HANDLE_HEIGHT_PX = 24;
+const HANDLE_WIDTH_PX = 4;
+
+/**
+ * Invisible hit target for visx `BrushHandle` drag interaction.
+ * Visible pills render in `ChartBrushHandleOverlay` above the blur layer.
+ */
+function brushHandleCursor(className: string) {
+ return className.includes("left") || className.includes("right")
+ ? "ew-resize"
+ : "ns-resize";
+}
+
+export function renderChartBrushHandle({
+ x,
+ y,
+ width,
+ height,
+ className,
+}: BrushHandleRenderProps) {
+ return (
+
+ );
+}
+
+export interface ChartBrushHandleOverlayProps {
+ innerWidth: number;
+ innerHeight: number;
+ /** Pixel x of the selection start within the plot area. */
+ selectionX0: number;
+ /** Pixel x of the selection end within the plot area. */
+ selectionX1: number;
+}
+
+/** Renders brush resize pills above the outside blur portal (`z-[2]`). */
+export function ChartBrushHandleOverlayContent({
+ containerRef,
+ margin,
+ innerWidth,
+ innerHeight,
+ selectionX0,
+ selectionX1,
+}: ChartBrushHandleOverlayProps & ChartBrushOverlayHost) {
+ const [mounted, setMounted] = useState(false);
+
+ useEffect(() => {
+ setMounted(true);
+ }, []);
+
+ const container = containerRef.current;
+ if (!(mounted && container)) {
+ return null;
+ }
+
+ const x0 = Math.max(0, Math.min(selectionX0, selectionX1, innerWidth));
+ const x1 = Math.max(
+ x0,
+ Math.min(Math.max(selectionX0, selectionX1), innerWidth)
+ );
+ const edges = x0 === x1 ? [x0] : [x0, x1];
+
+ const plotLeft = margin.left;
+ const plotTop = margin.top;
+ const handleTop = plotTop + (innerHeight - HANDLE_HEIGHT_PX) / 2;
+
+ return createPortal(
+
+ {edges.map((edgeX) => (
+
+ ))}
+
,
+ container
+ );
+}
+
+export function ChartBrushHandleOverlay(props: ChartBrushHandleOverlayProps) {
+ const { containerRef, margin } = useChartStable();
+
+ return (
+
+ );
+}
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/chart-brush-layout.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/chart-brush-layout.tsx
new file mode 100644
index 0000000..90d9d42
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/chart-brush-layout.tsx
@@ -0,0 +1,123 @@
+"use client";
+
+import {
+ memo,
+ type ReactNode,
+ useCallback,
+ useEffect,
+ useMemo,
+ useState,
+} from "react";
+import { cn } from "@/lib/utils";
+import type { ChartBrushSelection } from "./chart-brush";
+import { resolveBrushTrackXExtent } from "./filter-data-by-x-domain";
+
+export interface ChartBrushLayoutState {
+ xDomain: [Date, Date] | undefined;
+ xDomainSlotCount: number | undefined;
+ brushSelection: ChartBrushSelection | null;
+ onBrushSelectionChange: (selection: ChartBrushSelection | null) => void;
+}
+
+export interface ChartBrushLayoutProps {
+ /** Full dataset backing the brush strip and main chart. */
+ data: Record[];
+ xDataKey?: string;
+ /** Extends the brush track max beyond the last data row (e.g. projection horizon). */
+ xExtentMax?: Date;
+ /** When false, children render without brush zoom state. */
+ enabled: boolean;
+ /** Fixed height of the brush strip in pixels. */
+ height: number;
+ /** When true, the main chart area hugs content instead of growing to fill space. */
+ fitMainContent?: boolean;
+ className?: string;
+ children: (layout: ChartBrushLayoutState) => ReactNode;
+ /** Mini chart + brush handles rendered below the main chart when `enabled`. */
+ brushStrip?: (layout: ChartBrushLayoutState) => ReactNode;
+}
+
+function createXAccessor(xDataKey: string) {
+ return (d: Record): Date => {
+ const value = d[xDataKey];
+ return value instanceof Date ? value : new Date(value as string | number);
+ };
+}
+
+export const ChartBrushLayout = memo(function ChartBrushLayout({
+ data,
+ xDataKey = "date",
+ xExtentMax,
+ enabled,
+ height,
+ fitMainContent = false,
+ className,
+ children,
+ brushStrip,
+}: ChartBrushLayoutProps) {
+ const xAccessor = useMemo(() => createXAccessor(xDataKey), [xDataKey]);
+ const fullExtent = useMemo(
+ () => resolveBrushTrackXExtent(data, xAccessor, xExtentMax),
+ [data, xAccessor, xExtentMax]
+ );
+ const [brushSelection, setBrushSelection] =
+ useState(null);
+
+ useEffect(() => {
+ if (!fullExtent) {
+ setBrushSelection(null);
+ return;
+ }
+ setBrushSelection({ start: fullExtent[0], end: fullExtent[1] });
+ }, [fullExtent]);
+
+ const handleBrushSelectionChange = useCallback(
+ (selection: ChartBrushSelection | null) => {
+ if (!selection) {
+ if (fullExtent) {
+ setBrushSelection({ start: fullExtent[0], end: fullExtent[1] });
+ }
+ return;
+ }
+ setBrushSelection(selection);
+ },
+ [fullExtent]
+ );
+
+ const layoutState = useMemo(
+ () => ({
+ xDomain:
+ enabled && brushSelection
+ ? ([brushSelection.start, brushSelection.end] as [Date, Date])
+ : undefined,
+ xDomainSlotCount: enabled ? data.length : undefined,
+ brushSelection,
+ onBrushSelectionChange: handleBrushSelectionChange,
+ }),
+ [brushSelection, data.length, enabled, handleBrushSelectionChange]
+ );
+
+ return (
+
+
+ {children(layoutState)}
+
+ {enabled && brushStrip ? (
+
+ {brushStrip(layoutState)}
+
+ ) : null}
+
+ );
+});
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/chart-brush-selection-overlay.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/chart-brush-selection-overlay.tsx
new file mode 100644
index 0000000..99182d2
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/chart-brush-selection-overlay.tsx
@@ -0,0 +1,109 @@
+"use client";
+
+import { useEffect, useId, useState } from "react";
+import { createPortal } from "react-dom";
+import type { ChartBrushOverlayHost } from "./chart-brush-track-overlay";
+import { useChartStable } from "./chart-context";
+import {
+ type PatternPresetId,
+ type PatternPresetOptions,
+ renderPatternPreset,
+} from "./pattern-preset";
+
+export type ChartBrushPatternPreset = PatternPresetId;
+
+export interface ChartBrushSelectionPattern extends PatternPresetOptions {
+ preset: ChartBrushPatternPreset;
+ color: string;
+ opacity?: number;
+}
+
+export interface ChartBrushSelectionOverlayProps {
+ innerWidth: number;
+ innerHeight: number;
+ selectionX0: number;
+ selectionX1: number;
+ pattern?: ChartBrushSelectionPattern;
+}
+
+/** Pattern fill between brush handles (`z-[1]`, above blur panes). */
+export function ChartBrushSelectionOverlayContent({
+ containerRef,
+ margin,
+ innerWidth,
+ innerHeight,
+ selectionX0,
+ selectionX1,
+ pattern,
+}: ChartBrushSelectionOverlayProps & ChartBrushOverlayHost) {
+ const [mounted, setMounted] = useState(false);
+ const patternId = useId().replace(/:/g, "");
+
+ useEffect(() => {
+ setMounted(true);
+ }, []);
+
+ const container = containerRef.current;
+ if (!(mounted && container && pattern && pattern.preset !== "none")) {
+ return null;
+ }
+
+ const x0 = Math.max(0, Math.min(selectionX0, selectionX1, innerWidth));
+ const x1 = Math.max(
+ x0,
+ Math.min(Math.max(selectionX0, selectionX1), innerWidth)
+ );
+ const selectionWidth = x1 - x0;
+ if (selectionWidth <= 0) {
+ return null;
+ }
+
+ const plotLeft = margin.left;
+ const plotTop = margin.top;
+ const patternNode = renderPatternPreset(pattern.preset, patternId, {
+ color: pattern.color,
+ scale: pattern.scale,
+ strokeWidth: pattern.strokeWidth,
+ radius: pattern.radius,
+ complement: pattern.complement,
+ fill: pattern.fill,
+ tileBackground: pattern.tileBackground,
+ });
+ if (!patternNode) {
+ return null;
+ }
+
+ return createPortal(
+
+ {patternNode}
+
+ ,
+ container
+ );
+}
+
+export function ChartBrushSelectionOverlay(
+ props: ChartBrushSelectionOverlayProps
+) {
+ const { containerRef, margin } = useChartStable();
+
+ return (
+
+ );
+}
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/chart-brush-track-overlay.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/chart-brush-track-overlay.tsx
new file mode 100644
index 0000000..99734b1
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/chart-brush-track-overlay.tsx
@@ -0,0 +1,141 @@
+"use client";
+
+import type { RefObject } from "react";
+import { type CSSProperties, useEffect, useState } from "react";
+import { createPortal } from "react-dom";
+import type { Margin } from "./chart-context";
+import { useChartStable } from "./chart-context";
+
+/** Fade dimming panes at the outer track ends — matches series edge fade (15%). */
+const BRUSH_TRACK_OUTER_FADE = 0.15;
+
+export interface ChartBrushTrackOverlayStyle {
+ /** Backdrop blur in px (0–5). Default: 1.5 */
+ blurPx?: number;
+ /** Fade blur at the outer left/right track ends. Default: true */
+ fadeOuterEdges?: boolean;
+}
+
+function outsidePaneStyle(
+ edge: "left" | "right",
+ style: Required
+): CSSProperties {
+ const fadeStop = `${BRUSH_TRACK_OUTER_FADE * 100}%`;
+ let mask: string | undefined;
+ if (style.fadeOuterEdges) {
+ mask =
+ edge === "left"
+ ? `linear-gradient(to right, transparent 0%, black ${fadeStop}, black 100%)`
+ : `linear-gradient(to left, transparent 0%, black ${fadeStop}, black 100%)`;
+ }
+
+ return {
+ pointerEvents: "none",
+ backdropFilter: style.blurPx > 0 ? `blur(${style.blurPx}px)` : undefined,
+ WebkitBackdropFilter:
+ style.blurPx > 0 ? `blur(${style.blurPx}px)` : undefined,
+ maskImage: mask,
+ WebkitMaskImage: mask,
+ };
+}
+
+export interface ChartBrushTrackOverlayProps
+ extends ChartBrushTrackOverlayStyle {
+ innerWidth: number;
+ innerHeight: number;
+ /** Pixel x of the selection start within the plot area. */
+ selectionX0: number;
+ /** Pixel x of the selection end within the plot area. */
+ selectionX1: number;
+}
+
+export interface ChartBrushOverlayHost {
+ containerRef: RefObject;
+ margin: Margin;
+}
+
+export function ChartBrushTrackOverlayContent({
+ containerRef,
+ margin,
+ innerWidth,
+ innerHeight,
+ selectionX0,
+ selectionX1,
+ blurPx = 1.5,
+ fadeOuterEdges = true,
+}: ChartBrushTrackOverlayProps & ChartBrushOverlayHost) {
+ const [mounted, setMounted] = useState(false);
+ const overlayStyle: Required = {
+ blurPx: Math.min(5, Math.max(0, blurPx)),
+ fadeOuterEdges,
+ };
+
+ useEffect(() => {
+ setMounted(true);
+ }, []);
+
+ const container = containerRef.current;
+ if (!(mounted && container)) {
+ return null;
+ }
+
+ const x0 = Math.max(0, Math.min(selectionX0, selectionX1, innerWidth));
+ const x1 = Math.max(
+ x0,
+ Math.min(Math.max(selectionX0, selectionX1), innerWidth)
+ );
+ const leftWidth = Math.max(0, x0);
+ const rightWidth = Math.max(0, innerWidth - x1);
+
+ if (leftWidth <= 0 && rightWidth <= 0) {
+ return null;
+ }
+
+ const plotLeft = margin.left;
+ const plotTop = margin.top;
+
+ return createPortal(
+
+ {leftWidth > 0 ? (
+
+ ) : null}
+ {rightWidth > 0 ? (
+
+ ) : null}
+
,
+ container
+ );
+}
+
+export function ChartBrushTrackOverlay(props: ChartBrushTrackOverlayProps) {
+ const { containerRef, margin } = useChartStable();
+
+ return (
+
+ );
+}
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/chart-brush.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/chart-brush.tsx
new file mode 100644
index 0000000..1b976a0
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/chart-brush.tsx
@@ -0,0 +1,343 @@
+"use client";
+
+import { Brush } from "@visx/brush";
+import type { Bounds } from "@visx/brush/lib/types";
+import type React from "react";
+import { memo, useCallback, useEffect, useMemo, useState } from "react";
+import {
+ ChartBrushHandleOverlay,
+ renderChartBrushHandle,
+} from "./chart-brush-handle";
+import {
+ ChartBrushSelectionOverlay,
+ type ChartBrushSelectionPattern,
+} from "./chart-brush-selection-overlay";
+import {
+ ChartBrushTrackOverlay,
+ type ChartBrushTrackOverlayStyle,
+} from "./chart-brush-track-overlay";
+import { chartCssVars, useChartStable } from "./chart-context";
+
+interface BrushProps {
+ xScale: unknown;
+ yScale: unknown;
+ width: number;
+ height: number;
+ onBrushEnd?: (bounds: Bounds | null) => void;
+ onChange?: (bounds: Bounds | null) => void;
+ brushDirection?: "horizontal" | "vertical" | "both";
+ margin?: { top: number; left: number; right: number; bottom: number };
+ selectedBoxStyle?: React.SVGProps;
+ handleSize?: number;
+ renderBrushHandle?: (props: {
+ x: number;
+ y: number;
+ width: number;
+ height: number;
+ className: string;
+ isBrushActive: boolean;
+ }) => React.ReactNode;
+ useWindowMoveEvents?: boolean;
+ initialBrushPosition?: {
+ start?: { x?: number; y?: number };
+ end?: { x?: number; y?: number };
+ };
+}
+
+const BrushComponent = (() =>
+ Brush)() as unknown as React.ComponentType;
+
+export interface ChartBrushSelection {
+ start: Date;
+ end: Date;
+}
+
+export interface ChartBrushProps {
+ /** Callback when brush selection ends. Domain is the selected time range. */
+ onSelectionChange?: (domain: ChartBrushSelection | null) => void;
+ /** Brush direction. Default: "horizontal" for time range selection. */
+ brushDirection?: "horizontal" | "vertical" | "both";
+ /** Fill style for the selected region. Default: chart segment style. */
+ selectedBoxStyle?: React.SVGProps;
+ /** Initial selection so the brush overlay is visible on load (e.g. first 50% of range). */
+ initialSelection?: ChartBrushSelection | null;
+ /** Current selection (e.g. from parent state). When set, a visible selection rect is drawn. */
+ selection?: ChartBrushSelection | null;
+ /** Use window move events for brush (can fix coordinate offset when SVG is in transformed container). Default: true for brush-in-strip. */
+ useWindowMoveEvents?: boolean;
+ /** Backdrop blur on dimmed track (0–5 px). Default: 1.5 */
+ blurPx?: number;
+ /** Fade dimmed regions at the outer track edges. Default: true */
+ fadeOuterEdges?: boolean;
+ /** Optional pattern overlay inside the brush selection window. */
+ selectionPattern?: ChartBrushSelectionPattern;
+}
+
+interface ChartBrushInnerProps extends ChartBrushTrackOverlayStyle {
+ brushDirection?: ChartBrushProps["brushDirection"];
+ selectedBoxStyle?: ChartBrushProps["selectedBoxStyle"];
+ initialSelection?: ChartBrushProps["initialSelection"];
+ useWindowMoveEvents?: ChartBrushProps["useWindowMoveEvents"];
+ xScale: ReturnType["xScale"];
+ yScale: ReturnType["yScale"];
+ innerWidth: number;
+ innerHeight: number;
+ margin: ReturnType["margin"];
+ onBrushPreview?: (bounds: Bounds | null) => void;
+ onBrushCommit: (bounds: Bounds | null) => void;
+ selectionPattern?: ChartBrushSelectionPattern;
+}
+
+function toDate(value: number | Date | unknown): Date {
+ if (value instanceof Date) {
+ return value;
+ }
+ if (typeof value === "number") {
+ return new Date(value);
+ }
+ return new Date(Number(value));
+}
+
+function boundsToPixelExtent(
+ bounds: Bounds | null,
+ xScale: ReturnType["xScale"],
+ innerWidth: number
+): { x0: number; x1: number } | null {
+ if (
+ !bounds ||
+ typeof bounds.x0 === "undefined" ||
+ typeof bounds.x1 === "undefined"
+ ) {
+ return null;
+ }
+
+ const start = toDate(bounds.x0);
+ const end = toDate(bounds.x1);
+ const xScaleFn = xScale as (d: Date) => number;
+ const x0 = Math.max(0, xScaleFn(start < end ? start : end) ?? 0);
+ const x1 = Math.min(
+ innerWidth,
+ xScaleFn(end > start ? end : start) ?? innerWidth
+ );
+
+ if (x1 <= x0) {
+ return null;
+ }
+
+ return { x0, x1 };
+}
+
+const ChartBrushInner = memo(function ChartBrushInner({
+ brushDirection = "horizontal",
+ selectedBoxStyle,
+ initialSelection,
+ useWindowMoveEvents = true,
+ xScale,
+ yScale,
+ innerWidth,
+ innerHeight,
+ margin,
+ onBrushPreview,
+ onBrushCommit,
+ blurPx,
+ fadeOuterEdges,
+ selectionPattern,
+}: ChartBrushInnerProps) {
+ const initialBrushPosition = useMemo(() => {
+ if (!initialSelection || innerWidth <= 0 || innerHeight <= 0) {
+ return undefined;
+ }
+ const xScaleFn = xScale as (d: Date) => number;
+ const x0 = Math.max(0, xScaleFn(initialSelection.start) ?? 0);
+ const x1 = Math.min(
+ innerWidth,
+ xScaleFn(initialSelection.end) ?? innerWidth
+ );
+ if (x1 <= x0) {
+ return undefined;
+ }
+ return {
+ start: { x: x0, y: 0 },
+ end: { x: x1, y: innerHeight },
+ };
+ }, [initialSelection, xScale, innerWidth, innerHeight]);
+
+ const [pixelExtent, setPixelExtent] = useState(() => ({
+ x0: initialBrushPosition?.start.x ?? 0,
+ x1: initialBrushPosition?.end.x ?? innerWidth,
+ }));
+
+ useEffect(() => {
+ setPixelExtent({
+ x0: initialBrushPosition?.start.x ?? 0,
+ x1: initialBrushPosition?.end.x ?? innerWidth,
+ });
+ }, [initialBrushPosition, innerWidth]);
+
+ const updatePixelExtent = useCallback(
+ (bounds: Bounds | null) => {
+ const pixels = boundsToPixelExtent(bounds, xScale, innerWidth);
+ if (pixels) {
+ setPixelExtent(pixels);
+ }
+ },
+ [innerWidth, xScale]
+ );
+
+ const handleBrushPreview = useCallback(
+ (bounds: Bounds | null) => {
+ updatePixelExtent(bounds);
+ onBrushPreview?.(bounds);
+ },
+ [onBrushPreview, updatePixelExtent]
+ );
+
+ const handleBrushCommit = useCallback(
+ (bounds: Bounds | null) => {
+ updatePixelExtent(bounds);
+ onBrushCommit(bounds);
+ },
+ [onBrushCommit, updatePixelExtent]
+ );
+
+ const defaultStyle = {
+ fill: "transparent",
+ fillOpacity: 0,
+ stroke: chartCssVars.brushBorder,
+ strokeWidth: 1,
+ };
+
+ return (
+
+
+
+
+
+
+ );
+});
+
+export function ChartBrush({
+ onSelectionChange,
+ brushDirection = "horizontal",
+ selectedBoxStyle,
+ initialSelection,
+ selection: _selection,
+ useWindowMoveEvents = true,
+ blurPx,
+ fadeOuterEdges,
+ selectionPattern,
+}: ChartBrushProps) {
+ const { xScale, yScale, innerWidth, innerHeight, margin, isLoaded } =
+ useChartStable();
+
+ const boundsToSelection = useCallback(
+ (bounds: Bounds | null): ChartBrushSelection | null => {
+ if (
+ !bounds ||
+ typeof bounds.x0 === "undefined" ||
+ typeof bounds.x1 === "undefined"
+ ) {
+ return null;
+ }
+ const start = toDate(bounds.x0);
+ const end = toDate(bounds.x1);
+ if (start.getTime() === end.getTime()) {
+ return null;
+ }
+ return {
+ start: start < end ? start : end,
+ end: end > start ? end : start,
+ };
+ },
+ []
+ );
+
+ const notifySelectionChange = useCallback(
+ (bounds: Bounds | null) => {
+ if (!onSelectionChange) {
+ return;
+ }
+ onSelectionChange(boundsToSelection(bounds));
+ },
+ [onSelectionChange, boundsToSelection]
+ );
+
+ const handleBrushPreview = useCallback(
+ (bounds: Bounds | null) => {
+ notifySelectionChange(bounds);
+ },
+ [notifySelectionChange]
+ );
+
+ const handleBrushCommit = useCallback(
+ (bounds: Bounds | null) => {
+ notifySelectionChange(bounds);
+ },
+ [notifySelectionChange]
+ );
+
+ if (!isLoaded || innerWidth <= 0 || innerHeight <= 0) {
+ return null;
+ }
+
+ return (
+
+ );
+}
+
+ChartBrush.displayName = "ChartBrush";
+
+export default ChartBrush;
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/chart-center-typography.ts b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/chart-center-typography.ts
new file mode 100644
index 0000000..d81e780
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/chart-center-typography.ts
@@ -0,0 +1,16 @@
+/**
+ * Fluid typography for pie / ring / gauge center labels.
+ *
+ * Uses CSS container query units (`cqw`) so values scale with the center
+ * hole — not the viewport — which keeps stat text readable on small charts.
+ */
+export const chartCenterContainerClassName =
+ "@container/chart-center size-full min-w-0";
+
+/** Primary stat — ~22% of center width, clamped between text-sm and text-3xl. */
+export const chartCenterValueClassName =
+ "font-bold tabular-nums leading-none text-[clamp(0.75rem,22cqw,1.875rem)]";
+
+/** Supporting label — ~9% of center width, clamped between 10px and text-xs. */
+export const chartCenterLabelClassName =
+ "max-w-full truncate leading-tight text-[clamp(0.625rem,9cqw,0.75rem)]";
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/chart-child-passthrough.ts b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/chart-child-passthrough.ts
new file mode 100644
index 0000000..00d9d25
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/chart-child-passthrough.ts
@@ -0,0 +1,117 @@
+import {
+ Children,
+ cloneElement,
+ Fragment,
+ isValidElement,
+ type ReactElement,
+ type ReactNode,
+} from "react";
+
+/** Marker on wrapper components whose single child should inherit clip classification. */
+export const CHART_CLIP_PASSTHROUGH = "__chartClipPassthrough" as const;
+
+export function isChartClipPassthrough(type: unknown): boolean {
+ return (
+ typeof type === "function" &&
+ (type as { [CHART_CLIP_PASSTHROUGH]?: boolean })[CHART_CLIP_PASSTHROUGH] ===
+ true
+ );
+}
+
+/** Unwrap visibility wrappers so `Grid` / axes stay outside the series clip. */
+export function resolveChartChildElement(child: ReactElement): ReactElement {
+ if (isChartClipPassthrough(child.type)) {
+ const inner = (child.props as { children?: unknown }).children;
+ if (isValidElement(inner)) {
+ return resolveChartChildElement(inner);
+ }
+ }
+ return child;
+}
+
+/** Walk chart children, flattening React fragments (studio often groups layers in `<>...>`). */
+export function forEachChartChild(
+ children: ReactNode,
+ callback: (child: ReactElement, index: number) => void
+) {
+ let index = 0;
+ const visit = (nodes: ReactNode) => {
+ Children.forEach(nodes, (child) => {
+ if (!isValidElement(child)) {
+ return;
+ }
+ if (child.type === Fragment) {
+ visit((child.props as { children?: ReactNode }).children);
+ return;
+ }
+ callback(child, index);
+ index += 1;
+ });
+ };
+ visit(children);
+}
+
+const CLIP_EXCLUDED_COMPONENT_NAMES = new Set([
+ "Background",
+ "Grid",
+ "XAxis",
+ "YAxis",
+ "BarXAxis",
+ "BarYAxis",
+ "LiveXAxis",
+ "LiveYAxis",
+]);
+
+const UNDERLAY_COMPONENT_NAMES = new Set(["ReferenceArea"]);
+
+/** Markers render after the interaction overlay so they stay clickable. */
+export function isPostOverlayComponent(child: ReactElement): boolean {
+ const childType = child.type as {
+ displayName?: string;
+ name?: string;
+ __isChartMarkers?: boolean;
+ __isPostOverlay?: boolean;
+ };
+
+ if (childType.__isChartMarkers || childType.__isPostOverlay) {
+ return true;
+ }
+
+ const componentName =
+ typeof child.type === "function"
+ ? childType.displayName || childType.name || ""
+ : "";
+
+ return (
+ componentName === "ChartMarkers" ||
+ componentName === "MarkerGroup" ||
+ componentName === "ChartBrush"
+ );
+}
+
+/** Renders above grid/axes but below series; excluded from grow-clip reveal. */
+export function isUnderlayComponent(child: ReactElement): boolean {
+ const childType = child.type as { displayName?: string; name?: string };
+ const componentName =
+ typeof child.type === "function"
+ ? childType.displayName || childType.name || ""
+ : "";
+ return UNDERLAY_COMPONENT_NAMES.has(componentName);
+}
+
+/** Grid and axes stay visible during series clip reveal (e.g. loading → ready). */
+export function isClipExcludedComponent(child: ReactElement): boolean {
+ const childType = child.type as { displayName?: string; name?: string };
+ const componentName =
+ typeof child.type === "function"
+ ? childType.displayName || childType.name || ""
+ : "";
+ return CLIP_EXCLUDED_COMPONENT_NAMES.has(componentName);
+}
+
+/** SVG layer lists from chart shells need stable keys when rendered as arrays. */
+export function renderKeyedChartLayers(children: ReactElement[]) {
+ return children.map((child, index) =>
+ cloneElement(child, { key: child.key ?? `chart-layer-${index}` })
+ );
+}
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/chart-config-context.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/chart-config-context.tsx
new file mode 100644
index 0000000..2faf405
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/chart-config-context.tsx
@@ -0,0 +1,92 @@
+"use client";
+
+import { createContext, type ReactNode, useContext, useMemo } from "react";
+
+export interface SpringConfig {
+ stiffness: number;
+ damping: number;
+}
+
+export interface ChartConfigValue {
+ /** Crosshair indicator, tooltip dot, date pill. */
+ tooltipSpring: SpringConfig;
+ /** Floating tooltip panel. */
+ tooltipBoxSpring: SpringConfig;
+ /** Line/area hover-highlight band (x + width). */
+ highlightSpring: SpringConfig;
+}
+
+export const DEFAULT_CHART_CONFIG: ChartConfigValue = {
+ tooltipSpring: { stiffness: 300, damping: 30 },
+ tooltipBoxSpring: { stiffness: 100, damping: 20 },
+ highlightSpring: { stiffness: 180, damping: 28 },
+};
+
+const ChartConfigContext = createContext(null);
+
+export interface ChartConfigProviderProps {
+ value?: Partial;
+ children: ReactNode;
+}
+
+export function ChartConfigProvider({
+ value,
+ children,
+}: ChartConfigProviderProps) {
+ const merged = useMemo(
+ () => ({
+ ...DEFAULT_CHART_CONFIG,
+ ...value,
+ }),
+ [value]
+ );
+
+ return (
+
+ {children}
+
+ );
+}
+
+export function useChartConfig(): ChartConfigValue {
+ return useContext(ChartConfigContext) ?? DEFAULT_CHART_CONFIG;
+}
+
+const DEFAULT_TOOLTIP_BOX_DAMPING =
+ DEFAULT_CHART_CONFIG.tooltipBoxSpring.damping;
+
+/** Maps a damping slider to the floating tooltip panel follow spring. `0` = instant. */
+export function resolveTooltipBoxMotion(damping?: number): {
+ animate: boolean;
+ springConfig: SpringConfig;
+} {
+ if (damping === 0) {
+ return {
+ animate: false,
+ springConfig: DEFAULT_CHART_CONFIG.tooltipBoxSpring,
+ };
+ }
+
+ const effectiveDamping = damping ?? DEFAULT_TOOLTIP_BOX_DAMPING;
+ let stiffness = DEFAULT_CHART_CONFIG.tooltipBoxSpring.stiffness;
+
+ if (effectiveDamping < DEFAULT_TOOLTIP_BOX_DAMPING) {
+ const t =
+ (DEFAULT_TOOLTIP_BOX_DAMPING - effectiveDamping) /
+ DEFAULT_TOOLTIP_BOX_DAMPING;
+ stiffness += t * 400;
+ } else if (effectiveDamping > DEFAULT_TOOLTIP_BOX_DAMPING) {
+ const t =
+ (effectiveDamping - DEFAULT_TOOLTIP_BOX_DAMPING) /
+ (100 - DEFAULT_TOOLTIP_BOX_DAMPING);
+ stiffness -= t * 85;
+ }
+
+ return {
+ animate: true,
+ springConfig: {
+ stiffness: Math.max(12, Math.round(stiffness)),
+ damping: effectiveDamping,
+ },
+ };
+}
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/chart-context.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/chart-context.tsx
new file mode 100644
index 0000000..a00b4a3
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/chart-context.tsx
@@ -0,0 +1,426 @@
+"use client";
+
+import type { scaleBand, scaleLinear, scaleTime } from "@visx/scale";
+
+type ScaleLinear = ReturnType<
+ typeof scaleLinear
+>;
+type ScaleTime = ReturnType<
+ typeof scaleTime
+>;
+type ScaleBand = ReturnType<
+ typeof scaleBand
+>;
+
+import type { Transition } from "motion/react";
+import {
+ createContext,
+ type Dispatch,
+ type ReactNode,
+ type RefObject,
+ type SetStateAction,
+ useContext,
+ useMemo,
+} from "react";
+import type { ChartPhase, ChartStatus } from "./chart-phase";
+import type { ReferenceAreaConfig } from "./reference-area-config";
+import type { ChartSelection } from "./use-chart-interaction";
+import { DEFAULT_Y_AXIS_ID } from "./y-axis-scales";
+import type { YDomain } from "./y-domain-utils";
+
+// CSS variable references for theming
+export const chartCssVars = {
+ background: "var(--chart-background)",
+ foreground: "var(--chart-foreground)",
+ foregroundMuted: "var(--chart-foreground-muted)",
+ label: "var(--chart-label)",
+ linePrimary: "var(--chart-line-primary)",
+ lineSecondary: "var(--chart-line-secondary)",
+ crosshair: "var(--chart-crosshair)",
+ grid: "var(--chart-grid)",
+ indicatorColor: "var(--chart-indicator-color)",
+ indicatorSecondaryColor: "var(--chart-indicator-secondary-color)",
+ markerBackground: "var(--chart-marker-background)",
+ markerBorder: "var(--chart-marker-border)",
+ markerForeground: "var(--chart-marker-foreground)",
+ badgeBackground: "var(--chart-marker-badge-background)",
+ badgeForeground: "var(--chart-marker-badge-foreground)",
+ segmentBackground: "var(--chart-segment-background)",
+ segmentLine: "var(--chart-segment-line)",
+ brushBorder: "var(--chart-brush-border)",
+ tooltipBackground: "var(--chart-tooltip-background)",
+};
+
+/** Default scatter series colors from the chart palette (`--chart-1` … `--chart-5`). */
+export const defaultScatterColors = [
+ "var(--chart-1)",
+ "var(--chart-2)",
+ "var(--chart-3)",
+ "var(--chart-4)",
+ "var(--chart-5)",
+] as const;
+
+export interface Margin {
+ top: number;
+ right: number;
+ bottom: number;
+ left: number;
+}
+
+export interface TooltipData {
+ /** The data point being hovered */
+ point: Record;
+ /** Index in the data array */
+ index: number;
+ /** X position in pixels (relative to chart area) */
+ x: number;
+ /** Y positions for each line, keyed by dataKey */
+ yPositions: Record;
+ /** X positions for each series (for grouped bars), keyed by dataKey */
+ xPositions?: Record;
+}
+
+export interface LineConfig {
+ dataKey: string;
+ stroke: string;
+ strokeWidth: number;
+ /** Scale group id (Recharts `yAxisId`). Default: `"left"`. */
+ yAxisId?: string | number;
+}
+
+/**
+ * Hover/selection state — every field here changes on mouse movement.
+ * Lives in its own context so cold consumers (Grid, YAxis, PatternArea, …)
+ * can subscribe to the stable slice and skip re-rendering on every hover.
+ */
+export interface ChartHoverContextValue {
+ // Tooltip state
+ tooltipData: TooltipData | null;
+ setTooltipData: Dispatch>;
+
+ // Selection state (optional - only present when useChartInteraction is used)
+ /** Current drag/pinch selection range */
+ selection?: ChartSelection | null;
+ /** Clear the current selection */
+ clearSelection?: () => void;
+
+ // Bar chart hover (optional - only present in BarChart)
+ /** Index of currently hovered bar */
+ hoveredBarIndex?: number | null;
+ /** Setter for hovered bar index */
+ setHoveredBarIndex?: (index: number | null) => void;
+
+ // Candlestick hover (optional - only present in CandlestickChart)
+ /** Index of currently hovered candle */
+ hoveredCandleIndex?: number | null;
+ /** Setter for hovered candle index */
+ setHoveredCandleIndex?: (index: number | null) => void;
+}
+
+export interface ChartContextValue extends ChartHoverContextValue {
+ // Data
+ data: Record[];
+ /** Decimated subset for SVG path rendering; equals `data` when no decimation is needed. */
+ renderData: Record[];
+
+ // Scales
+ xScale: ScaleTime;
+ /** Primary (left) y-scale — alias for `yScales[DEFAULT_Y_AXIS_ID]`. */
+ yScale: ScaleLinear;
+ /** Per-axis y-scales keyed by `yAxisId`. */
+ yScales: Record>;
+
+ // Dimensions
+ width: number;
+ height: number;
+ innerWidth: number;
+ innerHeight: number;
+ margin: Margin;
+
+ // Column width for spacing calculations
+ columnWidth: number;
+
+ // Container ref for portals
+ containerRef: RefObject;
+
+ // Line configurations (extracted from children)
+ lines: LineConfig[];
+
+ /** {@link ReferenceArea} bands — drives y-axis label colors in range. */
+ referenceAreas: ReferenceAreaConfig[];
+
+ // Loading / lifecycle (LineChart status transitions)
+ chartPhase: ChartPhase;
+ chartStatus: ChartStatus;
+ /** Centered label while `chartPhase` shows loading chrome. */
+ loadingLabel?: string;
+ /** Y-domain tween duration when transitioning loading ↔ ready (ms). */
+ yDomainTweenDuration: number;
+ /** Nice’d y-domains per axis from skeleton data (placeholder). */
+ yDomainSkeletonByAxis: Record;
+ /** Nice’d y-domains per axis from the current target data. */
+ yDomainTargetByAxis: Record;
+
+ // Animation state
+ isLoaded: boolean;
+ animationDuration: number;
+ /** CSS easing for clip-reveal / line draw (cartesian charts). */
+ animationEasing?: string;
+ /** Motion enter transition (spring or tween) — drives clip reveal when spring. */
+ enterTransition?: Transition;
+ /** Increments when enter animation should replay. */
+ revealEpoch?: number;
+ /** Fired when a one-shot loading pulse (exit / enter) completes. */
+ notifyLoadingPulseComplete?: () => void;
+
+ // X accessor - how to get the x value from data points
+ xAccessor: (d: Record) => Date;
+
+ // Pre-computed date labels for ticker animation
+ dateLabels: string[];
+
+ /** Active brush zoom range — when set, axis ticks align to visible data rows. */
+ xDomain?: [Date, Date];
+ /** Full dataset length when brush zoom is enabled (for zoom vs full-range detection). */
+ xDomainSlotCount?: number;
+
+ // Bar chart specific (optional - only present in BarChart)
+ /** Band scale for categorical x-axis (bar charts) */
+ barScale?: ScaleBand;
+ /** Width of each bar band */
+ bandWidth?: number;
+ /** X accessor for bar charts (returns string instead of Date) */
+ barXAccessor?: (d: Record) => string;
+ /** Bar chart orientation */
+ orientation?: "vertical" | "horizontal";
+ /** Whether bars are stacked */
+ stacked?: boolean;
+ /** Stack offsets: Map of data index -> Map of dataKey -> cumulative offset */
+ stackOffsets?: Map>;
+
+ // ComposedChart + SeriesBar (optional)
+ /** `SeriesBar` dataKeys in tree order, for grouped columns at each x */
+ composedBarDataKeys?: string[];
+ /** Target bar width in px (Recharts `barSize` style). */
+ composedBarSize?: number;
+ /** Max bar width in px (Recharts `maxBarSize`). */
+ composedMaxBarSize?: number;
+ /** Gap between grouped `SeriesBar` columns in px. */
+ composedBarGap?: number;
+ /** When true, `SeriesBar` segments stack in child order at each x. */
+ composedStacked?: boolean;
+ /** Per-row cumulative offsets for stacked `SeriesBar` (data index → dataKey → offset). */
+ composedStackOffsets?: Map>;
+ /** Vertical gap in px between stacked `SeriesBar` segments. Default: 0 */
+ composedStackGap?: number;
+}
+
+/**
+ * Stable slice of the chart context — everything that doesn't change on hover
+ * (data, scales, dimensions, animation state, layout config). Consumers that
+ * subscribe via `useChartStable()` skip re-renders on every mouse move.
+ */
+export type ChartStableContextValue = Omit<
+ ChartContextValue,
+ keyof ChartHoverContextValue
+>;
+
+const ChartStableContext = createContext(null);
+const ChartHoverContext = createContext(null);
+
+/**
+ * Splits the merged `value` into a stable slice and a volatile hover slice,
+ * publishing each to its own context. Each slice is memoized on its own
+ * field identities, so changing `tooltipData` does not bust the stable
+ * slice — consumers of `useChartStable()` skip re-renders on hover.
+ */
+export function ChartProvider({
+ children,
+ value,
+}: {
+ children: ReactNode;
+ value: ChartContextValue;
+}) {
+ const stable = useMemo(
+ () => ({
+ data: value.data,
+ renderData: value.renderData,
+ xScale: value.xScale,
+ yScale: value.yScale,
+ yScales: value.yScales,
+ width: value.width,
+ height: value.height,
+ innerWidth: value.innerWidth,
+ innerHeight: value.innerHeight,
+ margin: value.margin,
+ columnWidth: value.columnWidth,
+ containerRef: value.containerRef,
+ lines: value.lines,
+ referenceAreas: value.referenceAreas,
+ chartPhase: value.chartPhase,
+ chartStatus: value.chartStatus,
+ loadingLabel: value.loadingLabel,
+ yDomainTweenDuration: value.yDomainTweenDuration,
+ yDomainSkeletonByAxis: value.yDomainSkeletonByAxis,
+ yDomainTargetByAxis: value.yDomainTargetByAxis,
+ isLoaded: value.isLoaded,
+ animationDuration: value.animationDuration,
+ animationEasing: value.animationEasing,
+ enterTransition: value.enterTransition,
+ revealEpoch: value.revealEpoch,
+ notifyLoadingPulseComplete: value.notifyLoadingPulseComplete,
+ xAccessor: value.xAccessor,
+ dateLabels: value.dateLabels,
+ xDomain: value.xDomain,
+ xDomainSlotCount: value.xDomainSlotCount,
+ barScale: value.barScale,
+ bandWidth: value.bandWidth,
+ barXAccessor: value.barXAccessor,
+ orientation: value.orientation,
+ stacked: value.stacked,
+ stackOffsets: value.stackOffsets,
+ composedBarDataKeys: value.composedBarDataKeys,
+ composedBarSize: value.composedBarSize,
+ composedMaxBarSize: value.composedMaxBarSize,
+ composedBarGap: value.composedBarGap,
+ composedStacked: value.composedStacked,
+ composedStackOffsets: value.composedStackOffsets,
+ composedStackGap: value.composedStackGap,
+ }),
+ [
+ value.data,
+ value.renderData,
+ value.xScale,
+ value.yScale,
+ value.yScales,
+ value.width,
+ value.height,
+ value.innerWidth,
+ value.innerHeight,
+ value.margin,
+ value.columnWidth,
+ value.containerRef,
+ value.lines,
+ value.referenceAreas,
+ value.chartPhase,
+ value.chartStatus,
+ value.loadingLabel,
+ value.yDomainTweenDuration,
+ value.yDomainSkeletonByAxis,
+ value.yDomainTargetByAxis,
+ value.isLoaded,
+ value.animationDuration,
+ value.animationEasing,
+ value.enterTransition,
+ value.revealEpoch,
+ value.notifyLoadingPulseComplete,
+ value.xAccessor,
+ value.dateLabels,
+ value.xDomain,
+ value.xDomainSlotCount,
+ value.barScale,
+ value.bandWidth,
+ value.barXAccessor,
+ value.orientation,
+ value.stacked,
+ value.stackOffsets,
+ value.composedBarDataKeys,
+ value.composedBarSize,
+ value.composedMaxBarSize,
+ value.composedBarGap,
+ value.composedStacked,
+ value.composedStackOffsets,
+ value.composedStackGap,
+ ]
+ );
+
+ const hover = useMemo(
+ () => ({
+ tooltipData: value.tooltipData,
+ setTooltipData: value.setTooltipData,
+ selection: value.selection,
+ clearSelection: value.clearSelection,
+ hoveredBarIndex: value.hoveredBarIndex,
+ setHoveredBarIndex: value.setHoveredBarIndex,
+ hoveredCandleIndex: value.hoveredCandleIndex,
+ setHoveredCandleIndex: value.setHoveredCandleIndex,
+ }),
+ [
+ value.tooltipData,
+ value.setTooltipData,
+ value.selection,
+ value.clearSelection,
+ value.hoveredBarIndex,
+ value.setHoveredBarIndex,
+ value.hoveredCandleIndex,
+ value.setHoveredCandleIndex,
+ ]
+ );
+
+ return (
+
+
+ {children}
+
+
+ );
+}
+
+/**
+ * Stable slice — data, scales, dimensions, animation state, layout config.
+ * Subscribers skip re-renders on hover (the hover slice lives in a separate
+ * context). Prefer this in cold consumers like axes, grid, pattern fills.
+ */
+export function useChartStable(): ChartStableContextValue {
+ const context = useContext(ChartStableContext);
+ if (!context) {
+ throw new Error(
+ "useChartStable must be used within a ChartProvider. " +
+ "Make sure your component is wrapped in , , , or ."
+ );
+ }
+ return context;
+}
+
+/** Y-scale for a series axis (`yAxisId` on Line / Area / YAxis). */
+export function useYScale(
+ yAxisId?: string | number
+): ScaleLinear {
+ const { yScales, yScale } = useChartStable();
+ const id =
+ yAxisId == null || yAxisId === "" ? DEFAULT_Y_AXIS_ID : String(yAxisId);
+ return yScales[id] ?? yScale;
+}
+
+/**
+ * Hover slice — tooltipData, selection, hovered bar / candle indices.
+ * Subscribers re-render on every mouse move. Use only when the component
+ * actually reads hover state.
+ */
+export function useChartHover(): ChartHoverContextValue {
+ const context = useContext(ChartHoverContext);
+ if (!context) {
+ throw new Error(
+ "useChartHover must be used within a ChartProvider. " +
+ "Make sure your component is wrapped in , , , or ."
+ );
+ }
+ return context;
+}
+
+/**
+ * Merged stable + hover context. Convenient for components that need both,
+ * but re-renders on every hover (because hover changes). Prefer
+ * `useChartStable()` or `useChartHover()` for hot consumers that only need
+ * one slice.
+ */
+export function useChart(): ChartContextValue {
+ const stable = useChartStable();
+ const hover = useChartHover();
+ // Identity changes on every hover (hover is the volatile slice) — that's
+ // fine for consumers using this merged hook; they explicitly opted in to
+ // re-rendering on hover.
+ return { ...stable, ...hover };
+}
+
+export default ChartStableContext;
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/chart-defs.ts b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/chart-defs.ts
new file mode 100644
index 0000000..6dd941a
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/chart-defs.ts
@@ -0,0 +1,72 @@
+import {
+ Children,
+ isValidElement,
+ type ReactElement,
+ type ReactNode,
+} from "react";
+
+export function getChartChildComponentName(child: ReactElement): string {
+ const childType = child.type as { displayName?: string; name?: string };
+ return typeof child.type === "function"
+ ? childType.displayName || childType.name || ""
+ : "";
+}
+
+const VISX_PATTERN_COMPONENT_NAMES = new Set([
+ "Lines",
+ "Circles",
+ "Waves",
+ "Hexagons",
+ "Path",
+ "Pattern",
+]);
+
+/** @visx/pattern default exports use short names (e.g. `Lines`); also match *Pattern* displayNames. */
+export function isPatternDefComponent(child: ReactElement): boolean {
+ const name = getChartChildComponentName(child);
+ return name.includes("Pattern") || VISX_PATTERN_COMPONENT_NAMES.has(name);
+}
+
+export function isGradientDefComponent(child: ReactElement): boolean {
+ const name = getChartChildComponentName(child);
+ return (
+ name.includes("Gradient") ||
+ name === "LinearGradient" ||
+ name === "RadialGradient"
+ );
+}
+
+export function isChartDefsComponent(child: ReactElement): boolean {
+ return isPatternDefComponent(child) || isGradientDefComponent(child);
+}
+
+/** Split hoisted defs: @visx/pattern nodes already wrap `` and render at the svg root. */
+export function partitionChartDefNodes(defNodes: ReactElement[]): {
+ patternDefNodes: ReactElement[];
+ gradientDefNodes: ReactElement[];
+} {
+ const patternDefNodes: ReactElement[] = [];
+ const gradientDefNodes: ReactElement[] = [];
+
+ for (const node of defNodes) {
+ if (isPatternDefComponent(node)) {
+ patternDefNodes.push(node);
+ } else {
+ gradientDefNodes.push(node);
+ }
+ }
+
+ return { patternDefNodes, gradientDefNodes };
+}
+
+export function collectChartDefsChildren(children: ReactNode): ReactElement[] {
+ const defNodes: ReactElement[] = [];
+
+ Children.forEach(children, (child) => {
+ if (isValidElement(child) && isChartDefsComponent(child)) {
+ defNodes.push(child);
+ }
+ });
+
+ return defNodes;
+}
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/chart-formatters.ts b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/chart-formatters.ts
new file mode 100644
index 0000000..d8b0070
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/chart-formatters.ts
@@ -0,0 +1,20 @@
+export const shortDateFmt = new Intl.DateTimeFormat("en-US", {
+ month: "short",
+ day: "numeric",
+});
+
+export const weekdayDateFmt = new Intl.DateTimeFormat("en-US", {
+ weekday: "short",
+ month: "short",
+ day: "numeric",
+});
+
+export const hmsTimeFmt = new Intl.DateTimeFormat("en-US", {
+ hour: "2-digit",
+ minute: "2-digit",
+ second: "2-digit",
+ hour12: false,
+});
+
+// `Intl.NumberFormat.prototype.format` is a bound getter — safe to extract.
+export const intFmt = new Intl.NumberFormat("en-US").format;
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/chart-legend-hover.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/chart-legend-hover.tsx
new file mode 100644
index 0000000..b95fc71
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/chart-legend-hover.tsx
@@ -0,0 +1,44 @@
+"use client";
+
+import { createContext, type ReactNode, useContext, useMemo } from "react";
+
+interface ChartLegendHoverContextValue {
+ hoveredIndex: number | null;
+ setHoveredIndex: (index: number | null) => void;
+}
+
+const ChartLegendHoverContext =
+ createContext(null);
+
+export function ChartLegendHoverProvider({
+ hoveredIndex,
+ onHoverChange,
+ children,
+}: {
+ hoveredIndex: number | null;
+ onHoverChange: (index: number | null) => void;
+ children: ReactNode;
+}) {
+ const value = useMemo(
+ () => ({ hoveredIndex, setHoveredIndex: onHoverChange }),
+ [hoveredIndex, onHoverChange]
+ );
+
+ return (
+
+ {children}
+
+ );
+}
+
+export function useChartLegendHover(): ChartLegendHoverContextValue {
+ const context = useContext(ChartLegendHoverContext);
+ return (
+ context ?? {
+ hoveredIndex: null,
+ setHoveredIndex: () => {
+ /* noop outside ChartLegendHoverProvider */
+ },
+ }
+ );
+}
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/chart-legend.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/chart-legend.tsx
new file mode 100644
index 0000000..47f3067
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/chart-legend.tsx
@@ -0,0 +1,261 @@
+"use client";
+
+import { Progress } from "@base-ui/react/progress";
+import type { ReactNode } from "react";
+import { cn } from "@/lib/utils";
+import { intFmt } from "./chart-formatters";
+
+export interface LegendItem {
+ /** Display label */
+ label: string;
+ /** Current value */
+ value: number;
+ /** Maximum value (for progress bar calculation) */
+ maxValue?: number;
+ /** Item color */
+ color: string;
+}
+
+export interface ChartLegendProps {
+ /** Legend items to display */
+ items: LegendItem[];
+ /** Currently hovered index (for highlight effect) */
+ hoveredIndex?: number | null;
+ /** Callback when an item is hovered */
+ onHover?: (index: number | null) => void;
+ /** Show progress bars. Default: false */
+ showProgress?: boolean;
+ /** Show color marker dot. Default: true */
+ showMarker?: boolean;
+ /** Show numeric value column. Default: true */
+ showValue?: boolean;
+ /** Show percentage value. Default: true when showProgress is true */
+ showPercentage?: boolean;
+ /** Format function for displaying values. Default: toLocaleString() */
+ formatValue?: (value: number) => string;
+ /** Title shown above the legend */
+ title?: string;
+ /** Additional class name for the container */
+ className?: string;
+ /** Class name for the title */
+ titleClassName?: string;
+ /** Class name for each legend item */
+ itemClassName?: string;
+ /** Class name for the label */
+ labelClassName?: string;
+ /** Class name for the value */
+ valueClassName?: string;
+ /** Custom render function for legend items */
+ renderItem?: (props: {
+ item: LegendItem;
+ index: number;
+ isHovered: boolean;
+ isFaded: boolean;
+ percentage: number;
+ }) => ReactNode;
+}
+
+// Progress bar item using base-ui
+interface ProgressItemProps {
+ item: LegendItem;
+ showMarker: boolean;
+ showValue: boolean;
+ showPercentage: boolean;
+ formatValue: (value: number) => string;
+ labelClassName: string;
+ valueClassName: string;
+}
+
+function ProgressItem({
+ item,
+ showMarker,
+ showValue,
+ showPercentage,
+ formatValue,
+ labelClassName,
+ valueClassName,
+}: ProgressItemProps) {
+ const percentage = item.maxValue ? (item.value / item.maxValue) * 100 : 0;
+
+ // Note: item.color must remain inline style as it's dynamic data
+ return (
+
+ {/* Color marker */}
+ {showMarker && (
+
+ )}
+
+ {/* Label */}
+
+ {item.label}
+
+
+ {showValue ? (
+
+ {formatValue(item.value)}
+
+ ) : null}
+
+ {/* Progress track and indicator */}
+
+
+
+
+ {/* Percentage */}
+ {showPercentage && (
+
+ {percentage.toFixed(0)}%
+
+ )}
+
+ );
+}
+
+// Simple item without progress bar
+interface SimpleItemProps {
+ item: LegendItem;
+ showMarker: boolean;
+ showValue: boolean;
+ formatValue: (value: number) => string;
+ labelClassName: string;
+ valueClassName: string;
+}
+
+function SimpleItem({
+ item,
+ showMarker,
+ showValue,
+ formatValue,
+ labelClassName,
+ valueClassName,
+}: SimpleItemProps) {
+ // Note: item.color must remain inline style as it's dynamic data
+ return (
+
+ {/* Color marker */}
+ {showMarker && (
+
+ )}
+
+ {/* Label */}
+
+ {item.label}
+
+
+ {showValue ? (
+
+ {formatValue(item.value)}
+
+ ) : null}
+
+ );
+}
+
+export function ChartLegend({
+ items,
+ hoveredIndex = null,
+ onHover,
+ showProgress = false,
+ showMarker = true,
+ showValue = true,
+ showPercentage,
+ formatValue = intFmt,
+ title,
+ className = "",
+ titleClassName = "text-sm font-semibold",
+ itemClassName = "",
+ labelClassName = "text-sm font-medium",
+ valueClassName = "text-sm tabular-nums",
+ renderItem,
+}: ChartLegendProps) {
+ // Default showPercentage to true when showProgress is true
+ const displayPercentage = showPercentage ?? showProgress;
+
+ return (
+
+ {title && (
+
+ {title}
+
+ )}
+ {items.map((item, i) => {
+ const percentage = item.maxValue
+ ? (item.value / item.maxValue) * 100
+ : 0;
+ const isHovered = hoveredIndex === i;
+ const isFaded = hoveredIndex !== null && hoveredIndex !== i;
+
+ // Allow custom rendering
+ if (renderItem) {
+ return (
+ // biome-ignore lint/a11y/noNoninteractiveElementInteractions: Legend item hover interaction
+ // biome-ignore lint/a11y/noStaticElementInteractions: Legend item hover interaction
+
onHover?.(i)}
+ onMouseLeave={() => onHover?.(null)}
+ >
+ {renderItem({ item, index: i, isHovered, isFaded, percentage })}
+
+ );
+ }
+
+ return (
+ // biome-ignore lint/a11y/noNoninteractiveElementInteractions: Legend item hover interaction
+ // biome-ignore lint/a11y/noStaticElementInteractions: Legend item hover interaction
+
onHover?.(i)}
+ onMouseLeave={() => onHover?.(null)}
+ >
+ {showProgress && item.maxValue ? (
+
+ ) : (
+
+ )}
+
+ );
+ })}
+
+ );
+}
+
+ChartLegend.displayName = "ChartLegend";
+
+export default ChartLegend;
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/chart-loading-label.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/chart-loading-label.tsx
new file mode 100644
index 0000000..73242a0
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/chart-loading-label.tsx
@@ -0,0 +1,56 @@
+"use client";
+
+import { motion } from "motion/react";
+import { cn } from "@/lib/utils";
+import { ShimmeringText } from "../components/shimmering-text";
+import {
+ LINE_LOADING_PULSE_EASE,
+ LOADING_LABEL_EXIT_S,
+ LOADING_LABEL_EXIT_Y_PX,
+} from "./line-loading-timing";
+
+export interface ChartLoadingLabelProps {
+ /** Label shown centered over the chart. */
+ text?: string;
+ className?: string;
+ /** Animate down, fade, and blur during loading → ready handoff. */
+ exiting?: boolean;
+}
+
+export function ChartLoadingLabel({
+ text = "Loading",
+ className,
+ exiting = false,
+}: ChartLoadingLabelProps) {
+ if (!text.trim()) {
+ return null;
+ }
+
+ return (
+
+
+
+ );
+}
+
+export default ChartLoadingLabel;
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/chart-phase.ts b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/chart-phase.ts
new file mode 100644
index 0000000..ada8632
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/chart-phase.ts
@@ -0,0 +1,53 @@
+/** Consumer-facing fetch / display status on time-series charts. */
+export type ChartStatus = "loading" | "ready";
+
+/** Loading animation style: the default traveling pulse, or a diagonal
+ * shimmer that sweeps across the skeleton. */
+export type LoadingStyle = "pulse" | "sweep";
+
+/**
+ * Internal visual lifecycle phase. Forward and reverse transitions add
+ * intermediate phases in later stack branches.
+ */
+export type ChartPhase =
+ | "loading"
+ | "exiting"
+ | "gridTweenReady"
+ | "revealing"
+ | "ready"
+ | "exitingReady"
+ | "gridTweenLoading"
+ | "revealingLoading";
+
+export const DEFAULT_CHART_STATUS: ChartStatus = "ready";
+
+/** Default Y-domain tween when transitioning loading ↔ ready (ms). */
+export const DEFAULT_Y_DOMAIN_TWEEN_MS = 500;
+
+/** Relative domain delta below which Y tween may be skipped (see plan). */
+export const Y_DOMAIN_TWEEN_SKIP_THRESHOLD = 0.02;
+
+/** Resting phase for a given status before transition orchestration runs. */
+export function resolveRestingChartPhase(status: ChartStatus): ChartPhase {
+ return status === "loading" ? "loading" : "ready";
+}
+
+export function isChartInteractionPhase(phase: ChartPhase): boolean {
+ return phase === "ready";
+}
+
+export const DEFAULT_CHART_LIFECYCLE = {
+ chartPhase: "ready",
+ chartStatus: "ready",
+ loadingLabel: undefined,
+ yDomainTweenDuration: DEFAULT_Y_DOMAIN_TWEEN_MS,
+ yDomainSkeletonByAxis: { left: [0, 100] as [number, number] },
+ yDomainTargetByAxis: { left: [0, 100] as [number, number] },
+} as const satisfies {
+ chartPhase: ChartPhase;
+ chartStatus: ChartStatus;
+ loadingLabel: undefined;
+ yDomainTweenDuration: number;
+ yDomainSkeletonByAxis: Record;
+ yDomainTargetByAxis: Record;
+};
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/chart-reveal-clip.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/chart-reveal-clip.tsx
new file mode 100644
index 0000000..ee4161a
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/chart-reveal-clip.tsx
@@ -0,0 +1,92 @@
+"use client";
+
+import type { Transition } from "motion/react";
+import { motion } from "motion/react";
+import { clipRevealTransition } from "./animation";
+
+export type ChartRevealClipMode = "reveal" | "conceal";
+
+export interface ChartRevealClipProps {
+ clipPathId: string;
+ height: number;
+ targetWidth: number;
+ enterTransition?: Transition;
+ /** Bumps when motion settings change to replay the reveal. */
+ revealEpoch: number;
+ /** Extra inset around the clip rect so edge glyphs are not cut off. */
+ padding?: number;
+ /** When false, clip stays at full width (no grow animation). */
+ animating?: boolean;
+ /** Reveal grows 0 → full; conceal shrinks full → 0 (ready → loading). */
+ mode?: ChartRevealClipMode;
+ /** Called when a conceal animation finishes. */
+ onComplete?: () => void;
+}
+
+/**
+ * Left-to-right clip reveal for cartesian series.
+ * Grows clip rect width from 0 → full (true LTR; scaleX is avoided — it reveals from center).
+ */
+export function ChartRevealClip({
+ clipPathId,
+ height,
+ targetWidth,
+ enterTransition,
+ revealEpoch,
+ padding = 0,
+ animating = true,
+ mode = "reveal",
+ onComplete,
+}: ChartRevealClipProps) {
+ const transition = clipRevealTransition(enterTransition);
+ const paddedWidth = Math.max(0, targetWidth + padding * 2);
+ const paddedHeight = height + padding * 2;
+
+ if (!animating) {
+ return (
+
+
+
+ );
+ }
+
+ if (mode === "conceal") {
+ // Mirror the LTR reveal: advance the clip's left edge rightward while width
+ // shrinks (same geometry as `LineLoadingPulseStroke` exit half-cycle).
+ const rightEdge = -padding + paddedWidth;
+
+ return (
+
+ onComplete?.()}
+ transition={transition}
+ y={-padding}
+ />
+
+ );
+ }
+
+ return (
+
+
+
+ );
+}
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/chart-scale.ts b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/chart-scale.ts
new file mode 100644
index 0000000..ec636eb
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/chart-scale.ts
@@ -0,0 +1,19 @@
+/** Sequential scale CSS variables for heatmaps, choropleths, and binned data (01 = lowest, 05 = highest). */
+export const CHART_SCALE_VARS = [
+ "var(--chart-scale-01)",
+ "var(--chart-scale-02)",
+ "var(--chart-scale-03)",
+ "var(--chart-scale-04)",
+ "var(--chart-scale-05)",
+] as const;
+
+export type ChartScaleVars = typeof CHART_SCALE_VARS;
+
+export const chartScaleCssVars = {
+ scale01: CHART_SCALE_VARS[0],
+ scale02: CHART_SCALE_VARS[1],
+ scale03: CHART_SCALE_VARS[2],
+ scale04: CHART_SCALE_VARS[3],
+ scale05: CHART_SCALE_VARS[4],
+ patternColor: "var(--chart-scale-pattern-color)",
+} as const;
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/chart-stat-flow.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/chart-stat-flow.tsx
new file mode 100644
index 0000000..1f139fe
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/chart-stat-flow.tsx
@@ -0,0 +1,124 @@
+"use client";
+
+import NumberFlow from "@number-flow/react";
+import { type ReactNode, useEffect, useMemo, useState } from "react";
+import { cn } from "@/lib/utils";
+
+/** Subset of `Intl.NumberFormatOptions` supported by NumberFlow */
+export interface ChartStatFlowFormat {
+ notation?: "standard" | "compact";
+ compactDisplay?: "short" | "long";
+ minimumFractionDigits?: number;
+ maximumFractionDigits?: number;
+ minimumIntegerDigits?: number;
+ minimumSignificantDigits?: number;
+ maximumSignificantDigits?: number;
+ style?: "decimal" | "percent" | "currency";
+ currency?: string;
+ currencyDisplay?: "symbol" | "narrowSymbol" | "code" | "name";
+ unit?: string;
+ unitDisplay?: "short" | "long" | "narrow";
+}
+
+export const defaultChartStatFlowFormat: ChartStatFlowFormat = {
+ notation: "standard",
+ maximumFractionDigits: 0,
+};
+
+function formatStatValue(
+ value: number,
+ formatOptions: ChartStatFlowFormat,
+ prefix?: string,
+ suffix?: string
+): string {
+ const formatted = new Intl.NumberFormat(undefined, formatOptions).format(
+ value
+ );
+ return `${prefix ?? ""}${formatted}${suffix ?? ""}`;
+}
+
+function useNumberFlowElementReady(): boolean {
+ const [ready, setReady] = useState(
+ () =>
+ typeof customElements !== "undefined" &&
+ Boolean(customElements.get("number-flow-react"))
+ );
+
+ useEffect(() => {
+ if (ready) {
+ return;
+ }
+ let cancelled = false;
+ customElements.whenDefined("number-flow-react").then(() => {
+ if (!cancelled) {
+ setReady(true);
+ }
+ });
+ return () => {
+ cancelled = true;
+ };
+ }, [ready]);
+
+ return ready;
+}
+
+export interface ChartStatFlowProps {
+ value: number;
+ label: string;
+ formatOptions?: ChartStatFlowFormat;
+ prefix?: string;
+ suffix?: string;
+ valueClassName?: string;
+ labelClassName?: string;
+ icon?: ReactNode;
+}
+
+/**
+ * Shared value + label stack using NumberFlow (same layout as pie / ring centers).
+ * Parent should provide flex alignment and sizing when needed.
+ */
+export function ChartStatFlow({
+ value,
+ label,
+ formatOptions = defaultChartStatFlowFormat,
+ prefix,
+ suffix,
+ valueClassName = "text-2xl font-bold",
+ labelClassName = "text-xs",
+ icon,
+}: ChartStatFlowProps) {
+ const numberFlowReady = useNumberFlowElementReady();
+ const staticValue = useMemo(
+ () => formatStatValue(value, formatOptions, prefix, suffix),
+ [value, formatOptions, prefix, suffix]
+ );
+
+ return (
+ <>
+ {icon ? (
+
+ {icon}
+
+ ) : null}
+
+ {numberFlowReady ? (
+
+ ) : (
+ staticValue
+ )}
+
+
+ {label}
+
+ >
+ );
+}
+
+ChartStatFlow.displayName = "ChartStatFlow";
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/choropleth/choropleth-chart.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/choropleth/choropleth-chart.tsx
new file mode 100644
index 0000000..565b318
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/choropleth/choropleth-chart.tsx
@@ -0,0 +1,512 @@
+"use client";
+
+import { Mercator } from "@visx/geo";
+import { ParentSize } from "@visx/responsive";
+import type { TransformMatrix } from "@visx/zoom";
+import { Zoom } from "@visx/zoom";
+import type { FeatureCollection, Geometry } from "geojson";
+import type { Transition } from "motion/react";
+import React, {
+ memo,
+ type ReactNode,
+ useCallback,
+ useEffect,
+ useMemo,
+ useRef,
+ useState,
+} from "react";
+import { cn } from "@/lib/utils";
+import {
+ type ChoroplethFeature,
+ type ChoroplethFeatureProperties,
+ ChoroplethInteractionShell,
+ ChoroplethStableProvider,
+ ChoroplethZoomContext,
+ type Margin,
+ useChoroplethInteraction,
+ type ZoomInstance,
+} from "./choropleth-context";
+import { ChoroplethFeature as ChoroplethFeatureLayer } from "./choropleth-feature";
+import { ChoroplethGraticule as ChoroplethGraticuleLayer } from "./choropleth-graticule";
+import { ChoroplethTooltip as ChoroplethTooltipLayer } from "./choropleth-tooltip";
+
+export interface ChoroplethChartProps {
+ /** GeoJSON FeatureCollection data */
+ data: FeatureCollection;
+ /** Chart margins */
+ margin?: Partial;
+ /** Animation duration in milliseconds. Default: 800 */
+ animationDuration?: number;
+ /** Motion enter transition (spring or cubic-bezier tween). */
+ enterTransition?: Transition;
+ /** Signature of motion URL state — triggers enter replay when it changes. */
+ revealSignature?: string;
+ /** Aspect ratio as "width / height". Default: "16 / 9" */
+ aspectRatio?: string;
+ /** Projection scale. If not provided, auto-calculated based on width */
+ scale?: number;
+ /** Center coordinates [longitude, latitude]. Default: [0, 20] */
+ center?: [number, number];
+ /** Translate offset [x, y]. If not provided, auto-calculated to center */
+ translate?: [number, number];
+ /** Enable zoom and pan. Default: false */
+ zoomEnabled?: boolean;
+ /** Minimum zoom scale. Default: 0.5 */
+ zoomMin?: number;
+ /** Maximum zoom scale. Default: 4 */
+ zoomMax?: number;
+ /** Initial zoom transform */
+ initialZoom?: TransformMatrix;
+ /** Additional class name for the container */
+ className?: string;
+ /** Child components (ChoroplethFeature, ChoroplethGraticule, ChoroplethTooltip) */
+ children: ReactNode;
+}
+
+const DEFAULT_MARGIN: Margin = { top: 0, right: 0, bottom: 0, left: 0 };
+
+// Known SVG component displayNames
+const SVG_COMPONENT_NAMES = new Set([
+ "ChoroplethFeature",
+ "ChoroplethGraticule",
+ "ChoroplethTooltip",
+]);
+
+const SVG_COMPONENT_TYPES = new Set([
+ ChoroplethFeatureLayer,
+ ChoroplethGraticuleLayer,
+ ChoroplethTooltipLayer,
+]);
+
+function resolveComponentType(type: unknown): unknown {
+ if (
+ typeof type === "object" &&
+ type !== null &&
+ "type" in type &&
+ (type as { type?: unknown }).type
+ ) {
+ return (type as { type: unknown }).type;
+ }
+ return type;
+}
+
+function getComponentDisplayName(type: unknown): string | null {
+ if (typeof type === "function") {
+ const fn = type as { displayName?: string; name?: string };
+ return fn.displayName ?? fn.name ?? null;
+ }
+ if (typeof type === "object" && type !== null) {
+ const wrapped = type as {
+ displayName?: string;
+ type?: { displayName?: string; name?: string };
+ };
+ if (wrapped.displayName) {
+ return wrapped.displayName;
+ }
+ const inner = wrapped.type;
+ if (typeof inner === "function") {
+ const innerFn = inner as { displayName?: string; name?: string };
+ return innerFn.displayName ?? innerFn.name ?? null;
+ }
+ }
+ return null;
+}
+
+function isChoroplethSvgChild(type: unknown): boolean {
+ if (SVG_COMPONENT_TYPES.has(type as never)) {
+ return true;
+ }
+ const resolved = resolveComponentType(type);
+ if (resolved !== type && SVG_COMPONENT_TYPES.has(resolved as never)) {
+ return true;
+ }
+ const displayName = getComponentDisplayName(type);
+ return displayName !== null && SVG_COMPONENT_NAMES.has(displayName);
+}
+
+// HTML elements that should render in overlay layer
+const HTML_ELEMENTS = new Set(["div", "span", "button", "p", "a"]);
+
+// Separate children into SVG and overlay layers
+function separateChildren(children: ReactNode): {
+ svgChildren: React.ReactNode[];
+ overlayChildren: React.ReactNode[];
+} {
+ const childArray = React.Children.toArray(children);
+ const svgChildren: React.ReactNode[] = [];
+ const overlayChildren: React.ReactNode[] = [];
+
+ for (const child of childArray) {
+ if (!React.isValidElement(child)) {
+ svgChildren.push(child);
+ continue;
+ }
+
+ if (isChoroplethSvgChild(child.type)) {
+ svgChildren.push(child);
+ } else if (typeof child.type === "string") {
+ if (HTML_ELEMENTS.has(child.type)) {
+ overlayChildren.push(child);
+ } else {
+ svgChildren.push(child);
+ }
+ } else {
+ overlayChildren.push(child);
+ }
+ }
+
+ return { svgChildren, overlayChildren };
+}
+
+const DEFAULT_INITIAL_ZOOM: TransformMatrix = {
+ scaleX: 1,
+ scaleY: 1,
+ translateX: 0,
+ translateY: 0,
+ skewX: 0,
+ skewY: 0,
+};
+
+interface MercatorRenderProps {
+ // biome-ignore lint/suspicious/noExplicitAny: visx geo projection bundle
+ path: (geo: any) => string | null;
+ projection: (coords: [number, number]) => [number, number] | null | undefined;
+}
+
+interface ChoroplethMercatorContentProps {
+ mercator: MercatorRenderProps;
+ data: FeatureCollection;
+ width: number;
+ height: number;
+ innerWidth: number;
+ innerHeight: number;
+ margin: Margin;
+ animationDuration: number;
+ enterTransition?: Transition;
+ revealEpoch: number;
+ isLoaded: boolean;
+ containerRef: React.RefObject;
+ svgChildren: React.ReactNode[];
+ overlayChildren: React.ReactNode[];
+ zoom?: ZoomInstance;
+}
+
+const ChoroplethSvg = memo(function ChoroplethSvg({
+ height,
+ width,
+ svgChildren,
+ zoom,
+}: {
+ height: number;
+ width: number;
+ svgChildren: React.ReactNode[];
+ zoom?: ZoomInstance;
+}) {
+ const { setHoveredFeatureIndex, setTooltipData } = useChoroplethInteraction();
+
+ const handleMouseLeave = useCallback(() => {
+ setHoveredFeatureIndex(null);
+ setTooltipData(null);
+ }, [setHoveredFeatureIndex, setTooltipData]);
+
+ return (
+
+
+ {svgChildren}
+
+
+ );
+});
+
+const ChoroplethMercatorContent = memo(function ChoroplethMercatorContent({
+ mercator,
+ data,
+ width,
+ height,
+ innerWidth,
+ innerHeight,
+ margin,
+ animationDuration,
+ enterTransition,
+ revealEpoch,
+ isLoaded,
+ containerRef,
+ svgChildren,
+ overlayChildren,
+ zoom,
+}: ChoroplethMercatorContentProps) {
+ const featurePaths = data.features.map(
+ (feature) => mercator.path(feature) ?? null
+ ) as (string | null)[];
+
+ const pathGenerator = useCallback(
+ (feature: ChoroplethFeature) => mercator.path(feature) ?? undefined,
+ [mercator]
+ );
+
+ const rawPathGenerator = useCallback(
+ // biome-ignore lint/suspicious/noExplicitAny: GeoJSON types are complex
+ (geo: any) => mercator.path(geo),
+ [mercator]
+ );
+
+ const projectPoint = useCallback(
+ (coords: [number, number]): [number, number] | null => {
+ const projected = mercator.projection(coords);
+ if (!projected) {
+ return null;
+ }
+ return projected as [number, number];
+ },
+ [mercator]
+ );
+
+ const stableValue = useMemo(
+ () => ({
+ features: data.features,
+ featureCollection: data,
+ featurePaths,
+ pathGenerator,
+ rawPathGenerator,
+ projectPoint,
+ width,
+ height,
+ innerWidth,
+ innerHeight,
+ margin,
+ containerRef,
+ isLoaded,
+ animationDuration,
+ enterTransition,
+ revealEpoch,
+ }),
+ [
+ animationDuration,
+ containerRef,
+ data,
+ enterTransition,
+ featurePaths,
+ height,
+ innerHeight,
+ innerWidth,
+ isLoaded,
+ margin,
+ pathGenerator,
+ projectPoint,
+ rawPathGenerator,
+ revealEpoch,
+ width,
+ ]
+ );
+
+ return (
+
+
+
+
+
+ {overlayChildren}
+
+
+
+
+ );
+});
+
+function ChoroplethChartInner({
+ data,
+ width,
+ height,
+ margin,
+ animationDuration,
+ enterTransition,
+ revealSignature = "",
+ scale: scaleProp,
+ center,
+ translate: translateProp,
+ zoomEnabled,
+ zoomMin,
+ zoomMax,
+ initialZoom,
+ children,
+}: {
+ data: FeatureCollection;
+ width: number;
+ height: number;
+ margin: Margin;
+ animationDuration: number;
+ enterTransition?: Transition;
+ revealSignature?: string;
+ scale?: number;
+ center: [number, number];
+ translate?: [number, number];
+ zoomEnabled: boolean;
+ zoomMin: number;
+ zoomMax: number;
+ initialZoom: TransformMatrix;
+ children: ReactNode;
+}) {
+ const containerRef = useRef(null);
+ const [isLoaded, setIsLoaded] = useState(false);
+ const [revealEpoch, setRevealEpoch] = useState(0);
+
+ const innerWidth = width - margin.left - margin.right;
+ const innerHeight = height - margin.top - margin.bottom;
+
+ const scale = scaleProp ?? (innerWidth / 630) * 100;
+
+ const translate = translateProp ?? [
+ innerWidth / 2 + margin.left,
+ innerHeight / 2 + margin.top + 50,
+ ];
+
+ const { svgChildren, overlayChildren } = useMemo(
+ () => separateChildren(children),
+ [children]
+ );
+
+ // biome-ignore lint/correctness/useExhaustiveDependencies: revealSignature
+ useEffect(() => {
+ setRevealEpoch((n) => n + 1);
+ setIsLoaded(false);
+ const timeout = setTimeout(() => {
+ setIsLoaded(true);
+ }, animationDuration);
+ return () => clearTimeout(timeout);
+ }, [animationDuration, revealSignature]);
+
+ if (width < 10 || height < 10) {
+ return null;
+ }
+
+ const mercatorContentProps = {
+ animationDuration,
+ containerRef,
+ data,
+ enterTransition,
+ height,
+ innerHeight,
+ innerWidth,
+ isLoaded,
+ margin,
+ overlayChildren,
+ revealEpoch,
+ svgChildren,
+ width,
+ };
+
+ return (
+
+ {(mercator) => {
+ const content = (zoom?: ZoomInstance) => (
+
+ );
+
+ if (zoomEnabled) {
+ return (
+
+ height={height}
+ initialTransformMatrix={initialZoom}
+ scaleXMax={zoomMax}
+ scaleXMin={zoomMin}
+ scaleYMax={zoomMax}
+ scaleYMin={zoomMin}
+ wheelDelta={(event) => {
+ const zoomScale = event.deltaY > 0 ? 0.95 : 1.05;
+ return { scaleX: zoomScale, scaleY: zoomScale };
+ }}
+ width={width}
+ >
+ {(zoom) => content(zoom)}
+
+ );
+ }
+
+ return content();
+ }}
+
+ );
+}
+
+export function ChoroplethChart({
+ data,
+ margin: marginProp,
+ animationDuration = 800,
+ enterTransition,
+ revealSignature,
+ aspectRatio = "16 / 9",
+ scale,
+ center = [0, 20],
+ translate,
+ zoomEnabled = false,
+ zoomMin = 0.5,
+ zoomMax = 4,
+ initialZoom = DEFAULT_INITIAL_ZOOM,
+ className = "",
+ children,
+}: ChoroplethChartProps) {
+ const margin = { ...DEFAULT_MARGIN, ...marginProp };
+
+ return (
+
+
+ {({ width, height }) =>
+ width > 0 && height > 0 ? (
+
+ {children}
+
+ ) : null
+ }
+
+
+ );
+}
+
+ChoroplethChart.displayName = "ChoroplethChart";
+
+export default ChoroplethChart;
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/choropleth/choropleth-context.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/choropleth/choropleth-context.tsx
new file mode 100644
index 0000000..fe20112
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/choropleth/choropleth-context.tsx
@@ -0,0 +1,262 @@
+"use client";
+
+import type { ProvidedZoom, TransformMatrix } from "@visx/zoom";
+import type { Feature, FeatureCollection, Geometry } from "geojson";
+import type { Transition } from "motion/react";
+import {
+ createContext,
+ type Dispatch,
+ type RefObject,
+ type SetStateAction,
+ useContext,
+ useMemo,
+ useState,
+} from "react";
+
+// ZoomState from visx/zoom that includes isDragging
+interface ZoomState {
+ initialTransformMatrix: TransformMatrix;
+ transformMatrix: TransformMatrix;
+ isDragging: boolean;
+}
+
+// Combined type from visx Zoom children prop
+export type ZoomInstance = ProvidedZoom & ZoomState;
+
+// Zoom context to share zoom controls with child components
+interface ChoroplethZoomContextValue {
+ zoom: ZoomInstance | null;
+}
+
+export const ChoroplethZoomContext = createContext({
+ zoom: null,
+});
+
+export function useChoroplethZoom() {
+ return useContext(ChoroplethZoomContext);
+}
+
+export interface Margin {
+ top: number;
+ right: number;
+ bottom: number;
+ left: number;
+}
+
+export interface ChoroplethFeatureProperties {
+ name?: string;
+ id?: string | number;
+ [key: string]: unknown;
+}
+
+export type ChoroplethFeature = Feature;
+
+export interface ChoroplethTooltipData {
+ featureIndex: number;
+ x: number;
+ y: number;
+ feature: ChoroplethFeature;
+}
+
+export interface ChoroplethInteractionContextValue {
+ hoveredFeatureIndex: number | null;
+ setHoveredFeatureIndex: (index: number | null) => void;
+ tooltipData: ChoroplethTooltipData | null;
+ setTooltipData: Dispatch>;
+}
+
+export interface ChoroplethStableContextValue {
+ // Geo data
+ features: ChoroplethFeature[];
+ featureCollection: FeatureCollection;
+
+ /** Precomputed SVG path strings — one per feature index. */
+ featurePaths: readonly (string | null)[];
+
+ // Projection function (returns path string)
+ pathGenerator: (feature: ChoroplethFeature) => string | undefined;
+
+ // Raw path function for graticule (accepts any geo object)
+ // biome-ignore lint/suspicious/noExplicitAny: GeoJSON types are complex
+ rawPathGenerator: (geo: any) => string | null;
+
+ // Project geo coordinates to screen coordinates
+ projectPoint: (coords: [number, number]) => [number, number] | null;
+
+ // Dimensions
+ width: number;
+ height: number;
+ innerWidth: number;
+ innerHeight: number;
+ margin: Margin;
+
+ containerRef: RefObject;
+
+ // Animation
+ isLoaded: boolean;
+ animationDuration: number;
+ enterTransition?: Transition;
+ revealEpoch: number;
+}
+
+export type ChoroplethContextValue = ChoroplethStableContextValue &
+ ChoroplethInteractionContextValue;
+
+const ChoroplethStableContext =
+ createContext(null);
+const ChoroplethInteractionContext =
+ createContext(null);
+
+export function ChoroplethStableProvider({
+ children,
+ value,
+}: {
+ children: React.ReactNode;
+ value: ChoroplethStableContextValue;
+}) {
+ return (
+
+ {children}
+
+ );
+}
+
+export function ChoroplethInteractionShell({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
+ const [hoveredFeatureIndex, setHoveredFeatureIndex] = useState(
+ null
+ );
+ const [tooltipData, setTooltipData] = useState(
+ null
+ );
+
+ const interaction = useMemo(
+ () => ({
+ hoveredFeatureIndex,
+ setHoveredFeatureIndex,
+ tooltipData,
+ setTooltipData,
+ }),
+ [hoveredFeatureIndex, tooltipData]
+ );
+
+ return (
+
+ {children}
+
+ );
+}
+
+export function ChoroplethProvider({
+ children,
+ value,
+}: {
+ children: React.ReactNode;
+ value: ChoroplethContextValue;
+}) {
+ const stable = useMemo(
+ () => ({
+ features: value.features,
+ featureCollection: value.featureCollection,
+ featurePaths: value.featurePaths,
+ pathGenerator: value.pathGenerator,
+ rawPathGenerator: value.rawPathGenerator,
+ projectPoint: value.projectPoint,
+ width: value.width,
+ height: value.height,
+ innerWidth: value.innerWidth,
+ innerHeight: value.innerHeight,
+ margin: value.margin,
+ containerRef: value.containerRef,
+ isLoaded: value.isLoaded,
+ animationDuration: value.animationDuration,
+ enterTransition: value.enterTransition,
+ revealEpoch: value.revealEpoch,
+ }),
+ [
+ value.features,
+ value.featureCollection,
+ value.featurePaths,
+ value.pathGenerator,
+ value.rawPathGenerator,
+ value.projectPoint,
+ value.width,
+ value.height,
+ value.innerWidth,
+ value.innerHeight,
+ value.margin,
+ value.containerRef,
+ value.isLoaded,
+ value.animationDuration,
+ value.enterTransition,
+ value.revealEpoch,
+ ]
+ );
+
+ const interaction = useMemo(
+ () => ({
+ hoveredFeatureIndex: value.hoveredFeatureIndex,
+ setHoveredFeatureIndex: value.setHoveredFeatureIndex,
+ tooltipData: value.tooltipData,
+ setTooltipData: value.setTooltipData,
+ }),
+ [
+ value.hoveredFeatureIndex,
+ value.setHoveredFeatureIndex,
+ value.tooltipData,
+ value.setTooltipData,
+ ]
+ );
+
+ return (
+
+
+ {children}
+
+
+ );
+}
+
+export function useChoroplethStable(): ChoroplethStableContextValue {
+ const context = useContext(ChoroplethStableContext);
+ if (!context) {
+ throw new Error(
+ "useChoroplethStable must be used within a ChoroplethProvider"
+ );
+ }
+ return context;
+}
+
+export function useChoroplethInteraction(): ChoroplethInteractionContextValue {
+ const context = useContext(ChoroplethInteractionContext);
+ if (!context) {
+ throw new Error(
+ "useChoroplethInteraction must be used within a ChoroplethProvider"
+ );
+ }
+ return context;
+}
+
+export function useChoropleth(): ChoroplethContextValue {
+ return { ...useChoroplethStable(), ...useChoroplethInteraction() };
+}
+
+import { CHART_SCALE_VARS, chartScaleCssVars } from "../chart-scale";
+
+// CSS variables for choropleth theming
+export const choroplethCssVars = {
+ scale01: chartScaleCssVars.scale01,
+ scale02: chartScaleCssVars.scale02,
+ scale03: chartScaleCssVars.scale03,
+ scale04: chartScaleCssVars.scale04,
+ scale05: chartScaleCssVars.scale05,
+ patternColor: chartScaleCssVars.patternColor,
+ stroke: "var(--chart-grid)",
+ background: "var(--background)",
+};
+
+// Default colors array for cycling through features
+export const defaultChoroplethColors = [...CHART_SCALE_VARS];
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/choropleth/choropleth-feature.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/choropleth/choropleth-feature.tsx
new file mode 100644
index 0000000..1769401
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/choropleth/choropleth-feature.tsx
@@ -0,0 +1,339 @@
+"use client";
+
+import { geoCentroid } from "d3-geo";
+import { motion, useTransform } from "motion/react";
+import { memo, useCallback, useMemo } from "react";
+import { useEnterComplete } from "../use-enter-complete";
+import { useMountProgress } from "../use-mount-progress";
+import {
+ type ChoroplethFeature as ChoroplethFeatureType,
+ defaultChoroplethColors,
+ useChoroplethInteraction,
+ useChoroplethStable,
+} from "./choropleth-context";
+
+export interface ChoroplethFeatureProps {
+ fill?: string;
+ stroke?: string;
+ strokeWidth?: number;
+ fadedOpacity?: number;
+ getFeatureColor?: (feature: ChoroplethFeatureType, index: number) => string;
+ patterns?: React.ReactNode;
+ getFeaturePattern?: (
+ feature: ChoroplethFeatureType,
+ index: number
+ ) => string | null | undefined;
+}
+
+interface FeatureRecord {
+ index: number;
+ path: string;
+ fill: string;
+ feature: ChoroplethFeatureType;
+ centroid: { x: number; y: number } | null;
+}
+
+function resolveFeatureFill(
+ feature: ChoroplethFeatureType,
+ index: number,
+ fill: string | undefined,
+ getFeatureColor: ChoroplethFeatureProps["getFeatureColor"],
+ getFeaturePattern: ChoroplethFeatureProps["getFeaturePattern"]
+): string {
+ const patternId = getFeaturePattern?.(feature, index);
+ if (patternId) {
+ return `url(#${patternId})`;
+ }
+ if (fill) {
+ return fill;
+ }
+ if (getFeatureColor) {
+ return getFeatureColor(feature, index);
+ }
+ return (
+ defaultChoroplethColors[index % defaultChoroplethColors.length] ??
+ "var(--chart-1)"
+ );
+}
+
+const StaticFeatureLayer = memo(function StaticFeatureLayer({
+ records,
+ stroke,
+ strokeWidth,
+ baseOpacity,
+ dimOpacity,
+ hoveredIndex,
+ onFeatureEnter,
+ onFeatureLeave,
+}: {
+ records: FeatureRecord[];
+ stroke: string;
+ strokeWidth: number;
+ baseOpacity: number;
+ dimOpacity: number;
+ hoveredIndex: number | null;
+ onFeatureEnter: (record: FeatureRecord) => void;
+ onFeatureLeave: () => void;
+}) {
+ const isDimmed = hoveredIndex !== null;
+
+ if (!isDimmed) {
+ return (
+
+ {records.map((record) => (
+ // biome-ignore lint/a11y/noStaticElementInteractions: SVG path used as hover hitbox
+ onFeatureEnter(record)}
+ onMouseLeave={onFeatureLeave}
+ stroke={stroke}
+ strokeWidth={strokeWidth}
+ />
+ ))}
+
+ );
+ }
+
+ const highlighted = records.find((record) => record.index === hoveredIndex);
+
+ return (
+ <>
+
+ {records
+ .filter((record) => record.index !== hoveredIndex)
+ .map((record) => (
+ // biome-ignore lint/a11y/noStaticElementInteractions: SVG path used as hover hitbox
+ onFeatureEnter(record)}
+ onMouseLeave={onFeatureLeave}
+ stroke={stroke}
+ strokeWidth={strokeWidth}
+ />
+ ))}
+
+ {highlighted ? (
+ // biome-ignore lint/a11y/noStaticElementInteractions: SVG path used as hover hitbox
+ onFeatureEnter(highlighted)}
+ onMouseLeave={onFeatureLeave}
+ opacity={1}
+ stroke={stroke}
+ strokeWidth={strokeWidth}
+ style={{ transition: "opacity 0.18s ease-out" }}
+ />
+ ) : null}
+ >
+ );
+});
+
+const EnterFeatureLayer = memo(function EnterFeatureLayer({
+ records,
+ stroke,
+ strokeWidth,
+ baseOpacity,
+ dimOpacity,
+ hoveredIndex,
+ onFeatureEnter,
+ onFeatureLeave,
+ revealEpoch,
+}: {
+ records: FeatureRecord[];
+ stroke: string;
+ strokeWidth: number;
+ baseOpacity: number;
+ dimOpacity: number;
+ hoveredIndex: number | null;
+ onFeatureEnter: (record: FeatureRecord) => void;
+ onFeatureLeave: () => void;
+ revealEpoch: number;
+}) {
+ const { enterTransition, animationDuration } = useChoroplethStable();
+ const mountProgress = useMountProgress(
+ enterTransition,
+ 0,
+ `choropleth-layer-${revealEpoch}`
+ );
+ const enterComplete = useEnterComplete(mountProgress);
+ const layerOpacity = useTransform(mountProgress, (t) => t * baseOpacity);
+
+ if (enterComplete) {
+ return (
+
+ );
+ }
+
+ return (
+
+ {records.map((record) => (
+ // biome-ignore lint/a11y/noStaticElementInteractions: SVG path used as hover hitbox
+ onFeatureEnter(record)}
+ onMouseLeave={onFeatureLeave}
+ stroke={stroke}
+ strokeWidth={strokeWidth}
+ />
+ ))}
+
+ );
+});
+
+export const ChoroplethFeature = memo(function ChoroplethFeature({
+ fill,
+ stroke = "var(--background)",
+ strokeWidth = 0.5,
+ fadedOpacity = 0.4,
+ getFeatureColor,
+ patterns,
+ getFeaturePattern,
+}: ChoroplethFeatureProps) {
+ const {
+ features,
+ featurePaths,
+ pathGenerator,
+ projectPoint,
+ isLoaded,
+ revealEpoch,
+ width,
+ height,
+ } = useChoroplethStable();
+ const { hoveredFeatureIndex, setHoveredFeatureIndex, setTooltipData } =
+ useChoroplethInteraction();
+
+ const featureCentroids = useMemo(() => {
+ return features.map((feature) => {
+ try {
+ const centroid = geoCentroid(feature);
+ if (
+ centroid &&
+ !Number.isNaN(centroid[0]) &&
+ !Number.isNaN(centroid[1])
+ ) {
+ const projected = projectPoint(centroid as [number, number]);
+ if (projected) {
+ const padding = 60;
+ return {
+ x: Math.max(padding, Math.min(width - padding, projected[0])),
+ y: Math.max(padding, Math.min(height - padding, projected[1])),
+ };
+ }
+ }
+ } catch {
+ // Some geometries may not have valid centroids
+ }
+ return null;
+ });
+ }, [features, projectPoint, width, height]);
+
+ const records = useMemo(() => {
+ const items: FeatureRecord[] = [];
+ for (let index = 0; index < features.length; index++) {
+ const feature = features[index];
+ if (!feature) {
+ continue;
+ }
+
+ const path = featurePaths[index] ?? pathGenerator(feature);
+ if (!path) {
+ continue;
+ }
+
+ items.push({
+ index,
+ path,
+ fill: resolveFeatureFill(
+ feature,
+ index,
+ fill,
+ getFeatureColor,
+ getFeaturePattern
+ ),
+ feature,
+ centroid: featureCentroids[index] ?? null,
+ });
+ }
+ return items;
+ }, [
+ featureCentroids,
+ featurePaths,
+ features,
+ fill,
+ getFeatureColor,
+ getFeaturePattern,
+ pathGenerator,
+ ]);
+
+ const handleFeatureEnter = useCallback(
+ (record: FeatureRecord) => {
+ setHoveredFeatureIndex(record.index);
+ setTooltipData({
+ featureIndex: record.index,
+ x: record.centroid?.x ?? width / 2,
+ y: record.centroid?.y ?? height / 2,
+ feature: record.feature,
+ });
+ },
+ [height, setHoveredFeatureIndex, setTooltipData, width]
+ );
+
+ const handleFeatureLeave = useCallback(() => {
+ setHoveredFeatureIndex(null);
+ setTooltipData(null);
+ }, [setHoveredFeatureIndex, setTooltipData]);
+
+ const layerProps = {
+ baseOpacity: 0.85,
+ dimOpacity: fadedOpacity,
+ hoveredIndex: hoveredFeatureIndex,
+ onFeatureEnter: handleFeatureEnter,
+ onFeatureLeave: handleFeatureLeave,
+ records,
+ stroke,
+ strokeWidth,
+ };
+
+ return (
+
+ {patterns ? {patterns} : null}
+ {isLoaded ? (
+
+ ) : (
+
+ )}
+
+ );
+});
+
+ChoroplethFeature.displayName = "ChoroplethFeature";
+
+export default ChoroplethFeature;
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/choropleth/choropleth-graticule.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/choropleth/choropleth-graticule.tsx
new file mode 100644
index 0000000..3feae2e
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/choropleth/choropleth-graticule.tsx
@@ -0,0 +1,35 @@
+"use client";
+
+import { Graticule } from "@visx/geo";
+import { memo } from "react";
+import { useChoroplethStable } from "./choropleth-context";
+
+export interface ChoroplethGraticuleProps {
+ /** Stroke color for graticule lines. Default: rgba(255,255,255,0.1) */
+ stroke?: string;
+ /** Stroke width for graticule lines. Default: 0.5 */
+ strokeWidth?: number;
+ /** Step intervals for graticule lines [longitude, latitude] in degrees. Default: [10, 10] */
+ step?: [number, number];
+}
+
+export const ChoroplethGraticule = memo(function ChoroplethGraticule({
+ stroke = "rgba(255,255,255,0.1)",
+ strokeWidth = 0.5,
+ step,
+}: ChoroplethGraticuleProps) {
+ const { rawPathGenerator } = useChoroplethStable();
+
+ return (
+ rawPathGenerator(g) || ""}
+ step={step}
+ stroke={stroke}
+ strokeWidth={strokeWidth}
+ />
+ );
+});
+
+ChoroplethGraticule.displayName = "ChoroplethGraticule";
+
+export default ChoroplethGraticule;
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/choropleth/choropleth-tooltip.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/choropleth/choropleth-tooltip.tsx
new file mode 100644
index 0000000..d0dcfea
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/choropleth/choropleth-tooltip.tsx
@@ -0,0 +1,131 @@
+"use client";
+
+import { intFmt } from "../chart-formatters";
+import { TooltipBox } from "../tooltip/tooltip-box";
+import { TooltipContent, type TooltipRow } from "../tooltip/tooltip-content";
+import {
+ type ChoroplethFeature,
+ useChoroplethInteraction,
+ useChoroplethStable,
+ useChoroplethZoom,
+} from "./choropleth-context";
+
+export interface ChoroplethTooltipProps {
+ /** Custom content renderer for feature tooltips */
+ content?: (props: {
+ feature: ChoroplethFeature;
+ index: number;
+ }) => React.ReactNode;
+ /** Value formatter function */
+ formatValue?: (value: number) => string;
+ /** Get the display name for a feature. Default: uses feature.properties.name */
+ getFeatureName?: (feature: ChoroplethFeature, index: number) => string;
+ /** Get the value for a feature (for display in tooltip) */
+ getFeatureValue?: (
+ feature: ChoroplethFeature,
+ index: number
+ ) => number | undefined;
+ /** Label for the value row. Default: "Value" */
+ valueLabel?: string;
+ /** Custom class name */
+ className?: string;
+ /** Inline styles for the tooltip panel (background, blur, etc.). */
+ panelStyle?: React.CSSProperties;
+ /**
+ * Tooltip panel background color (CSS variable or color value).
+ * Default: `var(--chart-tooltip-background)`.
+ */
+ backgroundColor?: string;
+}
+
+export function ChoroplethTooltip({
+ content,
+ formatValue = intFmt,
+ getFeatureName,
+ getFeatureValue,
+ valueLabel = "Value",
+ className = "",
+ panelStyle,
+ backgroundColor,
+}: ChoroplethTooltipProps) {
+ const { containerRef, width, height, features } = useChoroplethStable();
+ const { tooltipData } = useChoroplethInteraction();
+ const { zoom } = useChoroplethZoom();
+
+ if (!tooltipData) {
+ return null;
+ }
+
+ // Apply zoom transform to centroid position
+ let x = tooltipData.x;
+ let y = tooltipData.y;
+
+ if (zoom) {
+ // Apply the zoom transform matrix to the tooltip position
+ const transformed = zoom.applyToPoint({ x, y });
+ x = transformed.x;
+ y = transformed.y;
+ }
+
+ const feature = features[tooltipData.featureIndex];
+ if (!feature) {
+ return null;
+ }
+
+ // Get feature name
+ const featureName = getFeatureName
+ ? getFeatureName(feature, tooltipData.featureIndex)
+ : (feature.properties?.name ?? `Feature ${tooltipData.featureIndex}`);
+
+ // Custom content
+ if (content) {
+ return (
+
+ {content({ feature, index: tooltipData.featureIndex })}
+
+ );
+ }
+
+ // Default tooltip with optional value
+ const value = getFeatureValue?.(feature, tooltipData.featureIndex);
+ const rows: TooltipRow[] =
+ value === undefined
+ ? []
+ : [
+ {
+ color: "var(--chart-1)",
+ label: valueLabel,
+ value: formatValue(value),
+ },
+ ];
+
+ return (
+
+
+
+ );
+}
+
+ChoroplethTooltip.displayName = "ChoroplethTooltip";
+
+export default ChoroplethTooltip;
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/choropleth/index.ts b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/choropleth/index.ts
new file mode 100644
index 0000000..f0e47e1
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/choropleth/index.ts
@@ -0,0 +1,26 @@
+export type { TransformMatrix } from "@visx/zoom";
+export { ChoroplethChart, type ChoroplethChartProps } from "./choropleth-chart";
+export {
+ type ChoroplethContextValue,
+ type ChoroplethFeature,
+ type ChoroplethFeatureProperties,
+ ChoroplethProvider,
+ type ChoroplethTooltipData,
+ choroplethCssVars,
+ defaultChoroplethColors,
+ type Margin,
+ useChoropleth,
+ useChoroplethZoom,
+} from "./choropleth-context";
+export {
+ ChoroplethFeature as ChoroplethFeatureComponent,
+ type ChoroplethFeatureProps,
+} from "./choropleth-feature";
+export {
+ ChoroplethGraticule,
+ type ChoroplethGraticuleProps,
+} from "./choropleth-graticule";
+export {
+ ChoroplethTooltip,
+ type ChoroplethTooltipProps,
+} from "./choropleth-tooltip";
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/composed-chart.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/composed-chart.tsx
new file mode 100644
index 0000000..132d70c
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/composed-chart.tsx
@@ -0,0 +1,338 @@
+"use client";
+
+import { ParentSize } from "@visx/responsive";
+import type { Transition } from "motion/react";
+import {
+ Children,
+ isValidElement,
+ type ReactElement,
+ type ReactNode,
+ useMemo,
+ useRef,
+} from "react";
+import { cn } from "@/lib/utils";
+import { Area, type AreaProps } from "./area";
+import type { LineConfig, Margin } from "./chart-context";
+import type { ChartPhase } from "./chart-phase";
+import { Line, type LineProps } from "./line";
+import { SeriesBar, type SeriesBarProps } from "./series-bar";
+import { TimeSeriesChartInner } from "./time-series-chart-shell";
+
+export interface ComposedChartProps {
+ /** Data array — each row typically has a date and multiple numeric series */
+ data: Record[];
+ /** Key for the x-axis (time). Default: "date" */
+ xDataKey?: string;
+ margin?: Partial;
+ animationDuration?: number;
+ animationEasing?: string;
+ enterTransition?: Transition;
+ /** Signature of motion URL state — triggers reveal replay when it changes. */
+ revealSignature?: string;
+ aspectRatio?: string;
+ className?: string;
+ children: ReactNode;
+ /** Target bar width in px (Recharts-style `barSize`). */
+ barSize?: number;
+ /** Maximum bar width in px (`maxBarSize`). */
+ maxBarSize?: number;
+ /** Gap between grouped `SeriesBar` series in px. Default: 4 */
+ barGap?: number;
+ /** Stack `SeriesBar` segments in child order at each x (line/area are not stacked). */
+ stacked?: boolean;
+ /** Gap in px between stacked segments. Default: 0 */
+ stackGap?: number;
+ onPhaseChange?: (phase: ChartPhase) => void;
+}
+
+const DEFAULT_MARGIN: Margin = { top: 40, right: 40, bottom: 40, left: 40 };
+
+function getChildComponentName(child: ReactElement): string {
+ const childType = child.type as { displayName?: string; name?: string };
+ return typeof child.type === "function"
+ ? childType.displayName || childType.name || ""
+ : "";
+}
+
+function upsertLineConfig(lines: LineConfig[], config: LineConfig): void {
+ const index = lines.findIndex((line) => line.dataKey === config.dataKey);
+ if (index === -1) {
+ lines.push(config);
+ return;
+ }
+ // Area+Line pairs share a dataKey — keep the later config (Line over Area).
+ lines[index] = config;
+}
+
+function tryAppendSeriesBar(
+ child: ReactElement,
+ lines: LineConfig[],
+ barDataKeys: string[]
+): boolean {
+ const name = getChildComponentName(child);
+ if (!(child.type === SeriesBar || name === "SeriesBar")) {
+ return false;
+ }
+ const props = child.props as SeriesBarProps;
+ if (!props.dataKey) {
+ return true;
+ }
+ barDataKeys.push(props.dataKey);
+ upsertLineConfig(lines, {
+ dataKey: props.dataKey,
+ stroke: props.stroke || props.fill || "var(--chart-line-primary)",
+ strokeWidth: 0,
+ });
+ return true;
+}
+
+function tryAppendLine(child: ReactElement, lines: LineConfig[]): boolean {
+ const name = getChildComponentName(child);
+ if (!(child.type === Line || name === "Line")) {
+ return false;
+ }
+ const props = child.props as LineProps;
+ if (props.dataKey) {
+ upsertLineConfig(lines, {
+ dataKey: props.dataKey,
+ stroke: props.stroke || "var(--chart-line-primary)",
+ strokeWidth: props.strokeWidth ?? 2.5,
+ yAxisId: props.yAxisId,
+ });
+ }
+ return true;
+}
+
+function tryAppendArea(child: ReactElement, lines: LineConfig[]): boolean {
+ const name = getChildComponentName(child);
+ if (!(child.type === Area || name === "Area")) {
+ return false;
+ }
+ const props = child.props as AreaProps;
+ if (props.dataKey) {
+ upsertLineConfig(lines, {
+ dataKey: props.dataKey,
+ stroke: props.stroke || props.fill || "var(--chart-line-primary)",
+ strokeWidth: props.strokeWidth ?? 2,
+ yAxisId: props.yAxisId,
+ });
+ }
+ return true;
+}
+
+function extractComposedSeries(children: ReactNode): {
+ lines: LineConfig[];
+ barDataKeys: string[];
+} {
+ const lines: LineConfig[] = [];
+ const barDataKeys: string[] = [];
+
+ Children.forEach(children, (child) => {
+ if (!isValidElement(child)) {
+ return;
+ }
+ if (tryAppendSeriesBar(child, lines, barDataKeys)) {
+ return;
+ }
+ if (tryAppendLine(child, lines)) {
+ return;
+ }
+ tryAppendArea(child, lines);
+ });
+
+ return { lines, barDataKeys };
+}
+
+function computeComposedYScaleDomainMax(
+ data: Record[],
+ lines: LineConfig[],
+ barDataKeys: string[]
+): number | undefined {
+ const barSet = new Set(barDataKeys);
+ let max = 0;
+ for (const d of data) {
+ let barSum = 0;
+ for (const k of barDataKeys) {
+ const v = d[k];
+ if (typeof v === "number") {
+ barSum += v;
+ }
+ }
+ let rowMaxOther = 0;
+ for (const line of lines) {
+ if (barSet.has(line.dataKey)) {
+ continue;
+ }
+ const v = d[line.dataKey];
+ if (typeof v === "number") {
+ rowMaxOther = Math.max(rowMaxOther, v);
+ }
+ }
+ max = Math.max(max, barSum, rowMaxOther);
+ }
+ return max > 0 ? max : undefined;
+}
+
+interface ChartInnerProps {
+ width: number;
+ height: number;
+ data: Record[];
+ xDataKey: string;
+ margin: Margin;
+ animationDuration: number;
+ animationEasing?: string;
+ enterTransition?: Transition;
+ revealSignature?: string;
+ children: ReactNode;
+ containerRef: React.RefObject;
+ barSize?: number;
+ maxBarSize?: number;
+ barGap?: number;
+ stacked?: boolean;
+ stackGap?: number;
+ onPhaseChange?: (phase: ChartPhase) => void;
+}
+
+function ChartInner({
+ width,
+ height,
+ data,
+ xDataKey,
+ margin,
+ animationDuration,
+ animationEasing,
+ enterTransition,
+ revealSignature,
+ children,
+ containerRef,
+ barSize,
+ maxBarSize,
+ barGap,
+ stacked = false,
+ stackGap = 0,
+ onPhaseChange,
+}: ChartInnerProps) {
+ const { lines, barDataKeys } = useMemo(
+ () => extractComposedSeries(children),
+ [children]
+ );
+
+ const composedStackOffsets = useMemo(() => {
+ if (!(stacked && barDataKeys.length > 0)) {
+ return undefined;
+ }
+ const offsets = new Map>();
+ for (let i = 0; i < data.length; i++) {
+ const d = data[i];
+ if (!d) {
+ continue;
+ }
+ const pointOffsets = new Map();
+ let cumulative = 0;
+ for (const key of barDataKeys) {
+ pointOffsets.set(key, cumulative);
+ const v = d[key];
+ if (typeof v === "number") {
+ cumulative += v;
+ }
+ }
+ offsets.set(i, pointOffsets);
+ }
+ return offsets;
+ }, [data, barDataKeys, stacked]);
+
+ const yScaleDomainMax = useMemo(
+ () =>
+ stacked && barDataKeys.length > 0
+ ? computeComposedYScaleDomainMax(data, lines, barDataKeys)
+ : undefined,
+ [data, lines, barDataKeys, stacked]
+ );
+
+ return (
+ 0 ? barDataKeys : undefined}
+ composedBarGap={barGap}
+ composedBarSize={barSize}
+ composedMaxBarSize={maxBarSize}
+ composedStacked={stacked}
+ composedStackGap={stackGap}
+ composedStackOffsets={composedStackOffsets}
+ containerRef={containerRef}
+ data={data}
+ enterTransition={enterTransition}
+ height={height}
+ lines={lines}
+ margin={margin}
+ onPhaseChange={onPhaseChange}
+ revealSignature={revealSignature}
+ width={width}
+ xDataKey={xDataKey}
+ yScaleDomainMax={yScaleDomainMax}
+ >
+ {children}
+
+ );
+}
+
+export function ComposedChart({
+ data,
+ xDataKey = "date",
+ margin: marginProp,
+ animationDuration = 1100,
+ animationEasing,
+ enterTransition,
+ revealSignature,
+ aspectRatio = "2 / 1",
+ className = "",
+ children,
+ barSize,
+ maxBarSize,
+ barGap = 4,
+ stacked = false,
+ stackGap = 0,
+ onPhaseChange,
+}: ComposedChartProps) {
+ const containerRef = useRef(null);
+ const margin = { ...DEFAULT_MARGIN, ...marginProp };
+
+ return (
+
+
+ {({ width, height }) => (
+
+ {children}
+
+ )}
+
+
+ );
+}
+
+ComposedChart.displayName = "ComposedChart";
+
+export default ComposedChart;
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/dash-tail-stroke.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/dash-tail-stroke.tsx
new file mode 100644
index 0000000..becbca5
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/dash-tail-stroke.tsx
@@ -0,0 +1,75 @@
+"use client";
+
+import { useId } from "react";
+
+export interface DashTailStrokeProps {
+ /** SVG path `d` for the full series (single curved path). */
+ pathD: string | null;
+ /** Total length of `pathD` in user units. */
+ pathLength: number;
+ /** Path length at which the dashed tail begins. */
+ dashStartLength: number;
+ /** X coordinate (chart inner space) where the tail clip begins. */
+ dashStartX: number;
+ innerWidth: number;
+ innerHeight: number;
+ /** Stroke paint — solid color or gradient url. */
+ stroke: string;
+ strokeWidth: number;
+ dashArray: string;
+}
+
+export function DashTailStroke({
+ pathD,
+ pathLength,
+ dashStartLength,
+ dashStartX,
+ innerWidth,
+ innerHeight,
+ stroke,
+ strokeWidth,
+ dashArray,
+}: DashTailStrokeProps) {
+ const clipPathId = useId().replace(/:/g, "");
+
+ if (!pathD || pathLength <= 0 || dashStartLength >= pathLength) {
+ return null;
+ }
+
+ const pad = strokeWidth * 2;
+ const tailWidth = Math.max(0, innerWidth - dashStartX + pad);
+
+ return (
+ <>
+
+
+
+
+
+ {/* Solid head — same curved path, gradient/fade preserved */}
+
+ {/* Dashed tail — clipped to x ≥ dashStartX so dashes follow the curve */}
+
+ >
+ );
+}
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/decimate-time-series.ts b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/decimate-time-series.ts
new file mode 100644
index 0000000..afd77b4
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/decimate-time-series.ts
@@ -0,0 +1,140 @@
+/**
+ * Largest-Triangle-Three-Buckets downsampling for time-series SVG paths.
+ * Keeps first/last points and picks visually significant points per bucket.
+ */
+export function decimateTimeSeries>(
+ data: T[],
+ maxPoints: number,
+ valueKeys: string[] = []
+): T[] {
+ const len = data.length;
+ if (maxPoints >= len || maxPoints < 3) {
+ return data;
+ }
+
+ const getY = (point: T, index: number): number => {
+ if (valueKeys.length === 0) {
+ for (const val of Object.values(point)) {
+ if (typeof val === "number") {
+ return val;
+ }
+ }
+ return index;
+ }
+
+ let sum = 0;
+ let count = 0;
+ for (const key of valueKeys) {
+ const val = point[key];
+ if (typeof val === "number") {
+ sum += val;
+ count++;
+ }
+ }
+ return count > 0 ? sum / count : index;
+ };
+
+ const sampled: T[] = [data[0] as T];
+ const bucketSize = (len - 2) / (maxPoints - 2);
+ let previousIndex = 0;
+
+ for (let i = 0; i < maxPoints - 2; i++) {
+ const rangeStart = Math.floor((i + 1) * bucketSize) + 1;
+ const rangeEnd = Math.min(Math.floor((i + 2) * bucketSize) + 1, len - 1);
+
+ const nextRangeStart = Math.floor((i + 2) * bucketSize) + 1;
+ const nextRangeEnd = Math.min(Math.floor((i + 3) * bucketSize) + 1, len);
+ const nextCount = Math.max(0, nextRangeEnd - nextRangeStart);
+
+ let avgX = len - 1;
+ let avgY = getY(data[len - 1] as T, len - 1);
+ if (nextCount > 0) {
+ avgX = 0;
+ avgY = 0;
+ for (let j = nextRangeStart; j < nextRangeEnd; j++) {
+ avgX += j;
+ avgY += getY(data[j] as T, j);
+ }
+ avgX /= nextCount;
+ avgY /= nextCount;
+ }
+
+ const pointA = data[previousIndex] as T;
+ const ax = previousIndex;
+ const ay = getY(pointA, previousIndex);
+
+ let maxArea = -1;
+ let maxIndex = rangeStart;
+
+ for (let j = rangeStart; j < rangeEnd; j++) {
+ const area =
+ Math.abs(
+ (ax - avgX) * (getY(data[j] as T, j) - ay) - (ax - j) * (avgY - ay)
+ ) * 0.5;
+ if (area > maxArea) {
+ maxArea = area;
+ maxIndex = j;
+ }
+ }
+
+ sampled.push(data[maxIndex] as T);
+ previousIndex = maxIndex;
+ }
+
+ sampled.push(data[len - 1] as T);
+ return sampled;
+}
+
+/** ~1.5 points per pixel — enough for crisp curves without over-drawing. */
+export function maxRenderPointsForWidth(innerWidth: number): number {
+ return Math.max(64, Math.ceil(innerWidth * 1.5));
+}
+
+/** Bucket OHLC rows into fewer candles while preserving high/low extremes. */
+export function decimateOhlcData>(
+ data: T[],
+ maxPoints: number
+): T[] {
+ const len = data.length;
+ if (maxPoints >= len || maxPoints < 2) {
+ return data;
+ }
+
+ const bucketSize = len / maxPoints;
+ const sampled: T[] = [];
+
+ for (let i = 0; i < maxPoints; i++) {
+ const start = Math.floor(i * bucketSize);
+ const end = Math.min(len, Math.floor((i + 1) * bucketSize));
+ if (start >= end) {
+ continue;
+ }
+
+ const bucket = data.slice(start, end);
+ const first = bucket[0] as T;
+ const last = bucket.at(-1) as T;
+
+ let high = Number.NEGATIVE_INFINITY;
+ let low = Number.POSITIVE_INFINITY;
+ for (const row of bucket) {
+ const rowHigh = row.high;
+ const rowLow = row.low;
+ if (typeof rowHigh === "number" && rowHigh > high) {
+ high = rowHigh;
+ }
+ if (typeof rowLow === "number" && rowLow < low) {
+ low = rowLow;
+ }
+ }
+
+ sampled.push({
+ ...last,
+ open: first.open,
+ high: Number.isFinite(high) ? high : last.high,
+ low: Number.isFinite(low) ? low : last.low,
+ close: last.close,
+ } as T);
+ }
+
+ return sampled;
+}
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/fade-edges.ts b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/fade-edges.ts
new file mode 100644
index 0000000..8825a8e
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/fade-edges.ts
@@ -0,0 +1,58 @@
+/**
+ * Which side(s) of a series should fade to transparent at the chart edges.
+ * - `true` → fade both edges (default for ``)
+ * - `false` → no fade (default for ` `)
+ * - `"left"` / `"right"` → fade only that side
+ */
+export type FadeEdges = boolean | "left" | "right";
+
+export interface FadeSides {
+ /** Whether the left edge should fade out. */
+ left: boolean;
+ /** Whether the right edge should fade out. */
+ right: boolean;
+ /** True if either side fades — use to gate gradient/mask defs. */
+ any: boolean;
+}
+
+export function resolveFadeSides(fade: FadeEdges): FadeSides {
+ if (fade === false) {
+ return { left: false, right: false, any: false };
+ }
+ if (fade === "left") {
+ return { left: true, right: false, any: true };
+ }
+ if (fade === "right") {
+ return { left: false, right: true, any: true };
+ }
+ return { left: true, right: true, any: true };
+}
+
+export interface FadeGradientStop {
+ offset: string;
+ opacity: number;
+}
+
+/**
+ * Stops for a horizontal fade gradient with opacity 0 at the faded side(s)
+ * and opacity 1 in the middle. Matches the historic 0/15/85/100 pattern.
+ */
+export function fadeGradientStops(sides: FadeSides): FadeGradientStop[] {
+ return [
+ { offset: "0%", opacity: sides.left ? 0 : 1 },
+ { offset: "15%", opacity: 1 },
+ { offset: "85%", opacity: 1 },
+ { offset: "100%", opacity: sides.right ? 0 : 1 },
+ ];
+}
+
+/** Horizontal fade gradient pinned to the chart viewport (not the series path bounds). */
+export function viewportFadeGradientAttrs(innerWidth: number) {
+ return {
+ gradientUnits: "userSpaceOnUse" as const,
+ x1: 0,
+ x2: innerWidth,
+ y1: 0,
+ y2: 0,
+ };
+}
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/filter-data-by-x-domain.ts b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/filter-data-by-x-domain.ts
new file mode 100644
index 0000000..6c72279
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/filter-data-by-x-domain.ts
@@ -0,0 +1,59 @@
+export function filterDataByXDomain(
+ data: Record[],
+ xDomain: [Date, Date],
+ xAccessor: (d: Record) => Date
+): Record[] {
+ const start = xDomain[0].getTime();
+ const end = xDomain[1].getTime();
+ const minTime = Math.min(start, end);
+ const maxTime = Math.max(start, end);
+
+ return data.filter((d) => {
+ const time = xAccessor(d).getTime();
+ return time >= minTime && time <= maxTime;
+ });
+}
+
+export function resolveDataXExtent(
+ data: Record[],
+ xAccessor: (d: Record) => Date
+): [Date, Date] | null {
+ if (data.length === 0) {
+ return null;
+ }
+
+ let minTime = Number.POSITIVE_INFINITY;
+ let maxTime = Number.NEGATIVE_INFINITY;
+
+ for (const point of data) {
+ const time = xAccessor(point).getTime();
+ if (time < minTime) {
+ minTime = time;
+ }
+ if (time > maxTime) {
+ maxTime = time;
+ }
+ }
+
+ if (minTime === Number.POSITIVE_INFINITY) {
+ return null;
+ }
+
+ return [new Date(minTime), new Date(maxTime)];
+}
+
+/** Brush track extent — optionally extends past the last data row (e.g. projections). */
+export function resolveBrushTrackXExtent(
+ data: Record[],
+ xAccessor: (d: Record) => Date,
+ xExtentMax?: Date
+): [Date, Date] | null {
+ const extent = resolveDataXExtent(data, xAccessor);
+ if (!extent) {
+ return null;
+ }
+ if (!xExtentMax || xExtentMax.getTime() <= extent[1].getTime()) {
+ return extent;
+ }
+ return [extent[0], xExtentMax];
+}
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/funnel-chart.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/funnel-chart.tsx
new file mode 100644
index 0000000..be60c4e
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/funnel-chart.tsx
@@ -0,0 +1,1041 @@
+"use client";
+
+import type { Transition } from "motion/react";
+import { motion, useTransform } from "motion/react";
+import {
+ type CSSProperties,
+ type ReactNode,
+ useCallback,
+ useEffect,
+ useRef,
+ useState,
+} from "react";
+import { cn } from "@/lib/utils";
+import { useEnterComplete } from "./use-enter-complete";
+import { useMountProgress } from "./use-mount-progress";
+
+// ─── Public types ───────────────────────────────────────────────────
+
+export interface FunnelGradientStop {
+ offset: string | number;
+ color: string;
+}
+
+export interface FunnelStage {
+ label: string;
+ value: number;
+ displayValue?: string;
+ /** Override the chart-level color for this segment */
+ color?: string;
+ /**
+ * Apply a linear gradient to this segment.
+ * Provide an array of color stops, e.g. `[{ offset: "0%", color: "#8B5CF6" }, { offset: "100%", color: "#3B82F6" }]`.
+ * When set, this takes priority over the segment and chart-level `color` for the innermost ring.
+ * Outer halo rings use the first stop color as their solid color.
+ */
+ gradient?: FunnelGradientStop[];
+}
+
+export interface FunnelChartProps {
+ data: FunnelStage[];
+ orientation?: "horizontal" | "vertical";
+ color?: string;
+ layers?: number;
+ className?: string;
+ style?: CSSProperties;
+ showPercentage?: boolean;
+ showValues?: boolean;
+ showLabels?: boolean;
+ /** Controlled hover state — index of the hovered segment */
+ hoveredIndex?: number | null;
+ /** Callback when hover state changes */
+ onHoverChange?: (index: number | null) => void;
+ formatPercentage?: (pct: number) => string;
+ formatValue?: (value: number) => string;
+ /** Stagger delay between segments in seconds. Default 0.12 */
+ staggerDelay?: number;
+ /** Framer Motion transition for segment enter animation */
+ enterTransition?: Transition;
+ /** Gap between segments in pixels. Default 4 */
+ gap?: number;
+ /**
+ * Render a visx pattern definition. Receives a unique `id` string per segment
+ * and the resolved `color`. Return a `` (or any visx pattern)
+ * inside an SVG ``. The component will use `fill="url(#id)"` on the
+ * innermost ring while keeping outer halo rings as solid color.
+ */
+ renderPattern?: (id: string, color: string) => ReactNode;
+ /** Edge style for the funnel segments. Default "curved" */
+ edges?: "curved" | "straight";
+ /**
+ * Controls how segment labels (value, percentage, stage name) are arranged.
+ * - "spread": Value/percentage/label are spread apart (top/center/bottom for horizontal,
+ * left/center/right for vertical). This is the default.
+ * - "grouped": All label items stack together in a tight group.
+ *
+ * When "grouped", use `labelOrientation` and `labelAlign` for full control.
+ */
+ labelLayout?: "spread" | "grouped";
+ /**
+ * Stack direction of the label group. Only applies when `labelLayout="grouped"`.
+ * - "vertical": Items stack top-to-bottom. Default for horizontal funnels.
+ * - "horizontal": Items stack left-to-right. Default for vertical funnels.
+ */
+ labelOrientation?: "vertical" | "horizontal";
+ /**
+ * Where the label group sits within the segment cell.
+ * - "center" (default), "start", "end"
+ * For horizontal funnel: start=top, end=bottom.
+ * For vertical funnel: start=left, end=right.
+ */
+ labelAlign?: "center" | "start" | "end";
+ /** Grid configuration. Pass `true` for default bands + lines, or an object for fine control. */
+ grid?:
+ | boolean
+ | {
+ /** Show alternating background bands behind each segment. Default true */
+ bands?: boolean;
+ /** Color of the background bands. Default "var(--color-muted)" */
+ bandColor?: string;
+ /** Show grid lines at each gap between segments. Default true */
+ lines?: boolean;
+ /** Color of the grid lines. Default "var(--chart-grid)" */
+ lineColor?: string;
+ /** Opacity of the grid lines. Default 1 */
+ lineOpacity?: number;
+ /** Width of the grid lines in pixels. Default 1 */
+ lineWidth?: number;
+ };
+}
+
+// ─── Defaults ───────────────────────────────────────────────────────
+
+import { intFmt } from "./chart-formatters";
+
+const fmtPct = (p: number) => `${Math.round(p)}%`;
+const fmtVal = intFmt;
+
+// ─── SVG helpers ────────────────────────────────────────────────────
+
+/**
+ * Builds a single segment path for one stage in the funnel.
+ * Each segment is a smooth trapezoid-like shape transitioning from
+ * the height of the current norm to the next norm.
+ */
+function hSegmentPath(
+ normStart: number,
+ normEnd: number,
+ segW: number,
+ H: number,
+ layerScale: number,
+ straight = false
+) {
+ const my = H / 2;
+ const h0 = normStart * H * 0.44 * layerScale;
+ const h1 = normEnd * H * 0.44 * layerScale;
+
+ if (straight) {
+ return `M 0 ${my - h0} L ${segW} ${my - h1} L ${segW} ${my + h1} L 0 ${my + h0} Z`;
+ }
+
+ const cx = segW * 0.55;
+ const top = `M 0 ${my - h0} C ${cx} ${my - h0}, ${segW - cx} ${my - h1}, ${segW} ${my - h1}`;
+ const bot = `L ${segW} ${my + h1} C ${segW - cx} ${my + h1}, ${cx} ${my + h0}, 0 ${my + h0}`;
+ return `${top} ${bot} Z`;
+}
+
+function vSegmentPath(
+ normStart: number,
+ normEnd: number,
+ segH: number,
+ W: number,
+ layerScale: number,
+ straight = false
+) {
+ const mx = W / 2;
+ const w0 = normStart * W * 0.44 * layerScale;
+ const w1 = normEnd * W * 0.44 * layerScale;
+
+ if (straight) {
+ return `M ${mx - w0} 0 L ${mx - w1} ${segH} L ${mx + w1} ${segH} L ${mx + w0} 0 Z`;
+ }
+
+ const cy = segH * 0.55;
+ const left = `M ${mx - w0} 0 C ${mx - w0} ${cy}, ${mx - w1} ${segH - cy}, ${mx - w1} ${segH}`;
+ const right = `L ${mx + w1} ${segH} C ${mx + w1} ${segH - cy}, ${mx + w0} ${cy}, ${mx + w0} 0`;
+ return `${left} ${right} Z`;
+}
+
+// ─── Animated Segment ───────────────────────────────────────────────
+
+function HRing({
+ d,
+ color,
+ fill,
+ opacity,
+ hovered,
+ ringIndex,
+ totalRings,
+}: {
+ d: string;
+ color: string;
+ fill?: string;
+ opacity: number;
+ hovered: boolean;
+ ringIndex: number;
+ totalRings: number;
+}) {
+ const extraScale = 1 + (ringIndex / Math.max(totalRings - 1, 1)) * 0.12;
+
+ return (
+
+ );
+}
+
+function HSegment({
+ index,
+ normStart,
+ normEnd,
+ segW,
+ fullH,
+ color,
+ layers,
+ staggerDelay,
+ enterTransition,
+ hovered,
+ dimmed,
+ renderPattern,
+ straight,
+ gradientStops,
+}: {
+ index: number;
+ normStart: number;
+ normEnd: number;
+ segW: number;
+ fullH: number;
+ color: string;
+ layers: number;
+ staggerDelay: number;
+ enterTransition?: Transition;
+ hovered: boolean;
+ dimmed: boolean;
+ renderPattern?: (id: string, color: string) => ReactNode;
+ straight: boolean;
+ gradientStops?: FunnelGradientStop[];
+}) {
+ const patternId = `funnel-h-pattern-${index}`;
+ const gradientId = `funnel-h-grad-${index}`;
+ const mountProgress = useMountProgress(
+ enterTransition,
+ index * staggerDelay,
+ index
+ );
+ const enterComplete = useEnterComplete(mountProgress);
+ const entranceScaleX = useTransform(mountProgress, [0, 1], [0, 1]);
+ const entranceScaleY = useTransform(mountProgress, [0, 1], [0, 1]);
+
+ const rings = Array.from({ length: layers }, (_, l) => {
+ const scale = 1 - (l / layers) * 0.35;
+ const opacity = 0.18 + (l / (layers - 1 || 1)) * 0.65;
+ return {
+ d: hSegmentPath(normStart, normEnd, segW, fullH, scale, straight),
+ opacity,
+ };
+ });
+
+ return (
+
+ {enterComplete ? (
+
+
+
+ {gradientStops && (
+
+ {gradientStops.map((stop) => (
+
+ ))}
+
+ )}
+ {renderPattern?.(patternId, color)}
+
+ {rings.map((r, i) => {
+ const isInnermost = i === rings.length - 1;
+ let ringFill: string | undefined;
+ if (isInnermost && renderPattern) {
+ ringFill = `url(#${patternId})`;
+ } else if (isInnermost && gradientStops) {
+ ringFill = `url(#${gradientId})`;
+ }
+ const ringKey = `h-ring-${r.opacity.toFixed(2)}`;
+ return (
+
+ );
+ })}
+
+
+ ) : (
+
+
+
+ {gradientStops && (
+
+ {gradientStops.map((stop) => (
+
+ ))}
+
+ )}
+ {renderPattern?.(patternId, color)}
+
+ {rings.map((r, i) => {
+ const isInnermost = i === rings.length - 1;
+ let ringFill: string | undefined;
+ if (isInnermost && renderPattern) {
+ ringFill = `url(#${patternId})`;
+ } else if (isInnermost && gradientStops) {
+ ringFill = `url(#${gradientId})`;
+ }
+ const ringKey = `h-ring-${r.opacity.toFixed(2)}`;
+ return (
+
+ );
+ })}
+
+
+ )}
+
+ );
+}
+
+function VRing({
+ d,
+ color,
+ fill,
+ opacity,
+ hovered,
+ ringIndex,
+ totalRings,
+}: {
+ d: string;
+ color: string;
+ fill?: string;
+ opacity: number;
+ hovered: boolean;
+ ringIndex: number;
+ totalRings: number;
+}) {
+ const extraScale = 1 + (ringIndex / Math.max(totalRings - 1, 1)) * 0.12;
+
+ return (
+
+ );
+}
+
+function VSegment({
+ index,
+ normStart,
+ normEnd,
+ segH,
+ fullW,
+ color,
+ layers,
+ staggerDelay,
+ enterTransition,
+ hovered,
+ dimmed,
+ renderPattern,
+ straight,
+ gradientStops,
+}: {
+ index: number;
+ normStart: number;
+ normEnd: number;
+ segH: number;
+ fullW: number;
+ color: string;
+ layers: number;
+ staggerDelay: number;
+ enterTransition?: Transition;
+ hovered: boolean;
+ dimmed: boolean;
+ renderPattern?: (id: string, color: string) => ReactNode;
+ straight: boolean;
+ gradientStops?: FunnelGradientStop[];
+}) {
+ const patternId = `funnel-v-pattern-${index}`;
+ const gradientId = `funnel-v-grad-${index}`;
+ const mountProgress = useMountProgress(
+ enterTransition,
+ index * staggerDelay,
+ index
+ );
+ const enterComplete = useEnterComplete(mountProgress);
+ const entranceScaleY = useTransform(mountProgress, [0, 1], [0, 1]);
+ const entranceScaleX = useTransform(mountProgress, [0, 1], [0, 1]);
+
+ const rings = Array.from({ length: layers }, (_, l) => {
+ const scale = 1 - (l / layers) * 0.35;
+ const opacity = 0.18 + (l / (layers - 1 || 1)) * 0.65;
+ return {
+ d: vSegmentPath(normStart, normEnd, segH, fullW, scale, straight),
+ opacity,
+ };
+ });
+
+ return (
+
+ {enterComplete ? (
+
+
+
+ {gradientStops && (
+
+ {gradientStops.map((stop) => (
+
+ ))}
+
+ )}
+ {renderPattern?.(patternId, color)}
+
+ {rings.map((r, i) => {
+ const isInnermost = i === rings.length - 1;
+ let ringFill: string | undefined;
+ if (isInnermost && renderPattern) {
+ ringFill = `url(#${patternId})`;
+ } else if (isInnermost && gradientStops) {
+ ringFill = `url(#${gradientId})`;
+ }
+ const ringKey = `v-ring-${r.opacity.toFixed(2)}`;
+ return (
+
+ );
+ })}
+
+
+ ) : (
+
+
+
+ {gradientStops && (
+
+ {gradientStops.map((stop) => (
+
+ ))}
+
+ )}
+ {renderPattern?.(patternId, color)}
+
+ {rings.map((r, i) => {
+ const isInnermost = i === rings.length - 1;
+ let ringFill: string | undefined;
+ if (isInnermost && renderPattern) {
+ ringFill = `url(#${patternId})`;
+ } else if (isInnermost && gradientStops) {
+ ringFill = `url(#${gradientId})`;
+ }
+ const ringKey = `v-ring-${r.opacity.toFixed(2)}`;
+ return (
+
+ );
+ })}
+
+
+ )}
+
+ );
+}
+
+// ─── Label overlay ──────────────────────────────────────────────────
+
+function SegmentLabel({
+ stage,
+ pct,
+ isHorizontal,
+ showValues,
+ showPercentage,
+ showLabels,
+ formatPercentage,
+ formatValue,
+ index,
+ staggerDelay,
+ layout = "spread",
+ orientation,
+ align = "center",
+}: {
+ stage: FunnelStage;
+ pct: number;
+ isHorizontal: boolean;
+ showValues: boolean;
+ showPercentage: boolean;
+ showLabels: boolean;
+ formatPercentage: (p: number) => string;
+ formatValue: (v: number) => string;
+ index: number;
+ staggerDelay: number;
+ layout?: "spread" | "grouped";
+ orientation?: "vertical" | "horizontal";
+ align?: "center" | "start" | "end";
+}) {
+ const display = stage.displayValue ?? formatValue(stage.value);
+
+ const valueEl = showValues && (
+
+ {display}
+
+ );
+ const pctEl = showPercentage && (
+
+ {formatPercentage(pct)}
+
+ );
+ const labelEl = showLabels && (
+
+ {stage.label}
+
+ );
+
+ // ── Spread layout (default): items pushed to edges with center element ──
+ if (layout === "spread") {
+ return (
+
+ {isHorizontal ? (
+ <>
+
+ {valueEl}
+
+
+ {pctEl}
+
+
+ {labelEl}
+
+ >
+ ) : (
+ <>
+
+ {valueEl}
+
+
+ {pctEl}
+
+
+ {labelEl}
+
+ >
+ )}
+
+ );
+ }
+
+ // ── Grouped layout: items stacked tightly together ──
+ const resolvedOrientation =
+ orientation ?? (isHorizontal ? "vertical" : "horizontal");
+ const isVerticalStack = resolvedOrientation === "vertical";
+
+ // Map align to flexbox alignment on the cross axes
+ const justifyMap = {
+ start: "justify-start",
+ center: "justify-center",
+ end: "justify-end",
+ } as const;
+ const itemsMap = {
+ start: "items-start",
+ center: "items-center",
+ end: "items-end",
+ } as const;
+
+ // The outer container uses the chart orientation to position the group,
+ // and the inner group uses the label orientation for stacking.
+ return (
+
+
+ {valueEl}
+ {pctEl}
+ {labelEl}
+
+
+ );
+}
+
+// ─── Main Component ─────────────────────────────────────────────────
+
+export function FunnelChart({
+ data,
+ orientation = "horizontal",
+ color = "var(--chart-1)",
+ layers = 3,
+ className,
+ style,
+ showPercentage = true,
+ showValues = true,
+ showLabels = true,
+ hoveredIndex: hoveredIndexProp,
+ onHoverChange,
+ formatPercentage = fmtPct,
+ formatValue = fmtVal,
+ staggerDelay = 0.12,
+ enterTransition,
+ gap = 4,
+ renderPattern,
+ edges = "curved",
+ labelLayout = "spread",
+ labelOrientation,
+ labelAlign = "center",
+ grid: gridProp = false,
+}: FunnelChartProps) {
+ const ref = useRef(null);
+ const [sz, setSz] = useState({ w: 0, h: 0 });
+ const [internalHoveredIndex, setInternalHoveredIndex] = useState<
+ number | null
+ >(null);
+
+ const isControlled = hoveredIndexProp !== undefined;
+ const hoveredIndex = isControlled ? hoveredIndexProp : internalHoveredIndex;
+ const setHoveredIndex = useCallback(
+ (index: number | null) => {
+ if (isControlled) {
+ onHoverChange?.(index);
+ } else {
+ setInternalHoveredIndex(index);
+ }
+ },
+ [isControlled, onHoverChange]
+ );
+
+ const measure = useCallback(() => {
+ if (!ref.current) {
+ return;
+ }
+ const { width: w, height: h } = ref.current.getBoundingClientRect();
+ if (w > 0 && h > 0) {
+ setSz({ w, h });
+ }
+ }, []);
+
+ useEffect(() => {
+ measure();
+ const ro = new ResizeObserver(measure);
+ if (ref.current) {
+ ro.observe(ref.current);
+ }
+ return () => ro.disconnect();
+ }, [measure]);
+
+ if (!data.length) {
+ return null;
+ }
+
+ const first = data[0];
+ if (!first) {
+ return null;
+ }
+ const max = first.value;
+ const n = data.length;
+ const norms = data.map((d) => d.value / max);
+ const horiz = orientation === "horizontal";
+ const { w: W, h: H } = sz;
+
+ const totalGap = gap * (n - 1);
+ const segW = (W - (horiz ? totalGap : 0)) / n;
+ const segH = (H - (horiz ? 0 : totalGap)) / n;
+
+ // Resolve grid config
+ const gridEnabled = gridProp !== false;
+ const gridCfg = typeof gridProp === "object" ? gridProp : {};
+ const showBands = gridEnabled && (gridCfg.bands ?? true);
+ const bandColor = gridCfg.bandColor ?? "var(--color-muted)";
+ const showGridLines = gridEnabled && (gridCfg.lines ?? true);
+ const gridLineColor = gridCfg.lineColor ?? "var(--chart-grid)";
+ const gridLineOpacity = gridCfg.lineOpacity ?? 1;
+ const gridLineWidth = gridCfg.lineWidth ?? 1;
+
+ return (
+
+ {W > 0 && H > 0 && (
+ <>
+ {/* Grid layer: background bands + grid lines */}
+ {gridEnabled && (
+
+ {/* Background bands — alternating on even segments */}
+ {showBands &&
+ data.map((stage, i) => {
+ if (i % 2 !== 0) {
+ return null;
+ }
+ if (horiz) {
+ const x = (segW + gap) * i;
+ return (
+
+ );
+ }
+ const y = (segH + gap) * i;
+ return (
+
+ );
+ })}
+
+ )}
+
+ {/* Segments container — overflow-visible so hover scale is not clipped */}
+
+ {data.map((stage, i) => {
+ const normStart = norms[i] ?? 0;
+ const normEnd = norms[Math.min(i + 1, n - 1)] ?? 0;
+ const firstStop = stage.gradient?.[0];
+ const segColor = firstStop
+ ? firstStop.color
+ : (stage.color ?? color);
+
+ return horiz ? (
+
+ ) : (
+
+ );
+ })}
+
+
+ {/* Grid lines — rendered above segments so they're visible */}
+ {gridEnabled && showGridLines && (
+
+ {Array.from({ length: n - 1 }, (_, i) => {
+ const idx = i + 1;
+ const gridKey = `grid-${idx}`;
+ if (horiz) {
+ const x = segW * idx + gap * i + gap / 2;
+ return (
+
+ );
+ }
+ const y = segH * idx + gap * i + gap / 2;
+ return (
+
+ );
+ })}
+
+ )}
+
+ {/* Label overlays — one per segment, positioned over each segment cell.
+ These are the hover triggers for each segment. */}
+ {data.map((stage, i) => {
+ const pct = (stage.value / max) * 100;
+ const posStyle: CSSProperties = horiz
+ ? {
+ left: (segW + gap) * i,
+ width: segW,
+ top: 0,
+ height: H,
+ }
+ : {
+ top: (segH + gap) * i,
+ height: segH,
+ left: 0,
+ width: W,
+ };
+
+ const isDimmed = hoveredIndex !== null && hoveredIndex !== i;
+
+ return (
+
setHoveredIndex(i)}
+ onMouseLeave={() => setHoveredIndex(null)}
+ style={{ ...posStyle, zIndex: 20 }}
+ transition={{ type: "spring", stiffness: 300, damping: 24 }}
+ >
+
+
+ );
+ })}
+ >
+ )}
+
+ );
+}
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/gauge.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/gauge.tsx
new file mode 100644
index 0000000..df9ba9d
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/gauge.tsx
@@ -0,0 +1,516 @@
+"use client";
+
+import { ParentSize } from "@visx/responsive";
+import { motion, type Transition, useReducedMotion } from "motion/react";
+import {
+ Children,
+ Fragment,
+ isValidElement,
+ type ReactElement,
+ type ReactNode,
+ useId,
+ useMemo,
+} from "react";
+import { cn } from "@/lib/utils";
+import {
+ type ChartStatFlowFormat,
+ defaultChartStatFlowFormat,
+} from "./chart-stat-flow";
+import { PieCenterShell } from "./pie-center-shell";
+
+function isDefsComponent(child: ReactElement): boolean {
+ const typeLabel =
+ (child.type as { displayName?: string })?.displayName ||
+ (child.type as { name?: string })?.name ||
+ "";
+ return (
+ typeLabel.includes("Gradient") ||
+ typeLabel.includes("Pattern") ||
+ typeLabel === "LinearGradient" ||
+ typeLabel === "RadialGradient" ||
+ typeLabel === "Lines" ||
+ typeLabel === "PatternLines" ||
+ typeLabel === "Circles" ||
+ typeLabel === "Hexagons" ||
+ typeLabel === "Waves"
+ );
+}
+
+function collectDefsElements(nodes: ReactNode): ReactElement[] {
+ const out: ReactElement[] = [];
+ Children.forEach(nodes, (child) => {
+ if (!isValidElement(child)) {
+ return;
+ }
+ if (child.type === Fragment) {
+ out.push(
+ ...collectDefsElements(
+ (child.props as { children?: ReactNode }).children
+ )
+ );
+ return;
+ }
+ if (isDefsComponent(child)) {
+ out.push(child);
+ }
+ });
+ return out;
+}
+
+function interpolateHex(
+ color1: string,
+ color2: string,
+ factor: number
+): string {
+ const hex = (c: string) => Number.parseInt(c, 16);
+ const r1 = hex(color1.slice(1, 3));
+ const g1 = hex(color1.slice(3, 5));
+ const b1 = hex(color1.slice(5, 7));
+ const r2 = hex(color2.slice(1, 3));
+ const g2 = hex(color2.slice(3, 5));
+ const b2 = hex(color2.slice(5, 7));
+
+ const r = Math.round(r1 + (r2 - r1) * factor);
+ const g = Math.round(g1 + (g2 - g1) * factor);
+ const b = Math.round(b1 + (b2 - b1) * factor);
+
+ return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
+}
+
+const DEFAULT_ACTIVE_GRADIENT: readonly [string, string] = [
+ "#bef264",
+ "#10b981",
+];
+
+const DEFAULT_ACTIVE_FILL_OPACITY = 1;
+const DEFAULT_INACTIVE_FILL_OPACITY = 0.8;
+
+const DEFAULT_NOTCH_ENTER_TRANSITION: Transition = {
+ type: "spring",
+ stiffness: 300,
+ damping: 20,
+};
+
+export interface GaugeProps {
+ /** Fill level 0–100 */
+ value: number;
+ /** Number of arc notches */
+ totalNotches?: number;
+ /** Percentage of the arc reserved for gaps between notches */
+ spacing?: number;
+ /**
+ * Corner fillet radius for each notch corner (pixels). **0** = sharp corners;
+ * higher values read more rounded; geometry clamps so large values approach a
+ * capsule / near-circular silhouette.
+ */
+ notchCornerRadius?: number;
+ /** `true` = rectangular notches; `false` = tapered toward the center */
+ uniformWidth?: boolean;
+ startAngle?: number;
+ endAngle?: number;
+ useGradient?: boolean;
+ /**
+ * When `useGradient` is true, active notch colors interpolate along the arc
+ * between these hex stops (default lime → emerald).
+ */
+ activeGradient?: readonly [string, string];
+ /**
+ * When `useGradient` is true, inactive notch colors interpolate between these
+ * hex stops. Defaults to {@link activeGradient} when omitted.
+ */
+ inactiveGradient?: readonly [string, string];
+ /** Value passed to {@link PieCenterShell} / NumberFlow */
+ centerValue: number;
+ defaultLabel?: string;
+ prefix?: string;
+ suffix?: string;
+ formatOptions?: ChartStatFlowFormat;
+ /**
+ * Inactive / track notch fill — CSS color or `url(#patternId)` (define patterns
+ * in `children`).
+ */
+ inactiveFill?: string;
+ /**
+ * Active notch fill — CSS color or `url(#patternId)`.
+ * When set, overrides solid / gradient active fills for that layer.
+ */
+ activeFill?: string;
+ /**
+ * SVG `fill-opacity` for inactive / track notches (0–1).
+ * Default **0.8** (foreground default is **1**).
+ */
+ inactiveFillOpacity?: number;
+ /**
+ * SVG `fill-opacity` for active notches (0–1). Default **1**.
+ */
+ activeFillOpacity?: number;
+ /**
+ * `PatternLines`, gradients, etc. — rendered inside `` (same convention
+ * as `PieChart` children).
+ */
+ children?: ReactNode;
+ className?: string;
+ /**
+ * Explicit pixel size. When omitted, the gauge fills its parent; give the
+ * parent a size (e.g. `min-w-[300px]` + aspect box) for responsive layouts.
+ */
+ width?: number;
+ height?: number;
+ /** Minimum width (px) when using the built-in responsive wrapper. Default 300 */
+ minWidth?: number;
+ /**
+ * Radial depth of notches as a **%** of the built-in default (outer 42% /
+ * inner 28% of `size`). **100** = full length; lower values pull the inner
+ * edge toward the outer arc. Clamped **5–100**.
+ */
+ notchLengthPercent?: number;
+ /** Framer Motion transition for notch enter animation (opacity / scale). */
+ enterTransition?: Transition;
+ /** Scales notch stagger delays relative to default timing (1 = reference). */
+ enterStaggerScale?: number;
+}
+
+interface GaugeInnerProps extends Omit {
+ width: number;
+ height: number;
+}
+
+function GaugeInner({
+ value,
+ totalNotches = 40,
+ spacing = 25,
+ notchCornerRadius = 0,
+ uniformWidth = false,
+ width,
+ height,
+ startAngle = 135,
+ endAngle = 405,
+ useGradient = false,
+ activeGradient,
+ inactiveGradient,
+ centerValue,
+ defaultLabel = "Total",
+ prefix,
+ suffix,
+ formatOptions = defaultChartStatFlowFormat,
+ inactiveFill,
+ activeFill,
+ inactiveFillOpacity,
+ activeFillOpacity,
+ children,
+ notchLengthPercent = 100,
+ enterTransition,
+ enterStaggerScale = 1,
+}: GaugeInnerProps) {
+ const prefersReducedMotion = useReducedMotion();
+ const themeActiveGradientId = `gauge-theme-active-${useId().replace(/:/g, "")}`;
+ const defsChildren = useMemo(() => collectDefsElements(children), [children]);
+
+ const notchTransition: Transition = prefersReducedMotion
+ ? { duration: 0 }
+ : (enterTransition ?? DEFAULT_NOTCH_ENTER_TRANSITION);
+
+ const stagger = Math.max(0.25, Math.min(2.5, enterStaggerScale));
+
+ const hasCustomInactive =
+ inactiveFill !== undefined && inactiveFill.length > 0;
+ const hasCustomActive = activeFill !== undefined && activeFill.length > 0;
+
+ const resolvedActiveFillOpacity =
+ activeFillOpacity ?? DEFAULT_ACTIVE_FILL_OPACITY;
+ const resolvedInactiveFillOpacity =
+ inactiveFillOpacity ?? DEFAULT_INACTIVE_FILL_OPACITY;
+
+ const size = Math.min(width, height);
+ const centerX = width / 2;
+ const centerY = height / 2;
+ const outerRadius = size * 0.42;
+ const innerRadiusBase = size * 0.28;
+ const defaultRadialDepth = outerRadius - innerRadiusBase;
+ const depthFactor = Math.min(100, Math.max(5, notchLengthPercent)) / 100;
+ const notchLength = defaultRadialDepth * depthFactor;
+ const innerRadius = outerRadius - notchLength;
+
+ const activeNotches = Math.round((value / 100) * totalNotches);
+
+ const totalAngle = endAngle - startAngle;
+ const availableAngle = totalAngle * (1 - spacing / 100);
+ const notchAngle = totalNotches > 0 ? availableAngle / totalNotches : 0;
+ const gapDen = totalNotches - 1 > 0 ? totalNotches - 1 : 1;
+ const gapAngle = (totalAngle * (spacing / 100)) / gapDen;
+
+ const activeGrad0 = activeGradient?.[0] ?? DEFAULT_ACTIVE_GRADIENT[0];
+ const activeGrad1 = activeGradient?.[1] ?? DEFAULT_ACTIVE_GRADIENT[1];
+ const inactiveGrad0 = inactiveGradient?.[0] ?? activeGrad0;
+ const inactiveGrad1 = inactiveGradient?.[1] ?? activeGrad1;
+ const useThemePaletteGradient = useGradient && activeGradient === undefined;
+
+ const notches = useMemo(() => {
+ return Array.from({ length: totalNotches }, (_, i) => {
+ const angle = startAngle + i * (notchAngle + gapAngle) + notchAngle / 2;
+ const radians = (angle * Math.PI) / 180;
+
+ const notchWidth = notchAngle * 0.8;
+ const halfWidth = (notchWidth * Math.PI) / 180 / 2;
+
+ const x1 = centerX + Math.cos(radians - halfWidth) * outerRadius;
+ const y1 = centerY + Math.sin(radians - halfWidth) * outerRadius;
+ const x2 = centerX + Math.cos(radians + halfWidth) * outerRadius;
+ const y2 = centerY + Math.sin(radians + halfWidth) * outerRadius;
+
+ let x3: number;
+ let y3: number;
+ let x4: number;
+ let y4: number;
+
+ if (uniformWidth) {
+ const perpX = Math.cos(radians);
+ const perpY = Math.sin(radians);
+ x3 = x2 - perpX * notchLength;
+ y3 = y2 - perpY * notchLength;
+ x4 = x1 - perpX * notchLength;
+ y4 = y1 - perpY * notchLength;
+ } else {
+ x3 = centerX + Math.cos(radians + halfWidth) * innerRadius;
+ y3 = centerY + Math.sin(radians + halfWidth) * innerRadius;
+ x4 = centerX + Math.cos(radians - halfWidth) * innerRadius;
+ y4 = centerY + Math.sin(radians - halfWidth) * innerRadius;
+ }
+
+ const denom = totalNotches > 1 ? totalNotches - 1 : 1;
+ const gradientFactor = i / denom;
+ const gradientColor =
+ useGradient && !useThemePaletteGradient
+ ? interpolateHex(activeGrad0, activeGrad1, gradientFactor)
+ : "var(--chart-1)";
+
+ return {
+ index: i,
+ points: { x1, y1, x2, y2, x3, y3, x4, y4 },
+ isActive: i < activeNotches,
+ gradientColor,
+ };
+ });
+ }, [
+ totalNotches,
+ notchAngle,
+ gapAngle,
+ centerX,
+ centerY,
+ outerRadius,
+ innerRadius,
+ activeNotches,
+ startAngle,
+ uniformWidth,
+ notchLength,
+ activeGrad0,
+ activeGrad1,
+ useGradient,
+ useThemePaletteGradient,
+ ]);
+
+ const createNotchPath = (
+ points: {
+ x1: number;
+ y1: number;
+ x2: number;
+ y2: number;
+ x3: number;
+ y3: number;
+ x4: number;
+ y4: number;
+ },
+ cornerRadiusPx: number,
+ radialDepth: number
+ ) => {
+ const { x1, y1, x2, y2, x3, y3, x4, y4 } = points;
+
+ const lerp = (a: number, b: number, t: number) => a + (b - a) * t;
+ const dist = (ax: number, ay: number, bx: number, by: number) =>
+ Math.hypot(bx - ax, by - ay);
+
+ const d12 = dist(x1, y1, x2, y2);
+ const d23 = dist(x2, y2, x3, y3);
+ const d34 = dist(x3, y3, x4, y4);
+ const d41 = dist(x4, y4, x1, y1);
+
+ if (cornerRadiusPx <= 0) {
+ return `M ${x1} ${y1} L ${x2} ${y2} L ${x3} ${y3} L ${x4} ${y4} Z`;
+ }
+
+ const minEdge = Math.min(d12, d23, d34, d41);
+ const cr = Math.min(
+ cornerRadiusPx,
+ radialDepth * 0.48,
+ d12 * 0.49,
+ d23 * 0.49,
+ d34 * 0.49,
+ d41 * 0.49,
+ minEdge * 0.49
+ );
+
+ const r1 = Math.min(cr / d12, 0.49);
+ const r2 = Math.min(cr / d23, 0.49);
+ const r3 = Math.min(cr / d34, 0.49);
+ const r4 = Math.min(cr / d41, 0.49);
+
+ const p1a = { x: lerp(x1, x4, r4), y: lerp(y1, y4, r4) };
+ const p1b = { x: lerp(x1, x2, r1), y: lerp(y1, y2, r1) };
+ const p2a = { x: lerp(x2, x1, r1), y: lerp(y2, y1, r1) };
+ const p2b = { x: lerp(x2, x3, r2), y: lerp(y2, y3, r2) };
+ const p3a = { x: lerp(x3, x2, r2), y: lerp(y3, y2, r2) };
+ const p3b = { x: lerp(x3, x4, r3), y: lerp(y3, y4, r3) };
+ const p4a = { x: lerp(x4, x3, r3), y: lerp(y4, y3, r3) };
+ const p4b = { x: lerp(x4, x1, r4), y: lerp(y4, y1, r4) };
+
+ return `M ${p1a.x} ${p1a.y} Q ${x1} ${y1} ${p1b.x} ${p1b.y} L ${p2a.x} ${p2a.y} Q ${x2} ${y2} ${p2b.x} ${p2b.y} L ${p3a.x} ${p3a.y} Q ${x3} ${y3} ${p3b.x} ${p3b.y} L ${p4a.x} ${p4a.y} Q ${x4} ${y4} ${p4b.x} ${p4b.y} Z`;
+ };
+
+ const bgFillSolid = "var(--border)";
+ const activeFillSolid = "var(--chart-1)";
+
+ const denom = totalNotches > 1 ? totalNotches - 1 : 1;
+
+ const resolveBgFill = (notchIndex: number) => {
+ if (hasCustomInactive) {
+ return inactiveFill as string;
+ }
+ if (useThemePaletteGradient) {
+ return bgFillSolid;
+ }
+ if (useGradient) {
+ return interpolateHex(inactiveGrad0, inactiveGrad1, notchIndex / denom);
+ }
+ return bgFillSolid;
+ };
+
+ const resolveActiveFill = (notch: (typeof notches)[number]) => {
+ if (hasCustomActive) {
+ return activeFill as string;
+ }
+ if (useThemePaletteGradient) {
+ return `url(#${themeActiveGradientId})`;
+ }
+ if (useGradient) {
+ return notch.gradientColor;
+ }
+ return activeFillSolid;
+ };
+
+ return (
+
+
+ {defsChildren.length > 0 || useThemePaletteGradient ? (
+
+ {useThemePaletteGradient ? (
+
+
+
+
+ ) : null}
+ {defsChildren}
+
+ ) : null}
+ {notches.map((notch) => (
+
+ ))}
+
+ {notches
+ .filter((n) => n.isActive)
+ .map((notch) => (
+
+ ))}
+
+
+
+
+ );
+}
+
+export function Gauge({
+ width: widthProp,
+ height: heightProp,
+ className,
+ minWidth = 300,
+ ...props
+}: GaugeProps) {
+ if (widthProp != null && heightProp != null) {
+ return (
+
+
+
+ );
+ }
+
+ return (
+
+
+
+ {({ width, height }) =>
+ width > 0 && height > 0 ? (
+
+ ) : null
+ }
+
+
+
+ );
+}
+
+Gauge.displayName = "Gauge";
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/generate-chart-skeleton-data.ts b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/generate-chart-skeleton-data.ts
new file mode 100644
index 0000000..b838c5d
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/generate-chart-skeleton-data.ts
@@ -0,0 +1,42 @@
+const DEFAULT_SKELETON_DATA_KEY = "value";
+const DEFAULT_SKELETON_POINT_COUNT = 7;
+
+export interface GenerateChartSkeletonDataOptions {
+ /** Key used for y values in each row. Default: `"value"`. */
+ dataKey?: string;
+ /** Number of points. Default: 7. */
+ pointCount?: number;
+ /** Start date for the x axis. Default: 2025-01-01. */
+ baseDate?: Date;
+}
+
+/** Placeholder series used while `status="loading"` and data is empty. */
+export function generateChartSkeletonData(
+ options: GenerateChartSkeletonDataOptions = {}
+): Record[] {
+ const dataKey = options.dataKey ?? DEFAULT_SKELETON_DATA_KEY;
+ const pointCount = options.pointCount ?? DEFAULT_SKELETON_POINT_COUNT;
+ const baseDate = options.baseDate ?? new Date("2025-01-01");
+
+ return Array.from({ length: pointCount }, (_, index) => {
+ const date = new Date(baseDate);
+ date.setDate(baseDate.getDate() + index);
+ return {
+ date,
+ [dataKey]: Math.round(110 + Math.sin(index * 1.15) * 36 + index * 9),
+ };
+ });
+}
+
+/** Skeleton rows that mirror target dates/count with lower magnitudes for Y tween. */
+export function generateChartSkeletonFromTarget(
+ targetData: Record[],
+ dataKey: string
+): Record[] {
+ return targetData.map((row, index) => ({
+ ...row,
+ [dataKey]: Math.round(95 + Math.sin(index * 1.05) * 28 + index * 7),
+ }));
+}
+
+export { DEFAULT_SKELETON_DATA_KEY, DEFAULT_SKELETON_POINT_COUNT };
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/grid.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/grid.tsx
new file mode 100644
index 0000000..c030c38
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/grid.tsx
@@ -0,0 +1,317 @@
+"use client";
+
+import { GridColumns, GridRows } from "@visx/grid";
+import { motion } from "motion/react";
+import { useId } from "react";
+import { chartCssVars, useChartStable, useYScale } from "./chart-context";
+import { useGridShimmer } from "./use-grid-shimmer";
+import {
+ isLoadingChromePhase,
+ isLoadingGridChromePhase,
+} from "./y-domain-utils";
+
+const DEFAULT_SHIMMER_LENGTH_PX = 140;
+const DEFAULT_SHIMMER_SPEED = 1;
+const DEFAULT_SHIMMER_STROKE =
+ "color-mix(in oklch, var(--foreground) 68%, transparent)";
+
+export interface GridProps {
+ /** Show horizontal grid lines. Default: true */
+ horizontal?: boolean;
+ /** Show vertical grid lines. Default: false */
+ vertical?: boolean;
+ /** Number of horizontal grid lines. Default: 5 */
+ numTicksRows?: number;
+ /** Number of vertical grid lines. Default: 10 */
+ numTicksColumns?: number;
+ /** Explicit tick values for horizontal grid lines. Overrides numTicksRows. */
+ rowTickValues?: number[];
+ /** Grid line stroke color. Default: var(--chart-grid) */
+ stroke?: string;
+ /** Grid stroke while loading chrome is active. Falls back to `stroke`. */
+ loadingStroke?: string;
+ /** Grid line stroke opacity. Default: 1 */
+ strokeOpacity?: number;
+ /** Grid line stroke width. Default: 1 */
+ strokeWidth?: number;
+ /** Grid line dash array. Default: "4,4" for dashed lines */
+ strokeDasharray?: string;
+ /** Horizontal row values rendered with alternate styling (e.g. zero baseline). */
+ highlightRowValues?: number[];
+ /** Stroke for highlighted rows. Default: var(--chart-foreground-muted) */
+ highlightRowStroke?: string;
+ /** Stroke opacity for highlighted rows. Default: 1 */
+ highlightRowStrokeOpacity?: number;
+ /** Stroke width for highlighted rows. Default: 1 */
+ highlightRowStrokeWidth?: number;
+ /** Dash array for highlighted rows. Default: solid line */
+ highlightRowStrokeDasharray?: string;
+ /** Enable horizontal fade effect on grid rows (fades at left/right). Default: true */
+ fadeHorizontal?: boolean;
+ /** Enable vertical fade effect on grid columns (fades at top/bottom). Default: false */
+ fadeVertical?: boolean;
+ /** Omit the first and last horizontal grid lines. Default: false */
+ hideHorizontalEdgeLines?: boolean;
+ /** Omit the first and last vertical grid lines. Default: false */
+ hideVerticalEdgeLines?: boolean;
+ /** Y-scale for horizontal grid lines. Default: primary (`"left"`) axis. */
+ yAxisId?: string | number;
+ /** Animate a shimmer band across horizontal grid lines. Default: false */
+ shimmer?: boolean;
+ /** Shimmer band stroke (color and opacity via color-mix or oklch alpha). */
+ shimmerStroke?: string;
+ /** Shimmer band width in pixels. Default: 140 */
+ shimmerLength?: number;
+ /** Shimmer speed multiplier (higher = faster). Default: 1 */
+ shimmerSpeed?: number;
+ /** Match loop timing to the loading line pulse (cycle + inter-loop pause). */
+ shimmerSync?: boolean;
+}
+
+function hideEdgeTicks(ticks: T[], hideEdgeLines: boolean): T[] {
+ if (!hideEdgeLines || ticks.length <= 2) {
+ return ticks;
+ }
+ return ticks.slice(1, -1);
+}
+
+function resolveRowTickValues(options: {
+ hideHorizontalEdgeLines: boolean;
+ numTicksRows: number;
+ rowTickValues?: number[];
+ yScale: { ticks?: (count: number) => number[] };
+}): number[] | undefined {
+ const { hideHorizontalEdgeLines, numTicksRows, rowTickValues, yScale } =
+ options;
+ const ticks =
+ rowTickValues ?? (yScale.ticks ? yScale.ticks(numTicksRows) : []);
+ const filtered = hideEdgeTicks(ticks, hideHorizontalEdgeLines);
+ if (filtered === ticks && !rowTickValues && !hideHorizontalEdgeLines) {
+ return undefined;
+ }
+ return filtered.length > 0 ? filtered : undefined;
+}
+
+// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: grid fade masks and shimmer share one layer tree
+export function Grid({
+ horizontal = true,
+ vertical = false,
+ numTicksRows = 5,
+ numTicksColumns = 10,
+ rowTickValues,
+ stroke = chartCssVars.grid,
+ loadingStroke,
+ strokeOpacity = 1,
+ strokeWidth = 1,
+ strokeDasharray = "4,4",
+ highlightRowValues,
+ highlightRowStroke = chartCssVars.foregroundMuted,
+ highlightRowStrokeOpacity = 1,
+ highlightRowStrokeWidth = 1,
+ highlightRowStrokeDasharray = "0",
+ fadeHorizontal = true,
+ fadeVertical = false,
+ hideHorizontalEdgeLines = false,
+ hideVerticalEdgeLines = false,
+ yAxisId,
+ shimmer = false,
+ shimmerStroke = DEFAULT_SHIMMER_STROKE,
+ shimmerLength = DEFAULT_SHIMMER_LENGTH_PX,
+ shimmerSpeed = DEFAULT_SHIMMER_SPEED,
+ shimmerSync = false,
+}: GridProps) {
+ const { xScale, innerWidth, innerHeight, orientation, barScale, chartPhase } =
+ useChartStable();
+ const yScale = useYScale(yAxisId);
+ const shimmerActive = shimmer && isLoadingChromePhase(chartPhase);
+ const gridStroke =
+ isLoadingGridChromePhase(chartPhase) && loadingStroke != null
+ ? loadingStroke
+ : stroke;
+ const { shimmerEnabled, shimmerTransform } = useGridShimmer({
+ innerWidth,
+ shimmer,
+ shimmerLength,
+ shimmerSpeed,
+ shimmerSync,
+ active: shimmerActive,
+ });
+
+ // For bar charts, determine which scale to use for grid lines
+ // Horizontal bar charts: vertical grid should use yScale (value scale)
+ // Vertical bar charts: horizontal grid uses yScale (value scale)
+ const isHorizontalBarChart = orientation === "horizontal" && barScale;
+
+ // For vertical grid lines in horizontal bar charts, use yScale (the value scale)
+ // For time-based charts, use xScale
+ const columnScale = isHorizontalBarChart ? yScale : xScale;
+ const rowTickValuesResolved = resolveRowTickValues({
+ hideHorizontalEdgeLines,
+ numTicksRows,
+ rowTickValues,
+ yScale,
+ });
+ const columnTickValuesResolved =
+ vertical &&
+ columnScale &&
+ typeof columnScale === "function" &&
+ hideVerticalEdgeLines
+ ? (() => {
+ const ticks = columnScale.ticks?.(numTicksColumns) ?? [];
+ const filtered = hideEdgeTicks(ticks, true);
+ return filtered.length > 0 ? filtered : undefined;
+ })()
+ : undefined;
+ const uniqueId = useId();
+
+ // Horizontal fade mask (for grid rows - fades left/right)
+ const hMaskId = `grid-rows-fade-${uniqueId}`;
+ const hGradientId = `${hMaskId}-gradient`;
+ const shimmerGradientId = `grid-shimmer-${uniqueId}`;
+
+ // Vertical fade mask (for grid columns - fades top/bottom)
+ const vMaskId = `grid-cols-fade-${uniqueId}`;
+ const vGradientId = `${vMaskId}-gradient`;
+ const horizontalFadeMask = fadeHorizontal ? `url(#${hMaskId})` : undefined;
+
+ return (
+
+ {/* Gradient mask for horizontal grid lines - fades at left/right */}
+ {horizontal && fadeHorizontal && (
+
+
+
+
+
+
+
+
+
+
+
+ )}
+
+ {horizontal && shimmerEnabled ? (
+
+
+
+
+
+
+
+
+
+ ) : null}
+
+ {/* Gradient mask for vertical grid lines - fades at top/bottom */}
+ {vertical && fadeVertical && (
+
+
+
+
+
+
+
+
+
+
+
+ )}
+
+ {horizontal && (
+
+
+ {shimmerEnabled ? (
+
+ ) : null}
+
+ )}
+ {horizontal && highlightRowValues && highlightRowValues.length > 0 ? (
+
+ {highlightRowValues.map((value) => {
+ const y = yScale(value);
+ if (y == null || !Number.isFinite(y)) {
+ return null;
+ }
+
+ return (
+
+ );
+ })}
+
+ ) : null}
+ {vertical && columnScale && typeof columnScale === "function" && (
+
+
+
+ )}
+
+ );
+}
+
+Grid.displayName = "Grid";
+
+export default Grid;
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/heatmap/__tests__/heatmap-animation.test.ts b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/heatmap/__tests__/heatmap-animation.test.ts
new file mode 100644
index 0000000..6af0a65
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/heatmap/__tests__/heatmap-animation.test.ts
@@ -0,0 +1,76 @@
+import assert from "node:assert/strict";
+import { describe, it } from "node:test";
+import {
+ computeHeatmapEnterFadeDelayMs,
+ computeHeatmapLevelRange,
+ HEATMAP_DEFAULT_ENTER_DURATION_MS,
+ HEATMAP_DEFAULT_LOADING_CELL_MAX_OPACITY,
+ HEATMAP_ENTER_STAGGER_SPREAD,
+ heatmapLoadingCellParticipates,
+ resolveHeatmapEnterFadeDurationSec,
+} from "../heatmap-animation";
+import type { HeatmapColumn } from "../heatmap-context";
+
+function column(counts: number[]): HeatmapColumn {
+ return {
+ bin: 0,
+ bins: counts.map((count, day) => ({
+ bin: day,
+ count,
+ date: new Date(2025, 0, day + 1),
+ })),
+ };
+}
+
+describe("computeHeatmapLevelRange", () => {
+ it("returns the min and max levels present in the dataset", () => {
+ const data = [column([0, 1, 4]), column([2, 4, 4])];
+ assert.deepEqual(computeHeatmapLevelRange(data), { min: 0, max: 4 });
+ });
+
+ it("handles datasets with only bright cells", () => {
+ const data = [column([4, 4]), column([3, 4])];
+ assert.deepEqual(computeHeatmapLevelRange(data), { min: 3, max: 4 });
+ });
+});
+
+describe("enter fade helpers", () => {
+ it("defaults enter duration to 1.6s", () => {
+ assert.equal(HEATMAP_DEFAULT_ENTER_DURATION_MS, 1600);
+ });
+
+ it("derives fade duration from the motion transition when provided", () => {
+ assert.equal(
+ resolveHeatmapEnterFadeDurationSec({ duration: 0.8 }, 1600),
+ 0.8
+ );
+ });
+
+ it("keeps fade delays within the animation window", () => {
+ const fadeDurationSec = 0.4;
+ const animationDurationMs = 1600;
+ const delayMs = computeHeatmapEnterFadeDelayMs({
+ column: 4,
+ row: 2,
+ revealEpoch: 1,
+ animationDurationMs,
+ enterStaggerScale: 1,
+ fadeDurationSec,
+ });
+
+ const maxDelayMs =
+ (animationDurationMs - fadeDurationSec * 1000) *
+ HEATMAP_ENTER_STAGGER_SPREAD;
+ assert.ok(delayMs >= 0);
+ assert.ok(delayMs <= maxDelayMs);
+ });
+
+ it("defaults loading cell max opacity to 85%", () => {
+ assert.equal(HEATMAP_DEFAULT_LOADING_CELL_MAX_OPACITY, 0.85);
+ });
+
+ it("gates loading shimmer participation by randomness", () => {
+ assert.equal(heatmapLoadingCellParticipates(0, 0, 0), false);
+ assert.equal(heatmapLoadingCellParticipates(0, 0, 1), true);
+ });
+});
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/heatmap/generate-heatmap-skeleton-data.ts b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/heatmap/generate-heatmap-skeleton-data.ts
new file mode 100644
index 0000000..5022b53
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/heatmap/generate-heatmap-skeleton-data.ts
@@ -0,0 +1,15 @@
+import type { HeatmapColumn } from "./heatmap-context";
+
+/** Placeholder grid with the same week/day shape as target data (all empty cells). */
+export function generateHeatmapSkeletonFromTarget(
+ target: HeatmapColumn[]
+): HeatmapColumn[] {
+ return target.map((column) => ({
+ bin: column.bin,
+ bins: column.bins.map((bin) => ({
+ bin: bin.bin,
+ count: 0,
+ date: bin.date,
+ })),
+ }));
+}
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/heatmap/heatmap-animation.ts b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/heatmap/heatmap-animation.ts
new file mode 100644
index 0000000..cf7e220
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/heatmap/heatmap-animation.ts
@@ -0,0 +1,142 @@
+import type { Transition } from "motion/react";
+import type { HeatmapColumn } from "./heatmap-context";
+import { getHeatmapContributionLevel } from "./heatmap-utils";
+
+/** Default enter duration when no motion props are passed (ms). */
+export const HEATMAP_DEFAULT_ENTER_DURATION_MS = 1600;
+
+/** Default cubic-bezier for heatmap cell enter / loading transitions. */
+export const HEATMAP_DEFAULT_ENTER_EASE = [0.85, 0, 0.916, 0.282] as const;
+
+/** Default enter transition for {@link HeatmapChart}. */
+export const HEATMAP_DEFAULT_ENTER_TRANSITION: Transition = {
+ type: "tween",
+ duration: HEATMAP_DEFAULT_ENTER_DURATION_MS / 1000,
+ ease: HEATMAP_DEFAULT_ENTER_EASE,
+};
+
+/** Chart opacity while `status="loading"`. */
+export const HEATMAP_LOADING_CHART_OPACITY = 1;
+
+/** Default max per-cell opacity during loading shimmer. */
+export const HEATMAP_DEFAULT_LOADING_CELL_MAX_OPACITY = 0.85;
+
+/** Default share of cells that participate in loading shimmer (0–1). */
+export const HEATMAP_DEFAULT_LOADING_CELL_RANDOMNESS = 1;
+
+/** Opacity for loading cells that do not participate in shimmer. */
+export const HEATMAP_LOADING_BASE_CELL_OPACITY = 0.2;
+
+/** Duration for ready → loading cell conceal before shimmer resumes (ms). */
+export const HEATMAP_LOADING_CONCEAL_MS = 450;
+
+/** Share of the enter window used for random fade-in delays. */
+export const HEATMAP_ENTER_STAGGER_SPREAD = 0.6;
+
+function seededRandom(seed: number): () => number {
+ let state = seed % 2_147_483_647;
+ if (state <= 0) {
+ state += 2_147_483_646;
+ }
+ return () => {
+ state = (state * 16_807) % 2_147_483_647;
+ return (state - 1) / 2_147_483_646;
+ };
+}
+
+export function heatmapCellSeed(column: number, row: number): number {
+ return column * 1009 + row * 9176;
+}
+
+export interface HeatmapLevelRange {
+ min: number;
+ max: number;
+}
+
+/** Min/max contribution levels present in the dataset. */
+export function computeHeatmapLevelRange(
+ data: HeatmapColumn[]
+): HeatmapLevelRange {
+ let min = Number.POSITIVE_INFINITY;
+ let max = Number.NEGATIVE_INFINITY;
+
+ for (const column of data) {
+ for (const bin of column.bins) {
+ const level = getHeatmapContributionLevel(bin.count);
+ if (level < min) {
+ min = level;
+ }
+ if (level > max) {
+ max = level;
+ }
+ }
+ }
+
+ if (!(Number.isFinite(min) && Number.isFinite(max))) {
+ return { min: 0, max: 4 };
+ }
+
+ return { min, max };
+}
+
+/** Per-cell fade duration derived from the motion enter transition. */
+export function resolveHeatmapEnterFadeDurationSec(
+ enterTransition: Transition | undefined,
+ animationDurationMs: number
+): number {
+ if (
+ enterTransition &&
+ "duration" in enterTransition &&
+ typeof enterTransition.duration === "number"
+ ) {
+ return enterTransition.duration;
+ }
+
+ return Math.min(0.45, (animationDurationMs / 1000) * 0.3);
+}
+
+/** Random delay so all cells finish fading in within `animationDurationMs`. */
+export function computeHeatmapEnterFadeDelayMs({
+ column,
+ row,
+ revealEpoch,
+ animationDurationMs,
+ enterStaggerScale,
+ fadeDurationSec,
+}: {
+ column: number;
+ row: number;
+ revealEpoch: number;
+ animationDurationMs: number;
+ enterStaggerScale: number;
+ fadeDurationSec: number;
+}): number {
+ const random = seededRandom(
+ heatmapCellSeed(column, row) + revealEpoch * 524_287
+ );
+ const fadeMs = fadeDurationSec * 1000;
+ const maxDelayMs = Math.max(0, animationDurationMs - fadeMs);
+ const spreadMs =
+ maxDelayMs *
+ HEATMAP_ENTER_STAGGER_SPREAD *
+ Math.max(enterStaggerScale, 0.25);
+
+ return random() * spreadMs;
+}
+
+/** Whether a cell participates in loading shimmer for a given randomness (0–1). */
+export function heatmapLoadingCellParticipates(
+ column: number,
+ row: number,
+ randomness: number
+): boolean {
+ if (randomness >= 1) {
+ return true;
+ }
+ if (randomness <= 0) {
+ return false;
+ }
+
+ const random = seededRandom(heatmapCellSeed(column, row) + 73_133);
+ return random() < randomness;
+}
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/heatmap/heatmap-cells.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/heatmap/heatmap-cells.tsx
new file mode 100644
index 0000000..61ea943
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/heatmap/heatmap-cells.tsx
@@ -0,0 +1,512 @@
+"use client";
+
+import { Group } from "@visx/group";
+import { HeatmapRect } from "@visx/heatmap";
+import {
+ animate,
+ type MotionValue,
+ motion,
+ type Transition,
+ useMotionValue,
+} from "motion/react";
+import { memo, useCallback, useEffect, useMemo, useRef } from "react";
+import type { ChartStatus } from "../chart-phase";
+import { transitionWithDelay } from "../motion-utils";
+import {
+ computeHeatmapEnterFadeDelayMs,
+ HEATMAP_DEFAULT_ENTER_EASE,
+ HEATMAP_LOADING_BASE_CELL_OPACITY,
+ HEATMAP_LOADING_CONCEAL_MS,
+ heatmapLoadingCellParticipates,
+ resolveHeatmapEnterFadeDurationSec,
+} from "./heatmap-animation";
+import { heatmapLevelCellFillOpacity } from "./heatmap-colors";
+import {
+ type HeatmapBin,
+ type HeatmapColumn,
+ useHeatmap,
+ useHeatmapInteraction,
+} from "./heatmap-context";
+import { getHeatmapContributionLevel } from "./heatmap-utils";
+
+const HEATMAP_FADED_OPACITY = 0.3;
+const HEATMAP_HOVER_TRANSITION = { duration: 0.15, ease: "easeOut" as const };
+const HEATMAP_CONCEAL_TRANSITION = {
+ duration: HEATMAP_LOADING_CONCEAL_MS / 1000,
+ ease: HEATMAP_DEFAULT_ENTER_EASE,
+};
+
+function computeHeatmapCellFaded(
+ isCellHovering: boolean,
+ isLevelHovering: boolean,
+ hoveredCell: { column: number; row: number } | null,
+ hoveredLegendLevel: number | null,
+ cell: { column: number; row: number },
+ count: number
+): boolean {
+ if (isCellHovering && hoveredCell) {
+ return hoveredCell.column !== cell.column || hoveredCell.row !== cell.row;
+ }
+ if (isLevelHovering && hoveredLegendLevel !== null) {
+ return getHeatmapContributionLevel(count) !== hoveredLegendLevel;
+ }
+ return false;
+}
+
+interface SyncCellLayerParams {
+ animateCells: boolean;
+ chartStatus: ChartStatus;
+ dataOpacity: MotionValue;
+ isAwaitingLoadingConceal: boolean;
+ isExitingToLoading: boolean;
+ isLoadingResting: boolean;
+ isReadyResting: boolean;
+ isRevealActive: boolean;
+ participates: boolean;
+ pulseOpacity: MotionValue;
+ readyDataOpacity: number;
+ shimmerOpacity: MotionValue;
+ showLoadingCellsLayer: boolean;
+ showShimmerPulse: boolean;
+ staggeredTransition: Transition;
+}
+
+function syncHeatmapCellLayerOpacities(params: SyncCellLayerParams) {
+ const {
+ animateCells,
+ chartStatus,
+ dataOpacity,
+ isAwaitingLoadingConceal,
+ isExitingToLoading,
+ isLoadingResting,
+ isReadyResting,
+ isRevealActive,
+ participates,
+ pulseOpacity,
+ readyDataOpacity,
+ shimmerOpacity,
+ showLoadingCellsLayer,
+ showShimmerPulse,
+ staggeredTransition,
+ } = params;
+
+ if (!animateCells) {
+ if (isReadyResting) {
+ dataOpacity.set(readyDataOpacity);
+ shimmerOpacity.set(0);
+ pulseOpacity.set(0);
+ } else if (chartStatus === "loading") {
+ dataOpacity.set(0);
+ pulseOpacity.set(0);
+ shimmerOpacity.set(
+ participates && showLoadingCellsLayer
+ ? 0
+ : HEATMAP_LOADING_BASE_CELL_OPACITY
+ );
+ }
+ return;
+ }
+
+ if (isLoadingResting) {
+ animate(dataOpacity, 0, { duration: 0 });
+ if (!showShimmerPulse) {
+ animate(shimmerOpacity, HEATMAP_LOADING_BASE_CELL_OPACITY, {
+ duration: 0,
+ });
+ }
+ return;
+ }
+
+ if (isRevealActive) {
+ pulseOpacity.set(0);
+ shimmerOpacity.set(0);
+ animate(dataOpacity, readyDataOpacity, staggeredTransition);
+ return;
+ }
+
+ if (isAwaitingLoadingConceal || isExitingToLoading) {
+ animate(dataOpacity, 0, HEATMAP_CONCEAL_TRANSITION);
+ animate(shimmerOpacity, 0, { duration: 0 });
+ return;
+ }
+
+ if (isReadyResting) {
+ animate(dataOpacity, readyDataOpacity, HEATMAP_HOVER_TRANSITION);
+ animate(shimmerOpacity, 0, { duration: 0 });
+ pulseOpacity.set(0);
+ }
+}
+
+export interface HeatmapCellsProps {
+ /** Corner radius for each cell. Default: 2 */
+ cornerRadius?: number;
+ /** Override the default GitHub-style green color scale. */
+ colorScale?: (count: number | null | undefined) => string;
+ /** Opacity for non-hovered cells while a cell is hovered. Default: 0.3 */
+ fadedOpacity?: number;
+ /** Pointer hover + dimming. Default: true */
+ interactive?: boolean;
+}
+
+interface HeatmapCellRectProps {
+ cell: {
+ column: number;
+ row: number;
+ x: number;
+ y: number;
+ width: number;
+ height: number;
+ color?: string;
+ opacity?: number;
+ };
+ bin: HeatmapBin;
+ cornerRadius: number;
+ interactive: boolean;
+ fadedOpacity: number;
+ isFaded: boolean;
+ onEnter: (
+ column: number,
+ row: number,
+ bin: HeatmapBin,
+ x: number,
+ y: number
+ ) => void;
+ onLeave: () => void;
+}
+
+const HeatmapMotionCell = memo(function HeatmapMotionCell({
+ cell,
+ bin,
+ cornerRadius,
+ fillScale,
+ interactive,
+ fadedOpacity,
+ isFaded,
+ onEnter,
+ onLeave,
+}: HeatmapCellRectProps & {
+ fillScale: (count: number | null | undefined) => string;
+}) {
+ const {
+ chartStatus,
+ chartPhase,
+ isLoaded,
+ revealMode,
+ revealEpoch,
+ animationDuration,
+ enterTransition,
+ enterStaggerScale,
+ animateCells,
+ showLoadingCells: showLoadingCellsLayer,
+ loadingCellMaxOpacity,
+ loadingCellRandomness,
+ levelStyles,
+ } = useHeatmap();
+
+ const levelStyle =
+ levelStyles[getHeatmapContributionLevel(bin.count ?? 0)] ?? levelStyles[0];
+ const targetFill = fillScale(bin.count);
+ const emptyFill = fillScale(0);
+ const patternFillOpacity = heatmapLevelCellFillOpacity(levelStyle);
+ const dataOpacity = useMotionValue(0);
+ /** Orchestration layer: conceal, static loading base. */
+ const shimmerOpacity = useMotionValue(0);
+ /** Pulse loop only — kept separate so stagger tweens don't break re-entry. */
+ const pulseOpacity = useMotionValue(0);
+ const wasShimmerPulsingRef = useRef(false);
+
+ const isLoadingResting =
+ chartStatus === "loading" && chartPhase === "loading";
+ const isAwaitingLoadingConceal =
+ chartStatus === "loading" && chartPhase === "ready";
+ const isExitingToLoading = chartPhase === "exitingReady";
+ const isRevealActive =
+ chartPhase === "revealing" &&
+ !isLoaded &&
+ (revealMode === "fromLoading" || revealMode === "enter");
+ const isReadyResting =
+ chartStatus === "ready" && chartPhase === "ready" && isLoaded;
+
+ const participates = useMemo(
+ () =>
+ heatmapLoadingCellParticipates(
+ cell.column,
+ cell.row,
+ loadingCellRandomness
+ ),
+ [cell.column, cell.row, loadingCellRandomness]
+ );
+
+ const showShimmerPulse =
+ animateCells && showLoadingCellsLayer && participates && isLoadingResting;
+
+ const fadeDurationSec = resolveHeatmapEnterFadeDurationSec(
+ enterTransition,
+ animationDuration
+ );
+ const delayMs = computeHeatmapEnterFadeDelayMs({
+ column: cell.column,
+ row: cell.row,
+ revealEpoch,
+ animationDurationMs: animationDuration,
+ enterStaggerScale,
+ fadeDurationSec,
+ });
+ const staggeredTransition = transitionWithDelay(
+ enterTransition,
+ delayMs / 1000
+ );
+
+ const readyDataOpacity = isFaded ? fadedOpacity : 1;
+
+ useEffect(() => {
+ if (!showShimmerPulse) {
+ wasShimmerPulsingRef.current = false;
+ pulseOpacity.set(0);
+ return;
+ }
+
+ const isFreshLoadingPulse =
+ isLoadingResting && !wasShimmerPulsingRef.current;
+ wasShimmerPulsingRef.current = true;
+
+ if (isFreshLoadingPulse) {
+ pulseOpacity.set(0);
+ }
+
+ let cancelled = false;
+ let current: ReturnType | undefined;
+
+ const pulse = async () => {
+ while (!cancelled) {
+ const target = Math.random() * loadingCellMaxOpacity;
+ const duration = 0.35 + Math.random() * 0.85;
+ current = animate(pulseOpacity, target, {
+ duration,
+ ease: [0.45, 0, 0.55, 1],
+ });
+
+ try {
+ await current;
+ } catch {
+ break;
+ }
+
+ if (cancelled) {
+ break;
+ }
+
+ await new Promise((resolve) => {
+ window.setTimeout(resolve, 80 + Math.random() * 420);
+ });
+ }
+ };
+
+ pulse().catch(() => undefined);
+
+ return () => {
+ cancelled = true;
+ current?.stop();
+ };
+ }, [isLoadingResting, loadingCellMaxOpacity, pulseOpacity, showShimmerPulse]);
+
+ useEffect(() => {
+ syncHeatmapCellLayerOpacities({
+ animateCells,
+ chartStatus,
+ dataOpacity,
+ isAwaitingLoadingConceal,
+ isExitingToLoading,
+ isLoadingResting,
+ isReadyResting,
+ isRevealActive,
+ participates,
+ pulseOpacity,
+ readyDataOpacity,
+ shimmerOpacity,
+ showLoadingCellsLayer,
+ showShimmerPulse,
+ staggeredTransition,
+ });
+ }, [
+ animateCells,
+ chartStatus,
+ dataOpacity,
+ isAwaitingLoadingConceal,
+ isExitingToLoading,
+ isLoadingResting,
+ isReadyResting,
+ isRevealActive,
+ participates,
+ pulseOpacity,
+ readyDataOpacity,
+ shimmerOpacity,
+ showLoadingCellsLayer,
+ showShimmerPulse,
+ staggeredTransition,
+ ]);
+
+ const cellProps = {
+ className: "visx-heatmap-rect",
+ height: cell.height,
+ rx: cornerRadius,
+ ry: cornerRadius,
+ width: cell.width,
+ x: cell.x,
+ y: cell.y,
+ };
+
+ return (
+
+
+ onEnter(cell.column, cell.row, bin, cell.x, cell.y)
+ }
+ onPointerLeave={onLeave}
+ style={{
+ cursor: interactive ? "pointer" : undefined,
+ opacity: dataOpacity,
+ }}
+ />
+
+
+ );
+});
+
+export const HeatmapCells = memo(function HeatmapCells({
+ cornerRadius = 2,
+ colorScale: colorScaleProp,
+ fadedOpacity = HEATMAP_FADED_OPACITY,
+ interactive = true,
+}: HeatmapCellsProps) {
+ const {
+ data,
+ binWidth,
+ binHeight,
+ gap,
+ margin,
+ xScale,
+ yScale,
+ chartStatus,
+ colorScale: contextColorScale,
+ fillScale: contextFillScale,
+ } = useHeatmap();
+ const colorScale = colorScaleProp ?? contextColorScale;
+ const fillScale = contextFillScale;
+ const cellsInteractive = interactive && chartStatus !== "loading";
+ const {
+ hoveredCell,
+ hoveredLegendLevel,
+ setHoveredCell,
+ setHoveredLegendLevel,
+ setTooltipData,
+ } = useHeatmapInteraction();
+
+ const handleCellEnter = useCallback(
+ (column: number, row: number, bin: HeatmapBin, x: number, y: number) => {
+ if (!cellsInteractive) {
+ return;
+ }
+
+ setHoveredLegendLevel(null);
+ setHoveredCell({ column, row });
+ setTooltipData({
+ column,
+ row,
+ count: bin.count,
+ date: bin.date,
+ x: margin.left + x + binWidth / 2,
+ y: margin.top + y + binHeight / 2,
+ });
+ },
+ [
+ binHeight,
+ binWidth,
+ cellsInteractive,
+ margin.left,
+ margin.top,
+ setHoveredCell,
+ setHoveredLegendLevel,
+ setTooltipData,
+ ]
+ );
+
+ const handleCellLeave = useCallback(() => {
+ if (!cellsInteractive) {
+ return;
+ }
+
+ setHoveredCell(null);
+ setTooltipData(null);
+ }, [cellsInteractive, setHoveredCell, setTooltipData]);
+
+ const isCellHovering = cellsInteractive && hoveredCell !== null;
+ const isLevelHovering = cellsInteractive && hoveredLegendLevel !== null;
+
+ return (
+
+ binHeight={binHeight}
+ bins={(column) => column.bins}
+ binWidth={binWidth}
+ colorScale={(count) =>
+ colorScale(typeof count === "number" ? count : count?.valueOf())
+ }
+ count={(bin) => bin.count}
+ data={data}
+ gap={gap}
+ xScale={xScale}
+ yScale={yScale}
+ >
+ {(cells) => (
+
+ {cells.flatMap((column) =>
+ column.map((cell) => {
+ const bin = data[cell.column]?.bins[cell.row];
+ if (!bin) {
+ return null;
+ }
+
+ const isFaded = computeHeatmapCellFaded(
+ isCellHovering,
+ isLevelHovering,
+ hoveredCell,
+ hoveredLegendLevel,
+ cell,
+ bin.count
+ );
+
+ return (
+
+ );
+ })
+ )}
+
+ )}
+
+ );
+});
+
+HeatmapCells.displayName = "HeatmapCells";
+
+export default HeatmapCells;
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/heatmap/heatmap-chart-loading.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/heatmap/heatmap-chart-loading.tsx
new file mode 100644
index 0000000..6eda7c5
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/heatmap/heatmap-chart-loading.tsx
@@ -0,0 +1,60 @@
+"use client";
+
+import { useMemo } from "react";
+import type { Margin } from "../chart-context";
+import { generateHeatmapSkeletonFromTarget } from "./generate-heatmap-skeleton-data";
+import { HeatmapCells } from "./heatmap-cells";
+import { HeatmapChart } from "./heatmap-chart";
+import type { HeatmapColumn } from "./heatmap-context";
+import { HeatmapXAxis } from "./heatmap-x-axis";
+import { HeatmapYAxis } from "./heatmap-y-axis";
+
+export interface HeatmapChartLoadingProps {
+ /** Target column data used to size the skeleton grid. */
+ data: HeatmapColumn[];
+ /** Visible time range — filters week columns that overlap the domain. */
+ xDomain?: [Date, Date];
+ /** Chart margins */
+ margin?: Partial;
+ /** Gap between cells in pixels. Default: 2 */
+ gap?: number;
+ /** Corner radius for each cell. Default: 2 */
+ cornerRadius?: number;
+ /** Centered shimmer label text. Default: "Loading" */
+ label?: string;
+ /** Additional class name for the container */
+ className?: string;
+}
+
+export function HeatmapChartLoading({
+ data,
+ xDomain,
+ margin,
+ gap = 2,
+ cornerRadius = 2,
+ label = "Loading",
+ className = "",
+}: HeatmapChartLoadingProps) {
+ const skeletonData = useMemo(
+ () => generateHeatmapSkeletonFromTarget(data),
+ [data]
+ );
+
+ return (
+
+
+
+
+
+ );
+}
+
+export default HeatmapChartLoading;
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/heatmap/heatmap-chart.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/heatmap/heatmap-chart.tsx
new file mode 100644
index 0000000..0ff2f9a
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/heatmap/heatmap-chart.tsx
@@ -0,0 +1,655 @@
+"use client";
+
+import { ParentSize } from "@visx/responsive";
+import { scaleLinear, scaleTime } from "@visx/scale";
+import type { Transition } from "motion/react";
+import { useReducedMotion } from "motion/react";
+import {
+ type ReactNode,
+ useCallback,
+ useEffect,
+ useLayoutEffect,
+ useMemo,
+ useRef,
+ useState,
+} from "react";
+import { cn } from "@/lib/utils";
+import type { Margin } from "../chart-context";
+import { ChartLoadingLabel } from "../chart-loading-label";
+import {
+ type ChartPhase,
+ type ChartStatus,
+ DEFAULT_CHART_STATUS,
+ resolveRestingChartPhase,
+} from "../chart-phase";
+import {
+ HEATMAP_DEFAULT_ENTER_DURATION_MS,
+ HEATMAP_DEFAULT_ENTER_TRANSITION,
+ HEATMAP_DEFAULT_LOADING_CELL_MAX_OPACITY,
+ HEATMAP_DEFAULT_LOADING_CELL_RANDOMNESS,
+ HEATMAP_LOADING_CHART_OPACITY,
+ HEATMAP_LOADING_CONCEAL_MS,
+} from "./heatmap-animation";
+import type { HeatmapLevelColors, HeatmapLevelStyles } from "./heatmap-colors";
+import {
+ buildHeatmapColorScaleFromStyles,
+ buildHeatmapFillScale,
+ resolveHeatmapLevelStyles,
+} from "./heatmap-colors";
+import {
+ type HeatmapColumn,
+ type HeatmapContextValue,
+ HeatmapInteractionRoot,
+ HeatmapProvider,
+ type HeatmapRevealMode,
+ useHeatmap,
+ useHeatmapInteraction,
+} from "./heatmap-context";
+import { HeatmapPatternDefs } from "./heatmap-pattern-defs";
+import { filterHeatmapColumns, getHeatmapTimeExtent } from "./heatmap-utils";
+
+export type HeatmapLayout = "fluid" | "fill";
+
+export interface HeatmapChartProps {
+ /** Column data — one entry per week (or category) with row bins inside. */
+ data: HeatmapColumn[];
+ /** Visible time range — filters week columns that overlap the domain. */
+ xDomain?: [Date, Date];
+ /**
+ * Week columns used for cell sizing when `xDomain` is set. Keeps bin height
+ * stable while scrubbing (filtered columns can vary by ±1 at boundaries).
+ */
+ sizingColumnCount?: number;
+ /**
+ * `fluid` — width drives square cells; chart height hugs the grid (GitHub-style).
+ * `fill` — expands cells to fill the parent.
+ */
+ layout?: HeatmapLayout;
+ /** Chart margins */
+ margin?: Partial;
+ /** Fixed cell size in pixels. When 0, cells are square and sized to fit the plot. Default: 0 */
+ binSize?: number;
+ /** Gap between cells in pixels. Default: 2 */
+ gap?: number;
+ /** Override the default color scale. */
+ colorScale?: (count: number | null | undefined) => string;
+ /** Per-level colors for the Less → More scale (5 entries). */
+ levelColors?: HeatmapLevelColors;
+ /** Per-level fill styles (color and optional pattern). Takes precedence over `levelColors`. */
+ levelStyles?: HeatmapLevelStyles;
+ /** Optional outer aspect ratio (e.g. "2 / 1"). Omit to fill the parent. */
+ aspectRatio?: string;
+ /** Additional class name for the container */
+ className?: string;
+ /** Fetch / display status. Default: `"ready"`. */
+ status?: ChartStatus;
+ /** Centered label while loading. */
+ loadingLabel?: string;
+ /** Enter animation duration in milliseconds. Default: 1600 */
+ animationDuration?: number;
+ /** Motion enter transition (spring or cubic-bezier tween). */
+ enterTransition?: Transition;
+ /** Signature of motion URL state — triggers enter replay when it changes. */
+ revealSignature?: string;
+ /** Scales wave stagger delays (1 = default). */
+ enterStaggerScale?: number;
+ /** Play enter fade-in / loading shimmer animations. Default: true */
+ animate?: boolean;
+ /** Chart opacity while loading. Default: 0.5 */
+ loadingOpacity?: number;
+ /** Show loading cell shimmer while loading. Default: true */
+ showLoadingCells?: boolean;
+ /** Max opacity loading cells animate toward (0–1). Default: 0.5 */
+ loadingCellMaxOpacity?: number;
+ /** Share of cells that participate in loading shimmer (0–1). Default: 0.65 */
+ loadingCellRandomness?: number;
+ /** Child components (HeatmapCells, HeatmapXAxis, HeatmapYAxis) */
+ children: ReactNode;
+}
+
+const DEFAULT_MARGIN: Margin = { top: 28, right: 16, bottom: 0, left: 40 };
+
+function computeHeatmapDimensions({
+ width,
+ parentHeight,
+ margin,
+ columnCount,
+ rowCount,
+ binSize,
+ layout,
+}: {
+ width: number;
+ parentHeight: number;
+ margin: Margin;
+ columnCount: number;
+ rowCount: number;
+ binSize: number;
+ layout: HeatmapLayout;
+}) {
+ const innerWidth = Math.max(width - margin.left - margin.right, 0);
+ const availableHeight = Math.max(
+ parentHeight - margin.top - margin.bottom,
+ 0
+ );
+
+ let binWidth: number;
+ let binHeight: number;
+
+ if (binSize > 0) {
+ binWidth = binSize;
+ binHeight = binSize;
+ } else if (layout === "fluid") {
+ const cellSize = innerWidth / columnCount;
+ binWidth = cellSize;
+ binHeight = cellSize;
+ } else {
+ const cellSize = Math.min(
+ innerWidth / columnCount,
+ availableHeight / rowCount
+ );
+ binWidth = cellSize;
+ binHeight = cellSize;
+ }
+
+ const innerHeight = rowCount * binHeight;
+ const height =
+ layout === "fluid"
+ ? margin.top + innerHeight + margin.bottom
+ : Math.max(parentHeight, margin.top + innerHeight + margin.bottom);
+
+ return {
+ binWidth,
+ binHeight,
+ innerWidth,
+ innerHeight,
+ height,
+ width,
+ };
+}
+
+interface HeatmapChartInnerProps {
+ width: number;
+ height: number;
+ data: HeatmapColumn[];
+ xDomain?: [Date, Date];
+ sizingColumnCount?: number;
+ margin: Margin;
+ binSize: number;
+ gap: number;
+ layout: HeatmapLayout;
+ colorScale: (count: number | null | undefined) => string;
+ fillScale: (count: number | null | undefined) => string;
+ levelStyles: HeatmapLevelStyles;
+ chartStatus: ChartStatus;
+ chartPhase: ChartPhase;
+ isLoaded: boolean;
+ revealEpoch: number;
+ animationDuration: number;
+ enterTransition?: Transition;
+ enterStaggerScale: number;
+ animateCells: boolean;
+ loadingOpacity: number;
+ showLoadingCells: boolean;
+ loadingCellMaxOpacity: number;
+ loadingCellRandomness: number;
+ revealMode: HeatmapRevealMode;
+ loadingLabel?: string;
+ showLoadingLabel: boolean;
+ children: ReactNode;
+}
+
+function HeatmapChartInner({
+ width,
+ height: parentHeight,
+ data,
+ xDomain,
+ sizingColumnCount: sizingColumnCountProp,
+ margin,
+ binSize,
+ gap,
+ layout,
+ colorScale,
+ fillScale,
+ levelStyles,
+ chartStatus,
+ chartPhase,
+ isLoaded,
+ revealEpoch,
+ animationDuration,
+ enterTransition,
+ enterStaggerScale,
+ animateCells,
+ loadingOpacity,
+ showLoadingCells,
+ loadingCellMaxOpacity,
+ loadingCellRandomness,
+ revealMode,
+ loadingLabel,
+ showLoadingLabel,
+ children,
+}: HeatmapChartInnerProps) {
+ const containerRef = useRef(null);
+
+ const visibleData = useMemo(
+ () => filterHeatmapColumns(data, xDomain),
+ [data, xDomain]
+ );
+
+ const visibleColumnCount = Math.max(visibleData.length, 1);
+ const columnCount =
+ xDomain && sizingColumnCountProp != null
+ ? Math.max(sizingColumnCountProp, 1)
+ : visibleColumnCount;
+ const rowCount = Math.max(visibleData[0]?.bins.length ?? 7, 1);
+
+ const {
+ binWidth,
+ binHeight,
+ innerWidth,
+ innerHeight,
+ height,
+ width: chartWidth,
+ } = useMemo(
+ () =>
+ computeHeatmapDimensions({
+ width,
+ parentHeight,
+ margin,
+ columnCount,
+ rowCount,
+ binSize,
+ layout,
+ }),
+ [binSize, columnCount, layout, margin, parentHeight, rowCount, width]
+ );
+
+ const xScale = useMemo(
+ () => (columnIndex: number) => columnIndex * binWidth,
+ [binWidth]
+ );
+ const yScale = useMemo(
+ () => (rowIndex: number) => rowIndex * binHeight,
+ [binHeight]
+ );
+
+ const timeExtent = useMemo(() => getHeatmapTimeExtent(data), [data]);
+
+ const timeXScale = useMemo(() => {
+ const domain = timeExtent ?? [new Date(), new Date()];
+ return scaleTime({
+ domain,
+ range: [0, innerWidth],
+ });
+ }, [innerWidth, timeExtent]);
+
+ const brushYScale = useMemo(
+ () =>
+ scaleLinear({
+ domain: [0, 1],
+ range: [innerHeight, 0],
+ }),
+ [innerHeight]
+ );
+
+ const contextValue = useMemo(
+ () => ({
+ data: visibleData,
+ width: chartWidth,
+ height,
+ innerWidth,
+ innerHeight,
+ margin,
+ binWidth,
+ binHeight,
+ gap,
+ xScale,
+ yScale,
+ timeXScale,
+ brushYScale,
+ isReady: chartWidth >= 10 && height >= 10,
+ colorScale,
+ fillScale,
+ levelStyles,
+ containerRef,
+ chartStatus,
+ chartPhase,
+ isLoaded,
+ revealEpoch,
+ animationDuration,
+ enterTransition,
+ enterStaggerScale,
+ animateCells,
+ loadingOpacity,
+ showLoadingCells,
+ loadingCellMaxOpacity,
+ loadingCellRandomness,
+ revealMode,
+ loadingLabel,
+ showLoadingLabel,
+ }),
+ [
+ animateCells,
+ animationDuration,
+ binHeight,
+ binWidth,
+ brushYScale,
+ chartPhase,
+ chartStatus,
+ chartWidth,
+ colorScale,
+ fillScale,
+ enterStaggerScale,
+ enterTransition,
+ gap,
+ height,
+ innerHeight,
+ innerWidth,
+ isLoaded,
+ loadingCellMaxOpacity,
+ loadingCellRandomness,
+ loadingLabel,
+ levelStyles,
+ loadingOpacity,
+ margin,
+ revealMode,
+ showLoadingCells,
+ showLoadingLabel,
+ revealEpoch,
+ timeXScale,
+ visibleData,
+ xScale,
+ yScale,
+ ]
+ );
+
+ if (chartWidth < 10 || height < 10) {
+ return null;
+ }
+
+ return (
+
+
+ {children}
+
+
+ );
+}
+
+function HeatmapChartSurface({
+ layout,
+ children,
+}: {
+ layout: HeatmapLayout;
+ children: ReactNode;
+}) {
+ const {
+ containerRef,
+ height,
+ width,
+ margin,
+ levelStyles,
+ chartPhase,
+ loadingOpacity,
+ loadingLabel,
+ showLoadingLabel,
+ } = useHeatmap();
+ const { clearInteraction } = useHeatmapInteraction();
+ const reducedOpacity =
+ chartPhase === "loading" || chartPhase === "exitingReady"
+ ? loadingOpacity
+ : 1;
+
+ return (
+
+
+
+ {children}
+
+ {showLoadingLabel && loadingLabel?.trim() ? (
+
+
+
+ ) : null}
+
+ );
+}
+
+function useHeatmapChartLifecycle({
+ chartStatus,
+ animationDuration,
+ revealSignature = "",
+ animate,
+}: {
+ chartStatus: ChartStatus;
+ animationDuration: number;
+ revealSignature?: string;
+ animate: boolean;
+}) {
+ const reducedMotion = useReducedMotion();
+ const [chartPhase, setChartPhase] = useState(() =>
+ resolveRestingChartPhase(chartStatus)
+ );
+ const [isLoaded, setIsLoaded] = useState(
+ () => chartStatus === "ready" && (!animate || animationDuration <= 0)
+ );
+ const [revealEpoch, setRevealEpoch] = useState(0);
+ const [revealMode, setRevealMode] = useState(null);
+ const prevStatusRef = useRef(chartStatus);
+ const phaseRef = useRef(chartPhase);
+ phaseRef.current = chartPhase;
+
+ const animateCells = animate && !reducedMotion;
+ const animateEnter = animateCells && animationDuration > 0;
+
+ const finishReveal = useCallback(() => {
+ setIsLoaded(true);
+ setChartPhase("ready");
+ setRevealMode(null);
+ }, []);
+
+ const beginReveal = useCallback(() => {
+ setRevealMode("enter");
+ setRevealEpoch((epoch) => epoch + 1);
+ setIsLoaded(false);
+ setChartPhase("revealing");
+ if (!animateEnter) {
+ finishReveal();
+ }
+ }, [animateEnter, finishReveal]);
+
+ useLayoutEffect(() => {
+ const prevStatus = prevStatusRef.current;
+ if (prevStatus === chartStatus) {
+ return;
+ }
+ prevStatusRef.current = chartStatus;
+
+ if (chartStatus === "ready" && prevStatus === "loading") {
+ setRevealMode("fromLoading");
+ setIsLoaded(false);
+ setChartPhase("revealing");
+ return;
+ }
+
+ if (chartStatus === "loading" && prevStatus === "ready") {
+ setRevealMode(null);
+ setIsLoaded(false);
+ setChartPhase("exitingReady");
+ }
+ }, [chartStatus]);
+
+ useEffect(() => {
+ if (chartPhase !== "exitingReady") {
+ return;
+ }
+
+ const timer = window.setTimeout(() => {
+ setChartPhase("loading");
+ }, HEATMAP_LOADING_CONCEAL_MS);
+
+ return () => window.clearTimeout(timer);
+ }, [chartPhase]);
+
+ // biome-ignore lint/correctness/useExhaustiveDependencies: revealSignature replays enter
+ useEffect(() => {
+ if (!animateEnter) {
+ setIsLoaded(true);
+ setChartPhase(resolveRestingChartPhase(chartStatus));
+ return;
+ }
+ if (chartStatus !== "ready") {
+ return;
+ }
+ if (phaseRef.current !== "ready") {
+ return;
+ }
+
+ beginReveal();
+ }, [
+ animateEnter,
+ animationDuration,
+ beginReveal,
+ chartStatus,
+ revealSignature,
+ ]);
+
+ // biome-ignore lint/correctness/useExhaustiveDependencies: revealEpoch replays finish timer
+ useEffect(() => {
+ if (!animateEnter || chartPhase !== "revealing") {
+ return;
+ }
+
+ const finishTimer = window.setTimeout(finishReveal, animationDuration);
+ return () => window.clearTimeout(finishTimer);
+ }, [animateEnter, animationDuration, chartPhase, finishReveal, revealEpoch]);
+
+ return {
+ chartPhase,
+ isLoaded,
+ revealEpoch,
+ revealMode,
+ animateCells,
+ };
+}
+
+export function HeatmapChart({
+ data,
+ xDomain,
+ sizingColumnCount,
+ layout = "fluid",
+ margin: marginProp,
+ binSize = 0,
+ gap = 2,
+ colorScale: colorScaleProp,
+ levelColors,
+ levelStyles: levelStylesProp,
+ aspectRatio,
+ className = "",
+ status = DEFAULT_CHART_STATUS,
+ loadingLabel,
+ animationDuration = HEATMAP_DEFAULT_ENTER_DURATION_MS,
+ enterTransition = HEATMAP_DEFAULT_ENTER_TRANSITION,
+ revealSignature = "",
+ enterStaggerScale = 1,
+ animate = true,
+ loadingOpacity = HEATMAP_LOADING_CHART_OPACITY,
+ showLoadingCells = true,
+ loadingCellMaxOpacity = HEATMAP_DEFAULT_LOADING_CELL_MAX_OPACITY,
+ loadingCellRandomness = HEATMAP_DEFAULT_LOADING_CELL_RANDOMNESS,
+ children,
+}: HeatmapChartProps) {
+ const margin = { ...DEFAULT_MARGIN, ...marginProp };
+ const levelStyles = useMemo(
+ () => resolveHeatmapLevelStyles(levelColors, levelStylesProp),
+ [levelColors, levelStylesProp]
+ );
+ const colorScale = useMemo(
+ () => colorScaleProp ?? buildHeatmapColorScaleFromStyles(levelStyles),
+ [colorScaleProp, levelStyles]
+ );
+ const fillScale = useMemo(
+ () => buildHeatmapFillScale(levelStyles),
+ [levelStyles]
+ );
+
+ const { chartPhase, isLoaded, revealEpoch, revealMode, animateCells } =
+ useHeatmapChartLifecycle({
+ chartStatus: status,
+ animationDuration,
+ revealSignature,
+ animate,
+ });
+
+ const showLoadingLabel = Boolean(
+ loadingLabel?.trim() &&
+ status === "loading" &&
+ (chartPhase === "loading" || chartPhase === "exitingReady")
+ );
+
+ return (
+
+
+ {({ width, height: parentHeight }) => (
+
+ {children}
+
+ )}
+
+
+ );
+}
+
+HeatmapChart.displayName = "HeatmapChart";
+
+export default HeatmapChart;
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/heatmap/heatmap-colors.ts b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/heatmap/heatmap-colors.ts
new file mode 100644
index 0000000..319968a
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/heatmap/heatmap-colors.ts
@@ -0,0 +1,188 @@
+import { CHART_SCALE_VARS } from "../chart-scale";
+import type { PatternPresetId } from "../pattern-preset";
+import { getHeatmapContributionLevel } from "./heatmap-utils";
+
+/** Default Less → More scale using {@link CHART_SCALE_VARS}. */
+export const HEATMAP_DEFAULT_LEVEL_COLORS = CHART_SCALE_VARS;
+
+export type HeatmapLevelColors = readonly [
+ string,
+ string,
+ string,
+ string,
+ string,
+];
+
+export type HeatmapLevelFillMode = "solid" | "pattern";
+
+export interface HeatmapLevelStyle {
+ color: string;
+ fillMode?: HeatmapLevelFillMode;
+ pattern?: PatternPresetId;
+ patternColor?: string;
+ patternScale?: number;
+ patternStrokeWidth?: number;
+ patternRadius?: number;
+ patternComplement?: boolean;
+ patternFill?: string;
+ patternTileBackground?: string;
+ patternOpacity?: number;
+ patternDotsFill?: boolean;
+}
+
+export type HeatmapLevelStyles = readonly [
+ HeatmapLevelStyle,
+ HeatmapLevelStyle,
+ HeatmapLevelStyle,
+ HeatmapLevelStyle,
+ HeatmapLevelStyle,
+];
+
+export const HEATMAP_DEFAULT_LEVEL_STYLES = [
+ {
+ color: HEATMAP_DEFAULT_LEVEL_COLORS[0],
+ fillMode: "solid",
+ pattern: "none",
+ },
+ {
+ color: HEATMAP_DEFAULT_LEVEL_COLORS[1],
+ fillMode: "solid",
+ pattern: "none",
+ },
+ {
+ color: HEATMAP_DEFAULT_LEVEL_COLORS[2],
+ fillMode: "solid",
+ pattern: "none",
+ },
+ {
+ color: HEATMAP_DEFAULT_LEVEL_COLORS[3],
+ fillMode: "solid",
+ pattern: "none",
+ },
+ {
+ color: HEATMAP_DEFAULT_LEVEL_COLORS[4],
+ fillMode: "solid",
+ pattern: "none",
+ },
+] as const satisfies HeatmapLevelStyles;
+
+export function heatmapLevelPatternId(level: number): string {
+ return `heatmap-level-${level}`;
+}
+
+export function isHeatmapLevelPattern(style: HeatmapLevelStyle): boolean {
+ return (
+ style.fillMode === "pattern" &&
+ style.pattern != null &&
+ style.pattern !== "none"
+ );
+}
+
+export function heatmapPatternStrokeFallback(color: string): string {
+ return `color-mix(in oklch, ${color} 45%, white)`;
+}
+
+export function heatmapLevelPatternRenderOptions(style: HeatmapLevelStyle) {
+ const preset = style.pattern ?? "diagonal";
+ let defaultScale = 1;
+ if (preset === "cross") {
+ defaultScale = 1.33;
+ }
+
+ return {
+ color:
+ style.patternColor?.trim() ||
+ (preset === "accent"
+ ? "#e879f9"
+ : heatmapPatternStrokeFallback(style.color)),
+ tileBackground: style.patternTileBackground?.trim() || style.color,
+ scale: style.patternScale ?? defaultScale,
+ strokeWidth: style.patternStrokeWidth,
+ radius: style.patternRadius,
+ complement: style.patternComplement,
+ fill: style.patternFill?.trim() || undefined,
+ dotFill: style.patternDotsFill,
+ };
+}
+
+export function heatmapLevelCellFillOpacity(style: HeatmapLevelStyle): number {
+ if (!isHeatmapLevelPattern(style)) {
+ return 1;
+ }
+ return style.patternOpacity ?? 1;
+}
+
+export function levelColorsFromStyles(
+ levelStyles: HeatmapLevelStyles
+): HeatmapLevelColors {
+ return [
+ levelStyles[0].color,
+ levelStyles[1].color,
+ levelStyles[2].color,
+ levelStyles[3].color,
+ levelStyles[4].color,
+ ];
+}
+
+export function levelStylesFromColors(
+ levelColors: HeatmapLevelColors
+): HeatmapLevelStyles {
+ return [
+ { color: levelColors[0], fillMode: "solid", pattern: "none" },
+ { color: levelColors[1], fillMode: "solid", pattern: "none" },
+ { color: levelColors[2], fillMode: "solid", pattern: "none" },
+ { color: levelColors[3], fillMode: "solid", pattern: "none" },
+ { color: levelColors[4], fillMode: "solid", pattern: "none" },
+ ];
+}
+
+export function resolveHeatmapLevelStyles(
+ levelColors?: HeatmapLevelColors,
+ levelStyles?: HeatmapLevelStyles
+): HeatmapLevelStyles {
+ if (levelStyles) {
+ return levelStyles;
+ }
+ if (levelColors) {
+ return levelStylesFromColors(levelColors);
+ }
+ return HEATMAP_DEFAULT_LEVEL_STYLES;
+}
+
+export function buildHeatmapColorScale(
+ levelColors: HeatmapLevelColors
+): (count: number | null | undefined) => string {
+ return buildHeatmapColorScaleFromStyles(levelStylesFromColors(levelColors));
+}
+
+export function buildHeatmapColorScaleFromStyles(
+ levelStyles: HeatmapLevelStyles
+): (count: number | null | undefined) => string {
+ return (count: number | null | undefined) => {
+ const level = getHeatmapContributionLevel(count ?? 0);
+ return levelStyles[level]?.color ?? levelStyles[0].color;
+ };
+}
+
+export function buildHeatmapFillScale(
+ levelStyles: HeatmapLevelStyles
+): (count: number | null | undefined) => string {
+ return (count: number | null | undefined) => {
+ const level = getHeatmapContributionLevel(count ?? 0);
+ const style = levelStyles[level] ?? levelStyles[0];
+
+ if (isHeatmapLevelPattern(style)) {
+ return `url(#${heatmapLevelPatternId(level)})`;
+ }
+
+ return style.color;
+ };
+}
+
+export const defaultHeatmapColorScale = buildHeatmapColorScale(
+ HEATMAP_DEFAULT_LEVEL_COLORS
+);
+
+export const defaultHeatmapFillScale = buildHeatmapFillScale(
+ HEATMAP_DEFAULT_LEVEL_STYLES
+);
diff --git a/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/heatmap/heatmap-context.tsx b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/heatmap/heatmap-context.tsx
new file mode 100644
index 0000000..b3de81b
--- /dev/null
+++ b/seo_mode/seo_mode/SEO-farm/vendor/bklit-ui/packages/ui/src/charts/heatmap/heatmap-context.tsx
@@ -0,0 +1,212 @@
+"use client";
+
+import type { scaleLinear, scaleTime } from "@visx/scale";
+import type { Transition } from "motion/react";
+import {
+ createContext,
+ type Dispatch,
+ type ReactNode,
+ type RefObject,
+ type SetStateAction,
+ useCallback,
+ useContext,
+ useMemo,
+ useState,
+} from "react";
+import { cn } from "@/lib/utils";
+import type { Margin } from "../chart-context";
+import type { ChartPhase, ChartStatus } from "../chart-phase";
+import {
+ HEATMAP_DEFAULT_LEVEL_COLORS,
+ type HeatmapLevelStyles,
+} from "./heatmap-colors";
+
+type HeatmapTimeScale = ReturnType>;
+type HeatmapLinearScale = ReturnType>;
+
+export interface HeatmapBin {
+ count: number;
+ bin: number;
+ date: Date;
+}
+
+export interface HeatmapColumn {
+ bin: number;
+ bins: HeatmapBin[];
+}
+
+export interface HeatmapTooltipData {
+ column: number;
+ row: number;
+ count: number;
+ date: Date;
+ x: number;
+ y: number;
+}
+
+export interface HeatmapInteractionContextValue {
+ hoveredCell: { column: number; row: number } | null;
+ hoveredLegendLevel: number | null;
+ tooltipData: HeatmapTooltipData | null;
+ setHoveredCell: (cell: { column: number; row: number } | null) => void;
+ setHoveredLegendLevel: (level: number | null) => void;
+ setTooltipData: Dispatch>;
+ clearInteraction: () => void;
+}
+
+export interface HeatmapContextValue {
+ data: HeatmapColumn[];
+ width: number;
+ height: number;
+ innerWidth: number;
+ innerHeight: number;
+ margin: Margin;
+ binWidth: number;
+ binHeight: number;
+ gap: number;
+ xScale: (columnIndex: number) => number;
+ yScale: (rowIndex: number) => number;
+ timeXScale: HeatmapTimeScale;
+ brushYScale: HeatmapLinearScale;
+ isReady: boolean;
+ colorScale: (count: number | null | undefined) => string;
+ fillScale: (count: number | null | undefined) => string;
+ levelStyles: HeatmapLevelStyles;
+ containerRef: RefObject;
+ chartStatus: ChartStatus;
+ chartPhase: ChartPhase;
+ isLoaded: boolean;
+ revealEpoch: number;
+ animationDuration: number;
+ enterTransition?: Transition;
+ enterStaggerScale: number;
+ animateCells: boolean;
+ loadingOpacity: number;
+ showLoadingCells: boolean;
+ loadingCellMaxOpacity: number;
+ loadingCellRandomness: number;
+ revealMode: HeatmapRevealMode;
+ loadingLabel?: string;
+ showLoadingLabel: boolean;
+}
+
+/** How cells transition into view after loading or on first mount. */
+export type HeatmapRevealMode = "enter" | "fromLoading" | null;
+
+const HeatmapContext = createContext