@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.
- package/dist/BeforeAfterSection-6QUJOBO2.js +176 -0
- package/dist/BeforeAfterSection-6QUJOBO2.js.map +1 -0
- package/dist/BeforeAfterSection-DVAWWE4K.cjs +181 -0
- package/dist/BeforeAfterSection-DVAWWE4K.cjs.map +1 -0
- package/dist/CTASection-4JKLXEUF.cjs +111 -0
- package/dist/CTASection-4JKLXEUF.cjs.map +1 -0
- package/dist/CTASection-BJA72XIL.js +106 -0
- package/dist/CTASection-BJA72XIL.js.map +1 -0
- package/dist/ChallengesSection-GEQGVSJN.js +180 -0
- package/dist/ChallengesSection-GEQGVSJN.js.map +1 -0
- package/dist/ChallengesSection-IZ3DHECS.cjs +182 -0
- package/dist/ChallengesSection-IZ3DHECS.cjs.map +1 -0
- package/dist/ConversionFunnelSection-AUUSJ5HQ.cjs +209 -0
- package/dist/ConversionFunnelSection-AUUSJ5HQ.cjs.map +1 -0
- package/dist/ConversionFunnelSection-D3GE4NKE.js +203 -0
- package/dist/ConversionFunnelSection-D3GE4NKE.js.map +1 -0
- package/dist/DetailsSection-FB763FS7.js +135 -0
- package/dist/DetailsSection-FB763FS7.js.map +1 -0
- package/dist/DetailsSection-OACJFGH7.cjs +137 -0
- package/dist/DetailsSection-OACJFGH7.cjs.map +1 -0
- package/dist/FeatureSpotlightSection-B7P3JGNL.js +205 -0
- package/dist/FeatureSpotlightSection-B7P3JGNL.js.map +1 -0
- package/dist/FeatureSpotlightSection-WRHXS7TU.cjs +210 -0
- package/dist/FeatureSpotlightSection-WRHXS7TU.cjs.map +1 -0
- package/dist/GallerySection-VMKORC47.js +218 -0
- package/dist/GallerySection-VMKORC47.js.map +1 -0
- package/dist/GallerySection-WJ4PQDBI.cjs +219 -0
- package/dist/GallerySection-WJ4PQDBI.cjs.map +1 -0
- package/dist/MetricsTimelineSection-4L6DUHJ5.cjs +258 -0
- package/dist/MetricsTimelineSection-4L6DUHJ5.cjs.map +1 -0
- package/dist/MetricsTimelineSection-6BT5GNFV.js +253 -0
- package/dist/MetricsTimelineSection-6BT5GNFV.js.map +1 -0
- package/dist/ResultsSection-DFUJ5U6M.js +93 -0
- package/dist/ResultsSection-DFUJ5U6M.js.map +1 -0
- package/dist/ResultsSection-XLGMMQKY.cjs +95 -0
- package/dist/ResultsSection-XLGMMQKY.cjs.map +1 -0
- package/dist/ServicesSection-D5V3Q4GR.js +118 -0
- package/dist/ServicesSection-D5V3Q4GR.js.map +1 -0
- package/dist/ServicesSection-WJMGK2MF.cjs +120 -0
- package/dist/ServicesSection-WJMGK2MF.cjs.map +1 -0
- package/dist/StrategySection-3ED3QW4R.cjs +180 -0
- package/dist/StrategySection-3ED3QW4R.cjs.map +1 -0
- package/dist/StrategySection-VUWMIYYP.js +175 -0
- package/dist/StrategySection-VUWMIYYP.js.map +1 -0
- package/dist/TeamSection-DZVSNZE6.cjs +112 -0
- package/dist/TeamSection-DZVSNZE6.cjs.map +1 -0
- package/dist/TeamSection-HGKFW6PQ.js +107 -0
- package/dist/TeamSection-HGKFW6PQ.js.map +1 -0
- package/dist/TechStackSection-OCUYG4XT.js +90 -0
- package/dist/TechStackSection-OCUYG4XT.js.map +1 -0
- package/dist/TechStackSection-VKJK4KQB.cjs +91 -0
- package/dist/TechStackSection-VKJK4KQB.cjs.map +1 -0
- package/dist/TestimonialSection-6RGSMXQB.js +122 -0
- package/dist/TestimonialSection-6RGSMXQB.js.map +1 -0
- package/dist/TestimonialSection-XPTFUQIN.cjs +124 -0
- package/dist/TestimonialSection-XPTFUQIN.cjs.map +1 -0
- package/dist/VideoSection-4A2HC6K6.js +117 -0
- package/dist/VideoSection-4A2HC6K6.js.map +1 -0
- package/dist/VideoSection-G3DFS7UH.cjs +118 -0
- package/dist/VideoSection-G3DFS7UH.cjs.map +1 -0
- package/dist/chunk-2VNNFAG6.js +415 -0
- package/dist/chunk-2VNNFAG6.js.map +1 -0
- package/dist/chunk-2Y4O3LWM.js +53 -0
- package/dist/chunk-2Y4O3LWM.js.map +1 -0
- package/dist/chunk-5FKOLIV6.cjs +221 -0
- package/dist/chunk-5FKOLIV6.cjs.map +1 -0
- package/dist/chunk-7CFFAKDM.js +74 -0
- package/dist/chunk-7CFFAKDM.js.map +1 -0
- package/dist/chunk-A4I4IK7V.js +69 -0
- package/dist/chunk-A4I4IK7V.js.map +1 -0
- package/dist/chunk-IKBK7HYX.cjs +79 -0
- package/dist/chunk-IKBK7HYX.cjs.map +1 -0
- package/dist/chunk-KEOHORIH.cjs +79 -0
- package/dist/chunk-KEOHORIH.cjs.map +1 -0
- package/dist/chunk-NAS4K5UR.cjs +139 -0
- package/dist/chunk-NAS4K5UR.cjs.map +1 -0
- package/dist/chunk-QBLWP25X.cjs +73 -0
- package/dist/chunk-QBLWP25X.cjs.map +1 -0
- package/dist/chunk-QIC6JFFD.js +210 -0
- package/dist/chunk-QIC6JFFD.js.map +1 -0
- package/dist/chunk-TAPNXT7X.cjs +422 -0
- package/dist/chunk-TAPNXT7X.cjs.map +1 -0
- package/dist/chunk-XCKXHK44.js +15 -0
- package/dist/chunk-XCKXHK44.js.map +1 -0
- package/dist/chunk-XMC4DN6G.js +131 -0
- package/dist/chunk-XMC4DN6G.js.map +1 -0
- package/dist/chunk-XONXEFJY.cjs +58 -0
- package/dist/chunk-XONXEFJY.cjs.map +1 -0
- package/dist/chunk-XQNJED46.cjs +19 -0
- package/dist/chunk-XQNJED46.cjs.map +1 -0
- package/dist/chunk-YB4B3OMC.js +74 -0
- package/dist/chunk-YB4B3OMC.js.map +1 -0
- package/dist/index.cjs +271 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +137 -0
- package/dist/index.d.ts +137 -0
- package/dist/index.js +197 -0
- package/dist/index.js.map +1 -0
- package/dist/layout/index.cjs +13 -0
- package/dist/layout/index.cjs.map +1 -0
- package/dist/layout/index.d.cts +54 -0
- package/dist/layout/index.d.ts +54 -0
- package/dist/layout/index.js +4 -0
- package/dist/layout/index.js.map +1 -0
- package/dist/portfolio/client.cjs +18 -0
- package/dist/portfolio/client.cjs.map +1 -0
- package/dist/portfolio/client.d.cts +97 -0
- package/dist/portfolio/client.d.ts +97 -0
- package/dist/portfolio/client.js +6 -0
- package/dist/portfolio/client.js.map +1 -0
- package/dist/portfolio/index.cjs +41 -0
- package/dist/portfolio/index.cjs.map +1 -0
- package/dist/portfolio/index.d.cts +12 -0
- package/dist/portfolio/index.d.ts +12 -0
- package/dist/portfolio/index.js +8 -0
- package/dist/portfolio/index.js.map +1 -0
- package/dist/portfolio/sections.cjs +20 -0
- package/dist/portfolio/sections.cjs.map +1 -0
- package/dist/portfolio/sections.d.cts +42 -0
- package/dist/portfolio/sections.d.ts +42 -0
- package/dist/portfolio/sections.js +4 -0
- package/dist/portfolio/sections.js.map +1 -0
- package/dist/portfolio/server.cjs +141 -0
- package/dist/portfolio/server.cjs.map +1 -0
- package/dist/portfolio/server.d.cts +68 -0
- package/dist/portfolio/server.d.ts +68 -0
- package/dist/portfolio/server.js +134 -0
- package/dist/portfolio/server.js.map +1 -0
- package/dist/types-BMUhBhWx.d.cts +346 -0
- package/dist/types-BMUhBhWx.d.ts +346 -0
- 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"]}
|