@sonordev/agency-site-kit 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/BeforeAfterSection-6QUJOBO2.js +176 -0
- package/dist/BeforeAfterSection-6QUJOBO2.js.map +1 -0
- package/dist/BeforeAfterSection-DVAWWE4K.cjs +181 -0
- package/dist/BeforeAfterSection-DVAWWE4K.cjs.map +1 -0
- package/dist/CTASection-4JKLXEUF.cjs +111 -0
- package/dist/CTASection-4JKLXEUF.cjs.map +1 -0
- package/dist/CTASection-BJA72XIL.js +106 -0
- package/dist/CTASection-BJA72XIL.js.map +1 -0
- package/dist/ChallengesSection-GEQGVSJN.js +180 -0
- package/dist/ChallengesSection-GEQGVSJN.js.map +1 -0
- package/dist/ChallengesSection-IZ3DHECS.cjs +182 -0
- package/dist/ChallengesSection-IZ3DHECS.cjs.map +1 -0
- package/dist/ConversionFunnelSection-AUUSJ5HQ.cjs +209 -0
- package/dist/ConversionFunnelSection-AUUSJ5HQ.cjs.map +1 -0
- package/dist/ConversionFunnelSection-D3GE4NKE.js +203 -0
- package/dist/ConversionFunnelSection-D3GE4NKE.js.map +1 -0
- package/dist/DetailsSection-FB763FS7.js +135 -0
- package/dist/DetailsSection-FB763FS7.js.map +1 -0
- package/dist/DetailsSection-OACJFGH7.cjs +137 -0
- package/dist/DetailsSection-OACJFGH7.cjs.map +1 -0
- package/dist/FeatureSpotlightSection-B7P3JGNL.js +205 -0
- package/dist/FeatureSpotlightSection-B7P3JGNL.js.map +1 -0
- package/dist/FeatureSpotlightSection-WRHXS7TU.cjs +210 -0
- package/dist/FeatureSpotlightSection-WRHXS7TU.cjs.map +1 -0
- package/dist/GallerySection-VMKORC47.js +218 -0
- package/dist/GallerySection-VMKORC47.js.map +1 -0
- package/dist/GallerySection-WJ4PQDBI.cjs +219 -0
- package/dist/GallerySection-WJ4PQDBI.cjs.map +1 -0
- package/dist/MetricsTimelineSection-4L6DUHJ5.cjs +258 -0
- package/dist/MetricsTimelineSection-4L6DUHJ5.cjs.map +1 -0
- package/dist/MetricsTimelineSection-6BT5GNFV.js +253 -0
- package/dist/MetricsTimelineSection-6BT5GNFV.js.map +1 -0
- package/dist/ResultsSection-DFUJ5U6M.js +93 -0
- package/dist/ResultsSection-DFUJ5U6M.js.map +1 -0
- package/dist/ResultsSection-XLGMMQKY.cjs +95 -0
- package/dist/ResultsSection-XLGMMQKY.cjs.map +1 -0
- package/dist/ServicesSection-D5V3Q4GR.js +118 -0
- package/dist/ServicesSection-D5V3Q4GR.js.map +1 -0
- package/dist/ServicesSection-WJMGK2MF.cjs +120 -0
- package/dist/ServicesSection-WJMGK2MF.cjs.map +1 -0
- package/dist/StrategySection-3ED3QW4R.cjs +180 -0
- package/dist/StrategySection-3ED3QW4R.cjs.map +1 -0
- package/dist/StrategySection-VUWMIYYP.js +175 -0
- package/dist/StrategySection-VUWMIYYP.js.map +1 -0
- package/dist/TeamSection-DZVSNZE6.cjs +112 -0
- package/dist/TeamSection-DZVSNZE6.cjs.map +1 -0
- package/dist/TeamSection-HGKFW6PQ.js +107 -0
- package/dist/TeamSection-HGKFW6PQ.js.map +1 -0
- package/dist/TechStackSection-OCUYG4XT.js +90 -0
- package/dist/TechStackSection-OCUYG4XT.js.map +1 -0
- package/dist/TechStackSection-VKJK4KQB.cjs +91 -0
- package/dist/TechStackSection-VKJK4KQB.cjs.map +1 -0
- package/dist/TestimonialSection-6RGSMXQB.js +122 -0
- package/dist/TestimonialSection-6RGSMXQB.js.map +1 -0
- package/dist/TestimonialSection-XPTFUQIN.cjs +124 -0
- package/dist/TestimonialSection-XPTFUQIN.cjs.map +1 -0
- package/dist/VideoSection-4A2HC6K6.js +117 -0
- package/dist/VideoSection-4A2HC6K6.js.map +1 -0
- package/dist/VideoSection-G3DFS7UH.cjs +118 -0
- package/dist/VideoSection-G3DFS7UH.cjs.map +1 -0
- package/dist/chunk-2VNNFAG6.js +415 -0
- package/dist/chunk-2VNNFAG6.js.map +1 -0
- package/dist/chunk-2Y4O3LWM.js +53 -0
- package/dist/chunk-2Y4O3LWM.js.map +1 -0
- package/dist/chunk-5FKOLIV6.cjs +221 -0
- package/dist/chunk-5FKOLIV6.cjs.map +1 -0
- package/dist/chunk-7CFFAKDM.js +74 -0
- package/dist/chunk-7CFFAKDM.js.map +1 -0
- package/dist/chunk-A4I4IK7V.js +69 -0
- package/dist/chunk-A4I4IK7V.js.map +1 -0
- package/dist/chunk-IKBK7HYX.cjs +79 -0
- package/dist/chunk-IKBK7HYX.cjs.map +1 -0
- package/dist/chunk-KEOHORIH.cjs +79 -0
- package/dist/chunk-KEOHORIH.cjs.map +1 -0
- package/dist/chunk-NAS4K5UR.cjs +139 -0
- package/dist/chunk-NAS4K5UR.cjs.map +1 -0
- package/dist/chunk-QBLWP25X.cjs +73 -0
- package/dist/chunk-QBLWP25X.cjs.map +1 -0
- package/dist/chunk-QIC6JFFD.js +210 -0
- package/dist/chunk-QIC6JFFD.js.map +1 -0
- package/dist/chunk-TAPNXT7X.cjs +422 -0
- package/dist/chunk-TAPNXT7X.cjs.map +1 -0
- package/dist/chunk-XCKXHK44.js +15 -0
- package/dist/chunk-XCKXHK44.js.map +1 -0
- package/dist/chunk-XMC4DN6G.js +131 -0
- package/dist/chunk-XMC4DN6G.js.map +1 -0
- package/dist/chunk-XONXEFJY.cjs +58 -0
- package/dist/chunk-XONXEFJY.cjs.map +1 -0
- package/dist/chunk-XQNJED46.cjs +19 -0
- package/dist/chunk-XQNJED46.cjs.map +1 -0
- package/dist/chunk-YB4B3OMC.js +74 -0
- package/dist/chunk-YB4B3OMC.js.map +1 -0
- package/dist/index.cjs +271 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +137 -0
- package/dist/index.d.ts +137 -0
- package/dist/index.js +197 -0
- package/dist/index.js.map +1 -0
- package/dist/layout/index.cjs +13 -0
- package/dist/layout/index.cjs.map +1 -0
- package/dist/layout/index.d.cts +54 -0
- package/dist/layout/index.d.ts +54 -0
- package/dist/layout/index.js +4 -0
- package/dist/layout/index.js.map +1 -0
- package/dist/portfolio/client.cjs +18 -0
- package/dist/portfolio/client.cjs.map +1 -0
- package/dist/portfolio/client.d.cts +97 -0
- package/dist/portfolio/client.d.ts +97 -0
- package/dist/portfolio/client.js +6 -0
- package/dist/portfolio/client.js.map +1 -0
- package/dist/portfolio/index.cjs +41 -0
- package/dist/portfolio/index.cjs.map +1 -0
- package/dist/portfolio/index.d.cts +12 -0
- package/dist/portfolio/index.d.ts +12 -0
- package/dist/portfolio/index.js +8 -0
- package/dist/portfolio/index.js.map +1 -0
- package/dist/portfolio/sections.cjs +20 -0
- package/dist/portfolio/sections.cjs.map +1 -0
- package/dist/portfolio/sections.d.cts +42 -0
- package/dist/portfolio/sections.d.ts +42 -0
- package/dist/portfolio/sections.js +4 -0
- package/dist/portfolio/sections.js.map +1 -0
- package/dist/portfolio/server.cjs +141 -0
- package/dist/portfolio/server.cjs.map +1 -0
- package/dist/portfolio/server.d.cts +68 -0
- package/dist/portfolio/server.d.ts +68 -0
- package/dist/portfolio/server.js +134 -0
- package/dist/portfolio/server.js.map +1 -0
- package/dist/types-BMUhBhWx.d.cts +346 -0
- package/dist/types-BMUhBhWx.d.ts +346 -0
- package/package.json +71 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/portfolio/components/primitives/SplitHeadline.tsx","../src/portfolio/components/sections/HeroSection.tsx","../src/portfolio/components/PortfolioPage.tsx","../src/types.ts"],"names":["useRef","useEffect","gsap","ScrollTrigger","jsxs","jsx"],"mappings":"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","file":"chunk-2VNNFAG6.js","sourcesContent":["'use client';\n\nimport React, { useRef, useEffect, useMemo } from 'react';\nimport gsap from 'gsap';\nimport { ScrollTrigger } from 'gsap/ScrollTrigger';\n\ninterface SplitHeadlineProps {\n children: string;\n tag?: 'h1' | 'h2' | 'h3' | 'h4' | 'p';\n className?: string;\n style?: React.CSSProperties;\n delay?: number;\n duration?: number;\n staggerAmount?: number;\n}\n\nexport default function SplitHeadline({\n children,\n tag: Tag = 'h2',\n className,\n style,\n delay = 0,\n duration = 0.8,\n staggerAmount = 0.02,\n}: SplitHeadlineProps) {\n const ref = useRef<HTMLElement>(null);\n\n // Split text into words and characters, preserving spaces\n const splitContent = useMemo(() => {\n const words = children.split(' ');\n return words.map((word, wi) => (\n <span key={wi} style={{ display: 'inline-block', whiteSpace: 'nowrap' }}>\n {word.split('').map((char, ci) => (\n <span\n key={ci}\n className=\"split-char\"\n style={{ display: 'inline-block' }}\n >\n {char}\n </span>\n ))}\n {wi < words.length - 1 && (\n <span style={{ display: 'inline-block' }}> </span>\n )}\n </span>\n ));\n }, [children]);\n\n useEffect(() => {\n gsap.registerPlugin(ScrollTrigger);\n\n const el = ref.current;\n if (!el) return;\n\n // Respect prefers-reduced-motion\n const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n if (prefersReducedMotion) {\n gsap.set(el.querySelectorAll('.split-char'), { opacity: 1, y: 0 });\n return;\n }\n\n const chars = el.querySelectorAll('.split-char');\n\n const ctx = gsap.context(() => {\n gsap.fromTo(\n chars,\n {\n opacity: 0,\n y: 20,\n },\n {\n opacity: 1,\n y: 0,\n duration,\n delay,\n stagger: staggerAmount,\n ease: 'power2.out',\n scrollTrigger: {\n trigger: el,\n start: 'top 85%',\n toggleActions: 'play none none none',\n },\n },\n );\n }, el);\n\n return () => ctx.revert();\n }, [delay, duration, staggerAmount]);\n\n return (\n <Tag\n ref={ref as React.RefObject<any>}\n className={className}\n style={{ overflow: 'hidden', ...style }}\n >\n {splitContent}\n </Tag>\n );\n}\n","'use client';\n\nimport React, { useRef, useEffect } from 'react';\nimport gsap from 'gsap';\nimport { ScrollTrigger } from 'gsap/ScrollTrigger';\nimport type { PortfolioHeroData } from '../../../types';\nimport ScrollReveal from '../primitives/ScrollReveal';\nimport AnimatedCounter from '../primitives/AnimatedCounter';\nimport SplitHeadline from '../primitives/SplitHeadline';\nimport GlassCard from '../primitives/GlassCard';\n\ninterface HeroSectionProps {\n data: PortfolioHeroData;\n screenshots?: { desktop?: string; tablet?: string; mobile?: string };\n liveUrl?: string;\n}\n\nexport default function HeroSection({ data, screenshots, liveUrl }: HeroSectionProps) {\n const bgRef = useRef<HTMLDivElement>(null);\n const deviceRef = useRef<HTMLDivElement>(null);\n\n const shots = screenshots || data.screenshots;\n const url = liveUrl || data.liveUrl;\n\n useEffect(() => {\n gsap.registerPlugin(ScrollTrigger);\n\n const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n if (prefersReducedMotion) return;\n\n const ctx = gsap.context(() => {\n // Background parallax — bg moves at 0.3x scroll speed\n if (bgRef.current) {\n gsap.to(bgRef.current, {\n yPercent: 30,\n ease: 'none',\n scrollTrigger: {\n trigger: bgRef.current.parentElement,\n start: 'top top',\n end: 'bottom top',\n scrub: true,\n },\n });\n }\n\n // Device trifolio entrance\n if (deviceRef.current) {\n const devices = deviceRef.current.children;\n gsap.fromTo(\n devices,\n { y: 60, opacity: 0, scale: 0.95 },\n {\n y: 0,\n opacity: 1,\n scale: 1,\n duration: 1,\n stagger: 0.15,\n ease: 'power3.out',\n delay: 0.6,\n },\n );\n }\n });\n\n return () => ctx.revert();\n }, []);\n\n return (\n <section\n className=\"relative overflow-hidden w-full\"\n style={{\n minHeight: '90vh',\n display: 'flex',\n flexDirection: 'column',\n justifyContent: 'center',\n background: 'var(--sk-bg, #0a0a0a)',\n }}\n >\n {/* Parallax background layer */}\n <div\n ref={bgRef}\n className=\"absolute inset-0 pointer-events-none\"\n style={{\n background: `radial-gradient(ellipse at 50% 20%, color-mix(in srgb, var(--sk-primary, #6366f1) 15%, transparent) 0%, transparent 70%)`,\n willChange: 'transform',\n }}\n />\n\n <div className=\"relative z-10 max-w-7xl mx-auto w-full px-6 py-24 lg:py-32\">\n <div className=\"grid lg:grid-cols-2 gap-12 lg:gap-16 items-center\">\n {/* Text column */}\n <div className=\"flex flex-col gap-6\">\n {/* Category badge */}\n <ScrollReveal y={20} duration={0.6}>\n <span\n className=\"inline-flex items-center self-start px-4 py-1.5 rounded-full text-sm font-medium\"\n style={{\n background: 'color-mix(in srgb, var(--sk-primary, #6366f1) 15%, transparent)',\n color: 'var(--sk-primary, #6366f1)',\n border: '1px solid color-mix(in srgb, var(--sk-primary, #6366f1) 25%, transparent)',\n }}\n >\n {data.category}\n </span>\n </ScrollReveal>\n\n {/* Headline */}\n <SplitHeadline\n tag=\"h1\"\n className=\"text-4xl md:text-5xl lg:text-6xl font-bold leading-tight\"\n style={{\n color: 'var(--sk-text-primary, #ffffff)',\n fontFamily: 'var(--sk-font-heading, inherit)',\n }}\n >\n {data.headline}\n </SplitHeadline>\n\n {/* Subheadline */}\n <ScrollReveal y={20} delay={0.2}>\n <p\n className=\"text-xl md:text-2xl font-medium\"\n style={{ color: 'var(--sk-text-secondary, #a1a1aa)' }}\n >\n {data.subheadline}\n </p>\n </ScrollReveal>\n\n {/* Description */}\n <ScrollReveal y={20} delay={0.3}>\n <p\n className=\"text-base md:text-lg leading-relaxed\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n >\n {data.description}\n </p>\n </ScrollReveal>\n\n {/* Services tags */}\n {data.services.length > 0 && (\n <ScrollReveal y={20} delay={0.4} stagger={0.05}>\n <div className=\"flex flex-wrap gap-2\">\n {data.services.map((service) => (\n <span\n key={service}\n className=\"px-3 py-1 rounded-full text-xs 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 </div>\n </ScrollReveal>\n )}\n\n {/* CTA button */}\n {url && (\n <ScrollReveal y={20} delay={0.5}>\n <a\n href={url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"inline-flex items-center gap-2 self-start px-6 py-3 rounded-xl text-sm font-semibold transition-opacity hover:opacity-90\"\n style={{\n background: 'var(--sk-primary, #6366f1)',\n color: '#ffffff',\n }}\n >\n View Live Site\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M6 3h7v7M13 3L3 13\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n </a>\n </ScrollReveal>\n )}\n\n {/* KPI counter row */}\n {data.kpis.length > 0 && (\n <ScrollReveal y={30} delay={0.6} stagger={0.1}>\n <div className=\"grid grid-cols-2 md:grid-cols-3 gap-4 mt-4\">\n {data.kpis.map((kpi, i) => (\n <GlassCard key={i} padding=\"sm\" hover={false} className=\"text-center\">\n <div\n className=\"text-2xl md:text-3xl font-bold\"\n style={{ color: 'var(--sk-primary, #6366f1)' }}\n >\n <AnimatedCounter\n value={kpi.value}\n suffix={kpi.suffix}\n prefix={kpi.prefix}\n duration={2.5}\n />\n </div>\n <div\n className=\"text-xs mt-1 font-medium\"\n style={{ color: 'var(--sk-text-secondary, #a1a1aa)' }}\n >\n {kpi.label}\n </div>\n {/* Source badge */}\n <span\n className=\"inline-block mt-1.5 px-2 py-0.5 rounded text-[10px] uppercase tracking-wider font-medium\"\n style={{\n background:\n kpi.source === 'measured'\n ? 'color-mix(in srgb, #10b981 15%, transparent)'\n : 'color-mix(in srgb, #f59e0b 15%, transparent)',\n color: kpi.source === 'measured' ? '#10b981' : '#f59e0b',\n }}\n >\n {kpi.source}\n </span>\n </GlassCard>\n ))}\n </div>\n </ScrollReveal>\n )}\n </div>\n\n {/* Device trifolio column */}\n {(shots?.desktop || shots?.tablet || shots?.mobile) && (\n <div ref={deviceRef} className=\"relative flex items-end justify-center gap-3 lg:gap-4 min-h-[400px]\">\n {/* Desktop */}\n {shots.desktop && (\n <div\n className=\"relative w-full max-w-[480px] rounded-lg overflow-hidden shadow-2xl\"\n style={{\n border: '2px solid var(--sk-border, rgba(255,255,255,0.1))',\n aspectRatio: '16/10',\n }}\n >\n <img\n src={shots.desktop}\n alt=\"Desktop screenshot\"\n className=\"w-full h-full object-cover object-top\"\n loading=\"eager\"\n />\n </div>\n )}\n {/* Tablet */}\n {shots.tablet && (\n <div\n className=\"absolute bottom-0 right-0 w-[45%] max-w-[220px] rounded-lg overflow-hidden shadow-2xl z-10\"\n style={{\n border: '2px solid var(--sk-border, rgba(255,255,255,0.1))',\n aspectRatio: '3/4',\n transform: 'translateX(10%) translateY(5%)',\n }}\n >\n <img\n src={shots.tablet}\n alt=\"Tablet screenshot\"\n className=\"w-full h-full object-cover object-top\"\n loading=\"eager\"\n />\n </div>\n )}\n {/* Mobile */}\n {shots.mobile && (\n <div\n className=\"absolute bottom-0 right-[15%] w-[25%] max-w-[120px] rounded-xl overflow-hidden shadow-2xl z-20\"\n style={{\n border: '2px solid var(--sk-border, rgba(255,255,255,0.1))',\n aspectRatio: '9/19',\n transform: 'translateY(10%)',\n }}\n >\n <img\n src={shots.mobile}\n alt=\"Mobile screenshot\"\n className=\"w-full h-full object-cover object-top\"\n loading=\"eager\"\n />\n </div>\n )}\n </div>\n )}\n </div>\n </div>\n </section>\n );\n}\n","/**\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\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)\nimport HeroSection from './sections/HeroSection';\n\n// All other sections are loaded lazily\nconst ChallengesSection = dynamic(() => import('./sections/ChallengesSection'), { ssr: true });\nconst StrategySection = dynamic(() => import('./sections/StrategySection'), { ssr: true });\nconst ResultsSection = dynamic(() => import('./sections/ResultsSection'), { ssr: true });\nconst TechStackSection = dynamic(() => import('./sections/TechStackSection'), { ssr: true });\nconst ServicesSection = dynamic(() => import('./sections/ServicesSection'), { ssr: true });\nconst TestimonialSection = dynamic(() => import('./sections/TestimonialSection'), { ssr: true });\nconst GallerySection = dynamic(() => import('./sections/GallerySection'), { ssr: true });\nconst VideoSection = dynamic(() => import('./sections/VideoSection'), { ssr: true });\nconst TeamSection = dynamic(() => import('./sections/TeamSection'), { ssr: true });\nconst FeatureSpotlightSection = dynamic(() => import('./sections/FeatureSpotlightSection'), { ssr: true });\nconst BeforeAfterSection = dynamic(() => import('./sections/BeforeAfterSection'), { ssr: true });\nconst MetricsTimelineSection = dynamic(() => import('./sections/MetricsTimelineSection'), { ssr: true });\nconst ConversionFunnelSection = dynamic(() => import('./sections/ConversionFunnelSection'), { ssr: true });\nconst DetailsSection = dynamic(() => import('./sections/DetailsSection'), { ssr: true });\nconst CTASection = dynamic(() => import('./sections/CTASection'), { ssr: true });\n\ninterface PortfolioPageProps {\n item: PortfolioItemFull;\n}\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 <ResultsSection key={key} data={section.data as PortfolioResultsData} />;\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 '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\nexport default function PortfolioPage({ item }: PortfolioPageProps) {\n return (\n <article className=\"w-full flex flex-col\">\n {item.sections.map((section, index) => {\n const rendered = renderSection(section, item, index);\n if (!rendered) return null;\n return (\n <div key={`wrapper-${section.sectionType}-${index}`} className=\"w-full\">\n {rendered}\n </div>\n );\n })}\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 '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 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\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"]}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useRef, useState, useEffect } from 'react';
|
|
3
|
+
import gsap from 'gsap';
|
|
4
|
+
import { ScrollTrigger } from 'gsap/ScrollTrigger';
|
|
5
|
+
import { jsx } from 'react/jsx-runtime';
|
|
6
|
+
|
|
7
|
+
// src/portfolio/components/primitives/AnimatedCounter.tsx
|
|
8
|
+
function AnimatedCounter({
|
|
9
|
+
value,
|
|
10
|
+
suffix = "",
|
|
11
|
+
prefix = "",
|
|
12
|
+
duration = 2,
|
|
13
|
+
decimals = 0,
|
|
14
|
+
className,
|
|
15
|
+
once = true
|
|
16
|
+
}) {
|
|
17
|
+
const ref = useRef(null);
|
|
18
|
+
const [display, setDisplay] = useState(`${prefix}0${suffix}`);
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
gsap.registerPlugin(ScrollTrigger);
|
|
21
|
+
const el = ref.current;
|
|
22
|
+
if (!el) return;
|
|
23
|
+
const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
24
|
+
if (prefersReducedMotion) {
|
|
25
|
+
setDisplay(`${prefix}${value.toFixed(decimals)}${suffix}`);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const counter = { val: 0 };
|
|
29
|
+
const ctx = gsap.context(() => {
|
|
30
|
+
gsap.to(counter, {
|
|
31
|
+
val: value,
|
|
32
|
+
duration,
|
|
33
|
+
ease: "power2.out",
|
|
34
|
+
snap: decimals === 0 ? { val: 1 } : void 0,
|
|
35
|
+
scrollTrigger: {
|
|
36
|
+
trigger: el,
|
|
37
|
+
start: "top 85%",
|
|
38
|
+
toggleActions: once ? "play none none none" : "play reverse play reverse"
|
|
39
|
+
},
|
|
40
|
+
onUpdate() {
|
|
41
|
+
const formatted = decimals > 0 ? counter.val.toFixed(decimals) : Math.round(counter.val).toLocaleString();
|
|
42
|
+
setDisplay(`${prefix}${formatted}${suffix}`);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
}, el);
|
|
46
|
+
return () => ctx.revert();
|
|
47
|
+
}, [value, suffix, prefix, duration, decimals, once]);
|
|
48
|
+
return /* @__PURE__ */ jsx("span", { ref, className, children: display });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export { AnimatedCounter };
|
|
52
|
+
//# sourceMappingURL=chunk-2Y4O3LWM.js.map
|
|
53
|
+
//# sourceMappingURL=chunk-2Y4O3LWM.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/portfolio/components/primitives/AnimatedCounter.tsx"],"names":[],"mappings":";;;;;;AAgBe,SAAR,eAAA,CAAiC;AAAA,EACtC,KAAA;AAAA,EACA,MAAA,GAAS,EAAA;AAAA,EACT,MAAA,GAAS,EAAA;AAAA,EACT,QAAA,GAAW,CAAA;AAAA,EACX,QAAA,GAAW,CAAA;AAAA,EACX,SAAA;AAAA,EACA,IAAA,GAAO;AACT,CAAA,EAAyB;AACvB,EAAA,MAAM,GAAA,GAAM,OAAwB,IAAI,CAAA;AACxC,EAAA,MAAM,CAAC,SAAS,UAAU,CAAA,GAAI,SAAS,CAAA,EAAG,MAAM,CAAA,CAAA,EAAI,MAAM,CAAA,CAAE,CAAA;AAE5D,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAA,CAAK,eAAe,aAAa,CAAA;AAEjC,IAAA,MAAM,KAAK,GAAA,CAAI,OAAA;AACf,IAAA,IAAI,CAAC,EAAA,EAAI;AAGT,IAAA,MAAM,oBAAA,GAAuB,MAAA,CAAO,UAAA,CAAW,kCAAkC,CAAA,CAAE,OAAA;AACnF,IAAA,IAAI,oBAAA,EAAsB;AACxB,MAAA,UAAA,CAAW,CAAA,EAAG,MAAM,CAAA,EAAG,KAAA,CAAM,QAAQ,QAAQ,CAAC,CAAA,EAAG,MAAM,CAAA,CAAE,CAAA;AACzD,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,OAAA,GAAU,EAAE,GAAA,EAAK,CAAA,EAAE;AAEzB,IAAA,MAAM,GAAA,GAAM,IAAA,CAAK,OAAA,CAAQ,MAAM;AAC7B,MAAA,IAAA,CAAK,GAAG,OAAA,EAAS;AAAA,QACf,GAAA,EAAK,KAAA;AAAA,QACL,QAAA;AAAA,QACA,IAAA,EAAM,YAAA;AAAA,QACN,MAAM,QAAA,KAAa,CAAA,GAAI,EAAE,GAAA,EAAK,GAAE,GAAI,MAAA;AAAA,QACpC,aAAA,EAAe;AAAA,UACb,OAAA,EAAS,EAAA;AAAA,UACT,KAAA,EAAO,SAAA;AAAA,UACP,aAAA,EAAe,OAAO,qBAAA,GAAwB;AAAA,SAChD;AAAA,QACA,QAAA,GAAW;AACT,UAAA,MAAM,SAAA,GAAY,QAAA,GAAW,CAAA,GACzB,OAAA,CAAQ,GAAA,CAAI,OAAA,CAAQ,QAAQ,CAAA,GAC5B,IAAA,CAAK,KAAA,CAAM,OAAA,CAAQ,GAAG,EAAE,cAAA,EAAe;AAC3C,UAAA,UAAA,CAAW,GAAG,MAAM,CAAA,EAAG,SAAS,CAAA,EAAG,MAAM,CAAA,CAAE,CAAA;AAAA,QAC7C;AAAA,OACD,CAAA;AAAA,IACH,GAAG,EAAE,CAAA;AAEL,IAAA,OAAO,MAAM,IAAI,MAAA,EAAO;AAAA,EAC1B,CAAA,EAAG,CAAC,KAAA,EAAO,MAAA,EAAQ,QAAQ,QAAA,EAAU,QAAA,EAAU,IAAI,CAAC,CAAA;AAEpD,EAAA,uBACE,GAAA,CAAC,MAAA,EAAA,EAAK,GAAA,EAAU,SAAA,EACb,QAAA,EAAA,OAAA,EACH,CAAA;AAEJ","file":"chunk-2Y4O3LWM.js","sourcesContent":["'use client';\n\nimport React, { useRef, useEffect, useState } from 'react';\nimport gsap from 'gsap';\nimport { ScrollTrigger } from 'gsap/ScrollTrigger';\n\ninterface AnimatedCounterProps {\n value: number;\n suffix?: string;\n prefix?: string;\n duration?: number;\n decimals?: number;\n className?: string;\n once?: boolean;\n}\n\nexport default function AnimatedCounter({\n value,\n suffix = '',\n prefix = '',\n duration = 2,\n decimals = 0,\n className,\n once = true,\n}: AnimatedCounterProps) {\n const ref = useRef<HTMLSpanElement>(null);\n const [display, setDisplay] = useState(`${prefix}0${suffix}`);\n\n useEffect(() => {\n gsap.registerPlugin(ScrollTrigger);\n\n const el = ref.current;\n if (!el) return;\n\n // Respect prefers-reduced-motion\n const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n if (prefersReducedMotion) {\n setDisplay(`${prefix}${value.toFixed(decimals)}${suffix}`);\n return;\n }\n\n const counter = { val: 0 };\n\n const ctx = gsap.context(() => {\n gsap.to(counter, {\n val: value,\n duration,\n ease: 'power2.out',\n snap: decimals === 0 ? { val: 1 } : undefined,\n scrollTrigger: {\n trigger: el,\n start: 'top 85%',\n toggleActions: once ? 'play none none none' : 'play reverse play reverse',\n },\n onUpdate() {\n const formatted = decimals > 0\n ? counter.val.toFixed(decimals)\n : Math.round(counter.val).toLocaleString();\n setDisplay(`${prefix}${formatted}${suffix}`);\n },\n });\n }, el);\n\n return () => ctx.revert();\n }, [value, suffix, prefix, duration, decimals, once]);\n\n return (\n <span ref={ref} className={className}>\n {display}\n </span>\n );\n}\n"]}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunkQBLWP25X_cjs = require('./chunk-QBLWP25X.cjs');
|
|
4
|
+
var React = require('react');
|
|
5
|
+
|
|
6
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
7
|
+
|
|
8
|
+
var React__default = /*#__PURE__*/_interopDefault(React);
|
|
9
|
+
|
|
10
|
+
// src/shared/brand.ts
|
|
11
|
+
function hexToRgb(hex) {
|
|
12
|
+
if (!hex || typeof hex !== "string") return null;
|
|
13
|
+
const cleaned = hex.replace(/^#/, "");
|
|
14
|
+
let r;
|
|
15
|
+
let g;
|
|
16
|
+
let b;
|
|
17
|
+
if (cleaned.length === 3) {
|
|
18
|
+
r = parseInt(cleaned[0] + cleaned[0], 16);
|
|
19
|
+
g = parseInt(cleaned[1] + cleaned[1], 16);
|
|
20
|
+
b = parseInt(cleaned[2] + cleaned[2], 16);
|
|
21
|
+
} else if (cleaned.length === 6) {
|
|
22
|
+
r = parseInt(cleaned.substring(0, 2), 16);
|
|
23
|
+
g = parseInt(cleaned.substring(2, 4), 16);
|
|
24
|
+
b = parseInt(cleaned.substring(4, 6), 16);
|
|
25
|
+
} else {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
if (isNaN(r) || isNaN(g) || isNaN(b)) return null;
|
|
29
|
+
return `${r}, ${g}, ${b}`;
|
|
30
|
+
}
|
|
31
|
+
var DEFAULT_BRAND_CONFIG = {
|
|
32
|
+
primary: "#6366f1",
|
|
33
|
+
secondary: "#8b5cf6",
|
|
34
|
+
background: "#ffffff",
|
|
35
|
+
backgroundElevated: "#f8fafc",
|
|
36
|
+
surface: "#f1f5f9",
|
|
37
|
+
surfaceHover: "#e2e8f0",
|
|
38
|
+
surfaceBorder: "#e2e8f0",
|
|
39
|
+
textPrimary: "#0f172a",
|
|
40
|
+
textSecondary: "#475569",
|
|
41
|
+
textTertiary: "#94a3b8",
|
|
42
|
+
fontHeading: "'Inter', system-ui, -apple-system, sans-serif",
|
|
43
|
+
fontBody: "'Inter', system-ui, -apple-system, sans-serif",
|
|
44
|
+
radius: {
|
|
45
|
+
sm: "0.375rem",
|
|
46
|
+
md: "0.5rem",
|
|
47
|
+
lg: "0.75rem"
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
var DEFAULT_DARK_MODE = {
|
|
51
|
+
background: "#0f172a",
|
|
52
|
+
backgroundElevated: "#1e293b",
|
|
53
|
+
surface: "#1e293b",
|
|
54
|
+
surfaceHover: "#334155",
|
|
55
|
+
surfaceBorder: "#334155",
|
|
56
|
+
textPrimary: "#f8fafc",
|
|
57
|
+
textSecondary: "#cbd5e1",
|
|
58
|
+
textTertiary: "#64748b"
|
|
59
|
+
};
|
|
60
|
+
function buildVariables(config) {
|
|
61
|
+
const primaryRgb = hexToRgb(config.primary) || "99, 102, 241";
|
|
62
|
+
const secondaryRgb = hexToRgb(config.secondary) || "139, 92, 246";
|
|
63
|
+
const radius = config.radius || DEFAULT_BRAND_CONFIG.radius;
|
|
64
|
+
return [
|
|
65
|
+
// Primary tokens (--sk-*)
|
|
66
|
+
`--sk-primary: ${config.primary};`,
|
|
67
|
+
`--sk-primary-rgb: ${primaryRgb};`,
|
|
68
|
+
`--sk-secondary: ${config.secondary};`,
|
|
69
|
+
`--sk-secondary-rgb: ${secondaryRgb};`,
|
|
70
|
+
`--sk-bg: ${config.background};`,
|
|
71
|
+
`--sk-bg-elevated: ${config.backgroundElevated};`,
|
|
72
|
+
`--sk-surface: ${config.surface};`,
|
|
73
|
+
`--sk-surface-hover: ${config.surfaceHover};`,
|
|
74
|
+
`--sk-border: ${config.surfaceBorder};`,
|
|
75
|
+
`--sk-text-primary: ${config.textPrimary};`,
|
|
76
|
+
`--sk-text-secondary: ${config.textSecondary};`,
|
|
77
|
+
`--sk-text-tertiary: ${config.textTertiary};`,
|
|
78
|
+
`--sk-radius-sm: ${radius.sm};`,
|
|
79
|
+
`--sk-radius: ${radius.md};`,
|
|
80
|
+
`--sk-radius-lg: ${radius.lg};`,
|
|
81
|
+
`--sk-font-heading: ${config.fontHeading};`,
|
|
82
|
+
`--sk-font: ${config.fontBody};`
|
|
83
|
+
].join("\n ");
|
|
84
|
+
}
|
|
85
|
+
function buildDarkVariables(base) {
|
|
86
|
+
const dark = {
|
|
87
|
+
...DEFAULT_DARK_MODE,
|
|
88
|
+
...base.darkMode,
|
|
89
|
+
// Primary/secondary carry over unless explicitly overridden
|
|
90
|
+
primary: base.darkMode?.primary || base.primary,
|
|
91
|
+
secondary: base.darkMode?.secondary || base.secondary
|
|
92
|
+
};
|
|
93
|
+
const primaryRgb = hexToRgb(dark.primary) || hexToRgb(base.primary) || "99, 102, 241";
|
|
94
|
+
const secondaryRgb = hexToRgb(dark.secondary) || hexToRgb(base.secondary) || "139, 92, 246";
|
|
95
|
+
return [
|
|
96
|
+
// Primary tokens (--sk-*)
|
|
97
|
+
`--sk-primary: ${dark.primary};`,
|
|
98
|
+
`--sk-primary-rgb: ${primaryRgb};`,
|
|
99
|
+
`--sk-secondary: ${dark.secondary};`,
|
|
100
|
+
`--sk-secondary-rgb: ${secondaryRgb};`,
|
|
101
|
+
`--sk-bg: ${dark.background || DEFAULT_DARK_MODE.background};`,
|
|
102
|
+
`--sk-bg-elevated: ${dark.backgroundElevated || DEFAULT_DARK_MODE.backgroundElevated};`,
|
|
103
|
+
`--sk-surface: ${dark.surface || DEFAULT_DARK_MODE.surface};`,
|
|
104
|
+
`--sk-surface-hover: ${dark.surfaceHover || DEFAULT_DARK_MODE.surfaceHover};`,
|
|
105
|
+
`--sk-border: ${dark.surfaceBorder || DEFAULT_DARK_MODE.surfaceBorder};`,
|
|
106
|
+
`--sk-text-primary: ${dark.textPrimary || DEFAULT_DARK_MODE.textPrimary};`,
|
|
107
|
+
`--sk-text-secondary: ${dark.textSecondary || DEFAULT_DARK_MODE.textSecondary};`,
|
|
108
|
+
`--sk-text-tertiary: ${dark.textTertiary || DEFAULT_DARK_MODE.textTertiary};`
|
|
109
|
+
].join("\n ");
|
|
110
|
+
}
|
|
111
|
+
function generateBrandCSS(config) {
|
|
112
|
+
const merged = {
|
|
113
|
+
...DEFAULT_BRAND_CONFIG,
|
|
114
|
+
...config,
|
|
115
|
+
radius: {
|
|
116
|
+
...DEFAULT_BRAND_CONFIG.radius,
|
|
117
|
+
...config.radius
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
const lightVars = buildVariables(merged);
|
|
121
|
+
const darkVars = buildDarkVariables(merged);
|
|
122
|
+
return `
|
|
123
|
+
:root, [data-theme="light"] {
|
|
124
|
+
${lightVars}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
[data-theme="dark"] {
|
|
128
|
+
${darkVars}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
@media (prefers-color-scheme: dark) {
|
|
132
|
+
:root:not([data-theme="light"]) {
|
|
133
|
+
${darkVars}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
`.trim();
|
|
137
|
+
}
|
|
138
|
+
function mergeBrandConfig(partial) {
|
|
139
|
+
return {
|
|
140
|
+
...DEFAULT_BRAND_CONFIG,
|
|
141
|
+
...partial,
|
|
142
|
+
radius: {
|
|
143
|
+
...DEFAULT_BRAND_CONFIG.radius,
|
|
144
|
+
...partial.radius
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// src/layout/AgencySiteKitLayout.tsx
|
|
150
|
+
function portalApiOrigin(apiUrl) {
|
|
151
|
+
try {
|
|
152
|
+
return new URL(apiUrl.replace(/\/$/, "")).origin;
|
|
153
|
+
} catch {
|
|
154
|
+
return "https://api.sonor.io";
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
async function fetchBrandConfig(apiKey, apiUrl) {
|
|
158
|
+
const response = await chunkQBLWP25X_cjs.apiGet(
|
|
159
|
+
"/api/public/portfolio/config",
|
|
160
|
+
3600,
|
|
161
|
+
apiKey,
|
|
162
|
+
apiUrl
|
|
163
|
+
);
|
|
164
|
+
if (response?.brand) {
|
|
165
|
+
return mergeBrandConfig(response.brand);
|
|
166
|
+
}
|
|
167
|
+
return mergeBrandConfig({});
|
|
168
|
+
}
|
|
169
|
+
async function AgencySiteKitLayout({
|
|
170
|
+
children,
|
|
171
|
+
apiKey,
|
|
172
|
+
apiUrl,
|
|
173
|
+
brandConfig: brandConfigOverride,
|
|
174
|
+
theme,
|
|
175
|
+
className
|
|
176
|
+
}) {
|
|
177
|
+
const brandConfig = brandConfigOverride ? mergeBrandConfig(brandConfigOverride) : await fetchBrandConfig(apiKey, apiUrl);
|
|
178
|
+
const cssString = generateBrandCSS(brandConfig);
|
|
179
|
+
const resolvedApiUrl = apiUrl ?? process.env.SONOR_API_URL ?? process.env.UPTRADE_API_URL ?? "https://api.sonor.io";
|
|
180
|
+
const preconnectHref = portalApiOrigin(resolvedApiUrl);
|
|
181
|
+
return React__default.default.createElement(
|
|
182
|
+
React__default.default.Fragment,
|
|
183
|
+
null,
|
|
184
|
+
React__default.default.createElement("link", {
|
|
185
|
+
rel: "preconnect",
|
|
186
|
+
href: preconnectHref,
|
|
187
|
+
crossOrigin: "anonymous"
|
|
188
|
+
}),
|
|
189
|
+
React__default.default.createElement("link", {
|
|
190
|
+
rel: "dns-prefetch",
|
|
191
|
+
href: preconnectHref
|
|
192
|
+
}),
|
|
193
|
+
React__default.default.createElement("style", {
|
|
194
|
+
dangerouslySetInnerHTML: { __html: cssString },
|
|
195
|
+
"data-agency-site-kit": "brand"
|
|
196
|
+
}),
|
|
197
|
+
React__default.default.createElement(
|
|
198
|
+
"div",
|
|
199
|
+
{
|
|
200
|
+
"data-theme": theme || void 0,
|
|
201
|
+
"data-agency-site-kit": "root",
|
|
202
|
+
className,
|
|
203
|
+
style: {
|
|
204
|
+
fontFamily: "var(--sk-font)",
|
|
205
|
+
color: "var(--sk-text-primary)",
|
|
206
|
+
backgroundColor: "var(--sk-bg)"
|
|
207
|
+
}
|
|
208
|
+
},
|
|
209
|
+
children
|
|
210
|
+
)
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
exports.AgencySiteKitLayout = AgencySiteKitLayout;
|
|
215
|
+
exports.DEFAULT_BRAND_CONFIG = DEFAULT_BRAND_CONFIG;
|
|
216
|
+
exports.DEFAULT_DARK_MODE = DEFAULT_DARK_MODE;
|
|
217
|
+
exports.generateBrandCSS = generateBrandCSS;
|
|
218
|
+
exports.hexToRgb = hexToRgb;
|
|
219
|
+
exports.mergeBrandConfig = mergeBrandConfig;
|
|
220
|
+
//# sourceMappingURL=chunk-5FKOLIV6.cjs.map
|
|
221
|
+
//# sourceMappingURL=chunk-5FKOLIV6.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/shared/brand.ts","../src/layout/AgencySiteKitLayout.tsx"],"names":["apiGet","React"],"mappings":"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","file":"chunk-5FKOLIV6.cjs","sourcesContent":["/**\n * @sonordev/agency-site-kit — Brand customization system\n *\n * Generates CSS custom properties from a BrandConfig object.\n * Supports light/dark mode with automatic fallbacks.\n */\n\nimport type { BrandConfig } from '../types';\n\n// ---------------------------------------------------------------------------\n// Hex to RGB helper\n// ---------------------------------------------------------------------------\n\n/**\n * Converts a hex color string to an \"R, G, B\" string for use in rgb()/rgba().\n * Supports 3-digit (#abc) and 6-digit (#aabbcc) hex values.\n * Returns null for invalid inputs.\n */\nexport function hexToRgb(hex: string): string | null {\n if (!hex || typeof hex !== 'string') return null;\n\n const cleaned = hex.replace(/^#/, '');\n\n let r: number;\n let g: number;\n let b: number;\n\n if (cleaned.length === 3) {\n r = parseInt(cleaned[0] + cleaned[0], 16);\n g = parseInt(cleaned[1] + cleaned[1], 16);\n b = parseInt(cleaned[2] + cleaned[2], 16);\n } else if (cleaned.length === 6) {\n r = parseInt(cleaned.substring(0, 2), 16);\n g = parseInt(cleaned.substring(2, 4), 16);\n b = parseInt(cleaned.substring(4, 6), 16);\n } else {\n return null;\n }\n\n if (isNaN(r) || isNaN(g) || isNaN(b)) return null;\n\n return `${r}, ${g}, ${b}`;\n}\n\n// ---------------------------------------------------------------------------\n// Default theme values\n// ---------------------------------------------------------------------------\n\nexport const DEFAULT_BRAND_CONFIG: BrandConfig = {\n primary: '#6366f1',\n secondary: '#8b5cf6',\n background: '#ffffff',\n backgroundElevated: '#f8fafc',\n surface: '#f1f5f9',\n surfaceHover: '#e2e8f0',\n surfaceBorder: '#e2e8f0',\n textPrimary: '#0f172a',\n textSecondary: '#475569',\n textTertiary: '#94a3b8',\n fontHeading: \"'Inter', system-ui, -apple-system, sans-serif\",\n fontBody: \"'Inter', system-ui, -apple-system, sans-serif\",\n radius: {\n sm: '0.375rem',\n md: '0.5rem',\n lg: '0.75rem',\n },\n};\n\nexport const DEFAULT_DARK_MODE: Partial<BrandConfig> = {\n background: '#0f172a',\n backgroundElevated: '#1e293b',\n surface: '#1e293b',\n surfaceHover: '#334155',\n surfaceBorder: '#334155',\n textPrimary: '#f8fafc',\n textSecondary: '#cbd5e1',\n textTertiary: '#64748b',\n};\n\n// ---------------------------------------------------------------------------\n// CSS variable generation\n// ---------------------------------------------------------------------------\n\nfunction buildVariables(config: BrandConfig): string {\n const primaryRgb = hexToRgb(config.primary) || '99, 102, 241';\n const secondaryRgb = hexToRgb(config.secondary) || '139, 92, 246';\n\n const radius = config.radius || DEFAULT_BRAND_CONFIG.radius!;\n\n return [\n // Primary tokens (--sk-*)\n `--sk-primary: ${config.primary};`,\n `--sk-primary-rgb: ${primaryRgb};`,\n `--sk-secondary: ${config.secondary};`,\n `--sk-secondary-rgb: ${secondaryRgb};`,\n `--sk-bg: ${config.background};`,\n `--sk-bg-elevated: ${config.backgroundElevated};`,\n `--sk-surface: ${config.surface};`,\n `--sk-surface-hover: ${config.surfaceHover};`,\n `--sk-border: ${config.surfaceBorder};`,\n `--sk-text-primary: ${config.textPrimary};`,\n `--sk-text-secondary: ${config.textSecondary};`,\n `--sk-text-tertiary: ${config.textTertiary};`,\n `--sk-radius-sm: ${radius.sm};`,\n `--sk-radius: ${radius.md};`,\n `--sk-radius-lg: ${radius.lg};`,\n `--sk-font-heading: ${config.fontHeading};`,\n `--sk-font: ${config.fontBody};`,\n ].join('\\n ');\n}\n\nfunction buildDarkVariables(base: BrandConfig): string {\n const dark = {\n ...DEFAULT_DARK_MODE,\n ...base.darkMode,\n // Primary/secondary carry over unless explicitly overridden\n primary: base.darkMode?.primary || base.primary,\n secondary: base.darkMode?.secondary || base.secondary,\n };\n\n const primaryRgb = hexToRgb(dark.primary!) || hexToRgb(base.primary) || '99, 102, 241';\n const secondaryRgb = hexToRgb(dark.secondary!) || hexToRgb(base.secondary) || '139, 92, 246';\n\n return [\n // Primary tokens (--sk-*)\n `--sk-primary: ${dark.primary};`,\n `--sk-primary-rgb: ${primaryRgb};`,\n `--sk-secondary: ${dark.secondary};`,\n `--sk-secondary-rgb: ${secondaryRgb};`,\n `--sk-bg: ${dark.background || DEFAULT_DARK_MODE.background};`,\n `--sk-bg-elevated: ${dark.backgroundElevated || DEFAULT_DARK_MODE.backgroundElevated};`,\n `--sk-surface: ${dark.surface || DEFAULT_DARK_MODE.surface};`,\n `--sk-surface-hover: ${dark.surfaceHover || DEFAULT_DARK_MODE.surfaceHover};`,\n `--sk-border: ${dark.surfaceBorder || DEFAULT_DARK_MODE.surfaceBorder};`,\n `--sk-text-primary: ${dark.textPrimary || DEFAULT_DARK_MODE.textPrimary};`,\n `--sk-text-secondary: ${dark.textSecondary || DEFAULT_DARK_MODE.textSecondary};`,\n `--sk-text-tertiary: ${dark.textTertiary || DEFAULT_DARK_MODE.textTertiary};`,\n ].join('\\n ');\n}\n\n/**\n * Generates a complete CSS string with all --sk-* custom properties.\n *\n * Produces:\n * - `:root` / `[data-theme=\"light\"]` block with light mode values\n * - `[data-theme=\"dark\"]` block with dark mode values\n * - `@media (prefers-color-scheme: dark)` fallback for auto detection\n *\n * Usage: inject the returned string into a `<style>` tag.\n */\nexport function generateBrandCSS(config: BrandConfig): string {\n const merged: BrandConfig = {\n ...DEFAULT_BRAND_CONFIG,\n ...config,\n radius: {\n ...DEFAULT_BRAND_CONFIG.radius!,\n ...config.radius,\n },\n };\n\n const lightVars = buildVariables(merged);\n const darkVars = buildDarkVariables(merged);\n\n return `\n:root, [data-theme=\"light\"] {\n ${lightVars}\n}\n\n[data-theme=\"dark\"] {\n ${darkVars}\n}\n\n@media (prefers-color-scheme: dark) {\n :root:not([data-theme=\"light\"]) {\n ${darkVars}\n }\n}\n`.trim();\n}\n\n/**\n * Merges a partial brand config with defaults, returning a complete BrandConfig.\n */\nexport function mergeBrandConfig(partial: Partial<BrandConfig>): BrandConfig {\n return {\n ...DEFAULT_BRAND_CONFIG,\n ...partial,\n radius: {\n ...DEFAULT_BRAND_CONFIG.radius!,\n ...partial.radius,\n },\n };\n}\n","/**\n * @sonordev/agency-site-kit — AgencySiteKitLayout\n *\n * Server component that wraps children with brand CSS variables.\n * Fetches the brand configuration from the Portal API and injects\n * a <style> tag with all --sk-* custom properties (plus --agency-* aliases for backwards compatibility).\n *\n * Usage in app/layout.tsx:\n *\n * ```tsx\n * import { AgencySiteKitLayout } from '@sonordev/agency-site-kit';\n *\n * export default function RootLayout({ children }) {\n * return (\n * <html lang=\"en\">\n * <body>\n * <AgencySiteKitLayout>\n * {children}\n * </AgencySiteKitLayout>\n * </body>\n * </html>\n * );\n * }\n * ```\n */\n\nimport React from 'react';\nimport { apiGet } from '../shared/api';\nimport { generateBrandCSS, mergeBrandConfig } from '../shared/brand';\nimport type { BrandConfig, PortfolioConfigResponse } from '../types';\n\nfunction portalApiOrigin(apiUrl: string): string {\n try {\n return new URL(apiUrl.replace(/\\/$/, '')).origin\n } catch {\n return 'https://api.sonor.io'\n }\n}\n\nexport interface AgencySiteKitLayoutProps {\n children: React.ReactNode;\n /** Override the API key (defaults to process.env.SONOR_API_KEY) */\n apiKey?: string;\n /** Override the API URL (defaults to process.env.SONOR_API_URL or https://api.sonor.io) */\n apiUrl?: string;\n /** Override brand config instead of fetching from API */\n brandConfig?: Partial<BrandConfig>;\n /** Force a specific theme: \"light\" | \"dark\". Omit for system preference. */\n theme?: 'light' | 'dark';\n /** Additional class name for the wrapper div */\n className?: string;\n}\n\n/**\n * Fetches brand configuration from the Portal API.\n * Returns a merged BrandConfig with defaults applied.\n * Falls back to defaults if the API call fails.\n */\nasync function fetchBrandConfig(\n apiKey?: string,\n apiUrl?: string,\n): Promise<BrandConfig> {\n const response = await apiGet<PortfolioConfigResponse>(\n '/api/public/portfolio/config',\n 3600,\n apiKey,\n apiUrl,\n );\n\n if (response?.brand) {\n return mergeBrandConfig(response.brand);\n }\n\n return mergeBrandConfig({});\n}\n\n/**\n * AgencySiteKitLayout — Server component wrapper\n *\n * Fetches the agency's brand configuration and injects CSS custom properties\n * so all child components can use --sk-* variables for theming.\n *\n * This is an async server component (RSC). It makes one API call at render\n * time, cached via Next.js ISR (default 1 hour revalidation).\n */\nexport async function AgencySiteKitLayout({\n children,\n apiKey,\n apiUrl,\n brandConfig: brandConfigOverride,\n theme,\n className,\n}: AgencySiteKitLayoutProps): Promise<React.ReactElement> {\n // Use override brand config if provided, otherwise fetch from API\n const brandConfig = brandConfigOverride\n ? mergeBrandConfig(brandConfigOverride)\n : await fetchBrandConfig(apiKey, apiUrl);\n\n const cssString = generateBrandCSS(brandConfig);\n\n const resolvedApiUrl =\n apiUrl ??\n process.env.SONOR_API_URL ??\n process.env.UPTRADE_API_URL ??\n 'https://api.sonor.io';\n const preconnectHref = portalApiOrigin(resolvedApiUrl);\n\n return React.createElement(\n React.Fragment,\n null,\n React.createElement('link', {\n rel: 'preconnect',\n href: preconnectHref,\n crossOrigin: 'anonymous',\n }),\n React.createElement('link', {\n rel: 'dns-prefetch',\n href: preconnectHref,\n }),\n React.createElement('style', {\n dangerouslySetInnerHTML: { __html: cssString },\n 'data-agency-site-kit': 'brand',\n }),\n React.createElement(\n 'div',\n {\n 'data-theme': theme || undefined,\n 'data-agency-site-kit': 'root',\n className,\n style: {\n fontFamily: 'var(--sk-font)',\n color: 'var(--sk-text-primary)',\n backgroundColor: 'var(--sk-bg)',\n },\n },\n children,\n ),\n );\n}\n"]}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useRef, useEffect } from 'react';
|
|
3
|
+
import gsap from 'gsap';
|
|
4
|
+
import { ScrollTrigger } from 'gsap/ScrollTrigger';
|
|
5
|
+
import { jsx } from 'react/jsx-runtime';
|
|
6
|
+
|
|
7
|
+
// src/portfolio/components/primitives/ScrollReveal.tsx
|
|
8
|
+
function ScrollReveal({
|
|
9
|
+
children,
|
|
10
|
+
className,
|
|
11
|
+
delay = 0,
|
|
12
|
+
duration = 0.8,
|
|
13
|
+
y = 40,
|
|
14
|
+
x = 0,
|
|
15
|
+
opacity = 0,
|
|
16
|
+
scale,
|
|
17
|
+
stagger,
|
|
18
|
+
once = true,
|
|
19
|
+
threshold = 0.15
|
|
20
|
+
}) {
|
|
21
|
+
const ref = useRef(null);
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
gsap.registerPlugin(ScrollTrigger);
|
|
24
|
+
const el = ref.current;
|
|
25
|
+
if (!el) return;
|
|
26
|
+
const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
27
|
+
if (prefersReducedMotion) {
|
|
28
|
+
gsap.set(el.children.length > 0 && stagger ? el.children : el, {
|
|
29
|
+
opacity: 1,
|
|
30
|
+
y: 0,
|
|
31
|
+
x: 0,
|
|
32
|
+
scale: 1
|
|
33
|
+
});
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const targets = stagger && el.children.length > 0 ? el.children : el;
|
|
37
|
+
const fromVars = {
|
|
38
|
+
opacity,
|
|
39
|
+
y,
|
|
40
|
+
x
|
|
41
|
+
};
|
|
42
|
+
if (scale !== void 0) {
|
|
43
|
+
fromVars.scale = scale;
|
|
44
|
+
}
|
|
45
|
+
const toVars = {
|
|
46
|
+
opacity: 1,
|
|
47
|
+
y: 0,
|
|
48
|
+
x: 0,
|
|
49
|
+
duration,
|
|
50
|
+
delay,
|
|
51
|
+
ease: "power2.out",
|
|
52
|
+
scrollTrigger: {
|
|
53
|
+
trigger: el,
|
|
54
|
+
start: `top ${(1 - threshold) * 100}%`,
|
|
55
|
+
toggleActions: once ? "play none none none" : "play reverse play reverse"
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
if (scale !== void 0) {
|
|
59
|
+
toVars.scale = 1;
|
|
60
|
+
}
|
|
61
|
+
if (stagger) {
|
|
62
|
+
toVars.stagger = stagger;
|
|
63
|
+
}
|
|
64
|
+
const ctx = gsap.context(() => {
|
|
65
|
+
gsap.fromTo(targets, fromVars, toVars);
|
|
66
|
+
}, el);
|
|
67
|
+
return () => ctx.revert();
|
|
68
|
+
}, [delay, duration, y, x, opacity, scale, stagger, once, threshold]);
|
|
69
|
+
return /* @__PURE__ */ jsx("div", { ref, className, children });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export { ScrollReveal };
|
|
73
|
+
//# sourceMappingURL=chunk-7CFFAKDM.js.map
|
|
74
|
+
//# sourceMappingURL=chunk-7CFFAKDM.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/portfolio/components/primitives/ScrollReveal.tsx"],"names":[],"mappings":";;;;;;AAoBe,SAAR,YAAA,CAA8B;AAAA,EACnC,QAAA;AAAA,EACA,SAAA;AAAA,EACA,KAAA,GAAQ,CAAA;AAAA,EACR,QAAA,GAAW,GAAA;AAAA,EACX,CAAA,GAAI,EAAA;AAAA,EACJ,CAAA,GAAI,CAAA;AAAA,EACJ,OAAA,GAAU,CAAA;AAAA,EACV,KAAA;AAAA,EACA,OAAA;AAAA,EACA,IAAA,GAAO,IAAA;AAAA,EACP,SAAA,GAAY;AACd,CAAA,EAAsB;AACpB,EAAA,MAAM,GAAA,GAAM,OAAuB,IAAI,CAAA;AAEvC,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAA,CAAK,eAAe,aAAa,CAAA;AAEjC,IAAA,MAAM,KAAK,GAAA,CAAI,OAAA;AACf,IAAA,IAAI,CAAC,EAAA,EAAI;AAGT,IAAA,MAAM,oBAAA,GAAuB,MAAA,CAAO,UAAA,CAAW,kCAAkC,CAAA,CAAE,OAAA;AACnF,IAAA,IAAI,oBAAA,EAAsB;AACxB,MAAA,IAAA,CAAK,GAAA,CAAI,GAAG,QAAA,CAAS,MAAA,GAAS,KAAK,OAAA,GAAU,EAAA,CAAG,WAAW,EAAA,EAAI;AAAA,QAC7D,OAAA,EAAS,CAAA;AAAA,QACT,CAAA,EAAG,CAAA;AAAA,QACH,CAAA,EAAG,CAAA;AAAA,QACH,KAAA,EAAO;AAAA,OACR,CAAA;AACD,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,UAAU,OAAA,IAAW,EAAA,CAAG,SAAS,MAAA,GAAS,CAAA,GAAI,GAAG,QAAA,GAAW,EAAA;AAElE,IAAA,MAAM,QAAA,GAA2B;AAAA,MAC/B,OAAA;AAAA,MACA,CAAA;AAAA,MACA;AAAA,KACF;AACA,IAAA,IAAI,UAAU,MAAA,EAAW;AACvB,MAAA,QAAA,CAAS,KAAA,GAAQ,KAAA;AAAA,IACnB;AAEA,IAAA,MAAM,MAAA,GAAyB;AAAA,MAC7B,OAAA,EAAS,CAAA;AAAA,MACT,CAAA,EAAG,CAAA;AAAA,MACH,CAAA,EAAG,CAAA;AAAA,MACH,QAAA;AAAA,MACA,KAAA;AAAA,MACA,IAAA,EAAM,YAAA;AAAA,MACN,aAAA,EAAe;AAAA,QACb,OAAA,EAAS,EAAA;AAAA,QACT,KAAA,EAAO,CAAA,IAAA,EAAA,CAAQ,CAAA,GAAI,SAAA,IAAa,GAAG,CAAA,CAAA,CAAA;AAAA,QACnC,aAAA,EAAe,OAAO,qBAAA,GAAwB;AAAA;AAChD,KACF;AACA,IAAA,IAAI,UAAU,MAAA,EAAW;AACvB,MAAA,MAAA,CAAO,KAAA,GAAQ,CAAA;AAAA,IACjB;AACA,IAAA,IAAI,OAAA,EAAS;AACX,MAAA,MAAA,CAAO,OAAA,GAAU,OAAA;AAAA,IACnB;AAEA,IAAA,MAAM,GAAA,GAAM,IAAA,CAAK,OAAA,CAAQ,MAAM;AAC7B,MAAA,IAAA,CAAK,MAAA,CAAO,OAAA,EAAS,QAAA,EAAU,MAAM,CAAA;AAAA,IACvC,GAAG,EAAE,CAAA;AAEL,IAAA,OAAO,MAAM,IAAI,MAAA,EAAO;AAAA,EAC1B,CAAA,EAAG,CAAC,KAAA,EAAO,QAAA,EAAU,CAAA,EAAG,CAAA,EAAG,OAAA,EAAS,KAAA,EAAO,OAAA,EAAS,IAAA,EAAM,SAAS,CAAC,CAAA;AAEpE,EAAA,uBACE,GAAA,CAAC,KAAA,EAAA,EAAI,GAAA,EAAU,SAAA,EACZ,QAAA,EACH,CAAA;AAEJ","file":"chunk-7CFFAKDM.js","sourcesContent":["'use client';\n\nimport React, { useRef, useEffect } from 'react';\nimport gsap from 'gsap';\nimport { ScrollTrigger } from 'gsap/ScrollTrigger';\n\ninterface ScrollRevealProps {\n children: React.ReactNode;\n className?: string;\n delay?: number;\n duration?: number;\n y?: number;\n x?: number;\n opacity?: number;\n scale?: number;\n stagger?: number;\n once?: boolean;\n threshold?: number;\n}\n\nexport default function ScrollReveal({\n children,\n className,\n delay = 0,\n duration = 0.8,\n y = 40,\n x = 0,\n opacity = 0,\n scale,\n stagger,\n once = true,\n threshold = 0.15,\n}: ScrollRevealProps) {\n const ref = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n gsap.registerPlugin(ScrollTrigger);\n\n const el = ref.current;\n if (!el) return;\n\n // Respect prefers-reduced-motion\n const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n if (prefersReducedMotion) {\n gsap.set(el.children.length > 0 && stagger ? el.children : el, {\n opacity: 1,\n y: 0,\n x: 0,\n scale: 1,\n });\n return;\n }\n\n const targets = stagger && el.children.length > 0 ? el.children : el;\n\n const fromVars: gsap.TweenVars = {\n opacity,\n y,\n x,\n };\n if (scale !== undefined) {\n fromVars.scale = scale;\n }\n\n const toVars: gsap.TweenVars = {\n opacity: 1,\n y: 0,\n x: 0,\n duration,\n delay,\n ease: 'power2.out',\n scrollTrigger: {\n trigger: el,\n start: `top ${(1 - threshold) * 100}%`,\n toggleActions: once ? 'play none none none' : 'play reverse play reverse',\n },\n };\n if (scale !== undefined) {\n toVars.scale = 1;\n }\n if (stagger) {\n toVars.stagger = stagger;\n }\n\n const ctx = gsap.context(() => {\n gsap.fromTo(targets, fromVars, toVars);\n }, el);\n\n return () => ctx.revert();\n }, [delay, duration, y, x, opacity, scale, stagger, once, threshold]);\n\n return (\n <div ref={ref} className={className}>\n {children}\n </div>\n );\n}\n"]}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// src/shared/api.ts
|
|
2
|
+
var DEFAULT_API_URL = "https://api.sonor.io";
|
|
3
|
+
var DEFAULT_REVALIDATE = 3600;
|
|
4
|
+
function getApiConfig() {
|
|
5
|
+
const apiKey = process.env.SONOR_API_KEY || process.env.UPTRADE_API_KEY || "";
|
|
6
|
+
if (!apiKey) {
|
|
7
|
+
console.warn(
|
|
8
|
+
"[@sonordev/agency-site-kit] Missing SONOR_API_KEY environment variable. Portfolio data will not be fetched."
|
|
9
|
+
);
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
const apiUrl = (process.env.SONOR_API_URL || process.env.NEXT_PUBLIC_SONOR_API_URL || DEFAULT_API_URL).replace(/\/+$/, "");
|
|
13
|
+
return { apiKey, apiUrl };
|
|
14
|
+
}
|
|
15
|
+
async function apiFetch(path, options = {}) {
|
|
16
|
+
const {
|
|
17
|
+
revalidate = DEFAULT_REVALIDATE,
|
|
18
|
+
headers: extraHeaders = {},
|
|
19
|
+
apiKey: overrideApiKey,
|
|
20
|
+
apiUrl: overrideApiUrl,
|
|
21
|
+
...fetchOptions
|
|
22
|
+
} = options;
|
|
23
|
+
let apiKey = overrideApiKey || "";
|
|
24
|
+
let apiUrl = overrideApiUrl || "";
|
|
25
|
+
if (!apiKey || !apiUrl) {
|
|
26
|
+
const config = getApiConfig();
|
|
27
|
+
if (!config) return null;
|
|
28
|
+
if (!apiKey) apiKey = config.apiKey;
|
|
29
|
+
if (!apiUrl) apiUrl = config.apiUrl;
|
|
30
|
+
}
|
|
31
|
+
const url = `${apiUrl}${path.startsWith("/") ? path : `/${path}`}`;
|
|
32
|
+
try {
|
|
33
|
+
const response = await fetch(url, {
|
|
34
|
+
...fetchOptions,
|
|
35
|
+
headers: {
|
|
36
|
+
"Content-Type": "application/json",
|
|
37
|
+
"x-api-key": apiKey,
|
|
38
|
+
...extraHeaders
|
|
39
|
+
},
|
|
40
|
+
next: { revalidate }
|
|
41
|
+
});
|
|
42
|
+
if (!response.ok) {
|
|
43
|
+
console.warn(
|
|
44
|
+
`[@sonordev/agency-site-kit] API request failed: ${response.status} ${response.statusText} \u2014 ${url}`
|
|
45
|
+
);
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
const data = await response.json();
|
|
49
|
+
return data;
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.warn(
|
|
52
|
+
`[@sonordev/agency-site-kit] API request error for ${url}:`,
|
|
53
|
+
error instanceof Error ? error.message : error
|
|
54
|
+
);
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
async function apiGet(path, revalidate, apiKey, apiUrl) {
|
|
59
|
+
return apiFetch(path, {
|
|
60
|
+
method: "GET",
|
|
61
|
+
revalidate,
|
|
62
|
+
apiKey,
|
|
63
|
+
apiUrl
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export { apiFetch, apiGet, getApiConfig };
|
|
68
|
+
//# sourceMappingURL=chunk-A4I4IK7V.js.map
|
|
69
|
+
//# sourceMappingURL=chunk-A4I4IK7V.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/shared/api.ts"],"names":[],"mappings":";AAOA,IAAM,eAAA,GAAkB,sBAAA;AACxB,IAAM,kBAAA,GAAqB,IAAA;AAYpB,SAAS,YAAA,GAAiC;AAC/C,EAAA,MAAM,SACJ,OAAA,CAAQ,GAAA,CAAI,aAAA,IACZ,OAAA,CAAQ,IAAI,eAAA,IACZ,EAAA;AAEF,EAAA,IAAI,CAAC,MAAA,EAAQ;AACX,IAAA,OAAA,CAAQ,IAAA;AAAA,MACN;AAAA,KAEF;AACA,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,MAAM,MAAA,GAAA,CACJ,OAAA,CAAQ,GAAA,CAAI,aAAA,IACZ,OAAA,CAAQ,IAAI,yBAAA,IACZ,eAAA,EACA,OAAA,CAAQ,MAAA,EAAQ,EAAE,CAAA;AAEpB,EAAA,OAAO,EAAE,QAAQ,MAAA,EAAO;AAC1B;AAqBA,eAAsB,QAAA,CACpB,IAAA,EACA,OAAA,GAA2B,EAAC,EACT;AACnB,EAAA,MAAM;AAAA,IACJ,UAAA,GAAa,kBAAA;AAAA,IACb,OAAA,EAAS,eAAe,EAAC;AAAA,IACzB,MAAA,EAAQ,cAAA;AAAA,IACR,MAAA,EAAQ,cAAA;AAAA,IACR,GAAG;AAAA,GACL,GAAI,OAAA;AAEJ,EAAA,IAAI,SAAS,cAAA,IAAkB,EAAA;AAC/B,EAAA,IAAI,SAAS,cAAA,IAAkB,EAAA;AAE/B,EAAA,IAAI,CAAC,MAAA,IAAU,CAAC,MAAA,EAAQ;AACtB,IAAA,MAAM,SAAS,YAAA,EAAa;AAC5B,IAAA,IAAI,CAAC,QAAQ,OAAO,IAAA;AACpB,IAAA,IAAI,CAAC,MAAA,EAAQ,MAAA,GAAS,MAAA,CAAO,MAAA;AAC7B,IAAA,IAAI,CAAC,MAAA,EAAQ,MAAA,GAAS,MAAA,CAAO,MAAA;AAAA,EAC/B;AAEA,EAAA,MAAM,GAAA,GAAM,CAAA,EAAG,MAAM,CAAA,EAAG,IAAA,CAAK,UAAA,CAAW,GAAG,CAAA,GAAI,IAAA,GAAO,CAAA,CAAA,EAAI,IAAI,CAAA,CAAE,CAAA,CAAA;AAEhE,EAAA,IAAI;AACF,IAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,GAAA,EAAK;AAAA,MAChC,GAAG,YAAA;AAAA,MACH,OAAA,EAAS;AAAA,QACP,cAAA,EAAgB,kBAAA;AAAA,QAChB,WAAA,EAAa,MAAA;AAAA,QACb,GAAG;AAAA,OACL;AAAA,MACA,IAAA,EAAM,EAAE,UAAA;AAAW,KACL,CAAA;AAEhB,IAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,MAAA,OAAA,CAAQ,IAAA;AAAA,QACN,mDAAmD,QAAA,CAAS,MAAM,IAAI,QAAA,CAAS,UAAU,WAAM,GAAG,CAAA;AAAA,OACpG;AACA,MAAA,OAAO,IAAA;AAAA,IACT;AAEA,IAAA,MAAM,IAAA,GAAO,MAAM,QAAA,CAAS,IAAA,EAAK;AACjC,IAAA,OAAO,IAAA;AAAA,EACT,SAAS,KAAA,EAAO;AACd,IAAA,OAAA,CAAQ,IAAA;AAAA,MACN,qDAAqD,GAAG,CAAA,CAAA,CAAA;AAAA,MACxD,KAAA,YAAiB,KAAA,GAAQ,KAAA,CAAM,OAAA,GAAU;AAAA,KAC3C;AACA,IAAA,OAAO,IAAA;AAAA,EACT;AACF;AAUA,eAAsB,MAAA,CACpB,IAAA,EACA,UAAA,EACA,MAAA,EACA,MAAA,EACmB;AACnB,EAAA,OAAO,SAAY,IAAA,EAAM;AAAA,IACvB,MAAA,EAAQ,KAAA;AAAA,IACR,UAAA;AAAA,IACA,MAAA;AAAA,IACA;AAAA,GACD,CAAA;AACH","file":"chunk-A4I4IK7V.js","sourcesContent":["/**\n * @sonordev/agency-site-kit — API fetch utilities\n *\n * Server-side only. Reads SONOR_API_KEY and SONOR_API_URL from process.env.\n * Uses Next.js ISR caching via `next: { revalidate }` on every fetch.\n */\n\nconst DEFAULT_API_URL = 'https://api.sonor.io';\nconst DEFAULT_REVALIDATE = 3600; // 1 hour\n\ninterface ApiConfig {\n apiKey: string;\n apiUrl: string;\n}\n\n/**\n * Reads API configuration from environment variables.\n * Falls back to UPTRADE_API_KEY for legacy compatibility.\n * Returns null if no API key is found.\n */\nexport function getApiConfig(): ApiConfig | null {\n const apiKey =\n process.env.SONOR_API_KEY ||\n process.env.UPTRADE_API_KEY ||\n '';\n\n if (!apiKey) {\n console.warn(\n '[@sonordev/agency-site-kit] Missing SONOR_API_KEY environment variable. ' +\n 'Portfolio data will not be fetched.',\n );\n return null;\n }\n\n const apiUrl = (\n process.env.SONOR_API_URL ||\n process.env.NEXT_PUBLIC_SONOR_API_URL ||\n DEFAULT_API_URL\n ).replace(/\\/+$/, '');\n\n return { apiKey, apiUrl };\n}\n\ninterface ApiFetchOptions extends Omit<RequestInit, 'headers'> {\n /** ISR revalidation interval in seconds. Defaults to 3600. Set to 0 for no caching. */\n revalidate?: number;\n /** Additional headers to merge with defaults */\n headers?: Record<string, string>;\n /** Override API key (e.g. passed from layout props) */\n apiKey?: string;\n /** Override API URL */\n apiUrl?: string;\n}\n\n/**\n * Fetch wrapper for the public portfolio API.\n *\n * - Injects `x-api-key` header automatically\n * - Parses JSON responses\n * - Uses Next.js ISR caching via `next.revalidate`\n * - Returns null on failure instead of throwing\n */\nexport async function apiFetch<T>(\n path: string,\n options: ApiFetchOptions = {},\n): Promise<T | null> {\n const {\n revalidate = DEFAULT_REVALIDATE,\n headers: extraHeaders = {},\n apiKey: overrideApiKey,\n apiUrl: overrideApiUrl,\n ...fetchOptions\n } = options;\n\n let apiKey = overrideApiKey || '';\n let apiUrl = overrideApiUrl || '';\n\n if (!apiKey || !apiUrl) {\n const config = getApiConfig();\n if (!config) return null;\n if (!apiKey) apiKey = config.apiKey;\n if (!apiUrl) apiUrl = config.apiUrl;\n }\n\n const url = `${apiUrl}${path.startsWith('/') ? path : `/${path}`}`;\n\n try {\n const response = await fetch(url, {\n ...fetchOptions,\n headers: {\n 'Content-Type': 'application/json',\n 'x-api-key': apiKey,\n ...extraHeaders,\n },\n next: { revalidate },\n } as RequestInit);\n\n if (!response.ok) {\n console.warn(\n `[@sonordev/agency-site-kit] API request failed: ${response.status} ${response.statusText} — ${url}`,\n );\n return null;\n }\n\n const data = await response.json();\n return data as T;\n } catch (error) {\n console.warn(\n `[@sonordev/agency-site-kit] API request error for ${url}:`,\n error instanceof Error ? error.message : error,\n );\n return null;\n }\n}\n\n/**\n * GET shorthand for apiFetch.\n *\n * @param path API path (e.g. \"/api/public/portfolio/items\")\n * @param revalidate ISR revalidation in seconds (default 3600)\n * @param apiKey Optional override API key\n * @param apiUrl Optional override API URL\n */\nexport async function apiGet<T>(\n path: string,\n revalidate?: number,\n apiKey?: string,\n apiUrl?: string,\n): Promise<T | null> {\n return apiFetch<T>(path, {\n method: 'GET',\n revalidate,\n apiKey,\n apiUrl,\n });\n}\n"]}
|