+ {/* Grid lines */}
+ {gridLines.map((frac) => {
+ if (isColumn) {
+ const y = chartH - labelSpace - (chartH - labelSpace) * frac;
+ return (
+
+ );
+ }
+ const x = labelSpace + barArea * frac;
+ return (
+
+ );
+ })}
+
+ {items.map((item, i) => {
+ const color = item.color ?? "#6c7dff";
+ const enterF = i * ITEM_STAGGER;
+ const localFrame = frame - enterF;
+
+ const progress = interpolate(
+ localFrame,
+ [0, ITEM_ANIM_DURATION],
+ [0, 1],
+ { extrapolateLeft: "clamp", extrapolateRight: "clamp" },
+ );
+
+ const opacity = interpolate(
+ localFrame,
+ [0, ITEM_ANIM_DURATION * 0.5],
+ [0, 1],
+ { extrapolateLeft: "clamp", extrapolateRight: "clamp" },
+ );
+
+ const ratio = scale(item.value) * progress;
+ const bg = barFill(variant.chartStyle, color, isColumn);
+ const borderRadius = vmin * 0.5;
+
+ if (isColumn) {
+ const x = i * (barThickness + gap);
+ const maxBarH = chartH - labelSpace;
+ const barH = maxBarH * ratio;
+
+ return (
+
+
+ {/* Label below */}
+
+
+ {item.label}
+
+
+ {/* Value on top */}
+ {variant.showValues && (
+
+
+ {item.value}
+
+
+ )}
+
+ );
+ }
+
+ // Horizontal bar
+ const y = i * (barThickness + gap);
+ const barW = barArea * ratio;
+
+ return (
+
+ {/* Label on left */}
+
+
+ {item.label}
+
+
+ {/* Bar */}
+
+ {/* Value at end */}
+ {variant.showValues && (
+
+
+ {item.value}
+
+
+ )}
+
+ );
+ })}
+
+ );
+};
diff --git a/surfsense_video/src/remotion/scenes/chart/components/LineChart.tsx b/surfsense_video/src/remotion/scenes/chart/components/LineChart.tsx
new file mode 100644
index 000000000..3e9c58d75
--- /dev/null
+++ b/surfsense_video/src/remotion/scenes/chart/components/LineChart.tsx
@@ -0,0 +1,211 @@
+/**
+ * LineChart — animated line with data points and optional area fill.
+ * Uses d3-scale for axis mapping and d3-shape for curve generation.
+ * Supports overflow: when lineLayout.overflow is true, the chart is wider
+ * than the viewport and ChartScene pans the camera horizontally.
+ */
+import React from "react";
+import { useCurrentFrame, interpolate } from "remotion";
+import { scaleLinear, scalePoint } from "d3-scale";
+import { line as d3Line, area as d3Area, curveMonotoneX } from "d3-shape";
+import type { ThemeColors } from "../../../theme";
+import type { ChartItem } from "../types";
+import type { ChartVariant, ChartStyle } from "../variant";
+import { ITEM_STAGGER, ITEM_ANIM_DURATION } from "../constants";
+import type { LineLayoutInfo } from "../layout";
+
+interface LineChartProps {
+ items: ChartItem[];
+ chartW: number;
+ chartH: number;
+ vmin: number;
+ variant: ChartVariant;
+ theme: ThemeColors;
+ lineLayout?: LineLayoutInfo;
+}
+
+function lineColor(style: ChartStyle, color: string): string {
+ switch (style) {
+ case "gradient":
+ case "solid":
+ return color;
+ case "glass":
+ return `${color}cc`;
+ case "outlined":
+ return color;
+ }
+}
+
+export const LineChart: React.FC