diff --git a/components/recharts-poc/RechartsPocPage.tsx b/components/recharts-poc/RechartsPocPage.tsx new file mode 100644 index 00000000..3f227ab3 --- /dev/null +++ b/components/recharts-poc/RechartsPocPage.tsx @@ -0,0 +1,52 @@ +import { useRouter } from 'next/router'; + +import { theme } from './primitives/theme'; +import { EngagementSection } from './sections/EngagementSection'; +import { TrendsSection } from './sections/TrendsSection'; + +const CSS_VARS: Record = { + '--rcp-background': theme.background, + '--rcp-surface': theme.surface, + '--rcp-border': theme.border, + '--rcp-border-strong': theme.borderStrong, + '--rcp-text': theme.text, + '--rcp-text-muted': theme.textMuted, + '--rcp-text-subtle': theme.textSubtle, + '--rcp-tooltip-bg': theme.tooltipBackground, + '--rcp-tooltip-text': theme.tooltipText, +}; + +export function RechartsPocPage() { + const router = useRouter(); + const perfMode = router.query.perf === '1'; + + return ( +
+
+
+

+ Engagement Dashboard +

+

+ Recharts POC — engagement layout.{' '} + + ?perf=1 + {' '} + disables animations. + {perfMode ? ( + + perf mode + + ) : null} +

+
+ + + +
+
+ ); +} diff --git a/components/recharts-poc/charts/FeatureAdoptionCard.tsx b/components/recharts-poc/charts/FeatureAdoptionCard.tsx new file mode 100644 index 00000000..c1a58d3d --- /dev/null +++ b/components/recharts-poc/charts/FeatureAdoptionCard.tsx @@ -0,0 +1,74 @@ +import { Bar, BarChart, CartesianGrid, Tooltip, XAxis, YAxis } from 'recharts'; + +import { FEATURE_ADOPTION } from '../data/engagement'; +import { ChartCard } from '../primitives/ChartCard'; +import { theme } from '../primitives/theme'; + +interface FeatureAdoptionCardProps { + perfMode: boolean; +} + +const NUMBER_FMT = new Intl.NumberFormat('en-US'); + +export function FeatureAdoptionCard(props: FeatureAdoptionCardProps) { + const { perfMode } = props; + return ( + + + + NUMBER_FMT.format(v).replace(/,/g, ' ')} + /> + + [NUMBER_FMT.format(Number(v)), 'Users']} + /> + + + + ); +} diff --git a/components/recharts-poc/charts/FeatureRetentionCard.tsx b/components/recharts-poc/charts/FeatureRetentionCard.tsx new file mode 100644 index 00000000..060ecade --- /dev/null +++ b/components/recharts-poc/charts/FeatureRetentionCard.tsx @@ -0,0 +1,38 @@ +import { useState } from 'react'; + +import { + FEATURE_RETENTION, + FEATURES, + type FeatureKey, +} from '../data/engagement'; +import { FeatureDropdown } from '../primitives/FeatureDropdown'; +import { SimpleLineCard } from './SimpleLineCard'; + +interface FeatureRetentionCardProps { + perfMode: boolean; +} + +export function FeatureRetentionCard(props: FeatureRetentionCardProps) { + const { perfMode } = props; + const [feature, setFeature] = useState('workout'); + + return ( + `${v}%`} + perfMode={perfMode} + actions={ + + } + /> + ); +} diff --git a/components/recharts-poc/charts/MonthlyActiveUsersCard.tsx b/components/recharts-poc/charts/MonthlyActiveUsersCard.tsx new file mode 100644 index 00000000..d38a2a4e --- /dev/null +++ b/components/recharts-poc/charts/MonthlyActiveUsersCard.tsx @@ -0,0 +1,21 @@ +import { MONTHLY_ACTIVE_USERS } from '../data/engagement'; +import { SimpleLineCard } from './SimpleLineCard'; + +interface MonthlyActiveUsersCardProps { + perfMode: boolean; +} + +export function MonthlyActiveUsersCard(props: MonthlyActiveUsersCardProps) { + const { perfMode } = props; + return ( + + ); +} diff --git a/components/recharts-poc/charts/NewUsersCohortCard.tsx b/components/recharts-poc/charts/NewUsersCohortCard.tsx new file mode 100644 index 00000000..2e163a1a --- /dev/null +++ b/components/recharts-poc/charts/NewUsersCohortCard.tsx @@ -0,0 +1,21 @@ +import { NEW_USERS_COHORT } from '../data/engagement'; +import { SimpleLineCard } from './SimpleLineCard'; + +interface NewUsersCohortCardProps { + perfMode: boolean; +} + +export function NewUsersCohortCard(props: NewUsersCohortCardProps) { + const { perfMode } = props; + return ( + + ); +} diff --git a/components/recharts-poc/charts/ProgramUtilizationCard.tsx b/components/recharts-poc/charts/ProgramUtilizationCard.tsx new file mode 100644 index 00000000..4973a929 --- /dev/null +++ b/components/recharts-poc/charts/ProgramUtilizationCard.tsx @@ -0,0 +1,100 @@ +import { + Area, + AreaChart, + CartesianGrid, + Label, + ReferenceLine, + Tooltip, + XAxis, + YAxis, +} from 'recharts'; + +import { PROGRAM_UTILIZATION, UTILIZATION_TARGET } from '../data/engagement'; +import { ChartCard } from '../primitives/ChartCard'; +import { theme } from '../primitives/theme'; + +interface ProgramUtilizationCardProps { + perfMode: boolean; +} + +export function ProgramUtilizationCard(props: ProgramUtilizationCardProps) { + const { perfMode } = props; + return ( + + + + + + + + + + + `${v}%`} + /> + [`${Number(v)}%`, 'Utilization']} + /> + + + + + + ); +} diff --git a/components/recharts-poc/charts/SimpleLineCard.tsx b/components/recharts-poc/charts/SimpleLineCard.tsx new file mode 100644 index 00000000..e504b3a1 --- /dev/null +++ b/components/recharts-poc/charts/SimpleLineCard.tsx @@ -0,0 +1,120 @@ +import { type ReactNode } from 'react'; +import { + CartesianGrid, + Line, + LineChart, + Tooltip, + XAxis, + YAxis, +} from 'recharts'; + +import type { SeriesPoint } from '../data/engagement'; +import { ChartCard } from '../primitives/ChartCard'; +import { theme } from '../primitives/theme'; + +interface SimpleLineCardProps { + title: string; + description: string; + data: SeriesPoint[]; + seriesLabel: string; + yTicks: number[]; + yDomain: [number, number]; + yFormatter?: (v: number) => string; + perfMode: boolean; + actions?: ReactNode; +} + +const NUMBER_FMT = new Intl.NumberFormat('en-US'); + +export function SimpleLineCard(props: SimpleLineCardProps) { + const { + title, + description, + data, + seriesLabel, + yTicks, + yDomain, + yFormatter, + perfMode, + actions, + } = props; + + return ( + + + + + NUMBER_FMT.format(v))} + /> + { + const n = Number(v); + return [ + yFormatter ? yFormatter(n) : NUMBER_FMT.format(n), + seriesLabel, + ]; + }} + /> + + + + + ); +} diff --git a/components/recharts-poc/charts/UserActivitySegmentsCard.tsx b/components/recharts-poc/charts/UserActivitySegmentsCard.tsx new file mode 100644 index 00000000..8fcaaff3 --- /dev/null +++ b/components/recharts-poc/charts/UserActivitySegmentsCard.tsx @@ -0,0 +1,109 @@ +import { Bar, BarChart, CartesianGrid, Tooltip, XAxis, YAxis } from 'recharts'; + +import { + ACTIVITY_SEGMENTS, + type WeeklyActivity, + WEEKLY_ACTIVITY, +} from '../data/engagement'; +import { ChartCard } from '../primitives/ChartCard'; +import { SEGMENT_COLORS, theme } from '../primitives/theme'; + +interface UserActivitySegmentsCardProps { + perfMode: boolean; +} + +const NUMBER_FMT = new Intl.NumberFormat('en-US'); + +export function UserActivitySegmentsCard(props: UserActivitySegmentsCardProps) { + const { perfMode } = props; + return ( + + + + + `${v}%`} + /> + } + /> + + + + + + + ); +} + +interface StackTooltipProps { + active?: boolean; + payload?: Array<{ payload?: WeeklyActivity }>; +} + +function StackTooltip(props: StackTooltipProps) { + const { active, payload } = props; + if (!active || !payload?.length) return null; + const data = payload[0]?.payload; + if (!data) return null; + return ( +
+
100%
+
+ {NUMBER_FMT.format(data.total)} +
+
+ ); +} + +export const ACTIVITY_LEGEND = ACTIVITY_SEGMENTS; diff --git a/components/recharts-poc/data/engagement.ts b/components/recharts-poc/data/engagement.ts new file mode 100644 index 00000000..a9bfff34 --- /dev/null +++ b/components/recharts-poc/data/engagement.ts @@ -0,0 +1,205 @@ +export interface KpiTile { + label: string; + value: number; + format: 'count' | 'percent'; + info: string; +} + +export const KPIS: KpiTile[] = [ + { + label: 'Utilization rate, %', + value: 36, + format: 'percent', + info: 'Share of MAU who actively used the program during the selected period.', + }, + { + label: 'MAU', + value: 200, + format: 'count', + info: 'Monthly active users — unique users with at least one tracked action this month.', + }, +]; + +export interface WeeklyActivity { + week: string; + high: number; + moderate: number; + low: number; + inactive: number; + total: number; +} + +export const WEEKLY_ACTIVITY: WeeklyActivity[] = [ + { + week: 'Week 1', + high: 22, + moderate: 31, + low: 27, + inactive: 20, + total: 463_120, + }, + { + week: 'Week 2', + high: 28, + moderate: 34, + low: 23, + inactive: 15, + total: 478_523, + }, + { + week: 'Week 3', + high: 24, + moderate: 32, + low: 26, + inactive: 18, + total: 471_044, + }, + { + week: 'Week 4', + high: 26, + moderate: 30, + low: 25, + inactive: 19, + total: 469_812, + }, +]; + +export const ACTIVITY_SEGMENTS = [ + { key: 'high', label: 'High activity' }, + { key: 'moderate', label: 'Moderate activity' }, + { key: 'low', label: 'Low activity' }, + { key: 'inactive', label: 'Inactive' }, +] as const; + +export type ActivitySegmentKey = (typeof ACTIVITY_SEGMENTS)[number]['key']; + +export const FEATURE_KEYS = [ + 'meal-logging', + 'workout', + 'water-intake', + 'habit-check-in', + 'sleep-log', + 'step-challenge', +] as const; + +export type FeatureKey = (typeof FEATURE_KEYS)[number]; + +export interface FeatureMeta { + key: FeatureKey; + label: string; +} + +export const FEATURES: FeatureMeta[] = [ + { key: 'meal-logging', label: 'Meal Logging' }, + { key: 'workout', label: 'Workout' }, + { key: 'water-intake', label: 'Water Intake' }, + { key: 'habit-check-in', label: 'Habit Check-in' }, + { key: 'sleep-log', label: 'Sleep Log' }, + { key: 'step-challenge', label: 'Step Challenge' }, +]; + +export interface FeatureAdoption { + key: FeatureKey; + label: string; + users: number; +} + +export const FEATURE_ADOPTION: FeatureAdoption[] = [ + { key: 'meal-logging', label: 'Meal Logging', users: 8_400 }, + { key: 'workout', label: 'Workout', users: 6_200 }, + { key: 'water-intake', label: 'Water Intake', users: 4_900 }, + { key: 'habit-check-in', label: 'Habit Check-in', users: 3_700 }, + { key: 'sleep-log', label: 'Sleep Log', users: 2_400 }, + { key: 'step-challenge', label: 'Step Challenge', users: 1_900 }, +]; + +export const MONTHS = [ + 'Jan', + 'Feb', + 'Mar', + 'Apr', + 'May', + 'Jun', + 'Jul', + 'Aug', + 'Sep', + 'Oct', + 'Nov', + 'Dec', +] as const; + +export type Month = (typeof MONTHS)[number]; + +export interface SeriesPoint { + month: Month; + actual: number | null; + projected: number | null; +} + +function buildSeries( + values: Array, + projectedFrom: number, +): SeriesPoint[] { + return MONTHS.map((month, idx) => { + const v = values[idx] ?? null; + if (idx < projectedFrom) { + return { month, actual: v, projected: null }; + } + if (idx === projectedFrom) { + return { month, actual: v, projected: v }; + } + return { month, actual: null, projected: v }; + }); +} + +export const MONTHLY_ACTIVE_USERS: SeriesPoint[] = buildSeries( + [1_000, 460, 360, 320, 290, 260, 240, 230, 220, 215, 195, 180], + 9, +); + +export const NEW_USERS_COHORT: SeriesPoint[] = buildSeries( + [80, 400, 820, 720, 600, 480, 380, 310, 230, 220, 210, 200], + 9, +); + +export type RetentionByFeature = Record; + +export const FEATURE_RETENTION: RetentionByFeature = { + 'meal-logging': buildSeries( + [100, 62, 48, 41, 37, 34, 32, 31, 29, 28, 27, 26], + 9, + ), + workout: buildSeries([100, 38, 31, 27, 25, 24, 23, 22, 22, 21, 20, 19], 9), + 'water-intake': buildSeries( + [100, 55, 44, 38, 34, 32, 30, 28, 27, 26, 25, 24], + 9, + ), + 'habit-check-in': buildSeries( + [100, 48, 37, 31, 28, 26, 24, 23, 22, 21, 20, 19], + 9, + ), + 'sleep-log': buildSeries( + [100, 42, 32, 27, 24, 22, 21, 20, 19, 18, 17, 16], + 9, + ), + 'step-challenge': buildSeries( + [100, 33, 24, 19, 16, 14, 13, 12, 11, 10, 10, 9], + 9, + ), +}; + +export interface UtilizationPoint { + month: Month; + utilization: number; +} + +export const PROGRAM_UTILIZATION: UtilizationPoint[] = [ + { month: 'Jan', utilization: 10 }, + { month: 'Feb', utilization: 13 }, + { month: 'Mar', utilization: 11 }, + { month: 'Apr', utilization: 8 }, + { month: 'May', utilization: 12 }, + { month: 'Jun', utilization: 18 }, +]; + +export const UTILIZATION_TARGET = 40; diff --git a/components/recharts-poc/index.ts b/components/recharts-poc/index.ts new file mode 100644 index 00000000..0d73aded --- /dev/null +++ b/components/recharts-poc/index.ts @@ -0,0 +1 @@ +export { RechartsPocPage } from './RechartsPocPage'; diff --git a/components/recharts-poc/kpi/EngagementKpiCard.tsx b/components/recharts-poc/kpi/EngagementKpiCard.tsx new file mode 100644 index 00000000..7c915fec --- /dev/null +++ b/components/recharts-poc/kpi/EngagementKpiCard.tsx @@ -0,0 +1,34 @@ +import type { LucideIcon } from 'lucide-react'; + +import { InfoTooltip } from '../primitives/InfoTooltip'; + +interface EngagementKpiCardProps { + label: string; + value: number; + format: 'count' | 'percent'; + info: string; + icon: LucideIcon; +} + +const NUMBER_FMT = new Intl.NumberFormat('en-US'); + +export function EngagementKpiCard(props: EngagementKpiCardProps) { + const { label, value, format, info, icon: Icon } = props; + const formatted = + format === 'percent' ? String(value) : NUMBER_FMT.format(value); + + return ( +
+
+
+
+ + {formatted} + +
+ +
+ ); +} diff --git a/components/recharts-poc/primitives/ChartCard.tsx b/components/recharts-poc/primitives/ChartCard.tsx new file mode 100644 index 00000000..809cf5a0 --- /dev/null +++ b/components/recharts-poc/primitives/ChartCard.tsx @@ -0,0 +1,57 @@ +import { type ReactElement, type ReactNode } from 'react'; +import { ResponsiveContainer } from 'recharts'; + +import { useIsMounted } from './useIsMounted'; + +interface ChartCardProps { + title: string; + description?: string; + icon?: ReactNode; + actions?: ReactNode; + height: number; + children: ReactElement; +} + +export function ChartCard(props: ChartCardProps) { + const { title, description, icon, actions, height, children } = props; + const mounted = useIsMounted(); + + return ( +
+
+
+
+ {icon ? ( + + ) : null} +

+ {title} +

+
+ {description ? ( +

+ {description} +

+ ) : null} +
+ {actions ?
{actions}
: null} +
+ {mounted ? ( + + {children} + + ) : ( +
+ ); +} diff --git a/components/recharts-poc/primitives/ChartLegend.tsx b/components/recharts-poc/primitives/ChartLegend.tsx new file mode 100644 index 00000000..eb86d478 --- /dev/null +++ b/components/recharts-poc/primitives/ChartLegend.tsx @@ -0,0 +1,26 @@ +interface ChartLegendItem { + label: string; + color: string; +} + +interface ChartLegendProps { + items: ChartLegendItem[]; +} + +export function ChartLegend(props: ChartLegendProps) { + const { items } = props; + return ( +
    + {items.map((item) => ( +
  • +
  • + ))} +
+ ); +} diff --git a/components/recharts-poc/primitives/FeatureDropdown.tsx b/components/recharts-poc/primitives/FeatureDropdown.tsx new file mode 100644 index 00000000..4d88910c --- /dev/null +++ b/components/recharts-poc/primitives/FeatureDropdown.tsx @@ -0,0 +1,91 @@ +import { Check, ChevronDown } from 'lucide-react'; +import { useEffect, useId, useRef, useState } from 'react'; + +import type { FeatureKey, FeatureMeta } from '../data/engagement'; + +interface FeatureDropdownProps { + options: FeatureMeta[]; + value: FeatureKey; + onChange: (next: FeatureKey) => void; + groupLabel?: string; +} + +export function FeatureDropdown(props: FeatureDropdownProps) { + const { options, value, onChange, groupLabel = 'Feature' } = props; + const [open, setOpen] = useState(false); + const rootRef = useRef(null); + const labelId = useId(); + + const current = options.find((o) => o.key === value) ?? options[0]; + + useEffect(() => { + if (!open) return; + const handleClick = (e: MouseEvent) => { + if (!rootRef.current?.contains(e.target as Node)) setOpen(false); + }; + const handleKey = (e: KeyboardEvent) => { + if (e.key === 'Escape') setOpen(false); + }; + document.addEventListener('mousedown', handleClick); + document.addEventListener('keydown', handleKey); + return () => { + document.removeEventListener('mousedown', handleClick); + document.removeEventListener('keydown', handleKey); + }; + }, [open]); + + return ( +
+ + {open ? ( +
+
+ {groupLabel} +
+ {options.map((opt) => { + const selected = opt.key === value; + return ( + + ); + })} +
+ ) : null} +
+ ); +} diff --git a/components/recharts-poc/primitives/InfoTooltip.tsx b/components/recharts-poc/primitives/InfoTooltip.tsx new file mode 100644 index 00000000..cb1b1b81 --- /dev/null +++ b/components/recharts-poc/primitives/InfoTooltip.tsx @@ -0,0 +1,18 @@ +import { Info } from 'lucide-react'; + +interface InfoTooltipProps { + label: string; +} + +export function InfoTooltip(props: InfoTooltipProps) { + const { label } = props; + return ( + + + ); +} diff --git a/components/recharts-poc/primitives/SectionFrame.tsx b/components/recharts-poc/primitives/SectionFrame.tsx new file mode 100644 index 00000000..0b4b01ea --- /dev/null +++ b/components/recharts-poc/primitives/SectionFrame.tsx @@ -0,0 +1,16 @@ +import { type ReactNode } from 'react'; + +interface SectionFrameProps { + children: ReactNode; +} + +export function SectionFrame(props: SectionFrameProps) { + const { children } = props; + return ( +
+
+ {children} +
+
+ ); +} diff --git a/components/recharts-poc/primitives/theme.ts b/components/recharts-poc/primitives/theme.ts new file mode 100644 index 00000000..9357d3ee --- /dev/null +++ b/components/recharts-poc/primitives/theme.ts @@ -0,0 +1,35 @@ +export const theme = { + background: '#fbf9f4', + surface: '#fdfcf8', + border: '#e7e1d2', + borderStrong: '#d6cdb4', + text: '#1f1a12', + textMuted: '#6f6857', + textSubtle: '#8c8472', + + axis: '#9a9382', + grid: '#e7e1d2', + line: '#1f1a12', + lineDashed: '#1f1a12', + reference: '#c0552a', + + segment: { + high: '#494033', + moderate: '#857556', + low: '#b2a384', + inactive: '#dcd1b5', + }, + + bar: '#b2a384', + area: '#1f1a12', + + tooltipBackground: '#1f1a12', + tooltipText: '#fdfcf8', +} as const; + +export const SEGMENT_COLORS = { + high: theme.segment.high, + moderate: theme.segment.moderate, + low: theme.segment.low, + inactive: theme.segment.inactive, +} as const; diff --git a/components/recharts-poc/primitives/useIsMounted.ts b/components/recharts-poc/primitives/useIsMounted.ts new file mode 100644 index 00000000..977c1d71 --- /dev/null +++ b/components/recharts-poc/primitives/useIsMounted.ts @@ -0,0 +1,7 @@ +import { useEffect, useState } from 'react'; + +export function useIsMounted(): boolean { + const [mounted, setMounted] = useState(false); + useEffect(() => setMounted(true), []); + return mounted; +} diff --git a/components/recharts-poc/sections/EngagementSection.tsx b/components/recharts-poc/sections/EngagementSection.tsx new file mode 100644 index 00000000..d49cff0d --- /dev/null +++ b/components/recharts-poc/sections/EngagementSection.tsx @@ -0,0 +1,44 @@ +import { LineChart, Users } from 'lucide-react'; + +import { FeatureAdoptionCard } from '../charts/FeatureAdoptionCard'; +import { UserActivitySegmentsCard } from '../charts/UserActivitySegmentsCard'; +import { ACTIVITY_SEGMENTS, KPIS } from '../data/engagement'; +import { EngagementKpiCard } from '../kpi/EngagementKpiCard'; +import { ChartLegend } from '../primitives/ChartLegend'; +import { SectionFrame } from '../primitives/SectionFrame'; +import { SEGMENT_COLORS } from '../primitives/theme'; + +const KPI_ICONS = [LineChart, Users] as const; + +const ACTIVITY_LEGEND_ITEMS = ACTIVITY_SEGMENTS.map((segment) => ({ + label: segment.label, + color: SEGMENT_COLORS[segment.key], +})); + +interface EngagementSectionProps { + perfMode: boolean; +} + +export function EngagementSection(props: EngagementSectionProps) { + const { perfMode } = props; + return ( + +
+ {KPIS.map((kpi, idx) => ( + + ))} +
+
+ + +
+
+ +
+
+ ); +} diff --git a/components/recharts-poc/sections/TrendsSection.tsx b/components/recharts-poc/sections/TrendsSection.tsx new file mode 100644 index 00000000..0e698883 --- /dev/null +++ b/components/recharts-poc/sections/TrendsSection.tsx @@ -0,0 +1,60 @@ +import { TrendingUp } from 'lucide-react'; + +import { FeatureRetentionCard } from '../charts/FeatureRetentionCard'; +import { MonthlyActiveUsersCard } from '../charts/MonthlyActiveUsersCard'; +import { NewUsersCohortCard } from '../charts/NewUsersCohortCard'; +import { ProgramUtilizationCard } from '../charts/ProgramUtilizationCard'; +import { ChartLegend } from '../primitives/ChartLegend'; +import { SectionFrame } from '../primitives/SectionFrame'; +import { theme } from '../primitives/theme'; + +const ALL_USERS_LEGEND = [{ label: 'All users', color: theme.line }]; +const NEW_USERS_LEGEND = [{ label: 'New users', color: theme.line }]; +const UTILIZATION_LEGEND = [ + { label: 'Utilization %', color: theme.line }, + { label: 'Target 40%', color: theme.reference }, +]; + +interface TrendsSectionProps { + perfMode: boolean; +} + +export function TrendsSection(props: TrendsSectionProps) { + const { perfMode } = props; + return ( + +
+ +
+

+ Trends +

+

+ Shows how active users and retention change over time. +

+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ ); +} diff --git a/docs/recharts-poc-report.md b/docs/recharts-poc-report.md new file mode 100644 index 00000000..44efad10 --- /dev/null +++ b/docs/recharts-poc-report.md @@ -0,0 +1,70 @@ +# Recharts — POC Report + +**Date:** 2026-06-09 +**Author:** Serhii Shramko +**Demo route:** `/labs/recharts` (animations on) / `/labs/recharts?perf=1` (animations off) +**Source:** `components/recharts-poc/` +**Plan:** `docs/superpowers/plans/2026-06-09-recharts-poc-plan.md` (local-only) +**Design spec:** `docs/superpowers/specs/2026-06-09-recharts-poc-design.md` (local-only) + +## Verdict + +**Ship — conditional on perf finding on Marta's Lenovo.** Recharts cleanly satisfies every functional acceptance criterion attempted in this POC: bar/line/dual-axis/histogram/donut charts, a sortable breakdown table, period-over-period deltas, label position control, label toggling, legend-driven series visibility, and tooltips. The library is actively maintained, fully typed, well-documented, and the API surface for our use cases is small. The only open question is rendering performance on low-spec hardware, captured in the "Weak-laptop test" section below; the `?perf=1` opt-out gives Marta a clean A/B test on her Lenovo. + +## Functional acceptance criteria + +| # | Criterion | Status | Evidence | Notes | +| --- | ---------------------------------------------------------- | :----: | ----------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 1 | Bar chart w/ rounded corners + custom colors | ✅ | `components/recharts-poc/charts/BarChartCard.tsx` | `radius={[8,8,0,0]}` on ``; per-bar ``; colors driven by `usePalette()` so the palette adapts to theme. | +| 2 | Line chart w/ splines | ✅ | `components/recharts-poc/charts/LineChartCard.tsx` | `` + `` fill overlay for the long-tail series. | +| 3 | Dual-axis different scales | ✅ | `components/recharts-poc/charts/DualAxisChartCard.tsx` | `` + two `` with independent orientations, tick formatters, and colours. | +| 4 | Histogram comparing 2 metrics | ⚠️ | `components/recharts-poc/charts/HistogramCard.tsx` | Recharts has no native histogram primitive — we pre-bucket data in `mocks/keywordLength.ts` and render two `` series side-by-side. Standard, idiomatic Recharts approach. | +| 5 | Pie/donut chart with %-labels | ✅ | `components/recharts-poc/charts/DonutCard.tsx` | `` + custom percent-label renderer positioned outside the slice. Hover-grow via `activeShape={{ outerRadius: 120 }}` (note: Recharts 3.x drives this through internal Redux tooltip state, not the deprecated `activeIndex` prop). | +| 6 | Table with metric breakdown | ✅ | `components/recharts-poc/table/BreakdownTable.tsx` | Plain HTML table; sortable column headers; heat-tinted "position now" cell scaled by `\|delta\|/maxDelta`; Δ column rendered via shared `DeltaBadge`. | +| 7 | Data labels with position control + format hooks | ✅ | `` children across charts; position picker on `BarChartCard` | The bar chart exposes a `top / inside / center / insideTop` picker so a reviewer can see position control without reading source. Formatter hook used on bar + histogram (`Intl.NumberFormat('en-US')`). | +| 8 | Toggle data labels on/off | ✅ | `primitives/LabelsToggle.tsx` per chart | Bar, line, histogram each have an independent toggle. Donut renders labels always (it's unreadable without them). | +| 9 | Period-over-period delta | ✅ | `primitives/periodOverPeriod.ts` + `kpi/DeltaBadge.tsx` | Pure helper, fully tested (positive, negative, flat, zero-prev, zero-curr, both-zero). Renders `▲ +N%` / `▼ -N%` / `— 0%` and gracefully degrades to absolute delta when there's no prior-period baseline. | +| 10 | Tooltips, legend visible, series toggle, responsive layout | ✅ | `` + `` + `hide` prop | Legend click toggles a local `Set` of hidden series; the corresponding `` / `` reads from it. Applied to line, histogram, dual-axis (bar and donut don't need it — single effective series). | + +## Non-functional + +| Criterion | Status | Evidence | +| ----------------------- | :----: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Last release ≤ 6 months | ✅ | `recharts@3.8.1` released **2026-03-25** (npm registry). Today is 2026-06-09 — about 2.5 months ago. Well within the window. | +| TypeScript types | ✅ | Recharts ships its own types — no `@types/recharts` needed. The POC's `pnpm typecheck` is clean across 21 new files. Two type widenings were needed in v3 strict types: `Tooltip.formatter` / `LabelList.formatter` accept `(v: unknown) => ...`, and `Pie.activeShape` needs a cast to attach a plain object — both documented inline in the chart files. | +| Documentation quality | ✅ | recharts.org + Storybook stories were sufficient for every chart attempted. The only "discoverability" pain point is the v3 API rewrite around `Pie` hover behaviour, which has migrated from props to internal state — but the new behaviour is correct and the types make the right shape obvious. | +| Bundle impact | ⚠️ | Measured from `next build` output via `.next/build-manifest.json`. **First Load JS (gzipped) — `/labs/recharts`: 315.0 kB vs homepage `/`: 206.0 kB → Δ ≈ 109 kB.** Raw uncompressed delta: ~393 kB (Recharts + d3-\* family + Redux Toolkit + react-redux + immer, all from Recharts' transitive graph). Pages Router code-splits per route, so this is **route-local — the homepage and other pages are unaffected**. Reasonable for an analytics page; heavy for a marketing page. | + +## Weak-laptop test (Marta's Lenovo) + +To be filled in after running on the target hardware. Test instructions: + +1. Open `http://shramko.dev/labs/recharts` (or local dev URL). +2. Note cold-load time, scroll smoothness, tooltip lag, series-toggle responsiveness. +3. Re-open `http://shramko.dev/labs/recharts?perf=1` (animations off) and repeat. +4. Fill in the scores below (1 = unusable, 5 = smooth). + +| Scenario | Animations on | Animations off | +| ---------------------------- | ------------- | -------------- | +| Cold load to interactive | \_ s | \_ s | +| Scroll smoothness (1–5) | \_ | \_ | +| Tooltip lag (1–5) | \_ | \_ | +| Series toggle response (1–5) | \_ | \_ | +| Subjective verdict | \_ | \_ | + +## Known limitations / workarounds + +- **No native histogram primitive.** Recharts has no `HistogramChart` — we pre-bucket data and render with `` and two `` series. This is the library-idiomatic approach. +- **Legend `onClick` not wired by default.** We attach a small handler that toggles a local `Set` of hidden series; each series reads `hide={hiddenSet.has(key)}`. ~6 lines per chart. +- **`ResponsiveContainer` + SSR.** Under Pages Router SSR, `ResponsiveContainer` measures DOM dimensions before they exist and would render at 0×0 on first paint. Mitigated by a `useIsMounted()` guard in `ChartCard` that renders a same-height skeleton until the component mounts. No hydration warnings observed. +- **`next-themes` flash on first render.** `resolvedTheme` is `undefined` on first render. `usePalette()` defaults to the light palette until mounted, then switches once `resolvedTheme` is available. Combined with the `ChartCard` mount guard, charts only paint once the theme is resolved. +- **`Pie.activeIndex` removed in v3.** Hover-grow now flows through the chart's internal Redux tooltip state; the consumer just declares `activeShape={...}` and the active sector renders with those props automatically. Documented inline in `DonutCard.tsx`. +- **Bundle size.** ~109 kB gzipped per route is real. Pages Router code-splits per route, so this is contained to `/labs/recharts` and any future chart pages; the rest of the site is unaffected. + +## Extraction notes + +To lift this POC into a separate app: + +1. Copy `pages/labs/recharts.tsx` and the entire `components/recharts-poc/` folder. +2. Install: `recharts`, `next-themes`, `clsx`, `lucide-react`, plus the new app's normal Next.js + Tailwind setup. +3. The POC has zero imports reaching outside its own folder. Only the page file imports from the barrel `@/components/recharts-poc`. Verified by `grep -rn "recharts-poc" pages/ components/`. diff --git a/jest.config.js b/jest.config.js index 7f032ca9..7d5ce937 100644 --- a/jest.config.js +++ b/jest.config.js @@ -48,6 +48,10 @@ const config = { '/node_modules/', '^.+\\.module\\.(css|sass|scss)$', ], + coveragePathIgnorePatterns: [ + '/node_modules/', + '/components/recharts-poc/(?!primitives/(periodOverPeriod|colors)\\.ts$)', + ], }; export default config; diff --git a/next-env.d.ts b/next-env.d.ts index 19709046..7996d352 100644 --- a/next-env.d.ts +++ b/next-env.d.ts @@ -1,6 +1,6 @@ /// /// -import "./.next/types/routes.d.ts"; +import "./.next/dev/types/routes.d.ts"; // NOTE: This file should not be edited // see https://nextjs.org/docs/pages/api-reference/config/typescript for more information. diff --git a/next-sitemap.config.cjs b/next-sitemap.config.cjs index 652b1a2a..02fe23b2 100644 --- a/next-sitemap.config.cjs +++ b/next-sitemap.config.cjs @@ -10,7 +10,7 @@ const pathLevelToPriority = { 3: 0.7, }; -const EXCLUDED_PATHS = ['/feed.xml']; +const EXCLUDED_PATHS = ['/feed.xml', '/labs/*']; const getPathDepthLevel = (urlPath) => { if (urlPath === '/') { diff --git a/package.json b/package.json index f40ee44c..a6fb79da 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "react": "19.2.7", "react-dom": "19.2.7", "reading-time": "1.5.0", + "recharts": "^3.8.1", "shiki": "4.2.0", "swr": "2.4.1" }, @@ -50,7 +51,7 @@ "@shikijs/rehype": "4.2.0", "@shikijs/transformers": "4.2.0", "@tailwindcss/postcss": "4.3.0", - "@tailwindcss/typography": "0.5.19", + "@tailwindcss/typography": "0.5.20", "@testing-library/dom": "10.4.1", "@testing-library/jest-dom": "6.9.1", "@testing-library/react": "16.3.2", @@ -62,8 +63,8 @@ "jest": "30.4.2", "jest-environment-jsdom": "30.4.1", "next-sitemap": "4.2.3", - "oxfmt": "0.53.0", - "oxlint": "1.68.0", + "oxfmt": "0.54.0", + "oxlint": "1.69.0", "postcss": "8.5.15", "prisma": "7.8.0", "rehype-autolink-headings": "7.1.0", diff --git a/pages/labs/recharts.tsx b/pages/labs/recharts.tsx new file mode 100644 index 00000000..102d55a0 --- /dev/null +++ b/pages/labs/recharts.tsx @@ -0,0 +1,19 @@ +import Head from 'next/head'; + +import { RechartsPocPage } from '@/components/recharts-poc'; + +export default function RechartsLabPage() { + return ( + <> + + Recharts POC | Serhii Shramko + + + + + + ); +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4687d281..7d135b8c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -57,6 +57,9 @@ importers: reading-time: specifier: 1.5.0 version: 1.5.0 + recharts: + specifier: ^3.8.1 + version: 3.8.1(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react-is@19.2.6)(react@19.2.7)(redux@5.0.1) shiki: specifier: 4.2.0 version: 4.2.0 @@ -83,8 +86,8 @@ importers: specifier: 4.3.0 version: 4.3.0 '@tailwindcss/typography': - specifier: 0.5.19 - version: 0.5.19(tailwindcss@4.3.0) + specifier: 0.5.20 + version: 0.5.20(tailwindcss@4.3.0) '@testing-library/dom': specifier: 10.4.1 version: 10.4.1 @@ -119,11 +122,11 @@ importers: specifier: 4.2.3 version: 4.2.3(next@16.2.7(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)) oxfmt: - specifier: 0.53.0 - version: 0.53.0 + specifier: 0.54.0 + version: 0.54.0 oxlint: - specifier: 1.68.0 - version: 1.68.0 + specifier: 1.69.0 + version: 1.69.0 postcss: specifier: ^8.5.10 version: 8.5.15 @@ -895,246 +898,246 @@ packages: resolution: {integrity: sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw==} engines: {node: '>=14'} - '@oxfmt/binding-android-arm-eabi@0.53.0': - resolution: {integrity: sha512-XfVM8AmIovBTKXCt14Op5wbfcoM8418nttd+nhMgM3RAVaJg1MtJc73FyWfUt0oxLyBGVwfniNVUsbV/b3VmPg==} + '@oxfmt/binding-android-arm-eabi@0.54.0': + resolution: {integrity: sha512-NAtpl/SiaeU103e7/OmZw0MvUnsUUopW7hEm/ecegJg7YM0skQaA0IXEZoyTV6NUdiNPupdIUreRqUZTShbn/g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [android] - '@oxfmt/binding-android-arm64@0.53.0': - resolution: {integrity: sha512-btHDfXckwdf9zgyAVznfZkf+GVyB0I1m1hlvaOMRx2xoyz3hphfPX97s89J3wfCN8QBETLtk4lQUaeOkrMuQOg==} + '@oxfmt/binding-android-arm64@0.54.0': + resolution: {integrity: sha512-B4VZfBUlKK1rmMChsssNZbkZjE8+FzG3avMjGgMDwbGxXRoXkoeXiAZ+78Oa+eyDPHvDCiUb4zH/vmCOUSafLQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@oxfmt/binding-darwin-arm64@0.53.0': - resolution: {integrity: sha512-k2RjMcSTkHjoOlsVGbL35JVzXL+oQco3GHPl/5kjebVF4oHNfE24In8F5isqBh9LBJucycWHKDXdGrCchdWcHQ==} + '@oxfmt/binding-darwin-arm64@0.54.0': + resolution: {integrity: sha512-i02vF75b+ePsQP3tHqSxVYI5S6b8X/xqdPu7/mDHXtpgXLTYXi3jJmfHU0j+dnZZDKaYTx/ioCK7QYJmtiJR2g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@oxfmt/binding-darwin-x64@0.53.0': - resolution: {integrity: sha512-65jIBE2H1l5SSs16fmv6/7b6sAx/WpvnsgDhVWK9qSjNFDUro7MPQ6q5UhpY7kl46yltfR046iAnxy/Bzqbiew==} + '@oxfmt/binding-darwin-x64@0.54.0': + resolution: {integrity: sha512-8VMFvGvooXj7mswkbrhdVZ2/sgiDaBzWpkkbtO+qGDLV4EfJd67nQadHkQC0ZNbaWA9ajXfqI6i7PZLIeDzxEQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@oxfmt/binding-freebsd-x64@0.53.0': - resolution: {integrity: sha512-oYe1gkz7U49PCYrS9147d2fJZj8mDI4Di6AvlsU5fu9p+Tq8S7qqOMSZjUiVTLX8bXuSA9Lk/tIxuegVjkNYRA==} + '@oxfmt/binding-freebsd-x64@0.54.0': + resolution: {integrity: sha512-0cRHnp43WN1Jrc5s0BdbdKgR1XirdvHy7TAFi3JEsoEVQVJxTXMbpVd76sxXlgRswNMDhVFSJw+y7Eb8mEavFQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@oxfmt/binding-linux-arm-gnueabihf@0.53.0': - resolution: {integrity: sha512-ailB2vLzGi629tymdAb2VYJyEHref7oqGxP+tRBrtRBxQrb6NV55JMT7xtGZ8uTeG2+Y9zojqW4LhJYxQnz9Pg==} + '@oxfmt/binding-linux-arm-gnueabihf@0.54.0': + resolution: {integrity: sha512-JyQAk3hK/OEtup7Rw6kZwfdzbKqTVD5jXXb8Xpfay29suwZyfBDMVW/bj4RqEPySYWc6zCp198pOluf8n5uYzg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxfmt/binding-linux-arm-musleabihf@0.53.0': - resolution: {integrity: sha512-abh4mWBvOvD966sobqF7r103y2yYx7Rb4WGHLOS4+5igGqLbbPxS9aK5+45D6iUY7dWMsk3Muz9a8gUtufvqJA==} + '@oxfmt/binding-linux-arm-musleabihf@0.54.0': + resolution: {integrity: sha512-qnvLatTpM8vtvjOfcckBOzJjk+n6ce/wwpP8OFeUrD5aNLYcKyWAitwj+Rk3PK9jGanbZvKsJnv14JGQ6XqFdw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxfmt/binding-linux-arm64-gnu@0.53.0': - resolution: {integrity: sha512-z73PvuhJ8qA+cDbaiqbtopHglA91U4+y5wn2sTJJrnpB957d5P33FEuyP3DQIFd7ofljmDmfVT4G0CVGHZaJWg==} + '@oxfmt/binding-linux-arm64-gnu@0.54.0': + resolution: {integrity: sha512-SMkhnCzIYZYDk9vw3W/80eeYKmrMpGF0Giuxt4HruFlCH7jEtnPeb3SdQKMfgYi/dgtaf+hZAb5XWPYnxqCQ3w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-arm64-musl@0.53.0': - resolution: {integrity: sha512-I6bhOTroqc3ThrwZ89l2k3ivKuELhdPLbAcJhRNyjWvlgwb0vjRgEnVL1XLx5Jud04/ypNRZBykAWrSk6l/D+g==} + '@oxfmt/binding-linux-arm64-musl@0.54.0': + resolution: {integrity: sha512-QrwJlBFFKnxOd95TAaszpMbZBLzMoYMpGaQTZF8oibacnF5rv8l12IhILhQRPmksWiBqg0YSe2Mnl7ayeJAHSA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@oxfmt/binding-linux-ppc64-gnu@0.53.0': - resolution: {integrity: sha512-w0p3JzB/PkkQjXALMJMqP9YfP3yq4w6zGsu5kezQmUnxRkN3b/Theg2l/nDgBsOcczxS3gL6Gam5XNAVrO6QJQ==} + '@oxfmt/binding-linux-ppc64-gnu@0.54.0': + resolution: {integrity: sha512-WILatiol/TUHTlhod7R09+7Az/XlhKwmY1MHfLZNmewltPWNN/EwxP2rQSHahibZ/cB8gmckEBjBOByD+5bYsQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-riscv64-gnu@0.53.0': - resolution: {integrity: sha512-mzBhF6k1Yq1K/dqDmVe/AAafnlJfEpx7yfUiksyeWXJk5iSzZqBSxcsa02zIytYgQFRZ7h6WPZfwHg/DoOE1Kw==} + '@oxfmt/binding-linux-riscv64-gnu@0.54.0': + resolution: {integrity: sha512-f05YMG4BH4G8S4ME6UM6fi1MnJ9094mrnvO5Pa4SJlMfWlUM+1/ZWMEF4NnjM7shZAvbHsHRuVYpUo0PHC4P9Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-riscv64-musl@0.53.0': - resolution: {integrity: sha512-AlFCpnRQhogQFzZXWbO6xB6/Udy745L+eQNmDPGg7G/OeWsYmJc4jZYfUN5pQg0reOPWSED2mOQqKZOJM1U8cA==} + '@oxfmt/binding-linux-riscv64-musl@0.54.0': + resolution: {integrity: sha512-UfL+2hj1ClNqcCRT9s8vBU4axDpjxgVxX96G+9DYAYjoc5b0u15CJtn2jgsi9iM+EbGNc5CW1HVRgwVu76UsSA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [musl] - '@oxfmt/binding-linux-s390x-gnu@0.53.0': - resolution: {integrity: sha512-XD4ulY4f1DWbuuZXAqxhVn+gdPmrhnmojWtFN78ctVoupmS845fGhsUrk1HZXKQI+iymbaiz9vAjPsghHNQ7Ag==} + '@oxfmt/binding-linux-s390x-gnu@0.54.0': + resolution: {integrity: sha512-3/XZe931Hka+J6NjnaqJzYpsWWxDTuRdUdwSQHnOuJEgbC+SehIMFJS8hsEjV7LBhVSL2OCnRLvbVW8O97XIyw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-x64-gnu@0.53.0': - resolution: {integrity: sha512-xg8KWX0QnxmYWRe60CgHYWXI0ZOtBbqTsXvWiWrcl2XUHJ3fht2QerOk2iWvylzX3zNT2GpvBRxGoR4d3sxPRQ==} + '@oxfmt/binding-linux-x64-gnu@0.54.0': + resolution: {integrity: sha512-Ik93RlObtu43GbxApafayFjwYE06L6Xr08cSwpBPYbDrLp2ReZx0Jm1DqwRyYRnukUJy+rK2WaEvUQOxdytU9Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-x64-musl@0.53.0': - resolution: {integrity: sha512-MWExpYBGvl+pIvVB/gj/CcWlN2al8AizT7rUbtaYaWNoQkhWARM6W3qpgoCr72CYSN9PborzPmM5MIRe2BrNdA==} + '@oxfmt/binding-linux-x64-musl@0.54.0': + resolution: {integrity: sha512-yZcakmPlD86CNymknd7KfW+FH+qfbqJH+i0h69CYfV1+KMoVeM9UED+8+TDVoU4haxI0NxY7RPCvRLy3Sqd2Qg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@oxfmt/binding-openharmony-arm64@0.53.0': - resolution: {integrity: sha512-u4sajgO4nxgmJIgc/y2AqPhkdbOkQH8WugXpA1+pW0ESQhvGZ1oGq61Q4xMbJHJU1hFgtO18QNrcFYDPYH0gwQ==} + '@oxfmt/binding-openharmony-arm64@0.54.0': + resolution: {integrity: sha512-GiVBZNnEZnKu00f1jTg49nomv187d0GQX+O+ocykoLeiaALuEO+swoTehHn9TehTfi7V8H0i0e/yvUjCqnwk1w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@oxfmt/binding-win32-arm64-msvc@0.53.0': - resolution: {integrity: sha512-Yq9sOZoIOJ5xPjO0qOyHJS4CiPuTkB2en9auxZz7Ar2p5RaC7BzLyVVmAA7zz9/L9YnjjY1DwNxN+ivKXimN/A==} + '@oxfmt/binding-win32-arm64-msvc@0.54.0': + resolution: {integrity: sha512-J0SSB8Z1Fre2sxRolYcW6Rl1RQmKdQ2hnHyq4YJrfBRiXTObLw4DXnIVraM/UyqGqwOi7yTrQA4VT7DPxlHVKA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@oxfmt/binding-win32-ia32-msvc@0.53.0': - resolution: {integrity: sha512-es1fVNZEkBqEcQtBpn19SYFgZF7FawlkCjkT/iImfEAus4gun8fBwB1E9hpV5LcR9B0DBNvRIXhW8BQk3JaE+Q==} + '@oxfmt/binding-win32-ia32-msvc@0.54.0': + resolution: {integrity: sha512-O61UDVj8zz6yXJjkHPf05VaMLOXmEF8P5kf/N0W7AQMmd6bcQogl+KJc7rMutKTL524oE9iH32JXZClBFmEQIg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ia32] os: [win32] - '@oxfmt/binding-win32-x64-msvc@0.53.0': - resolution: {integrity: sha512-QFmJs2bEu9AO4O6qsmEaZNGi6dFq8N+rT8EHAAnZIq/B9SeJDUbc4DzVxQ48MfDsL7D3sCZzo37zuTuspcURgg==} + '@oxfmt/binding-win32-x64-msvc@0.54.0': + resolution: {integrity: sha512-1MDpqJPiFqxWtIHas8vkb1VZ7f7eKyTffAwmO8isxQYMaG1OFKsH666BWLeXQLO+IWNfiMssLD55hbR1lIPTqg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] - '@oxlint/binding-android-arm-eabi@1.68.0': - resolution: {integrity: sha512-wEdsIspexXLLMCPAEOcCuFLMt6aE3AzTuA/nQKLPRnoJ+EQTturmGheDkhHuuVHx0GbutjQ3JKmEn+Gz6Ag28Q==} + '@oxlint/binding-android-arm-eabi@1.69.0': + resolution: {integrity: sha512-DKQQbD5cZ/MYfDgDI7YGyGD9FSxABlsBsYFo5p26lloob543tP9+4N3guwdXIYJN+7HSZxLe8YJuwcOWw5qnHg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [android] - '@oxlint/binding-android-arm64@1.68.0': - resolution: {integrity: sha512-6aZRNNXQTsYtgaus8HTb9nuCcsrQTlKXGnktwvwW0n/SooRWNxNb3925grDkC63aEYZuCIyOVLV16IdYIoC2aQ==} + '@oxlint/binding-android-arm64@1.69.0': + resolution: {integrity: sha512-lEhb+I5pr4inux+JFwfCa1HRq3Os7NirEFQ0H1I35SVEHPm6byX0Ah47xmRha3qi6LAkxUcxViL8o/9PivjzBg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@oxlint/binding-darwin-arm64@1.68.0': - resolution: {integrity: sha512-lVTbsE3kO4bLpZELgjRZuAJc8kP98wb83yMXWH8gaPaFZ+cM2IDeZto4ByoUAYj0Mxv2rvw+A1ssZequSepVSg==} + '@oxlint/binding-darwin-arm64@1.69.0': + resolution: {integrity: sha512-GY2YE8lOZW59BW1Ia1y+1gR0XyjrZRvVWHAr8LGeGhYHE0OQJ/7cRKXTkx1P+E9/6awEc3SX8a68SFTjh/E//A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@oxlint/binding-darwin-x64@1.68.0': - resolution: {integrity: sha512-nCmw2XrmQskjBUh/sfP5yKs93V68LijQgjd1cuuZ/q4SCARngLYs60/qqyzuMsg8QQ9KArDI98hxs/RDGE4KRQ==} + '@oxlint/binding-darwin-x64@1.69.0': + resolution: {integrity: sha512-ax1oZnOjHX3LB7myQyHEaQkDwfLb6str3/nSP6O7EVUviQGNkEGzGV0EqcBJWK+Ufwx0l4xPgyYayurvhAdl2Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@oxlint/binding-freebsd-x64@1.68.0': - resolution: {integrity: sha512-TI4ovQJliYE9V6e06cEv+qEI9uj7Ao65fmif4er4HD+aouyYyh0P31q2jh3KtqsOHHcQqv2PZ61TjJFLpBDGWQ==} + '@oxlint/binding-freebsd-x64@1.69.0': + resolution: {integrity: sha512-kHWeHv4g2h8NY+mpCxzCtY4uerMJWTN/TSnNj1CPbakFpHEJ6cTya2wWV0pDSYWOJ2+0UiEbhn3AtXxHtsnKjg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@oxlint/binding-linux-arm-gnueabihf@1.68.0': - resolution: {integrity: sha512-LcNnEi9g71Cmry5ZpLbKT+oVv+/zYG3hYVAbBBB5X85nOQZSk8l92CnDkxJMcxUg0NCnMCOFZuaVDlMyv4tYJw==} + '@oxlint/binding-linux-arm-gnueabihf@1.69.0': + resolution: {integrity: sha512-gq84vM1a1oEehXo27YCDzGVcxPsZDI1yswZwz2Da1/cbnWtrL16XZZnz0G/+gIU8edtHpfjxq5c+vWEHqJfWoQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxlint/binding-linux-arm-musleabihf@1.68.0': - resolution: {integrity: sha512-OovHahL3FX4UaK+hgSf11llUx2vszqjSdQQ61Ck9InOEI/ptZoC4XSQJurITqItVvd53JSlmkLMeaNjM1PoQew==} + '@oxlint/binding-linux-arm-musleabihf@1.69.0': + resolution: {integrity: sha512-kIqEa98JQ0VRyrcncxA417m2AzasqTlD+FyVT1AksjvjkqQcvm7pBWYvoW3/mpyOP2XYvi5nSCCTIe6De1yu5g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxlint/binding-linux-arm64-gnu@1.68.0': - resolution: {integrity: sha512-YbzTglnHLzzi9zv5or8Ztz5fykAoZE8W9iM42/bOrF4HBSB6rJTqdLQWuoP76EHQw9DuKl76K1QmFlG29sPJXQ==} + '@oxlint/binding-linux-arm64-gnu@1.69.0': + resolution: {integrity: sha512-j+xYiXozxGWx2cpjCrwwGR4awTxPFsRv3JZrv23RCogEPMc4R7UqjHW47p/RG0aRlbWiROCJ8coUfCwy0dvzHA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-arm64-musl@1.68.0': - resolution: {integrity: sha512-qVKtCZNic+OoNnOr/hCQAu22HSQzflI7Fsq/Blzkw02SnLuv163k3kfmrVpZjSBlUHgsRKj6WgQiw30d3SX02Q==} + '@oxlint/binding-linux-arm64-musl@1.69.0': + resolution: {integrity: sha512-xEPpNppTfN1l/nM7gYSf9iocscu/as+p/7vxkLeLEKnYU+09Dm+5V6IhDYDh+Uz6FajEupWwCLt5SOG0y1PCKg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@oxlint/binding-linux-ppc64-gnu@1.68.0': - resolution: {integrity: sha512-zExyZ8ZOUuAyQ0y9jpTcyjKUz62YY9JhKPyVxzvjTpXzZ3ujdqiVwfPWDdnA1SsIOrxdtxHn7KErDHLWskFjXg==} + '@oxlint/binding-linux-ppc64-gnu@1.69.0': + resolution: {integrity: sha512-Ug0+eU7HJBlek+SjklYH62IlOMirEJsdxpihH0kSqX0XdrDD4NdHpQc10fK1JC35yn6KrrcN+uYzlHD38XAf8Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-riscv64-gnu@1.68.0': - resolution: {integrity: sha512-6C4MPuwewyDavA7sxM14wzgRi5GGL68HPIxRCdVyS75U4MDbpFVYzKO9WNR6KLKTMPq2pcz3THwo1sK2uiqngw==} + '@oxlint/binding-linux-riscv64-gnu@1.69.0': + resolution: {integrity: sha512-iEyI3GIg0l/s3G4qy2TlaaWKdzj4PJJStwtlocpDTC00PY9hZueotf6OKUj9+yfQh0lrpBW/pLMgTztbAHKJEg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-riscv64-musl@1.68.0': - resolution: {integrity: sha512-bnZooVeHAcvA+dH0EDLgx+7HY/DRi6e0hFszg3P+OBatuUjV6EvfIyNIzWOusmqAVh4L6r21GGTZtiKE4iqM4Q==} + '@oxlint/binding-linux-riscv64-musl@1.69.0': + resolution: {integrity: sha512-NjHjpiI4WIKSMwuoJSZi5VToPeoYOS1FR52HLIDG6lidMdqquusgtODb4iLk0+lb1q3Z0nv2/aPRcC/olmpQGg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [musl] - '@oxlint/binding-linux-s390x-gnu@1.68.0': - resolution: {integrity: sha512-dIqnZnJSmHCMOUpUcWQOiV14o3DDPVx1DSsMaSzvdhNjC1tB1iEPZbdiMSCIEYbkgbsYznHXWqFdKL8WUB3F8g==} + '@oxlint/binding-linux-s390x-gnu@1.69.0': + resolution: {integrity: sha512-Ai/prDewoItkDXbp38gwGZi41DycZbUTZJ3UidwoHgQC0/DaqC2TGdtBTQLJ6hSD+SAxASzh8+/eSBPmxfOacA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@oxlint/binding-linux-x64-gnu@1.68.0': - resolution: {integrity: sha512-zc9lEnfV/HreDTY6gdMlZe+irkwHSxQ4/B1pS9GyK7RVaA5LxhoZY/w6/o2vIwLLEYiXQ5ujGxOM1ZazeFAAIA==} + '@oxlint/binding-linux-x64-gnu@1.69.0': + resolution: {integrity: sha512-Gt3KHgp46mRKz4sJeaASmKvD8ayXookRw07RMf+NowhEztGGDZ7VrXpoW96XuKJLjFukWizOFVNjmYb/u7caNQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-x64-musl@1.68.0': - resolution: {integrity: sha512-Dl5QEX0TCo/40Cdh1o1JdPS//+YiWqjC+Hrrya5OQmStZZr4svAFtdlqcpCrU9yq2Mo3vRVyO9B3h0dzD8s36Q==} + '@oxlint/binding-linux-x64-musl@1.69.0': + resolution: {integrity: sha512-7tQhJ2+p/oHv1zcfnjYI7YVzC/7iBaVOfIvFYtxdJ5F45mWgEdrCyXZXZGfiLey5t/5JhOhsaMnnv1kAzckd7g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@oxlint/binding-openharmony-arm64@1.68.0': - resolution: {integrity: sha512-/qy6dOvi4S3/LeXq0l5BT5pRKPYA7oj3uKwJOAZOr5HRLL+HK6jdBynvWuXIA2wwfE01RzNYmbBdM7vwYx00sA==} + '@oxlint/binding-openharmony-arm64@1.69.0': + resolution: {integrity: sha512-vmWz6TKp/3hfA4lksR0zHBv/6xuX1jhym6eqOjdH2DXsDDHZWcp2f0KG0VCAnlVbIrjk29G4wAWMXb/Hn1YobA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@oxlint/binding-win32-arm64-msvc@1.68.0': - resolution: {integrity: sha512-fHNtVqPHSYE7UFDSLVFUjxQjnSVXxseNJmRW+XuP4pXXDwePdPda43NL7/BBCFTxHjycOc44JNDaOPtFDNui9A==} + '@oxlint/binding-win32-arm64-msvc@1.69.0': + resolution: {integrity: sha512-9RExaLgmaw6IoIkU9cTpT71mLfI0xZ86iZH8x518LVsOkjquJMYqb9P7KpC8lgd1t0Dxs41p2pxynq4XR3Ttzw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@oxlint/binding-win32-ia32-msvc@1.68.0': - resolution: {integrity: sha512-NnKXr4Wgo4nps3erhrE0f8shBvBPZMHg72nDsvX0JyrRvsNiP3f1JNvbCKh+A6VFvpF7ZoJxu904P3cKMhvZnA==} + '@oxlint/binding-win32-ia32-msvc@1.69.0': + resolution: {integrity: sha512-1907kRPF8/PrcIw1E7LMs9JbVrpgnt/MvFdss3an8oDkYNAACXzTntV3t3869ZZhMZxb2AzRGbz1pA/jdFatXA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ia32] os: [win32] - '@oxlint/binding-win32-x64-msvc@1.68.0': - resolution: {integrity: sha512-zg5pA+84AlU6XHJ3ruiRxziO71QTrz8nLsk6u01JGS5+tL9/bnlakFiklFrcy4R1/V7ktWtaNitN3JZWmKnf6g==} + '@oxlint/binding-win32-x64-msvc@1.69.0': + resolution: {integrity: sha512-w8SOXv3mT9Fi6jY8OXdXCfnvX/3KNLXGNr4HEz2TA7S4Mv/PYAOmpB8y/ge40mxvBMgGNaSaaDwZpAsQn7HtWA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] @@ -1284,6 +1287,17 @@ packages: '@types/react': optional: true + '@reduxjs/toolkit@2.12.0': + resolution: {integrity: sha512-KiT+RzZbp6mQET+Mg+h2c97+9j1sNflUxQkIHI7Yuzf6Peu+OYpmkn6nbHWmLLWj+1ZODUJFwGZ7gx3L9R9EOw==} + peerDependencies: + react: ^16.9.0 || ^17.0.0 || ^18 || ^19 + react-redux: ^7.2.1 || ^8.1.3 || ^9.0.0 + peerDependenciesMeta: + react: + optional: true + react-redux: + optional: true + '@resvg/resvg-wasm@2.4.0': resolution: {integrity: sha512-C7c51Nn4yTxXFKvgh2txJFNweaVcfUPQxwEUFw4aWsCmfiBDJsTSwviIF8EcwjQ6k8bPyMWCl1vw4BdxE569Cg==} engines: {node: '>= 10'} @@ -1655,6 +1669,9 @@ packages: '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@standard-schema/utils@0.3.0': + resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==} + '@swc/helpers@0.5.15': resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} @@ -1750,10 +1767,10 @@ packages: '@tailwindcss/postcss@4.3.0': resolution: {integrity: sha512-Jm05Tjx+9yCLGv5qw1c+84Psds8MnyrEQYCB+FFk2lgGiUjlRqdxke4mVTuYrj2xnVZqKim2Apr5ySuQRYAw/w==} - '@tailwindcss/typography@0.5.19': - resolution: {integrity: sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==} + '@tailwindcss/typography@0.5.20': + resolution: {integrity: sha512-hwbzQuNUfcPvbegQFatVPl/MY/tcM9KLl963hQ5laJKPh81TEZ1+dNG9PirGvcaDBkp+BCshExAyKVPW91dozw==} peerDependencies: - tailwindcss: '>=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1' + tailwindcss: '>=3.0.0 || >=4.0.0 || insiders' '@testing-library/dom@10.4.1': resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} @@ -1814,6 +1831,33 @@ packages: '@types/babel__traverse@7.28.0': resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + '@types/d3-array@3.2.2': + resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==} + + '@types/d3-color@3.1.3': + resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} + + '@types/d3-ease@3.0.2': + resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==} + + '@types/d3-interpolate@3.0.4': + resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} + + '@types/d3-path@3.1.1': + resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==} + + '@types/d3-scale@4.0.9': + resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==} + + '@types/d3-shape@3.1.8': + resolution: {integrity: sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==} + + '@types/d3-time@3.0.4': + resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==} + + '@types/d3-timer@3.0.2': + resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} + '@types/debug@4.1.13': resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==} @@ -1885,6 +1929,9 @@ packages: '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + '@types/use-sync-external-store@0.0.6': + resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==} + '@types/yargs-parser@21.0.3': resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} @@ -2473,6 +2520,50 @@ packages: csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + d3-array@3.2.4: + resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} + engines: {node: '>=12'} + + d3-color@3.1.0: + resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} + engines: {node: '>=12'} + + d3-ease@3.0.1: + resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} + engines: {node: '>=12'} + + d3-format@3.1.2: + resolution: {integrity: sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==} + engines: {node: '>=12'} + + d3-interpolate@3.0.1: + resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} + engines: {node: '>=12'} + + d3-path@3.1.0: + resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==} + engines: {node: '>=12'} + + d3-scale@4.0.2: + resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} + engines: {node: '>=12'} + + d3-shape@3.2.0: + resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==} + engines: {node: '>=12'} + + d3-time-format@4.1.0: + resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==} + engines: {node: '>=12'} + + d3-time@3.1.0: + resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==} + engines: {node: '>=12'} + + d3-timer@3.0.1: + resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} + engines: {node: '>=12'} + data-urls@5.0.0: resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} engines: {node: '>=18'} @@ -2486,6 +2577,9 @@ packages: supports-color: optional: true + decimal.js-light@2.5.1: + resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==} + decimal.js@10.6.0: resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} @@ -2676,6 +2770,9 @@ packages: estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + eventemitter3@5.0.4: + resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} + events@3.3.0: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} @@ -2910,6 +3007,12 @@ packages: resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} engines: {node: '>=0.10.0'} + immer@10.2.0: + resolution: {integrity: sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==} + + immer@11.1.8: + resolution: {integrity: sha512-/tbkHMW7y10Lx6i1crLjD4/OhNkRG+Fo7byZHtah0547nIeXYcpIXaUh0IAQY6gO5459qpGGYapcEOHtFXkIuA==} + import-fresh@3.3.1: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} @@ -2945,6 +3048,10 @@ packages: inline-style-parser@0.2.7: resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==} + internmap@2.0.3: + resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} + engines: {node: '>=12'} + is-alphabetical@2.0.1: resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} @@ -3699,8 +3806,8 @@ packages: oniguruma-to-es@4.3.6: resolution: {integrity: sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA==} - oxfmt@0.53.0: - resolution: {integrity: sha512-9cB5glS3Ip6NMuZ+6NYTao9FCWkDhRtPYCtR3QBu/NxHoFbgzzTvi41N4jxz/GqGfuLKspui1qb/LlSu2IbMcw==} + oxfmt@0.54.0: + resolution: {integrity: sha512-DjnMwn7smSLF+Mc2+pRItnuPftm/dkUFpY/d4+33y9TfKrsHZo8GLhmUg9BrOIUEy94Rlom1Q11N6vuhE+e0oQ==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -3712,8 +3819,8 @@ packages: vite-plus: optional: true - oxlint@1.68.0: - resolution: {integrity: sha512-dXcbq+xsmLrMy6T8d0euf3IYUfLmjHIE11pOxiUSi5LHkFZaYPv568R6sEjcavVpUxoaQe66UBuK4HEi74NxpA==} + oxlint@1.69.0: + resolution: {integrity: sha512-ypZkK/aDc5NQV8zIR6s2H2Tl3aNW8FmJ1m9+2qsaYuRenl8vgnHNCGwTHviWJdUQzglOlHFchgopdtGhSy17Rw==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -3953,6 +4060,18 @@ packages: react-is@19.2.6: resolution: {integrity: sha512-XjBR15BhXuylgWGuslhDKqlSayuqvqBX91BP8pauG8kd1zY8kotkNWbXksTCNRarse4kuGbe2kIY05ARtwNIvw==} + react-redux@9.3.0: + resolution: {integrity: sha512-KQopgqFo/p/fgmAs5qz6p5RWaNAzq40WAu7fJIXnQpYxFPbJYtsJPWvGeF2rOBaY/kEuV77AVsX8TsQzKm+A/g==} + peerDependencies: + '@types/react': ^18.2.25 || ^19 + react: ^18.0 || ^19 + redux: ^5.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + redux: + optional: true + react@19.2.7: resolution: {integrity: sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==} engines: {node: '>=0.10.0'} @@ -3964,6 +4083,14 @@ packages: reading-time@1.5.0: resolution: {integrity: sha512-onYyVhBNr4CmAxFsKS7bz+uTLRakypIe4R+5A824vBSkQy/hB3fZepoVEf8OVAxzLvK+H/jm9TzpI3ETSm64Kg==} + recharts@3.8.1: + resolution: {integrity: sha512-mwzmO1s9sFL0TduUpwndxCUNoXsBw3u3E/0+A+cLcrSfQitSG62L32N69GhqUrrT5qKcAE3pCGVINC6pqkBBQg==} + engines: {node: '>=18'} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-is: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + recma-build-jsx@1.0.0: resolution: {integrity: sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==} @@ -3982,6 +4109,14 @@ packages: resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} engines: {node: '>=8'} + redux-thunk@3.1.0: + resolution: {integrity: sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==} + peerDependencies: + redux: ^5.0.0 + + redux@5.0.1: + resolution: {integrity: sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==} + regex-recursion@6.0.2: resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==} @@ -4034,6 +4169,9 @@ packages: resolution: {integrity: sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ==} engines: {node: '>=9.3.0 || >=8.10.0 <9.0.0'} + reselect@5.1.1: + resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==} + resolve-cwd@3.0.0: resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} engines: {node: '>=8'} @@ -4293,6 +4431,9 @@ packages: tiny-inflate@1.0.3: resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==} + tiny-invariant@1.3.3: + resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + tinyexec@1.1.1: resolution: {integrity: sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==} engines: {node: '>=18'} @@ -4440,6 +4581,9 @@ packages: vfile@6.0.3: resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + victory-vendor@37.3.6: + resolution: {integrity: sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==} + w3c-xmlserializer@5.0.0: resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} engines: {node: '>=18'} @@ -5438,118 +5582,118 @@ snapshots: '@opentelemetry/semantic-conventions@1.40.0': {} - '@oxfmt/binding-android-arm-eabi@0.53.0': + '@oxfmt/binding-android-arm-eabi@0.54.0': optional: true - '@oxfmt/binding-android-arm64@0.53.0': + '@oxfmt/binding-android-arm64@0.54.0': optional: true - '@oxfmt/binding-darwin-arm64@0.53.0': + '@oxfmt/binding-darwin-arm64@0.54.0': optional: true - '@oxfmt/binding-darwin-x64@0.53.0': + '@oxfmt/binding-darwin-x64@0.54.0': optional: true - '@oxfmt/binding-freebsd-x64@0.53.0': + '@oxfmt/binding-freebsd-x64@0.54.0': optional: true - '@oxfmt/binding-linux-arm-gnueabihf@0.53.0': + '@oxfmt/binding-linux-arm-gnueabihf@0.54.0': optional: true - '@oxfmt/binding-linux-arm-musleabihf@0.53.0': + '@oxfmt/binding-linux-arm-musleabihf@0.54.0': optional: true - '@oxfmt/binding-linux-arm64-gnu@0.53.0': + '@oxfmt/binding-linux-arm64-gnu@0.54.0': optional: true - '@oxfmt/binding-linux-arm64-musl@0.53.0': + '@oxfmt/binding-linux-arm64-musl@0.54.0': optional: true - '@oxfmt/binding-linux-ppc64-gnu@0.53.0': + '@oxfmt/binding-linux-ppc64-gnu@0.54.0': optional: true - '@oxfmt/binding-linux-riscv64-gnu@0.53.0': + '@oxfmt/binding-linux-riscv64-gnu@0.54.0': optional: true - '@oxfmt/binding-linux-riscv64-musl@0.53.0': + '@oxfmt/binding-linux-riscv64-musl@0.54.0': optional: true - '@oxfmt/binding-linux-s390x-gnu@0.53.0': + '@oxfmt/binding-linux-s390x-gnu@0.54.0': optional: true - '@oxfmt/binding-linux-x64-gnu@0.53.0': + '@oxfmt/binding-linux-x64-gnu@0.54.0': optional: true - '@oxfmt/binding-linux-x64-musl@0.53.0': + '@oxfmt/binding-linux-x64-musl@0.54.0': optional: true - '@oxfmt/binding-openharmony-arm64@0.53.0': + '@oxfmt/binding-openharmony-arm64@0.54.0': optional: true - '@oxfmt/binding-win32-arm64-msvc@0.53.0': + '@oxfmt/binding-win32-arm64-msvc@0.54.0': optional: true - '@oxfmt/binding-win32-ia32-msvc@0.53.0': + '@oxfmt/binding-win32-ia32-msvc@0.54.0': optional: true - '@oxfmt/binding-win32-x64-msvc@0.53.0': + '@oxfmt/binding-win32-x64-msvc@0.54.0': optional: true - '@oxlint/binding-android-arm-eabi@1.68.0': + '@oxlint/binding-android-arm-eabi@1.69.0': optional: true - '@oxlint/binding-android-arm64@1.68.0': + '@oxlint/binding-android-arm64@1.69.0': optional: true - '@oxlint/binding-darwin-arm64@1.68.0': + '@oxlint/binding-darwin-arm64@1.69.0': optional: true - '@oxlint/binding-darwin-x64@1.68.0': + '@oxlint/binding-darwin-x64@1.69.0': optional: true - '@oxlint/binding-freebsd-x64@1.68.0': + '@oxlint/binding-freebsd-x64@1.69.0': optional: true - '@oxlint/binding-linux-arm-gnueabihf@1.68.0': + '@oxlint/binding-linux-arm-gnueabihf@1.69.0': optional: true - '@oxlint/binding-linux-arm-musleabihf@1.68.0': + '@oxlint/binding-linux-arm-musleabihf@1.69.0': optional: true - '@oxlint/binding-linux-arm64-gnu@1.68.0': + '@oxlint/binding-linux-arm64-gnu@1.69.0': optional: true - '@oxlint/binding-linux-arm64-musl@1.68.0': + '@oxlint/binding-linux-arm64-musl@1.69.0': optional: true - '@oxlint/binding-linux-ppc64-gnu@1.68.0': + '@oxlint/binding-linux-ppc64-gnu@1.69.0': optional: true - '@oxlint/binding-linux-riscv64-gnu@1.68.0': + '@oxlint/binding-linux-riscv64-gnu@1.69.0': optional: true - '@oxlint/binding-linux-riscv64-musl@1.68.0': + '@oxlint/binding-linux-riscv64-musl@1.69.0': optional: true - '@oxlint/binding-linux-s390x-gnu@1.68.0': + '@oxlint/binding-linux-s390x-gnu@1.69.0': optional: true - '@oxlint/binding-linux-x64-gnu@1.68.0': + '@oxlint/binding-linux-x64-gnu@1.69.0': optional: true - '@oxlint/binding-linux-x64-musl@1.68.0': + '@oxlint/binding-linux-x64-musl@1.69.0': optional: true - '@oxlint/binding-openharmony-arm64@1.68.0': + '@oxlint/binding-openharmony-arm64@1.69.0': optional: true - '@oxlint/binding-win32-arm64-msvc@1.68.0': + '@oxlint/binding-win32-arm64-msvc@1.69.0': optional: true - '@oxlint/binding-win32-ia32-msvc@1.68.0': + '@oxlint/binding-win32-ia32-msvc@1.69.0': optional: true - '@oxlint/binding-win32-x64-msvc@1.68.0': + '@oxlint/binding-win32-x64-msvc@1.69.0': optional: true '@pkgjs/parseargs@0.11.0': @@ -5712,6 +5856,18 @@ snapshots: optionalDependencies: '@types/react': 19.2.17 + '@reduxjs/toolkit@2.12.0(react-redux@9.3.0(@types/react@19.2.17)(react@19.2.7)(redux@5.0.1))(react@19.2.7)': + dependencies: + '@standard-schema/spec': 1.1.0 + '@standard-schema/utils': 0.3.0 + immer: 11.1.8 + redux: 5.0.1 + redux-thunk: 3.1.0(redux@5.0.1) + reselect: 5.1.1 + optionalDependencies: + react: 19.2.7 + react-redux: 9.3.0(@types/react@19.2.17)(react@19.2.7)(redux@5.0.1) + '@resvg/resvg-wasm@2.4.0': {} '@rollup/plugin-commonjs@28.0.1(rollup@4.60.3)': @@ -6058,6 +6214,8 @@ snapshots: '@standard-schema/spec@1.1.0': {} + '@standard-schema/utils@0.3.0': {} + '@swc/helpers@0.5.15': dependencies: tslib: 2.8.1 @@ -6131,7 +6289,7 @@ snapshots: postcss: 8.5.15 tailwindcss: 4.3.0 - '@tailwindcss/typography@0.5.19(tailwindcss@4.3.0)': + '@tailwindcss/typography@0.5.20(tailwindcss@4.3.0)': dependencies: postcss-selector-parser: 6.0.10 tailwindcss: 4.3.0 @@ -6210,6 +6368,30 @@ snapshots: dependencies: '@babel/types': 7.29.0 + '@types/d3-array@3.2.2': {} + + '@types/d3-color@3.1.3': {} + + '@types/d3-ease@3.0.2': {} + + '@types/d3-interpolate@3.0.4': + dependencies: + '@types/d3-color': 3.1.3 + + '@types/d3-path@3.1.1': {} + + '@types/d3-scale@4.0.9': + dependencies: + '@types/d3-time': 3.0.4 + + '@types/d3-shape@3.1.8': + dependencies: + '@types/d3-path': 3.1.1 + + '@types/d3-time@3.0.4': {} + + '@types/d3-timer@3.0.2': {} + '@types/debug@4.1.13': dependencies: '@types/ms': 2.1.0 @@ -6291,6 +6473,8 @@ snapshots: '@types/unist@3.0.3': {} + '@types/use-sync-external-store@0.0.6': {} + '@types/yargs-parser@21.0.3': {} '@types/yargs@17.0.35': @@ -6799,6 +6983,44 @@ snapshots: csstype@3.2.3: {} + d3-array@3.2.4: + dependencies: + internmap: 2.0.3 + + d3-color@3.1.0: {} + + d3-ease@3.0.1: {} + + d3-format@3.1.2: {} + + d3-interpolate@3.0.1: + dependencies: + d3-color: 3.1.0 + + d3-path@3.1.0: {} + + d3-scale@4.0.2: + dependencies: + d3-array: 3.2.4 + d3-format: 3.1.2 + d3-interpolate: 3.0.1 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + + d3-shape@3.2.0: + dependencies: + d3-path: 3.1.0 + + d3-time-format@4.1.0: + dependencies: + d3-time: 3.1.0 + + d3-time@3.1.0: + dependencies: + d3-array: 3.2.4 + + d3-timer@3.0.1: {} + data-urls@5.0.0: dependencies: whatwg-mimetype: 4.0.0 @@ -6808,6 +7030,8 @@ snapshots: dependencies: ms: 2.1.3 + decimal.js-light@2.5.1: {} + decimal.js@10.6.0: {} decode-named-character-reference@1.3.0: @@ -6963,6 +7187,8 @@ snapshots: dependencies: '@types/estree': 1.0.8 + eventemitter3@5.0.4: {} + events@3.3.0: {} execa@5.1.1: @@ -7254,6 +7480,10 @@ snapshots: dependencies: safer-buffer: 2.1.2 + immer@10.2.0: {} + + immer@11.1.8: {} + import-fresh@3.3.1: dependencies: parent-module: 1.0.1 @@ -7286,6 +7516,8 @@ snapshots: inline-style-parser@0.2.7: {} + internmap@2.0.3: {} + is-alphabetical@2.0.1: {} is-alphanumerical@2.0.1: @@ -8472,51 +8704,51 @@ snapshots: regex: 6.1.0 regex-recursion: 6.0.2 - oxfmt@0.53.0: + oxfmt@0.54.0: dependencies: tinypool: 2.1.0 optionalDependencies: - '@oxfmt/binding-android-arm-eabi': 0.53.0 - '@oxfmt/binding-android-arm64': 0.53.0 - '@oxfmt/binding-darwin-arm64': 0.53.0 - '@oxfmt/binding-darwin-x64': 0.53.0 - '@oxfmt/binding-freebsd-x64': 0.53.0 - '@oxfmt/binding-linux-arm-gnueabihf': 0.53.0 - '@oxfmt/binding-linux-arm-musleabihf': 0.53.0 - '@oxfmt/binding-linux-arm64-gnu': 0.53.0 - '@oxfmt/binding-linux-arm64-musl': 0.53.0 - '@oxfmt/binding-linux-ppc64-gnu': 0.53.0 - '@oxfmt/binding-linux-riscv64-gnu': 0.53.0 - '@oxfmt/binding-linux-riscv64-musl': 0.53.0 - '@oxfmt/binding-linux-s390x-gnu': 0.53.0 - '@oxfmt/binding-linux-x64-gnu': 0.53.0 - '@oxfmt/binding-linux-x64-musl': 0.53.0 - '@oxfmt/binding-openharmony-arm64': 0.53.0 - '@oxfmt/binding-win32-arm64-msvc': 0.53.0 - '@oxfmt/binding-win32-ia32-msvc': 0.53.0 - '@oxfmt/binding-win32-x64-msvc': 0.53.0 - - oxlint@1.68.0: + '@oxfmt/binding-android-arm-eabi': 0.54.0 + '@oxfmt/binding-android-arm64': 0.54.0 + '@oxfmt/binding-darwin-arm64': 0.54.0 + '@oxfmt/binding-darwin-x64': 0.54.0 + '@oxfmt/binding-freebsd-x64': 0.54.0 + '@oxfmt/binding-linux-arm-gnueabihf': 0.54.0 + '@oxfmt/binding-linux-arm-musleabihf': 0.54.0 + '@oxfmt/binding-linux-arm64-gnu': 0.54.0 + '@oxfmt/binding-linux-arm64-musl': 0.54.0 + '@oxfmt/binding-linux-ppc64-gnu': 0.54.0 + '@oxfmt/binding-linux-riscv64-gnu': 0.54.0 + '@oxfmt/binding-linux-riscv64-musl': 0.54.0 + '@oxfmt/binding-linux-s390x-gnu': 0.54.0 + '@oxfmt/binding-linux-x64-gnu': 0.54.0 + '@oxfmt/binding-linux-x64-musl': 0.54.0 + '@oxfmt/binding-openharmony-arm64': 0.54.0 + '@oxfmt/binding-win32-arm64-msvc': 0.54.0 + '@oxfmt/binding-win32-ia32-msvc': 0.54.0 + '@oxfmt/binding-win32-x64-msvc': 0.54.0 + + oxlint@1.69.0: optionalDependencies: - '@oxlint/binding-android-arm-eabi': 1.68.0 - '@oxlint/binding-android-arm64': 1.68.0 - '@oxlint/binding-darwin-arm64': 1.68.0 - '@oxlint/binding-darwin-x64': 1.68.0 - '@oxlint/binding-freebsd-x64': 1.68.0 - '@oxlint/binding-linux-arm-gnueabihf': 1.68.0 - '@oxlint/binding-linux-arm-musleabihf': 1.68.0 - '@oxlint/binding-linux-arm64-gnu': 1.68.0 - '@oxlint/binding-linux-arm64-musl': 1.68.0 - '@oxlint/binding-linux-ppc64-gnu': 1.68.0 - '@oxlint/binding-linux-riscv64-gnu': 1.68.0 - '@oxlint/binding-linux-riscv64-musl': 1.68.0 - '@oxlint/binding-linux-s390x-gnu': 1.68.0 - '@oxlint/binding-linux-x64-gnu': 1.68.0 - '@oxlint/binding-linux-x64-musl': 1.68.0 - '@oxlint/binding-openharmony-arm64': 1.68.0 - '@oxlint/binding-win32-arm64-msvc': 1.68.0 - '@oxlint/binding-win32-ia32-msvc': 1.68.0 - '@oxlint/binding-win32-x64-msvc': 1.68.0 + '@oxlint/binding-android-arm-eabi': 1.69.0 + '@oxlint/binding-android-arm64': 1.69.0 + '@oxlint/binding-darwin-arm64': 1.69.0 + '@oxlint/binding-darwin-x64': 1.69.0 + '@oxlint/binding-freebsd-x64': 1.69.0 + '@oxlint/binding-linux-arm-gnueabihf': 1.69.0 + '@oxlint/binding-linux-arm-musleabihf': 1.69.0 + '@oxlint/binding-linux-arm64-gnu': 1.69.0 + '@oxlint/binding-linux-arm64-musl': 1.69.0 + '@oxlint/binding-linux-ppc64-gnu': 1.69.0 + '@oxlint/binding-linux-riscv64-gnu': 1.69.0 + '@oxlint/binding-linux-riscv64-musl': 1.69.0 + '@oxlint/binding-linux-s390x-gnu': 1.69.0 + '@oxlint/binding-linux-x64-gnu': 1.69.0 + '@oxlint/binding-linux-x64-musl': 1.69.0 + '@oxlint/binding-openharmony-arm64': 1.69.0 + '@oxlint/binding-win32-arm64-msvc': 1.69.0 + '@oxlint/binding-win32-ia32-msvc': 1.69.0 + '@oxlint/binding-win32-x64-msvc': 1.69.0 p-limit@2.3.0: dependencies: @@ -8742,12 +8974,41 @@ snapshots: react-is@19.2.6: {} + react-redux@9.3.0(@types/react@19.2.17)(react@19.2.7)(redux@5.0.1): + dependencies: + '@types/use-sync-external-store': 0.0.6 + react: 19.2.7 + use-sync-external-store: 1.6.0(react@19.2.7) + optionalDependencies: + '@types/react': 19.2.17 + redux: 5.0.1 + react@19.2.7: {} readdirp@5.0.0: {} reading-time@1.5.0: {} + recharts@3.8.1(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react-is@19.2.6)(react@19.2.7)(redux@5.0.1): + dependencies: + '@reduxjs/toolkit': 2.12.0(react-redux@9.3.0(@types/react@19.2.17)(react@19.2.7)(redux@5.0.1))(react@19.2.7) + clsx: 2.1.1 + decimal.js-light: 2.5.1 + es-toolkit: 1.46.1 + eventemitter3: 5.0.4 + immer: 10.2.0 + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + react-is: 19.2.6 + react-redux: 9.3.0(@types/react@19.2.17)(react@19.2.7)(redux@5.0.1) + reselect: 5.1.1 + tiny-invariant: 1.3.3 + use-sync-external-store: 1.6.0(react@19.2.7) + victory-vendor: 37.3.6 + transitivePeerDependencies: + - '@types/react' + - redux + recma-build-jsx@1.0.0: dependencies: '@types/estree': 1.0.8 @@ -8782,6 +9043,12 @@ snapshots: indent-string: 4.0.0 strip-indent: 3.0.0 + redux-thunk@3.1.0(redux@5.0.1): + dependencies: + redux: 5.0.1 + + redux@5.0.1: {} + regex-recursion@6.0.2: dependencies: regex-utilities: 2.3.0 @@ -8875,6 +9142,8 @@ snapshots: transitivePeerDependencies: - supports-color + reselect@5.1.1: {} + resolve-cwd@3.0.0: dependencies: resolve-from: 5.0.0 @@ -9165,6 +9434,8 @@ snapshots: tiny-inflate@1.0.3: {} + tiny-invariant@1.3.3: {} + tinyexec@1.1.1: {} tinypool@2.1.0: {} @@ -9344,6 +9615,23 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 + victory-vendor@37.3.6: + dependencies: + '@types/d3-array': 3.2.2 + '@types/d3-ease': 3.0.2 + '@types/d3-interpolate': 3.0.4 + '@types/d3-scale': 4.0.9 + '@types/d3-shape': 3.1.8 + '@types/d3-time': 3.0.4 + '@types/d3-timer': 3.0.2 + d3-array: 3.2.4 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-scale: 4.0.2 + d3-shape: 3.2.0 + d3-time: 3.1.0 + d3-timer: 3.0.1 + w3c-xmlserializer@5.0.0: dependencies: xml-name-validator: 5.0.0