@sonordev/agency-site-kit 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +302 -0
- package/bin/setup.mjs +79 -0
- package/dist/{chunk-RXBK2EOE.js → chunk-4IES23GV.js} +3 -3
- package/dist/{chunk-RXBK2EOE.js.map → chunk-4IES23GV.js.map} +1 -1
- package/dist/chunk-4N7ENJ7L.cjs +108 -0
- package/dist/chunk-4N7ENJ7L.cjs.map +1 -0
- package/dist/{chunk-WNR4KNG4.cjs → chunk-5MEZU3OG.cjs} +4 -4
- package/dist/{chunk-WNR4KNG4.cjs.map → chunk-5MEZU3OG.cjs.map} +1 -1
- package/dist/chunk-7ZHRMWJU.cjs +306 -0
- package/dist/chunk-7ZHRMWJU.cjs.map +1 -0
- package/dist/chunk-CVT7I5BQ.js +71 -0
- package/dist/chunk-CVT7I5BQ.js.map +1 -0
- package/dist/chunk-HQGAX2LI.cjs +8 -0
- package/dist/chunk-HQGAX2LI.cjs.map +1 -0
- package/dist/chunk-KXLROAO5.js +291 -0
- package/dist/chunk-KXLROAO5.js.map +1 -0
- package/dist/chunk-L4EOQVRJ.js +6 -0
- package/dist/chunk-L4EOQVRJ.js.map +1 -0
- package/dist/chunk-L7E3UHQC.cjs +535 -0
- package/dist/chunk-L7E3UHQC.cjs.map +1 -0
- package/dist/chunk-NI4FVCLS.cjs +32 -0
- package/dist/chunk-NI4FVCLS.cjs.map +1 -0
- package/dist/chunk-NIZJLONK.js +106 -0
- package/dist/chunk-NIZJLONK.js.map +1 -0
- package/dist/chunk-QDXZ27R7.js +33 -0
- package/dist/chunk-QDXZ27R7.js.map +1 -0
- package/dist/{chunk-TVFXN6W6.js → chunk-QL6KVIFR.js} +8 -4
- package/dist/chunk-QL6KVIFR.js.map +1 -0
- package/dist/chunk-UNTOEWQL.js +504 -0
- package/dist/chunk-UNTOEWQL.js.map +1 -0
- package/dist/chunk-XJ434N7I.js +30 -0
- package/dist/chunk-XJ434N7I.js.map +1 -0
- package/dist/chunk-XLK77USA.js +45 -0
- package/dist/chunk-XLK77USA.js.map +1 -0
- package/dist/{chunk-EE75WRCM.cjs → chunk-XOWSXZYW.cjs} +8 -4
- package/dist/chunk-XOWSXZYW.cjs.map +1 -0
- package/dist/chunk-ZINKVJQU.js +67 -0
- package/dist/chunk-ZINKVJQU.js.map +1 -0
- package/dist/index.cjs +29 -31
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +17 -9
- package/dist/index.d.ts +17 -9
- package/dist/index.js +7 -9
- package/dist/index.js.map +1 -1
- package/dist/layout/index.cjs +3 -3
- package/dist/layout/index.d.cts +1 -1
- package/dist/layout/index.d.ts +1 -1
- package/dist/layout/index.js +2 -2
- package/dist/portfolio/client.cjs +342 -12
- package/dist/portfolio/client.cjs.map +1 -1
- package/dist/portfolio/client.d.cts +11 -88
- package/dist/portfolio/client.d.ts +11 -88
- package/dist/portfolio/client.js +150 -4
- package/dist/portfolio/client.js.map +1 -1
- package/dist/portfolio/components/PortfolioAnalytics.cjs +87 -0
- package/dist/portfolio/components/PortfolioAnalytics.cjs.map +1 -0
- package/dist/portfolio/components/PortfolioAnalytics.d.cts +5 -0
- package/dist/portfolio/components/PortfolioAnalytics.d.ts +5 -0
- package/dist/portfolio/components/PortfolioAnalytics.js +9 -0
- package/dist/portfolio/components/PortfolioAnalytics.js.map +1 -0
- package/dist/portfolio/components/PortfolioErrorFallback.cjs +65 -0
- package/dist/portfolio/components/PortfolioErrorFallback.cjs.map +1 -0
- package/dist/portfolio/components/PortfolioErrorFallback.d.cts +22 -0
- package/dist/portfolio/components/PortfolioErrorFallback.d.ts +22 -0
- package/dist/portfolio/components/PortfolioErrorFallback.js +9 -0
- package/dist/portfolio/components/PortfolioErrorFallback.js.map +1 -0
- package/dist/portfolio/components/PortfolioLeadCTA.cjs +91 -0
- package/dist/portfolio/components/PortfolioLeadCTA.cjs.map +1 -0
- package/dist/portfolio/components/PortfolioLeadCTA.d.cts +34 -0
- package/dist/portfolio/components/PortfolioLeadCTA.d.ts +34 -0
- package/dist/portfolio/components/PortfolioLeadCTA.js +9 -0
- package/dist/portfolio/components/PortfolioLeadCTA.js.map +1 -0
- package/dist/portfolio/index.cjs +19 -20
- package/dist/portfolio/index.d.cts +133 -7
- package/dist/portfolio/index.d.ts +133 -7
- package/dist/portfolio/index.js +6 -7
- package/dist/portfolio/primitives/AnimatedCounter.cjs +88 -0
- package/dist/portfolio/primitives/AnimatedCounter.cjs.map +1 -0
- package/dist/portfolio/primitives/AnimatedCounter.d.cts +22 -0
- package/dist/portfolio/primitives/AnimatedCounter.d.ts +22 -0
- package/dist/portfolio/primitives/AnimatedCounter.js +68 -0
- package/dist/portfolio/primitives/AnimatedCounter.js.map +1 -0
- package/dist/portfolio/primitives/AnnotatedImage.cjs +320 -0
- package/dist/portfolio/primitives/AnnotatedImage.cjs.map +1 -0
- package/dist/portfolio/primitives/AnnotatedImage.d.cts +18 -0
- package/dist/portfolio/primitives/AnnotatedImage.d.ts +18 -0
- package/dist/portfolio/primitives/AnnotatedImage.js +264 -0
- package/dist/portfolio/primitives/AnnotatedImage.js.map +1 -0
- package/dist/portfolio/primitives/BeforeAfterSlider.cjs +348 -0
- package/dist/portfolio/primitives/BeforeAfterSlider.cjs.map +1 -0
- package/dist/portfolio/primitives/BeforeAfterSlider.d.cts +13 -0
- package/dist/portfolio/primitives/BeforeAfterSlider.d.ts +13 -0
- package/dist/portfolio/primitives/BeforeAfterSlider.js +292 -0
- package/dist/portfolio/primitives/BeforeAfterSlider.js.map +1 -0
- package/dist/portfolio/primitives/DeviceTrifolio.cjs +298 -0
- package/dist/portfolio/primitives/DeviceTrifolio.cjs.map +1 -0
- package/dist/portfolio/primitives/DeviceTrifolio.d.cts +14 -0
- package/dist/portfolio/primitives/DeviceTrifolio.d.ts +14 -0
- package/dist/portfolio/primitives/DeviceTrifolio.js +242 -0
- package/dist/portfolio/primitives/DeviceTrifolio.js.map +1 -0
- package/dist/portfolio/primitives/FrequencyBarSkeleton.cjs +88 -0
- package/dist/portfolio/primitives/FrequencyBarSkeleton.cjs.map +1 -0
- package/dist/portfolio/primitives/FrequencyBarSkeleton.d.cts +10 -0
- package/dist/portfolio/primitives/FrequencyBarSkeleton.d.ts +10 -0
- package/dist/portfolio/primitives/FrequencyBarSkeleton.js +68 -0
- package/dist/portfolio/primitives/FrequencyBarSkeleton.js.map +1 -0
- package/dist/portfolio/primitives/GlassCard.cjs +45 -0
- package/dist/portfolio/primitives/GlassCard.cjs.map +1 -0
- package/dist/portfolio/primitives/GlassCard.d.cts +11 -0
- package/dist/portfolio/primitives/GlassCard.d.ts +11 -0
- package/dist/portfolio/primitives/GlassCard.js +43 -0
- package/dist/portfolio/primitives/GlassCard.js.map +1 -0
- package/dist/portfolio/primitives/MetricBadge.cjs +74 -0
- package/dist/portfolio/primitives/MetricBadge.cjs.map +1 -0
- package/dist/portfolio/primitives/MetricBadge.d.cts +9 -0
- package/dist/portfolio/primitives/MetricBadge.d.ts +9 -0
- package/dist/portfolio/primitives/MetricBadge.js +54 -0
- package/dist/portfolio/primitives/MetricBadge.js.map +1 -0
- package/dist/portfolio/primitives/SVGChart.cjs +316 -0
- package/dist/portfolio/primitives/SVGChart.cjs.map +1 -0
- package/dist/portfolio/primitives/SVGChart.d.cts +18 -0
- package/dist/portfolio/primitives/SVGChart.d.ts +18 -0
- package/dist/portfolio/primitives/SVGChart.js +296 -0
- package/dist/portfolio/primitives/SVGChart.js.map +1 -0
- package/dist/portfolio/primitives/ScrollReveal.cjs +97 -0
- package/dist/portfolio/primitives/ScrollReveal.cjs.map +1 -0
- package/dist/portfolio/primitives/ScrollReveal.d.cts +27 -0
- package/dist/portfolio/primitives/ScrollReveal.d.ts +27 -0
- package/dist/portfolio/primitives/ScrollReveal.js +77 -0
- package/dist/portfolio/primitives/ScrollReveal.js.map +1 -0
- package/dist/portfolio/primitives/SplitHeadline.cjs +107 -0
- package/dist/portfolio/primitives/SplitHeadline.cjs.map +1 -0
- package/dist/portfolio/primitives/SplitHeadline.d.cts +24 -0
- package/dist/portfolio/primitives/SplitHeadline.d.ts +24 -0
- package/dist/portfolio/primitives/SplitHeadline.js +87 -0
- package/dist/portfolio/primitives/SplitHeadline.js.map +1 -0
- package/dist/portfolio/sections/BeforeAfterSection.cjs +271 -0
- package/dist/portfolio/sections/BeforeAfterSection.cjs.map +1 -0
- package/dist/portfolio/sections/BeforeAfterSection.d.cts +9 -0
- package/dist/portfolio/sections/BeforeAfterSection.d.ts +9 -0
- package/dist/{BeforeAfterSection-6BHFLY4Y.js → portfolio/sections/BeforeAfterSection.js} +66 -27
- package/dist/portfolio/sections/BeforeAfterSection.js.map +1 -0
- package/dist/portfolio/sections/CTASection.cjs +123 -0
- package/dist/portfolio/sections/CTASection.cjs.map +1 -0
- package/dist/portfolio/sections/CTASection.d.cts +9 -0
- package/dist/portfolio/sections/CTASection.d.ts +9 -0
- package/dist/portfolio/sections/CTASection.js +93 -0
- package/dist/portfolio/sections/CTASection.js.map +1 -0
- package/dist/{ChallengesSection-IZ3DHECS.cjs → portfolio/sections/ChallengesSection.cjs} +12 -6
- package/dist/portfolio/sections/ChallengesSection.cjs.map +1 -0
- package/dist/portfolio/sections/ChallengesSection.d.cts +9 -0
- package/dist/portfolio/sections/ChallengesSection.d.ts +9 -0
- package/dist/{ChallengesSection-GEQGVSJN.js → portfolio/sections/ChallengesSection.js} +5 -4
- package/dist/portfolio/sections/ChallengesSection.js.map +1 -0
- package/dist/portfolio/sections/ConversionFunnelSection.cjs +263 -0
- package/dist/portfolio/sections/ConversionFunnelSection.cjs.map +1 -0
- package/dist/portfolio/sections/ConversionFunnelSection.d.cts +9 -0
- package/dist/portfolio/sections/ConversionFunnelSection.d.ts +9 -0
- package/dist/{ConversionFunnelSection-D3GE4NKE.js → portfolio/sections/ConversionFunnelSection.js} +60 -56
- package/dist/portfolio/sections/ConversionFunnelSection.js.map +1 -0
- package/dist/{DesignSystemSection-KXIQXITF.cjs → portfolio/sections/DesignSystemSection.cjs} +13 -7
- package/dist/portfolio/sections/DesignSystemSection.cjs.map +1 -0
- package/dist/portfolio/sections/DesignSystemSection.d.cts +32 -0
- package/dist/portfolio/sections/DesignSystemSection.d.ts +32 -0
- package/dist/{DesignSystemSection-2R5BRBGO.js → portfolio/sections/DesignSystemSection.js} +5 -4
- package/dist/portfolio/sections/DesignSystemSection.js.map +1 -0
- package/dist/{DetailsSection-TTUZAPZZ.cjs → portfolio/sections/DetailsSection.cjs} +12 -6
- package/dist/portfolio/sections/DetailsSection.cjs.map +1 -0
- package/dist/portfolio/sections/DetailsSection.d.cts +9 -0
- package/dist/portfolio/sections/DetailsSection.d.ts +9 -0
- package/dist/{DetailsSection-A6PZQUQL.js → portfolio/sections/DetailsSection.js} +5 -4
- package/dist/portfolio/sections/DetailsSection.js.map +1 -0
- package/dist/portfolio/sections/FeatureSpotlightSection.cjs +267 -0
- package/dist/portfolio/sections/FeatureSpotlightSection.cjs.map +1 -0
- package/dist/portfolio/sections/FeatureSpotlightSection.d.cts +9 -0
- package/dist/portfolio/sections/FeatureSpotlightSection.d.ts +9 -0
- package/dist/{FeatureSpotlightSection-B7P3JGNL.js → portfolio/sections/FeatureSpotlightSection.js} +39 -33
- package/dist/portfolio/sections/FeatureSpotlightSection.js.map +1 -0
- package/dist/portfolio/sections/GallerySection.cjs +321 -0
- package/dist/portfolio/sections/GallerySection.cjs.map +1 -0
- package/dist/portfolio/sections/GallerySection.d.cts +9 -0
- package/dist/portfolio/sections/GallerySection.d.ts +9 -0
- package/dist/{GallerySection-VMKORC47.js → portfolio/sections/GallerySection.js} +65 -18
- package/dist/portfolio/sections/GallerySection.js.map +1 -0
- package/dist/portfolio/sections/HeroSection.cjs +180 -0
- package/dist/portfolio/sections/HeroSection.cjs.map +1 -0
- package/dist/portfolio/sections/HeroSection.d.cts +15 -0
- package/dist/portfolio/sections/HeroSection.d.ts +15 -0
- package/dist/portfolio/sections/HeroSection.js +171 -0
- package/dist/portfolio/sections/HeroSection.js.map +1 -0
- package/dist/{MetricsTimelineSection-4L6DUHJ5.cjs → portfolio/sections/MetricsTimelineSection.cjs} +102 -61
- package/dist/portfolio/sections/MetricsTimelineSection.cjs.map +1 -0
- package/dist/portfolio/sections/MetricsTimelineSection.d.cts +9 -0
- package/dist/portfolio/sections/MetricsTimelineSection.d.ts +9 -0
- package/dist/{MetricsTimelineSection-6BT5GNFV.js → portfolio/sections/MetricsTimelineSection.js} +45 -29
- package/dist/portfolio/sections/MetricsTimelineSection.js.map +1 -0
- package/dist/{PerformanceSection-24TVVFZA.cjs → portfolio/sections/PerformanceSection.cjs} +136 -93
- package/dist/portfolio/sections/PerformanceSection.cjs.map +1 -0
- package/dist/portfolio/sections/PerformanceSection.d.cts +22 -0
- package/dist/portfolio/sections/PerformanceSection.d.ts +22 -0
- package/dist/{PerformanceSection-MGCEIXDX.js → portfolio/sections/PerformanceSection.js} +58 -40
- package/dist/portfolio/sections/PerformanceSection.js.map +1 -0
- package/dist/{ResultsSection-XLGMMQKY.cjs → portfolio/sections/ResultsSection.cjs} +22 -10
- package/dist/portfolio/sections/ResultsSection.cjs.map +1 -0
- package/dist/portfolio/sections/ResultsSection.d.cts +13 -0
- package/dist/portfolio/sections/ResultsSection.d.ts +13 -0
- package/dist/{ResultsSection-DFUJ5U6M.js → portfolio/sections/ResultsSection.js} +13 -7
- package/dist/portfolio/sections/ResultsSection.js.map +1 -0
- package/dist/portfolio/sections/ServicesSection.cjs +129 -0
- package/dist/portfolio/sections/ServicesSection.cjs.map +1 -0
- package/dist/portfolio/sections/ServicesSection.d.cts +9 -0
- package/dist/portfolio/sections/ServicesSection.d.ts +9 -0
- package/dist/portfolio/sections/ServicesSection.js +122 -0
- package/dist/portfolio/sections/ServicesSection.js.map +1 -0
- package/dist/{SiteArchitectureSection-J7BPBERH.cjs → portfolio/sections/SiteArchitectureSection.cjs} +116 -92
- package/dist/portfolio/sections/SiteArchitectureSection.cjs.map +1 -0
- package/dist/portfolio/sections/SiteArchitectureSection.d.cts +21 -0
- package/dist/portfolio/sections/SiteArchitectureSection.d.ts +21 -0
- package/dist/{SiteArchitectureSection-TE22OAWV.js → portfolio/sections/SiteArchitectureSection.js} +46 -47
- package/dist/portfolio/sections/SiteArchitectureSection.js.map +1 -0
- package/dist/{SpeedComparisonSection-EZKFQVGW.cjs → portfolio/sections/SpeedComparisonSection.cjs} +12 -6
- package/dist/portfolio/sections/SpeedComparisonSection.cjs.map +1 -0
- package/dist/portfolio/sections/SpeedComparisonSection.d.cts +20 -0
- package/dist/portfolio/sections/SpeedComparisonSection.d.ts +20 -0
- package/dist/{SpeedComparisonSection-Y3K7OFZQ.js → portfolio/sections/SpeedComparisonSection.js} +5 -4
- package/dist/portfolio/sections/SpeedComparisonSection.js.map +1 -0
- package/dist/portfolio/sections/StrategySection.cjs +233 -0
- package/dist/portfolio/sections/StrategySection.cjs.map +1 -0
- package/dist/portfolio/sections/StrategySection.d.cts +9 -0
- package/dist/portfolio/sections/StrategySection.d.ts +9 -0
- package/dist/{StrategySection-DI5RSCJU.js → portfolio/sections/StrategySection.js} +60 -26
- package/dist/portfolio/sections/StrategySection.js.map +1 -0
- package/dist/portfolio/sections/TeamSection.cjs +162 -0
- package/dist/portfolio/sections/TeamSection.cjs.map +1 -0
- package/dist/portfolio/sections/TeamSection.d.cts +9 -0
- package/dist/portfolio/sections/TeamSection.d.ts +9 -0
- package/dist/portfolio/sections/TeamSection.js +106 -0
- package/dist/portfolio/sections/TeamSection.js.map +1 -0
- package/dist/portfolio/sections/TechStackSection.cjs +106 -0
- package/dist/portfolio/sections/TechStackSection.cjs.map +1 -0
- package/dist/portfolio/sections/TechStackSection.d.cts +9 -0
- package/dist/portfolio/sections/TechStackSection.d.ts +9 -0
- package/dist/portfolio/sections/TechStackSection.js +99 -0
- package/dist/portfolio/sections/TechStackSection.js.map +1 -0
- package/dist/{TestimonialSection-XPTFUQIN.cjs → portfolio/sections/TestimonialSection.cjs} +47 -28
- package/dist/portfolio/sections/TestimonialSection.cjs.map +1 -0
- package/dist/portfolio/sections/TestimonialSection.d.cts +9 -0
- package/dist/portfolio/sections/TestimonialSection.d.ts +9 -0
- package/dist/{TestimonialSection-6RGSMXQB.js → portfolio/sections/TestimonialSection.js} +40 -27
- package/dist/portfolio/sections/TestimonialSection.js.map +1 -0
- package/dist/portfolio/sections/VideoSection.cjs +181 -0
- package/dist/portfolio/sections/VideoSection.cjs.map +1 -0
- package/dist/portfolio/sections/VideoSection.d.cts +9 -0
- package/dist/portfolio/sections/VideoSection.d.ts +9 -0
- package/dist/{VideoSection-4A2HC6K6.js → portfolio/sections/VideoSection.js} +21 -13
- package/dist/portfolio/sections/VideoSection.js.map +1 -0
- package/dist/portfolio/server.cjs +56 -144
- package/dist/portfolio/server.cjs.map +1 -1
- package/dist/portfolio/server.d.cts +157 -7
- package/dist/portfolio/server.d.ts +157 -7
- package/dist/portfolio/server.js +3 -142
- package/dist/portfolio/server.js.map +1 -1
- package/dist/preview.cjs +56 -0
- package/dist/preview.cjs.map +1 -0
- package/dist/preview.d.cts +31 -0
- package/dist/preview.d.ts +31 -0
- package/dist/preview.js +54 -0
- package/dist/preview.js.map +1 -0
- package/dist/{types-DL4t_Cfa.d.ts → types-0tVPy4pU.d.cts} +7 -1
- package/dist/{types-DL4t_Cfa.d.cts → types-0tVPy4pU.d.ts} +7 -1
- package/dist/types-D0nb6KAx.d.cts +299 -0
- package/dist/types-D0nb6KAx.d.ts +299 -0
- package/package.json +38 -14
- package/templates/api-portfolio-preview.ts +6 -0
- package/templates/api-revalidate.ts +5 -0
- package/templates/work-category-page.tsx +40 -0
- package/templates/work-error.tsx +3 -0
- package/templates/work-loading.tsx +1 -0
- package/templates/work-page.tsx +37 -0
- package/templates/work-slug-loading.tsx +1 -0
- package/templates/work-slug-page.tsx +41 -0
- package/dist/BeforeAfterSection-6BHFLY4Y.js.map +0 -1
- package/dist/BeforeAfterSection-JTORBR3A.cjs +0 -181
- package/dist/BeforeAfterSection-JTORBR3A.cjs.map +0 -1
- package/dist/CTASection-4JKLXEUF.cjs +0 -111
- package/dist/CTASection-4JKLXEUF.cjs.map +0 -1
- package/dist/CTASection-BJA72XIL.js +0 -106
- package/dist/CTASection-BJA72XIL.js.map +0 -1
- package/dist/ChallengesSection-GEQGVSJN.js.map +0 -1
- package/dist/ChallengesSection-IZ3DHECS.cjs.map +0 -1
- package/dist/ConversionFunnelSection-AUUSJ5HQ.cjs +0 -209
- package/dist/ConversionFunnelSection-AUUSJ5HQ.cjs.map +0 -1
- package/dist/ConversionFunnelSection-D3GE4NKE.js.map +0 -1
- package/dist/DesignSystemSection-2R5BRBGO.js.map +0 -1
- package/dist/DesignSystemSection-KXIQXITF.cjs.map +0 -1
- package/dist/DetailsSection-A6PZQUQL.js.map +0 -1
- package/dist/DetailsSection-TTUZAPZZ.cjs.map +0 -1
- package/dist/FeatureSpotlightSection-B7P3JGNL.js.map +0 -1
- package/dist/FeatureSpotlightSection-WRHXS7TU.cjs +0 -210
- package/dist/FeatureSpotlightSection-WRHXS7TU.cjs.map +0 -1
- package/dist/GallerySection-VMKORC47.js.map +0 -1
- package/dist/GallerySection-WJ4PQDBI.cjs +0 -219
- package/dist/GallerySection-WJ4PQDBI.cjs.map +0 -1
- package/dist/MetricsTimelineSection-4L6DUHJ5.cjs.map +0 -1
- package/dist/MetricsTimelineSection-6BT5GNFV.js.map +0 -1
- package/dist/PerformanceSection-24TVVFZA.cjs.map +0 -1
- package/dist/PerformanceSection-MGCEIXDX.js.map +0 -1
- package/dist/ResultsSection-DFUJ5U6M.js.map +0 -1
- package/dist/ResultsSection-XLGMMQKY.cjs.map +0 -1
- package/dist/ServicesSection-D5V3Q4GR.js +0 -118
- package/dist/ServicesSection-D5V3Q4GR.js.map +0 -1
- package/dist/ServicesSection-WJMGK2MF.cjs +0 -120
- package/dist/ServicesSection-WJMGK2MF.cjs.map +0 -1
- package/dist/SiteArchitectureSection-J7BPBERH.cjs.map +0 -1
- package/dist/SiteArchitectureSection-TE22OAWV.js.map +0 -1
- package/dist/SpeedComparisonSection-EZKFQVGW.cjs.map +0 -1
- package/dist/SpeedComparisonSection-Y3K7OFZQ.js.map +0 -1
- package/dist/StrategySection-CJ7Y6OFQ.cjs +0 -174
- package/dist/StrategySection-CJ7Y6OFQ.cjs.map +0 -1
- package/dist/StrategySection-DI5RSCJU.js.map +0 -1
- package/dist/TeamSection-DZVSNZE6.cjs +0 -112
- package/dist/TeamSection-DZVSNZE6.cjs.map +0 -1
- package/dist/TeamSection-HGKFW6PQ.js +0 -107
- package/dist/TeamSection-HGKFW6PQ.js.map +0 -1
- package/dist/TechStackSection-2AQ7RGY3.js +0 -93
- package/dist/TechStackSection-2AQ7RGY3.js.map +0 -1
- package/dist/TechStackSection-VTNNZR5V.cjs +0 -95
- package/dist/TechStackSection-VTNNZR5V.cjs.map +0 -1
- package/dist/TestimonialSection-6RGSMXQB.js.map +0 -1
- package/dist/TestimonialSection-XPTFUQIN.cjs.map +0 -1
- package/dist/VideoSection-4A2HC6K6.js.map +0 -1
- package/dist/VideoSection-G3DFS7UH.cjs +0 -118
- package/dist/VideoSection-G3DFS7UH.cjs.map +0 -1
- package/dist/chunk-2Y4O3LWM.js +0 -53
- package/dist/chunk-2Y4O3LWM.js.map +0 -1
- package/dist/chunk-633UMQQH.cjs +0 -31
- package/dist/chunk-633UMQQH.cjs.map +0 -1
- package/dist/chunk-7CFFAKDM.js +0 -74
- package/dist/chunk-7CFFAKDM.js.map +0 -1
- package/dist/chunk-APG2QSMB.js +0 -131
- package/dist/chunk-APG2QSMB.js.map +0 -1
- package/dist/chunk-AVCH4LMG.cjs +0 -620
- package/dist/chunk-AVCH4LMG.cjs.map +0 -1
- package/dist/chunk-AYBUHRFS.js +0 -613
- package/dist/chunk-AYBUHRFS.js.map +0 -1
- package/dist/chunk-EE75WRCM.cjs.map +0 -1
- package/dist/chunk-IKBK7HYX.cjs +0 -79
- package/dist/chunk-IKBK7HYX.cjs.map +0 -1
- package/dist/chunk-KEOHORIH.cjs +0 -79
- package/dist/chunk-KEOHORIH.cjs.map +0 -1
- package/dist/chunk-OA5ZM4OA.cjs +0 -139
- package/dist/chunk-OA5ZM4OA.cjs.map +0 -1
- package/dist/chunk-TVFXN6W6.js.map +0 -1
- package/dist/chunk-VVTK4OL6.js +0 -28
- package/dist/chunk-VVTK4OL6.js.map +0 -1
- package/dist/chunk-XCKXHK44.js +0 -15
- package/dist/chunk-XCKXHK44.js.map +0 -1
- package/dist/chunk-XONXEFJY.cjs +0 -58
- package/dist/chunk-XONXEFJY.cjs.map +0 -1
- package/dist/chunk-XQNJED46.cjs +0 -19
- package/dist/chunk-XQNJED46.cjs.map +0 -1
- package/dist/chunk-YB4B3OMC.js +0 -74
- package/dist/chunk-YB4B3OMC.js.map +0 -1
- package/dist/portfolio/sections.cjs +0 -20
- package/dist/portfolio/sections.cjs.map +0 -1
- package/dist/portfolio/sections.d.cts +0 -42
- package/dist/portfolio/sections.d.ts +0 -42
- package/dist/portfolio/sections.js +0 -4
- package/dist/portfolio/sections.js.map +0 -1
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
"use client";
|
|
3
|
+
import {
|
|
4
|
+
resolvePortfolioImage
|
|
5
|
+
} from "../../chunk-QDXZ27R7.js";
|
|
6
6
|
|
|
7
|
+
// src/portfolio/components/sections/VideoSection.tsx
|
|
8
|
+
import { useState, useCallback } from "react";
|
|
9
|
+
import Image from "next/image";
|
|
10
|
+
import ScrollReveal from "@sonordev/agency-site-kit/portfolio/primitives/ScrollReveal";
|
|
11
|
+
import GlassCard from "@sonordev/agency-site-kit/portfolio/primitives/GlassCard";
|
|
12
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
13
|
function getEmbedUrl(data) {
|
|
8
14
|
if (data.embedUrl) return data.embedUrl;
|
|
9
15
|
if (!data.url) return "";
|
|
@@ -17,7 +23,7 @@ function getEmbedUrl(data) {
|
|
|
17
23
|
}
|
|
18
24
|
function VideoSection({ data }) {
|
|
19
25
|
const [playing, setPlaying] = useState(false);
|
|
20
|
-
const
|
|
26
|
+
const thumbnail = resolvePortfolioImage(data.thumbnail);
|
|
21
27
|
const embedUrl = getEmbedUrl(data);
|
|
22
28
|
const handlePlay = useCallback(() => {
|
|
23
29
|
if (embedUrl) setPlaying(true);
|
|
@@ -56,12 +62,14 @@ function VideoSection({ data }) {
|
|
|
56
62
|
className: "absolute inset-0 w-full h-full group cursor-pointer",
|
|
57
63
|
"aria-label": `Play ${data.title}`,
|
|
58
64
|
children: [
|
|
59
|
-
|
|
60
|
-
|
|
65
|
+
thumbnail && /* @__PURE__ */ jsx(
|
|
66
|
+
Image,
|
|
61
67
|
{
|
|
62
|
-
src:
|
|
68
|
+
src: thumbnail.url,
|
|
63
69
|
alt: data.title,
|
|
64
|
-
|
|
70
|
+
fill: true,
|
|
71
|
+
sizes: "(max-width: 1024px) 100vw, 976px",
|
|
72
|
+
className: "object-cover"
|
|
65
73
|
}
|
|
66
74
|
),
|
|
67
75
|
/* @__PURE__ */ jsx(
|
|
@@ -111,7 +119,7 @@ function VideoSection({ data }) {
|
|
|
111
119
|
}
|
|
112
120
|
);
|
|
113
121
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
//# sourceMappingURL=VideoSection
|
|
122
|
+
export {
|
|
123
|
+
VideoSection as default
|
|
124
|
+
};
|
|
125
|
+
//# sourceMappingURL=VideoSection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/portfolio/components/sections/VideoSection.tsx"],"sourcesContent":["'use client';\n\nimport React, { useState, useCallback } from 'react';\nimport Image from 'next/image';\nimport type { PortfolioVideoData } from '../../../types';\nimport { resolvePortfolioImage } from '../../../shared/images';\nimport ScrollReveal from '@sonordev/agency-site-kit/portfolio/primitives/ScrollReveal';\nimport GlassCard from '@sonordev/agency-site-kit/portfolio/primitives/GlassCard';\n\ninterface VideoSectionProps {\n data: PortfolioVideoData;\n}\n\nfunction getEmbedUrl(data: PortfolioVideoData): string {\n if (data.embedUrl) return data.embedUrl;\n if (!data.url) return '';\n\n // Parse YouTube URLs\n const ytMatch = data.url.match(\n /(?:youtube\\.com\\/(?:watch\\?v=|embed\\/)|youtu\\.be\\/)([a-zA-Z0-9_-]{11})/,\n );\n if (ytMatch) return `https://www.youtube.com/embed/${ytMatch[1]}?autoplay=1`;\n\n // Parse Vimeo URLs\n const vimeoMatch = data.url.match(/vimeo\\.com\\/(\\d+)/);\n if (vimeoMatch) return `https://player.vimeo.com/video/${vimeoMatch[1]}?autoplay=1`;\n\n return data.url;\n}\n\nexport default function VideoSection({ data }: VideoSectionProps) {\n const [playing, setPlaying] = useState(false);\n\n const thumbnail = resolvePortfolioImage(data.thumbnail);\n const embedUrl = getEmbedUrl(data);\n\n const handlePlay = useCallback(() => {\n if (embedUrl) setPlaying(true);\n }, [embedUrl]);\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 {data.title}\n </h2>\n </ScrollReveal>\n\n <ScrollReveal y={40} delay={0.15}>\n <GlassCard padding=\"sm\" hover={false}>\n <div className=\"relative w-full overflow-hidden rounded-xl\" style={{ aspectRatio: '16/9' }}>\n {playing ? (\n <iframe\n src={embedUrl}\n className=\"absolute inset-0 w-full h-full\"\n allow=\"autoplay; fullscreen; picture-in-picture\"\n allowFullScreen\n title={data.title}\n style={{ border: 'none' }}\n />\n ) : (\n <button\n onClick={handlePlay}\n className=\"absolute inset-0 w-full h-full group cursor-pointer\"\n aria-label={`Play ${data.title}`}\n >\n {/* Thumbnail */}\n {thumbnail && (\n <Image\n src={thumbnail.url}\n alt={data.title}\n fill\n sizes=\"(max-width: 1024px) 100vw, 976px\"\n className=\"object-cover\"\n />\n )}\n\n {/* Dark overlay */}\n <div\n className=\"absolute inset-0 transition-opacity duration-300 group-hover:opacity-60\"\n style={{ background: 'rgba(0,0,0,0.4)', opacity: 0.5 }}\n />\n\n {/* Play button */}\n <div className=\"absolute inset-0 flex items-center justify-center\">\n <div\n className=\"flex items-center justify-center w-20 h-20 rounded-full transition-transform duration-300 group-hover:scale-110\"\n style={{\n background: 'var(--sk-primary, #6366f1)',\n boxShadow: '0 8px 32px color-mix(in srgb, var(--sk-primary, #6366f1) 40%, transparent)',\n }}\n >\n <svg\n width=\"32\"\n height=\"32\"\n viewBox=\"0 0 32 32\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path d=\"M12 8l14 8-14 8V8z\" fill=\"#ffffff\" />\n </svg>\n </div>\n </div>\n\n {/* Duration badge */}\n {data.duration && (\n <span\n className=\"absolute bottom-4 right-4 px-3 py-1 rounded-lg text-xs font-medium\"\n style={{\n background: 'rgba(0,0,0,0.6)',\n color: '#ffffff',\n backdropFilter: 'blur(4px)',\n }}\n >\n {data.duration}\n </span>\n )}\n </button>\n )}\n </div>\n </GlassCard>\n </ScrollReveal>\n </div>\n </section>\n );\n}\n"],"mappings":";;;;;;;AAEA,SAAgB,UAAU,mBAAmB;AAC7C,OAAO,WAAW;AAGlB,OAAO,kBAAkB;AACzB,OAAO,eAAe;AAwCZ,cAwBM,YAxBN;AAlCV,SAAS,YAAY,MAAkC;AACrD,MAAI,KAAK,SAAU,QAAO,KAAK;AAC/B,MAAI,CAAC,KAAK,IAAK,QAAO;AAGtB,QAAM,UAAU,KAAK,IAAI;AAAA,IACvB;AAAA,EACF;AACA,MAAI,QAAS,QAAO,iCAAiC,QAAQ,CAAC,CAAC;AAG/D,QAAM,aAAa,KAAK,IAAI,MAAM,mBAAmB;AACrD,MAAI,WAAY,QAAO,kCAAkC,WAAW,CAAC,CAAC;AAEtE,SAAO,KAAK;AACd;AAEe,SAAR,aAA8B,EAAE,KAAK,GAAsB;AAChE,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,KAAK;AAE5C,QAAM,YAAY,sBAAsB,KAAK,SAAS;AACtD,QAAM,WAAW,YAAY,IAAI;AAEjC,QAAM,aAAa,YAAY,MAAM;AACnC,QAAI,SAAU,YAAW,IAAI;AAAA,EAC/B,GAAG,CAAC,QAAQ,CAAC;AAEb,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAU;AAAA,MACV,OAAO,EAAE,YAAY,wBAAwB;AAAA,MAE7C,+BAAC,SAAI,WAAU,0BACb;AAAA,4BAAC,gBAAa,GAAG,IACf;AAAA,UAAC;AAAA;AAAA,YACC,WAAU;AAAA,YACV,OAAO;AAAA,cACL,OAAO;AAAA,cACP,YAAY;AAAA,YACd;AAAA,YAEC,eAAK;AAAA;AAAA,QACR,GACF;AAAA,QAEA,oBAAC,gBAAa,GAAG,IAAI,OAAO,MAC1B,8BAAC,aAAU,SAAQ,MAAK,OAAO,OAC7B,8BAAC,SAAI,WAAU,8CAA6C,OAAO,EAAE,aAAa,OAAO,GACtF,oBACC;AAAA,UAAC;AAAA;AAAA,YACC,KAAK;AAAA,YACL,WAAU;AAAA,YACV,OAAM;AAAA,YACN,iBAAe;AAAA,YACf,OAAO,KAAK;AAAA,YACZ,OAAO,EAAE,QAAQ,OAAO;AAAA;AAAA,QAC1B,IAEA;AAAA,UAAC;AAAA;AAAA,YACC,SAAS;AAAA,YACT,WAAU;AAAA,YACV,cAAY,QAAQ,KAAK,KAAK;AAAA,YAG7B;AAAA,2BACC;AAAA,gBAAC;AAAA;AAAA,kBACC,KAAK,UAAU;AAAA,kBACf,KAAK,KAAK;AAAA,kBACV,MAAI;AAAA,kBACJ,OAAM;AAAA,kBACN,WAAU;AAAA;AAAA,cACZ;AAAA,cAIF;AAAA,gBAAC;AAAA;AAAA,kBACC,WAAU;AAAA,kBACV,OAAO,EAAE,YAAY,mBAAmB,SAAS,IAAI;AAAA;AAAA,cACvD;AAAA,cAGA,oBAAC,SAAI,WAAU,qDACb;AAAA,gBAAC;AAAA;AAAA,kBACC,WAAU;AAAA,kBACV,OAAO;AAAA,oBACL,YAAY;AAAA,oBACZ,WAAW;AAAA,kBACb;AAAA,kBAEA;AAAA,oBAAC;AAAA;AAAA,sBACC,OAAM;AAAA,sBACN,QAAO;AAAA,sBACP,SAAQ;AAAA,sBACR,MAAK;AAAA,sBACL,OAAM;AAAA,sBAEN,8BAAC,UAAK,GAAE,sBAAqB,MAAK,WAAU;AAAA;AAAA,kBAC9C;AAAA;AAAA,cACF,GACF;AAAA,cAGC,KAAK,YACJ;AAAA,gBAAC;AAAA;AAAA,kBACC,WAAU;AAAA,kBACV,OAAO;AAAA,oBACL,YAAY;AAAA,oBACZ,OAAO;AAAA,oBACP,gBAAgB;AAAA,kBAClB;AAAA,kBAEC,eAAK;AAAA;AAAA,cACR;AAAA;AAAA;AAAA,QAEJ,GAEJ,GACF,GACF;AAAA,SACF;AAAA;AAAA,EACF;AAEJ;","names":[]}
|
|
@@ -1,155 +1,67 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
4
|
-
var chunkEE75WRCM_cjs = require('../chunk-EE75WRCM.cjs');
|
|
3
|
+
var chunk7ZHRMWJU_cjs = require('../chunk-7ZHRMWJU.cjs');
|
|
5
4
|
var chunkFCKDJ4MJ_cjs = require('../chunk-FCKDJ4MJ.cjs');
|
|
5
|
+
require('../chunk-HQGAX2LI.cjs');
|
|
6
|
+
require('../chunk-XOWSXZYW.cjs');
|
|
6
7
|
|
|
7
|
-
// src/portfolio/server.ts
|
|
8
|
-
var LOG_PREFIX = "[@sonordev/agency-site-kit/portfolio]";
|
|
9
|
-
function portfolioGet(path) {
|
|
10
|
-
return chunkEE75WRCM_cjs.apiFetch(path, { method: "GET", tags: [chunkFCKDJ4MJ_cjs.PORTFOLIO_CACHE_TAG] });
|
|
11
|
-
}
|
|
12
|
-
function buildQuery(params) {
|
|
13
|
-
const entries = Object.entries(params).filter(
|
|
14
|
-
(entry) => entry[1] != null
|
|
15
|
-
);
|
|
16
|
-
if (entries.length === 0) return "";
|
|
17
|
-
const qs = new URLSearchParams(
|
|
18
|
-
entries.map(([k, v]) => [k, String(v)])
|
|
19
|
-
).toString();
|
|
20
|
-
return `?${qs}`;
|
|
21
|
-
}
|
|
22
|
-
async function getPortfolioItems(options) {
|
|
23
|
-
const fallback = { items: [], total: 0, limit: 0, offset: 0 };
|
|
24
|
-
const query = buildQuery({
|
|
25
|
-
category: options?.category,
|
|
26
|
-
featured: options?.featured,
|
|
27
|
-
limit: options?.limit,
|
|
28
|
-
offset: options?.offset
|
|
29
|
-
});
|
|
30
|
-
const data = await portfolioGet(
|
|
31
|
-
`/api/public/portfolio/items${query}`
|
|
32
|
-
);
|
|
33
|
-
if (!data) {
|
|
34
|
-
console.warn(`${LOG_PREFIX} Failed to fetch portfolio items.`);
|
|
35
|
-
return fallback;
|
|
36
|
-
}
|
|
37
|
-
return data;
|
|
38
|
-
}
|
|
39
|
-
async function getPortfolioItem(slug) {
|
|
40
|
-
if (!slug) {
|
|
41
|
-
console.warn(`${LOG_PREFIX} getPortfolioItem called with empty slug.`);
|
|
42
|
-
return null;
|
|
43
|
-
}
|
|
44
|
-
const data = await portfolioGet(
|
|
45
|
-
`/api/public/portfolio/items/${encodeURIComponent(slug)}`
|
|
46
|
-
);
|
|
47
|
-
if (!data || !data.found || !data.item) {
|
|
48
|
-
console.warn(`${LOG_PREFIX} Portfolio item not found for slug: "${slug}".`);
|
|
49
|
-
return null;
|
|
50
|
-
}
|
|
51
|
-
const { item } = data;
|
|
52
|
-
return {
|
|
53
|
-
id: item.id,
|
|
54
|
-
slug: item.slug,
|
|
55
|
-
title: item.title,
|
|
56
|
-
subtitle: item.subtitle,
|
|
57
|
-
category: item.category,
|
|
58
|
-
services: item.services,
|
|
59
|
-
description: item.description,
|
|
60
|
-
hero_image: item.hero_image,
|
|
61
|
-
hero_image_alt: item.hero_image_alt,
|
|
62
|
-
live_url: item.live_url,
|
|
63
|
-
project_id: item.project_id ?? null,
|
|
64
|
-
kpis: item.kpis,
|
|
65
|
-
details: item.details,
|
|
66
|
-
seo: item.seo,
|
|
67
|
-
featured: item.featured,
|
|
68
|
-
order: item.order,
|
|
69
|
-
published_at: item.published_at,
|
|
70
|
-
hero_screenshots: item.hero_screenshots,
|
|
71
|
-
baseline_metrics: item.baseline_metrics,
|
|
72
|
-
current_metrics: item.current_metrics,
|
|
73
|
-
metrics_last_refreshed_at: item.metrics_last_refreshed_at,
|
|
74
|
-
// Sanitized HERE because this is the last genuinely-server-side hop:
|
|
75
|
-
// PortfolioPage compiles to a client component (tsup 'use client'
|
|
76
|
-
// banner), so raw client-site paths in sections would serialize into
|
|
77
|
-
// the RSC payload and get crawled against the agency site (GSC 404s).
|
|
78
|
-
sections: chunk633UMQQH_cjs.sanitizePortfolioSections(item.sections ?? []),
|
|
79
|
-
metricsDelta: item.metricsDelta ?? []
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
async function getPortfolioCategories() {
|
|
83
|
-
const data = await portfolioGet("/api/public/portfolio/categories");
|
|
84
|
-
if (!data) {
|
|
85
|
-
console.warn(`${LOG_PREFIX} Failed to fetch portfolio categories.`);
|
|
86
|
-
return [];
|
|
87
|
-
}
|
|
88
|
-
return data;
|
|
89
|
-
}
|
|
90
|
-
async function getPortfolioBrandConfig() {
|
|
91
|
-
const fallback = {
|
|
92
|
-
primary: "#6366f1",
|
|
93
|
-
secondary: "#8b5cf6",
|
|
94
|
-
background: "#0a0a0f",
|
|
95
|
-
backgroundElevated: "#12121a",
|
|
96
|
-
surface: "#1a1a2e",
|
|
97
|
-
surfaceHover: "#22223a",
|
|
98
|
-
surfaceBorder: "#2a2a3e",
|
|
99
|
-
textPrimary: "#f0f0f5",
|
|
100
|
-
textSecondary: "#a0a0b5",
|
|
101
|
-
textTertiary: "#6b6b80",
|
|
102
|
-
fontHeading: "Inter, system-ui, sans-serif",
|
|
103
|
-
fontBody: "Inter, system-ui, sans-serif"
|
|
104
|
-
};
|
|
105
|
-
const data = await portfolioGet(
|
|
106
|
-
"/api/public/portfolio/config"
|
|
107
|
-
);
|
|
108
|
-
if (!data || !data.brand) {
|
|
109
|
-
console.warn(`${LOG_PREFIX} Failed to fetch brand config, using defaults.`);
|
|
110
|
-
return fallback;
|
|
111
|
-
}
|
|
112
|
-
return data.brand;
|
|
113
|
-
}
|
|
114
|
-
async function generatePortfolioMetadata(slug) {
|
|
115
|
-
const item = await getPortfolioItem(slug);
|
|
116
|
-
if (!item) {
|
|
117
|
-
return {};
|
|
118
|
-
}
|
|
119
|
-
const seo = item.seo;
|
|
120
|
-
const title = seo?.metaTitle || item.title;
|
|
121
|
-
const description = seo?.metaDescription || item.description;
|
|
122
|
-
const keywords = seo?.keywords ?? [];
|
|
123
|
-
const ogImages = [];
|
|
124
|
-
if (item.hero_image) {
|
|
125
|
-
ogImages.push(item.hero_image);
|
|
126
|
-
}
|
|
127
|
-
return {
|
|
128
|
-
title,
|
|
129
|
-
description,
|
|
130
|
-
keywords: keywords.length > 0 ? keywords : void 0,
|
|
131
|
-
openGraph: {
|
|
132
|
-
title,
|
|
133
|
-
description,
|
|
134
|
-
images: ogImages.length > 0 ? ogImages : void 0,
|
|
135
|
-
type: "article"
|
|
136
|
-
}
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
async function generatePortfolioStaticParams() {
|
|
140
|
-
const data = await getPortfolioItems({ limit: 500, offset: 0 });
|
|
141
|
-
return data.items.map((item) => ({ slug: item.slug }));
|
|
142
|
-
}
|
|
143
8
|
|
|
9
|
+
|
|
10
|
+
Object.defineProperty(exports, "PortfolioApiError", {
|
|
11
|
+
enumerable: true,
|
|
12
|
+
get: function () { return chunk7ZHRMWJU_cjs.PortfolioApiError; }
|
|
13
|
+
});
|
|
14
|
+
Object.defineProperty(exports, "PortfolioContractError", {
|
|
15
|
+
enumerable: true,
|
|
16
|
+
get: function () { return chunk7ZHRMWJU_cjs.PortfolioContractError; }
|
|
17
|
+
});
|
|
18
|
+
Object.defineProperty(exports, "assertPortfolioItemShape", {
|
|
19
|
+
enumerable: true,
|
|
20
|
+
get: function () { return chunk7ZHRMWJU_cjs.assertPortfolioItemShape; }
|
|
21
|
+
});
|
|
22
|
+
Object.defineProperty(exports, "generatePortfolioCategoryStaticParams", {
|
|
23
|
+
enumerable: true,
|
|
24
|
+
get: function () { return chunk7ZHRMWJU_cjs.generatePortfolioCategoryStaticParams; }
|
|
25
|
+
});
|
|
26
|
+
Object.defineProperty(exports, "generatePortfolioIndexMetadata", {
|
|
27
|
+
enumerable: true,
|
|
28
|
+
get: function () { return chunk7ZHRMWJU_cjs.generatePortfolioIndexMetadata; }
|
|
29
|
+
});
|
|
30
|
+
Object.defineProperty(exports, "generatePortfolioMetadata", {
|
|
31
|
+
enumerable: true,
|
|
32
|
+
get: function () { return chunk7ZHRMWJU_cjs.generatePortfolioMetadata; }
|
|
33
|
+
});
|
|
34
|
+
Object.defineProperty(exports, "generatePortfolioSitemapEntries", {
|
|
35
|
+
enumerable: true,
|
|
36
|
+
get: function () { return chunk7ZHRMWJU_cjs.generatePortfolioSitemapEntries; }
|
|
37
|
+
});
|
|
38
|
+
Object.defineProperty(exports, "generatePortfolioStaticParams", {
|
|
39
|
+
enumerable: true,
|
|
40
|
+
get: function () { return chunk7ZHRMWJU_cjs.generatePortfolioStaticParams; }
|
|
41
|
+
});
|
|
42
|
+
Object.defineProperty(exports, "getPortfolioBrandConfig", {
|
|
43
|
+
enumerable: true,
|
|
44
|
+
get: function () { return chunk7ZHRMWJU_cjs.getPortfolioBrandConfig; }
|
|
45
|
+
});
|
|
46
|
+
Object.defineProperty(exports, "getPortfolioCategories", {
|
|
47
|
+
enumerable: true,
|
|
48
|
+
get: function () { return chunk7ZHRMWJU_cjs.getPortfolioCategories; }
|
|
49
|
+
});
|
|
50
|
+
Object.defineProperty(exports, "getPortfolioItem", {
|
|
51
|
+
enumerable: true,
|
|
52
|
+
get: function () { return chunk7ZHRMWJU_cjs.getPortfolioItem; }
|
|
53
|
+
});
|
|
54
|
+
Object.defineProperty(exports, "getPortfolioItems", {
|
|
55
|
+
enumerable: true,
|
|
56
|
+
get: function () { return chunk7ZHRMWJU_cjs.getPortfolioItems; }
|
|
57
|
+
});
|
|
58
|
+
Object.defineProperty(exports, "slugifyCategory", {
|
|
59
|
+
enumerable: true,
|
|
60
|
+
get: function () { return chunk7ZHRMWJU_cjs.slugifyCategory; }
|
|
61
|
+
});
|
|
144
62
|
Object.defineProperty(exports, "PORTFOLIO_CACHE_TAG", {
|
|
145
63
|
enumerable: true,
|
|
146
64
|
get: function () { return chunkFCKDJ4MJ_cjs.PORTFOLIO_CACHE_TAG; }
|
|
147
65
|
});
|
|
148
|
-
exports.generatePortfolioMetadata = generatePortfolioMetadata;
|
|
149
|
-
exports.generatePortfolioStaticParams = generatePortfolioStaticParams;
|
|
150
|
-
exports.getPortfolioBrandConfig = getPortfolioBrandConfig;
|
|
151
|
-
exports.getPortfolioCategories = getPortfolioCategories;
|
|
152
|
-
exports.getPortfolioItem = getPortfolioItem;
|
|
153
|
-
exports.getPortfolioItems = getPortfolioItems;
|
|
154
66
|
//# sourceMappingURL=server.cjs.map
|
|
155
67
|
//# sourceMappingURL=server.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/portfolio/server.ts"],"names":["apiFetch","PORTFOLIO_CACHE_TAG","sanitizePortfolioSections"],"mappings":"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","file":"server.cjs","sourcesContent":["/**\n * @sonordev/agency-site-kit — Portfolio server-side data fetching\n *\n * RSC (React Server Component) data layer for portfolio pages.\n * All functions are async and designed for use in Next.js App Router\n * server components, generateMetadata(), and generateStaticParams().\n *\n * This file is server-only — no 'use client' directive.\n */\n\nimport { apiFetch } from '../shared/api';\nimport { PORTFOLIO_CACHE_TAG } from '../shared/cache';\nimport { sanitizePortfolioSections } from './sanitize';\nimport type {\n BrandConfig,\n MetricsDelta,\n PortfolioConfigResponse,\n PortfolioItemFull,\n PortfolioListResponse,\n PortfolioSeoData,\n PortfolioSection,\n} from '../types';\n\nconst LOG_PREFIX = '[@sonordev/agency-site-kit/portfolio]';\n\n// Re-export so consumers and route handlers can reference the same tag.\nexport { PORTFOLIO_CACHE_TAG } from '../shared/cache';\n\n/**\n * GET against the public portfolio API with the shared portfolio cache tag,\n * so the Portal's revalidation webhook can invalidate every portfolio fetch\n * (list, detail, categories, config, sitemap data) via one revalidateTag.\n */\nfunction portfolioGet<T>(path: string): Promise<T | null> {\n return apiFetch<T>(path, { method: 'GET', tags: [PORTFOLIO_CACHE_TAG] });\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Builds a query string from an object, omitting undefined/null values.\n */\nfunction buildQuery(params: Record<string, string | number | boolean | undefined | null>): string {\n const entries = Object.entries(params).filter(\n (entry): entry is [string, string | number | boolean] => entry[1] != null,\n );\n if (entries.length === 0) return '';\n const qs = new URLSearchParams(\n entries.map(([k, v]) => [k, String(v)]),\n ).toString();\n return `?${qs}`;\n}\n\n// ---------------------------------------------------------------------------\n// Public API — List items\n// ---------------------------------------------------------------------------\n\n/**\n * Fetches published portfolio items from the public API.\n * Cached via Next.js ISR (revalidate: 3600).\n */\nexport async function getPortfolioItems(options?: {\n category?: string;\n featured?: boolean;\n limit?: number;\n offset?: number;\n}): Promise<PortfolioListResponse> {\n const fallback: PortfolioListResponse = { items: [], total: 0, limit: 0, offset: 0 };\n\n const query = buildQuery({\n category: options?.category,\n featured: options?.featured,\n limit: options?.limit,\n offset: options?.offset,\n });\n\n const data = await portfolioGet<PortfolioListResponse>(\n `/api/public/portfolio/items${query}`,\n );\n\n if (!data) {\n console.warn(`${LOG_PREFIX} Failed to fetch portfolio items.`);\n return fallback;\n }\n\n return data;\n}\n\n// ---------------------------------------------------------------------------\n// Public API — Single item\n// ---------------------------------------------------------------------------\n\n/** Shape returned by the single-item API endpoint. */\ninterface PortfolioItemApiResponse {\n found: boolean;\n item: {\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 project_id: string | null;\n kpis: PortfolioItemFull['kpis'];\n details: PortfolioItemFull['details'];\n seo: PortfolioItemFull['seo'];\n featured: boolean;\n order?: number;\n published_at: string | null;\n hero_screenshots: PortfolioItemFull['hero_screenshots'];\n baseline_metrics: PortfolioItemFull['baseline_metrics'];\n current_metrics: PortfolioItemFull['current_metrics'];\n metrics_last_refreshed_at: string | null;\n sections: PortfolioSection[];\n metricsDelta: MetricsDelta[];\n };\n}\n\n/**\n * Fetches a single portfolio item by slug, including full Sanity sections\n * and live metrics delta. This is the main data function for portfolio detail pages.\n *\n * Returns null when the item is not found or the API call fails.\n */\nexport async function getPortfolioItem(slug: string): Promise<PortfolioItemFull | null> {\n if (!slug) {\n console.warn(`${LOG_PREFIX} getPortfolioItem called with empty slug.`);\n return null;\n }\n\n const data = await portfolioGet<PortfolioItemApiResponse>(\n `/api/public/portfolio/items/${encodeURIComponent(slug)}`,\n );\n\n if (!data || !data.found || !data.item) {\n console.warn(`${LOG_PREFIX} Portfolio item not found for slug: \"${slug}\".`);\n return null;\n }\n\n // Map the API response to the PortfolioItemFull type\n const { item } = data;\n return {\n id: item.id,\n slug: item.slug,\n title: item.title,\n subtitle: item.subtitle,\n category: item.category,\n services: item.services,\n description: item.description,\n hero_image: item.hero_image,\n hero_image_alt: item.hero_image_alt,\n live_url: item.live_url,\n project_id: item.project_id ?? null,\n kpis: item.kpis,\n details: item.details,\n seo: item.seo,\n featured: item.featured,\n order: item.order,\n published_at: item.published_at,\n hero_screenshots: item.hero_screenshots,\n baseline_metrics: item.baseline_metrics,\n current_metrics: item.current_metrics,\n metrics_last_refreshed_at: item.metrics_last_refreshed_at,\n // Sanitized HERE because this is the last genuinely-server-side hop:\n // PortfolioPage compiles to a client component (tsup 'use client'\n // banner), so raw client-site paths in sections would serialize into\n // the RSC payload and get crawled against the agency site (GSC 404s).\n sections: sanitizePortfolioSections(item.sections ?? []),\n metricsDelta: item.metricsDelta ?? [],\n };\n}\n\n// ---------------------------------------------------------------------------\n// Public API — Categories\n// ---------------------------------------------------------------------------\n\n/**\n * Fetches available portfolio categories.\n * Returns an empty array on failure.\n */\nexport async function getPortfolioCategories(): Promise<string[]> {\n const data = await portfolioGet<string[]>('/api/public/portfolio/categories');\n\n if (!data) {\n console.warn(`${LOG_PREFIX} Failed to fetch portfolio categories.`);\n return [];\n }\n\n return data;\n}\n\n// ---------------------------------------------------------------------------\n// Public API — Brand config\n// ---------------------------------------------------------------------------\n\n/**\n * Fetches brand configuration for the agency (colors, fonts, logo).\n * Returns sensible defaults when the API call fails.\n */\nexport async function getPortfolioBrandConfig(): Promise<BrandConfig> {\n const fallback: BrandConfig = {\n primary: '#6366f1',\n secondary: '#8b5cf6',\n background: '#0a0a0f',\n backgroundElevated: '#12121a',\n surface: '#1a1a2e',\n surfaceHover: '#22223a',\n surfaceBorder: '#2a2a3e',\n textPrimary: '#f0f0f5',\n textSecondary: '#a0a0b5',\n textTertiary: '#6b6b80',\n fontHeading: 'Inter, system-ui, sans-serif',\n fontBody: 'Inter, system-ui, sans-serif',\n };\n\n const data = await portfolioGet<PortfolioConfigResponse>(\n '/api/public/portfolio/config',\n );\n\n if (!data || !data.brand) {\n console.warn(`${LOG_PREFIX} Failed to fetch brand config, using defaults.`);\n return fallback;\n }\n\n return data.brand;\n}\n\n// ---------------------------------------------------------------------------\n// Next.js Metadata helper\n// ---------------------------------------------------------------------------\n\n/**\n * Generates Next.js Metadata for a portfolio item page.\n * For use in generateMetadata() in the App Router.\n *\n * Returns an empty object when the item cannot be found, which is safe\n * to spread into the metadata return value.\n */\nexport async function generatePortfolioMetadata(slug: string): Promise<{\n title?: string;\n description?: string;\n keywords?: string[];\n openGraph?: {\n title?: string;\n description?: string;\n images?: string[];\n type?: string;\n };\n}> {\n const item = await getPortfolioItem(slug);\n\n if (!item) {\n return {};\n }\n\n // Prefer dedicated SEO data if available, fall back to item fields\n const seo: PortfolioSeoData | null = item.seo;\n const title = seo?.metaTitle || item.title;\n const description = seo?.metaDescription || item.description;\n const keywords = seo?.keywords ?? [];\n\n // Collect OG images: prefer SEO og image, then hero image\n const ogImages: string[] = [];\n if (item.hero_image) {\n ogImages.push(item.hero_image);\n }\n\n return {\n title,\n description,\n keywords: keywords.length > 0 ? keywords : undefined,\n openGraph: {\n title,\n description,\n images: ogImages.length > 0 ? ogImages : undefined,\n type: 'article',\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Next.js Static Params helper\n// ---------------------------------------------------------------------------\n\n/**\n * Generates static params for portfolio pages.\n * For use in generateStaticParams() in the App Router.\n *\n * Fetches all published portfolio items and returns their slugs.\n */\nexport async function generatePortfolioStaticParams(): Promise<Array<{ slug: string }>> {\n // Fetch a large batch to cover all published items\n const data = await getPortfolioItems({ limit: 500, offset: 0 });\n\n return data.items.map((item) => ({ slug: item.slug }));\n}\n"]}
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"server.cjs"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { P as PortfolioItemFull, a as PortfolioSection, M as MetricsDelta, B as BrandConfig, b as PortfolioListResponse } from '../types-0tVPy4pU.cjs';
|
|
2
2
|
export { P as PORTFOLIO_CACHE_TAG } from '../cache-CE_ujDK4.cjs';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -11,9 +11,48 @@ export { P as PORTFOLIO_CACHE_TAG } from '../cache-CE_ujDK4.cjs';
|
|
|
11
11
|
* This file is server-only — no 'use client' directive.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Error contract (single source of truth for portfolio content fetching):
|
|
16
|
+
*
|
|
17
|
+
* - "Not found" is a DATA answer: the API returns 200 with `found: false`,
|
|
18
|
+
* and `getPortfolioItem` returns null. Consumers map null → notFound().
|
|
19
|
+
* - Infrastructure failure (network error, 5xx, rate-limit exhaustion,
|
|
20
|
+
* missing SONOR_API_KEY) THROWS PortfolioApiError. It must never be
|
|
21
|
+
* conflated with "not found": treating an outage as a 404 turns indexed
|
|
22
|
+
* /work URLs into real 404s during ISR regeneration (the GSC 404-flood
|
|
23
|
+
* class of incident). A thrown error makes Next.js keep serving the last
|
|
24
|
+
* good page during background revalidation, and fails builds loudly
|
|
25
|
+
* instead of silently generating an empty portfolio.
|
|
26
|
+
*
|
|
27
|
+
* The one exception is getPortfolioBrandConfig, which degrades to default
|
|
28
|
+
* branding on failure — cosmetic fallback is safe; content fallback is not.
|
|
29
|
+
*/
|
|
30
|
+
declare class PortfolioApiError extends Error {
|
|
31
|
+
readonly path: string;
|
|
32
|
+
constructor(path: string, message?: string);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* A 200 response whose body violates the expected shape. Thrown before any
|
|
36
|
+
* mapping so drift between the API and this package surfaces as a loud,
|
|
37
|
+
* named error instead of `undefined` text on live pages.
|
|
38
|
+
*/
|
|
39
|
+
declare class PortfolioContractError extends PortfolioApiError {
|
|
40
|
+
readonly field: string;
|
|
41
|
+
constructor(path: string, field: string, detail: string);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Minimal runtime shape check for the single-item response. Deliberately
|
|
45
|
+
* lenient about unknown extra fields (the API may grow) and strict about
|
|
46
|
+
* the fields this package renders from.
|
|
47
|
+
*/
|
|
48
|
+
declare function assertPortfolioItemShape(item: unknown, path: string): asserts item is PortfolioItemApiResponse['item'];
|
|
14
49
|
/**
|
|
15
50
|
* Fetches published portfolio items from the public API.
|
|
16
51
|
* Cached via Next.js ISR (revalidate: 3600).
|
|
52
|
+
*
|
|
53
|
+
* An empty portfolio is `{ items: [] }` from a healthy API. Infrastructure
|
|
54
|
+
* failure throws PortfolioApiError (see the error contract above) — it never
|
|
55
|
+
* silently renders an empty portfolio or an empty sitemap.
|
|
17
56
|
*/
|
|
18
57
|
declare function getPortfolioItems(options?: {
|
|
19
58
|
category?: string;
|
|
@@ -21,29 +60,65 @@ declare function getPortfolioItems(options?: {
|
|
|
21
60
|
limit?: number;
|
|
22
61
|
offset?: number;
|
|
23
62
|
}): Promise<PortfolioListResponse>;
|
|
63
|
+
/** Shape returned by the single-item API endpoint. */
|
|
64
|
+
interface PortfolioItemApiResponse {
|
|
65
|
+
found: boolean;
|
|
66
|
+
item: {
|
|
67
|
+
id: string;
|
|
68
|
+
slug: string;
|
|
69
|
+
title: string;
|
|
70
|
+
subtitle: string;
|
|
71
|
+
category: string;
|
|
72
|
+
services: string[];
|
|
73
|
+
description: string;
|
|
74
|
+
hero_image: string;
|
|
75
|
+
hero_image_alt: string;
|
|
76
|
+
live_url: string | null;
|
|
77
|
+
project_id: string | null;
|
|
78
|
+
kpis: PortfolioItemFull['kpis'];
|
|
79
|
+
details: PortfolioItemFull['details'];
|
|
80
|
+
seo: PortfolioItemFull['seo'];
|
|
81
|
+
featured: boolean;
|
|
82
|
+
order?: number;
|
|
83
|
+
published_at: string | null;
|
|
84
|
+
hero_screenshots: PortfolioItemFull['hero_screenshots'];
|
|
85
|
+
baseline_metrics: PortfolioItemFull['baseline_metrics'];
|
|
86
|
+
current_metrics: PortfolioItemFull['current_metrics'];
|
|
87
|
+
metrics_last_refreshed_at: string | null;
|
|
88
|
+
sections: PortfolioSection[];
|
|
89
|
+
metricsDelta: MetricsDelta[];
|
|
90
|
+
sectionsOrdered?: boolean;
|
|
91
|
+
};
|
|
92
|
+
}
|
|
24
93
|
/**
|
|
25
94
|
* Fetches a single portfolio item by slug, including full Sanity sections
|
|
26
95
|
* and live metrics delta. This is the main data function for portfolio detail pages.
|
|
27
96
|
*
|
|
28
|
-
* Returns null when the item
|
|
97
|
+
* Returns null ONLY when the item genuinely does not exist (API answered
|
|
98
|
+
* `found: false`) — safe to map to notFound(). Infrastructure failures throw
|
|
99
|
+
* PortfolioApiError instead, so an API outage during ISR regeneration keeps
|
|
100
|
+
* serving the last good page rather than minting a real 404.
|
|
29
101
|
*/
|
|
30
102
|
declare function getPortfolioItem(slug: string): Promise<PortfolioItemFull | null>;
|
|
31
103
|
/**
|
|
32
104
|
* Fetches available portfolio categories.
|
|
33
|
-
*
|
|
105
|
+
* Infrastructure failure throws PortfolioApiError (see error contract).
|
|
34
106
|
*/
|
|
35
107
|
declare function getPortfolioCategories(): Promise<string[]>;
|
|
36
108
|
/**
|
|
37
109
|
* Fetches brand configuration for the agency (colors, fonts, logo).
|
|
38
|
-
* Returns sensible defaults when the API call fails
|
|
110
|
+
* Returns sensible defaults when the API call fails — the ONE deliberate
|
|
111
|
+
* exception to the throw-on-failure contract: brand config is cosmetic, and
|
|
112
|
+
* an API blip must not take down the whole layout that wraps every page.
|
|
39
113
|
*/
|
|
40
114
|
declare function getPortfolioBrandConfig(): Promise<BrandConfig>;
|
|
41
115
|
/**
|
|
42
116
|
* Generates Next.js Metadata for a portfolio item page.
|
|
43
117
|
* For use in generateMetadata() in the App Router.
|
|
44
118
|
*
|
|
45
|
-
* Returns an empty object when the item
|
|
46
|
-
*
|
|
119
|
+
* Returns an empty object when the item does not exist (safe to spread into
|
|
120
|
+
* the metadata return value); infrastructure failures throw so a build never
|
|
121
|
+
* silently ships pages with missing metadata.
|
|
47
122
|
*/
|
|
48
123
|
declare function generatePortfolioMetadata(slug: string): Promise<{
|
|
49
124
|
title?: string;
|
|
@@ -61,9 +136,84 @@ declare function generatePortfolioMetadata(slug: string): Promise<{
|
|
|
61
136
|
* For use in generateStaticParams() in the App Router.
|
|
62
137
|
*
|
|
63
138
|
* Fetches all published portfolio items and returns their slugs.
|
|
139
|
+
* Infrastructure failure throws, failing the build loudly instead of
|
|
140
|
+
* silently building a site with zero portfolio pages.
|
|
64
141
|
*/
|
|
65
142
|
declare function generatePortfolioStaticParams(): Promise<Array<{
|
|
66
143
|
slug: string;
|
|
67
144
|
}>>;
|
|
145
|
+
/**
|
|
146
|
+
* URL slug for a category display value ("Web Development" → "web-development").
|
|
147
|
+
* Single source of truth for category URLs — PortfolioIndex resolves slugs
|
|
148
|
+
* back to the original values by matching against getPortfolioCategories().
|
|
149
|
+
*/
|
|
150
|
+
declare function slugifyCategory(category: string): string;
|
|
151
|
+
/**
|
|
152
|
+
* Static params for the category filter routes.
|
|
153
|
+
* For use in app/work/category/[category]/page.tsx:
|
|
154
|
+
*
|
|
155
|
+
* ```ts
|
|
156
|
+
* export async function generateStaticParams() {
|
|
157
|
+
* return generatePortfolioCategoryStaticParams();
|
|
158
|
+
* }
|
|
159
|
+
* ```
|
|
160
|
+
*/
|
|
161
|
+
declare function generatePortfolioCategoryStaticParams(): Promise<Array<{
|
|
162
|
+
category: string;
|
|
163
|
+
}>>;
|
|
164
|
+
/**
|
|
165
|
+
* Metadata for index + category + paginated portfolio routes: a stable
|
|
166
|
+
* title/canonical so filtered and paginated variants don't compete in
|
|
167
|
+
* search. Spread into generateMetadata()'s return value.
|
|
168
|
+
*/
|
|
169
|
+
declare function generatePortfolioIndexMetadata(options: {
|
|
170
|
+
/** Site origin (e.g. "https://youragency.com"). */
|
|
171
|
+
baseUrl: string;
|
|
172
|
+
/** Route prefix. Defaults to "/work". */
|
|
173
|
+
basePath?: string;
|
|
174
|
+
/** Active category SLUG (from the route param), if any. */
|
|
175
|
+
category?: string | null;
|
|
176
|
+
/** 1-based page number. */
|
|
177
|
+
page?: number;
|
|
178
|
+
/** Title base, e.g. "Our Work". Category/page qualifiers are appended. */
|
|
179
|
+
titleBase?: string;
|
|
180
|
+
}): Promise<{
|
|
181
|
+
title: string;
|
|
182
|
+
alternates: {
|
|
183
|
+
canonical: string;
|
|
184
|
+
};
|
|
185
|
+
}>;
|
|
186
|
+
/** One entry in the shape Next.js expects from app/sitemap.(js|ts). */
|
|
187
|
+
interface PortfolioSitemapEntry {
|
|
188
|
+
url: string;
|
|
189
|
+
lastModified?: string;
|
|
190
|
+
changeFrequency?: 'always' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never';
|
|
191
|
+
priority?: number;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Builds sitemap entries for every published portfolio item, in the shape
|
|
195
|
+
* Next.js `app/sitemap.(js|ts)` expects — spread the result into the array
|
|
196
|
+
* you return:
|
|
197
|
+
*
|
|
198
|
+
* ```ts
|
|
199
|
+
* const portfolio = await generatePortfolioSitemapEntries({
|
|
200
|
+
* baseUrl: 'https://youragency.com',
|
|
201
|
+
* });
|
|
202
|
+
* return [...staticEntries, ...portfolio];
|
|
203
|
+
* ```
|
|
204
|
+
*
|
|
205
|
+
* `lastModified` prefers the metrics refresh timestamp (case studies change
|
|
206
|
+
* when their live metrics update) and falls back to the publish date.
|
|
207
|
+
* Infrastructure failure throws — a sitemap silently missing every case
|
|
208
|
+
* study is how portfolio URLs fall out of the index.
|
|
209
|
+
*/
|
|
210
|
+
declare function generatePortfolioSitemapEntries(options: {
|
|
211
|
+
/** Site origin, no trailing slash needed (e.g. "https://youragency.com"). */
|
|
212
|
+
baseUrl: string;
|
|
213
|
+
/** Route prefix for case studies. Defaults to "/work". */
|
|
214
|
+
basePath?: string;
|
|
215
|
+
changeFrequency?: PortfolioSitemapEntry['changeFrequency'];
|
|
216
|
+
priority?: number;
|
|
217
|
+
}): Promise<PortfolioSitemapEntry[]>;
|
|
68
218
|
|
|
69
|
-
export { generatePortfolioMetadata, generatePortfolioStaticParams, getPortfolioBrandConfig, getPortfolioCategories, getPortfolioItem, getPortfolioItems };
|
|
219
|
+
export { PortfolioApiError, PortfolioContractError, type PortfolioSitemapEntry, assertPortfolioItemShape, generatePortfolioCategoryStaticParams, generatePortfolioIndexMetadata, generatePortfolioMetadata, generatePortfolioSitemapEntries, generatePortfolioStaticParams, getPortfolioBrandConfig, getPortfolioCategories, getPortfolioItem, getPortfolioItems, slugifyCategory };
|