@sonordev/agency-site-kit 0.4.0 → 0.4.2
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/{chunk-L7E3UHQC.cjs → chunk-2Y2RJB3A.cjs} +16 -13
- package/dist/chunk-2Y2RJB3A.cjs.map +1 -0
- package/dist/{chunk-4N7ENJ7L.cjs → chunk-6OWFFV6N.cjs} +14 -12
- package/dist/chunk-6OWFFV6N.cjs.map +1 -0
- package/dist/chunk-7D4H36UF.js +13 -0
- package/dist/chunk-7D4H36UF.js.map +1 -0
- package/dist/{chunk-UNTOEWQL.js → chunk-7FFZX7C5.js} +10 -7
- package/dist/chunk-7FFZX7C5.js.map +1 -0
- package/dist/{chunk-7ZHRMWJU.cjs → chunk-A5WCMHAE.cjs} +2 -2
- package/dist/{chunk-7ZHRMWJU.cjs.map → chunk-A5WCMHAE.cjs.map} +1 -1
- package/dist/chunk-FM25LB67.js +50 -0
- package/dist/chunk-FM25LB67.js.map +1 -0
- package/dist/{chunk-KXLROAO5.js → chunk-FT2SNEYP.js} +2 -2
- package/dist/{chunk-KXLROAO5.js.map → chunk-FT2SNEYP.js.map} +1 -1
- package/dist/{chunk-XLK77USA.js → chunk-K2BGTDSN.js} +2 -2
- package/dist/{chunk-XLK77USA.js.map → chunk-K2BGTDSN.js.map} +1 -1
- package/dist/chunk-X76U7VLF.cjs +57 -0
- package/dist/chunk-X76U7VLF.cjs.map +1 -0
- package/dist/{chunk-NIZJLONK.js → chunk-X7UCJ45A.js} +14 -12
- package/dist/chunk-X7UCJ45A.js.map +1 -0
- package/dist/chunk-ZBPF7TLZ.cjs +16 -0
- package/dist/chunk-ZBPF7TLZ.cjs.map +1 -0
- package/dist/format-C-axf13G.d.cts +68 -0
- package/dist/format-poUpLtQ2.d.ts +68 -0
- package/dist/index.cjs +36 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -2
- package/dist/index.d.ts +4 -2
- package/dist/index.js +5 -3
- package/dist/index.js.map +1 -1
- package/dist/layout/index.d.cts +1 -1
- package/dist/layout/index.d.ts +1 -1
- package/dist/portfolio/client.cjs +25 -6
- package/dist/portfolio/client.cjs.map +1 -1
- package/dist/portfolio/client.d.cts +20 -1
- package/dist/portfolio/client.d.ts +20 -1
- package/dist/portfolio/client.js +22 -5
- package/dist/portfolio/client.js.map +1 -1
- package/dist/portfolio/components/PortfolioErrorFallback.cjs +1 -1
- package/dist/portfolio/components/PortfolioErrorFallback.cjs.map +1 -1
- package/dist/portfolio/components/PortfolioErrorFallback.js +1 -1
- package/dist/portfolio/index.cjs +36 -10
- package/dist/portfolio/index.d.cts +12 -4
- package/dist/portfolio/index.d.ts +12 -4
- package/dist/portfolio/index.js +5 -3
- package/dist/portfolio/sections/ChallengesSection.cjs +1 -1
- package/dist/portfolio/sections/ChallengesSection.cjs.map +1 -1
- package/dist/portfolio/sections/ChallengesSection.d.cts +1 -1
- package/dist/portfolio/sections/ChallengesSection.d.ts +1 -1
- package/dist/portfolio/sections/ChallengesSection.js +1 -1
- package/dist/portfolio/sections/ChallengesSection.js.map +1 -1
- package/dist/portfolio/sections/DetailsSection.d.cts +1 -1
- package/dist/portfolio/sections/DetailsSection.d.ts +1 -1
- package/dist/portfolio/sections/HeroSection.cjs +3 -3
- package/dist/portfolio/sections/HeroSection.cjs.map +1 -1
- package/dist/portfolio/sections/HeroSection.d.cts +1 -1
- package/dist/portfolio/sections/HeroSection.d.ts +1 -1
- package/dist/portfolio/sections/HeroSection.js +3 -3
- package/dist/portfolio/sections/HeroSection.js.map +1 -1
- package/dist/portfolio/sections/PerformanceSection.cjs +1 -64
- package/dist/portfolio/sections/PerformanceSection.cjs.map +1 -1
- package/dist/portfolio/sections/PerformanceSection.js +1 -64
- package/dist/portfolio/sections/PerformanceSection.js.map +1 -1
- package/dist/portfolio/sections/ResultsSection.cjs +4 -2
- package/dist/portfolio/sections/ResultsSection.cjs.map +1 -1
- package/dist/portfolio/sections/ResultsSection.d.cts +2 -2
- package/dist/portfolio/sections/ResultsSection.d.ts +2 -2
- package/dist/portfolio/sections/ResultsSection.js +3 -1
- package/dist/portfolio/sections/ResultsSection.js.map +1 -1
- package/dist/portfolio/sections/ServicesSection.d.cts +1 -1
- package/dist/portfolio/sections/ServicesSection.d.ts +1 -1
- package/dist/portfolio/sections/SiteArchitectureSection.cjs +1 -1
- package/dist/portfolio/sections/SiteArchitectureSection.cjs.map +1 -1
- package/dist/portfolio/sections/SiteArchitectureSection.js +1 -1
- package/dist/portfolio/sections/SiteArchitectureSection.js.map +1 -1
- package/dist/portfolio/sections/SpeedComparisonSection.cjs +1 -1
- package/dist/portfolio/sections/SpeedComparisonSection.cjs.map +1 -1
- package/dist/portfolio/sections/SpeedComparisonSection.js +1 -1
- package/dist/portfolio/sections/SpeedComparisonSection.js.map +1 -1
- package/dist/portfolio/sections/TechStackSection.d.cts +1 -1
- package/dist/portfolio/sections/TechStackSection.d.ts +1 -1
- package/dist/portfolio/sections/TestimonialSection.d.cts +1 -1
- package/dist/portfolio/sections/TestimonialSection.d.ts +1 -1
- package/dist/portfolio/server.cjs +40 -14
- package/dist/portfolio/server.d.cts +3 -1
- package/dist/portfolio/server.d.ts +3 -1
- package/dist/portfolio/server.js +3 -1
- package/dist/{types-0tVPy4pU.d.cts → types-He5A9okM.d.cts} +1 -1
- package/dist/{types-0tVPy4pU.d.ts → types-He5A9okM.d.ts} +1 -1
- package/package.json +3 -3
- package/dist/chunk-4N7ENJ7L.cjs.map +0 -1
- package/dist/chunk-L7E3UHQC.cjs.map +0 -1
- package/dist/chunk-NIZJLONK.js.map +0 -1
- package/dist/chunk-UNTOEWQL.js.map +0 -1
|
@@ -136,24 +136,6 @@ function ScoreGauge({
|
|
|
136
136
|
)
|
|
137
137
|
] });
|
|
138
138
|
}
|
|
139
|
-
var SCORE_EXPLANATIONS = {
|
|
140
|
-
performance: {
|
|
141
|
-
why: "Faster sites convert more visitors into customers. A 1-second delay in load time reduces conversions by up to 7%. Google uses page speed as a direct ranking factor.",
|
|
142
|
-
competitors: "Wix and GoDaddy sites typically score 30\u201350 due to bloated JavaScript bundles and shared hosting. Template builders prioritize ease-of-use over speed."
|
|
143
|
-
},
|
|
144
|
-
seo: {
|
|
145
|
-
why: "Technical SEO determines whether search engines can properly crawl, index, and rank your pages. Higher scores mean more organic visibility and free traffic.",
|
|
146
|
-
competitors: "DIY builders often miss critical SEO fundamentals\u2014proper meta tags, structured data, canonical URLs, and server-side rendering that search engines need."
|
|
147
|
-
},
|
|
148
|
-
accessibility: {
|
|
149
|
-
why: "Accessible sites reach more customers, including the 1 in 4 adults with disabilities. It also reduces legal risk and improves usability for everyone.",
|
|
150
|
-
competitors: "Template platforms generate markup that frequently fails WCAG standards\u2014missing alt text, poor contrast ratios, and keyboard navigation issues."
|
|
151
|
-
},
|
|
152
|
-
bestPractices: {
|
|
153
|
-
why: "Best practices cover security (HTTPS, safe APIs), modern code standards, and browser compatibility. They protect your business and your visitors.",
|
|
154
|
-
competitors: "Older platforms rely on outdated libraries, insecure dependencies, and legacy patterns that create vulnerabilities and degrade user trust."
|
|
155
|
-
}
|
|
156
|
-
};
|
|
157
139
|
function PerformanceSection({ data }) {
|
|
158
140
|
const hasBeforeAfter = data.before && data.after;
|
|
159
141
|
const scores = data.after || data.before;
|
|
@@ -181,7 +163,7 @@ function PerformanceSection({ data }) {
|
|
|
181
163
|
{
|
|
182
164
|
className: "text-lg mb-12 max-w-2xl",
|
|
183
165
|
style: { color: "var(--sk-text-secondary, #a1a1aa)" },
|
|
184
|
-
children: hasBeforeAfter ? "Real Lighthouse scores
|
|
166
|
+
children: hasBeforeAfter ? "Real Lighthouse scores, before and after our rebuild." : "Real performance metrics measured by Google Lighthouse."
|
|
185
167
|
}
|
|
186
168
|
)
|
|
187
169
|
] }),
|
|
@@ -200,51 +182,6 @@ function PerformanceSection({ data }) {
|
|
|
200
182
|
cat.key
|
|
201
183
|
);
|
|
202
184
|
}) }) }),
|
|
203
|
-
scores && /* @__PURE__ */ jsx(ScrollReveal, { y: 30, delay: 0.2, children: /* @__PURE__ */ jsx("div", { className: "grid md:grid-cols-2 gap-4 mb-12", children: CATEGORIES.map((cat) => {
|
|
204
|
-
const currentScore = scores[cat.key];
|
|
205
|
-
if (currentScore === void 0) return null;
|
|
206
|
-
const explanation = SCORE_EXPLANATIONS[cat.key];
|
|
207
|
-
if (!explanation) return null;
|
|
208
|
-
return /* @__PURE__ */ jsx(GlassCard, { padding: "md", hover: false, children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
|
|
209
|
-
/* @__PURE__ */ jsx(
|
|
210
|
-
"div",
|
|
211
|
-
{
|
|
212
|
-
className: "w-10 h-10 rounded-lg flex items-center justify-center shrink-0 text-sm font-bold",
|
|
213
|
-
style: {
|
|
214
|
-
background: `color-mix(in srgb, ${getScoreColor(currentScore)} 15%, transparent)`,
|
|
215
|
-
color: getScoreColor(currentScore)
|
|
216
|
-
},
|
|
217
|
-
children: currentScore
|
|
218
|
-
}
|
|
219
|
-
),
|
|
220
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
221
|
-
/* @__PURE__ */ jsx(
|
|
222
|
-
"h4",
|
|
223
|
-
{
|
|
224
|
-
className: "text-sm font-semibold mb-1",
|
|
225
|
-
style: { color: "var(--sk-text-primary, #ffffff)" },
|
|
226
|
-
children: cat.label
|
|
227
|
-
}
|
|
228
|
-
),
|
|
229
|
-
/* @__PURE__ */ jsx(
|
|
230
|
-
"p",
|
|
231
|
-
{
|
|
232
|
-
className: "text-xs leading-relaxed mb-2",
|
|
233
|
-
style: { color: "var(--sk-text-secondary, #a1a1aa)" },
|
|
234
|
-
children: explanation.why
|
|
235
|
-
}
|
|
236
|
-
),
|
|
237
|
-
/* @__PURE__ */ jsx(
|
|
238
|
-
"p",
|
|
239
|
-
{
|
|
240
|
-
className: "text-xs leading-relaxed",
|
|
241
|
-
style: { color: "var(--sk-text-tertiary, #71717a)" },
|
|
242
|
-
children: explanation.competitors
|
|
243
|
-
}
|
|
244
|
-
)
|
|
245
|
-
] })
|
|
246
|
-
] }) }, cat.key);
|
|
247
|
-
}) }) }),
|
|
248
185
|
hasBeforeAfter && /* @__PURE__ */ jsx(ScrollReveal, { y: 30, delay: 0.3, children: /* @__PURE__ */ jsxs("div", { className: "grid md:grid-cols-2 gap-4", children: [
|
|
249
186
|
/* @__PURE__ */ jsxs(GlassCard, { padding: "lg", hover: false, children: [
|
|
250
187
|
/* @__PURE__ */ jsx(
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/portfolio/components/sections/PerformanceSection.tsx"],"sourcesContent":["'use client';\n\nimport React, { useRef, useEffect, useState } from 'react';\nimport ScrollReveal from '@sonordev/agency-site-kit/portfolio/primitives/ScrollReveal';\nimport GlassCard from '@sonordev/agency-site-kit/portfolio/primitives/GlassCard';\n\ninterface LighthouseScores {\n performance?: number;\n seo?: number;\n accessibility?: number;\n bestPractices?: number;\n}\n\ninterface PerformanceSectionProps {\n data: {\n before?: LighthouseScores;\n after?: LighthouseScores;\n cwv?: {\n lcp?: number;\n cls?: number;\n inp?: number;\n };\n };\n}\n\nconst CATEGORIES = [\n { key: 'performance', label: 'Performance', color: '#10b981' },\n { key: 'seo', label: 'SEO', color: '#6366f1' },\n { key: 'accessibility', label: 'Accessibility', color: '#f59e0b' },\n { key: 'bestPractices', label: 'Best Practices', color: '#3b82f6' },\n] as const;\n\nfunction getScoreColor(score: number): string {\n if (score >= 90) return '#10b981'; // green\n if (score >= 50) return '#f59e0b'; // amber\n return '#ef4444'; // red\n}\n\nconst EASE = 'cubic-bezier(0.25, 0.46, 0.45, 0.94)'; // ≈ power2.out\nconst RADIUS = 44;\nconst CIRCUMFERENCE = 2 * Math.PI * RADIUS;\n\nfunction ScoreGauge({\n score,\n label,\n previousScore,\n delay = 0,\n}: {\n score: number;\n label: string;\n previousScore?: number;\n delay?: number;\n}) {\n const circleRef = useRef<SVGCircleElement>(null);\n // SSR renders the real final score + arc (crawlers and no-JS users see the\n // true number); the count-up only runs for gauges below the viewport.\n const [displayScore, setDisplayScore] = useState(score);\n\n useEffect(() => {\n const circle = circleRef.current;\n if (!circle) return;\n\n if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {\n setDisplayScore(score);\n return;\n }\n\n // Never re-hide a gauge already in the viewport at hydration.\n const rect = circle.getBoundingClientRect();\n if (rect.top < window.innerHeight && rect.bottom > 0) {\n setDisplayScore(score);\n return;\n }\n\n // Hidden state: arc fully retracted, score zeroed.\n const finalOffset = CIRCUMFERENCE - (score / 100) * CIRCUMFERENCE;\n circle.style.strokeDashoffset = String(CIRCUMFERENCE);\n setDisplayScore(0);\n\n let raf = 0;\n const easeOut = (t: number) => 1 - Math.pow(1 - t, 2); // ≈ power2.out\n\n const io = new IntersectionObserver(\n ([entry]) => {\n if (!entry.isIntersecting) return;\n io.disconnect();\n // Draw the arc in, and count the score up in sync with the draw.\n circle.style.transition = `stroke-dashoffset 1.5s ${EASE} ${delay}s`;\n circle.style.strokeDashoffset = String(finalOffset);\n const start = performance.now();\n const tick = (now: number) => {\n const p = Math.min(1, Math.max(0, (now - start - delay * 1000) / 1500));\n setDisplayScore(Math.round(easeOut(p) * score));\n if (p < 1) raf = requestAnimationFrame(tick);\n };\n raf = requestAnimationFrame(tick);\n },\n { rootMargin: '0px 0px -15% 0px' }, // ≈ the old 'top 85%' trigger\n );\n io.observe(circle);\n\n return () => {\n io.disconnect();\n cancelAnimationFrame(raf);\n circle.style.transition = '';\n circle.style.strokeDashoffset = '';\n };\n }, [score, delay]);\n\n const color = getScoreColor(score);\n const improvement = previousScore ? score - previousScore : undefined;\n\n return (\n <div className=\"flex flex-col items-center gap-3\">\n <div className=\"relative w-28 h-28\">\n <svg className=\"w-full h-full -rotate-90\" viewBox=\"0 0 100 100\">\n {/* Background circle */}\n <circle\n cx=\"50\"\n cy=\"50\"\n r=\"44\"\n fill=\"none\"\n stroke=\"var(--sk-surface, rgba(255,255,255,0.05))\"\n strokeWidth=\"6\"\n />\n {/* Score arc — rendered at its final sweep; the effect retracts and\n redraws it only when the gauge scrolls in */}\n <circle\n ref={circleRef}\n cx=\"50\"\n cy=\"50\"\n r={RADIUS}\n fill=\"none\"\n stroke={color}\n strokeWidth=\"6\"\n strokeLinecap=\"round\"\n strokeDasharray={CIRCUMFERENCE}\n strokeDashoffset={CIRCUMFERENCE - (score / 100) * CIRCUMFERENCE}\n />\n </svg>\n {/* Score text */}\n <div className=\"absolute inset-0 flex items-center justify-center\">\n <span\n className=\"text-2xl font-bold\"\n style={{ color }}\n >\n {displayScore}\n </span>\n </div>\n </div>\n <span\n className=\"text-sm font-medium text-center\"\n style={{ color: 'var(--sk-text-primary, #ffffff)' }}\n >\n {label}\n </span>\n {improvement !== undefined && improvement > 0 && (\n <span\n className=\"text-xs font-semibold px-2 py-0.5 rounded-full\"\n style={{\n background: 'color-mix(in srgb, #10b981 15%, transparent)',\n color: '#10b981',\n }}\n >\n +{improvement} pts\n </span>\n )}\n </div>\n );\n}\n\n/** Explains why each Lighthouse category matters for the business */\nconst SCORE_EXPLANATIONS: Record<string, { why: string; competitors: string }> = {\n performance: {\n why: 'Faster sites convert more visitors into customers. A 1-second delay in load time reduces conversions by up to 7%. Google uses page speed as a direct ranking factor.',\n competitors: 'Wix and GoDaddy sites typically score 30\\u201350 due to bloated JavaScript bundles and shared hosting. Template builders prioritize ease-of-use over speed.',\n },\n seo: {\n why: 'Technical SEO determines whether search engines can properly crawl, index, and rank your pages. Higher scores mean more organic visibility and free traffic.',\n competitors: 'DIY builders often miss critical SEO fundamentals\\u2014proper meta tags, structured data, canonical URLs, and server-side rendering that search engines need.',\n },\n accessibility: {\n why: 'Accessible sites reach more customers, including the 1 in 4 adults with disabilities. It also reduces legal risk and improves usability for everyone.',\n competitors: 'Template platforms generate markup that frequently fails WCAG standards\\u2014missing alt text, poor contrast ratios, and keyboard navigation issues.',\n },\n bestPractices: {\n why: 'Best practices cover security (HTTPS, safe APIs), modern code standards, and browser compatibility. They protect your business and your visitors.',\n competitors: 'Older platforms rely on outdated libraries, insecure dependencies, and legacy patterns that create vulnerabilities and degrade user trust.',\n },\n};\n\nexport default function PerformanceSection({ data }: PerformanceSectionProps) {\n const hasBeforeAfter = data.before && data.after;\n const scores = data.after || data.before;\n if (!scores && !data.cwv) return null;\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-5xl mx-auto px-6\">\n <ScrollReveal y={30}>\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 Performance\n </h2>\n <p\n className=\"text-lg mb-12 max-w-2xl\"\n style={{ color: 'var(--sk-text-secondary, #a1a1aa)' }}\n >\n {hasBeforeAfter\n ? 'Real Lighthouse scores — before and after our rebuild.'\n : 'Real performance metrics measured by Google Lighthouse.'}\n </p>\n </ScrollReveal>\n\n {/* Score gauges */}\n {scores && (\n <ScrollReveal y={30}>\n <div className=\"grid grid-cols-2 md:grid-cols-4 gap-8 mb-8\">\n {CATEGORIES.map((cat, i) => {\n const currentScore = (scores as any)[cat.key];\n const previousScore = hasBeforeAfter ? (data.before as any)?.[cat.key] : undefined;\n if (currentScore === undefined) return null;\n return (\n <ScoreGauge\n key={cat.key}\n score={currentScore}\n label={cat.label}\n previousScore={previousScore}\n delay={i * 0.15}\n />\n );\n })}\n </div>\n </ScrollReveal>\n )}\n\n {/* Why these scores matter — explanation cards */}\n {scores && (\n <ScrollReveal y={30} delay={0.2}>\n <div className=\"grid md:grid-cols-2 gap-4 mb-12\">\n {CATEGORIES.map((cat) => {\n const currentScore = (scores as any)[cat.key];\n if (currentScore === undefined) return null;\n const explanation = SCORE_EXPLANATIONS[cat.key];\n if (!explanation) return null;\n return (\n <GlassCard key={cat.key} padding=\"md\" hover={false}>\n <div className=\"flex items-start gap-3\">\n <div\n className=\"w-10 h-10 rounded-lg flex items-center justify-center shrink-0 text-sm font-bold\"\n style={{\n background: `color-mix(in srgb, ${getScoreColor(currentScore)} 15%, transparent)`,\n color: getScoreColor(currentScore),\n }}\n >\n {currentScore}\n </div>\n <div>\n <h4\n className=\"text-sm font-semibold mb-1\"\n style={{ color: 'var(--sk-text-primary, #ffffff)' }}\n >\n {cat.label}\n </h4>\n <p\n className=\"text-xs leading-relaxed mb-2\"\n style={{ color: 'var(--sk-text-secondary, #a1a1aa)' }}\n >\n {explanation.why}\n </p>\n <p\n className=\"text-xs leading-relaxed\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n >\n {explanation.competitors}\n </p>\n </div>\n </div>\n </GlassCard>\n );\n })}\n </div>\n </ScrollReveal>\n )}\n\n {/* Before/After comparison cards */}\n {hasBeforeAfter && (\n <ScrollReveal y={30} delay={0.3}>\n <div className=\"grid md:grid-cols-2 gap-4\">\n <GlassCard padding=\"lg\" hover={false}>\n <h3\n className=\"text-sm font-semibold uppercase tracking-wider mb-4\"\n style={{ color: '#ef4444' }}\n >\n Before (Original Site)\n </h3>\n <div className=\"grid grid-cols-2 gap-3\">\n {CATEGORIES.map((cat) => {\n const val = (data.before as any)?.[cat.key];\n if (val === undefined) return null;\n return (\n <div key={cat.key} className=\"text-center\">\n <span className=\"text-2xl font-bold\" style={{ color: getScoreColor(val) }}>\n {val}\n </span>\n <span\n className=\"block text-[11px] mt-1\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n >\n {cat.label}\n </span>\n </div>\n );\n })}\n </div>\n </GlassCard>\n <GlassCard padding=\"lg\" hover={false}>\n <h3\n className=\"text-sm font-semibold uppercase tracking-wider mb-4\"\n style={{ color: '#10b981' }}\n >\n After (Next.js Rebuild)\n </h3>\n <div className=\"grid grid-cols-2 gap-3\">\n {CATEGORIES.map((cat) => {\n const val = (data.after as any)?.[cat.key];\n if (val === undefined) return null;\n return (\n <div key={cat.key} className=\"text-center\">\n <span className=\"text-2xl font-bold\" style={{ color: getScoreColor(val) }}>\n {val}\n </span>\n <span\n className=\"block text-[11px] mt-1\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n >\n {cat.label}\n </span>\n </div>\n );\n })}\n </div>\n </GlassCard>\n </div>\n </ScrollReveal>\n )}\n\n {/* Core Web Vitals */}\n {data.cwv && (data.cwv.lcp != null || data.cwv.cls != null || data.cwv.inp != null) && (\n <ScrollReveal y={30} delay={0.4}>\n <div className=\"grid grid-cols-3 gap-4 mt-8\">\n {data.cwv.lcp != null && (\n <GlassCard padding=\"md\" hover={false} className=\"text-center\">\n <span\n className=\"text-2xl font-bold\"\n style={{ color: data.cwv.lcp < 2500 ? '#10b981' : data.cwv.lcp < 4000 ? '#f59e0b' : '#ef4444' }}\n >\n {(data.cwv.lcp / 1000).toFixed(1)}s\n </span>\n <span\n className=\"block text-xs mt-1\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n >\n LCP\n </span>\n </GlassCard>\n )}\n {data.cwv.cls != null && (\n <GlassCard padding=\"md\" hover={false} className=\"text-center\">\n <span\n className=\"text-2xl font-bold\"\n style={{ color: data.cwv.cls < 0.1 ? '#10b981' : data.cwv.cls < 0.25 ? '#f59e0b' : '#ef4444' }}\n >\n {data.cwv.cls.toFixed(3)}\n </span>\n <span\n className=\"block text-xs mt-1\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n >\n CLS\n </span>\n </GlassCard>\n )}\n {data.cwv.inp != null && (\n <GlassCard padding=\"md\" hover={false} className=\"text-center\">\n <span\n className=\"text-2xl font-bold\"\n style={{ color: data.cwv.inp < 200 ? '#10b981' : data.cwv.inp < 500 ? '#f59e0b' : '#ef4444' }}\n >\n {data.cwv.inp}ms\n </span>\n <span\n className=\"block text-xs mt-1\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n >\n INP\n </span>\n </GlassCard>\n )}\n </div>\n </ScrollReveal>\n )}\n </div>\n </section>\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/portfolio/components/sections/PerformanceSection.tsx"],"sourcesContent":["'use client';\n\nimport React, { useRef, useEffect, useState } from 'react';\nimport ScrollReveal from '@sonordev/agency-site-kit/portfolio/primitives/ScrollReveal';\nimport GlassCard from '@sonordev/agency-site-kit/portfolio/primitives/GlassCard';\n\ninterface LighthouseScores {\n performance?: number;\n seo?: number;\n accessibility?: number;\n bestPractices?: number;\n}\n\ninterface PerformanceSectionProps {\n data: {\n before?: LighthouseScores;\n after?: LighthouseScores;\n cwv?: {\n lcp?: number;\n cls?: number;\n inp?: number;\n };\n };\n}\n\nconst CATEGORIES = [\n { key: 'performance', label: 'Performance', color: '#10b981' },\n { key: 'seo', label: 'SEO', color: '#6366f1' },\n { key: 'accessibility', label: 'Accessibility', color: '#f59e0b' },\n { key: 'bestPractices', label: 'Best Practices', color: '#3b82f6' },\n] as const;\n\nfunction getScoreColor(score: number): string {\n if (score >= 90) return '#10b981'; // green\n if (score >= 50) return '#f59e0b'; // amber\n return '#ef4444'; // red\n}\n\nconst EASE = 'cubic-bezier(0.25, 0.46, 0.45, 0.94)'; // ≈ power2.out\nconst RADIUS = 44;\nconst CIRCUMFERENCE = 2 * Math.PI * RADIUS;\n\nfunction ScoreGauge({\n score,\n label,\n previousScore,\n delay = 0,\n}: {\n score: number;\n label: string;\n previousScore?: number;\n delay?: number;\n}) {\n const circleRef = useRef<SVGCircleElement>(null);\n // SSR renders the real final score + arc (crawlers and no-JS users see the\n // true number); the count-up only runs for gauges below the viewport.\n const [displayScore, setDisplayScore] = useState(score);\n\n useEffect(() => {\n const circle = circleRef.current;\n if (!circle) return;\n\n if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {\n setDisplayScore(score);\n return;\n }\n\n // Never re-hide a gauge already in the viewport at hydration.\n const rect = circle.getBoundingClientRect();\n if (rect.top < window.innerHeight && rect.bottom > 0) {\n setDisplayScore(score);\n return;\n }\n\n // Hidden state: arc fully retracted, score zeroed.\n const finalOffset = CIRCUMFERENCE - (score / 100) * CIRCUMFERENCE;\n circle.style.strokeDashoffset = String(CIRCUMFERENCE);\n setDisplayScore(0);\n\n let raf = 0;\n const easeOut = (t: number) => 1 - Math.pow(1 - t, 2); // ≈ power2.out\n\n const io = new IntersectionObserver(\n ([entry]) => {\n if (!entry.isIntersecting) return;\n io.disconnect();\n // Draw the arc in, and count the score up in sync with the draw.\n circle.style.transition = `stroke-dashoffset 1.5s ${EASE} ${delay}s`;\n circle.style.strokeDashoffset = String(finalOffset);\n const start = performance.now();\n const tick = (now: number) => {\n const p = Math.min(1, Math.max(0, (now - start - delay * 1000) / 1500));\n setDisplayScore(Math.round(easeOut(p) * score));\n if (p < 1) raf = requestAnimationFrame(tick);\n };\n raf = requestAnimationFrame(tick);\n },\n { rootMargin: '0px 0px -15% 0px' }, // ≈ the old 'top 85%' trigger\n );\n io.observe(circle);\n\n return () => {\n io.disconnect();\n cancelAnimationFrame(raf);\n circle.style.transition = '';\n circle.style.strokeDashoffset = '';\n };\n }, [score, delay]);\n\n const color = getScoreColor(score);\n const improvement = previousScore ? score - previousScore : undefined;\n\n return (\n <div className=\"flex flex-col items-center gap-3\">\n <div className=\"relative w-28 h-28\">\n <svg className=\"w-full h-full -rotate-90\" viewBox=\"0 0 100 100\">\n {/* Background circle */}\n <circle\n cx=\"50\"\n cy=\"50\"\n r=\"44\"\n fill=\"none\"\n stroke=\"var(--sk-surface, rgba(255,255,255,0.05))\"\n strokeWidth=\"6\"\n />\n {/* Score arc — rendered at its final sweep; the effect retracts and\n redraws it only when the gauge scrolls in */}\n <circle\n ref={circleRef}\n cx=\"50\"\n cy=\"50\"\n r={RADIUS}\n fill=\"none\"\n stroke={color}\n strokeWidth=\"6\"\n strokeLinecap=\"round\"\n strokeDasharray={CIRCUMFERENCE}\n strokeDashoffset={CIRCUMFERENCE - (score / 100) * CIRCUMFERENCE}\n />\n </svg>\n {/* Score text */}\n <div className=\"absolute inset-0 flex items-center justify-center\">\n <span\n className=\"text-2xl font-bold\"\n style={{ color }}\n >\n {displayScore}\n </span>\n </div>\n </div>\n <span\n className=\"text-sm font-medium text-center\"\n style={{ color: 'var(--sk-text-primary, #ffffff)' }}\n >\n {label}\n </span>\n {improvement !== undefined && improvement > 0 && (\n <span\n className=\"text-xs font-semibold px-2 py-0.5 rounded-full\"\n style={{\n background: 'color-mix(in srgb, #10b981 15%, transparent)',\n color: '#10b981',\n }}\n >\n +{improvement} pts\n </span>\n )}\n </div>\n );\n}\n\nexport default function PerformanceSection({ data }: PerformanceSectionProps) {\n const hasBeforeAfter = data.before && data.after;\n const scores = data.after || data.before;\n if (!scores && !data.cwv) return null;\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-5xl mx-auto px-6\">\n <ScrollReveal y={30}>\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 Performance\n </h2>\n <p\n className=\"text-lg mb-12 max-w-2xl\"\n style={{ color: 'var(--sk-text-secondary, #a1a1aa)' }}\n >\n {hasBeforeAfter\n ? 'Real Lighthouse scores, before and after our rebuild.'\n : 'Real performance metrics measured by Google Lighthouse.'}\n </p>\n </ScrollReveal>\n\n {/* Score gauges */}\n {scores && (\n <ScrollReveal y={30}>\n <div className=\"grid grid-cols-2 md:grid-cols-4 gap-8 mb-8\">\n {CATEGORIES.map((cat, i) => {\n const currentScore = (scores as any)[cat.key];\n const previousScore = hasBeforeAfter ? (data.before as any)?.[cat.key] : undefined;\n if (currentScore === undefined) return null;\n return (\n <ScoreGauge\n key={cat.key}\n score={currentScore}\n label={cat.label}\n previousScore={previousScore}\n delay={i * 0.15}\n />\n );\n })}\n </div>\n </ScrollReveal>\n )}\n\n {/* Before/After comparison cards */}\n {hasBeforeAfter && (\n <ScrollReveal y={30} delay={0.3}>\n <div className=\"grid md:grid-cols-2 gap-4\">\n <GlassCard padding=\"lg\" hover={false}>\n <h3\n className=\"text-sm font-semibold uppercase tracking-wider mb-4\"\n style={{ color: '#ef4444' }}\n >\n Before (Original Site)\n </h3>\n <div className=\"grid grid-cols-2 gap-3\">\n {CATEGORIES.map((cat) => {\n const val = (data.before as any)?.[cat.key];\n if (val === undefined) return null;\n return (\n <div key={cat.key} className=\"text-center\">\n <span className=\"text-2xl font-bold\" style={{ color: getScoreColor(val) }}>\n {val}\n </span>\n <span\n className=\"block text-[11px] mt-1\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n >\n {cat.label}\n </span>\n </div>\n );\n })}\n </div>\n </GlassCard>\n <GlassCard padding=\"lg\" hover={false}>\n <h3\n className=\"text-sm font-semibold uppercase tracking-wider mb-4\"\n style={{ color: '#10b981' }}\n >\n After (Next.js Rebuild)\n </h3>\n <div className=\"grid grid-cols-2 gap-3\">\n {CATEGORIES.map((cat) => {\n const val = (data.after as any)?.[cat.key];\n if (val === undefined) return null;\n return (\n <div key={cat.key} className=\"text-center\">\n <span className=\"text-2xl font-bold\" style={{ color: getScoreColor(val) }}>\n {val}\n </span>\n <span\n className=\"block text-[11px] mt-1\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n >\n {cat.label}\n </span>\n </div>\n );\n })}\n </div>\n </GlassCard>\n </div>\n </ScrollReveal>\n )}\n\n {/* Core Web Vitals */}\n {data.cwv && (data.cwv.lcp != null || data.cwv.cls != null || data.cwv.inp != null) && (\n <ScrollReveal y={30} delay={0.4}>\n <div className=\"grid grid-cols-3 gap-4 mt-8\">\n {data.cwv.lcp != null && (\n <GlassCard padding=\"md\" hover={false} className=\"text-center\">\n <span\n className=\"text-2xl font-bold\"\n style={{ color: data.cwv.lcp < 2500 ? '#10b981' : data.cwv.lcp < 4000 ? '#f59e0b' : '#ef4444' }}\n >\n {(data.cwv.lcp / 1000).toFixed(1)}s\n </span>\n <span\n className=\"block text-xs mt-1\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n >\n LCP\n </span>\n </GlassCard>\n )}\n {data.cwv.cls != null && (\n <GlassCard padding=\"md\" hover={false} className=\"text-center\">\n <span\n className=\"text-2xl font-bold\"\n style={{ color: data.cwv.cls < 0.1 ? '#10b981' : data.cwv.cls < 0.25 ? '#f59e0b' : '#ef4444' }}\n >\n {data.cwv.cls.toFixed(3)}\n </span>\n <span\n className=\"block text-xs mt-1\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n >\n CLS\n </span>\n </GlassCard>\n )}\n {data.cwv.inp != null && (\n <GlassCard padding=\"md\" hover={false} className=\"text-center\">\n <span\n className=\"text-2xl font-bold\"\n style={{ color: data.cwv.inp < 200 ? '#10b981' : data.cwv.inp < 500 ? '#f59e0b' : '#ef4444' }}\n >\n {data.cwv.inp}ms\n </span>\n <span\n className=\"block text-xs mt-1\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n >\n INP\n </span>\n </GlassCard>\n )}\n </div>\n </ScrollReveal>\n )}\n </div>\n </section>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunk6OWFFV6N_cjs = require('../../chunk-6OWFFV6N.cjs');
|
|
4
|
+
var chunkX76U7VLF_cjs = require('../../chunk-X76U7VLF.cjs');
|
|
4
5
|
var ScrollReveal = require('@sonordev/agency-site-kit/portfolio/primitives/ScrollReveal');
|
|
5
6
|
var AnimatedCounter = require('@sonordev/agency-site-kit/portfolio/primitives/AnimatedCounter');
|
|
6
7
|
var GlassCard = require('@sonordev/agency-site-kit/portfolio/primitives/GlassCard');
|
|
@@ -17,6 +18,7 @@ function ResultsSection({
|
|
|
17
18
|
metricsDelta,
|
|
18
19
|
metricsRefreshedAt
|
|
19
20
|
}) {
|
|
21
|
+
if (!data.items.length && !chunkX76U7VLF_cjs.gateMetricsDeltas(metricsDelta).length) return null;
|
|
20
22
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
21
23
|
"section",
|
|
22
24
|
{
|
|
@@ -96,7 +98,7 @@ function ResultsSection({
|
|
|
96
98
|
}
|
|
97
99
|
)
|
|
98
100
|
] }) }, index)) }) }),
|
|
99
|
-
metricsDelta && metricsDelta.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(ScrollReveal__default.default, { y: 30, delay: 0.1, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
101
|
+
metricsDelta && metricsDelta.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(ScrollReveal__default.default, { y: 30, delay: 0.1, children: /* @__PURE__ */ jsxRuntime.jsx(chunk6OWFFV6N_cjs.MetricsProvenance, { deltas: metricsDelta, refreshedAt: metricsRefreshedAt }) })
|
|
100
102
|
] })
|
|
101
103
|
}
|
|
102
104
|
);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/portfolio/components/sections/ResultsSection.tsx"],"names":["jsx","jsxs","ScrollReveal","GlassCard","AnimatedCounter","MetricsProvenance"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../../../src/portfolio/components/sections/ResultsSection.tsx"],"names":["gateMetricsDeltas","jsx","jsxs","ScrollReveal","GlassCard","AnimatedCounter","MetricsProvenance"],"mappings":"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","file":"ResultsSection.cjs","sourcesContent":["// Server component — renders on the server; the client primitives below cross the\n// RSC boundary via package subpaths (never import them relatively here).\nimport React from 'react';\nimport type { MetricsDelta, PortfolioResultsData } from '../../../types';\nimport ScrollReveal from '@sonordev/agency-site-kit/portfolio/primitives/ScrollReveal';\nimport AnimatedCounter from '@sonordev/agency-site-kit/portfolio/primitives/AnimatedCounter';\nimport GlassCard from '@sonordev/agency-site-kit/portfolio/primitives/GlassCard';\nimport MetricsProvenance from '../MetricsProvenance';\nimport { gateMetricsDeltas } from '../../curate-proof';\n\ninterface ResultsSectionProps {\n data: PortfolioResultsData;\n /** Live metric deltas computed by the platform (item.metricsDelta). */\n metricsDelta?: MetricsDelta[];\n /** ISO timestamp of the last live-metrics refresh. */\n metricsRefreshedAt?: string | null;\n}\n\nexport default function ResultsSection({\n data,\n metricsDelta,\n metricsRefreshedAt,\n}: ResultsSectionProps) {\n // Nothing survives display curation → no section, not an empty header.\n if (!data.items.length && !gateMetricsDeltas(metricsDelta).length) return null;\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-7xl mx-auto px-6\">\n <ScrollReveal y={30}>\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 Results\n </h2>\n <p\n className=\"text-lg mb-12 max-w-2xl\"\n style={{ color: 'var(--sk-text-secondary, #a1a1aa)' }}\n >\n Measurable outcomes that speak for themselves.\n </p>\n </ScrollReveal>\n\n <ScrollReveal stagger={0.1}>\n <div className=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6\">\n {data.items.map((item, index) => (\n <GlassCard key={index} padding=\"lg\" hover>\n <div className=\"flex flex-col gap-3\">\n {/* Metric counter */}\n {item.metric && (\n <div\n className=\"text-3xl md:text-4xl font-bold\"\n style={{ color: 'var(--sk-primary, #6366f1)' }}\n >\n <AnimatedCounter\n value={item.metric.value}\n suffix={item.metric.suffix}\n prefix={item.metric.prefix}\n duration={2.5}\n />\n </div>\n )}\n\n <h3\n className=\"text-lg font-semibold\"\n style={{ color: 'var(--sk-text-primary, #ffffff)' }}\n >\n {item.title}\n </h3>\n\n <p\n className=\"text-sm leading-relaxed\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n >\n {item.description}\n </p>\n\n {/* Source badge */}\n <span\n className=\"inline-flex self-start items-center gap-1 px-2.5 py-1 rounded-full text-[11px] uppercase tracking-wider font-medium mt-1\"\n style={{\n background:\n item.source === 'measured'\n ? 'color-mix(in srgb, #10b981 12%, transparent)'\n : 'color-mix(in srgb, #f59e0b 12%, transparent)',\n color: item.source === 'measured' ? '#10b981' : '#f59e0b',\n border: `1px solid ${\n item.source === 'measured'\n ? 'color-mix(in srgb, #10b981 20%, transparent)'\n : 'color-mix(in srgb, #f59e0b 20%, transparent)'\n }`,\n }}\n >\n {item.source === 'measured' ? (\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" fill=\"none\">\n <path d=\"M10 3L4.5 8.5 2 6\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n ) : (\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" fill=\"none\">\n <circle cx=\"6\" cy=\"6\" r=\"4\" stroke=\"currentColor\" strokeWidth=\"1.5\" />\n <path d=\"M6 4v2.5l1.5 1\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" />\n </svg>\n )}\n {item.source}\n </span>\n </div>\n </GlassCard>\n ))}\n </div>\n </ScrollReveal>\n\n {/* Live verified metrics — the numbers a static portfolio can't fake */}\n {metricsDelta && metricsDelta.length > 0 && (\n <ScrollReveal y={30} delay={0.1}>\n <MetricsProvenance deltas={metricsDelta} refreshedAt={metricsRefreshedAt} />\n </ScrollReveal>\n )}\n </div>\n </section>\n );\n}\n"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { f as PortfolioResultsData, M as MetricsDelta } from '../../types-
|
|
2
|
+
import { f as PortfolioResultsData, M as MetricsDelta } from '../../types-He5A9okM.cjs';
|
|
3
3
|
|
|
4
4
|
interface ResultsSectionProps {
|
|
5
5
|
data: PortfolioResultsData;
|
|
@@ -8,6 +8,6 @@ interface ResultsSectionProps {
|
|
|
8
8
|
/** ISO timestamp of the last live-metrics refresh. */
|
|
9
9
|
metricsRefreshedAt?: string | null;
|
|
10
10
|
}
|
|
11
|
-
declare function ResultsSection({ data, metricsDelta, metricsRefreshedAt, }: ResultsSectionProps): React.JSX.Element;
|
|
11
|
+
declare function ResultsSection({ data, metricsDelta, metricsRefreshedAt, }: ResultsSectionProps): React.JSX.Element | null;
|
|
12
12
|
|
|
13
13
|
export { ResultsSection as default };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { f as PortfolioResultsData, M as MetricsDelta } from '../../types-
|
|
2
|
+
import { f as PortfolioResultsData, M as MetricsDelta } from '../../types-He5A9okM.js';
|
|
3
3
|
|
|
4
4
|
interface ResultsSectionProps {
|
|
5
5
|
data: PortfolioResultsData;
|
|
@@ -8,6 +8,6 @@ interface ResultsSectionProps {
|
|
|
8
8
|
/** ISO timestamp of the last live-metrics refresh. */
|
|
9
9
|
metricsRefreshedAt?: string | null;
|
|
10
10
|
}
|
|
11
|
-
declare function ResultsSection({ data, metricsDelta, metricsRefreshedAt, }: ResultsSectionProps): React.JSX.Element;
|
|
11
|
+
declare function ResultsSection({ data, metricsDelta, metricsRefreshedAt, }: ResultsSectionProps): React.JSX.Element | null;
|
|
12
12
|
|
|
13
13
|
export { ResultsSection as default };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { MetricsProvenance } from '../../chunk-
|
|
1
|
+
import { MetricsProvenance } from '../../chunk-X7UCJ45A.js';
|
|
2
|
+
import { gateMetricsDeltas } from '../../chunk-FM25LB67.js';
|
|
2
3
|
import ScrollReveal from '@sonordev/agency-site-kit/portfolio/primitives/ScrollReveal';
|
|
3
4
|
import AnimatedCounter from '@sonordev/agency-site-kit/portfolio/primitives/AnimatedCounter';
|
|
4
5
|
import GlassCard from '@sonordev/agency-site-kit/portfolio/primitives/GlassCard';
|
|
@@ -9,6 +10,7 @@ function ResultsSection({
|
|
|
9
10
|
metricsDelta,
|
|
10
11
|
metricsRefreshedAt
|
|
11
12
|
}) {
|
|
13
|
+
if (!data.items.length && !gateMetricsDeltas(metricsDelta).length) return null;
|
|
12
14
|
return /* @__PURE__ */ jsx(
|
|
13
15
|
"section",
|
|
14
16
|
{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/portfolio/components/sections/ResultsSection.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../../../src/portfolio/components/sections/ResultsSection.tsx"],"names":[],"mappings":"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","file":"ResultsSection.js","sourcesContent":["// Server component — renders on the server; the client primitives below cross the\n// RSC boundary via package subpaths (never import them relatively here).\nimport React from 'react';\nimport type { MetricsDelta, PortfolioResultsData } from '../../../types';\nimport ScrollReveal from '@sonordev/agency-site-kit/portfolio/primitives/ScrollReveal';\nimport AnimatedCounter from '@sonordev/agency-site-kit/portfolio/primitives/AnimatedCounter';\nimport GlassCard from '@sonordev/agency-site-kit/portfolio/primitives/GlassCard';\nimport MetricsProvenance from '../MetricsProvenance';\nimport { gateMetricsDeltas } from '../../curate-proof';\n\ninterface ResultsSectionProps {\n data: PortfolioResultsData;\n /** Live metric deltas computed by the platform (item.metricsDelta). */\n metricsDelta?: MetricsDelta[];\n /** ISO timestamp of the last live-metrics refresh. */\n metricsRefreshedAt?: string | null;\n}\n\nexport default function ResultsSection({\n data,\n metricsDelta,\n metricsRefreshedAt,\n}: ResultsSectionProps) {\n // Nothing survives display curation → no section, not an empty header.\n if (!data.items.length && !gateMetricsDeltas(metricsDelta).length) return null;\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-7xl mx-auto px-6\">\n <ScrollReveal y={30}>\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 Results\n </h2>\n <p\n className=\"text-lg mb-12 max-w-2xl\"\n style={{ color: 'var(--sk-text-secondary, #a1a1aa)' }}\n >\n Measurable outcomes that speak for themselves.\n </p>\n </ScrollReveal>\n\n <ScrollReveal stagger={0.1}>\n <div className=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6\">\n {data.items.map((item, index) => (\n <GlassCard key={index} padding=\"lg\" hover>\n <div className=\"flex flex-col gap-3\">\n {/* Metric counter */}\n {item.metric && (\n <div\n className=\"text-3xl md:text-4xl font-bold\"\n style={{ color: 'var(--sk-primary, #6366f1)' }}\n >\n <AnimatedCounter\n value={item.metric.value}\n suffix={item.metric.suffix}\n prefix={item.metric.prefix}\n duration={2.5}\n />\n </div>\n )}\n\n <h3\n className=\"text-lg font-semibold\"\n style={{ color: 'var(--sk-text-primary, #ffffff)' }}\n >\n {item.title}\n </h3>\n\n <p\n className=\"text-sm leading-relaxed\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n >\n {item.description}\n </p>\n\n {/* Source badge */}\n <span\n className=\"inline-flex self-start items-center gap-1 px-2.5 py-1 rounded-full text-[11px] uppercase tracking-wider font-medium mt-1\"\n style={{\n background:\n item.source === 'measured'\n ? 'color-mix(in srgb, #10b981 12%, transparent)'\n : 'color-mix(in srgb, #f59e0b 12%, transparent)',\n color: item.source === 'measured' ? '#10b981' : '#f59e0b',\n border: `1px solid ${\n item.source === 'measured'\n ? 'color-mix(in srgb, #10b981 20%, transparent)'\n : 'color-mix(in srgb, #f59e0b 20%, transparent)'\n }`,\n }}\n >\n {item.source === 'measured' ? (\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" fill=\"none\">\n <path d=\"M10 3L4.5 8.5 2 6\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n ) : (\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" fill=\"none\">\n <circle cx=\"6\" cy=\"6\" r=\"4\" stroke=\"currentColor\" strokeWidth=\"1.5\" />\n <path d=\"M6 4v2.5l1.5 1\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" />\n </svg>\n )}\n {item.source}\n </span>\n </div>\n </GlassCard>\n ))}\n </div>\n </ScrollReveal>\n\n {/* Live verified metrics — the numbers a static portfolio can't fake */}\n {metricsDelta && metricsDelta.length > 0 && (\n <ScrollReveal y={30} delay={0.1}>\n <MetricsProvenance deltas={metricsDelta} refreshedAt={metricsRefreshedAt} />\n </ScrollReveal>\n )}\n </div>\n </section>\n );\n}\n"]}
|
|
@@ -220,7 +220,7 @@ function SiteArchitectureSection({ data }) {
|
|
|
220
220
|
{
|
|
221
221
|
className: "text-[10px] mt-1 block transition-colors",
|
|
222
222
|
style: { color: "var(--sk-text-tertiary, #71717a)" },
|
|
223
|
-
children: isExpanded ? "click to collapse" : `${group.count} ${group.count === 1 ? "page" : "pages"} \
|
|
223
|
+
children: isExpanded ? "click to collapse" : `${group.count} ${group.count === 1 ? "page" : "pages"} \xB7 click to expand`
|
|
224
224
|
}
|
|
225
225
|
)
|
|
226
226
|
]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/portfolio/components/sections/SiteArchitectureSection.tsx"],"sourcesContent":["'use client';\n\nimport React, { useRef, useEffect, useState } from 'react';\nimport ScrollReveal from '@sonordev/agency-site-kit/portfolio/primitives/ScrollReveal';\nimport GlassCard from '@sonordev/agency-site-kit/portfolio/primitives/GlassCard';\n\ninterface PageGroup {\n label: string;\n count: number;\n // path is optional: PortfolioPage strips raw client-site paths before they\n // reach this client component so they never enter the RSC flight payload\n // (Googlebot crawls path-like strings against the host site → 404 floods).\n pages: Array<{ path?: string; title: string }>;\n}\n\nexport interface SiteArchitectureData {\n totalPages: number;\n totalWords: number;\n groups: PageGroup[];\n}\n\ninterface SiteArchitectureSectionProps {\n data: SiteArchitectureData;\n}\n\n/** Generates a pleasing color from the brand palette for each branch */\nfunction getBranchColor(index: number): string {\n const hues = [\n 'var(--sk-primary, #6366f1)',\n '#10b981',\n '#f59e0b',\n '#8b5cf6',\n '#ec4899',\n '#06b6d4',\n '#f97316',\n '#14b8a6',\n ];\n return hues[index % hues.length];\n}\n\nconst EASE = 'cubic-bezier(0.25, 0.46, 0.45, 0.94)'; // ≈ power2.out\nconst EASE_BACK = 'cubic-bezier(0.34, 1.4, 0.64, 1)'; // ≈ back.out(1.4)\n\nexport default function SiteArchitectureSection({ data }: SiteArchitectureSectionProps) {\n const { totalPages, totalWords, groups } = data;\n const treeRef = useRef<HTMLDivElement>(null);\n const [expandedGroup, setExpandedGroup] = useState<number | null>(null);\n\n // Animate each branch node in on scroll\n useEffect(() => {\n const tree = treeRef.current;\n if (!tree) return;\n if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;\n\n // Never re-hide a tree already in the viewport at hydration.\n const rect = tree.getBoundingClientRect();\n if (rect.top < window.innerHeight && rect.bottom > 0) return;\n\n const nodes = Array.from(tree.querySelectorAll<HTMLElement>('.arch-node'));\n if (nodes.length === 0) return;\n\n // Hidden state — nodes pop in with a slight back-out overshoot on reveal.\n for (const node of nodes) {\n node.style.opacity = '0';\n node.style.transform = 'translateY(20px) scale(0.8)';\n node.style.willChange = 'opacity, transform';\n }\n\n const io = new IntersectionObserver(\n ([entry]) => {\n if (!entry.isIntersecting) return;\n io.disconnect();\n nodes.forEach((node, i) => {\n const d = i * 0.08;\n node.style.transition = `opacity 0.5s ${EASE} ${d}s, transform 0.5s ${EASE_BACK} ${d}s`;\n node.style.opacity = '1';\n node.style.transform = 'translateY(0px) scale(1)';\n });\n },\n { rootMargin: '0px 0px -30% 0px' }, // ≈ the old 'top 70%' trigger\n );\n io.observe(tree);\n\n return () => {\n io.disconnect();\n for (const node of nodes) {\n node.style.opacity = '';\n node.style.transform = '';\n node.style.transition = '';\n node.style.willChange = '';\n }\n };\n }, [groups.length]);\n\n const hasWords = totalWords > 0;\n const subtitle = hasWords\n ? `${totalPages} pages structured across ${groups.length} content ${groups.length === 1 ? 'category' : 'categories'} with ${totalWords.toLocaleString()} words of content.`\n : `${totalPages} pages structured across ${groups.length} content ${groups.length === 1 ? 'category' : 'categories'}.`;\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 <ScrollReveal y={30}>\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 Site Architecture\n </h2>\n <p\n className=\"text-lg mb-16 max-w-3xl\"\n style={{ color: 'var(--sk-text-secondary, #a1a1aa)' }}\n >\n {subtitle}\n </p>\n </ScrollReveal>\n\n {/* Visual Tree */}\n <div ref={treeRef} className=\"relative\">\n {/* Root node — the domain */}\n <div className=\"flex justify-center mb-8\">\n <div\n className=\"arch-node relative px-6 py-3 rounded-xl text-center\"\n style={{\n background: 'color-mix(in srgb, var(--sk-primary, #6366f1) 15%, var(--sk-bg, #0a0a0a))',\n border: '2px solid var(--sk-primary, #6366f1)',\n boxShadow: '0 0 30px color-mix(in srgb, var(--sk-primary, #6366f1) 20%, transparent)',\n }}\n >\n <span\n className=\"text-sm font-bold tracking-wide\"\n style={{ color: 'var(--sk-primary, #6366f1)' }}\n >\n /\n </span>\n <span\n className=\"block text-xs mt-0.5\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n >\n {totalPages} pages\n </span>\n </div>\n </div>\n\n {/* Branch nodes — each page group */}\n <div className=\"grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4\">\n {groups.map((group, gi) => {\n const color = getBranchColor(gi);\n const isExpanded = expandedGroup === gi;\n const hasChildren = group.pages.length > 0;\n\n return (\n <div\n key={gi}\n className=\"arch-node flex flex-col\"\n >\n {/* Branch connector line (vertical from root) */}\n <div\n className=\"w-px h-6 mx-auto mb-2\"\n style={{\n background: `linear-gradient(to bottom, color-mix(in srgb, ${color} 40%, transparent), ${color})`,\n }}\n />\n\n {/* Branch node */}\n <button\n onClick={() => hasChildren && setExpandedGroup(isExpanded ? null : gi)}\n className=\"relative rounded-xl p-4 text-left transition-all duration-300 cursor-pointer group\"\n style={{\n background: `color-mix(in srgb, ${color} 8%, var(--sk-bg, #0a0a0a))`,\n border: `1.5px solid color-mix(in srgb, ${color} ${isExpanded ? '40' : '20'}%, transparent)`,\n boxShadow: isExpanded\n ? `0 0 20px color-mix(in srgb, ${color} 15%, transparent)`\n : 'none',\n }}\n >\n {/* Count badge */}\n <div\n className=\"absolute -top-2.5 -right-2 px-2 py-0.5 rounded-full text-xs font-bold\"\n style={{\n background: color,\n color: '#000',\n boxShadow: `0 0 10px color-mix(in srgb, ${color} 40%, transparent)`,\n }}\n >\n {group.count}\n </div>\n\n <div className=\"flex items-center gap-2\">\n {/* Branch icon — folder-like indicator */}\n <div\n className=\"w-2 h-2 rounded-full shrink-0\"\n style={{ background: color }}\n />\n <span\n className=\"text-sm font-semibold truncate\"\n style={{ color: 'var(--sk-text-primary, #ffffff)' }}\n >\n {group.label}\n </span>\n </div>\n\n {/* Expand indicator */}\n {hasChildren && (\n <span\n className=\"text-[10px] mt-1 block transition-colors\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n >\n {isExpanded ? 'click to collapse' : `${group.count} ${group.count === 1 ? 'page' : 'pages'} — click to expand`}\n </span>\n )}\n </button>\n\n {/* Expanded page list */}\n {isExpanded && hasChildren && (\n <div\n className=\"mt-2 rounded-lg overflow-hidden\"\n style={{\n background: `color-mix(in srgb, ${color} 5%, var(--sk-bg, #0a0a0a))`,\n border: `1px solid color-mix(in srgb, ${color} 15%, transparent)`,\n }}\n >\n {group.pages.map((page, pi) => (\n <div\n key={pi}\n className=\"flex items-center gap-2 px-3 py-1.5\"\n style={{\n borderBottom:\n pi < group.pages.length - 1\n ? `1px solid color-mix(in srgb, ${color} 8%, transparent)`\n : 'none',\n }}\n >\n <div\n className=\"w-1 h-1 rounded-full shrink-0\"\n style={{ background: color, opacity: 0.5 }}\n />\n <span\n className=\"text-xs truncate\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n title={page.title || page.path}\n >\n {page.title || page.path}\n </span>\n </div>\n ))}\n {group.count > group.pages.length && (\n <div\n className=\"px-3 py-1.5 text-xs italic\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)', opacity: 0.6 }}\n >\n +{group.count - group.pages.length} more pages\n </div>\n )}\n </div>\n )}\n </div>\n );\n })}\n </div>\n </div>\n\n {/* Bottom stats bar */}\n <ScrollReveal y={20} delay={0.4}>\n <div\n className=\"mt-12 flex items-center justify-center gap-8 md:gap-12 py-4 px-6 rounded-xl mx-auto max-w-2xl\"\n style={{\n background: 'color-mix(in srgb, var(--sk-primary, #6366f1) 5%, var(--sk-bg, #0a0a0a))',\n border: '1px solid color-mix(in srgb, var(--sk-primary, #6366f1) 15%, transparent)',\n }}\n >\n <div className=\"text-center\">\n <span\n className=\"text-2xl font-bold block\"\n style={{ color: 'var(--sk-primary, #6366f1)' }}\n >\n {totalPages}\n </span>\n <span\n className=\"text-[10px] uppercase tracking-wider block mt-0.5\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n >\n Pages\n </span>\n </div>\n <div\n className=\"w-px h-8\"\n style={{ background: 'color-mix(in srgb, var(--sk-text-tertiary, #71717a) 20%, transparent)' }}\n />\n <div className=\"text-center\">\n <span\n className=\"text-2xl font-bold block\"\n style={{ color: 'var(--sk-primary, #6366f1)' }}\n >\n {groups.length}\n </span>\n <span\n className=\"text-[10px] uppercase tracking-wider block mt-0.5\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n >\n Categories\n </span>\n </div>\n {hasWords && (\n <>\n <div\n className=\"w-px h-8\"\n style={{ background: 'color-mix(in srgb, var(--sk-text-tertiary, #71717a) 20%, transparent)' }}\n />\n <div className=\"text-center\">\n <span\n className=\"text-2xl font-bold block\"\n style={{ color: 'var(--sk-primary, #6366f1)' }}\n >\n {totalWords > 1000 ? `${(totalWords / 1000).toFixed(0)}k` : totalWords}\n </span>\n <span\n className=\"text-[10px] uppercase tracking-wider block mt-0.5\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n >\n Words\n </span>\n </div>\n </>\n )}\n </div>\n </ScrollReveal>\n </div>\n </section>\n );\n}\n"],"mappings":"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","names":["ScrollReveal"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/portfolio/components/sections/SiteArchitectureSection.tsx"],"sourcesContent":["'use client';\n\nimport React, { useRef, useEffect, useState } from 'react';\nimport ScrollReveal from '@sonordev/agency-site-kit/portfolio/primitives/ScrollReveal';\nimport GlassCard from '@sonordev/agency-site-kit/portfolio/primitives/GlassCard';\n\ninterface PageGroup {\n label: string;\n count: number;\n // path is optional: PortfolioPage strips raw client-site paths before they\n // reach this client component so they never enter the RSC flight payload\n // (Googlebot crawls path-like strings against the host site → 404 floods).\n pages: Array<{ path?: string; title: string }>;\n}\n\nexport interface SiteArchitectureData {\n totalPages: number;\n totalWords: number;\n groups: PageGroup[];\n}\n\ninterface SiteArchitectureSectionProps {\n data: SiteArchitectureData;\n}\n\n/** Generates a pleasing color from the brand palette for each branch */\nfunction getBranchColor(index: number): string {\n const hues = [\n 'var(--sk-primary, #6366f1)',\n '#10b981',\n '#f59e0b',\n '#8b5cf6',\n '#ec4899',\n '#06b6d4',\n '#f97316',\n '#14b8a6',\n ];\n return hues[index % hues.length];\n}\n\nconst EASE = 'cubic-bezier(0.25, 0.46, 0.45, 0.94)'; // ≈ power2.out\nconst EASE_BACK = 'cubic-bezier(0.34, 1.4, 0.64, 1)'; // ≈ back.out(1.4)\n\nexport default function SiteArchitectureSection({ data }: SiteArchitectureSectionProps) {\n const { totalPages, totalWords, groups } = data;\n const treeRef = useRef<HTMLDivElement>(null);\n const [expandedGroup, setExpandedGroup] = useState<number | null>(null);\n\n // Animate each branch node in on scroll\n useEffect(() => {\n const tree = treeRef.current;\n if (!tree) return;\n if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;\n\n // Never re-hide a tree already in the viewport at hydration.\n const rect = tree.getBoundingClientRect();\n if (rect.top < window.innerHeight && rect.bottom > 0) return;\n\n const nodes = Array.from(tree.querySelectorAll<HTMLElement>('.arch-node'));\n if (nodes.length === 0) return;\n\n // Hidden state — nodes pop in with a slight back-out overshoot on reveal.\n for (const node of nodes) {\n node.style.opacity = '0';\n node.style.transform = 'translateY(20px) scale(0.8)';\n node.style.willChange = 'opacity, transform';\n }\n\n const io = new IntersectionObserver(\n ([entry]) => {\n if (!entry.isIntersecting) return;\n io.disconnect();\n nodes.forEach((node, i) => {\n const d = i * 0.08;\n node.style.transition = `opacity 0.5s ${EASE} ${d}s, transform 0.5s ${EASE_BACK} ${d}s`;\n node.style.opacity = '1';\n node.style.transform = 'translateY(0px) scale(1)';\n });\n },\n { rootMargin: '0px 0px -30% 0px' }, // ≈ the old 'top 70%' trigger\n );\n io.observe(tree);\n\n return () => {\n io.disconnect();\n for (const node of nodes) {\n node.style.opacity = '';\n node.style.transform = '';\n node.style.transition = '';\n node.style.willChange = '';\n }\n };\n }, [groups.length]);\n\n const hasWords = totalWords > 0;\n const subtitle = hasWords\n ? `${totalPages} pages structured across ${groups.length} content ${groups.length === 1 ? 'category' : 'categories'} with ${totalWords.toLocaleString()} words of content.`\n : `${totalPages} pages structured across ${groups.length} content ${groups.length === 1 ? 'category' : 'categories'}.`;\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 <ScrollReveal y={30}>\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 Site Architecture\n </h2>\n <p\n className=\"text-lg mb-16 max-w-3xl\"\n style={{ color: 'var(--sk-text-secondary, #a1a1aa)' }}\n >\n {subtitle}\n </p>\n </ScrollReveal>\n\n {/* Visual Tree */}\n <div ref={treeRef} className=\"relative\">\n {/* Root node — the domain */}\n <div className=\"flex justify-center mb-8\">\n <div\n className=\"arch-node relative px-6 py-3 rounded-xl text-center\"\n style={{\n background: 'color-mix(in srgb, var(--sk-primary, #6366f1) 15%, var(--sk-bg, #0a0a0a))',\n border: '2px solid var(--sk-primary, #6366f1)',\n boxShadow: '0 0 30px color-mix(in srgb, var(--sk-primary, #6366f1) 20%, transparent)',\n }}\n >\n <span\n className=\"text-sm font-bold tracking-wide\"\n style={{ color: 'var(--sk-primary, #6366f1)' }}\n >\n /\n </span>\n <span\n className=\"block text-xs mt-0.5\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n >\n {totalPages} pages\n </span>\n </div>\n </div>\n\n {/* Branch nodes — each page group */}\n <div className=\"grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4\">\n {groups.map((group, gi) => {\n const color = getBranchColor(gi);\n const isExpanded = expandedGroup === gi;\n const hasChildren = group.pages.length > 0;\n\n return (\n <div\n key={gi}\n className=\"arch-node flex flex-col\"\n >\n {/* Branch connector line (vertical from root) */}\n <div\n className=\"w-px h-6 mx-auto mb-2\"\n style={{\n background: `linear-gradient(to bottom, color-mix(in srgb, ${color} 40%, transparent), ${color})`,\n }}\n />\n\n {/* Branch node */}\n <button\n onClick={() => hasChildren && setExpandedGroup(isExpanded ? null : gi)}\n className=\"relative rounded-xl p-4 text-left transition-all duration-300 cursor-pointer group\"\n style={{\n background: `color-mix(in srgb, ${color} 8%, var(--sk-bg, #0a0a0a))`,\n border: `1.5px solid color-mix(in srgb, ${color} ${isExpanded ? '40' : '20'}%, transparent)`,\n boxShadow: isExpanded\n ? `0 0 20px color-mix(in srgb, ${color} 15%, transparent)`\n : 'none',\n }}\n >\n {/* Count badge */}\n <div\n className=\"absolute -top-2.5 -right-2 px-2 py-0.5 rounded-full text-xs font-bold\"\n style={{\n background: color,\n color: '#000',\n boxShadow: `0 0 10px color-mix(in srgb, ${color} 40%, transparent)`,\n }}\n >\n {group.count}\n </div>\n\n <div className=\"flex items-center gap-2\">\n {/* Branch icon — folder-like indicator */}\n <div\n className=\"w-2 h-2 rounded-full shrink-0\"\n style={{ background: color }}\n />\n <span\n className=\"text-sm font-semibold truncate\"\n style={{ color: 'var(--sk-text-primary, #ffffff)' }}\n >\n {group.label}\n </span>\n </div>\n\n {/* Expand indicator */}\n {hasChildren && (\n <span\n className=\"text-[10px] mt-1 block transition-colors\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n >\n {isExpanded ? 'click to collapse' : `${group.count} ${group.count === 1 ? 'page' : 'pages'} · click to expand`}\n </span>\n )}\n </button>\n\n {/* Expanded page list */}\n {isExpanded && hasChildren && (\n <div\n className=\"mt-2 rounded-lg overflow-hidden\"\n style={{\n background: `color-mix(in srgb, ${color} 5%, var(--sk-bg, #0a0a0a))`,\n border: `1px solid color-mix(in srgb, ${color} 15%, transparent)`,\n }}\n >\n {group.pages.map((page, pi) => (\n <div\n key={pi}\n className=\"flex items-center gap-2 px-3 py-1.5\"\n style={{\n borderBottom:\n pi < group.pages.length - 1\n ? `1px solid color-mix(in srgb, ${color} 8%, transparent)`\n : 'none',\n }}\n >\n <div\n className=\"w-1 h-1 rounded-full shrink-0\"\n style={{ background: color, opacity: 0.5 }}\n />\n <span\n className=\"text-xs truncate\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n title={page.title || page.path}\n >\n {page.title || page.path}\n </span>\n </div>\n ))}\n {group.count > group.pages.length && (\n <div\n className=\"px-3 py-1.5 text-xs italic\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)', opacity: 0.6 }}\n >\n +{group.count - group.pages.length} more pages\n </div>\n )}\n </div>\n )}\n </div>\n );\n })}\n </div>\n </div>\n\n {/* Bottom stats bar */}\n <ScrollReveal y={20} delay={0.4}>\n <div\n className=\"mt-12 flex items-center justify-center gap-8 md:gap-12 py-4 px-6 rounded-xl mx-auto max-w-2xl\"\n style={{\n background: 'color-mix(in srgb, var(--sk-primary, #6366f1) 5%, var(--sk-bg, #0a0a0a))',\n border: '1px solid color-mix(in srgb, var(--sk-primary, #6366f1) 15%, transparent)',\n }}\n >\n <div className=\"text-center\">\n <span\n className=\"text-2xl font-bold block\"\n style={{ color: 'var(--sk-primary, #6366f1)' }}\n >\n {totalPages}\n </span>\n <span\n className=\"text-[10px] uppercase tracking-wider block mt-0.5\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n >\n Pages\n </span>\n </div>\n <div\n className=\"w-px h-8\"\n style={{ background: 'color-mix(in srgb, var(--sk-text-tertiary, #71717a) 20%, transparent)' }}\n />\n <div className=\"text-center\">\n <span\n className=\"text-2xl font-bold block\"\n style={{ color: 'var(--sk-primary, #6366f1)' }}\n >\n {groups.length}\n </span>\n <span\n className=\"text-[10px] uppercase tracking-wider block mt-0.5\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n >\n Categories\n </span>\n </div>\n {hasWords && (\n <>\n <div\n className=\"w-px h-8\"\n style={{ background: 'color-mix(in srgb, var(--sk-text-tertiary, #71717a) 20%, transparent)' }}\n />\n <div className=\"text-center\">\n <span\n className=\"text-2xl font-bold block\"\n style={{ color: 'var(--sk-primary, #6366f1)' }}\n >\n {totalWords > 1000 ? `${(totalWords / 1000).toFixed(0)}k` : totalWords}\n </span>\n <span\n className=\"text-[10px] uppercase tracking-wider block mt-0.5\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n >\n Words\n </span>\n </div>\n </>\n )}\n </div>\n </ScrollReveal>\n </div>\n </section>\n );\n}\n"],"mappings":"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","names":["ScrollReveal"]}
|
|
@@ -187,7 +187,7 @@ function SiteArchitectureSection({ data }) {
|
|
|
187
187
|
{
|
|
188
188
|
className: "text-[10px] mt-1 block transition-colors",
|
|
189
189
|
style: { color: "var(--sk-text-tertiary, #71717a)" },
|
|
190
|
-
children: isExpanded ? "click to collapse" : `${group.count} ${group.count === 1 ? "page" : "pages"} \
|
|
190
|
+
children: isExpanded ? "click to collapse" : `${group.count} ${group.count === 1 ? "page" : "pages"} \xB7 click to expand`
|
|
191
191
|
}
|
|
192
192
|
)
|
|
193
193
|
]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/portfolio/components/sections/SiteArchitectureSection.tsx"],"sourcesContent":["'use client';\n\nimport React, { useRef, useEffect, useState } from 'react';\nimport ScrollReveal from '@sonordev/agency-site-kit/portfolio/primitives/ScrollReveal';\nimport GlassCard from '@sonordev/agency-site-kit/portfolio/primitives/GlassCard';\n\ninterface PageGroup {\n label: string;\n count: number;\n // path is optional: PortfolioPage strips raw client-site paths before they\n // reach this client component so they never enter the RSC flight payload\n // (Googlebot crawls path-like strings against the host site → 404 floods).\n pages: Array<{ path?: string; title: string }>;\n}\n\nexport interface SiteArchitectureData {\n totalPages: number;\n totalWords: number;\n groups: PageGroup[];\n}\n\ninterface SiteArchitectureSectionProps {\n data: SiteArchitectureData;\n}\n\n/** Generates a pleasing color from the brand palette for each branch */\nfunction getBranchColor(index: number): string {\n const hues = [\n 'var(--sk-primary, #6366f1)',\n '#10b981',\n '#f59e0b',\n '#8b5cf6',\n '#ec4899',\n '#06b6d4',\n '#f97316',\n '#14b8a6',\n ];\n return hues[index % hues.length];\n}\n\nconst EASE = 'cubic-bezier(0.25, 0.46, 0.45, 0.94)'; // ≈ power2.out\nconst EASE_BACK = 'cubic-bezier(0.34, 1.4, 0.64, 1)'; // ≈ back.out(1.4)\n\nexport default function SiteArchitectureSection({ data }: SiteArchitectureSectionProps) {\n const { totalPages, totalWords, groups } = data;\n const treeRef = useRef<HTMLDivElement>(null);\n const [expandedGroup, setExpandedGroup] = useState<number | null>(null);\n\n // Animate each branch node in on scroll\n useEffect(() => {\n const tree = treeRef.current;\n if (!tree) return;\n if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;\n\n // Never re-hide a tree already in the viewport at hydration.\n const rect = tree.getBoundingClientRect();\n if (rect.top < window.innerHeight && rect.bottom > 0) return;\n\n const nodes = Array.from(tree.querySelectorAll<HTMLElement>('.arch-node'));\n if (nodes.length === 0) return;\n\n // Hidden state — nodes pop in with a slight back-out overshoot on reveal.\n for (const node of nodes) {\n node.style.opacity = '0';\n node.style.transform = 'translateY(20px) scale(0.8)';\n node.style.willChange = 'opacity, transform';\n }\n\n const io = new IntersectionObserver(\n ([entry]) => {\n if (!entry.isIntersecting) return;\n io.disconnect();\n nodes.forEach((node, i) => {\n const d = i * 0.08;\n node.style.transition = `opacity 0.5s ${EASE} ${d}s, transform 0.5s ${EASE_BACK} ${d}s`;\n node.style.opacity = '1';\n node.style.transform = 'translateY(0px) scale(1)';\n });\n },\n { rootMargin: '0px 0px -30% 0px' }, // ≈ the old 'top 70%' trigger\n );\n io.observe(tree);\n\n return () => {\n io.disconnect();\n for (const node of nodes) {\n node.style.opacity = '';\n node.style.transform = '';\n node.style.transition = '';\n node.style.willChange = '';\n }\n };\n }, [groups.length]);\n\n const hasWords = totalWords > 0;\n const subtitle = hasWords\n ? `${totalPages} pages structured across ${groups.length} content ${groups.length === 1 ? 'category' : 'categories'} with ${totalWords.toLocaleString()} words of content.`\n : `${totalPages} pages structured across ${groups.length} content ${groups.length === 1 ? 'category' : 'categories'}.`;\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 <ScrollReveal y={30}>\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 Site Architecture\n </h2>\n <p\n className=\"text-lg mb-16 max-w-3xl\"\n style={{ color: 'var(--sk-text-secondary, #a1a1aa)' }}\n >\n {subtitle}\n </p>\n </ScrollReveal>\n\n {/* Visual Tree */}\n <div ref={treeRef} className=\"relative\">\n {/* Root node — the domain */}\n <div className=\"flex justify-center mb-8\">\n <div\n className=\"arch-node relative px-6 py-3 rounded-xl text-center\"\n style={{\n background: 'color-mix(in srgb, var(--sk-primary, #6366f1) 15%, var(--sk-bg, #0a0a0a))',\n border: '2px solid var(--sk-primary, #6366f1)',\n boxShadow: '0 0 30px color-mix(in srgb, var(--sk-primary, #6366f1) 20%, transparent)',\n }}\n >\n <span\n className=\"text-sm font-bold tracking-wide\"\n style={{ color: 'var(--sk-primary, #6366f1)' }}\n >\n /\n </span>\n <span\n className=\"block text-xs mt-0.5\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n >\n {totalPages} pages\n </span>\n </div>\n </div>\n\n {/* Branch nodes — each page group */}\n <div className=\"grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4\">\n {groups.map((group, gi) => {\n const color = getBranchColor(gi);\n const isExpanded = expandedGroup === gi;\n const hasChildren = group.pages.length > 0;\n\n return (\n <div\n key={gi}\n className=\"arch-node flex flex-col\"\n >\n {/* Branch connector line (vertical from root) */}\n <div\n className=\"w-px h-6 mx-auto mb-2\"\n style={{\n background: `linear-gradient(to bottom, color-mix(in srgb, ${color} 40%, transparent), ${color})`,\n }}\n />\n\n {/* Branch node */}\n <button\n onClick={() => hasChildren && setExpandedGroup(isExpanded ? null : gi)}\n className=\"relative rounded-xl p-4 text-left transition-all duration-300 cursor-pointer group\"\n style={{\n background: `color-mix(in srgb, ${color} 8%, var(--sk-bg, #0a0a0a))`,\n border: `1.5px solid color-mix(in srgb, ${color} ${isExpanded ? '40' : '20'}%, transparent)`,\n boxShadow: isExpanded\n ? `0 0 20px color-mix(in srgb, ${color} 15%, transparent)`\n : 'none',\n }}\n >\n {/* Count badge */}\n <div\n className=\"absolute -top-2.5 -right-2 px-2 py-0.5 rounded-full text-xs font-bold\"\n style={{\n background: color,\n color: '#000',\n boxShadow: `0 0 10px color-mix(in srgb, ${color} 40%, transparent)`,\n }}\n >\n {group.count}\n </div>\n\n <div className=\"flex items-center gap-2\">\n {/* Branch icon — folder-like indicator */}\n <div\n className=\"w-2 h-2 rounded-full shrink-0\"\n style={{ background: color }}\n />\n <span\n className=\"text-sm font-semibold truncate\"\n style={{ color: 'var(--sk-text-primary, #ffffff)' }}\n >\n {group.label}\n </span>\n </div>\n\n {/* Expand indicator */}\n {hasChildren && (\n <span\n className=\"text-[10px] mt-1 block transition-colors\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n >\n {isExpanded ? 'click to collapse' : `${group.count} ${group.count === 1 ? 'page' : 'pages'} — click to expand`}\n </span>\n )}\n </button>\n\n {/* Expanded page list */}\n {isExpanded && hasChildren && (\n <div\n className=\"mt-2 rounded-lg overflow-hidden\"\n style={{\n background: `color-mix(in srgb, ${color} 5%, var(--sk-bg, #0a0a0a))`,\n border: `1px solid color-mix(in srgb, ${color} 15%, transparent)`,\n }}\n >\n {group.pages.map((page, pi) => (\n <div\n key={pi}\n className=\"flex items-center gap-2 px-3 py-1.5\"\n style={{\n borderBottom:\n pi < group.pages.length - 1\n ? `1px solid color-mix(in srgb, ${color} 8%, transparent)`\n : 'none',\n }}\n >\n <div\n className=\"w-1 h-1 rounded-full shrink-0\"\n style={{ background: color, opacity: 0.5 }}\n />\n <span\n className=\"text-xs truncate\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n title={page.title || page.path}\n >\n {page.title || page.path}\n </span>\n </div>\n ))}\n {group.count > group.pages.length && (\n <div\n className=\"px-3 py-1.5 text-xs italic\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)', opacity: 0.6 }}\n >\n +{group.count - group.pages.length} more pages\n </div>\n )}\n </div>\n )}\n </div>\n );\n })}\n </div>\n </div>\n\n {/* Bottom stats bar */}\n <ScrollReveal y={20} delay={0.4}>\n <div\n className=\"mt-12 flex items-center justify-center gap-8 md:gap-12 py-4 px-6 rounded-xl mx-auto max-w-2xl\"\n style={{\n background: 'color-mix(in srgb, var(--sk-primary, #6366f1) 5%, var(--sk-bg, #0a0a0a))',\n border: '1px solid color-mix(in srgb, var(--sk-primary, #6366f1) 15%, transparent)',\n }}\n >\n <div className=\"text-center\">\n <span\n className=\"text-2xl font-bold block\"\n style={{ color: 'var(--sk-primary, #6366f1)' }}\n >\n {totalPages}\n </span>\n <span\n className=\"text-[10px] uppercase tracking-wider block mt-0.5\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n >\n Pages\n </span>\n </div>\n <div\n className=\"w-px h-8\"\n style={{ background: 'color-mix(in srgb, var(--sk-text-tertiary, #71717a) 20%, transparent)' }}\n />\n <div className=\"text-center\">\n <span\n className=\"text-2xl font-bold block\"\n style={{ color: 'var(--sk-primary, #6366f1)' }}\n >\n {groups.length}\n </span>\n <span\n className=\"text-[10px] uppercase tracking-wider block mt-0.5\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n >\n Categories\n </span>\n </div>\n {hasWords && (\n <>\n <div\n className=\"w-px h-8\"\n style={{ background: 'color-mix(in srgb, var(--sk-text-tertiary, #71717a) 20%, transparent)' }}\n />\n <div className=\"text-center\">\n <span\n className=\"text-2xl font-bold block\"\n style={{ color: 'var(--sk-primary, #6366f1)' }}\n >\n {totalWords > 1000 ? `${(totalWords / 1000).toFixed(0)}k` : totalWords}\n </span>\n <span\n className=\"text-[10px] uppercase tracking-wider block mt-0.5\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n >\n Words\n </span>\n </div>\n </>\n )}\n </div>\n </ScrollReveal>\n </div>\n </section>\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/portfolio/components/sections/SiteArchitectureSection.tsx"],"sourcesContent":["'use client';\n\nimport React, { useRef, useEffect, useState } from 'react';\nimport ScrollReveal from '@sonordev/agency-site-kit/portfolio/primitives/ScrollReveal';\nimport GlassCard from '@sonordev/agency-site-kit/portfolio/primitives/GlassCard';\n\ninterface PageGroup {\n label: string;\n count: number;\n // path is optional: PortfolioPage strips raw client-site paths before they\n // reach this client component so they never enter the RSC flight payload\n // (Googlebot crawls path-like strings against the host site → 404 floods).\n pages: Array<{ path?: string; title: string }>;\n}\n\nexport interface SiteArchitectureData {\n totalPages: number;\n totalWords: number;\n groups: PageGroup[];\n}\n\ninterface SiteArchitectureSectionProps {\n data: SiteArchitectureData;\n}\n\n/** Generates a pleasing color from the brand palette for each branch */\nfunction getBranchColor(index: number): string {\n const hues = [\n 'var(--sk-primary, #6366f1)',\n '#10b981',\n '#f59e0b',\n '#8b5cf6',\n '#ec4899',\n '#06b6d4',\n '#f97316',\n '#14b8a6',\n ];\n return hues[index % hues.length];\n}\n\nconst EASE = 'cubic-bezier(0.25, 0.46, 0.45, 0.94)'; // ≈ power2.out\nconst EASE_BACK = 'cubic-bezier(0.34, 1.4, 0.64, 1)'; // ≈ back.out(1.4)\n\nexport default function SiteArchitectureSection({ data }: SiteArchitectureSectionProps) {\n const { totalPages, totalWords, groups } = data;\n const treeRef = useRef<HTMLDivElement>(null);\n const [expandedGroup, setExpandedGroup] = useState<number | null>(null);\n\n // Animate each branch node in on scroll\n useEffect(() => {\n const tree = treeRef.current;\n if (!tree) return;\n if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;\n\n // Never re-hide a tree already in the viewport at hydration.\n const rect = tree.getBoundingClientRect();\n if (rect.top < window.innerHeight && rect.bottom > 0) return;\n\n const nodes = Array.from(tree.querySelectorAll<HTMLElement>('.arch-node'));\n if (nodes.length === 0) return;\n\n // Hidden state — nodes pop in with a slight back-out overshoot on reveal.\n for (const node of nodes) {\n node.style.opacity = '0';\n node.style.transform = 'translateY(20px) scale(0.8)';\n node.style.willChange = 'opacity, transform';\n }\n\n const io = new IntersectionObserver(\n ([entry]) => {\n if (!entry.isIntersecting) return;\n io.disconnect();\n nodes.forEach((node, i) => {\n const d = i * 0.08;\n node.style.transition = `opacity 0.5s ${EASE} ${d}s, transform 0.5s ${EASE_BACK} ${d}s`;\n node.style.opacity = '1';\n node.style.transform = 'translateY(0px) scale(1)';\n });\n },\n { rootMargin: '0px 0px -30% 0px' }, // ≈ the old 'top 70%' trigger\n );\n io.observe(tree);\n\n return () => {\n io.disconnect();\n for (const node of nodes) {\n node.style.opacity = '';\n node.style.transform = '';\n node.style.transition = '';\n node.style.willChange = '';\n }\n };\n }, [groups.length]);\n\n const hasWords = totalWords > 0;\n const subtitle = hasWords\n ? `${totalPages} pages structured across ${groups.length} content ${groups.length === 1 ? 'category' : 'categories'} with ${totalWords.toLocaleString()} words of content.`\n : `${totalPages} pages structured across ${groups.length} content ${groups.length === 1 ? 'category' : 'categories'}.`;\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 <ScrollReveal y={30}>\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 Site Architecture\n </h2>\n <p\n className=\"text-lg mb-16 max-w-3xl\"\n style={{ color: 'var(--sk-text-secondary, #a1a1aa)' }}\n >\n {subtitle}\n </p>\n </ScrollReveal>\n\n {/* Visual Tree */}\n <div ref={treeRef} className=\"relative\">\n {/* Root node — the domain */}\n <div className=\"flex justify-center mb-8\">\n <div\n className=\"arch-node relative px-6 py-3 rounded-xl text-center\"\n style={{\n background: 'color-mix(in srgb, var(--sk-primary, #6366f1) 15%, var(--sk-bg, #0a0a0a))',\n border: '2px solid var(--sk-primary, #6366f1)',\n boxShadow: '0 0 30px color-mix(in srgb, var(--sk-primary, #6366f1) 20%, transparent)',\n }}\n >\n <span\n className=\"text-sm font-bold tracking-wide\"\n style={{ color: 'var(--sk-primary, #6366f1)' }}\n >\n /\n </span>\n <span\n className=\"block text-xs mt-0.5\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n >\n {totalPages} pages\n </span>\n </div>\n </div>\n\n {/* Branch nodes — each page group */}\n <div className=\"grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4\">\n {groups.map((group, gi) => {\n const color = getBranchColor(gi);\n const isExpanded = expandedGroup === gi;\n const hasChildren = group.pages.length > 0;\n\n return (\n <div\n key={gi}\n className=\"arch-node flex flex-col\"\n >\n {/* Branch connector line (vertical from root) */}\n <div\n className=\"w-px h-6 mx-auto mb-2\"\n style={{\n background: `linear-gradient(to bottom, color-mix(in srgb, ${color} 40%, transparent), ${color})`,\n }}\n />\n\n {/* Branch node */}\n <button\n onClick={() => hasChildren && setExpandedGroup(isExpanded ? null : gi)}\n className=\"relative rounded-xl p-4 text-left transition-all duration-300 cursor-pointer group\"\n style={{\n background: `color-mix(in srgb, ${color} 8%, var(--sk-bg, #0a0a0a))`,\n border: `1.5px solid color-mix(in srgb, ${color} ${isExpanded ? '40' : '20'}%, transparent)`,\n boxShadow: isExpanded\n ? `0 0 20px color-mix(in srgb, ${color} 15%, transparent)`\n : 'none',\n }}\n >\n {/* Count badge */}\n <div\n className=\"absolute -top-2.5 -right-2 px-2 py-0.5 rounded-full text-xs font-bold\"\n style={{\n background: color,\n color: '#000',\n boxShadow: `0 0 10px color-mix(in srgb, ${color} 40%, transparent)`,\n }}\n >\n {group.count}\n </div>\n\n <div className=\"flex items-center gap-2\">\n {/* Branch icon — folder-like indicator */}\n <div\n className=\"w-2 h-2 rounded-full shrink-0\"\n style={{ background: color }}\n />\n <span\n className=\"text-sm font-semibold truncate\"\n style={{ color: 'var(--sk-text-primary, #ffffff)' }}\n >\n {group.label}\n </span>\n </div>\n\n {/* Expand indicator */}\n {hasChildren && (\n <span\n className=\"text-[10px] mt-1 block transition-colors\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n >\n {isExpanded ? 'click to collapse' : `${group.count} ${group.count === 1 ? 'page' : 'pages'} · click to expand`}\n </span>\n )}\n </button>\n\n {/* Expanded page list */}\n {isExpanded && hasChildren && (\n <div\n className=\"mt-2 rounded-lg overflow-hidden\"\n style={{\n background: `color-mix(in srgb, ${color} 5%, var(--sk-bg, #0a0a0a))`,\n border: `1px solid color-mix(in srgb, ${color} 15%, transparent)`,\n }}\n >\n {group.pages.map((page, pi) => (\n <div\n key={pi}\n className=\"flex items-center gap-2 px-3 py-1.5\"\n style={{\n borderBottom:\n pi < group.pages.length - 1\n ? `1px solid color-mix(in srgb, ${color} 8%, transparent)`\n : 'none',\n }}\n >\n <div\n className=\"w-1 h-1 rounded-full shrink-0\"\n style={{ background: color, opacity: 0.5 }}\n />\n <span\n className=\"text-xs truncate\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n title={page.title || page.path}\n >\n {page.title || page.path}\n </span>\n </div>\n ))}\n {group.count > group.pages.length && (\n <div\n className=\"px-3 py-1.5 text-xs italic\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)', opacity: 0.6 }}\n >\n +{group.count - group.pages.length} more pages\n </div>\n )}\n </div>\n )}\n </div>\n );\n })}\n </div>\n </div>\n\n {/* Bottom stats bar */}\n <ScrollReveal y={20} delay={0.4}>\n <div\n className=\"mt-12 flex items-center justify-center gap-8 md:gap-12 py-4 px-6 rounded-xl mx-auto max-w-2xl\"\n style={{\n background: 'color-mix(in srgb, var(--sk-primary, #6366f1) 5%, var(--sk-bg, #0a0a0a))',\n border: '1px solid color-mix(in srgb, var(--sk-primary, #6366f1) 15%, transparent)',\n }}\n >\n <div className=\"text-center\">\n <span\n className=\"text-2xl font-bold block\"\n style={{ color: 'var(--sk-primary, #6366f1)' }}\n >\n {totalPages}\n </span>\n <span\n className=\"text-[10px] uppercase tracking-wider block mt-0.5\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n >\n Pages\n </span>\n </div>\n <div\n className=\"w-px h-8\"\n style={{ background: 'color-mix(in srgb, var(--sk-text-tertiary, #71717a) 20%, transparent)' }}\n />\n <div className=\"text-center\">\n <span\n className=\"text-2xl font-bold block\"\n style={{ color: 'var(--sk-primary, #6366f1)' }}\n >\n {groups.length}\n </span>\n <span\n className=\"text-[10px] uppercase tracking-wider block mt-0.5\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n >\n Categories\n </span>\n </div>\n {hasWords && (\n <>\n <div\n className=\"w-px h-8\"\n style={{ background: 'color-mix(in srgb, var(--sk-text-tertiary, #71717a) 20%, transparent)' }}\n />\n <div className=\"text-center\">\n <span\n className=\"text-2xl font-bold block\"\n style={{ color: 'var(--sk-primary, #6366f1)' }}\n >\n {totalWords > 1000 ? `${(totalWords / 1000).toFixed(0)}k` : totalWords}\n </span>\n <span\n className=\"text-[10px] uppercase tracking-wider block mt-0.5\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n >\n Words\n </span>\n </div>\n </>\n )}\n </div>\n </ScrollReveal>\n </div>\n </section>\n );\n}\n"],"mappings":"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","names":[]}
|