@sonordev/agency-site-kit 0.4.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/dist/chunk-7D4H36UF.js +13 -0
  2. package/dist/chunk-7D4H36UF.js.map +1 -0
  3. package/dist/{chunk-XLK77USA.js → chunk-K2BGTDSN.js} +2 -2
  4. package/dist/{chunk-XLK77USA.js.map → chunk-K2BGTDSN.js.map} +1 -1
  5. package/dist/{chunk-UNTOEWQL.js → chunk-NLGUPJZA.js} +9 -6
  6. package/dist/chunk-NLGUPJZA.js.map +1 -0
  7. package/dist/{chunk-L7E3UHQC.cjs → chunk-QGSW43TM.cjs} +9 -6
  8. package/dist/chunk-QGSW43TM.cjs.map +1 -0
  9. package/dist/{chunk-NIZJLONK.js → chunk-RWQ675N6.js} +72 -13
  10. package/dist/chunk-RWQ675N6.js.map +1 -0
  11. package/dist/{chunk-4N7ENJ7L.cjs → chunk-UXUU7FVT.cjs} +75 -12
  12. package/dist/chunk-UXUU7FVT.cjs.map +1 -0
  13. package/dist/chunk-ZBPF7TLZ.cjs +16 -0
  14. package/dist/chunk-ZBPF7TLZ.cjs.map +1 -0
  15. package/dist/index.cjs +34 -9
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.d.cts +1 -1
  18. package/dist/index.d.ts +1 -1
  19. package/dist/index.js +3 -2
  20. package/dist/index.js.map +1 -1
  21. package/dist/portfolio/client.cjs +40 -6
  22. package/dist/portfolio/client.cjs.map +1 -1
  23. package/dist/portfolio/client.d.cts +20 -1
  24. package/dist/portfolio/client.d.ts +20 -1
  25. package/dist/portfolio/client.js +37 -5
  26. package/dist/portfolio/client.js.map +1 -1
  27. package/dist/portfolio/components/PortfolioErrorFallback.cjs +1 -1
  28. package/dist/portfolio/components/PortfolioErrorFallback.cjs.map +1 -1
  29. package/dist/portfolio/components/PortfolioErrorFallback.js +1 -1
  30. package/dist/portfolio/index.cjs +34 -9
  31. package/dist/portfolio/index.d.cts +82 -5
  32. package/dist/portfolio/index.d.ts +82 -5
  33. package/dist/portfolio/index.js +3 -2
  34. package/dist/portfolio/sections/ChallengesSection.cjs +1 -1
  35. package/dist/portfolio/sections/ChallengesSection.cjs.map +1 -1
  36. package/dist/portfolio/sections/ChallengesSection.js +1 -1
  37. package/dist/portfolio/sections/ChallengesSection.js.map +1 -1
  38. package/dist/portfolio/sections/HeroSection.cjs +3 -3
  39. package/dist/portfolio/sections/HeroSection.cjs.map +1 -1
  40. package/dist/portfolio/sections/HeroSection.js +3 -3
  41. package/dist/portfolio/sections/HeroSection.js.map +1 -1
  42. package/dist/portfolio/sections/PerformanceSection.cjs +1 -64
  43. package/dist/portfolio/sections/PerformanceSection.cjs.map +1 -1
  44. package/dist/portfolio/sections/PerformanceSection.js +1 -64
  45. package/dist/portfolio/sections/PerformanceSection.js.map +1 -1
  46. package/dist/portfolio/sections/ResultsSection.cjs +3 -2
  47. package/dist/portfolio/sections/ResultsSection.cjs.map +1 -1
  48. package/dist/portfolio/sections/ResultsSection.d.cts +1 -1
  49. package/dist/portfolio/sections/ResultsSection.d.ts +1 -1
  50. package/dist/portfolio/sections/ResultsSection.js +2 -1
  51. package/dist/portfolio/sections/ResultsSection.js.map +1 -1
  52. package/dist/portfolio/sections/SiteArchitectureSection.cjs +1 -1
  53. package/dist/portfolio/sections/SiteArchitectureSection.cjs.map +1 -1
  54. package/dist/portfolio/sections/SiteArchitectureSection.js +1 -1
  55. package/dist/portfolio/sections/SiteArchitectureSection.js.map +1 -1
  56. package/dist/portfolio/sections/SpeedComparisonSection.cjs +1 -1
  57. package/dist/portfolio/sections/SpeedComparisonSection.cjs.map +1 -1
  58. package/dist/portfolio/sections/SpeedComparisonSection.js +1 -1
  59. package/dist/portfolio/sections/SpeedComparisonSection.js.map +1 -1
  60. package/package.json +1 -1
  61. package/dist/chunk-4N7ENJ7L.cjs.map +0 -1
  62. package/dist/chunk-L7E3UHQC.cjs.map +0 -1
  63. package/dist/chunk-NIZJLONK.js.map +0 -1
  64. package/dist/chunk-UNTOEWQL.js.map +0 -1
@@ -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":[]}
@@ -11,7 +11,7 @@ var GlassCard__default = /*#__PURE__*/_interopDefault(GlassCard);
11
11
 
12
12
  // src/portfolio/components/sections/SpeedComparisonSection.tsx
13
13
  var CATEGORIES = [
14
- { key: "performance", label: "Performance", why: "Faster sites convert better \u2014 every 100ms of improvement increases conversions by up to 8%." },
14
+ { key: "performance", label: "Performance", why: "Faster sites convert better: every 100ms of improvement increases conversions by up to 8%." },
15
15
  { key: "seo", label: "SEO", why: "Higher SEO scores mean better search rankings, more organic traffic, and lower customer acquisition costs." },
16
16
  { key: "accessibility", label: "Accessibility", why: "Accessible sites reach 15-20% more users and reduce legal liability." },
17
17
  { key: "bestPractices", label: "Best Practices", why: "Modern security headers, HTTPS, and optimized assets build trust and protect users." }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/portfolio/components/sections/SpeedComparisonSection.tsx"],"names":["jsxs","jsx","ScrollReveal","GlassCard"],"mappings":"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","file":"SpeedComparisonSection.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 ScrollReveal from '@sonordev/agency-site-kit/portfolio/primitives/ScrollReveal';\nimport GlassCard from '@sonordev/agency-site-kit/portfolio/primitives/GlassCard';\n\ninterface Scores {\n performance: number;\n seo: number;\n accessibility: number;\n bestPractices: number;\n}\n\nexport interface SpeedComparisonData {\n platform: string;\n platformLabel: string;\n benchmarks: Scores;\n actual: Scores;\n}\n\ninterface SpeedComparisonSectionProps {\n data: SpeedComparisonData;\n}\n\nconst CATEGORIES: Array<{ key: keyof Scores; label: string; why: string }> = [\n { key: 'performance', label: 'Performance', why: 'Faster sites convert better — every 100ms of improvement increases conversions by up to 8%.' },\n { key: 'seo', label: 'SEO', why: 'Higher SEO scores mean better search rankings, more organic traffic, and lower customer acquisition costs.' },\n { key: 'accessibility', label: 'Accessibility', why: 'Accessible sites reach 15-20% more users and reduce legal liability.' },\n { key: 'bestPractices', label: 'Best Practices', why: 'Modern security headers, HTTPS, and optimized assets build trust and protect users.' },\n];\n\nfunction getScoreColor(score: number): string {\n if (score >= 90) return '#10b981';\n if (score >= 50) return '#f59e0b';\n return '#ef4444';\n}\n\n/** Circular score gauge */\nfunction ScoreRing({ score, size = 72 }: { score: number; size?: number }) {\n const strokeWidth = 5;\n const radius = (size - strokeWidth) / 2;\n const circumference = 2 * Math.PI * radius;\n const progress = (score / 100) * circumference;\n const color = getScoreColor(score);\n\n return (\n <div className=\"relative\" style={{ width: size, height: size }}>\n <svg width={size} height={size} className=\"transform -rotate-90\">\n <circle\n cx={size / 2}\n cy={size / 2}\n r={radius}\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={strokeWidth}\n style={{ color: 'color-mix(in srgb, var(--sk-text-tertiary, #71717a) 20%, transparent)' }}\n />\n <circle\n cx={size / 2}\n cy={size / 2}\n r={radius}\n fill=\"none\"\n stroke={color}\n strokeWidth={strokeWidth}\n strokeDasharray={`${progress} ${circumference - progress}`}\n strokeLinecap=\"round\"\n />\n </svg>\n <span\n className=\"absolute inset-0 flex items-center justify-center text-lg font-bold\"\n style={{ color }}\n >\n {score}\n </span>\n </div>\n );\n}\n\nexport default function SpeedComparisonSection({ data }: SpeedComparisonSectionProps) {\n const { platformLabel, benchmarks, actual } = data;\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 Why This Matters\n </h2>\n <p\n className=\"text-lg mb-4 max-w-3xl\"\n style={{ color: 'var(--sk-text-secondary, #a1a1aa)' }}\n >\n Lighthouse measures real-world site quality across performance, SEO, accessibility, and best practices.\n These scores directly impact search rankings, conversion rates, and user experience.\n </p>\n <p\n className=\"text-base mb-12 max-w-3xl\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n >\n Here&apos;s how this Next.js build compares to the average {platformLabel} site.\n </p>\n </ScrollReveal>\n\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-6\">\n {CATEGORIES.map((cat, ci) => {\n const ours = actual[cat.key];\n const theirs = benchmarks[cat.key];\n const diff = ours - theirs;\n\n return (\n <ScrollReveal key={cat.key} y={20} delay={ci * 0.1}>\n <GlassCard padding=\"lg\" hover={false} className=\"h-full\">\n <div className=\"flex items-start gap-5\">\n {/* Score rings side by side */}\n <div className=\"flex flex-col items-center gap-3 shrink-0\">\n <ScoreRing score={ours} />\n <span\n className=\"text-[10px] font-medium uppercase tracking-wider\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n >\n Next.js\n </span>\n </div>\n <div className=\"flex flex-col items-center gap-3 shrink-0\">\n <ScoreRing score={theirs} />\n <span\n className=\"text-[10px] font-medium uppercase tracking-wider\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n >\n {platformLabel}\n </span>\n </div>\n\n {/* Details */}\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2 mb-2\">\n <h3\n className=\"text-base font-semibold\"\n style={{ color: 'var(--sk-text-primary, #ffffff)' }}\n >\n {cat.label}\n </h3>\n {diff > 0 && (\n <span\n className=\"text-xs font-bold px-2 py-0.5 rounded-full\"\n style={{\n background: 'color-mix(in srgb, #10b981 15%, transparent)',\n color: '#10b981',\n }}\n >\n +{diff} pts\n </span>\n )}\n </div>\n <p\n className=\"text-sm leading-relaxed\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n >\n {cat.why}\n </p>\n </div>\n </div>\n </GlassCard>\n </ScrollReveal>\n );\n })}\n </div>\n </div>\n </section>\n );\n}\n"]}
1
+ {"version":3,"sources":["../../../src/portfolio/components/sections/SpeedComparisonSection.tsx"],"names":["jsxs","jsx","ScrollReveal","GlassCard"],"mappings":"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","file":"SpeedComparisonSection.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 ScrollReveal from '@sonordev/agency-site-kit/portfolio/primitives/ScrollReveal';\nimport GlassCard from '@sonordev/agency-site-kit/portfolio/primitives/GlassCard';\n\ninterface Scores {\n performance: number;\n seo: number;\n accessibility: number;\n bestPractices: number;\n}\n\nexport interface SpeedComparisonData {\n platform: string;\n platformLabel: string;\n benchmarks: Scores;\n actual: Scores;\n}\n\ninterface SpeedComparisonSectionProps {\n data: SpeedComparisonData;\n}\n\nconst CATEGORIES: Array<{ key: keyof Scores; label: string; why: string }> = [\n { key: 'performance', label: 'Performance', why: 'Faster sites convert better: every 100ms of improvement increases conversions by up to 8%.' },\n { key: 'seo', label: 'SEO', why: 'Higher SEO scores mean better search rankings, more organic traffic, and lower customer acquisition costs.' },\n { key: 'accessibility', label: 'Accessibility', why: 'Accessible sites reach 15-20% more users and reduce legal liability.' },\n { key: 'bestPractices', label: 'Best Practices', why: 'Modern security headers, HTTPS, and optimized assets build trust and protect users.' },\n];\n\nfunction getScoreColor(score: number): string {\n if (score >= 90) return '#10b981';\n if (score >= 50) return '#f59e0b';\n return '#ef4444';\n}\n\n/** Circular score gauge */\nfunction ScoreRing({ score, size = 72 }: { score: number; size?: number }) {\n const strokeWidth = 5;\n const radius = (size - strokeWidth) / 2;\n const circumference = 2 * Math.PI * radius;\n const progress = (score / 100) * circumference;\n const color = getScoreColor(score);\n\n return (\n <div className=\"relative\" style={{ width: size, height: size }}>\n <svg width={size} height={size} className=\"transform -rotate-90\">\n <circle\n cx={size / 2}\n cy={size / 2}\n r={radius}\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={strokeWidth}\n style={{ color: 'color-mix(in srgb, var(--sk-text-tertiary, #71717a) 20%, transparent)' }}\n />\n <circle\n cx={size / 2}\n cy={size / 2}\n r={radius}\n fill=\"none\"\n stroke={color}\n strokeWidth={strokeWidth}\n strokeDasharray={`${progress} ${circumference - progress}`}\n strokeLinecap=\"round\"\n />\n </svg>\n <span\n className=\"absolute inset-0 flex items-center justify-center text-lg font-bold\"\n style={{ color }}\n >\n {score}\n </span>\n </div>\n );\n}\n\nexport default function SpeedComparisonSection({ data }: SpeedComparisonSectionProps) {\n const { platformLabel, benchmarks, actual } = data;\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 Why This Matters\n </h2>\n <p\n className=\"text-lg mb-4 max-w-3xl\"\n style={{ color: 'var(--sk-text-secondary, #a1a1aa)' }}\n >\n Lighthouse measures real-world site quality across performance, SEO, accessibility, and best practices.\n These scores directly impact search rankings, conversion rates, and user experience.\n </p>\n <p\n className=\"text-base mb-12 max-w-3xl\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n >\n Here&apos;s how this Next.js build compares to the average {platformLabel} site.\n </p>\n </ScrollReveal>\n\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-6\">\n {CATEGORIES.map((cat, ci) => {\n const ours = actual[cat.key];\n const theirs = benchmarks[cat.key];\n const diff = ours - theirs;\n\n return (\n <ScrollReveal key={cat.key} y={20} delay={ci * 0.1}>\n <GlassCard padding=\"lg\" hover={false} className=\"h-full\">\n <div className=\"flex items-start gap-5\">\n {/* Score rings side by side */}\n <div className=\"flex flex-col items-center gap-3 shrink-0\">\n <ScoreRing score={ours} />\n <span\n className=\"text-[10px] font-medium uppercase tracking-wider\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n >\n Next.js\n </span>\n </div>\n <div className=\"flex flex-col items-center gap-3 shrink-0\">\n <ScoreRing score={theirs} />\n <span\n className=\"text-[10px] font-medium uppercase tracking-wider\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n >\n {platformLabel}\n </span>\n </div>\n\n {/* Details */}\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2 mb-2\">\n <h3\n className=\"text-base font-semibold\"\n style={{ color: 'var(--sk-text-primary, #ffffff)' }}\n >\n {cat.label}\n </h3>\n {diff > 0 && (\n <span\n className=\"text-xs font-bold px-2 py-0.5 rounded-full\"\n style={{\n background: 'color-mix(in srgb, #10b981 15%, transparent)',\n color: '#10b981',\n }}\n >\n +{diff} pts\n </span>\n )}\n </div>\n <p\n className=\"text-sm leading-relaxed\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n >\n {cat.why}\n </p>\n </div>\n </div>\n </GlassCard>\n </ScrollReveal>\n );\n })}\n </div>\n </div>\n </section>\n );\n}\n"]}
@@ -4,7 +4,7 @@ import { jsx, jsxs } from 'react/jsx-runtime';
4
4
 
5
5
  // src/portfolio/components/sections/SpeedComparisonSection.tsx
6
6
  var CATEGORIES = [
7
- { key: "performance", label: "Performance", why: "Faster sites convert better \u2014 every 100ms of improvement increases conversions by up to 8%." },
7
+ { key: "performance", label: "Performance", why: "Faster sites convert better: every 100ms of improvement increases conversions by up to 8%." },
8
8
  { key: "seo", label: "SEO", why: "Higher SEO scores mean better search rankings, more organic traffic, and lower customer acquisition costs." },
9
9
  { key: "accessibility", label: "Accessibility", why: "Accessible sites reach 15-20% more users and reduce legal liability." },
10
10
  { key: "bestPractices", label: "Best Practices", why: "Modern security headers, HTTPS, and optimized assets build trust and protect users." }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/portfolio/components/sections/SpeedComparisonSection.tsx"],"names":[],"mappings":"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","file":"SpeedComparisonSection.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 ScrollReveal from '@sonordev/agency-site-kit/portfolio/primitives/ScrollReveal';\nimport GlassCard from '@sonordev/agency-site-kit/portfolio/primitives/GlassCard';\n\ninterface Scores {\n performance: number;\n seo: number;\n accessibility: number;\n bestPractices: number;\n}\n\nexport interface SpeedComparisonData {\n platform: string;\n platformLabel: string;\n benchmarks: Scores;\n actual: Scores;\n}\n\ninterface SpeedComparisonSectionProps {\n data: SpeedComparisonData;\n}\n\nconst CATEGORIES: Array<{ key: keyof Scores; label: string; why: string }> = [\n { key: 'performance', label: 'Performance', why: 'Faster sites convert better — every 100ms of improvement increases conversions by up to 8%.' },\n { key: 'seo', label: 'SEO', why: 'Higher SEO scores mean better search rankings, more organic traffic, and lower customer acquisition costs.' },\n { key: 'accessibility', label: 'Accessibility', why: 'Accessible sites reach 15-20% more users and reduce legal liability.' },\n { key: 'bestPractices', label: 'Best Practices', why: 'Modern security headers, HTTPS, and optimized assets build trust and protect users.' },\n];\n\nfunction getScoreColor(score: number): string {\n if (score >= 90) return '#10b981';\n if (score >= 50) return '#f59e0b';\n return '#ef4444';\n}\n\n/** Circular score gauge */\nfunction ScoreRing({ score, size = 72 }: { score: number; size?: number }) {\n const strokeWidth = 5;\n const radius = (size - strokeWidth) / 2;\n const circumference = 2 * Math.PI * radius;\n const progress = (score / 100) * circumference;\n const color = getScoreColor(score);\n\n return (\n <div className=\"relative\" style={{ width: size, height: size }}>\n <svg width={size} height={size} className=\"transform -rotate-90\">\n <circle\n cx={size / 2}\n cy={size / 2}\n r={radius}\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={strokeWidth}\n style={{ color: 'color-mix(in srgb, var(--sk-text-tertiary, #71717a) 20%, transparent)' }}\n />\n <circle\n cx={size / 2}\n cy={size / 2}\n r={radius}\n fill=\"none\"\n stroke={color}\n strokeWidth={strokeWidth}\n strokeDasharray={`${progress} ${circumference - progress}`}\n strokeLinecap=\"round\"\n />\n </svg>\n <span\n className=\"absolute inset-0 flex items-center justify-center text-lg font-bold\"\n style={{ color }}\n >\n {score}\n </span>\n </div>\n );\n}\n\nexport default function SpeedComparisonSection({ data }: SpeedComparisonSectionProps) {\n const { platformLabel, benchmarks, actual } = data;\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 Why This Matters\n </h2>\n <p\n className=\"text-lg mb-4 max-w-3xl\"\n style={{ color: 'var(--sk-text-secondary, #a1a1aa)' }}\n >\n Lighthouse measures real-world site quality across performance, SEO, accessibility, and best practices.\n These scores directly impact search rankings, conversion rates, and user experience.\n </p>\n <p\n className=\"text-base mb-12 max-w-3xl\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n >\n Here&apos;s how this Next.js build compares to the average {platformLabel} site.\n </p>\n </ScrollReveal>\n\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-6\">\n {CATEGORIES.map((cat, ci) => {\n const ours = actual[cat.key];\n const theirs = benchmarks[cat.key];\n const diff = ours - theirs;\n\n return (\n <ScrollReveal key={cat.key} y={20} delay={ci * 0.1}>\n <GlassCard padding=\"lg\" hover={false} className=\"h-full\">\n <div className=\"flex items-start gap-5\">\n {/* Score rings side by side */}\n <div className=\"flex flex-col items-center gap-3 shrink-0\">\n <ScoreRing score={ours} />\n <span\n className=\"text-[10px] font-medium uppercase tracking-wider\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n >\n Next.js\n </span>\n </div>\n <div className=\"flex flex-col items-center gap-3 shrink-0\">\n <ScoreRing score={theirs} />\n <span\n className=\"text-[10px] font-medium uppercase tracking-wider\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n >\n {platformLabel}\n </span>\n </div>\n\n {/* Details */}\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2 mb-2\">\n <h3\n className=\"text-base font-semibold\"\n style={{ color: 'var(--sk-text-primary, #ffffff)' }}\n >\n {cat.label}\n </h3>\n {diff > 0 && (\n <span\n className=\"text-xs font-bold px-2 py-0.5 rounded-full\"\n style={{\n background: 'color-mix(in srgb, #10b981 15%, transparent)',\n color: '#10b981',\n }}\n >\n +{diff} pts\n </span>\n )}\n </div>\n <p\n className=\"text-sm leading-relaxed\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n >\n {cat.why}\n </p>\n </div>\n </div>\n </GlassCard>\n </ScrollReveal>\n );\n })}\n </div>\n </div>\n </section>\n );\n}\n"]}
1
+ {"version":3,"sources":["../../../src/portfolio/components/sections/SpeedComparisonSection.tsx"],"names":[],"mappings":"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","file":"SpeedComparisonSection.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 ScrollReveal from '@sonordev/agency-site-kit/portfolio/primitives/ScrollReveal';\nimport GlassCard from '@sonordev/agency-site-kit/portfolio/primitives/GlassCard';\n\ninterface Scores {\n performance: number;\n seo: number;\n accessibility: number;\n bestPractices: number;\n}\n\nexport interface SpeedComparisonData {\n platform: string;\n platformLabel: string;\n benchmarks: Scores;\n actual: Scores;\n}\n\ninterface SpeedComparisonSectionProps {\n data: SpeedComparisonData;\n}\n\nconst CATEGORIES: Array<{ key: keyof Scores; label: string; why: string }> = [\n { key: 'performance', label: 'Performance', why: 'Faster sites convert better: every 100ms of improvement increases conversions by up to 8%.' },\n { key: 'seo', label: 'SEO', why: 'Higher SEO scores mean better search rankings, more organic traffic, and lower customer acquisition costs.' },\n { key: 'accessibility', label: 'Accessibility', why: 'Accessible sites reach 15-20% more users and reduce legal liability.' },\n { key: 'bestPractices', label: 'Best Practices', why: 'Modern security headers, HTTPS, and optimized assets build trust and protect users.' },\n];\n\nfunction getScoreColor(score: number): string {\n if (score >= 90) return '#10b981';\n if (score >= 50) return '#f59e0b';\n return '#ef4444';\n}\n\n/** Circular score gauge */\nfunction ScoreRing({ score, size = 72 }: { score: number; size?: number }) {\n const strokeWidth = 5;\n const radius = (size - strokeWidth) / 2;\n const circumference = 2 * Math.PI * radius;\n const progress = (score / 100) * circumference;\n const color = getScoreColor(score);\n\n return (\n <div className=\"relative\" style={{ width: size, height: size }}>\n <svg width={size} height={size} className=\"transform -rotate-90\">\n <circle\n cx={size / 2}\n cy={size / 2}\n r={radius}\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={strokeWidth}\n style={{ color: 'color-mix(in srgb, var(--sk-text-tertiary, #71717a) 20%, transparent)' }}\n />\n <circle\n cx={size / 2}\n cy={size / 2}\n r={radius}\n fill=\"none\"\n stroke={color}\n strokeWidth={strokeWidth}\n strokeDasharray={`${progress} ${circumference - progress}`}\n strokeLinecap=\"round\"\n />\n </svg>\n <span\n className=\"absolute inset-0 flex items-center justify-center text-lg font-bold\"\n style={{ color }}\n >\n {score}\n </span>\n </div>\n );\n}\n\nexport default function SpeedComparisonSection({ data }: SpeedComparisonSectionProps) {\n const { platformLabel, benchmarks, actual } = data;\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 Why This Matters\n </h2>\n <p\n className=\"text-lg mb-4 max-w-3xl\"\n style={{ color: 'var(--sk-text-secondary, #a1a1aa)' }}\n >\n Lighthouse measures real-world site quality across performance, SEO, accessibility, and best practices.\n These scores directly impact search rankings, conversion rates, and user experience.\n </p>\n <p\n className=\"text-base mb-12 max-w-3xl\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n >\n Here&apos;s how this Next.js build compares to the average {platformLabel} site.\n </p>\n </ScrollReveal>\n\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-6\">\n {CATEGORIES.map((cat, ci) => {\n const ours = actual[cat.key];\n const theirs = benchmarks[cat.key];\n const diff = ours - theirs;\n\n return (\n <ScrollReveal key={cat.key} y={20} delay={ci * 0.1}>\n <GlassCard padding=\"lg\" hover={false} className=\"h-full\">\n <div className=\"flex items-start gap-5\">\n {/* Score rings side by side */}\n <div className=\"flex flex-col items-center gap-3 shrink-0\">\n <ScoreRing score={ours} />\n <span\n className=\"text-[10px] font-medium uppercase tracking-wider\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n >\n Next.js\n </span>\n </div>\n <div className=\"flex flex-col items-center gap-3 shrink-0\">\n <ScoreRing score={theirs} />\n <span\n className=\"text-[10px] font-medium uppercase tracking-wider\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n >\n {platformLabel}\n </span>\n </div>\n\n {/* Details */}\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2 mb-2\">\n <h3\n className=\"text-base font-semibold\"\n style={{ color: 'var(--sk-text-primary, #ffffff)' }}\n >\n {cat.label}\n </h3>\n {diff > 0 && (\n <span\n className=\"text-xs font-bold px-2 py-0.5 rounded-full\"\n style={{\n background: 'color-mix(in srgb, #10b981 15%, transparent)',\n color: '#10b981',\n }}\n >\n +{diff} pts\n </span>\n )}\n </div>\n <p\n className=\"text-sm leading-relaxed\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n >\n {cat.why}\n </p>\n </div>\n </div>\n </GlassCard>\n </ScrollReveal>\n );\n })}\n </div>\n </div>\n </section>\n );\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sonordev/agency-site-kit",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "Premium portfolio showcase components for Sonor-powered agency websites",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/portfolio/components/MetricsProvenance.tsx"],"names":["jsxs","jsx"],"mappings":"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","file":"chunk-4N7ENJ7L.cjs","sourcesContent":["/**\n * MetricsProvenance — \"these numbers are real\" strip for case studies.\n *\n * Server component, zero JS. Renders the live metric deltas the platform\n * computes from GSC/analytics baselines (MetricsDelta[]) plus the refresh\n * timestamp — the trust signal a static agency portfolio can't fake.\n *\n * `direction` semantics come from the API: 'up' ALWAYS means \"improved\"\n * (lower-is-better metrics like Avg Position/LCP are inverted server-side\n * in metrics-baseline.service), so up renders positive/green.\n */\n\nimport React from 'react';\nimport type { MetricsDelta } from '../../types';\n\ninterface MetricsProvenanceProps {\n deltas: MetricsDelta[];\n /** ISO timestamp of the last live-metrics refresh. */\n refreshedAt?: string | null;\n}\n\nconst DIRECTION_STYLE: Record<MetricsDelta['direction'], { color: string; arrow: string }> = {\n up: { color: '#10b981', arrow: '▲' },\n down: { color: '#ef4444', arrow: '▼' },\n flat: { color: 'var(--sk-text-tertiary, #71717a)', arrow: '—' },\n};\n\nfunction formatValue(v: number): string {\n return Number.isInteger(v)\n ? v.toLocaleString('en-US')\n : v.toLocaleString('en-US', { maximumFractionDigits: 2 });\n}\n\nfunction formatDate(iso: string): string {\n try {\n return new Intl.DateTimeFormat('en-US', {\n year: 'numeric',\n month: 'long',\n day: 'numeric',\n }).format(new Date(iso));\n } catch {\n return iso;\n }\n}\n\nexport default function MetricsProvenance({ deltas, refreshedAt }: MetricsProvenanceProps) {\n if (!deltas.length) return null;\n\n return (\n <div\n className=\"mt-10 rounded-2xl overflow-hidden\"\n style={{\n background: 'var(--sk-surface, rgba(255,255,255,0.05))',\n border: '1px solid var(--sk-border, rgba(255,255,255,0.1))',\n }}\n >\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3\">\n {deltas.map((d, i) => {\n const dir = DIRECTION_STYLE[d.direction] ?? DIRECTION_STYLE.flat;\n return (\n <div\n key={`${d.metric}-${i}`}\n className=\"flex flex-col gap-1 p-5\"\n style={{ borderBottom: '1px solid var(--sk-border, rgba(255,255,255,0.06))' }}\n >\n <span\n className=\"text-xs font-semibold uppercase tracking-wider\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n >\n {d.metric}\n </span>\n <span className=\"flex items-baseline gap-2\">\n <span\n className=\"text-lg font-bold\"\n style={{ color: 'var(--sk-text-primary, #ffffff)' }}\n >\n {formatValue(d.baseline)} → {formatValue(d.current)}\n </span>\n <span className=\"text-sm font-semibold\" style={{ color: dir.color }}>\n {dir.arrow} {Math.abs(d.deltaPercent)}%\n </span>\n </span>\n <span className=\"text-xs\" style={{ color: 'var(--sk-text-tertiary, #71717a)' }}>\n {d.timespan}\n </span>\n </div>\n );\n })}\n </div>\n\n <div\n className=\"flex items-center gap-2 px-5 py-3\"\n style={{\n background: 'color-mix(in srgb, #10b981 6%, transparent)',\n color: 'var(--sk-text-secondary, #a1a1aa)',\n }}\n >\n <span\n className=\"inline-block w-2 h-2 rounded-full shrink-0\"\n style={{ background: '#10b981' }}\n />\n <span className=\"text-xs font-medium\">\n Verified from live analytics\n {refreshedAt ? ` · Refreshed ${formatDate(refreshedAt)}` : ''}\n </span>\n </div>\n </div>\n );\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/portfolio/components/PortfolioPage.tsx","../src/portfolio/components/PortfolioSchema.tsx","../src/portfolio/components/PortfolioCard.tsx","../src/portfolio/components/PortfolioGrid.tsx","../src/portfolio/components/PortfolioIndex.tsx","../src/portfolio/components/PortfolioSkeletons.tsx","../src/types.ts"],"names":["dynamic","jsx","HeroSection","ChallengesSection","ResultsSection","TechStackSection","ServicesSection","TestimonialSection","SpeedComparisonSection","sanitizeSiteArchitectureData","DesignSystemSection","DetailsSection","jsxs","PortfolioAnalytics","Link","GlassCard","Image","ScrollReveal","getPortfolioCategories","slugifyCategory","getPortfolioItems"],"mappings":"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","file":"chunk-L7E3UHQC.cjs","sourcesContent":["/**\n * PortfolioPage — Server component that renders a full portfolio case study.\n *\n * Maps each section in item.sections to the correct renderer component.\n * All sections below the hero are loaded lazily via next/dynamic.\n *\n * NOTE: This file is intentionally NOT a client component — it is designed\n * to be used as an RSC in a Next.js app directory page.\n *\n * Sections MUST be imported via `@sonordev/agency-site-kit/portfolio/sections/*`\n * (self-referencing subpaths, external in tsup's server build), never via\n * relative paths: a relative import would bundle the client sections into\n * this server chunk, the 'use client' boundary would be lost, and the whole\n * PortfolioItemFull would serialize into the RSC flight payload again.\n *\n * Whether a section is a server or client component is decided by the\n * section file itself (its 'use client' directive; tsup routes it to the\n * matching build). Server sections are imported statically — they ship zero\n * client JS, so there is nothing to lazy-load. Client sections load through\n * next/dynamic so each becomes its own browser chunk, fetched only when the\n * case study actually renders that section type.\n */\n\nimport React from 'react';\nimport dynamic from 'next/dynamic';\nimport type {\n PortfolioItemFull,\n PortfolioSection,\n PortfolioHeroData,\n PortfolioChallengesData,\n PortfolioStrategyData,\n PortfolioResultsData,\n PortfolioTechStackData,\n PortfolioServicesData,\n PortfolioTestimonialData,\n PortfolioGalleryData,\n PortfolioVideoData,\n PortfolioTeamData,\n PortfolioFeatureSpotlightData,\n PortfolioBeforeAfterData,\n PortfolioMetricsTimelineData,\n PortfolioConversionFunnelData,\n PortfolioDetailsData,\n PortfolioCTAData,\n} from '../../types';\n\n// Hero is loaded eagerly (above the fold, client — it animates)\nimport HeroSection from '@sonordev/agency-site-kit/portfolio/sections/HeroSection';\n\n// Server sections — no 'use client' in their files, so they render on the\n// server and ship ZERO component JS to the browser. Static imports: there is\n// no client chunk to defer.\nimport ChallengesSection from '@sonordev/agency-site-kit/portfolio/sections/ChallengesSection';\nimport ResultsSection from '@sonordev/agency-site-kit/portfolio/sections/ResultsSection';\nimport TechStackSection from '@sonordev/agency-site-kit/portfolio/sections/TechStackSection';\nimport ServicesSection from '@sonordev/agency-site-kit/portfolio/sections/ServicesSection';\nimport TestimonialSection from '@sonordev/agency-site-kit/portfolio/sections/TestimonialSection';\nimport SpeedComparisonSection from '@sonordev/agency-site-kit/portfolio/sections/SpeedComparisonSection';\nimport DesignSystemSection from '@sonordev/agency-site-kit/portfolio/sections/DesignSystemSection';\nimport DetailsSection from '@sonordev/agency-site-kit/portfolio/sections/DetailsSection';\n\n// Client sections — animated/interactive; lazily loaded so each becomes its\n// own browser chunk, fetched only when rendered.\nconst StrategySection = dynamic(() => import('@sonordev/agency-site-kit/portfolio/sections/StrategySection'), { ssr: true });\nconst GallerySection = dynamic(() => import('@sonordev/agency-site-kit/portfolio/sections/GallerySection'), { ssr: true });\nconst VideoSection = dynamic(() => import('@sonordev/agency-site-kit/portfolio/sections/VideoSection'), { ssr: true });\nconst TeamSection = dynamic(() => import('@sonordev/agency-site-kit/portfolio/sections/TeamSection'), { ssr: true });\nconst FeatureSpotlightSection = dynamic(() => import('@sonordev/agency-site-kit/portfolio/sections/FeatureSpotlightSection'), { ssr: true });\nconst BeforeAfterSection = dynamic(() => import('@sonordev/agency-site-kit/portfolio/sections/BeforeAfterSection'), { ssr: true });\nconst MetricsTimelineSection = dynamic(() => import('@sonordev/agency-site-kit/portfolio/sections/MetricsTimelineSection'), { ssr: true });\nconst ConversionFunnelSection = dynamic(() => import('@sonordev/agency-site-kit/portfolio/sections/ConversionFunnelSection'), { ssr: true });\nconst PerformanceSection = dynamic(() => import('@sonordev/agency-site-kit/portfolio/sections/PerformanceSection'), { ssr: true });\nconst SiteArchitectureSection = dynamic(() => import('@sonordev/agency-site-kit/portfolio/sections/SiteArchitectureSection'), { ssr: true });\nconst CTASection = dynamic(() => import('@sonordev/agency-site-kit/portfolio/sections/CTASection'), { ssr: true });\n\ninterface PortfolioPageProps {\n item: PortfolioItemFull;\n}\n\n// Defense-in-depth only — the enforcing sanitize pass runs in\n// getPortfolioItem (portfolio/server.ts), the last genuinely-server-side hop.\n// See src/portfolio/sanitize.ts for the single source of truth and the story.\nimport { sanitizeSiteArchitectureData } from '../sanitize';\n\n// Engagement-tracking island (client) — crosses the boundary via subpath.\nimport PortfolioAnalytics from '@sonordev/agency-site-kit/portfolio/components/PortfolioAnalytics';\n\nfunction renderSection(section: PortfolioSection, item: PortfolioItemFull, index: number) {\n const key = `${section.sectionType}-${index}`;\n\n switch (section.sectionType) {\n case 'portfolioHero':\n return (\n <HeroSection\n key={key}\n data={section.data as PortfolioHeroData}\n screenshots={item.hero_screenshots ?? undefined}\n liveUrl={item.live_url ?? undefined}\n />\n );\n case 'portfolioChallenges':\n return <ChallengesSection key={key} data={section.data as PortfolioChallengesData} />;\n case 'portfolioStrategy':\n return <StrategySection key={key} data={section.data as PortfolioStrategyData} />;\n case 'portfolioResults':\n return (\n <ResultsSection\n key={key}\n data={section.data as PortfolioResultsData}\n metricsDelta={item.metricsDelta}\n metricsRefreshedAt={item.metrics_last_refreshed_at}\n />\n );\n case 'portfolioTechStack':\n return <TechStackSection key={key} data={section.data as PortfolioTechStackData} />;\n case 'portfolioServices':\n return <ServicesSection key={key} data={section.data as PortfolioServicesData} />;\n case 'portfolioTestimonial':\n return <TestimonialSection key={key} data={section.data as PortfolioTestimonialData} />;\n case 'portfolioGallery':\n return <GallerySection key={key} data={section.data as PortfolioGalleryData} />;\n case 'portfolioVideo':\n return <VideoSection key={key} data={section.data as PortfolioVideoData} />;\n case 'portfolioTeam':\n return <TeamSection key={key} data={section.data as PortfolioTeamData} />;\n case 'portfolioFeatureSpotlight':\n return <FeatureSpotlightSection key={key} data={section.data as PortfolioFeatureSpotlightData} />;\n case 'portfolioBeforeAfter':\n return <BeforeAfterSection key={key} data={section.data as PortfolioBeforeAfterData} />;\n case 'portfolioMetricsTimeline':\n return <MetricsTimelineSection key={key} data={section.data as PortfolioMetricsTimelineData} />;\n case 'portfolioConversionFunnel':\n return <ConversionFunnelSection key={key} data={section.data as PortfolioConversionFunnelData} />;\n case 'portfolioPerformance':\n return <PerformanceSection key={key} data={section.data as any} />;\n case 'portfolioSpeedComparison':\n return <SpeedComparisonSection key={key} data={section.data as any} />;\n case 'portfolioSiteArchitecture':\n return <SiteArchitectureSection key={key} data={sanitizeSiteArchitectureData(section.data) as any} />;\n case 'portfolioDesignSystem':\n return <DesignSystemSection key={key} data={section.data as any} />;\n case 'portfolioDetails':\n return <DetailsSection key={key} data={section.data as PortfolioDetailsData} />;\n case 'portfolioCTA':\n return <CTASection key={key} data={section.data as PortfolioCTAData} />;\n case 'portfolioSeo':\n // SEO section is metadata-only; nothing to render visually.\n return null;\n default:\n return null;\n }\n}\n\n// Canonical display order — ensures consistent rendering regardless of API\n// order. Keys must match PortfolioSectionType exactly (note 'portfolioCTA' —\n// a lowercased 'portfolioCta' key silently fell through to the ?? 99 fallback\n// for years). Unknown/future types sort last via the fallback.\nconst SECTION_ORDER: Record<string, number> = {\n portfolioHero: 0,\n portfolioChallenges: 1,\n portfolioStrategy: 2,\n portfolioResults: 3,\n portfolioPerformance: 4,\n portfolioSpeedComparison: 5,\n portfolioSiteArchitecture: 6,\n portfolioTechStack: 7,\n portfolioDesignSystem: 8,\n portfolioServices: 9,\n portfolioBeforeAfter: 10,\n portfolioTestimonial: 11,\n portfolioMetricsTimeline: 12,\n portfolioConversionFunnel: 13,\n portfolioDetails: 14,\n portfolioSeo: 15,\n portfolioCTA: 16,\n};\n\nexport default function PortfolioPage({ item }: PortfolioPageProps) {\n // Section order: once an agency has deliberately reordered in the\n // dashboard (item.sectionsOrdered), the array order IS the narrative —\n // respect it, with the hero pinned first (it's the LCP shell). Items\n // never reordered keep the canonical order below, so nothing shifts\n // out from under existing case studies.\n const sortedSections = item.sectionsOrdered\n ? [...item.sections].sort(\n (a, b) =>\n (a.sectionType === 'portfolioHero' ? 0 : 1) -\n (b.sectionType === 'portfolioHero' ? 0 : 1),\n )\n : [...item.sections].sort(\n (a, b) => (SECTION_ORDER[a.sectionType] ?? 99) - (SECTION_ORDER[b.sectionType] ?? 99)\n );\n\n return (\n <article className=\"flex w-full min-w-0 max-w-full flex-col overflow-x-clip\">\n {sortedSections.map((section, index) => {\n const rendered = renderSection(section, item, index);\n if (!rendered) return null;\n return (\n <div\n key={`wrapper-${section.sectionType}-${index}`}\n className=\"w-full\"\n data-portfolio-section={section.sectionType}\n >\n {rendered}\n </div>\n );\n })}\n {/* Engagement tracking — childless idle-deferred sibling, never a wrapper */}\n <PortfolioAnalytics slug={item.slug} />\n </article>\n );\n}\n","/**\n * PortfolioSchema — JSON-LD structured data for a case study page.\n *\n * Server component: renders a single <script type=\"application/ld+json\">\n * describing the case study as a schema.org Article (there is no dedicated\n * CaseStudy type; Article is what Google documents for editorial content).\n * Ships zero client JS and adds rich-result eligibility + entity clarity for\n * the agency's portfolio pages.\n *\n * Usage (in the case study page, next to <PortfolioPage />):\n *\n * ```tsx\n * <PortfolioSchema\n * item={item}\n * url={`https://youragency.com/work/${item.slug}`}\n * publisher={{ name: 'Your Agency', url: 'https://youragency.com' }}\n * />\n * ```\n *\n * Only reads fields from the already-sanitized PortfolioItemFull (see\n * getPortfolioItem / sanitize.ts) — never section internals.\n */\n\nimport React from 'react';\nimport type { PortfolioItemFull } from '../../types';\n\nexport interface PortfolioSchemaProps {\n item: PortfolioItemFull;\n /** Canonical URL of this case study page. */\n url: string;\n /** The agency, emitted as author + publisher Organization. */\n publisher?: {\n name: string;\n url?: string;\n /** Absolute URL to a logo image. */\n logo?: string;\n };\n}\n\n/** JSON-LD-safe serialization — escape `<` so `</script>` can never break out. */\nfunction jsonLd(value: unknown): string {\n return JSON.stringify(value).replace(/</g, '\\\\u003c');\n}\n\nexport default function PortfolioSchema({ item, url, publisher }: PortfolioSchemaProps) {\n const headline = item.seo?.metaTitle || item.title;\n const description = item.seo?.metaDescription || item.description;\n\n const keywords = [\n ...(item.seo?.keywords ?? []),\n item.category,\n ...item.services,\n ].filter(Boolean);\n\n const org = publisher\n ? {\n '@type': 'Organization',\n name: publisher.name,\n ...(publisher.url ? { url: publisher.url } : {}),\n ...(publisher.logo\n ? { logo: { '@type': 'ImageObject', url: publisher.logo } }\n : {}),\n }\n : undefined;\n\n const schema = {\n '@context': 'https://schema.org',\n '@type': 'Article',\n headline,\n ...(description ? { description } : {}),\n ...(item.hero_image ? { image: [item.hero_image] } : {}),\n url,\n mainEntityOfPage: { '@type': 'WebPage', '@id': url },\n ...(item.published_at ? { datePublished: item.published_at } : {}),\n ...(item.metrics_last_refreshed_at || item.published_at\n ? { dateModified: item.metrics_last_refreshed_at || item.published_at }\n : {}),\n ...(keywords.length ? { keywords: keywords.join(', ') } : {}),\n ...(org ? { author: org, publisher: org } : {}),\n ...(item.details?.industry ? { about: item.details.industry } : {}),\n };\n\n return (\n <script\n type=\"application/ld+json\"\n dangerouslySetInnerHTML={{ __html: jsonLd(schema) }}\n />\n );\n}\n","// Shared component — no directive on purpose: hook-free, so it renders\n// server-side (zero JS) from PortfolioIndex and stays usable from client\n// code via the /portfolio/client barrel.\nimport React from 'react';\nimport Image from 'next/image';\nimport Link from 'next/link';\nimport type { PortfolioItem } from '../../types';\nimport GlassCard from '@sonordev/agency-site-kit/portfolio/primitives/GlassCard';\n\ninterface PortfolioCardProps {\n item: PortfolioItem;\n className?: string;\n /** Base path for portfolio detail links (default: '/work') */\n basePath?: string;\n}\n\nexport default function PortfolioCard({ item, className = '', basePath = '/work' }: PortfolioCardProps) {\n // Use hero_screenshots desktop → hero_image → trifolio screenshot convention\n const thumbnailSrc =\n item.hero_screenshots?.desktop ||\n item.hero_image ||\n (item.project_id ? `/portfolio-previews/${item.slug}.png` : undefined);\n const kpis = Array.isArray(item.kpis) ? item.kpis : [];\n const services = Array.isArray(item.services) ? item.services : [];\n const kpiPreview = kpis.slice(0, 3);\n const servicePreview = services.slice(0, 3);\n\n return (\n <Link href={`${basePath}/${item.slug}`} className={`block group ${className}`}>\n <GlassCard padding=\"sm\" hover>\n <div className=\"flex flex-col gap-4\">\n {/* Thumbnail */}\n {thumbnailSrc && (\n <div\n className=\"relative w-full overflow-hidden rounded-xl\"\n style={{ aspectRatio: '16/10' }}\n >\n <Image\n src={thumbnailSrc}\n alt={item.hero_image_alt || item.title}\n fill\n sizes=\"(max-width: 768px) 100vw, (max-width: 1024px) 50vw, 33vw\"\n className=\"object-cover object-top transition-transform duration-500 group-hover:scale-105\"\n />\n {/* Category badge */}\n <span\n className=\"absolute top-3 left-3 px-3 py-1 rounded-full text-[11px] uppercase tracking-wider font-semibold\"\n style={{\n background: 'color-mix(in srgb, var(--sk-primary, #6366f1) 80%, transparent)',\n color: '#ffffff',\n backdropFilter: 'blur(8px)',\n }}\n >\n {item.category?.replace(/[-_]/g, ' ').replace(/\\b\\w/g, (c: string) => c.toUpperCase())}\n </span>\n </div>\n )}\n\n {/* Text content */}\n <div className=\"flex flex-col gap-2 px-2 pb-2\">\n <h3\n className=\"text-lg font-semibold line-clamp-1\"\n style={{ color: 'var(--sk-text-primary, #ffffff)' }}\n >\n {item.title}\n </h3>\n\n {item.subtitle && (\n <p\n className=\"text-sm line-clamp-2\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n >\n {item.subtitle}\n </p>\n )}\n\n {/* KPI preview */}\n {kpiPreview.length > 0 && (\n <div className=\"flex items-center gap-4 mt-1\">\n {kpiPreview.map((kpi, i) => (\n <div key={i} className=\"flex flex-col\">\n <span\n className=\"text-base font-bold\"\n style={{ color: 'var(--sk-primary, #6366f1)' }}\n >\n {kpi.prefix || ''}\n {kpi.value.toLocaleString()}\n {kpi.suffix}\n </span>\n <span\n className=\"text-[10px] uppercase tracking-wider font-medium\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n >\n {kpi.label}\n </span>\n </div>\n ))}\n </div>\n )}\n\n {/* Services tags */}\n {servicePreview.length > 0 && (\n <div className=\"flex flex-wrap gap-1.5 mt-2\">\n {servicePreview.map((service) => (\n <span\n key={service}\n className=\"px-2.5 py-0.5 rounded-full text-[11px] font-medium\"\n style={{\n background: 'var(--sk-surface, rgba(255,255,255,0.05))',\n color: 'var(--sk-text-secondary, #a1a1aa)',\n border: '1px solid var(--sk-border, rgba(255,255,255,0.1))',\n }}\n >\n {service}\n </span>\n ))}\n {services.length > 3 && (\n <span\n className=\"px-2.5 py-0.5 rounded-full text-[11px] font-medium\"\n style={{\n background: 'var(--sk-surface, rgba(255,255,255,0.05))',\n color: 'var(--sk-text-tertiary, #71717a)',\n }}\n >\n +{item.services.length - 3}\n </span>\n )}\n </div>\n )}\n </div>\n </div>\n </GlassCard>\n </Link>\n );\n}\n","// Shared component — no directive on purpose: hook-free, so it renders\n// server-side (zero JS) from PortfolioIndex and stays usable from client\n// code via the /portfolio/client barrel.\nimport React from 'react';\nimport type { PortfolioItem } from '../../types';\nimport ScrollReveal from '@sonordev/agency-site-kit/portfolio/primitives/ScrollReveal';\nimport PortfolioCard from './PortfolioCard';\n\ninterface PortfolioGridProps {\n items: PortfolioItem[];\n className?: string;\n /** Base path for portfolio detail links (default: '/work') */\n basePath?: string;\n}\n\nexport default function PortfolioGrid({ items, className = '', basePath = '/work' }: PortfolioGridProps) {\n return (\n <ScrollReveal stagger={0.1}>\n <div\n className={`grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 ${className}`}\n style={{\n gap: 'var(--sk-grid-gap, 24px)',\n }}\n >\n {items.map((item) => (\n <PortfolioCard key={item.id} item={item} basePath={basePath} />\n ))}\n </div>\n </ScrollReveal>\n );\n}\n","/**\n * PortfolioIndex — turnkey, server-rendered /work index.\n *\n * Fetches items + categories and renders: category nav (real links —\n * crawlable, no client state), the portfolio grid, pagination, and an\n * intentional empty state. Ships zero component JS.\n *\n * Category filtering is route-based for SEO:\n * /work → all items\n * /work/category/[category] → one category (slug via slugifyCategory)\n * Pagination via ?page= on either route.\n */\n\nimport React from 'react';\nimport {\n getPortfolioCategories,\n getPortfolioItems,\n slugifyCategory,\n} from '../server';\nimport PortfolioGrid from './PortfolioGrid';\n\nexport interface PortfolioIndexProps {\n /** Route prefix for case studies + category links. Defaults to \"/work\". */\n basePath?: string;\n /** Active category SLUG (from the [category] route param). */\n category?: string | null;\n /** 1-based page number (from ?page=). */\n page?: number;\n /** Items per page. Defaults to 12. */\n pageSize?: number;\n /** Optional heading block rendered above the category nav. */\n heading?: React.ReactNode;\n /** Override the empty state. */\n emptyState?: React.ReactNode;\n /** Hide the category nav (e.g. single-category portfolios). */\n showCategories?: boolean;\n className?: string;\n}\n\nfunction CategoryLink({\n href,\n label,\n active,\n}: {\n href: string;\n label: string;\n active: boolean;\n}) {\n return (\n <a\n href={href}\n aria-current={active ? 'page' : undefined}\n className=\"px-4 py-1.5 rounded-full text-sm font-medium transition-colors\"\n style={{\n background: active\n ? 'color-mix(in srgb, var(--sk-primary, #6366f1) 15%, transparent)'\n : 'var(--sk-surface, rgba(255,255,255,0.05))',\n color: active ? 'var(--sk-primary, #6366f1)' : 'var(--sk-text-secondary, #a1a1aa)',\n border: `1px solid ${\n active\n ? 'color-mix(in srgb, var(--sk-primary, #6366f1) 25%, transparent)'\n : 'var(--sk-border, rgba(255,255,255,0.1))'\n }`,\n }}\n >\n {label}\n </a>\n );\n}\n\nfunction PageLink({ href, label, disabled }: { href: string; label: string; disabled: boolean }) {\n if (disabled) {\n return (\n <span\n aria-disabled=\"true\"\n className=\"px-4 py-2 rounded-xl text-sm font-medium opacity-40\"\n style={{\n background: 'var(--sk-surface, rgba(255,255,255,0.05))',\n color: 'var(--sk-text-tertiary, #71717a)',\n border: '1px solid var(--sk-border, rgba(255,255,255,0.1))',\n }}\n >\n {label}\n </span>\n );\n }\n return (\n <a\n href={href}\n className=\"px-4 py-2 rounded-xl text-sm font-medium transition-colors\"\n style={{\n background: 'var(--sk-surface, rgba(255,255,255,0.05))',\n color: 'var(--sk-text-secondary, #a1a1aa)',\n border: '1px solid var(--sk-border, rgba(255,255,255,0.1))',\n }}\n >\n {label}\n </a>\n );\n}\n\nexport default async function PortfolioIndex({\n basePath = '/work',\n category = null,\n page = 1,\n pageSize = 12,\n heading,\n emptyState,\n showCategories = true,\n className = '',\n}: PortfolioIndexProps) {\n const currentPage = Math.max(1, Math.floor(page));\n const categories = await getPortfolioCategories();\n\n // Route param is a slug; the API filter wants the original category value.\n const activeCategory = category\n ? categories.find((c) => slugifyCategory(c) === category) ?? null\n : null;\n\n const { items, total } = await getPortfolioItems({\n category: activeCategory ?? undefined,\n limit: pageSize,\n offset: (currentPage - 1) * pageSize,\n });\n\n const totalPages = Math.max(1, Math.ceil(total / pageSize));\n const pageHref = (p: number) => {\n const base = activeCategory\n ? `${basePath}/category/${slugifyCategory(activeCategory)}`\n : basePath;\n return p <= 1 ? base : `${base}?page=${p}`;\n };\n\n return (\n <section className={`w-full ${className}`}>\n <div className=\"max-w-7xl mx-auto px-6\">\n {heading}\n\n {/* Category nav — real links, crawlable */}\n {showCategories && categories.length > 1 && (\n <nav aria-label=\"Portfolio categories\" className=\"flex flex-wrap gap-2 mb-10\">\n <CategoryLink href={basePath} label=\"All\" active={!activeCategory} />\n {categories.map((c) => (\n <CategoryLink\n key={c}\n href={`${basePath}/category/${slugifyCategory(c)}`}\n label={c}\n active={activeCategory === c}\n />\n ))}\n </nav>\n )}\n\n {items.length > 0 ? (\n <PortfolioGrid items={items} basePath={basePath} />\n ) : (\n emptyState ?? (\n <div\n className=\"flex flex-col items-center gap-3 py-24 rounded-2xl text-center\"\n style={{\n background: 'var(--sk-surface, rgba(255,255,255,0.03))',\n border: '1px dashed var(--sk-border, rgba(255,255,255,0.1))',\n }}\n >\n <span\n className=\"text-lg font-semibold\"\n style={{ color: 'var(--sk-text-primary, #ffffff)' }}\n >\n Case studies coming soon\n </span>\n <span className=\"text-sm\" style={{ color: 'var(--sk-text-tertiary, #71717a)' }}>\n {activeCategory\n ? `Nothing published in ${activeCategory} yet.`\n : \"We're writing up our latest work right now.\"}\n </span>\n </div>\n )\n )}\n\n {/* Pagination */}\n {totalPages > 1 && (\n <nav\n aria-label=\"Portfolio pages\"\n className=\"flex items-center justify-center gap-3 mt-12\"\n >\n <PageLink\n href={pageHref(currentPage - 1)}\n label=\"← Previous\"\n disabled={currentPage <= 1}\n />\n <span className=\"text-sm\" style={{ color: 'var(--sk-text-tertiary, #71717a)' }}>\n Page {currentPage} of {totalPages}\n </span>\n <PageLink\n href={pageHref(currentPage + 1)}\n label=\"Next →\"\n disabled={currentPage >= totalPages}\n />\n </nav>\n )}\n </div>\n </section>\n );\n}\n","/**\n * Loading skeletons for portfolio routes — pure CSS pulse, server-safe,\n * zero JS. Drop into `loading.tsx`:\n *\n * ```tsx\n * export { PortfolioIndexSkeleton as default } from '@sonordev/agency-site-kit/portfolio';\n * ```\n */\n\nimport React from 'react';\n\nconst PULSE_CSS = `\n.sk-skeleton{animation:sk-skeleton-pulse 1.6s ease-in-out infinite;background:var(--sk-surface,rgba(255,255,255,0.06));border:1px solid var(--sk-border,rgba(255,255,255,0.08));border-radius:16px}\n@keyframes sk-skeleton-pulse{0%,100%{opacity:.55}50%{opacity:1}}\n@media (prefers-reduced-motion:reduce){.sk-skeleton{animation:none}}\n`;\n\nfunction SkeletonStyles() {\n return (\n <style href=\"sk-skeletons\" precedence=\"default\">\n {PULSE_CSS}\n </style>\n );\n}\n\n/** Grid of pulsing card placeholders for the /work index. */\nexport function PortfolioIndexSkeleton({ cards = 6 }: { cards?: number }) {\n return (\n <section className=\"w-full py-16\">\n <SkeletonStyles />\n <div className=\"max-w-7xl mx-auto px-6\">\n <div className=\"sk-skeleton h-9 w-56 mb-4\" />\n <div className=\"sk-skeleton h-5 w-80 mb-10\" />\n <div className=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6\">\n {Array.from({ length: cards }).map((_, i) => (\n <div key={i} className=\"flex flex-col gap-3\">\n <div className=\"sk-skeleton w-full\" style={{ aspectRatio: '16/10' }} />\n <div className=\"sk-skeleton h-5 w-3/4\" />\n <div className=\"sk-skeleton h-4 w-1/2\" />\n </div>\n ))}\n </div>\n </div>\n </section>\n );\n}\n\n/** Hero-shaped placeholder for /work/[slug] case study pages. */\nexport function PortfolioPageSkeleton() {\n return (\n <article className=\"w-full\">\n <SkeletonStyles />\n <div className=\"max-w-7xl mx-auto px-6 py-24\">\n <div className=\"grid grid-cols-1 lg:grid-cols-2 gap-12 items-start\">\n <div className=\"flex flex-col gap-5\">\n <div className=\"sk-skeleton h-7 w-32\" style={{ borderRadius: '9999px' }} />\n <div className=\"sk-skeleton h-12 w-full\" />\n <div className=\"sk-skeleton h-12 w-4/5\" />\n <div className=\"sk-skeleton h-5 w-full\" />\n <div className=\"sk-skeleton h-5 w-2/3\" />\n </div>\n <div className=\"sk-skeleton w-full\" style={{ aspectRatio: '11/5' }} />\n </div>\n </div>\n </article>\n );\n}\n","/**\n * @sonordev/agency-site-kit — Type definitions\n *\n * These types mirror the Portal API's cms/types.ts definitions for portfolio\n * content. Keep them in sync when modifying the API response shapes.\n */\n\n// ---------------------------------------------------------------------------\n// Brand Configuration\n// ---------------------------------------------------------------------------\n\nexport interface BrandConfig {\n /** Primary brand color (hex) */\n primary: string;\n /** Secondary/accent color (hex) */\n secondary: string;\n /** Background color (hex) */\n background: string;\n /** Elevated background — cards, modals (hex) */\n backgroundElevated: string;\n /** Surface color — interactive containers (hex) */\n surface: string;\n /** Surface hover state (hex) */\n surfaceHover: string;\n /** Surface border color (hex) */\n surfaceBorder: string;\n /** Primary text color (hex) */\n textPrimary: string;\n /** Secondary text color (hex) */\n textSecondary: string;\n /** Tertiary/muted text color (hex) */\n textTertiary: string;\n /** Heading font family */\n fontHeading: string;\n /** Body font family */\n fontBody: string;\n /** Logo URL (absolute or relative) */\n logoUrl?: string;\n /** Logo alt text */\n logoAlt?: string;\n /** Dark mode overrides — partial, merged with base values */\n darkMode?: Partial<Omit<BrandConfig, 'fontHeading' | 'fontBody' | 'logoUrl' | 'logoAlt' | 'darkMode'>>;\n /** Border radius tokens */\n radius?: {\n sm: string;\n md: string;\n lg: string;\n };\n}\n\n// ---------------------------------------------------------------------------\n// Sanity Image Reference\n// ---------------------------------------------------------------------------\n\nexport interface SanityImageRef {\n _type: 'image';\n asset: { _type: 'reference'; _ref: string };\n hotspot?: { x: number; y: number; height: number; width: number };\n crop?: { top: number; bottom: number; left: number; right: number };\n alt?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Metrics\n// ---------------------------------------------------------------------------\n\nexport type MetricSource = 'measured' | 'estimated';\n\nexport interface PortfolioKPI {\n label: string;\n value: number;\n suffix: string;\n prefix?: string;\n description: string;\n source: MetricSource;\n}\n\nexport interface MetricsDelta {\n metric: string;\n baseline: number;\n current: number;\n delta: number;\n deltaPercent: number;\n direction: 'up' | 'down' | 'flat';\n timespan: string;\n}\n\nexport interface MetricsSnapshot {\n seo: {\n clicks_28d?: number;\n impressions_28d?: number;\n avg_position?: number;\n ctr?: number;\n };\n cwv: {\n lcp_ms?: number;\n inp_ms?: number;\n cls?: number;\n performance_score?: number;\n };\n analytics: {\n sessions_28d?: number;\n page_views_28d?: number;\n conversion_rate?: number;\n bounce_rate?: number;\n };\n pages: {\n total_indexed?: number;\n total_crawled?: number;\n total_pages?: number;\n };\n}\n\n// ---------------------------------------------------------------------------\n// Portfolio Section Data Types\n// ---------------------------------------------------------------------------\n\nexport interface PortfolioHeroData {\n headline: string;\n subheadline: string;\n description: string;\n category: string;\n services: string[];\n liveUrl?: string;\n screenshots: {\n desktop?: string;\n tablet?: string;\n mobile?: string;\n };\n kpis: PortfolioKPI[];\n}\n\nexport interface PortfolioChallengeItem {\n title: string;\n description: string;\n solution: string;\n result?: string;\n icon?: string;\n}\n\nexport interface PortfolioChallengesData {\n items: PortfolioChallengeItem[];\n}\n\nexport interface PortfolioStrategyPhase {\n number: number;\n title: string;\n description: string;\n deliverables?: string[];\n icon?: string;\n timeline?: string;\n}\n\nexport interface PortfolioStrategyData {\n phases: PortfolioStrategyPhase[];\n}\n\nexport interface PortfolioResultItem {\n title: string;\n description: string;\n metric?: {\n value: number;\n suffix: string;\n prefix?: string;\n };\n source: MetricSource;\n icon?: string;\n}\n\nexport interface PortfolioResultsData {\n items: PortfolioResultItem[];\n}\n\nexport interface PortfolioTechItem {\n name: string;\n category: string;\n icon?: string;\n description?: string;\n}\n\nexport interface PortfolioTechStackData {\n technologies: PortfolioTechItem[];\n}\n\nexport interface PortfolioServiceItem {\n title: string;\n description: string;\n features: string[];\n icon?: string;\n}\n\nexport interface PortfolioServicesData {\n items: PortfolioServiceItem[];\n}\n\nexport interface PortfolioTestimonialData {\n quote: string;\n author: string;\n title: string;\n company: string;\n avatar?: SanityImageRef;\n rating?: number;\n}\n\nexport interface PortfolioGalleryImage {\n image: SanityImageRef;\n caption?: string;\n type?: 'screenshot' | 'before' | 'after' | 'design';\n}\n\nexport interface PortfolioGalleryData {\n images: PortfolioGalleryImage[];\n layout?: 'grid' | 'masonry';\n}\n\nexport interface PortfolioVideoData {\n url?: string;\n embedUrl?: string;\n thumbnail: SanityImageRef;\n title: string;\n duration?: string;\n platform?: 'youtube' | 'vimeo' | 'custom';\n}\n\nexport interface PortfolioTeamMember {\n name: string;\n role: string;\n avatar?: SanityImageRef;\n}\n\nexport interface PortfolioTeamData {\n members: PortfolioTeamMember[];\n}\n\nexport interface PortfolioAnnotation {\n x: number;\n y: number;\n label: string;\n description: string;\n icon?: string;\n}\n\nexport interface PortfolioFeatureSpotlightData {\n image: SanityImageRef;\n title: string;\n description: string;\n layout: 'left' | 'right' | 'full';\n annotations: PortfolioAnnotation[];\n}\n\nexport interface PortfolioBeforeAfterData {\n before: SanityImageRef;\n after: SanityImageRef;\n title?: string;\n description?: string;\n defaultPosition?: number;\n}\n\nexport interface PortfolioMetricsDataPoint {\n month: string;\n label: string;\n metrics: {\n traffic?: number;\n conversions?: number;\n revenue?: number;\n rankings?: number;\n };\n}\n\nexport interface PortfolioMetricsTimelineData {\n dataPoints: PortfolioMetricsDataPoint[];\n baselineDate: string;\n title?: string;\n description?: string;\n}\n\nexport interface PortfolioFunnelStage {\n label: string;\n value: number;\n icon?: string;\n color?: string;\n}\n\nexport interface PortfolioConversionFunnelData {\n stages: PortfolioFunnelStage[];\n title?: string;\n description?: string;\n}\n\nexport interface PortfolioDetailsData {\n industry: string;\n location?: string;\n website?: string;\n timeline?: string;\n launchDate?: string;\n budgetRange?: string;\n}\n\nexport interface PortfolioSeoData {\n metaTitle: string;\n metaDescription: string;\n keywords: string[];\n ogImage?: SanityImageRef;\n}\n\nexport interface PortfolioCTAData {\n headline: string;\n description: string;\n buttonText: string;\n buttonUrl: string;\n style?: 'primary' | 'glass';\n}\n\n// ---------------------------------------------------------------------------\n// Portfolio Sections\n// ---------------------------------------------------------------------------\n\nexport const PORTFOLIO_SECTION_TYPES = [\n 'portfolioHero',\n 'portfolioChallenges',\n 'portfolioStrategy',\n 'portfolioResults',\n 'portfolioTechStack',\n 'portfolioServices',\n 'portfolioTestimonial',\n 'portfolioGallery',\n 'portfolioVideo',\n 'portfolioTeam',\n 'portfolioFeatureSpotlight',\n 'portfolioBeforeAfter',\n 'portfolioMetricsTimeline',\n 'portfolioConversionFunnel',\n 'portfolioPerformance',\n 'portfolioSpeedComparison',\n 'portfolioSiteArchitecture',\n 'portfolioDesignSystem',\n 'portfolioDetails',\n 'portfolioSeo',\n 'portfolioCTA',\n] as const;\n\nexport type PortfolioSectionType = (typeof PORTFOLIO_SECTION_TYPES)[number];\n\nexport type PortfolioSectionData =\n | PortfolioHeroData\n | PortfolioChallengesData\n | PortfolioStrategyData\n | PortfolioResultsData\n | PortfolioTechStackData\n | PortfolioServicesData\n | PortfolioTestimonialData\n | PortfolioGalleryData\n | PortfolioVideoData\n | PortfolioTeamData\n | PortfolioFeatureSpotlightData\n | PortfolioBeforeAfterData\n | PortfolioMetricsTimelineData\n | PortfolioConversionFunnelData\n | PortfolioDetailsData\n | PortfolioSeoData\n | PortfolioCTAData;\n\nexport interface PortfolioSectionDataMap {\n portfolioHero: PortfolioHeroData;\n portfolioChallenges: PortfolioChallengesData;\n portfolioStrategy: PortfolioStrategyData;\n portfolioResults: PortfolioResultsData;\n portfolioTechStack: PortfolioTechStackData;\n portfolioServices: PortfolioServicesData;\n portfolioTestimonial: PortfolioTestimonialData;\n portfolioGallery: PortfolioGalleryData;\n portfolioVideo: PortfolioVideoData;\n portfolioTeam: PortfolioTeamData;\n portfolioFeatureSpotlight: PortfolioFeatureSpotlightData;\n portfolioBeforeAfter: PortfolioBeforeAfterData;\n portfolioMetricsTimeline: PortfolioMetricsTimelineData;\n portfolioConversionFunnel: PortfolioConversionFunnelData;\n portfolioDetails: PortfolioDetailsData;\n portfolioSeo: PortfolioSeoData;\n portfolioCTA: PortfolioCTAData;\n}\n\nexport interface PortfolioSection<T extends PortfolioSectionType = PortfolioSectionType> {\n sectionType: T;\n displayName: string;\n data: T extends keyof PortfolioSectionDataMap ? PortfolioSectionDataMap[T] : PortfolioSectionData;\n}\n\n// ---------------------------------------------------------------------------\n// Portfolio Item (mirrors public API response)\n// ---------------------------------------------------------------------------\n\nexport interface PortfolioItem {\n id: string;\n slug: string;\n title: string;\n subtitle: string;\n category: string;\n services: string[];\n description: string;\n hero_image: string;\n hero_image_alt: string;\n live_url: string | null;\n kpis: PortfolioKPI[];\n details: PortfolioDetailsData | null;\n seo: PortfolioSeoData | null;\n featured: boolean;\n order?: number;\n published_at: string | null;\n /** Sonor project ID (present for items generated from a managed project) */\n project_id: string | null;\n hero_screenshots: {\n desktop?: string;\n tablet?: string;\n mobile?: string;\n } | null;\n baseline_metrics: MetricsSnapshot | null;\n current_metrics: MetricsSnapshot | null;\n metrics_last_refreshed_at: string | null;\n}\n\nexport interface PortfolioItemFull extends PortfolioItem {\n sections: PortfolioSection[];\n metricsDelta: MetricsDelta[];\n /**\n * True once the agency deliberately reordered sections in the dashboard.\n * PortfolioPage respects the sections array order when set (hero pinned\n * first); otherwise it applies the canonical order.\n */\n sectionsOrdered?: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// API Responses\n// ---------------------------------------------------------------------------\n\nexport interface PortfolioListResponse {\n items: PortfolioItem[];\n total: number;\n limit: number;\n offset: number;\n}\n\nexport interface PortfolioConfigResponse {\n brand: BrandConfig;\n orgName: string;\n orgSlug: string;\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/portfolio/components/MetricsProvenance.tsx"],"names":[],"mappings":";;;AAqBA,IAAM,eAAA,GAAuF;AAAA,EAC3F,EAAA,EAAI,EAAE,KAAA,EAAO,SAAA,EAAW,OAAO,QAAA,EAAI;AAAA,EACnC,IAAA,EAAM,EAAE,KAAA,EAAO,SAAA,EAAW,OAAO,QAAA,EAAI;AAAA,EACrC,IAAA,EAAM,EAAE,KAAA,EAAO,kCAAA,EAAoC,OAAO,QAAA;AAC5D,CAAA;AAEA,SAAS,YAAY,CAAA,EAAmB;AACtC,EAAA,OAAO,MAAA,CAAO,SAAA,CAAU,CAAC,CAAA,GACrB,EAAE,cAAA,CAAe,OAAO,CAAA,GACxB,CAAA,CAAE,cAAA,CAAe,OAAA,EAAS,EAAE,qBAAA,EAAuB,GAAG,CAAA;AAC5D;AAEA,SAAS,WAAW,GAAA,EAAqB;AACvC,EAAA,IAAI;AACF,IAAA,OAAO,IAAI,IAAA,CAAK,cAAA,CAAe,OAAA,EAAS;AAAA,MACtC,IAAA,EAAM,SAAA;AAAA,MACN,KAAA,EAAO,MAAA;AAAA,MACP,GAAA,EAAK;AAAA,KACN,CAAA,CAAE,MAAA,CAAO,IAAI,IAAA,CAAK,GAAG,CAAC,CAAA;AAAA,EACzB,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,GAAA;AAAA,EACT;AACF;AAEe,SAAR,iBAAA,CAAmC,EAAE,MAAA,EAAQ,WAAA,EAAY,EAA2B;AACzF,EAAA,IAAI,CAAC,MAAA,CAAO,MAAA,EAAQ,OAAO,IAAA;AAE3B,EAAA,uBACE,IAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,SAAA,EAAU,mCAAA;AAAA,MACV,KAAA,EAAO;AAAA,QACL,UAAA,EAAY,2CAAA;AAAA,QACZ,MAAA,EAAQ;AAAA,OACV;AAAA,MAEA,QAAA,EAAA;AAAA,wBAAA,GAAA,CAAC,SAAI,SAAA,EAAU,gDAAA,EACZ,iBAAO,GAAA,CAAI,CAAC,GAAG,CAAA,KAAM;AACpB,UAAA,MAAM,GAAA,GAAM,eAAA,CAAgB,CAAA,CAAE,SAAS,KAAK,eAAA,CAAgB,IAAA;AAC5D,UAAA,uBACE,IAAA;AAAA,YAAC,KAAA;AAAA,YAAA;AAAA,cAEC,SAAA,EAAU,yBAAA;AAAA,cACV,KAAA,EAAO,EAAE,YAAA,EAAc,oDAAA,EAAqD;AAAA,cAE5E,QAAA,EAAA;AAAA,gCAAA,GAAA;AAAA,kBAAC,MAAA;AAAA,kBAAA;AAAA,oBACC,SAAA,EAAU,gDAAA;AAAA,oBACV,KAAA,EAAO,EAAE,KAAA,EAAO,kCAAA,EAAmC;AAAA,oBAElD,QAAA,EAAA,CAAA,CAAE;AAAA;AAAA,iBACL;AAAA,gCACA,IAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,2BAAA,EACd,QAAA,EAAA;AAAA,kCAAA,IAAA;AAAA,oBAAC,MAAA;AAAA,oBAAA;AAAA,sBACC,SAAA,EAAU,mBAAA;AAAA,sBACV,KAAA,EAAO,EAAE,KAAA,EAAO,iCAAA,EAAkC;AAAA,sBAEjD,QAAA,EAAA;AAAA,wBAAA,WAAA,CAAY,EAAE,QAAQ,CAAA;AAAA,wBAAE,UAAA;AAAA,wBAAI,WAAA,CAAY,EAAE,OAAO;AAAA;AAAA;AAAA,mBACpD;AAAA,kCACA,IAAA,CAAC,UAAK,SAAA,EAAU,uBAAA,EAAwB,OAAO,EAAE,KAAA,EAAO,GAAA,CAAI,KAAA,EAAM,EAC/D,QAAA,EAAA;AAAA,oBAAA,GAAA,CAAI,KAAA;AAAA,oBAAM,GAAA;AAAA,oBAAE,IAAA,CAAK,GAAA,CAAI,CAAA,CAAE,YAAY,CAAA;AAAA,oBAAE;AAAA,mBAAA,EACxC;AAAA,iBAAA,EACF,CAAA;AAAA,gCACA,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,SAAA,EAAU,KAAA,EAAO,EAAE,KAAA,EAAO,kCAAA,EAAmC,EAC1E,QAAA,EAAA,CAAA,CAAE,QAAA,EACL;AAAA;AAAA,aAAA;AAAA,YAvBK,CAAA,EAAG,CAAA,CAAE,MAAM,CAAA,CAAA,EAAI,CAAC,CAAA;AAAA,WAwBvB;AAAA,QAEJ,CAAC,CAAA,EACH,CAAA;AAAA,wBAEA,IAAA;AAAA,UAAC,KAAA;AAAA,UAAA;AAAA,YACC,SAAA,EAAU,mCAAA;AAAA,YACV,KAAA,EAAO;AAAA,cACL,UAAA,EAAY,6CAAA;AAAA,cACZ,KAAA,EAAO;AAAA,aACT;AAAA,YAEA,QAAA,EAAA;AAAA,8BAAA,GAAA;AAAA,gBAAC,MAAA;AAAA,gBAAA;AAAA,kBACC,SAAA,EAAU,4CAAA;AAAA,kBACV,KAAA,EAAO,EAAE,UAAA,EAAY,SAAA;AAAU;AAAA,eACjC;AAAA,8BACA,IAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,qBAAA,EAAsB,QAAA,EAAA;AAAA,gBAAA,8BAAA;AAAA,gBAEnC,WAAA,GAAc,CAAA,gBAAA,EAAgB,UAAA,CAAW,WAAW,CAAC,CAAA,CAAA,GAAK;AAAA,eAAA,EAC7D;AAAA;AAAA;AAAA;AACF;AAAA;AAAA,GACF;AAEJ","file":"chunk-NIZJLONK.js","sourcesContent":["/**\n * MetricsProvenance — \"these numbers are real\" strip for case studies.\n *\n * Server component, zero JS. Renders the live metric deltas the platform\n * computes from GSC/analytics baselines (MetricsDelta[]) plus the refresh\n * timestamp — the trust signal a static agency portfolio can't fake.\n *\n * `direction` semantics come from the API: 'up' ALWAYS means \"improved\"\n * (lower-is-better metrics like Avg Position/LCP are inverted server-side\n * in metrics-baseline.service), so up renders positive/green.\n */\n\nimport React from 'react';\nimport type { MetricsDelta } from '../../types';\n\ninterface MetricsProvenanceProps {\n deltas: MetricsDelta[];\n /** ISO timestamp of the last live-metrics refresh. */\n refreshedAt?: string | null;\n}\n\nconst DIRECTION_STYLE: Record<MetricsDelta['direction'], { color: string; arrow: string }> = {\n up: { color: '#10b981', arrow: '▲' },\n down: { color: '#ef4444', arrow: '▼' },\n flat: { color: 'var(--sk-text-tertiary, #71717a)', arrow: '—' },\n};\n\nfunction formatValue(v: number): string {\n return Number.isInteger(v)\n ? v.toLocaleString('en-US')\n : v.toLocaleString('en-US', { maximumFractionDigits: 2 });\n}\n\nfunction formatDate(iso: string): string {\n try {\n return new Intl.DateTimeFormat('en-US', {\n year: 'numeric',\n month: 'long',\n day: 'numeric',\n }).format(new Date(iso));\n } catch {\n return iso;\n }\n}\n\nexport default function MetricsProvenance({ deltas, refreshedAt }: MetricsProvenanceProps) {\n if (!deltas.length) return null;\n\n return (\n <div\n className=\"mt-10 rounded-2xl overflow-hidden\"\n style={{\n background: 'var(--sk-surface, rgba(255,255,255,0.05))',\n border: '1px solid var(--sk-border, rgba(255,255,255,0.1))',\n }}\n >\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3\">\n {deltas.map((d, i) => {\n const dir = DIRECTION_STYLE[d.direction] ?? DIRECTION_STYLE.flat;\n return (\n <div\n key={`${d.metric}-${i}`}\n className=\"flex flex-col gap-1 p-5\"\n style={{ borderBottom: '1px solid var(--sk-border, rgba(255,255,255,0.06))' }}\n >\n <span\n className=\"text-xs font-semibold uppercase tracking-wider\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n >\n {d.metric}\n </span>\n <span className=\"flex items-baseline gap-2\">\n <span\n className=\"text-lg font-bold\"\n style={{ color: 'var(--sk-text-primary, #ffffff)' }}\n >\n {formatValue(d.baseline)} → {formatValue(d.current)}\n </span>\n <span className=\"text-sm font-semibold\" style={{ color: dir.color }}>\n {dir.arrow} {Math.abs(d.deltaPercent)}%\n </span>\n </span>\n <span className=\"text-xs\" style={{ color: 'var(--sk-text-tertiary, #71717a)' }}>\n {d.timespan}\n </span>\n </div>\n );\n })}\n </div>\n\n <div\n className=\"flex items-center gap-2 px-5 py-3\"\n style={{\n background: 'color-mix(in srgb, #10b981 6%, transparent)',\n color: 'var(--sk-text-secondary, #a1a1aa)',\n }}\n >\n <span\n className=\"inline-block w-2 h-2 rounded-full shrink-0\"\n style={{ background: '#10b981' }}\n />\n <span className=\"text-xs font-medium\">\n Verified from live analytics\n {refreshedAt ? ` · Refreshed ${formatDate(refreshedAt)}` : ''}\n </span>\n </div>\n </div>\n );\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/portfolio/components/PortfolioPage.tsx","../src/portfolio/components/PortfolioSchema.tsx","../src/portfolio/components/PortfolioCard.tsx","../src/portfolio/components/PortfolioGrid.tsx","../src/portfolio/components/PortfolioIndex.tsx","../src/portfolio/components/PortfolioSkeletons.tsx","../src/types.ts"],"names":["jsx","jsxs"],"mappings":"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","file":"chunk-UNTOEWQL.js","sourcesContent":["/**\n * PortfolioPage — Server component that renders a full portfolio case study.\n *\n * Maps each section in item.sections to the correct renderer component.\n * All sections below the hero are loaded lazily via next/dynamic.\n *\n * NOTE: This file is intentionally NOT a client component — it is designed\n * to be used as an RSC in a Next.js app directory page.\n *\n * Sections MUST be imported via `@sonordev/agency-site-kit/portfolio/sections/*`\n * (self-referencing subpaths, external in tsup's server build), never via\n * relative paths: a relative import would bundle the client sections into\n * this server chunk, the 'use client' boundary would be lost, and the whole\n * PortfolioItemFull would serialize into the RSC flight payload again.\n *\n * Whether a section is a server or client component is decided by the\n * section file itself (its 'use client' directive; tsup routes it to the\n * matching build). Server sections are imported statically — they ship zero\n * client JS, so there is nothing to lazy-load. Client sections load through\n * next/dynamic so each becomes its own browser chunk, fetched only when the\n * case study actually renders that section type.\n */\n\nimport React from 'react';\nimport dynamic from 'next/dynamic';\nimport type {\n PortfolioItemFull,\n PortfolioSection,\n PortfolioHeroData,\n PortfolioChallengesData,\n PortfolioStrategyData,\n PortfolioResultsData,\n PortfolioTechStackData,\n PortfolioServicesData,\n PortfolioTestimonialData,\n PortfolioGalleryData,\n PortfolioVideoData,\n PortfolioTeamData,\n PortfolioFeatureSpotlightData,\n PortfolioBeforeAfterData,\n PortfolioMetricsTimelineData,\n PortfolioConversionFunnelData,\n PortfolioDetailsData,\n PortfolioCTAData,\n} from '../../types';\n\n// Hero is loaded eagerly (above the fold, client — it animates)\nimport HeroSection from '@sonordev/agency-site-kit/portfolio/sections/HeroSection';\n\n// Server sections — no 'use client' in their files, so they render on the\n// server and ship ZERO component JS to the browser. Static imports: there is\n// no client chunk to defer.\nimport ChallengesSection from '@sonordev/agency-site-kit/portfolio/sections/ChallengesSection';\nimport ResultsSection from '@sonordev/agency-site-kit/portfolio/sections/ResultsSection';\nimport TechStackSection from '@sonordev/agency-site-kit/portfolio/sections/TechStackSection';\nimport ServicesSection from '@sonordev/agency-site-kit/portfolio/sections/ServicesSection';\nimport TestimonialSection from '@sonordev/agency-site-kit/portfolio/sections/TestimonialSection';\nimport SpeedComparisonSection from '@sonordev/agency-site-kit/portfolio/sections/SpeedComparisonSection';\nimport DesignSystemSection from '@sonordev/agency-site-kit/portfolio/sections/DesignSystemSection';\nimport DetailsSection from '@sonordev/agency-site-kit/portfolio/sections/DetailsSection';\n\n// Client sections — animated/interactive; lazily loaded so each becomes its\n// own browser chunk, fetched only when rendered.\nconst StrategySection = dynamic(() => import('@sonordev/agency-site-kit/portfolio/sections/StrategySection'), { ssr: true });\nconst GallerySection = dynamic(() => import('@sonordev/agency-site-kit/portfolio/sections/GallerySection'), { ssr: true });\nconst VideoSection = dynamic(() => import('@sonordev/agency-site-kit/portfolio/sections/VideoSection'), { ssr: true });\nconst TeamSection = dynamic(() => import('@sonordev/agency-site-kit/portfolio/sections/TeamSection'), { ssr: true });\nconst FeatureSpotlightSection = dynamic(() => import('@sonordev/agency-site-kit/portfolio/sections/FeatureSpotlightSection'), { ssr: true });\nconst BeforeAfterSection = dynamic(() => import('@sonordev/agency-site-kit/portfolio/sections/BeforeAfterSection'), { ssr: true });\nconst MetricsTimelineSection = dynamic(() => import('@sonordev/agency-site-kit/portfolio/sections/MetricsTimelineSection'), { ssr: true });\nconst ConversionFunnelSection = dynamic(() => import('@sonordev/agency-site-kit/portfolio/sections/ConversionFunnelSection'), { ssr: true });\nconst PerformanceSection = dynamic(() => import('@sonordev/agency-site-kit/portfolio/sections/PerformanceSection'), { ssr: true });\nconst SiteArchitectureSection = dynamic(() => import('@sonordev/agency-site-kit/portfolio/sections/SiteArchitectureSection'), { ssr: true });\nconst CTASection = dynamic(() => import('@sonordev/agency-site-kit/portfolio/sections/CTASection'), { ssr: true });\n\ninterface PortfolioPageProps {\n item: PortfolioItemFull;\n}\n\n// Defense-in-depth only — the enforcing sanitize pass runs in\n// getPortfolioItem (portfolio/server.ts), the last genuinely-server-side hop.\n// See src/portfolio/sanitize.ts for the single source of truth and the story.\nimport { sanitizeSiteArchitectureData } from '../sanitize';\n\n// Engagement-tracking island (client) — crosses the boundary via subpath.\nimport PortfolioAnalytics from '@sonordev/agency-site-kit/portfolio/components/PortfolioAnalytics';\n\nfunction renderSection(section: PortfolioSection, item: PortfolioItemFull, index: number) {\n const key = `${section.sectionType}-${index}`;\n\n switch (section.sectionType) {\n case 'portfolioHero':\n return (\n <HeroSection\n key={key}\n data={section.data as PortfolioHeroData}\n screenshots={item.hero_screenshots ?? undefined}\n liveUrl={item.live_url ?? undefined}\n />\n );\n case 'portfolioChallenges':\n return <ChallengesSection key={key} data={section.data as PortfolioChallengesData} />;\n case 'portfolioStrategy':\n return <StrategySection key={key} data={section.data as PortfolioStrategyData} />;\n case 'portfolioResults':\n return (\n <ResultsSection\n key={key}\n data={section.data as PortfolioResultsData}\n metricsDelta={item.metricsDelta}\n metricsRefreshedAt={item.metrics_last_refreshed_at}\n />\n );\n case 'portfolioTechStack':\n return <TechStackSection key={key} data={section.data as PortfolioTechStackData} />;\n case 'portfolioServices':\n return <ServicesSection key={key} data={section.data as PortfolioServicesData} />;\n case 'portfolioTestimonial':\n return <TestimonialSection key={key} data={section.data as PortfolioTestimonialData} />;\n case 'portfolioGallery':\n return <GallerySection key={key} data={section.data as PortfolioGalleryData} />;\n case 'portfolioVideo':\n return <VideoSection key={key} data={section.data as PortfolioVideoData} />;\n case 'portfolioTeam':\n return <TeamSection key={key} data={section.data as PortfolioTeamData} />;\n case 'portfolioFeatureSpotlight':\n return <FeatureSpotlightSection key={key} data={section.data as PortfolioFeatureSpotlightData} />;\n case 'portfolioBeforeAfter':\n return <BeforeAfterSection key={key} data={section.data as PortfolioBeforeAfterData} />;\n case 'portfolioMetricsTimeline':\n return <MetricsTimelineSection key={key} data={section.data as PortfolioMetricsTimelineData} />;\n case 'portfolioConversionFunnel':\n return <ConversionFunnelSection key={key} data={section.data as PortfolioConversionFunnelData} />;\n case 'portfolioPerformance':\n return <PerformanceSection key={key} data={section.data as any} />;\n case 'portfolioSpeedComparison':\n return <SpeedComparisonSection key={key} data={section.data as any} />;\n case 'portfolioSiteArchitecture':\n return <SiteArchitectureSection key={key} data={sanitizeSiteArchitectureData(section.data) as any} />;\n case 'portfolioDesignSystem':\n return <DesignSystemSection key={key} data={section.data as any} />;\n case 'portfolioDetails':\n return <DetailsSection key={key} data={section.data as PortfolioDetailsData} />;\n case 'portfolioCTA':\n return <CTASection key={key} data={section.data as PortfolioCTAData} />;\n case 'portfolioSeo':\n // SEO section is metadata-only; nothing to render visually.\n return null;\n default:\n return null;\n }\n}\n\n// Canonical display order — ensures consistent rendering regardless of API\n// order. Keys must match PortfolioSectionType exactly (note 'portfolioCTA' —\n// a lowercased 'portfolioCta' key silently fell through to the ?? 99 fallback\n// for years). Unknown/future types sort last via the fallback.\nconst SECTION_ORDER: Record<string, number> = {\n portfolioHero: 0,\n portfolioChallenges: 1,\n portfolioStrategy: 2,\n portfolioResults: 3,\n portfolioPerformance: 4,\n portfolioSpeedComparison: 5,\n portfolioSiteArchitecture: 6,\n portfolioTechStack: 7,\n portfolioDesignSystem: 8,\n portfolioServices: 9,\n portfolioBeforeAfter: 10,\n portfolioTestimonial: 11,\n portfolioMetricsTimeline: 12,\n portfolioConversionFunnel: 13,\n portfolioDetails: 14,\n portfolioSeo: 15,\n portfolioCTA: 16,\n};\n\nexport default function PortfolioPage({ item }: PortfolioPageProps) {\n // Section order: once an agency has deliberately reordered in the\n // dashboard (item.sectionsOrdered), the array order IS the narrative —\n // respect it, with the hero pinned first (it's the LCP shell). Items\n // never reordered keep the canonical order below, so nothing shifts\n // out from under existing case studies.\n const sortedSections = item.sectionsOrdered\n ? [...item.sections].sort(\n (a, b) =>\n (a.sectionType === 'portfolioHero' ? 0 : 1) -\n (b.sectionType === 'portfolioHero' ? 0 : 1),\n )\n : [...item.sections].sort(\n (a, b) => (SECTION_ORDER[a.sectionType] ?? 99) - (SECTION_ORDER[b.sectionType] ?? 99)\n );\n\n return (\n <article className=\"flex w-full min-w-0 max-w-full flex-col overflow-x-clip\">\n {sortedSections.map((section, index) => {\n const rendered = renderSection(section, item, index);\n if (!rendered) return null;\n return (\n <div\n key={`wrapper-${section.sectionType}-${index}`}\n className=\"w-full\"\n data-portfolio-section={section.sectionType}\n >\n {rendered}\n </div>\n );\n })}\n {/* Engagement tracking — childless idle-deferred sibling, never a wrapper */}\n <PortfolioAnalytics slug={item.slug} />\n </article>\n );\n}\n","/**\n * PortfolioSchema — JSON-LD structured data for a case study page.\n *\n * Server component: renders a single <script type=\"application/ld+json\">\n * describing the case study as a schema.org Article (there is no dedicated\n * CaseStudy type; Article is what Google documents for editorial content).\n * Ships zero client JS and adds rich-result eligibility + entity clarity for\n * the agency's portfolio pages.\n *\n * Usage (in the case study page, next to <PortfolioPage />):\n *\n * ```tsx\n * <PortfolioSchema\n * item={item}\n * url={`https://youragency.com/work/${item.slug}`}\n * publisher={{ name: 'Your Agency', url: 'https://youragency.com' }}\n * />\n * ```\n *\n * Only reads fields from the already-sanitized PortfolioItemFull (see\n * getPortfolioItem / sanitize.ts) — never section internals.\n */\n\nimport React from 'react';\nimport type { PortfolioItemFull } from '../../types';\n\nexport interface PortfolioSchemaProps {\n item: PortfolioItemFull;\n /** Canonical URL of this case study page. */\n url: string;\n /** The agency, emitted as author + publisher Organization. */\n publisher?: {\n name: string;\n url?: string;\n /** Absolute URL to a logo image. */\n logo?: string;\n };\n}\n\n/** JSON-LD-safe serialization — escape `<` so `</script>` can never break out. */\nfunction jsonLd(value: unknown): string {\n return JSON.stringify(value).replace(/</g, '\\\\u003c');\n}\n\nexport default function PortfolioSchema({ item, url, publisher }: PortfolioSchemaProps) {\n const headline = item.seo?.metaTitle || item.title;\n const description = item.seo?.metaDescription || item.description;\n\n const keywords = [\n ...(item.seo?.keywords ?? []),\n item.category,\n ...item.services,\n ].filter(Boolean);\n\n const org = publisher\n ? {\n '@type': 'Organization',\n name: publisher.name,\n ...(publisher.url ? { url: publisher.url } : {}),\n ...(publisher.logo\n ? { logo: { '@type': 'ImageObject', url: publisher.logo } }\n : {}),\n }\n : undefined;\n\n const schema = {\n '@context': 'https://schema.org',\n '@type': 'Article',\n headline,\n ...(description ? { description } : {}),\n ...(item.hero_image ? { image: [item.hero_image] } : {}),\n url,\n mainEntityOfPage: { '@type': 'WebPage', '@id': url },\n ...(item.published_at ? { datePublished: item.published_at } : {}),\n ...(item.metrics_last_refreshed_at || item.published_at\n ? { dateModified: item.metrics_last_refreshed_at || item.published_at }\n : {}),\n ...(keywords.length ? { keywords: keywords.join(', ') } : {}),\n ...(org ? { author: org, publisher: org } : {}),\n ...(item.details?.industry ? { about: item.details.industry } : {}),\n };\n\n return (\n <script\n type=\"application/ld+json\"\n dangerouslySetInnerHTML={{ __html: jsonLd(schema) }}\n />\n );\n}\n","// Shared component — no directive on purpose: hook-free, so it renders\n// server-side (zero JS) from PortfolioIndex and stays usable from client\n// code via the /portfolio/client barrel.\nimport React from 'react';\nimport Image from 'next/image';\nimport Link from 'next/link';\nimport type { PortfolioItem } from '../../types';\nimport GlassCard from '@sonordev/agency-site-kit/portfolio/primitives/GlassCard';\n\ninterface PortfolioCardProps {\n item: PortfolioItem;\n className?: string;\n /** Base path for portfolio detail links (default: '/work') */\n basePath?: string;\n}\n\nexport default function PortfolioCard({ item, className = '', basePath = '/work' }: PortfolioCardProps) {\n // Use hero_screenshots desktop → hero_image → trifolio screenshot convention\n const thumbnailSrc =\n item.hero_screenshots?.desktop ||\n item.hero_image ||\n (item.project_id ? `/portfolio-previews/${item.slug}.png` : undefined);\n const kpis = Array.isArray(item.kpis) ? item.kpis : [];\n const services = Array.isArray(item.services) ? item.services : [];\n const kpiPreview = kpis.slice(0, 3);\n const servicePreview = services.slice(0, 3);\n\n return (\n <Link href={`${basePath}/${item.slug}`} className={`block group ${className}`}>\n <GlassCard padding=\"sm\" hover>\n <div className=\"flex flex-col gap-4\">\n {/* Thumbnail */}\n {thumbnailSrc && (\n <div\n className=\"relative w-full overflow-hidden rounded-xl\"\n style={{ aspectRatio: '16/10' }}\n >\n <Image\n src={thumbnailSrc}\n alt={item.hero_image_alt || item.title}\n fill\n sizes=\"(max-width: 768px) 100vw, (max-width: 1024px) 50vw, 33vw\"\n className=\"object-cover object-top transition-transform duration-500 group-hover:scale-105\"\n />\n {/* Category badge */}\n <span\n className=\"absolute top-3 left-3 px-3 py-1 rounded-full text-[11px] uppercase tracking-wider font-semibold\"\n style={{\n background: 'color-mix(in srgb, var(--sk-primary, #6366f1) 80%, transparent)',\n color: '#ffffff',\n backdropFilter: 'blur(8px)',\n }}\n >\n {item.category?.replace(/[-_]/g, ' ').replace(/\\b\\w/g, (c: string) => c.toUpperCase())}\n </span>\n </div>\n )}\n\n {/* Text content */}\n <div className=\"flex flex-col gap-2 px-2 pb-2\">\n <h3\n className=\"text-lg font-semibold line-clamp-1\"\n style={{ color: 'var(--sk-text-primary, #ffffff)' }}\n >\n {item.title}\n </h3>\n\n {item.subtitle && (\n <p\n className=\"text-sm line-clamp-2\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n >\n {item.subtitle}\n </p>\n )}\n\n {/* KPI preview */}\n {kpiPreview.length > 0 && (\n <div className=\"flex items-center gap-4 mt-1\">\n {kpiPreview.map((kpi, i) => (\n <div key={i} className=\"flex flex-col\">\n <span\n className=\"text-base font-bold\"\n style={{ color: 'var(--sk-primary, #6366f1)' }}\n >\n {kpi.prefix || ''}\n {kpi.value.toLocaleString()}\n {kpi.suffix}\n </span>\n <span\n className=\"text-[10px] uppercase tracking-wider font-medium\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n >\n {kpi.label}\n </span>\n </div>\n ))}\n </div>\n )}\n\n {/* Services tags */}\n {servicePreview.length > 0 && (\n <div className=\"flex flex-wrap gap-1.5 mt-2\">\n {servicePreview.map((service) => (\n <span\n key={service}\n className=\"px-2.5 py-0.5 rounded-full text-[11px] font-medium\"\n style={{\n background: 'var(--sk-surface, rgba(255,255,255,0.05))',\n color: 'var(--sk-text-secondary, #a1a1aa)',\n border: '1px solid var(--sk-border, rgba(255,255,255,0.1))',\n }}\n >\n {service}\n </span>\n ))}\n {services.length > 3 && (\n <span\n className=\"px-2.5 py-0.5 rounded-full text-[11px] font-medium\"\n style={{\n background: 'var(--sk-surface, rgba(255,255,255,0.05))',\n color: 'var(--sk-text-tertiary, #71717a)',\n }}\n >\n +{item.services.length - 3}\n </span>\n )}\n </div>\n )}\n </div>\n </div>\n </GlassCard>\n </Link>\n );\n}\n","// Shared component — no directive on purpose: hook-free, so it renders\n// server-side (zero JS) from PortfolioIndex and stays usable from client\n// code via the /portfolio/client barrel.\nimport React from 'react';\nimport type { PortfolioItem } from '../../types';\nimport ScrollReveal from '@sonordev/agency-site-kit/portfolio/primitives/ScrollReveal';\nimport PortfolioCard from './PortfolioCard';\n\ninterface PortfolioGridProps {\n items: PortfolioItem[];\n className?: string;\n /** Base path for portfolio detail links (default: '/work') */\n basePath?: string;\n}\n\nexport default function PortfolioGrid({ items, className = '', basePath = '/work' }: PortfolioGridProps) {\n return (\n <ScrollReveal stagger={0.1}>\n <div\n className={`grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 ${className}`}\n style={{\n gap: 'var(--sk-grid-gap, 24px)',\n }}\n >\n {items.map((item) => (\n <PortfolioCard key={item.id} item={item} basePath={basePath} />\n ))}\n </div>\n </ScrollReveal>\n );\n}\n","/**\n * PortfolioIndex — turnkey, server-rendered /work index.\n *\n * Fetches items + categories and renders: category nav (real links —\n * crawlable, no client state), the portfolio grid, pagination, and an\n * intentional empty state. Ships zero component JS.\n *\n * Category filtering is route-based for SEO:\n * /work → all items\n * /work/category/[category] → one category (slug via slugifyCategory)\n * Pagination via ?page= on either route.\n */\n\nimport React from 'react';\nimport {\n getPortfolioCategories,\n getPortfolioItems,\n slugifyCategory,\n} from '../server';\nimport PortfolioGrid from './PortfolioGrid';\n\nexport interface PortfolioIndexProps {\n /** Route prefix for case studies + category links. Defaults to \"/work\". */\n basePath?: string;\n /** Active category SLUG (from the [category] route param). */\n category?: string | null;\n /** 1-based page number (from ?page=). */\n page?: number;\n /** Items per page. Defaults to 12. */\n pageSize?: number;\n /** Optional heading block rendered above the category nav. */\n heading?: React.ReactNode;\n /** Override the empty state. */\n emptyState?: React.ReactNode;\n /** Hide the category nav (e.g. single-category portfolios). */\n showCategories?: boolean;\n className?: string;\n}\n\nfunction CategoryLink({\n href,\n label,\n active,\n}: {\n href: string;\n label: string;\n active: boolean;\n}) {\n return (\n <a\n href={href}\n aria-current={active ? 'page' : undefined}\n className=\"px-4 py-1.5 rounded-full text-sm font-medium transition-colors\"\n style={{\n background: active\n ? 'color-mix(in srgb, var(--sk-primary, #6366f1) 15%, transparent)'\n : 'var(--sk-surface, rgba(255,255,255,0.05))',\n color: active ? 'var(--sk-primary, #6366f1)' : 'var(--sk-text-secondary, #a1a1aa)',\n border: `1px solid ${\n active\n ? 'color-mix(in srgb, var(--sk-primary, #6366f1) 25%, transparent)'\n : 'var(--sk-border, rgba(255,255,255,0.1))'\n }`,\n }}\n >\n {label}\n </a>\n );\n}\n\nfunction PageLink({ href, label, disabled }: { href: string; label: string; disabled: boolean }) {\n if (disabled) {\n return (\n <span\n aria-disabled=\"true\"\n className=\"px-4 py-2 rounded-xl text-sm font-medium opacity-40\"\n style={{\n background: 'var(--sk-surface, rgba(255,255,255,0.05))',\n color: 'var(--sk-text-tertiary, #71717a)',\n border: '1px solid var(--sk-border, rgba(255,255,255,0.1))',\n }}\n >\n {label}\n </span>\n );\n }\n return (\n <a\n href={href}\n className=\"px-4 py-2 rounded-xl text-sm font-medium transition-colors\"\n style={{\n background: 'var(--sk-surface, rgba(255,255,255,0.05))',\n color: 'var(--sk-text-secondary, #a1a1aa)',\n border: '1px solid var(--sk-border, rgba(255,255,255,0.1))',\n }}\n >\n {label}\n </a>\n );\n}\n\nexport default async function PortfolioIndex({\n basePath = '/work',\n category = null,\n page = 1,\n pageSize = 12,\n heading,\n emptyState,\n showCategories = true,\n className = '',\n}: PortfolioIndexProps) {\n const currentPage = Math.max(1, Math.floor(page));\n const categories = await getPortfolioCategories();\n\n // Route param is a slug; the API filter wants the original category value.\n const activeCategory = category\n ? categories.find((c) => slugifyCategory(c) === category) ?? null\n : null;\n\n const { items, total } = await getPortfolioItems({\n category: activeCategory ?? undefined,\n limit: pageSize,\n offset: (currentPage - 1) * pageSize,\n });\n\n const totalPages = Math.max(1, Math.ceil(total / pageSize));\n const pageHref = (p: number) => {\n const base = activeCategory\n ? `${basePath}/category/${slugifyCategory(activeCategory)}`\n : basePath;\n return p <= 1 ? base : `${base}?page=${p}`;\n };\n\n return (\n <section className={`w-full ${className}`}>\n <div className=\"max-w-7xl mx-auto px-6\">\n {heading}\n\n {/* Category nav — real links, crawlable */}\n {showCategories && categories.length > 1 && (\n <nav aria-label=\"Portfolio categories\" className=\"flex flex-wrap gap-2 mb-10\">\n <CategoryLink href={basePath} label=\"All\" active={!activeCategory} />\n {categories.map((c) => (\n <CategoryLink\n key={c}\n href={`${basePath}/category/${slugifyCategory(c)}`}\n label={c}\n active={activeCategory === c}\n />\n ))}\n </nav>\n )}\n\n {items.length > 0 ? (\n <PortfolioGrid items={items} basePath={basePath} />\n ) : (\n emptyState ?? (\n <div\n className=\"flex flex-col items-center gap-3 py-24 rounded-2xl text-center\"\n style={{\n background: 'var(--sk-surface, rgba(255,255,255,0.03))',\n border: '1px dashed var(--sk-border, rgba(255,255,255,0.1))',\n }}\n >\n <span\n className=\"text-lg font-semibold\"\n style={{ color: 'var(--sk-text-primary, #ffffff)' }}\n >\n Case studies coming soon\n </span>\n <span className=\"text-sm\" style={{ color: 'var(--sk-text-tertiary, #71717a)' }}>\n {activeCategory\n ? `Nothing published in ${activeCategory} yet.`\n : \"We're writing up our latest work right now.\"}\n </span>\n </div>\n )\n )}\n\n {/* Pagination */}\n {totalPages > 1 && (\n <nav\n aria-label=\"Portfolio pages\"\n className=\"flex items-center justify-center gap-3 mt-12\"\n >\n <PageLink\n href={pageHref(currentPage - 1)}\n label=\"← Previous\"\n disabled={currentPage <= 1}\n />\n <span className=\"text-sm\" style={{ color: 'var(--sk-text-tertiary, #71717a)' }}>\n Page {currentPage} of {totalPages}\n </span>\n <PageLink\n href={pageHref(currentPage + 1)}\n label=\"Next →\"\n disabled={currentPage >= totalPages}\n />\n </nav>\n )}\n </div>\n </section>\n );\n}\n","/**\n * Loading skeletons for portfolio routes — pure CSS pulse, server-safe,\n * zero JS. Drop into `loading.tsx`:\n *\n * ```tsx\n * export { PortfolioIndexSkeleton as default } from '@sonordev/agency-site-kit/portfolio';\n * ```\n */\n\nimport React from 'react';\n\nconst PULSE_CSS = `\n.sk-skeleton{animation:sk-skeleton-pulse 1.6s ease-in-out infinite;background:var(--sk-surface,rgba(255,255,255,0.06));border:1px solid var(--sk-border,rgba(255,255,255,0.08));border-radius:16px}\n@keyframes sk-skeleton-pulse{0%,100%{opacity:.55}50%{opacity:1}}\n@media (prefers-reduced-motion:reduce){.sk-skeleton{animation:none}}\n`;\n\nfunction SkeletonStyles() {\n return (\n <style href=\"sk-skeletons\" precedence=\"default\">\n {PULSE_CSS}\n </style>\n );\n}\n\n/** Grid of pulsing card placeholders for the /work index. */\nexport function PortfolioIndexSkeleton({ cards = 6 }: { cards?: number }) {\n return (\n <section className=\"w-full py-16\">\n <SkeletonStyles />\n <div className=\"max-w-7xl mx-auto px-6\">\n <div className=\"sk-skeleton h-9 w-56 mb-4\" />\n <div className=\"sk-skeleton h-5 w-80 mb-10\" />\n <div className=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6\">\n {Array.from({ length: cards }).map((_, i) => (\n <div key={i} className=\"flex flex-col gap-3\">\n <div className=\"sk-skeleton w-full\" style={{ aspectRatio: '16/10' }} />\n <div className=\"sk-skeleton h-5 w-3/4\" />\n <div className=\"sk-skeleton h-4 w-1/2\" />\n </div>\n ))}\n </div>\n </div>\n </section>\n );\n}\n\n/** Hero-shaped placeholder for /work/[slug] case study pages. */\nexport function PortfolioPageSkeleton() {\n return (\n <article className=\"w-full\">\n <SkeletonStyles />\n <div className=\"max-w-7xl mx-auto px-6 py-24\">\n <div className=\"grid grid-cols-1 lg:grid-cols-2 gap-12 items-start\">\n <div className=\"flex flex-col gap-5\">\n <div className=\"sk-skeleton h-7 w-32\" style={{ borderRadius: '9999px' }} />\n <div className=\"sk-skeleton h-12 w-full\" />\n <div className=\"sk-skeleton h-12 w-4/5\" />\n <div className=\"sk-skeleton h-5 w-full\" />\n <div className=\"sk-skeleton h-5 w-2/3\" />\n </div>\n <div className=\"sk-skeleton w-full\" style={{ aspectRatio: '11/5' }} />\n </div>\n </div>\n </article>\n );\n}\n","/**\n * @sonordev/agency-site-kit — Type definitions\n *\n * These types mirror the Portal API's cms/types.ts definitions for portfolio\n * content. Keep them in sync when modifying the API response shapes.\n */\n\n// ---------------------------------------------------------------------------\n// Brand Configuration\n// ---------------------------------------------------------------------------\n\nexport interface BrandConfig {\n /** Primary brand color (hex) */\n primary: string;\n /** Secondary/accent color (hex) */\n secondary: string;\n /** Background color (hex) */\n background: string;\n /** Elevated background — cards, modals (hex) */\n backgroundElevated: string;\n /** Surface color — interactive containers (hex) */\n surface: string;\n /** Surface hover state (hex) */\n surfaceHover: string;\n /** Surface border color (hex) */\n surfaceBorder: string;\n /** Primary text color (hex) */\n textPrimary: string;\n /** Secondary text color (hex) */\n textSecondary: string;\n /** Tertiary/muted text color (hex) */\n textTertiary: string;\n /** Heading font family */\n fontHeading: string;\n /** Body font family */\n fontBody: string;\n /** Logo URL (absolute or relative) */\n logoUrl?: string;\n /** Logo alt text */\n logoAlt?: string;\n /** Dark mode overrides — partial, merged with base values */\n darkMode?: Partial<Omit<BrandConfig, 'fontHeading' | 'fontBody' | 'logoUrl' | 'logoAlt' | 'darkMode'>>;\n /** Border radius tokens */\n radius?: {\n sm: string;\n md: string;\n lg: string;\n };\n}\n\n// ---------------------------------------------------------------------------\n// Sanity Image Reference\n// ---------------------------------------------------------------------------\n\nexport interface SanityImageRef {\n _type: 'image';\n asset: { _type: 'reference'; _ref: string };\n hotspot?: { x: number; y: number; height: number; width: number };\n crop?: { top: number; bottom: number; left: number; right: number };\n alt?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Metrics\n// ---------------------------------------------------------------------------\n\nexport type MetricSource = 'measured' | 'estimated';\n\nexport interface PortfolioKPI {\n label: string;\n value: number;\n suffix: string;\n prefix?: string;\n description: string;\n source: MetricSource;\n}\n\nexport interface MetricsDelta {\n metric: string;\n baseline: number;\n current: number;\n delta: number;\n deltaPercent: number;\n direction: 'up' | 'down' | 'flat';\n timespan: string;\n}\n\nexport interface MetricsSnapshot {\n seo: {\n clicks_28d?: number;\n impressions_28d?: number;\n avg_position?: number;\n ctr?: number;\n };\n cwv: {\n lcp_ms?: number;\n inp_ms?: number;\n cls?: number;\n performance_score?: number;\n };\n analytics: {\n sessions_28d?: number;\n page_views_28d?: number;\n conversion_rate?: number;\n bounce_rate?: number;\n };\n pages: {\n total_indexed?: number;\n total_crawled?: number;\n total_pages?: number;\n };\n}\n\n// ---------------------------------------------------------------------------\n// Portfolio Section Data Types\n// ---------------------------------------------------------------------------\n\nexport interface PortfolioHeroData {\n headline: string;\n subheadline: string;\n description: string;\n category: string;\n services: string[];\n liveUrl?: string;\n screenshots: {\n desktop?: string;\n tablet?: string;\n mobile?: string;\n };\n kpis: PortfolioKPI[];\n}\n\nexport interface PortfolioChallengeItem {\n title: string;\n description: string;\n solution: string;\n result?: string;\n icon?: string;\n}\n\nexport interface PortfolioChallengesData {\n items: PortfolioChallengeItem[];\n}\n\nexport interface PortfolioStrategyPhase {\n number: number;\n title: string;\n description: string;\n deliverables?: string[];\n icon?: string;\n timeline?: string;\n}\n\nexport interface PortfolioStrategyData {\n phases: PortfolioStrategyPhase[];\n}\n\nexport interface PortfolioResultItem {\n title: string;\n description: string;\n metric?: {\n value: number;\n suffix: string;\n prefix?: string;\n };\n source: MetricSource;\n icon?: string;\n}\n\nexport interface PortfolioResultsData {\n items: PortfolioResultItem[];\n}\n\nexport interface PortfolioTechItem {\n name: string;\n category: string;\n icon?: string;\n description?: string;\n}\n\nexport interface PortfolioTechStackData {\n technologies: PortfolioTechItem[];\n}\n\nexport interface PortfolioServiceItem {\n title: string;\n description: string;\n features: string[];\n icon?: string;\n}\n\nexport interface PortfolioServicesData {\n items: PortfolioServiceItem[];\n}\n\nexport interface PortfolioTestimonialData {\n quote: string;\n author: string;\n title: string;\n company: string;\n avatar?: SanityImageRef;\n rating?: number;\n}\n\nexport interface PortfolioGalleryImage {\n image: SanityImageRef;\n caption?: string;\n type?: 'screenshot' | 'before' | 'after' | 'design';\n}\n\nexport interface PortfolioGalleryData {\n images: PortfolioGalleryImage[];\n layout?: 'grid' | 'masonry';\n}\n\nexport interface PortfolioVideoData {\n url?: string;\n embedUrl?: string;\n thumbnail: SanityImageRef;\n title: string;\n duration?: string;\n platform?: 'youtube' | 'vimeo' | 'custom';\n}\n\nexport interface PortfolioTeamMember {\n name: string;\n role: string;\n avatar?: SanityImageRef;\n}\n\nexport interface PortfolioTeamData {\n members: PortfolioTeamMember[];\n}\n\nexport interface PortfolioAnnotation {\n x: number;\n y: number;\n label: string;\n description: string;\n icon?: string;\n}\n\nexport interface PortfolioFeatureSpotlightData {\n image: SanityImageRef;\n title: string;\n description: string;\n layout: 'left' | 'right' | 'full';\n annotations: PortfolioAnnotation[];\n}\n\nexport interface PortfolioBeforeAfterData {\n before: SanityImageRef;\n after: SanityImageRef;\n title?: string;\n description?: string;\n defaultPosition?: number;\n}\n\nexport interface PortfolioMetricsDataPoint {\n month: string;\n label: string;\n metrics: {\n traffic?: number;\n conversions?: number;\n revenue?: number;\n rankings?: number;\n };\n}\n\nexport interface PortfolioMetricsTimelineData {\n dataPoints: PortfolioMetricsDataPoint[];\n baselineDate: string;\n title?: string;\n description?: string;\n}\n\nexport interface PortfolioFunnelStage {\n label: string;\n value: number;\n icon?: string;\n color?: string;\n}\n\nexport interface PortfolioConversionFunnelData {\n stages: PortfolioFunnelStage[];\n title?: string;\n description?: string;\n}\n\nexport interface PortfolioDetailsData {\n industry: string;\n location?: string;\n website?: string;\n timeline?: string;\n launchDate?: string;\n budgetRange?: string;\n}\n\nexport interface PortfolioSeoData {\n metaTitle: string;\n metaDescription: string;\n keywords: string[];\n ogImage?: SanityImageRef;\n}\n\nexport interface PortfolioCTAData {\n headline: string;\n description: string;\n buttonText: string;\n buttonUrl: string;\n style?: 'primary' | 'glass';\n}\n\n// ---------------------------------------------------------------------------\n// Portfolio Sections\n// ---------------------------------------------------------------------------\n\nexport const PORTFOLIO_SECTION_TYPES = [\n 'portfolioHero',\n 'portfolioChallenges',\n 'portfolioStrategy',\n 'portfolioResults',\n 'portfolioTechStack',\n 'portfolioServices',\n 'portfolioTestimonial',\n 'portfolioGallery',\n 'portfolioVideo',\n 'portfolioTeam',\n 'portfolioFeatureSpotlight',\n 'portfolioBeforeAfter',\n 'portfolioMetricsTimeline',\n 'portfolioConversionFunnel',\n 'portfolioPerformance',\n 'portfolioSpeedComparison',\n 'portfolioSiteArchitecture',\n 'portfolioDesignSystem',\n 'portfolioDetails',\n 'portfolioSeo',\n 'portfolioCTA',\n] as const;\n\nexport type PortfolioSectionType = (typeof PORTFOLIO_SECTION_TYPES)[number];\n\nexport type PortfolioSectionData =\n | PortfolioHeroData\n | PortfolioChallengesData\n | PortfolioStrategyData\n | PortfolioResultsData\n | PortfolioTechStackData\n | PortfolioServicesData\n | PortfolioTestimonialData\n | PortfolioGalleryData\n | PortfolioVideoData\n | PortfolioTeamData\n | PortfolioFeatureSpotlightData\n | PortfolioBeforeAfterData\n | PortfolioMetricsTimelineData\n | PortfolioConversionFunnelData\n | PortfolioDetailsData\n | PortfolioSeoData\n | PortfolioCTAData;\n\nexport interface PortfolioSectionDataMap {\n portfolioHero: PortfolioHeroData;\n portfolioChallenges: PortfolioChallengesData;\n portfolioStrategy: PortfolioStrategyData;\n portfolioResults: PortfolioResultsData;\n portfolioTechStack: PortfolioTechStackData;\n portfolioServices: PortfolioServicesData;\n portfolioTestimonial: PortfolioTestimonialData;\n portfolioGallery: PortfolioGalleryData;\n portfolioVideo: PortfolioVideoData;\n portfolioTeam: PortfolioTeamData;\n portfolioFeatureSpotlight: PortfolioFeatureSpotlightData;\n portfolioBeforeAfter: PortfolioBeforeAfterData;\n portfolioMetricsTimeline: PortfolioMetricsTimelineData;\n portfolioConversionFunnel: PortfolioConversionFunnelData;\n portfolioDetails: PortfolioDetailsData;\n portfolioSeo: PortfolioSeoData;\n portfolioCTA: PortfolioCTAData;\n}\n\nexport interface PortfolioSection<T extends PortfolioSectionType = PortfolioSectionType> {\n sectionType: T;\n displayName: string;\n data: T extends keyof PortfolioSectionDataMap ? PortfolioSectionDataMap[T] : PortfolioSectionData;\n}\n\n// ---------------------------------------------------------------------------\n// Portfolio Item (mirrors public API response)\n// ---------------------------------------------------------------------------\n\nexport interface PortfolioItem {\n id: string;\n slug: string;\n title: string;\n subtitle: string;\n category: string;\n services: string[];\n description: string;\n hero_image: string;\n hero_image_alt: string;\n live_url: string | null;\n kpis: PortfolioKPI[];\n details: PortfolioDetailsData | null;\n seo: PortfolioSeoData | null;\n featured: boolean;\n order?: number;\n published_at: string | null;\n /** Sonor project ID (present for items generated from a managed project) */\n project_id: string | null;\n hero_screenshots: {\n desktop?: string;\n tablet?: string;\n mobile?: string;\n } | null;\n baseline_metrics: MetricsSnapshot | null;\n current_metrics: MetricsSnapshot | null;\n metrics_last_refreshed_at: string | null;\n}\n\nexport interface PortfolioItemFull extends PortfolioItem {\n sections: PortfolioSection[];\n metricsDelta: MetricsDelta[];\n /**\n * True once the agency deliberately reordered sections in the dashboard.\n * PortfolioPage respects the sections array order when set (hero pinned\n * first); otherwise it applies the canonical order.\n */\n sectionsOrdered?: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// API Responses\n// ---------------------------------------------------------------------------\n\nexport interface PortfolioListResponse {\n items: PortfolioItem[];\n total: number;\n limit: number;\n offset: number;\n}\n\nexport interface PortfolioConfigResponse {\n brand: BrandConfig;\n orgName: string;\n orgSlug: string;\n}\n"]}