@sonordev/agency-site-kit 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/dist/BeforeAfterSection-6QUJOBO2.js +176 -0
  2. package/dist/BeforeAfterSection-6QUJOBO2.js.map +1 -0
  3. package/dist/BeforeAfterSection-DVAWWE4K.cjs +181 -0
  4. package/dist/BeforeAfterSection-DVAWWE4K.cjs.map +1 -0
  5. package/dist/CTASection-4JKLXEUF.cjs +111 -0
  6. package/dist/CTASection-4JKLXEUF.cjs.map +1 -0
  7. package/dist/CTASection-BJA72XIL.js +106 -0
  8. package/dist/CTASection-BJA72XIL.js.map +1 -0
  9. package/dist/ChallengesSection-GEQGVSJN.js +180 -0
  10. package/dist/ChallengesSection-GEQGVSJN.js.map +1 -0
  11. package/dist/ChallengesSection-IZ3DHECS.cjs +182 -0
  12. package/dist/ChallengesSection-IZ3DHECS.cjs.map +1 -0
  13. package/dist/ConversionFunnelSection-AUUSJ5HQ.cjs +209 -0
  14. package/dist/ConversionFunnelSection-AUUSJ5HQ.cjs.map +1 -0
  15. package/dist/ConversionFunnelSection-D3GE4NKE.js +203 -0
  16. package/dist/ConversionFunnelSection-D3GE4NKE.js.map +1 -0
  17. package/dist/DetailsSection-FB763FS7.js +135 -0
  18. package/dist/DetailsSection-FB763FS7.js.map +1 -0
  19. package/dist/DetailsSection-OACJFGH7.cjs +137 -0
  20. package/dist/DetailsSection-OACJFGH7.cjs.map +1 -0
  21. package/dist/FeatureSpotlightSection-B7P3JGNL.js +205 -0
  22. package/dist/FeatureSpotlightSection-B7P3JGNL.js.map +1 -0
  23. package/dist/FeatureSpotlightSection-WRHXS7TU.cjs +210 -0
  24. package/dist/FeatureSpotlightSection-WRHXS7TU.cjs.map +1 -0
  25. package/dist/GallerySection-VMKORC47.js +218 -0
  26. package/dist/GallerySection-VMKORC47.js.map +1 -0
  27. package/dist/GallerySection-WJ4PQDBI.cjs +219 -0
  28. package/dist/GallerySection-WJ4PQDBI.cjs.map +1 -0
  29. package/dist/MetricsTimelineSection-4L6DUHJ5.cjs +258 -0
  30. package/dist/MetricsTimelineSection-4L6DUHJ5.cjs.map +1 -0
  31. package/dist/MetricsTimelineSection-6BT5GNFV.js +253 -0
  32. package/dist/MetricsTimelineSection-6BT5GNFV.js.map +1 -0
  33. package/dist/ResultsSection-DFUJ5U6M.js +93 -0
  34. package/dist/ResultsSection-DFUJ5U6M.js.map +1 -0
  35. package/dist/ResultsSection-XLGMMQKY.cjs +95 -0
  36. package/dist/ResultsSection-XLGMMQKY.cjs.map +1 -0
  37. package/dist/ServicesSection-D5V3Q4GR.js +118 -0
  38. package/dist/ServicesSection-D5V3Q4GR.js.map +1 -0
  39. package/dist/ServicesSection-WJMGK2MF.cjs +120 -0
  40. package/dist/ServicesSection-WJMGK2MF.cjs.map +1 -0
  41. package/dist/StrategySection-3ED3QW4R.cjs +180 -0
  42. package/dist/StrategySection-3ED3QW4R.cjs.map +1 -0
  43. package/dist/StrategySection-VUWMIYYP.js +175 -0
  44. package/dist/StrategySection-VUWMIYYP.js.map +1 -0
  45. package/dist/TeamSection-DZVSNZE6.cjs +112 -0
  46. package/dist/TeamSection-DZVSNZE6.cjs.map +1 -0
  47. package/dist/TeamSection-HGKFW6PQ.js +107 -0
  48. package/dist/TeamSection-HGKFW6PQ.js.map +1 -0
  49. package/dist/TechStackSection-OCUYG4XT.js +90 -0
  50. package/dist/TechStackSection-OCUYG4XT.js.map +1 -0
  51. package/dist/TechStackSection-VKJK4KQB.cjs +91 -0
  52. package/dist/TechStackSection-VKJK4KQB.cjs.map +1 -0
  53. package/dist/TestimonialSection-6RGSMXQB.js +122 -0
  54. package/dist/TestimonialSection-6RGSMXQB.js.map +1 -0
  55. package/dist/TestimonialSection-XPTFUQIN.cjs +124 -0
  56. package/dist/TestimonialSection-XPTFUQIN.cjs.map +1 -0
  57. package/dist/VideoSection-4A2HC6K6.js +117 -0
  58. package/dist/VideoSection-4A2HC6K6.js.map +1 -0
  59. package/dist/VideoSection-G3DFS7UH.cjs +118 -0
  60. package/dist/VideoSection-G3DFS7UH.cjs.map +1 -0
  61. package/dist/chunk-2VNNFAG6.js +415 -0
  62. package/dist/chunk-2VNNFAG6.js.map +1 -0
  63. package/dist/chunk-2Y4O3LWM.js +53 -0
  64. package/dist/chunk-2Y4O3LWM.js.map +1 -0
  65. package/dist/chunk-5FKOLIV6.cjs +221 -0
  66. package/dist/chunk-5FKOLIV6.cjs.map +1 -0
  67. package/dist/chunk-7CFFAKDM.js +74 -0
  68. package/dist/chunk-7CFFAKDM.js.map +1 -0
  69. package/dist/chunk-A4I4IK7V.js +69 -0
  70. package/dist/chunk-A4I4IK7V.js.map +1 -0
  71. package/dist/chunk-IKBK7HYX.cjs +79 -0
  72. package/dist/chunk-IKBK7HYX.cjs.map +1 -0
  73. package/dist/chunk-KEOHORIH.cjs +79 -0
  74. package/dist/chunk-KEOHORIH.cjs.map +1 -0
  75. package/dist/chunk-NAS4K5UR.cjs +139 -0
  76. package/dist/chunk-NAS4K5UR.cjs.map +1 -0
  77. package/dist/chunk-QBLWP25X.cjs +73 -0
  78. package/dist/chunk-QBLWP25X.cjs.map +1 -0
  79. package/dist/chunk-QIC6JFFD.js +210 -0
  80. package/dist/chunk-QIC6JFFD.js.map +1 -0
  81. package/dist/chunk-TAPNXT7X.cjs +422 -0
  82. package/dist/chunk-TAPNXT7X.cjs.map +1 -0
  83. package/dist/chunk-XCKXHK44.js +15 -0
  84. package/dist/chunk-XCKXHK44.js.map +1 -0
  85. package/dist/chunk-XMC4DN6G.js +131 -0
  86. package/dist/chunk-XMC4DN6G.js.map +1 -0
  87. package/dist/chunk-XONXEFJY.cjs +58 -0
  88. package/dist/chunk-XONXEFJY.cjs.map +1 -0
  89. package/dist/chunk-XQNJED46.cjs +19 -0
  90. package/dist/chunk-XQNJED46.cjs.map +1 -0
  91. package/dist/chunk-YB4B3OMC.js +74 -0
  92. package/dist/chunk-YB4B3OMC.js.map +1 -0
  93. package/dist/index.cjs +271 -0
  94. package/dist/index.cjs.map +1 -0
  95. package/dist/index.d.cts +137 -0
  96. package/dist/index.d.ts +137 -0
  97. package/dist/index.js +197 -0
  98. package/dist/index.js.map +1 -0
  99. package/dist/layout/index.cjs +13 -0
  100. package/dist/layout/index.cjs.map +1 -0
  101. package/dist/layout/index.d.cts +54 -0
  102. package/dist/layout/index.d.ts +54 -0
  103. package/dist/layout/index.js +4 -0
  104. package/dist/layout/index.js.map +1 -0
  105. package/dist/portfolio/client.cjs +18 -0
  106. package/dist/portfolio/client.cjs.map +1 -0
  107. package/dist/portfolio/client.d.cts +97 -0
  108. package/dist/portfolio/client.d.ts +97 -0
  109. package/dist/portfolio/client.js +6 -0
  110. package/dist/portfolio/client.js.map +1 -0
  111. package/dist/portfolio/index.cjs +41 -0
  112. package/dist/portfolio/index.cjs.map +1 -0
  113. package/dist/portfolio/index.d.cts +12 -0
  114. package/dist/portfolio/index.d.ts +12 -0
  115. package/dist/portfolio/index.js +8 -0
  116. package/dist/portfolio/index.js.map +1 -0
  117. package/dist/portfolio/sections.cjs +20 -0
  118. package/dist/portfolio/sections.cjs.map +1 -0
  119. package/dist/portfolio/sections.d.cts +42 -0
  120. package/dist/portfolio/sections.d.ts +42 -0
  121. package/dist/portfolio/sections.js +4 -0
  122. package/dist/portfolio/sections.js.map +1 -0
  123. package/dist/portfolio/server.cjs +141 -0
  124. package/dist/portfolio/server.cjs.map +1 -0
  125. package/dist/portfolio/server.d.cts +68 -0
  126. package/dist/portfolio/server.d.ts +68 -0
  127. package/dist/portfolio/server.js +134 -0
  128. package/dist/portfolio/server.js.map +1 -0
  129. package/dist/types-BMUhBhWx.d.cts +346 -0
  130. package/dist/types-BMUhBhWx.d.ts +346 -0
  131. package/package.json +71 -0
@@ -0,0 +1,258 @@
1
+ 'use strict';
2
+
3
+ var chunkKEOHORIH_cjs = require('./chunk-KEOHORIH.cjs');
4
+ var chunkIKBK7HYX_cjs = require('./chunk-IKBK7HYX.cjs');
5
+ var react = require('react');
6
+ var gsap = require('gsap');
7
+ var ScrollTrigger = require('gsap/ScrollTrigger');
8
+ var jsxRuntime = require('react/jsx-runtime');
9
+
10
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
11
+
12
+ var gsap__default = /*#__PURE__*/_interopDefault(gsap);
13
+
14
+ var metricColors = {
15
+ traffic: "#6366f1",
16
+ conversions: "#10b981",
17
+ revenue: "#f59e0b",
18
+ rankings: "#ec4899"
19
+ };
20
+ var metricLabels = {
21
+ traffic: "Traffic",
22
+ conversions: "Conversions",
23
+ revenue: "Revenue",
24
+ rankings: "Rankings"
25
+ };
26
+ function MetricsTimelineSection({ data }) {
27
+ const svgRef = react.useRef(null);
28
+ const pathRef = react.useRef(null);
29
+ const [activeMetric, setActiveMetric] = react.useState("traffic");
30
+ const [hasTriggered, setHasTriggered] = react.useState(false);
31
+ const availableMetrics = react.useMemo(() => {
32
+ const keys = ["traffic", "conversions", "revenue", "rankings"];
33
+ return keys.filter(
34
+ (key) => data.dataPoints.some((dp) => dp.metrics[key] !== void 0 && dp.metrics[key] !== null)
35
+ );
36
+ }, [data.dataPoints]);
37
+ const chartData = react.useMemo(() => {
38
+ const values = data.dataPoints.map((dp) => dp.metrics[activeMetric] ?? 0);
39
+ const maxVal = Math.max(...values, 1);
40
+ const minVal = Math.min(...values, 0);
41
+ const range = maxVal - minVal || 1;
42
+ const chartWidth = 800;
43
+ const chartHeight = 300;
44
+ const padding = 40;
45
+ const points = values.map((val, i) => ({
46
+ x: padding + i / Math.max(values.length - 1, 1) * (chartWidth - padding * 2),
47
+ y: chartHeight - padding - (val - minVal) / range * (chartHeight - padding * 2)
48
+ }));
49
+ let d = "";
50
+ if (points.length > 0) {
51
+ d = `M ${points[0].x} ${points[0].y}`;
52
+ for (let i = 1; i < points.length; i++) {
53
+ const cpx1 = (points[i - 1].x + points[i].x) / 2;
54
+ const cpy1 = points[i - 1].y;
55
+ const cpx2 = (points[i - 1].x + points[i].x) / 2;
56
+ const cpy2 = points[i].y;
57
+ d += ` C ${cpx1} ${cpy1}, ${cpx2} ${cpy2}, ${points[i].x} ${points[i].y}`;
58
+ }
59
+ }
60
+ let areaD = d;
61
+ if (points.length > 0) {
62
+ areaD += ` L ${points[points.length - 1].x} ${chartHeight - padding} L ${points[0].x} ${chartHeight - padding} Z`;
63
+ }
64
+ const baselineIdx = data.dataPoints.findIndex((dp) => dp.month === data.baselineDate);
65
+ const baselineX = baselineIdx >= 0 ? points[baselineIdx]?.x : void 0;
66
+ return {
67
+ d,
68
+ areaD,
69
+ points,
70
+ chartWidth,
71
+ chartHeight,
72
+ padding,
73
+ baselineX,
74
+ labels: data.dataPoints.map((dp) => dp.label)
75
+ };
76
+ }, [data.dataPoints, data.baselineDate, activeMetric]);
77
+ react.useEffect(() => {
78
+ gsap__default.default.registerPlugin(ScrollTrigger.ScrollTrigger);
79
+ const path = pathRef.current;
80
+ const svg = svgRef.current;
81
+ if (!path || !svg) return;
82
+ const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
83
+ const ctx = gsap__default.default.context(() => {
84
+ const length = path.getTotalLength();
85
+ gsap__default.default.set(path, { strokeDasharray: length, strokeDashoffset: length });
86
+ if (prefersReducedMotion) {
87
+ gsap__default.default.set(path, { strokeDashoffset: 0 });
88
+ setHasTriggered(true);
89
+ return;
90
+ }
91
+ gsap__default.default.to(path, {
92
+ strokeDashoffset: 0,
93
+ duration: 2,
94
+ ease: "power2.out",
95
+ scrollTrigger: {
96
+ trigger: svg,
97
+ start: "top 75%",
98
+ toggleActions: "play none none none"
99
+ },
100
+ onComplete: () => setHasTriggered(true)
101
+ });
102
+ });
103
+ return () => ctx.revert();
104
+ }, [activeMetric]);
105
+ const handleMetricChange = react.useCallback((metric) => {
106
+ setActiveMetric(metric);
107
+ setHasTriggered(false);
108
+ }, []);
109
+ const color = metricColors[activeMetric];
110
+ return /* @__PURE__ */ jsxRuntime.jsx(
111
+ "section",
112
+ {
113
+ className: "w-full py-20 md:py-28",
114
+ style: { background: "var(--sk-bg, #0a0a0a)" },
115
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "max-w-6xl mx-auto px-6", children: [
116
+ (data.title || data.description) && /* @__PURE__ */ jsxRuntime.jsxs(chunkIKBK7HYX_cjs.ScrollReveal, { y: 30, children: [
117
+ data.title && /* @__PURE__ */ jsxRuntime.jsx(
118
+ "h2",
119
+ {
120
+ className: "text-3xl md:text-4xl font-bold mb-4",
121
+ style: {
122
+ color: "var(--sk-text-primary, #ffffff)",
123
+ fontFamily: "var(--sk-font-heading, inherit)"
124
+ },
125
+ children: data.title
126
+ }
127
+ ),
128
+ data.description && /* @__PURE__ */ jsxRuntime.jsx(
129
+ "p",
130
+ {
131
+ className: "text-lg mb-8 max-w-2xl",
132
+ style: { color: "var(--sk-text-secondary, #a1a1aa)" },
133
+ children: data.description
134
+ }
135
+ )
136
+ ] }),
137
+ /* @__PURE__ */ jsxRuntime.jsx(chunkIKBK7HYX_cjs.ScrollReveal, { y: 20, delay: 0.1, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-2 mb-8", children: availableMetrics.map((metric) => /* @__PURE__ */ jsxRuntime.jsx(
138
+ "button",
139
+ {
140
+ onClick: () => handleMetricChange(metric),
141
+ className: "px-4 py-2 rounded-full text-sm font-medium transition-all duration-200 cursor-pointer",
142
+ style: {
143
+ background: activeMetric === metric ? metricColors[metric] : "var(--sk-surface, rgba(255,255,255,0.05))",
144
+ color: activeMetric === metric ? "#ffffff" : "var(--sk-text-secondary, #a1a1aa)",
145
+ border: `1px solid ${activeMetric === metric ? metricColors[metric] : "var(--sk-border, rgba(255,255,255,0.1))"}`
146
+ },
147
+ children: metricLabels[metric]
148
+ },
149
+ metric
150
+ )) }) }),
151
+ /* @__PURE__ */ jsxRuntime.jsx(chunkIKBK7HYX_cjs.ScrollReveal, { y: 40, delay: 0.2, children: /* @__PURE__ */ jsxRuntime.jsx(chunkKEOHORIH_cjs.GlassCard, { padding: "lg", hover: false, children: /* @__PURE__ */ jsxRuntime.jsxs(
152
+ "svg",
153
+ {
154
+ ref: svgRef,
155
+ viewBox: `0 0 ${chartData.chartWidth} ${chartData.chartHeight}`,
156
+ className: "w-full h-auto",
157
+ preserveAspectRatio: "xMidYMid meet",
158
+ children: [
159
+ [0, 1, 2, 3, 4].map((i) => {
160
+ const y = chartData.padding + i / 4 * (chartData.chartHeight - chartData.padding * 2);
161
+ return /* @__PURE__ */ jsxRuntime.jsx(
162
+ "line",
163
+ {
164
+ x1: chartData.padding,
165
+ y1: y,
166
+ x2: chartData.chartWidth - chartData.padding,
167
+ y2: y,
168
+ stroke: "var(--sk-border, rgba(255,255,255,0.1))",
169
+ strokeWidth: "1"
170
+ },
171
+ i
172
+ );
173
+ }),
174
+ chartData.baselineX !== void 0 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
175
+ /* @__PURE__ */ jsxRuntime.jsx(
176
+ "line",
177
+ {
178
+ x1: chartData.baselineX,
179
+ y1: chartData.padding,
180
+ x2: chartData.baselineX,
181
+ y2: chartData.chartHeight - chartData.padding,
182
+ stroke: "var(--sk-text-tertiary, #71717a)",
183
+ strokeWidth: "1.5",
184
+ strokeDasharray: "6 4"
185
+ }
186
+ ),
187
+ /* @__PURE__ */ jsxRuntime.jsx(
188
+ "text",
189
+ {
190
+ x: chartData.baselineX,
191
+ y: chartData.padding - 10,
192
+ textAnchor: "middle",
193
+ fill: "var(--sk-text-tertiary, #71717a)",
194
+ fontSize: "11",
195
+ fontFamily: "var(--sk-font, sans-serif)",
196
+ children: "Launch"
197
+ }
198
+ )
199
+ ] }),
200
+ /* @__PURE__ */ jsxRuntime.jsx("defs", { children: /* @__PURE__ */ jsxRuntime.jsxs("linearGradient", { id: `area-gradient-${activeMetric}`, x1: "0", y1: "0", x2: "0", y2: "1", children: [
201
+ /* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "0%", stopColor: color, stopOpacity: "0.3" }),
202
+ /* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "100%", stopColor: color, stopOpacity: "0" })
203
+ ] }) }),
204
+ hasTriggered && chartData.areaD && /* @__PURE__ */ jsxRuntime.jsx(
205
+ "path",
206
+ {
207
+ d: chartData.areaD,
208
+ fill: `url(#area-gradient-${activeMetric})`
209
+ }
210
+ ),
211
+ chartData.d && /* @__PURE__ */ jsxRuntime.jsx(
212
+ "path",
213
+ {
214
+ ref: pathRef,
215
+ d: chartData.d,
216
+ fill: "none",
217
+ stroke: color,
218
+ strokeWidth: "3",
219
+ strokeLinecap: "round",
220
+ strokeLinejoin: "round"
221
+ }
222
+ ),
223
+ hasTriggered && chartData.points.map((pt, i) => /* @__PURE__ */ jsxRuntime.jsx(
224
+ "circle",
225
+ {
226
+ cx: pt.x,
227
+ cy: pt.y,
228
+ r: "4",
229
+ fill: color,
230
+ stroke: "var(--sk-bg, #0a0a0a)",
231
+ strokeWidth: "2"
232
+ },
233
+ i
234
+ )),
235
+ chartData.labels.map((label, i) => /* @__PURE__ */ jsxRuntime.jsx(
236
+ "text",
237
+ {
238
+ x: chartData.points[i]?.x ?? 0,
239
+ y: chartData.chartHeight - 10,
240
+ textAnchor: "middle",
241
+ fill: "var(--sk-text-tertiary, #71717a)",
242
+ fontSize: "10",
243
+ fontFamily: "var(--sk-font, sans-serif)",
244
+ children: label
245
+ },
246
+ i
247
+ ))
248
+ ]
249
+ }
250
+ ) }) })
251
+ ] })
252
+ }
253
+ );
254
+ }
255
+
256
+ module.exports = MetricsTimelineSection;
257
+ //# sourceMappingURL=MetricsTimelineSection-4L6DUHJ5.cjs.map
258
+ //# sourceMappingURL=MetricsTimelineSection-4L6DUHJ5.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/portfolio/components/sections/MetricsTimelineSection.tsx"],"names":["useRef","useState","useMemo","useEffect","gsap","ScrollTrigger","useCallback","jsx","jsxs","ScrollReveal","GlassCard","Fragment"],"mappings":"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","file":"MetricsTimelineSection-4L6DUHJ5.cjs","sourcesContent":["'use client';\n\nimport React, { useRef, useEffect, useState, useCallback, useMemo } from 'react';\nimport gsap from 'gsap';\nimport { ScrollTrigger } from 'gsap/ScrollTrigger';\nimport type { PortfolioMetricsTimelineData } from '../../../types';\nimport ScrollReveal from '../primitives/ScrollReveal';\nimport GlassCard from '../primitives/GlassCard';\n\ninterface MetricsTimelineSectionProps {\n data: PortfolioMetricsTimelineData;\n}\n\ntype MetricKey = 'traffic' | 'conversions' | 'revenue' | 'rankings';\n\nconst metricColors: Record<MetricKey, string> = {\n traffic: '#6366f1',\n conversions: '#10b981',\n revenue: '#f59e0b',\n rankings: '#ec4899',\n};\n\nconst metricLabels: Record<MetricKey, string> = {\n traffic: 'Traffic',\n conversions: 'Conversions',\n revenue: 'Revenue',\n rankings: 'Rankings',\n};\n\nexport default function MetricsTimelineSection({ data }: MetricsTimelineSectionProps) {\n const svgRef = useRef<SVGSVGElement>(null);\n const pathRef = useRef<SVGPathElement>(null);\n const [activeMetric, setActiveMetric] = useState<MetricKey>('traffic');\n const [hasTriggered, setHasTriggered] = useState(false);\n\n // Determine available metrics\n const availableMetrics = useMemo(() => {\n const keys: MetricKey[] = ['traffic', 'conversions', 'revenue', 'rankings'];\n return keys.filter((key) =>\n data.dataPoints.some((dp) => dp.metrics[key] !== undefined && dp.metrics[key] !== null),\n );\n }, [data.dataPoints]);\n\n // Compute chart path for the active metric\n const chartData = useMemo(() => {\n const values = data.dataPoints.map((dp) => dp.metrics[activeMetric] ?? 0);\n const maxVal = Math.max(...values, 1);\n const minVal = Math.min(...values, 0);\n const range = maxVal - minVal || 1;\n\n const chartWidth = 800;\n const chartHeight = 300;\n const padding = 40;\n\n const points = values.map((val, i) => ({\n x: padding + (i / Math.max(values.length - 1, 1)) * (chartWidth - padding * 2),\n y: chartHeight - padding - ((val - minVal) / range) * (chartHeight - padding * 2),\n }));\n\n // Build SVG path\n let d = '';\n if (points.length > 0) {\n d = `M ${points[0].x} ${points[0].y}`;\n for (let i = 1; i < points.length; i++) {\n const cpx1 = (points[i - 1].x + points[i].x) / 2;\n const cpy1 = points[i - 1].y;\n const cpx2 = (points[i - 1].x + points[i].x) / 2;\n const cpy2 = points[i].y;\n d += ` C ${cpx1} ${cpy1}, ${cpx2} ${cpy2}, ${points[i].x} ${points[i].y}`;\n }\n }\n\n // Area fill path\n let areaD = d;\n if (points.length > 0) {\n areaD += ` L ${points[points.length - 1].x} ${chartHeight - padding} L ${points[0].x} ${chartHeight - padding} Z`;\n }\n\n // Find baseline index\n const baselineIdx = data.dataPoints.findIndex((dp) => dp.month === data.baselineDate);\n const baselineX = baselineIdx >= 0 ? points[baselineIdx]?.x : undefined;\n\n return {\n d,\n areaD,\n points,\n chartWidth,\n chartHeight,\n padding,\n baselineX,\n labels: data.dataPoints.map((dp) => dp.label),\n };\n }, [data.dataPoints, data.baselineDate, activeMetric]);\n\n // Animate path draw on scroll trigger\n useEffect(() => {\n gsap.registerPlugin(ScrollTrigger);\n\n const path = pathRef.current;\n const svg = svgRef.current;\n if (!path || !svg) return;\n\n const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n\n const ctx = gsap.context(() => {\n const length = path.getTotalLength();\n gsap.set(path, { strokeDasharray: length, strokeDashoffset: length });\n\n if (prefersReducedMotion) {\n gsap.set(path, { strokeDashoffset: 0 });\n setHasTriggered(true);\n return;\n }\n\n gsap.to(path, {\n strokeDashoffset: 0,\n duration: 2,\n ease: 'power2.out',\n scrollTrigger: {\n trigger: svg,\n start: 'top 75%',\n toggleActions: 'play none none none',\n },\n onComplete: () => setHasTriggered(true),\n });\n });\n\n return () => ctx.revert();\n }, [activeMetric]);\n\n const handleMetricChange = useCallback((metric: MetricKey) => {\n setActiveMetric(metric);\n setHasTriggered(false);\n }, []);\n\n const color = metricColors[activeMetric];\n\n return (\n <section\n className=\"w-full py-20 md:py-28\"\n style={{ background: 'var(--sk-bg, #0a0a0a)' }}\n >\n <div className=\"max-w-6xl mx-auto px-6\">\n {(data.title || data.description) && (\n <ScrollReveal y={30}>\n {data.title && (\n <h2\n className=\"text-3xl md:text-4xl font-bold mb-4\"\n style={{\n color: 'var(--sk-text-primary, #ffffff)',\n fontFamily: 'var(--sk-font-heading, inherit)',\n }}\n >\n {data.title}\n </h2>\n )}\n {data.description && (\n <p\n className=\"text-lg mb-8 max-w-2xl\"\n style={{ color: 'var(--sk-text-secondary, #a1a1aa)' }}\n >\n {data.description}\n </p>\n )}\n </ScrollReveal>\n )}\n\n {/* Metric toggle pills */}\n <ScrollReveal y={20} delay={0.1}>\n <div className=\"flex flex-wrap gap-2 mb-8\">\n {availableMetrics.map((metric) => (\n <button\n key={metric}\n onClick={() => handleMetricChange(metric)}\n className=\"px-4 py-2 rounded-full text-sm font-medium transition-all duration-200 cursor-pointer\"\n style={{\n background:\n activeMetric === metric\n ? metricColors[metric]\n : 'var(--sk-surface, rgba(255,255,255,0.05))',\n color:\n activeMetric === metric\n ? '#ffffff'\n : 'var(--sk-text-secondary, #a1a1aa)',\n border: `1px solid ${\n activeMetric === metric\n ? metricColors[metric]\n : 'var(--sk-border, rgba(255,255,255,0.1))'\n }`,\n }}\n >\n {metricLabels[metric]}\n </button>\n ))}\n </div>\n </ScrollReveal>\n\n <ScrollReveal y={40} delay={0.2}>\n <GlassCard padding=\"lg\" hover={false}>\n <svg\n ref={svgRef}\n viewBox={`0 0 ${chartData.chartWidth} ${chartData.chartHeight}`}\n className=\"w-full h-auto\"\n preserveAspectRatio=\"xMidYMid meet\"\n >\n {/* Grid lines */}\n {[0, 1, 2, 3, 4].map((i) => {\n const y =\n chartData.padding +\n (i / 4) * (chartData.chartHeight - chartData.padding * 2);\n return (\n <line\n key={i}\n x1={chartData.padding}\n y1={y}\n x2={chartData.chartWidth - chartData.padding}\n y2={y}\n stroke=\"var(--sk-border, rgba(255,255,255,0.1))\"\n strokeWidth=\"1\"\n />\n );\n })}\n\n {/* Baseline marker */}\n {chartData.baselineX !== undefined && (\n <>\n <line\n x1={chartData.baselineX}\n y1={chartData.padding}\n x2={chartData.baselineX}\n y2={chartData.chartHeight - chartData.padding}\n stroke=\"var(--sk-text-tertiary, #71717a)\"\n strokeWidth=\"1.5\"\n strokeDasharray=\"6 4\"\n />\n <text\n x={chartData.baselineX}\n y={chartData.padding - 10}\n textAnchor=\"middle\"\n fill=\"var(--sk-text-tertiary, #71717a)\"\n fontSize=\"11\"\n fontFamily=\"var(--sk-font, sans-serif)\"\n >\n Launch\n </text>\n </>\n )}\n\n {/* Area fill (gradient) */}\n <defs>\n <linearGradient id={`area-gradient-${activeMetric}`} x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <stop offset=\"0%\" stopColor={color} stopOpacity=\"0.3\" />\n <stop offset=\"100%\" stopColor={color} stopOpacity=\"0\" />\n </linearGradient>\n </defs>\n {hasTriggered && chartData.areaD && (\n <path\n d={chartData.areaD}\n fill={`url(#area-gradient-${activeMetric})`}\n />\n )}\n\n {/* Chart line (animated draw) */}\n {chartData.d && (\n <path\n ref={pathRef}\n d={chartData.d}\n fill=\"none\"\n stroke={color}\n strokeWidth=\"3\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n )}\n\n {/* Data points */}\n {hasTriggered &&\n chartData.points.map((pt, i) => (\n <circle\n key={i}\n cx={pt.x}\n cy={pt.y}\n r=\"4\"\n fill={color}\n stroke=\"var(--sk-bg, #0a0a0a)\"\n strokeWidth=\"2\"\n />\n ))}\n\n {/* X-axis labels */}\n {chartData.labels.map((label, i) => (\n <text\n key={i}\n x={chartData.points[i]?.x ?? 0}\n y={chartData.chartHeight - 10}\n textAnchor=\"middle\"\n fill=\"var(--sk-text-tertiary, #71717a)\"\n fontSize=\"10\"\n fontFamily=\"var(--sk-font, sans-serif)\"\n >\n {label}\n </text>\n ))}\n </svg>\n </GlassCard>\n </ScrollReveal>\n </div>\n </section>\n );\n}\n"]}
@@ -0,0 +1,253 @@
1
+ 'use client';
2
+ import { GlassCard } from './chunk-YB4B3OMC.js';
3
+ import { ScrollReveal } from './chunk-7CFFAKDM.js';
4
+ import { useRef, useState, useMemo, useEffect, useCallback } from 'react';
5
+ import gsap from 'gsap';
6
+ import { ScrollTrigger } from 'gsap/ScrollTrigger';
7
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
8
+
9
+ var metricColors = {
10
+ traffic: "#6366f1",
11
+ conversions: "#10b981",
12
+ revenue: "#f59e0b",
13
+ rankings: "#ec4899"
14
+ };
15
+ var metricLabels = {
16
+ traffic: "Traffic",
17
+ conversions: "Conversions",
18
+ revenue: "Revenue",
19
+ rankings: "Rankings"
20
+ };
21
+ function MetricsTimelineSection({ data }) {
22
+ const svgRef = useRef(null);
23
+ const pathRef = useRef(null);
24
+ const [activeMetric, setActiveMetric] = useState("traffic");
25
+ const [hasTriggered, setHasTriggered] = useState(false);
26
+ const availableMetrics = useMemo(() => {
27
+ const keys = ["traffic", "conversions", "revenue", "rankings"];
28
+ return keys.filter(
29
+ (key) => data.dataPoints.some((dp) => dp.metrics[key] !== void 0 && dp.metrics[key] !== null)
30
+ );
31
+ }, [data.dataPoints]);
32
+ const chartData = useMemo(() => {
33
+ const values = data.dataPoints.map((dp) => dp.metrics[activeMetric] ?? 0);
34
+ const maxVal = Math.max(...values, 1);
35
+ const minVal = Math.min(...values, 0);
36
+ const range = maxVal - minVal || 1;
37
+ const chartWidth = 800;
38
+ const chartHeight = 300;
39
+ const padding = 40;
40
+ const points = values.map((val, i) => ({
41
+ x: padding + i / Math.max(values.length - 1, 1) * (chartWidth - padding * 2),
42
+ y: chartHeight - padding - (val - minVal) / range * (chartHeight - padding * 2)
43
+ }));
44
+ let d = "";
45
+ if (points.length > 0) {
46
+ d = `M ${points[0].x} ${points[0].y}`;
47
+ for (let i = 1; i < points.length; i++) {
48
+ const cpx1 = (points[i - 1].x + points[i].x) / 2;
49
+ const cpy1 = points[i - 1].y;
50
+ const cpx2 = (points[i - 1].x + points[i].x) / 2;
51
+ const cpy2 = points[i].y;
52
+ d += ` C ${cpx1} ${cpy1}, ${cpx2} ${cpy2}, ${points[i].x} ${points[i].y}`;
53
+ }
54
+ }
55
+ let areaD = d;
56
+ if (points.length > 0) {
57
+ areaD += ` L ${points[points.length - 1].x} ${chartHeight - padding} L ${points[0].x} ${chartHeight - padding} Z`;
58
+ }
59
+ const baselineIdx = data.dataPoints.findIndex((dp) => dp.month === data.baselineDate);
60
+ const baselineX = baselineIdx >= 0 ? points[baselineIdx]?.x : void 0;
61
+ return {
62
+ d,
63
+ areaD,
64
+ points,
65
+ chartWidth,
66
+ chartHeight,
67
+ padding,
68
+ baselineX,
69
+ labels: data.dataPoints.map((dp) => dp.label)
70
+ };
71
+ }, [data.dataPoints, data.baselineDate, activeMetric]);
72
+ useEffect(() => {
73
+ gsap.registerPlugin(ScrollTrigger);
74
+ const path = pathRef.current;
75
+ const svg = svgRef.current;
76
+ if (!path || !svg) return;
77
+ const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
78
+ const ctx = gsap.context(() => {
79
+ const length = path.getTotalLength();
80
+ gsap.set(path, { strokeDasharray: length, strokeDashoffset: length });
81
+ if (prefersReducedMotion) {
82
+ gsap.set(path, { strokeDashoffset: 0 });
83
+ setHasTriggered(true);
84
+ return;
85
+ }
86
+ gsap.to(path, {
87
+ strokeDashoffset: 0,
88
+ duration: 2,
89
+ ease: "power2.out",
90
+ scrollTrigger: {
91
+ trigger: svg,
92
+ start: "top 75%",
93
+ toggleActions: "play none none none"
94
+ },
95
+ onComplete: () => setHasTriggered(true)
96
+ });
97
+ });
98
+ return () => ctx.revert();
99
+ }, [activeMetric]);
100
+ const handleMetricChange = useCallback((metric) => {
101
+ setActiveMetric(metric);
102
+ setHasTriggered(false);
103
+ }, []);
104
+ const color = metricColors[activeMetric];
105
+ return /* @__PURE__ */ jsx(
106
+ "section",
107
+ {
108
+ className: "w-full py-20 md:py-28",
109
+ style: { background: "var(--sk-bg, #0a0a0a)" },
110
+ children: /* @__PURE__ */ jsxs("div", { className: "max-w-6xl mx-auto px-6", children: [
111
+ (data.title || data.description) && /* @__PURE__ */ jsxs(ScrollReveal, { y: 30, children: [
112
+ data.title && /* @__PURE__ */ jsx(
113
+ "h2",
114
+ {
115
+ className: "text-3xl md:text-4xl font-bold mb-4",
116
+ style: {
117
+ color: "var(--sk-text-primary, #ffffff)",
118
+ fontFamily: "var(--sk-font-heading, inherit)"
119
+ },
120
+ children: data.title
121
+ }
122
+ ),
123
+ data.description && /* @__PURE__ */ jsx(
124
+ "p",
125
+ {
126
+ className: "text-lg mb-8 max-w-2xl",
127
+ style: { color: "var(--sk-text-secondary, #a1a1aa)" },
128
+ children: data.description
129
+ }
130
+ )
131
+ ] }),
132
+ /* @__PURE__ */ jsx(ScrollReveal, { y: 20, delay: 0.1, children: /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2 mb-8", children: availableMetrics.map((metric) => /* @__PURE__ */ jsx(
133
+ "button",
134
+ {
135
+ onClick: () => handleMetricChange(metric),
136
+ className: "px-4 py-2 rounded-full text-sm font-medium transition-all duration-200 cursor-pointer",
137
+ style: {
138
+ background: activeMetric === metric ? metricColors[metric] : "var(--sk-surface, rgba(255,255,255,0.05))",
139
+ color: activeMetric === metric ? "#ffffff" : "var(--sk-text-secondary, #a1a1aa)",
140
+ border: `1px solid ${activeMetric === metric ? metricColors[metric] : "var(--sk-border, rgba(255,255,255,0.1))"}`
141
+ },
142
+ children: metricLabels[metric]
143
+ },
144
+ metric
145
+ )) }) }),
146
+ /* @__PURE__ */ jsx(ScrollReveal, { y: 40, delay: 0.2, children: /* @__PURE__ */ jsx(GlassCard, { padding: "lg", hover: false, children: /* @__PURE__ */ jsxs(
147
+ "svg",
148
+ {
149
+ ref: svgRef,
150
+ viewBox: `0 0 ${chartData.chartWidth} ${chartData.chartHeight}`,
151
+ className: "w-full h-auto",
152
+ preserveAspectRatio: "xMidYMid meet",
153
+ children: [
154
+ [0, 1, 2, 3, 4].map((i) => {
155
+ const y = chartData.padding + i / 4 * (chartData.chartHeight - chartData.padding * 2);
156
+ return /* @__PURE__ */ jsx(
157
+ "line",
158
+ {
159
+ x1: chartData.padding,
160
+ y1: y,
161
+ x2: chartData.chartWidth - chartData.padding,
162
+ y2: y,
163
+ stroke: "var(--sk-border, rgba(255,255,255,0.1))",
164
+ strokeWidth: "1"
165
+ },
166
+ i
167
+ );
168
+ }),
169
+ chartData.baselineX !== void 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
170
+ /* @__PURE__ */ jsx(
171
+ "line",
172
+ {
173
+ x1: chartData.baselineX,
174
+ y1: chartData.padding,
175
+ x2: chartData.baselineX,
176
+ y2: chartData.chartHeight - chartData.padding,
177
+ stroke: "var(--sk-text-tertiary, #71717a)",
178
+ strokeWidth: "1.5",
179
+ strokeDasharray: "6 4"
180
+ }
181
+ ),
182
+ /* @__PURE__ */ jsx(
183
+ "text",
184
+ {
185
+ x: chartData.baselineX,
186
+ y: chartData.padding - 10,
187
+ textAnchor: "middle",
188
+ fill: "var(--sk-text-tertiary, #71717a)",
189
+ fontSize: "11",
190
+ fontFamily: "var(--sk-font, sans-serif)",
191
+ children: "Launch"
192
+ }
193
+ )
194
+ ] }),
195
+ /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsxs("linearGradient", { id: `area-gradient-${activeMetric}`, x1: "0", y1: "0", x2: "0", y2: "1", children: [
196
+ /* @__PURE__ */ jsx("stop", { offset: "0%", stopColor: color, stopOpacity: "0.3" }),
197
+ /* @__PURE__ */ jsx("stop", { offset: "100%", stopColor: color, stopOpacity: "0" })
198
+ ] }) }),
199
+ hasTriggered && chartData.areaD && /* @__PURE__ */ jsx(
200
+ "path",
201
+ {
202
+ d: chartData.areaD,
203
+ fill: `url(#area-gradient-${activeMetric})`
204
+ }
205
+ ),
206
+ chartData.d && /* @__PURE__ */ jsx(
207
+ "path",
208
+ {
209
+ ref: pathRef,
210
+ d: chartData.d,
211
+ fill: "none",
212
+ stroke: color,
213
+ strokeWidth: "3",
214
+ strokeLinecap: "round",
215
+ strokeLinejoin: "round"
216
+ }
217
+ ),
218
+ hasTriggered && chartData.points.map((pt, i) => /* @__PURE__ */ jsx(
219
+ "circle",
220
+ {
221
+ cx: pt.x,
222
+ cy: pt.y,
223
+ r: "4",
224
+ fill: color,
225
+ stroke: "var(--sk-bg, #0a0a0a)",
226
+ strokeWidth: "2"
227
+ },
228
+ i
229
+ )),
230
+ chartData.labels.map((label, i) => /* @__PURE__ */ jsx(
231
+ "text",
232
+ {
233
+ x: chartData.points[i]?.x ?? 0,
234
+ y: chartData.chartHeight - 10,
235
+ textAnchor: "middle",
236
+ fill: "var(--sk-text-tertiary, #71717a)",
237
+ fontSize: "10",
238
+ fontFamily: "var(--sk-font, sans-serif)",
239
+ children: label
240
+ },
241
+ i
242
+ ))
243
+ ]
244
+ }
245
+ ) }) })
246
+ ] })
247
+ }
248
+ );
249
+ }
250
+
251
+ export { MetricsTimelineSection as default };
252
+ //# sourceMappingURL=MetricsTimelineSection-6BT5GNFV.js.map
253
+ //# sourceMappingURL=MetricsTimelineSection-6BT5GNFV.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/portfolio/components/sections/MetricsTimelineSection.tsx"],"names":[],"mappings":"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","file":"MetricsTimelineSection-6BT5GNFV.js","sourcesContent":["'use client';\n\nimport React, { useRef, useEffect, useState, useCallback, useMemo } from 'react';\nimport gsap from 'gsap';\nimport { ScrollTrigger } from 'gsap/ScrollTrigger';\nimport type { PortfolioMetricsTimelineData } from '../../../types';\nimport ScrollReveal from '../primitives/ScrollReveal';\nimport GlassCard from '../primitives/GlassCard';\n\ninterface MetricsTimelineSectionProps {\n data: PortfolioMetricsTimelineData;\n}\n\ntype MetricKey = 'traffic' | 'conversions' | 'revenue' | 'rankings';\n\nconst metricColors: Record<MetricKey, string> = {\n traffic: '#6366f1',\n conversions: '#10b981',\n revenue: '#f59e0b',\n rankings: '#ec4899',\n};\n\nconst metricLabels: Record<MetricKey, string> = {\n traffic: 'Traffic',\n conversions: 'Conversions',\n revenue: 'Revenue',\n rankings: 'Rankings',\n};\n\nexport default function MetricsTimelineSection({ data }: MetricsTimelineSectionProps) {\n const svgRef = useRef<SVGSVGElement>(null);\n const pathRef = useRef<SVGPathElement>(null);\n const [activeMetric, setActiveMetric] = useState<MetricKey>('traffic');\n const [hasTriggered, setHasTriggered] = useState(false);\n\n // Determine available metrics\n const availableMetrics = useMemo(() => {\n const keys: MetricKey[] = ['traffic', 'conversions', 'revenue', 'rankings'];\n return keys.filter((key) =>\n data.dataPoints.some((dp) => dp.metrics[key] !== undefined && dp.metrics[key] !== null),\n );\n }, [data.dataPoints]);\n\n // Compute chart path for the active metric\n const chartData = useMemo(() => {\n const values = data.dataPoints.map((dp) => dp.metrics[activeMetric] ?? 0);\n const maxVal = Math.max(...values, 1);\n const minVal = Math.min(...values, 0);\n const range = maxVal - minVal || 1;\n\n const chartWidth = 800;\n const chartHeight = 300;\n const padding = 40;\n\n const points = values.map((val, i) => ({\n x: padding + (i / Math.max(values.length - 1, 1)) * (chartWidth - padding * 2),\n y: chartHeight - padding - ((val - minVal) / range) * (chartHeight - padding * 2),\n }));\n\n // Build SVG path\n let d = '';\n if (points.length > 0) {\n d = `M ${points[0].x} ${points[0].y}`;\n for (let i = 1; i < points.length; i++) {\n const cpx1 = (points[i - 1].x + points[i].x) / 2;\n const cpy1 = points[i - 1].y;\n const cpx2 = (points[i - 1].x + points[i].x) / 2;\n const cpy2 = points[i].y;\n d += ` C ${cpx1} ${cpy1}, ${cpx2} ${cpy2}, ${points[i].x} ${points[i].y}`;\n }\n }\n\n // Area fill path\n let areaD = d;\n if (points.length > 0) {\n areaD += ` L ${points[points.length - 1].x} ${chartHeight - padding} L ${points[0].x} ${chartHeight - padding} Z`;\n }\n\n // Find baseline index\n const baselineIdx = data.dataPoints.findIndex((dp) => dp.month === data.baselineDate);\n const baselineX = baselineIdx >= 0 ? points[baselineIdx]?.x : undefined;\n\n return {\n d,\n areaD,\n points,\n chartWidth,\n chartHeight,\n padding,\n baselineX,\n labels: data.dataPoints.map((dp) => dp.label),\n };\n }, [data.dataPoints, data.baselineDate, activeMetric]);\n\n // Animate path draw on scroll trigger\n useEffect(() => {\n gsap.registerPlugin(ScrollTrigger);\n\n const path = pathRef.current;\n const svg = svgRef.current;\n if (!path || !svg) return;\n\n const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n\n const ctx = gsap.context(() => {\n const length = path.getTotalLength();\n gsap.set(path, { strokeDasharray: length, strokeDashoffset: length });\n\n if (prefersReducedMotion) {\n gsap.set(path, { strokeDashoffset: 0 });\n setHasTriggered(true);\n return;\n }\n\n gsap.to(path, {\n strokeDashoffset: 0,\n duration: 2,\n ease: 'power2.out',\n scrollTrigger: {\n trigger: svg,\n start: 'top 75%',\n toggleActions: 'play none none none',\n },\n onComplete: () => setHasTriggered(true),\n });\n });\n\n return () => ctx.revert();\n }, [activeMetric]);\n\n const handleMetricChange = useCallback((metric: MetricKey) => {\n setActiveMetric(metric);\n setHasTriggered(false);\n }, []);\n\n const color = metricColors[activeMetric];\n\n return (\n <section\n className=\"w-full py-20 md:py-28\"\n style={{ background: 'var(--sk-bg, #0a0a0a)' }}\n >\n <div className=\"max-w-6xl mx-auto px-6\">\n {(data.title || data.description) && (\n <ScrollReveal y={30}>\n {data.title && (\n <h2\n className=\"text-3xl md:text-4xl font-bold mb-4\"\n style={{\n color: 'var(--sk-text-primary, #ffffff)',\n fontFamily: 'var(--sk-font-heading, inherit)',\n }}\n >\n {data.title}\n </h2>\n )}\n {data.description && (\n <p\n className=\"text-lg mb-8 max-w-2xl\"\n style={{ color: 'var(--sk-text-secondary, #a1a1aa)' }}\n >\n {data.description}\n </p>\n )}\n </ScrollReveal>\n )}\n\n {/* Metric toggle pills */}\n <ScrollReveal y={20} delay={0.1}>\n <div className=\"flex flex-wrap gap-2 mb-8\">\n {availableMetrics.map((metric) => (\n <button\n key={metric}\n onClick={() => handleMetricChange(metric)}\n className=\"px-4 py-2 rounded-full text-sm font-medium transition-all duration-200 cursor-pointer\"\n style={{\n background:\n activeMetric === metric\n ? metricColors[metric]\n : 'var(--sk-surface, rgba(255,255,255,0.05))',\n color:\n activeMetric === metric\n ? '#ffffff'\n : 'var(--sk-text-secondary, #a1a1aa)',\n border: `1px solid ${\n activeMetric === metric\n ? metricColors[metric]\n : 'var(--sk-border, rgba(255,255,255,0.1))'\n }`,\n }}\n >\n {metricLabels[metric]}\n </button>\n ))}\n </div>\n </ScrollReveal>\n\n <ScrollReveal y={40} delay={0.2}>\n <GlassCard padding=\"lg\" hover={false}>\n <svg\n ref={svgRef}\n viewBox={`0 0 ${chartData.chartWidth} ${chartData.chartHeight}`}\n className=\"w-full h-auto\"\n preserveAspectRatio=\"xMidYMid meet\"\n >\n {/* Grid lines */}\n {[0, 1, 2, 3, 4].map((i) => {\n const y =\n chartData.padding +\n (i / 4) * (chartData.chartHeight - chartData.padding * 2);\n return (\n <line\n key={i}\n x1={chartData.padding}\n y1={y}\n x2={chartData.chartWidth - chartData.padding}\n y2={y}\n stroke=\"var(--sk-border, rgba(255,255,255,0.1))\"\n strokeWidth=\"1\"\n />\n );\n })}\n\n {/* Baseline marker */}\n {chartData.baselineX !== undefined && (\n <>\n <line\n x1={chartData.baselineX}\n y1={chartData.padding}\n x2={chartData.baselineX}\n y2={chartData.chartHeight - chartData.padding}\n stroke=\"var(--sk-text-tertiary, #71717a)\"\n strokeWidth=\"1.5\"\n strokeDasharray=\"6 4\"\n />\n <text\n x={chartData.baselineX}\n y={chartData.padding - 10}\n textAnchor=\"middle\"\n fill=\"var(--sk-text-tertiary, #71717a)\"\n fontSize=\"11\"\n fontFamily=\"var(--sk-font, sans-serif)\"\n >\n Launch\n </text>\n </>\n )}\n\n {/* Area fill (gradient) */}\n <defs>\n <linearGradient id={`area-gradient-${activeMetric}`} x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <stop offset=\"0%\" stopColor={color} stopOpacity=\"0.3\" />\n <stop offset=\"100%\" stopColor={color} stopOpacity=\"0\" />\n </linearGradient>\n </defs>\n {hasTriggered && chartData.areaD && (\n <path\n d={chartData.areaD}\n fill={`url(#area-gradient-${activeMetric})`}\n />\n )}\n\n {/* Chart line (animated draw) */}\n {chartData.d && (\n <path\n ref={pathRef}\n d={chartData.d}\n fill=\"none\"\n stroke={color}\n strokeWidth=\"3\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n )}\n\n {/* Data points */}\n {hasTriggered &&\n chartData.points.map((pt, i) => (\n <circle\n key={i}\n cx={pt.x}\n cy={pt.y}\n r=\"4\"\n fill={color}\n stroke=\"var(--sk-bg, #0a0a0a)\"\n strokeWidth=\"2\"\n />\n ))}\n\n {/* X-axis labels */}\n {chartData.labels.map((label, i) => (\n <text\n key={i}\n x={chartData.points[i]?.x ?? 0}\n y={chartData.chartHeight - 10}\n textAnchor=\"middle\"\n fill=\"var(--sk-text-tertiary, #71717a)\"\n fontSize=\"10\"\n fontFamily=\"var(--sk-font, sans-serif)\"\n >\n {label}\n </text>\n ))}\n </svg>\n </GlassCard>\n </ScrollReveal>\n </div>\n </section>\n );\n}\n"]}