@sonordev/agency-site-kit 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-633UMQQH.cjs +31 -0
- package/dist/chunk-633UMQQH.cjs.map +1 -0
- package/dist/{chunk-FDFVLYAM.cjs → chunk-AVCH4LMG.cjs} +4 -21
- package/dist/chunk-AVCH4LMG.cjs.map +1 -0
- package/dist/{chunk-2UNKXWQM.js → chunk-AYBUHRFS.js} +3 -20
- package/dist/chunk-AYBUHRFS.js.map +1 -0
- package/dist/chunk-VVTK4OL6.js +28 -0
- package/dist/chunk-VVTK4OL6.js.map +1 -0
- package/dist/index.cjs +4 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/portfolio/index.cjs +4 -3
- package/dist/portfolio/index.js +2 -1
- package/dist/portfolio/server.cjs +6 -1
- package/dist/portfolio/server.cjs.map +1 -1
- package/dist/portfolio/server.js +6 -1
- package/dist/portfolio/server.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-2UNKXWQM.js.map +0 -1
- package/dist/chunk-FDFVLYAM.cjs.map +0 -1
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/portfolio/sanitize.ts
|
|
4
|
+
function humanizeArchPageEntry(page) {
|
|
5
|
+
const title = (page?.title || "").trim();
|
|
6
|
+
if (title && !title.startsWith("/")) return { title };
|
|
7
|
+
const source = title || (page?.path || "").trim();
|
|
8
|
+
const lastSegment = source.split(/[?#]/)[0].split("/").filter(Boolean).pop() || "";
|
|
9
|
+
const label = lastSegment.replace(/\.(html?|php|aspx?)$/i, "").replace(/[-_]+/g, " ").trim().replace(/\b\w/g, (c) => c.toUpperCase());
|
|
10
|
+
return { title: label || "Home" };
|
|
11
|
+
}
|
|
12
|
+
function sanitizeSiteArchitectureData(data) {
|
|
13
|
+
if (!data || !Array.isArray(data.groups)) return data;
|
|
14
|
+
return {
|
|
15
|
+
...data,
|
|
16
|
+
groups: data.groups.map((group) => ({
|
|
17
|
+
...group,
|
|
18
|
+
pages: Array.isArray(group.pages) ? group.pages.map(humanizeArchPageEntry) : []
|
|
19
|
+
}))
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function sanitizePortfolioSections(sections) {
|
|
23
|
+
return sections.map(
|
|
24
|
+
(section) => section?.sectionType === "portfolioSiteArchitecture" ? { ...section, data: sanitizeSiteArchitectureData(section.data) } : section
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
exports.sanitizePortfolioSections = sanitizePortfolioSections;
|
|
29
|
+
exports.sanitizeSiteArchitectureData = sanitizeSiteArchitectureData;
|
|
30
|
+
//# sourceMappingURL=chunk-633UMQQH.cjs.map
|
|
31
|
+
//# sourceMappingURL=chunk-633UMQQH.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/portfolio/sanitize.ts"],"names":[],"mappings":";;;AAuBO,SAAS,sBAAsB,IAAA,EAAwC;AAC5E,EAAA,MAAM,KAAA,GAAA,CAAS,IAAA,EAAM,KAAA,IAAS,EAAA,EAAI,IAAA,EAAK;AACvC,EAAA,IAAI,KAAA,IAAS,CAAC,KAAA,CAAM,UAAA,CAAW,GAAG,CAAA,EAAG,OAAO,EAAE,KAAA,EAAM;AACpD,EAAA,MAAM,MAAA,GAAS,KAAA,IAAA,CAAU,IAAA,EAAM,IAAA,IAAQ,IAAI,IAAA,EAAK;AAChD,EAAA,MAAM,WAAA,GAAc,MAAA,CAAO,KAAA,CAAM,MAAM,EAAE,CAAC,CAAA,CAAE,KAAA,CAAM,GAAG,CAAA,CAAE,MAAA,CAAO,OAAO,CAAA,CAAE,KAAI,IAAK,EAAA;AAChF,EAAA,MAAM,QAAQ,WAAA,CACX,OAAA,CAAQ,yBAAyB,EAAE,CAAA,CACnC,QAAQ,QAAA,EAAU,GAAG,CAAA,CACrB,IAAA,GACA,OAAA,CAAQ,OAAA,EAAS,CAAC,CAAA,KAAM,CAAA,CAAE,aAAa,CAAA;AAC1C,EAAA,OAAO,EAAE,KAAA,EAAO,KAAA,IAAS,MAAA,EAAO;AAClC;AAGO,SAAS,6BAA6B,IAAA,EAAgB;AAC3D,EAAA,IAAI,CAAC,QAAQ,CAAC,KAAA,CAAM,QAAQ,IAAA,CAAK,MAAM,GAAG,OAAO,IAAA;AACjD,EAAA,OAAO;AAAA,IACL,GAAG,IAAA;AAAA,IACH,MAAA,EAAQ,IAAA,CAAK,MAAA,CAAO,GAAA,CAAI,CAAC,KAAA,MAAgB;AAAA,MACvC,GAAG,KAAA;AAAA,MACH,KAAA,EAAO,KAAA,CAAM,OAAA,CAAQ,KAAA,CAAM,KAAK,CAAA,GAAI,KAAA,CAAM,KAAA,CAAM,GAAA,CAAI,qBAAqB,CAAA,GAAI;AAAC,KAChF,CAAE;AAAA,GACJ;AACF;AAGO,SAAS,0BACd,QAAA,EACK;AACL,EAAA,OAAO,QAAA,CAAS,GAAA;AAAA,IAAI,CAAC,OAAA,KACnB,OAAA,EAAS,WAAA,KAAgB,2BAAA,GACrB,EAAE,GAAG,OAAA,EAAS,IAAA,EAAM,4BAAA,CAA6B,OAAA,CAAQ,IAAI,GAAE,GAC/D;AAAA,GACN;AACF","file":"chunk-633UMQQH.cjs","sourcesContent":["/**\n * Portfolio section sanitization — single source of truth.\n *\n * Site-architecture entries arrive from generation with raw CLIENT-site paths\n * (often title === path, e.g. \"/wheels/amp-tires\"). Those strings must never\n * cross a server→client props boundary: Googlebot extracts URL-like strings\n * from the RSC flight payload and crawls them against the AGENCY site's\n * origin, flooding Search Console with 404s (seen on upforge.io, June 2026 —\n * ~100 404'd client paths, all traced to /work/* case-study payloads).\n *\n * The enforcement point is `getPortfolioItem` (portfolio/server.ts): tsup's\n * \"add 'use client' to chunks with hooks\" post-processing turns PortfolioPage\n * into a client component in the published build, so sanitizing at render\n * time is too late — the full `item` prop is already serialized. The\n * PortfolioPage call remains as defense-in-depth for a future true-RSC build.\n */\n\ninterface ArchPageEntry {\n path?: string;\n title?: string;\n}\n\n/** Convert a raw path/title entry into a display-only { title } label. */\nexport function humanizeArchPageEntry(page: ArchPageEntry): { title: string } {\n const title = (page?.title || '').trim();\n if (title && !title.startsWith('/')) return { title };\n const source = title || (page?.path || '').trim();\n const lastSegment = source.split(/[?#]/)[0].split('/').filter(Boolean).pop() || '';\n const label = lastSegment\n .replace(/\\.(html?|php|aspx?)$/i, '')\n .replace(/[-_]+/g, ' ')\n .trim()\n .replace(/\\b\\w/g, (c) => c.toUpperCase());\n return { title: label || 'Home' };\n}\n\n/** Strip raw page paths from a portfolioSiteArchitecture data payload. */\nexport function sanitizeSiteArchitectureData(data: any): any {\n if (!data || !Array.isArray(data.groups)) return data;\n return {\n ...data,\n groups: data.groups.map((group: any) => ({\n ...group,\n pages: Array.isArray(group.pages) ? group.pages.map(humanizeArchPageEntry) : [],\n })),\n };\n}\n\n/** Sanitize every section that can carry raw client-site paths. */\nexport function sanitizePortfolioSections<T extends { sectionType?: string; data?: any }>(\n sections: T[],\n): T[] {\n return sections.map((section) =>\n section?.sectionType === 'portfolioSiteArchitecture'\n ? { ...section, data: sanitizeSiteArchitectureData(section.data) }\n : section,\n );\n}\n"]}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var chunkXONXEFJY_cjs = require('./chunk-XONXEFJY.cjs');
|
|
4
|
+
var chunk633UMQQH_cjs = require('./chunk-633UMQQH.cjs');
|
|
4
5
|
var chunkKEOHORIH_cjs = require('./chunk-KEOHORIH.cjs');
|
|
5
6
|
var chunkIKBK7HYX_cjs = require('./chunk-IKBK7HYX.cjs');
|
|
6
7
|
var dynamic = require('next/dynamic');
|
|
@@ -501,24 +502,6 @@ var SpeedComparisonSection = dynamic__default.default(() => import('./SpeedCompa
|
|
|
501
502
|
var DesignSystemSection = dynamic__default.default(() => import('./DesignSystemSection-KXIQXITF.cjs'), { ssr: true });
|
|
502
503
|
var DetailsSection = dynamic__default.default(() => import('./DetailsSection-TTUZAPZZ.cjs'), { ssr: true });
|
|
503
504
|
var CTASection = dynamic__default.default(() => import('./CTASection-4JKLXEUF.cjs'), { ssr: true });
|
|
504
|
-
function humanizeArchPageEntry(page) {
|
|
505
|
-
const title = (page?.title || "").trim();
|
|
506
|
-
if (title && !title.startsWith("/")) return { title };
|
|
507
|
-
const source = title || (page?.path || "").trim();
|
|
508
|
-
const lastSegment = source.split(/[?#]/)[0].split("/").filter(Boolean).pop() || "";
|
|
509
|
-
const label = lastSegment.replace(/\.(html?|php|aspx?)$/i, "").replace(/[-_]+/g, " ").trim().replace(/\b\w/g, (c) => c.toUpperCase());
|
|
510
|
-
return { title: label || "Home" };
|
|
511
|
-
}
|
|
512
|
-
function sanitizeSiteArchitectureData(data) {
|
|
513
|
-
if (!data || !Array.isArray(data.groups)) return data;
|
|
514
|
-
return {
|
|
515
|
-
...data,
|
|
516
|
-
groups: data.groups.map((group) => ({
|
|
517
|
-
...group,
|
|
518
|
-
pages: Array.isArray(group.pages) ? group.pages.map(humanizeArchPageEntry) : []
|
|
519
|
-
}))
|
|
520
|
-
};
|
|
521
|
-
}
|
|
522
505
|
function renderSection(section, item, index) {
|
|
523
506
|
const key = `${section.sectionType}-${index}`;
|
|
524
507
|
switch (section.sectionType) {
|
|
@@ -563,7 +546,7 @@ function renderSection(section, item, index) {
|
|
|
563
546
|
case "portfolioSpeedComparison":
|
|
564
547
|
return /* @__PURE__ */ jsxRuntime.jsx(SpeedComparisonSection, { data: section.data }, key);
|
|
565
548
|
case "portfolioSiteArchitecture":
|
|
566
|
-
return /* @__PURE__ */ jsxRuntime.jsx(SiteArchitectureSection, { data: sanitizeSiteArchitectureData(section.data) }, key);
|
|
549
|
+
return /* @__PURE__ */ jsxRuntime.jsx(SiteArchitectureSection, { data: chunk633UMQQH_cjs.sanitizeSiteArchitectureData(section.data) }, key);
|
|
567
550
|
case "portfolioDesignSystem":
|
|
568
551
|
return /* @__PURE__ */ jsxRuntime.jsx(DesignSystemSection, { data: section.data }, key);
|
|
569
552
|
case "portfolioDetails":
|
|
@@ -633,5 +616,5 @@ var PORTFOLIO_SECTION_TYPES = [
|
|
|
633
616
|
|
|
634
617
|
exports.PORTFOLIO_SECTION_TYPES = PORTFOLIO_SECTION_TYPES;
|
|
635
618
|
exports.PortfolioPage = PortfolioPage;
|
|
636
|
-
//# sourceMappingURL=chunk-
|
|
637
|
-
//# sourceMappingURL=chunk-
|
|
619
|
+
//# sourceMappingURL=chunk-AVCH4LMG.cjs.map
|
|
620
|
+
//# sourceMappingURL=chunk-AVCH4LMG.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/portfolio/components/primitives/SplitHeadline.tsx","../src/portfolio/components/primitives/DeviceTrifolio.tsx","../src/portfolio/components/sections/HeroSection.tsx","../src/portfolio/components/PortfolioPage.tsx","../src/types.ts"],"names":["useRef","useMemo","jsxs","jsx","useEffect","gsap","ScrollTrigger","useState","useCallback","ScrollReveal","GlassCard","AnimatedCounter","dynamic","sanitizeSiteArchitectureData"],"mappings":"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","file":"chunk-AVCH4LMG.cjs","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={{\n overflowX: 'clip',\n overflowY: 'hidden',\n overflowWrap: 'break-word',\n wordBreak: 'break-word',\n ...style,\n }}\n >\n {splitContent}\n </Tag>\n );\n}\n","'use client';\n\nimport React, { useRef, useEffect, useState, useCallback } from 'react';\nimport gsap from 'gsap';\nimport { ScrollTrigger } from 'gsap/ScrollTrigger';\n\ninterface DeviceTrifolioProps {\n url?: string;\n screenshots?: {\n desktop?: string;\n tablet?: string;\n mobile?: string;\n };\n className?: string;\n disableAnimation?: boolean;\n}\n\n// ─── Hooks ───────────────────────────────────────────────────────────────────\n\nfunction useScaledIframe(viewportWidth: number) {\n const containerRef = useRef<HTMLDivElement>(null);\n const [scale, setScale] = useState(1);\n\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const updateScale = () => {\n const containerWidth = container.offsetWidth;\n setScale(containerWidth / viewportWidth);\n };\n\n updateScale();\n const resizeObserver = new ResizeObserver(updateScale);\n resizeObserver.observe(container);\n return () => resizeObserver.disconnect();\n }, [viewportWidth]);\n\n return { containerRef, scale };\n}\n\nfunction useScrollSafeIframe() {\n const [isLoaded, setIsLoaded] = useState(false);\n const [iframeFailed, setIframeFailed] = useState(false);\n const iframeRef = useRef<HTMLIFrameElement>(null);\n const loadTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const handleLoad = useCallback(() => {\n const iframe = iframeRef.current;\n if (!iframe) return;\n\n // Wait for iframe to paint, then verify it rendered real content\n loadTimerRef.current = setTimeout(() => {\n let isCrossOrigin = false;\n\n try {\n // Try accessing contentDocument — cross-origin sites throw here\n const doc = iframe.contentDocument;\n if (!doc || !doc.body) {\n setIframeFailed(true);\n return;\n }\n\n // Same-origin access succeeded. This means either:\n // 1. A same-origin site loaded (e.g., our own site in dev)\n // 2. Browser rendered its own error page (X-Frame-Options block, connection refused, etc.)\n //\n // Browser error pages are same-origin and have short/generic content.\n // Real sites have <title>, many DOM nodes, scripts, styles, etc.\n const title = doc.title || '';\n const bodyText = doc.body?.innerText?.trim() || '';\n const numElements = doc.querySelectorAll('*').length;\n\n // Heuristic: real sites have a meaningful title AND many DOM elements\n // Browser error pages have very few elements and generic/empty titles\n if (numElements > 20 && title.length > 0 && bodyText.length > 50) {\n setIsLoaded(true);\n } else {\n setIframeFailed(true);\n }\n } catch {\n isCrossOrigin = true;\n }\n\n if (isCrossOrigin) {\n // Cross-origin: the real remote site loaded (can't be a browser error page\n // since those are always same-origin). This is the success case.\n setIsLoaded(true);\n }\n }, 2000);\n }, []);\n\n const handleError = useCallback(() => {\n setIframeFailed(true);\n if (loadTimerRef.current) clearTimeout(loadTimerRef.current);\n }, []);\n\n // Cleanup timer on unmount\n useEffect(() => {\n return () => {\n if (loadTimerRef.current) clearTimeout(loadTimerRef.current);\n };\n }, []);\n\n const wrapperStyle: React.CSSProperties = {\n position: 'absolute',\n left: 0,\n top: 0,\n zIndex: 1,\n overflow: 'hidden',\n width: '100%',\n height: '100%',\n };\n\n const iframeStyle: React.CSSProperties = {};\n\n return { iframeRef, handleLoad, handleError, wrapperStyle, iframeStyle, isLoaded, iframeFailed };\n}\n\n// ─── Device Frames ───────────────────────────────────────────────────────────\n\n/**\n * Hook that detects if an iframe successfully loaded real content.\n * Cross-origin = success (real site loaded). Same-origin with minimal DOM = blocked/error.\n */\nfunction useIframeLoadDetection() {\n const [loaded, setLoaded] = useState(false);\n const iframeRef = useRef<HTMLIFrameElement>(null);\n\n const onLoad = useCallback(() => {\n const iframe = iframeRef.current;\n if (!iframe) return;\n\n // Delay check to let the iframe paint\n setTimeout(() => {\n try {\n // If we can access contentDocument, it's same-origin.\n // Real sites are cross-origin and throw here → catch block → success.\n const doc = iframe.contentDocument;\n if (!doc?.body) { return; } // blocked\n const numElements = doc.querySelectorAll('*').length;\n // Browser error pages have very few elements. Real same-origin sites have many.\n if (numElements > 20) setLoaded(true);\n } catch {\n // Cross-origin error = the real remote site loaded successfully\n setLoaded(true);\n }\n }, 1500);\n }, []);\n\n return { iframeRef, loaded, onLoad };\n}\n\nfunction MacBookFrame({ url, screenshot }: { url: string; screenshot?: string }) {\n const viewportWidth = 1440;\n const viewportHeight = 900;\n const { containerRef, scale } = useScaledIframe(viewportWidth);\n const { iframeRef, loaded: iframeLoaded, onLoad: onIframeLoad } = useIframeLoadDetection();\n\n return (\n <div className=\"w-full\">\n <div\n className=\"relative rounded-t-[10px] p-[4px] pb-0\"\n style={{\n background: 'linear-gradient(135deg, #e8e8ed 0%, #d1d1d6 100%)',\n boxShadow: '0 -4px 30px rgba(0,0,0,0.3), inset 0 1px 0 rgba(255,255,255,0.9)',\n }}\n >\n <div\n className=\"relative rounded-t-[7px] px-[8px] pt-[8px] pb-0\"\n style={{ background: '#0a0a0a' }}\n >\n <div\n className=\"absolute top-0 left-1/2 -translate-x-1/2 w-[70px] h-[18px] flex items-center justify-center z-10\"\n style={{ background: '#0a0a0a', borderRadius: '0 0 10px 10px' }}\n >\n <div className=\"w-[5px] h-[5px] rounded-full\" style={{ background: '#2a3a2a', boxShadow: '0 0 0 1px #1a1a1a' }} />\n </div>\n\n <div ref={containerRef} className=\"relative overflow-hidden rounded-t-[3px]\" style={{ aspectRatio: `${viewportWidth}/${viewportHeight}`, background: '#0a0a0a' }}>\n {screenshot && <img src={screenshot} alt=\"Desktop preview\" style={{ width: '100%', height: '100%', objectFit: 'cover', objectPosition: 'top center', display: 'block', position: 'absolute' as const, inset: 0, zIndex: 2, opacity: iframeLoaded ? 0 : 1, transition: 'opacity 0.8s ease', pointerEvents: 'none' as const }} />}\n {url && <iframe src={url} title=\"Desktop\" loading=\"eager\" scrolling=\"no\" tabIndex={-1} sandbox=\"allow-scripts allow-same-origin\" ref={iframeRef} onLoad={onIframeLoad} className=\"border-0 pointer-events-none\" style={{ position: 'absolute', top: 0, left: 0, width: `${viewportWidth}px`, height: `${viewportHeight}px`, transformOrigin: 'top left', transform: `scale(${scale})` }} />}\n </div>\n </div>\n </div>\n\n {/* Black chin */}\n <div className=\"h-[8px]\" style={{ background: '#0a0a0a', boxShadow: '0 1px 4px rgba(0,0,0,0.3)' }} />\n\n {/* Silver base */}\n <div\n className=\"relative h-[12px] rounded-b-[10px]\"\n style={{\n background: 'linear-gradient(to bottom, #e0e0e5, #c8c8cd 20%, #b0b0b5 80%, #a0a0a5)',\n boxShadow: '0 4px 15px rgba(0,0,0,0.3)',\n }}\n >\n <div\n className=\"absolute top-0 left-1/2 -translate-x-1/2 w-[25%] h-[4px] rounded-b-[4px]\"\n style={{ background: 'linear-gradient(to bottom, #909095, #a8a8ad)' }}\n />\n </div>\n </div>\n );\n}\n\nfunction IPadFrame({ url, screenshot }: { url: string; screenshot?: string }) {\n const viewportWidth = 768;\n const viewportHeight = 1024;\n const { containerRef, scale } = useScaledIframe(viewportWidth);\n const { iframeRef, loaded: iframeLoaded, onLoad: onIframeLoad } = useIframeLoadDetection();\n\n return (\n <div className=\"relative p-[1.5%]\" style={{ background: 'linear-gradient(135deg, #e8e8ed 0%, #d1d1d6 100%)', borderRadius: '8% / 6%', boxShadow: '0 30px 60px -15px rgba(0,0,0,0.4), 0 0 0 1px rgba(255,255,255,0.9) inset, inset 0 1px 0 rgba(255,255,255,0.8)' }}>\n <div className=\"relative p-[2%]\" style={{ background: '#0a0a0a', borderRadius: '6% / 4.5%' }}>\n <div className=\"absolute top-[1.2%] left-1/2 -translate-x-1/2 w-[1.8%] aspect-square rounded-full z-10\" style={{ background: '#1a1a1f' }} />\n <div ref={containerRef} className=\"relative overflow-hidden\" style={{ borderRadius: '2%', aspectRatio: `${viewportWidth}/${viewportHeight}`, background: '#0a0a0a' }}>\n {screenshot && <img src={screenshot} alt=\"Tablet preview\" style={{ width: '100%', height: '100%', objectFit: 'cover', objectPosition: 'top center', display: 'block', position: 'absolute' as const, inset: 0, zIndex: 2, opacity: iframeLoaded ? 0 : 1, transition: 'opacity 0.8s ease', pointerEvents: 'none' as const }} />}\n {url && <iframe src={url} title=\"Tablet\" loading=\"eager\" scrolling=\"no\" tabIndex={-1} sandbox=\"allow-scripts allow-same-origin\" ref={iframeRef} onLoad={onIframeLoad} className=\"border-0 pointer-events-none\" style={{ position: 'absolute', top: 0, left: 0, width: `${viewportWidth}px`, height: `${viewportHeight}px`, transformOrigin: 'top left', transform: `scale(${scale})` }} />}\n </div>\n </div>\n </div>\n );\n}\n\nfunction IPhoneFrame({ url, screenshot }: { url: string; screenshot?: string }) {\n const viewportWidth = 393;\n const viewportHeight = 852;\n const { containerRef, scale } = useScaledIframe(viewportWidth);\n const { iframeRef, loaded: iframeLoaded, onLoad: onIframeLoad } = useIframeLoadDetection();\n\n return (\n <div\n className=\"relative p-[2.5%]\"\n style={{\n background: 'linear-gradient(135deg, #e8e8ed 0%, #d1d1d6 100%)',\n borderRadius: '18% / 8%',\n boxShadow:\n '0 30px 60px -15px rgba(0,0,0,0.4), 0 0 0 1px rgba(255,255,255,0.9) inset, inset 0 1px 0 rgba(255,255,255,0.8)',\n }}\n >\n {/* Side buttons */}\n <div className=\"absolute left-0 top-[18%] w-[1%] h-[6%] rounded-l-sm\" style={{ background: '#c8c8cd' }} />\n <div className=\"absolute left-0 top-[26%] w-[1%] h-[10%] rounded-l-sm\" style={{ background: '#c8c8cd' }} />\n <div className=\"absolute left-0 top-[38%] w-[1%] h-[10%] rounded-l-sm\" style={{ background: '#c8c8cd' }} />\n <div className=\"absolute right-0 top-[22%] w-[1%] h-[12%] rounded-r-sm\" style={{ background: '#c8c8cd' }} />\n\n {/* Black bezel */}\n <div\n className=\"relative p-[3%]\"\n style={{ background: '#0a0a0a', borderRadius: '14% / 6%' }}\n >\n {/* Dynamic Island */}\n <div\n className=\"absolute top-[2.5%] left-1/2 -translate-x-1/2 w-[28%] h-[3.5%] rounded-full z-10\"\n style={{ background: '#1a1a1a' }}\n />\n\n <div ref={containerRef} className=\"relative overflow-hidden\" style={{ borderRadius: '8% / 4%', aspectRatio: `${viewportWidth}/${viewportHeight}`, background: '#0a0a0a' }}>\n {screenshot && <img src={screenshot} alt=\"Mobile preview\" style={{ width: '100%', height: '100%', objectFit: 'cover', objectPosition: 'top center', display: 'block', position: 'absolute' as const, inset: 0, zIndex: 2, opacity: iframeLoaded ? 0 : 1, transition: 'opacity 0.8s ease', pointerEvents: 'none' as const }} />}\n {url && <iframe src={url} title=\"Mobile\" loading=\"eager\" scrolling=\"no\" tabIndex={-1} sandbox=\"allow-scripts allow-same-origin\" ref={iframeRef} onLoad={onIframeLoad} className=\"border-0 pointer-events-none\" style={{ position: 'absolute', top: 0, left: 0, width: `${viewportWidth}px`, height: `${viewportHeight}px`, transformOrigin: 'top left', transform: `scale(${scale})` }} />}\n </div>\n\n {/* Home indicator */}\n <div\n className=\"absolute bottom-[2%] left-1/2 -translate-x-1/2 w-[34%] h-[1%] rounded-full\"\n style={{ background: 'rgba(255,255,255,0.25)' }}\n />\n </div>\n </div>\n );\n}\n\n// ─── Main Component ──────────────────────────────────────────────────────────\n\nexport default function DeviceTrifolio({\n url,\n screenshots,\n className = '',\n disableAnimation = false,\n}: DeviceTrifolioProps) {\n const trifolioRef = useRef<HTMLDivElement>(null);\n\n // Pass URL directly — iframes render immediately\n const frameUrl = url || '';\n\n // GSAP ScrollTrigger animation for device entrance\n useEffect(() => {\n gsap.registerPlugin(ScrollTrigger);\n\n const container = trifolioRef.current;\n if (!container || disableAnimation) return;\n\n const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n if (prefersReducedMotion) return;\n\n const macbook = container.querySelector<HTMLElement>('[data-device=\"macbook\"]');\n const ipad = container.querySelector<HTMLElement>('[data-device=\"ipad\"]');\n const iphone = container.querySelector<HTMLElement>('[data-device=\"iphone\"]');\n\n if (!macbook || !ipad || !iphone) return;\n\n const ctx = gsap.context(() => {\n gsap.set(container, { opacity: 0 });\n gsap.set(macbook, { opacity: 0, y: 40 });\n gsap.set(ipad, { opacity: 0, x: -60, y: 30 });\n gsap.set(iphone, { opacity: 0, x: 60, y: 30 });\n\n const tl = gsap.timeline({\n scrollTrigger: {\n trigger: container,\n start: 'top bottom',\n toggleActions: 'play none none none',\n },\n });\n\n tl.to(container, { opacity: 1, duration: 0.3 })\n .to(macbook, { opacity: 1, y: 0, duration: 0.7, ease: 'power2.out' }, 0.1)\n .to(ipad, { opacity: 1, x: 0, y: 0, duration: 0.6, ease: 'power2.out' }, 0.25)\n .to(iphone, { opacity: 1, x: 0, y: 0, duration: 0.6, ease: 'power2.out' }, 0.4);\n }, container);\n\n return () => ctx.revert();\n }, [disableAnimation]);\n\n return (\n <div\n ref={trifolioRef}\n data-screenshot-target=\"device-trifolio\"\n className={`relative w-full min-w-0 max-w-full overflow-x-clip ${className}`}\n style={{ aspectRatio: '11/5', minHeight: '200px' }}\n >\n {/* MacBook Pro - Center Back */}\n <div\n data-device=\"macbook\"\n className=\"absolute left-1/2 -translate-x-1/2 bottom-0 w-[65%] z-10\"\n >\n <MacBookFrame url={frameUrl} screenshot={screenshots?.desktop} />\n </div>\n\n {/* iPad - Front Left */}\n <div\n data-device=\"ipad\"\n className=\"absolute left-[8%] bottom-0 w-[28%] z-20\"\n >\n <IPadFrame url={frameUrl} screenshot={screenshots?.tablet} />\n </div>\n\n {/* iPhone - Front Right */}\n <div\n data-device=\"iphone\"\n className=\"absolute right-[10%] bottom-0 w-[14%] z-30\"\n >\n <IPhoneFrame url={frameUrl} screenshot={screenshots?.mobile} />\n </div>\n </div>\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';\nimport DeviceTrifolio from '../primitives/DeviceTrifolio';\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\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 handles its own entrance animation via DeviceTrifolio component\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 min-w-0 px-4 sm:px-6 py-24 lg:py-32\">\n <div className=\"grid grid-cols-1 min-w-0 lg:grid-cols-2 gap-12 lg:gap-16 items-start\">\n {/* Text column — min-w-0 so grid track can shrink below content intrinsic width (mobile) */}\n <div className=\"flex min-w-0 max-w-full 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?.replace(/[-_]/g, ' ').replace(/\\b\\w/g, (c: string) => c.toUpperCase())}\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 </div>\n\n {/* Right column — trifolio + KPIs */}\n <div className=\"flex min-w-0 max-w-full flex-col gap-10 lg:pt-8\">\n {/* Device trifolio — realistic device frames */}\n {(url || shots?.desktop || shots?.tablet || shots?.mobile) && (\n <div ref={deviceRef} className=\"min-w-0 max-w-full\">\n <DeviceTrifolio\n screenshots={{\n desktop: shots?.desktop,\n tablet: shots?.tablet,\n mobile: shots?.mobile,\n }}\n url={url}\n />\n </div>\n )}\n\n {/* KPI cards — below trifolio in right column */}\n {data.kpis.length > 0 && (\n <ScrollReveal y={30} delay={0.6} stagger={0.1}>\n <div className=\"grid grid-cols-2 gap-3\">\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 </GlassCard>\n ))}\n </div>\n </ScrollReveal>\n )}\n </div>\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 PerformanceSection = dynamic(() => import('./sections/PerformanceSection'), { ssr: true });\nconst SiteArchitectureSection = dynamic(() => import('./sections/SiteArchitectureSection'), { ssr: true });\nconst SpeedComparisonSection = dynamic(() => import('./sections/SpeedComparisonSection'), { ssr: true });\nconst DesignSystemSection = dynamic(() => import('./sections/DesignSystemSection'), { 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\n// Defense-in-depth only — the enforcing sanitize pass runs in\n// getPortfolioItem (portfolio/server.ts), the last genuinely-server-side hop.\n// See src/portfolio/sanitize.ts for the single source of truth and the story.\nimport { sanitizeSiteArchitectureData } from '../sanitize';\n\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 'portfolioPerformance':\n return <PerformanceSection key={key} data={section.data as any} />;\n case 'portfolioSpeedComparison':\n return <SpeedComparisonSection key={key} data={section.data as any} />;\n case 'portfolioSiteArchitecture':\n return <SiteArchitectureSection key={key} data={sanitizeSiteArchitectureData(section.data) as any} />;\n case 'portfolioDesignSystem':\n return <DesignSystemSection key={key} data={section.data as any} />;\n case 'portfolioDetails':\n return <DetailsSection key={key} data={section.data as PortfolioDetailsData} />;\n case 'portfolioCTA':\n return <CTASection key={key} data={section.data as PortfolioCTAData} />;\n case 'portfolioSeo':\n // SEO section is metadata-only; nothing to render visually.\n return null;\n default:\n return null;\n }\n}\n\n// Canonical display order — ensures consistent rendering regardless of API order\nconst SECTION_ORDER: Record<string, number> = {\n portfolioHero: 0,\n portfolioChallenges: 1,\n portfolioStrategy: 2,\n portfolioResults: 3,\n portfolioPerformance: 4,\n portfolioSpeedComparison: 5,\n portfolioSiteArchitecture: 6,\n portfolioTechStack: 7,\n portfolioDesignSystem: 8,\n portfolioServices: 9,\n portfolioBeforeAfter: 10,\n portfolioTestimonial: 11,\n portfolioMetricsTimeline: 12,\n portfolioConversionFunnel: 13,\n portfolioDetails: 11,\n portfolioSeo: 12,\n portfolioCta: 13,\n};\n\nexport default function PortfolioPage({ item }: PortfolioPageProps) {\n const sortedSections = [...item.sections].sort(\n (a, b) => (SECTION_ORDER[a.sectionType] ?? 99) - (SECTION_ORDER[b.sectionType] ?? 99)\n );\n\n return (\n <article className=\"flex w-full min-w-0 max-w-full flex-col overflow-x-clip\">\n {sortedSections.map((section, index) => {\n const rendered = renderSection(section, item, index);\n if (!rendered) return null;\n return (\n <div 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 'portfolioPerformance',\n 'portfolioSpeedComparison',\n 'portfolioSiteArchitecture',\n 'portfolioDesignSystem',\n 'portfolioDetails',\n 'portfolioSeo',\n 'portfolioCTA',\n] as const;\n\nexport type PortfolioSectionType = (typeof PORTFOLIO_SECTION_TYPES)[number];\n\nexport type PortfolioSectionData =\n | PortfolioHeroData\n | PortfolioChallengesData\n | PortfolioStrategyData\n | PortfolioResultsData\n | PortfolioTechStackData\n | PortfolioServicesData\n | PortfolioTestimonialData\n | PortfolioGalleryData\n | PortfolioVideoData\n | PortfolioTeamData\n | PortfolioFeatureSpotlightData\n | PortfolioBeforeAfterData\n | PortfolioMetricsTimelineData\n | PortfolioConversionFunnelData\n | PortfolioDetailsData\n | PortfolioSeoData\n | PortfolioCTAData;\n\nexport interface PortfolioSectionDataMap {\n portfolioHero: PortfolioHeroData;\n portfolioChallenges: PortfolioChallengesData;\n portfolioStrategy: PortfolioStrategyData;\n portfolioResults: PortfolioResultsData;\n portfolioTechStack: PortfolioTechStackData;\n portfolioServices: PortfolioServicesData;\n portfolioTestimonial: PortfolioTestimonialData;\n portfolioGallery: PortfolioGalleryData;\n portfolioVideo: PortfolioVideoData;\n portfolioTeam: PortfolioTeamData;\n portfolioFeatureSpotlight: PortfolioFeatureSpotlightData;\n portfolioBeforeAfter: PortfolioBeforeAfterData;\n portfolioMetricsTimeline: PortfolioMetricsTimelineData;\n portfolioConversionFunnel: PortfolioConversionFunnelData;\n portfolioDetails: PortfolioDetailsData;\n portfolioSeo: PortfolioSeoData;\n portfolioCTA: PortfolioCTAData;\n}\n\nexport interface PortfolioSection<T extends PortfolioSectionType = PortfolioSectionType> {\n sectionType: T;\n displayName: string;\n data: T extends keyof PortfolioSectionDataMap ? PortfolioSectionDataMap[T] : PortfolioSectionData;\n}\n\n// ---------------------------------------------------------------------------\n// Portfolio Item (mirrors public API response)\n// ---------------------------------------------------------------------------\n\nexport interface PortfolioItem {\n id: string;\n slug: string;\n title: string;\n subtitle: string;\n category: string;\n services: string[];\n description: string;\n hero_image: string;\n hero_image_alt: string;\n live_url: string | null;\n kpis: PortfolioKPI[];\n details: PortfolioDetailsData | null;\n seo: PortfolioSeoData | null;\n featured: boolean;\n order?: number;\n published_at: string | null;\n /** Sonor project ID (present for items generated from a managed project) */\n project_id: string | null;\n hero_screenshots: {\n desktop?: string;\n tablet?: string;\n mobile?: string;\n } | null;\n baseline_metrics: MetricsSnapshot | null;\n current_metrics: MetricsSnapshot | null;\n metrics_last_refreshed_at: string | null;\n}\n\nexport interface PortfolioItemFull extends PortfolioItem {\n sections: PortfolioSection[];\n metricsDelta: MetricsDelta[];\n}\n\n// ---------------------------------------------------------------------------\n// API Responses\n// ---------------------------------------------------------------------------\n\nexport interface PortfolioListResponse {\n items: PortfolioItem[];\n total: number;\n limit: number;\n offset: number;\n}\n\nexport interface PortfolioConfigResponse {\n brand: BrandConfig;\n orgName: string;\n orgSlug: string;\n}\n"]}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { AnimatedCounter } from './chunk-2Y4O3LWM.js';
|
|
3
|
+
import { sanitizeSiteArchitectureData } from './chunk-VVTK4OL6.js';
|
|
3
4
|
import { GlassCard } from './chunk-YB4B3OMC.js';
|
|
4
5
|
import { ScrollReveal } from './chunk-7CFFAKDM.js';
|
|
5
6
|
import dynamic from 'next/dynamic';
|
|
@@ -495,24 +496,6 @@ var SpeedComparisonSection = dynamic(() => import('./SpeedComparisonSection-Y3K7
|
|
|
495
496
|
var DesignSystemSection = dynamic(() => import('./DesignSystemSection-2R5BRBGO.js'), { ssr: true });
|
|
496
497
|
var DetailsSection = dynamic(() => import('./DetailsSection-A6PZQUQL.js'), { ssr: true });
|
|
497
498
|
var CTASection = dynamic(() => import('./CTASection-BJA72XIL.js'), { ssr: true });
|
|
498
|
-
function humanizeArchPageEntry(page) {
|
|
499
|
-
const title = (page?.title || "").trim();
|
|
500
|
-
if (title && !title.startsWith("/")) return { title };
|
|
501
|
-
const source = title || (page?.path || "").trim();
|
|
502
|
-
const lastSegment = source.split(/[?#]/)[0].split("/").filter(Boolean).pop() || "";
|
|
503
|
-
const label = lastSegment.replace(/\.(html?|php|aspx?)$/i, "").replace(/[-_]+/g, " ").trim().replace(/\b\w/g, (c) => c.toUpperCase());
|
|
504
|
-
return { title: label || "Home" };
|
|
505
|
-
}
|
|
506
|
-
function sanitizeSiteArchitectureData(data) {
|
|
507
|
-
if (!data || !Array.isArray(data.groups)) return data;
|
|
508
|
-
return {
|
|
509
|
-
...data,
|
|
510
|
-
groups: data.groups.map((group) => ({
|
|
511
|
-
...group,
|
|
512
|
-
pages: Array.isArray(group.pages) ? group.pages.map(humanizeArchPageEntry) : []
|
|
513
|
-
}))
|
|
514
|
-
};
|
|
515
|
-
}
|
|
516
499
|
function renderSection(section, item, index) {
|
|
517
500
|
const key = `${section.sectionType}-${index}`;
|
|
518
501
|
switch (section.sectionType) {
|
|
@@ -626,5 +609,5 @@ var PORTFOLIO_SECTION_TYPES = [
|
|
|
626
609
|
];
|
|
627
610
|
|
|
628
611
|
export { PORTFOLIO_SECTION_TYPES, PortfolioPage };
|
|
629
|
-
//# sourceMappingURL=chunk-
|
|
630
|
-
//# sourceMappingURL=chunk-
|
|
612
|
+
//# sourceMappingURL=chunk-AYBUHRFS.js.map
|
|
613
|
+
//# sourceMappingURL=chunk-AYBUHRFS.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/portfolio/components/primitives/SplitHeadline.tsx","../src/portfolio/components/primitives/DeviceTrifolio.tsx","../src/portfolio/components/sections/HeroSection.tsx","../src/portfolio/components/PortfolioPage.tsx","../src/types.ts"],"names":["gsap","useRef","useEffect","jsxs","jsx","ScrollTrigger"],"mappings":"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","file":"chunk-AYBUHRFS.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={{\n overflowX: 'clip',\n overflowY: 'hidden',\n overflowWrap: 'break-word',\n wordBreak: 'break-word',\n ...style,\n }}\n >\n {splitContent}\n </Tag>\n );\n}\n","'use client';\n\nimport React, { useRef, useEffect, useState, useCallback } from 'react';\nimport gsap from 'gsap';\nimport { ScrollTrigger } from 'gsap/ScrollTrigger';\n\ninterface DeviceTrifolioProps {\n url?: string;\n screenshots?: {\n desktop?: string;\n tablet?: string;\n mobile?: string;\n };\n className?: string;\n disableAnimation?: boolean;\n}\n\n// ─── Hooks ───────────────────────────────────────────────────────────────────\n\nfunction useScaledIframe(viewportWidth: number) {\n const containerRef = useRef<HTMLDivElement>(null);\n const [scale, setScale] = useState(1);\n\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const updateScale = () => {\n const containerWidth = container.offsetWidth;\n setScale(containerWidth / viewportWidth);\n };\n\n updateScale();\n const resizeObserver = new ResizeObserver(updateScale);\n resizeObserver.observe(container);\n return () => resizeObserver.disconnect();\n }, [viewportWidth]);\n\n return { containerRef, scale };\n}\n\nfunction useScrollSafeIframe() {\n const [isLoaded, setIsLoaded] = useState(false);\n const [iframeFailed, setIframeFailed] = useState(false);\n const iframeRef = useRef<HTMLIFrameElement>(null);\n const loadTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const handleLoad = useCallback(() => {\n const iframe = iframeRef.current;\n if (!iframe) return;\n\n // Wait for iframe to paint, then verify it rendered real content\n loadTimerRef.current = setTimeout(() => {\n let isCrossOrigin = false;\n\n try {\n // Try accessing contentDocument — cross-origin sites throw here\n const doc = iframe.contentDocument;\n if (!doc || !doc.body) {\n setIframeFailed(true);\n return;\n }\n\n // Same-origin access succeeded. This means either:\n // 1. A same-origin site loaded (e.g., our own site in dev)\n // 2. Browser rendered its own error page (X-Frame-Options block, connection refused, etc.)\n //\n // Browser error pages are same-origin and have short/generic content.\n // Real sites have <title>, many DOM nodes, scripts, styles, etc.\n const title = doc.title || '';\n const bodyText = doc.body?.innerText?.trim() || '';\n const numElements = doc.querySelectorAll('*').length;\n\n // Heuristic: real sites have a meaningful title AND many DOM elements\n // Browser error pages have very few elements and generic/empty titles\n if (numElements > 20 && title.length > 0 && bodyText.length > 50) {\n setIsLoaded(true);\n } else {\n setIframeFailed(true);\n }\n } catch {\n isCrossOrigin = true;\n }\n\n if (isCrossOrigin) {\n // Cross-origin: the real remote site loaded (can't be a browser error page\n // since those are always same-origin). This is the success case.\n setIsLoaded(true);\n }\n }, 2000);\n }, []);\n\n const handleError = useCallback(() => {\n setIframeFailed(true);\n if (loadTimerRef.current) clearTimeout(loadTimerRef.current);\n }, []);\n\n // Cleanup timer on unmount\n useEffect(() => {\n return () => {\n if (loadTimerRef.current) clearTimeout(loadTimerRef.current);\n };\n }, []);\n\n const wrapperStyle: React.CSSProperties = {\n position: 'absolute',\n left: 0,\n top: 0,\n zIndex: 1,\n overflow: 'hidden',\n width: '100%',\n height: '100%',\n };\n\n const iframeStyle: React.CSSProperties = {};\n\n return { iframeRef, handleLoad, handleError, wrapperStyle, iframeStyle, isLoaded, iframeFailed };\n}\n\n// ─── Device Frames ───────────────────────────────────────────────────────────\n\n/**\n * Hook that detects if an iframe successfully loaded real content.\n * Cross-origin = success (real site loaded). Same-origin with minimal DOM = blocked/error.\n */\nfunction useIframeLoadDetection() {\n const [loaded, setLoaded] = useState(false);\n const iframeRef = useRef<HTMLIFrameElement>(null);\n\n const onLoad = useCallback(() => {\n const iframe = iframeRef.current;\n if (!iframe) return;\n\n // Delay check to let the iframe paint\n setTimeout(() => {\n try {\n // If we can access contentDocument, it's same-origin.\n // Real sites are cross-origin and throw here → catch block → success.\n const doc = iframe.contentDocument;\n if (!doc?.body) { return; } // blocked\n const numElements = doc.querySelectorAll('*').length;\n // Browser error pages have very few elements. Real same-origin sites have many.\n if (numElements > 20) setLoaded(true);\n } catch {\n // Cross-origin error = the real remote site loaded successfully\n setLoaded(true);\n }\n }, 1500);\n }, []);\n\n return { iframeRef, loaded, onLoad };\n}\n\nfunction MacBookFrame({ url, screenshot }: { url: string; screenshot?: string }) {\n const viewportWidth = 1440;\n const viewportHeight = 900;\n const { containerRef, scale } = useScaledIframe(viewportWidth);\n const { iframeRef, loaded: iframeLoaded, onLoad: onIframeLoad } = useIframeLoadDetection();\n\n return (\n <div className=\"w-full\">\n <div\n className=\"relative rounded-t-[10px] p-[4px] pb-0\"\n style={{\n background: 'linear-gradient(135deg, #e8e8ed 0%, #d1d1d6 100%)',\n boxShadow: '0 -4px 30px rgba(0,0,0,0.3), inset 0 1px 0 rgba(255,255,255,0.9)',\n }}\n >\n <div\n className=\"relative rounded-t-[7px] px-[8px] pt-[8px] pb-0\"\n style={{ background: '#0a0a0a' }}\n >\n <div\n className=\"absolute top-0 left-1/2 -translate-x-1/2 w-[70px] h-[18px] flex items-center justify-center z-10\"\n style={{ background: '#0a0a0a', borderRadius: '0 0 10px 10px' }}\n >\n <div className=\"w-[5px] h-[5px] rounded-full\" style={{ background: '#2a3a2a', boxShadow: '0 0 0 1px #1a1a1a' }} />\n </div>\n\n <div ref={containerRef} className=\"relative overflow-hidden rounded-t-[3px]\" style={{ aspectRatio: `${viewportWidth}/${viewportHeight}`, background: '#0a0a0a' }}>\n {screenshot && <img src={screenshot} alt=\"Desktop preview\" style={{ width: '100%', height: '100%', objectFit: 'cover', objectPosition: 'top center', display: 'block', position: 'absolute' as const, inset: 0, zIndex: 2, opacity: iframeLoaded ? 0 : 1, transition: 'opacity 0.8s ease', pointerEvents: 'none' as const }} />}\n {url && <iframe src={url} title=\"Desktop\" loading=\"eager\" scrolling=\"no\" tabIndex={-1} sandbox=\"allow-scripts allow-same-origin\" ref={iframeRef} onLoad={onIframeLoad} className=\"border-0 pointer-events-none\" style={{ position: 'absolute', top: 0, left: 0, width: `${viewportWidth}px`, height: `${viewportHeight}px`, transformOrigin: 'top left', transform: `scale(${scale})` }} />}\n </div>\n </div>\n </div>\n\n {/* Black chin */}\n <div className=\"h-[8px]\" style={{ background: '#0a0a0a', boxShadow: '0 1px 4px rgba(0,0,0,0.3)' }} />\n\n {/* Silver base */}\n <div\n className=\"relative h-[12px] rounded-b-[10px]\"\n style={{\n background: 'linear-gradient(to bottom, #e0e0e5, #c8c8cd 20%, #b0b0b5 80%, #a0a0a5)',\n boxShadow: '0 4px 15px rgba(0,0,0,0.3)',\n }}\n >\n <div\n className=\"absolute top-0 left-1/2 -translate-x-1/2 w-[25%] h-[4px] rounded-b-[4px]\"\n style={{ background: 'linear-gradient(to bottom, #909095, #a8a8ad)' }}\n />\n </div>\n </div>\n );\n}\n\nfunction IPadFrame({ url, screenshot }: { url: string; screenshot?: string }) {\n const viewportWidth = 768;\n const viewportHeight = 1024;\n const { containerRef, scale } = useScaledIframe(viewportWidth);\n const { iframeRef, loaded: iframeLoaded, onLoad: onIframeLoad } = useIframeLoadDetection();\n\n return (\n <div className=\"relative p-[1.5%]\" style={{ background: 'linear-gradient(135deg, #e8e8ed 0%, #d1d1d6 100%)', borderRadius: '8% / 6%', boxShadow: '0 30px 60px -15px rgba(0,0,0,0.4), 0 0 0 1px rgba(255,255,255,0.9) inset, inset 0 1px 0 rgba(255,255,255,0.8)' }}>\n <div className=\"relative p-[2%]\" style={{ background: '#0a0a0a', borderRadius: '6% / 4.5%' }}>\n <div className=\"absolute top-[1.2%] left-1/2 -translate-x-1/2 w-[1.8%] aspect-square rounded-full z-10\" style={{ background: '#1a1a1f' }} />\n <div ref={containerRef} className=\"relative overflow-hidden\" style={{ borderRadius: '2%', aspectRatio: `${viewportWidth}/${viewportHeight}`, background: '#0a0a0a' }}>\n {screenshot && <img src={screenshot} alt=\"Tablet preview\" style={{ width: '100%', height: '100%', objectFit: 'cover', objectPosition: 'top center', display: 'block', position: 'absolute' as const, inset: 0, zIndex: 2, opacity: iframeLoaded ? 0 : 1, transition: 'opacity 0.8s ease', pointerEvents: 'none' as const }} />}\n {url && <iframe src={url} title=\"Tablet\" loading=\"eager\" scrolling=\"no\" tabIndex={-1} sandbox=\"allow-scripts allow-same-origin\" ref={iframeRef} onLoad={onIframeLoad} className=\"border-0 pointer-events-none\" style={{ position: 'absolute', top: 0, left: 0, width: `${viewportWidth}px`, height: `${viewportHeight}px`, transformOrigin: 'top left', transform: `scale(${scale})` }} />}\n </div>\n </div>\n </div>\n );\n}\n\nfunction IPhoneFrame({ url, screenshot }: { url: string; screenshot?: string }) {\n const viewportWidth = 393;\n const viewportHeight = 852;\n const { containerRef, scale } = useScaledIframe(viewportWidth);\n const { iframeRef, loaded: iframeLoaded, onLoad: onIframeLoad } = useIframeLoadDetection();\n\n return (\n <div\n className=\"relative p-[2.5%]\"\n style={{\n background: 'linear-gradient(135deg, #e8e8ed 0%, #d1d1d6 100%)',\n borderRadius: '18% / 8%',\n boxShadow:\n '0 30px 60px -15px rgba(0,0,0,0.4), 0 0 0 1px rgba(255,255,255,0.9) inset, inset 0 1px 0 rgba(255,255,255,0.8)',\n }}\n >\n {/* Side buttons */}\n <div className=\"absolute left-0 top-[18%] w-[1%] h-[6%] rounded-l-sm\" style={{ background: '#c8c8cd' }} />\n <div className=\"absolute left-0 top-[26%] w-[1%] h-[10%] rounded-l-sm\" style={{ background: '#c8c8cd' }} />\n <div className=\"absolute left-0 top-[38%] w-[1%] h-[10%] rounded-l-sm\" style={{ background: '#c8c8cd' }} />\n <div className=\"absolute right-0 top-[22%] w-[1%] h-[12%] rounded-r-sm\" style={{ background: '#c8c8cd' }} />\n\n {/* Black bezel */}\n <div\n className=\"relative p-[3%]\"\n style={{ background: '#0a0a0a', borderRadius: '14% / 6%' }}\n >\n {/* Dynamic Island */}\n <div\n className=\"absolute top-[2.5%] left-1/2 -translate-x-1/2 w-[28%] h-[3.5%] rounded-full z-10\"\n style={{ background: '#1a1a1a' }}\n />\n\n <div ref={containerRef} className=\"relative overflow-hidden\" style={{ borderRadius: '8% / 4%', aspectRatio: `${viewportWidth}/${viewportHeight}`, background: '#0a0a0a' }}>\n {screenshot && <img src={screenshot} alt=\"Mobile preview\" style={{ width: '100%', height: '100%', objectFit: 'cover', objectPosition: 'top center', display: 'block', position: 'absolute' as const, inset: 0, zIndex: 2, opacity: iframeLoaded ? 0 : 1, transition: 'opacity 0.8s ease', pointerEvents: 'none' as const }} />}\n {url && <iframe src={url} title=\"Mobile\" loading=\"eager\" scrolling=\"no\" tabIndex={-1} sandbox=\"allow-scripts allow-same-origin\" ref={iframeRef} onLoad={onIframeLoad} className=\"border-0 pointer-events-none\" style={{ position: 'absolute', top: 0, left: 0, width: `${viewportWidth}px`, height: `${viewportHeight}px`, transformOrigin: 'top left', transform: `scale(${scale})` }} />}\n </div>\n\n {/* Home indicator */}\n <div\n className=\"absolute bottom-[2%] left-1/2 -translate-x-1/2 w-[34%] h-[1%] rounded-full\"\n style={{ background: 'rgba(255,255,255,0.25)' }}\n />\n </div>\n </div>\n );\n}\n\n// ─── Main Component ──────────────────────────────────────────────────────────\n\nexport default function DeviceTrifolio({\n url,\n screenshots,\n className = '',\n disableAnimation = false,\n}: DeviceTrifolioProps) {\n const trifolioRef = useRef<HTMLDivElement>(null);\n\n // Pass URL directly — iframes render immediately\n const frameUrl = url || '';\n\n // GSAP ScrollTrigger animation for device entrance\n useEffect(() => {\n gsap.registerPlugin(ScrollTrigger);\n\n const container = trifolioRef.current;\n if (!container || disableAnimation) return;\n\n const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n if (prefersReducedMotion) return;\n\n const macbook = container.querySelector<HTMLElement>('[data-device=\"macbook\"]');\n const ipad = container.querySelector<HTMLElement>('[data-device=\"ipad\"]');\n const iphone = container.querySelector<HTMLElement>('[data-device=\"iphone\"]');\n\n if (!macbook || !ipad || !iphone) return;\n\n const ctx = gsap.context(() => {\n gsap.set(container, { opacity: 0 });\n gsap.set(macbook, { opacity: 0, y: 40 });\n gsap.set(ipad, { opacity: 0, x: -60, y: 30 });\n gsap.set(iphone, { opacity: 0, x: 60, y: 30 });\n\n const tl = gsap.timeline({\n scrollTrigger: {\n trigger: container,\n start: 'top bottom',\n toggleActions: 'play none none none',\n },\n });\n\n tl.to(container, { opacity: 1, duration: 0.3 })\n .to(macbook, { opacity: 1, y: 0, duration: 0.7, ease: 'power2.out' }, 0.1)\n .to(ipad, { opacity: 1, x: 0, y: 0, duration: 0.6, ease: 'power2.out' }, 0.25)\n .to(iphone, { opacity: 1, x: 0, y: 0, duration: 0.6, ease: 'power2.out' }, 0.4);\n }, container);\n\n return () => ctx.revert();\n }, [disableAnimation]);\n\n return (\n <div\n ref={trifolioRef}\n data-screenshot-target=\"device-trifolio\"\n className={`relative w-full min-w-0 max-w-full overflow-x-clip ${className}`}\n style={{ aspectRatio: '11/5', minHeight: '200px' }}\n >\n {/* MacBook Pro - Center Back */}\n <div\n data-device=\"macbook\"\n className=\"absolute left-1/2 -translate-x-1/2 bottom-0 w-[65%] z-10\"\n >\n <MacBookFrame url={frameUrl} screenshot={screenshots?.desktop} />\n </div>\n\n {/* iPad - Front Left */}\n <div\n data-device=\"ipad\"\n className=\"absolute left-[8%] bottom-0 w-[28%] z-20\"\n >\n <IPadFrame url={frameUrl} screenshot={screenshots?.tablet} />\n </div>\n\n {/* iPhone - Front Right */}\n <div\n data-device=\"iphone\"\n className=\"absolute right-[10%] bottom-0 w-[14%] z-30\"\n >\n <IPhoneFrame url={frameUrl} screenshot={screenshots?.mobile} />\n </div>\n </div>\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';\nimport DeviceTrifolio from '../primitives/DeviceTrifolio';\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\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 handles its own entrance animation via DeviceTrifolio component\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 min-w-0 px-4 sm:px-6 py-24 lg:py-32\">\n <div className=\"grid grid-cols-1 min-w-0 lg:grid-cols-2 gap-12 lg:gap-16 items-start\">\n {/* Text column — min-w-0 so grid track can shrink below content intrinsic width (mobile) */}\n <div className=\"flex min-w-0 max-w-full 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?.replace(/[-_]/g, ' ').replace(/\\b\\w/g, (c: string) => c.toUpperCase())}\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 </div>\n\n {/* Right column — trifolio + KPIs */}\n <div className=\"flex min-w-0 max-w-full flex-col gap-10 lg:pt-8\">\n {/* Device trifolio — realistic device frames */}\n {(url || shots?.desktop || shots?.tablet || shots?.mobile) && (\n <div ref={deviceRef} className=\"min-w-0 max-w-full\">\n <DeviceTrifolio\n screenshots={{\n desktop: shots?.desktop,\n tablet: shots?.tablet,\n mobile: shots?.mobile,\n }}\n url={url}\n />\n </div>\n )}\n\n {/* KPI cards — below trifolio in right column */}\n {data.kpis.length > 0 && (\n <ScrollReveal y={30} delay={0.6} stagger={0.1}>\n <div className=\"grid grid-cols-2 gap-3\">\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 </GlassCard>\n ))}\n </div>\n </ScrollReveal>\n )}\n </div>\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 PerformanceSection = dynamic(() => import('./sections/PerformanceSection'), { ssr: true });\nconst SiteArchitectureSection = dynamic(() => import('./sections/SiteArchitectureSection'), { ssr: true });\nconst SpeedComparisonSection = dynamic(() => import('./sections/SpeedComparisonSection'), { ssr: true });\nconst DesignSystemSection = dynamic(() => import('./sections/DesignSystemSection'), { 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\n// Defense-in-depth only — the enforcing sanitize pass runs in\n// getPortfolioItem (portfolio/server.ts), the last genuinely-server-side hop.\n// See src/portfolio/sanitize.ts for the single source of truth and the story.\nimport { sanitizeSiteArchitectureData } from '../sanitize';\n\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 'portfolioPerformance':\n return <PerformanceSection key={key} data={section.data as any} />;\n case 'portfolioSpeedComparison':\n return <SpeedComparisonSection key={key} data={section.data as any} />;\n case 'portfolioSiteArchitecture':\n return <SiteArchitectureSection key={key} data={sanitizeSiteArchitectureData(section.data) as any} />;\n case 'portfolioDesignSystem':\n return <DesignSystemSection key={key} data={section.data as any} />;\n case 'portfolioDetails':\n return <DetailsSection key={key} data={section.data as PortfolioDetailsData} />;\n case 'portfolioCTA':\n return <CTASection key={key} data={section.data as PortfolioCTAData} />;\n case 'portfolioSeo':\n // SEO section is metadata-only; nothing to render visually.\n return null;\n default:\n return null;\n }\n}\n\n// Canonical display order — ensures consistent rendering regardless of API order\nconst SECTION_ORDER: Record<string, number> = {\n portfolioHero: 0,\n portfolioChallenges: 1,\n portfolioStrategy: 2,\n portfolioResults: 3,\n portfolioPerformance: 4,\n portfolioSpeedComparison: 5,\n portfolioSiteArchitecture: 6,\n portfolioTechStack: 7,\n portfolioDesignSystem: 8,\n portfolioServices: 9,\n portfolioBeforeAfter: 10,\n portfolioTestimonial: 11,\n portfolioMetricsTimeline: 12,\n portfolioConversionFunnel: 13,\n portfolioDetails: 11,\n portfolioSeo: 12,\n portfolioCta: 13,\n};\n\nexport default function PortfolioPage({ item }: PortfolioPageProps) {\n const sortedSections = [...item.sections].sort(\n (a, b) => (SECTION_ORDER[a.sectionType] ?? 99) - (SECTION_ORDER[b.sectionType] ?? 99)\n );\n\n return (\n <article className=\"flex w-full min-w-0 max-w-full flex-col overflow-x-clip\">\n {sortedSections.map((section, index) => {\n const rendered = renderSection(section, item, index);\n if (!rendered) return null;\n return (\n <div 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 'portfolioPerformance',\n 'portfolioSpeedComparison',\n 'portfolioSiteArchitecture',\n 'portfolioDesignSystem',\n 'portfolioDetails',\n 'portfolioSeo',\n 'portfolioCTA',\n] as const;\n\nexport type PortfolioSectionType = (typeof PORTFOLIO_SECTION_TYPES)[number];\n\nexport type PortfolioSectionData =\n | PortfolioHeroData\n | PortfolioChallengesData\n | PortfolioStrategyData\n | PortfolioResultsData\n | PortfolioTechStackData\n | PortfolioServicesData\n | PortfolioTestimonialData\n | PortfolioGalleryData\n | PortfolioVideoData\n | PortfolioTeamData\n | PortfolioFeatureSpotlightData\n | PortfolioBeforeAfterData\n | PortfolioMetricsTimelineData\n | PortfolioConversionFunnelData\n | PortfolioDetailsData\n | PortfolioSeoData\n | PortfolioCTAData;\n\nexport interface PortfolioSectionDataMap {\n portfolioHero: PortfolioHeroData;\n portfolioChallenges: PortfolioChallengesData;\n portfolioStrategy: PortfolioStrategyData;\n portfolioResults: PortfolioResultsData;\n portfolioTechStack: PortfolioTechStackData;\n portfolioServices: PortfolioServicesData;\n portfolioTestimonial: PortfolioTestimonialData;\n portfolioGallery: PortfolioGalleryData;\n portfolioVideo: PortfolioVideoData;\n portfolioTeam: PortfolioTeamData;\n portfolioFeatureSpotlight: PortfolioFeatureSpotlightData;\n portfolioBeforeAfter: PortfolioBeforeAfterData;\n portfolioMetricsTimeline: PortfolioMetricsTimelineData;\n portfolioConversionFunnel: PortfolioConversionFunnelData;\n portfolioDetails: PortfolioDetailsData;\n portfolioSeo: PortfolioSeoData;\n portfolioCTA: PortfolioCTAData;\n}\n\nexport interface PortfolioSection<T extends PortfolioSectionType = PortfolioSectionType> {\n sectionType: T;\n displayName: string;\n data: T extends keyof PortfolioSectionDataMap ? PortfolioSectionDataMap[T] : PortfolioSectionData;\n}\n\n// ---------------------------------------------------------------------------\n// Portfolio Item (mirrors public API response)\n// ---------------------------------------------------------------------------\n\nexport interface PortfolioItem {\n id: string;\n slug: string;\n title: string;\n subtitle: string;\n category: string;\n services: string[];\n description: string;\n hero_image: string;\n hero_image_alt: string;\n live_url: string | null;\n kpis: PortfolioKPI[];\n details: PortfolioDetailsData | null;\n seo: PortfolioSeoData | null;\n featured: boolean;\n order?: number;\n published_at: string | null;\n /** Sonor project ID (present for items generated from a managed project) */\n project_id: string | null;\n hero_screenshots: {\n desktop?: string;\n tablet?: string;\n mobile?: string;\n } | null;\n baseline_metrics: MetricsSnapshot | null;\n current_metrics: MetricsSnapshot | null;\n metrics_last_refreshed_at: string | null;\n}\n\nexport interface PortfolioItemFull extends PortfolioItem {\n sections: PortfolioSection[];\n metricsDelta: MetricsDelta[];\n}\n\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,28 @@
|
|
|
1
|
+
// src/portfolio/sanitize.ts
|
|
2
|
+
function humanizeArchPageEntry(page) {
|
|
3
|
+
const title = (page?.title || "").trim();
|
|
4
|
+
if (title && !title.startsWith("/")) return { title };
|
|
5
|
+
const source = title || (page?.path || "").trim();
|
|
6
|
+
const lastSegment = source.split(/[?#]/)[0].split("/").filter(Boolean).pop() || "";
|
|
7
|
+
const label = lastSegment.replace(/\.(html?|php|aspx?)$/i, "").replace(/[-_]+/g, " ").trim().replace(/\b\w/g, (c) => c.toUpperCase());
|
|
8
|
+
return { title: label || "Home" };
|
|
9
|
+
}
|
|
10
|
+
function sanitizeSiteArchitectureData(data) {
|
|
11
|
+
if (!data || !Array.isArray(data.groups)) return data;
|
|
12
|
+
return {
|
|
13
|
+
...data,
|
|
14
|
+
groups: data.groups.map((group) => ({
|
|
15
|
+
...group,
|
|
16
|
+
pages: Array.isArray(group.pages) ? group.pages.map(humanizeArchPageEntry) : []
|
|
17
|
+
}))
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function sanitizePortfolioSections(sections) {
|
|
21
|
+
return sections.map(
|
|
22
|
+
(section) => section?.sectionType === "portfolioSiteArchitecture" ? { ...section, data: sanitizeSiteArchitectureData(section.data) } : section
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export { sanitizePortfolioSections, sanitizeSiteArchitectureData };
|
|
27
|
+
//# sourceMappingURL=chunk-VVTK4OL6.js.map
|
|
28
|
+
//# sourceMappingURL=chunk-VVTK4OL6.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/portfolio/sanitize.ts"],"names":[],"mappings":";AAuBO,SAAS,sBAAsB,IAAA,EAAwC;AAC5E,EAAA,MAAM,KAAA,GAAA,CAAS,IAAA,EAAM,KAAA,IAAS,EAAA,EAAI,IAAA,EAAK;AACvC,EAAA,IAAI,KAAA,IAAS,CAAC,KAAA,CAAM,UAAA,CAAW,GAAG,CAAA,EAAG,OAAO,EAAE,KAAA,EAAM;AACpD,EAAA,MAAM,MAAA,GAAS,KAAA,IAAA,CAAU,IAAA,EAAM,IAAA,IAAQ,IAAI,IAAA,EAAK;AAChD,EAAA,MAAM,WAAA,GAAc,MAAA,CAAO,KAAA,CAAM,MAAM,EAAE,CAAC,CAAA,CAAE,KAAA,CAAM,GAAG,CAAA,CAAE,MAAA,CAAO,OAAO,CAAA,CAAE,KAAI,IAAK,EAAA;AAChF,EAAA,MAAM,QAAQ,WAAA,CACX,OAAA,CAAQ,yBAAyB,EAAE,CAAA,CACnC,QAAQ,QAAA,EAAU,GAAG,CAAA,CACrB,IAAA,GACA,OAAA,CAAQ,OAAA,EAAS,CAAC,CAAA,KAAM,CAAA,CAAE,aAAa,CAAA;AAC1C,EAAA,OAAO,EAAE,KAAA,EAAO,KAAA,IAAS,MAAA,EAAO;AAClC;AAGO,SAAS,6BAA6B,IAAA,EAAgB;AAC3D,EAAA,IAAI,CAAC,QAAQ,CAAC,KAAA,CAAM,QAAQ,IAAA,CAAK,MAAM,GAAG,OAAO,IAAA;AACjD,EAAA,OAAO;AAAA,IACL,GAAG,IAAA;AAAA,IACH,MAAA,EAAQ,IAAA,CAAK,MAAA,CAAO,GAAA,CAAI,CAAC,KAAA,MAAgB;AAAA,MACvC,GAAG,KAAA;AAAA,MACH,KAAA,EAAO,KAAA,CAAM,OAAA,CAAQ,KAAA,CAAM,KAAK,CAAA,GAAI,KAAA,CAAM,KAAA,CAAM,GAAA,CAAI,qBAAqB,CAAA,GAAI;AAAC,KAChF,CAAE;AAAA,GACJ;AACF;AAGO,SAAS,0BACd,QAAA,EACK;AACL,EAAA,OAAO,QAAA,CAAS,GAAA;AAAA,IAAI,CAAC,OAAA,KACnB,OAAA,EAAS,WAAA,KAAgB,2BAAA,GACrB,EAAE,GAAG,OAAA,EAAS,IAAA,EAAM,4BAAA,CAA6B,OAAA,CAAQ,IAAI,GAAE,GAC/D;AAAA,GACN;AACF","file":"chunk-VVTK4OL6.js","sourcesContent":["/**\n * Portfolio section sanitization — single source of truth.\n *\n * Site-architecture entries arrive from generation with raw CLIENT-site paths\n * (often title === path, e.g. \"/wheels/amp-tires\"). Those strings must never\n * cross a server→client props boundary: Googlebot extracts URL-like strings\n * from the RSC flight payload and crawls them against the AGENCY site's\n * origin, flooding Search Console with 404s (seen on upforge.io, June 2026 —\n * ~100 404'd client paths, all traced to /work/* case-study payloads).\n *\n * The enforcement point is `getPortfolioItem` (portfolio/server.ts): tsup's\n * \"add 'use client' to chunks with hooks\" post-processing turns PortfolioPage\n * into a client component in the published build, so sanitizing at render\n * time is too late — the full `item` prop is already serialized. The\n * PortfolioPage call remains as defense-in-depth for a future true-RSC build.\n */\n\ninterface ArchPageEntry {\n path?: string;\n title?: string;\n}\n\n/** Convert a raw path/title entry into a display-only { title } label. */\nexport function humanizeArchPageEntry(page: ArchPageEntry): { title: string } {\n const title = (page?.title || '').trim();\n if (title && !title.startsWith('/')) return { title };\n const source = title || (page?.path || '').trim();\n const lastSegment = source.split(/[?#]/)[0].split('/').filter(Boolean).pop() || '';\n const label = lastSegment\n .replace(/\\.(html?|php|aspx?)$/i, '')\n .replace(/[-_]+/g, ' ')\n .trim()\n .replace(/\\b\\w/g, (c) => c.toUpperCase());\n return { title: label || 'Home' };\n}\n\n/** Strip raw page paths from a portfolioSiteArchitecture data payload. */\nexport function sanitizeSiteArchitectureData(data: any): any {\n if (!data || !Array.isArray(data.groups)) return data;\n return {\n ...data,\n groups: data.groups.map((group: any) => ({\n ...group,\n pages: Array.isArray(group.pages) ? group.pages.map(humanizeArchPageEntry) : [],\n })),\n };\n}\n\n/** Sanitize every section that can carry raw client-site paths. */\nexport function sanitizePortfolioSections<T extends { sectionType?: string; data?: any }>(\n sections: T[],\n): T[] {\n return sections.map((section) =>\n section?.sectionType === 'portfolioSiteArchitecture'\n ? { ...section, data: sanitizeSiteArchitectureData(section.data) }\n : section,\n );\n}\n"]}
|
package/dist/index.cjs
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkAVCH4LMG_cjs = require('./chunk-AVCH4LMG.cjs');
|
|
4
4
|
require('./chunk-XONXEFJY.cjs');
|
|
5
|
+
require('./chunk-633UMQQH.cjs');
|
|
5
6
|
var chunkXQNJED46_cjs = require('./chunk-XQNJED46.cjs');
|
|
6
7
|
var chunkOA5ZM4OA_cjs = require('./chunk-OA5ZM4OA.cjs');
|
|
7
8
|
var chunkWNR4KNG4_cjs = require('./chunk-WNR4KNG4.cjs');
|
|
@@ -200,11 +201,11 @@ function Icon({
|
|
|
200
201
|
|
|
201
202
|
Object.defineProperty(exports, "PORTFOLIO_SECTION_TYPES", {
|
|
202
203
|
enumerable: true,
|
|
203
|
-
get: function () { return
|
|
204
|
+
get: function () { return chunkAVCH4LMG_cjs.PORTFOLIO_SECTION_TYPES; }
|
|
204
205
|
});
|
|
205
206
|
Object.defineProperty(exports, "PortfolioPage", {
|
|
206
207
|
enumerable: true,
|
|
207
|
-
get: function () { return
|
|
208
|
+
get: function () { return chunkAVCH4LMG_cjs.PortfolioPage; }
|
|
208
209
|
});
|
|
209
210
|
Object.defineProperty(exports, "getRegisteredSectionTypes", {
|
|
210
211
|
enumerable: true,
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/shared/icons.ts"],"names":["React"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * @sonordev/agency-site-kit — Icon registry\n *\n * Lightweight icon system that maps string names to SVG path data.\n * No external icon library dependency — keeps the bundle small.\n * Icons are rendered as inline SVGs.\n */\n\nimport React from 'react';\n\ninterface IconDefinition {\n viewBox: string;\n paths: string[];\n /** Optional fill-rule for complex paths */\n fillRule?: 'evenodd' | 'nonzero';\n}\n\n/**\n * Icon registry — 24x24 viewBox icons (matching Lucide/Heroicons conventions).\n * All paths use stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\".\n */\nconst ICON_REGISTRY: Record<string, IconDefinition> = {\n 'arrow-up': {\n viewBox: '0 0 24 24',\n paths: ['M12 19V5', 'M5 12l7-7 7 7'],\n },\n 'arrow-down': {\n viewBox: '0 0 24 24',\n paths: ['M12 5v14', 'M19 12l-7 7-7-7'],\n },\n 'chart-bar': {\n viewBox: '0 0 24 24',\n paths: ['M12 20V10', 'M18 20V4', 'M6 20v-4'],\n },\n check: {\n viewBox: '0 0 24 24',\n paths: ['M20 6L9 17l-5-5'],\n },\n clock: {\n viewBox: '0 0 24 24',\n paths: ['M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20z', 'M12 6v6l4 2'],\n },\n code: {\n viewBox: '0 0 24 24',\n paths: ['M16 18l6-6-6-6', 'M8 6l-6 6 6 6'],\n },\n globe: {\n viewBox: '0 0 24 24',\n paths: [\n 'M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20z',\n 'M2 12h20',\n 'M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10A15.3 15.3 0 0 1 12 2z',\n ],\n },\n layout: {\n viewBox: '0 0 24 24',\n paths: [\n 'M19 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2z',\n 'M3 9h18',\n 'M9 21V9',\n ],\n },\n lightning: {\n viewBox: '0 0 24 24',\n paths: ['M13 2L3 14h9l-1 8 10-12h-9l1-8'],\n },\n link: {\n viewBox: '0 0 24 24',\n paths: [\n 'M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71',\n 'M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71',\n ],\n },\n lock: {\n viewBox: '0 0 24 24',\n paths: [\n 'M19 11H5a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7a2 2 0 0 0-2-2z',\n 'M7 11V7a5 5 0 0 1 10 0v4',\n ],\n },\n mail: {\n viewBox: '0 0 24 24',\n paths: [\n 'M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z',\n 'M22 6l-10 7L2 6',\n ],\n },\n monitor: {\n viewBox: '0 0 24 24',\n paths: [\n 'M20 3H4a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2z',\n 'M8 21h8',\n 'M12 17v4',\n ],\n },\n palette: {\n viewBox: '0 0 24 24',\n paths: [\n 'M12 2C6.49 2 2 6.49 2 12s4.49 10 10 10a2 2 0 0 0 2-2v-.09a1.65 1.65 0 0 1 .92-1.48A1.65 1.65 0 0 1 16.5 19H19a2 2 0 0 0 2-2 10 10 0 0 0-9-15z',\n 'M6.5 11.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z',\n 'M10 7.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z',\n 'M14 7.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z',\n 'M17.5 11.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z',\n ],\n },\n rocket: {\n viewBox: '0 0 24 24',\n paths: [\n 'M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09z',\n 'M12 15l-3-3a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 0 1-4 2z',\n 'M9 12H4s.55-3.03 2-4c1.62-1.08 5 0 5 0',\n 'M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5',\n ],\n },\n search: {\n viewBox: '0 0 24 24',\n paths: [\n 'M11 19a8 8 0 1 0 0-16 8 8 0 0 0 0 16z',\n 'M21 21l-4.35-4.35',\n ],\n },\n settings: {\n viewBox: '0 0 24 24',\n paths: [\n 'M12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6z',\n 'M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09a1.65 1.65 0 0 0-1.08-1.51 1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09a1.65 1.65 0 0 0 1.51-1.08 1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9c.26.604.852.997 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1.08z',\n ],\n },\n shield: {\n viewBox: '0 0 24 24',\n paths: ['M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z'],\n },\n star: {\n viewBox: '0 0 24 24',\n paths: [\n 'M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z',\n ],\n },\n target: {\n viewBox: '0 0 24 24',\n paths: [\n 'M12 22a10 10 0 1 0 0-20 10 10 0 0 0 0 20z',\n 'M12 18a6 6 0 1 0 0-12 6 6 0 0 0 0 12z',\n 'M12 14a2 2 0 1 0 0-4 2 2 0 0 0 0 4z',\n ],\n },\n 'trending-up': {\n viewBox: '0 0 24 24',\n paths: ['M23 6l-9.5 9.5-5-5L1 18', 'M17 6h6v6'],\n },\n users: {\n viewBox: '0 0 24 24',\n paths: [\n 'M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2',\n 'M9 11a4 4 0 1 0 0-8 4 4 0 0 0 0 8z',\n 'M23 21v-2a4 4 0 0 0-3-3.87',\n 'M16 3.13a4 4 0 0 1 0 7.75',\n ],\n },\n zap: {\n viewBox: '0 0 24 24',\n paths: ['M13 2L3 14h9l-1 8 10-12h-9l1-8'],\n },\n};\n\n/**\n * Returns the icon definition (viewBox + paths) for a given icon name.\n * Returns null if the icon is not found.\n */\nexport function getIconDefinition(name: string): IconDefinition | null {\n return ICON_REGISTRY[name] ?? null;\n}\n\n/**\n * Returns just the SVG path strings for a given icon name.\n * Useful when building custom SVG elements.\n */\nexport function getIconPaths(name: string): string[] | null {\n const def = ICON_REGISTRY[name];\n return def ? def.paths : null;\n}\n\n/**\n * Returns a list of all available icon names.\n */\nexport function getAvailableIcons(): string[] {\n return Object.keys(ICON_REGISTRY);\n}\n\n/**\n * Renders an icon as a React SVG element.\n *\n * @param name Icon name from the registry\n * @param size Width/height in pixels (default 24)\n * @param className Optional CSS class name\n * @returns React SVG element, or null if icon not found\n */\nexport function renderIcon(\n name: string,\n size: number = 24,\n className?: string,\n): React.ReactElement | null {\n const def = ICON_REGISTRY[name];\n if (!def) return null;\n\n return React.createElement(\n 'svg',\n {\n xmlns: 'http://www.w3.org/2000/svg',\n width: size,\n height: size,\n viewBox: def.viewBox,\n fill: 'none',\n stroke: 'currentColor',\n strokeWidth: 2,\n strokeLinecap: 'round' as const,\n strokeLinejoin: 'round' as const,\n className,\n 'aria-hidden': 'true',\n },\n ...def.paths.map((d, i) =>\n React.createElement('path', { key: i, d }),\n ),\n );\n}\n\n/**\n * Icon component for use in JSX.\n *\n * Usage:\n * ```tsx\n * <Icon name=\"trending-up\" size={20} className=\"text-green-500\" />\n * ```\n */\nexport function Icon({\n name,\n size = 24,\n className,\n}: {\n name: string;\n size?: number;\n className?: string;\n}): React.ReactElement | null {\n return renderIcon(name, size, className);\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/shared/icons.ts"],"names":["React"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * @sonordev/agency-site-kit — Icon registry\n *\n * Lightweight icon system that maps string names to SVG path data.\n * No external icon library dependency — keeps the bundle small.\n * Icons are rendered as inline SVGs.\n */\n\nimport React from 'react';\n\ninterface IconDefinition {\n viewBox: string;\n paths: string[];\n /** Optional fill-rule for complex paths */\n fillRule?: 'evenodd' | 'nonzero';\n}\n\n/**\n * Icon registry — 24x24 viewBox icons (matching Lucide/Heroicons conventions).\n * All paths use stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\".\n */\nconst ICON_REGISTRY: Record<string, IconDefinition> = {\n 'arrow-up': {\n viewBox: '0 0 24 24',\n paths: ['M12 19V5', 'M5 12l7-7 7 7'],\n },\n 'arrow-down': {\n viewBox: '0 0 24 24',\n paths: ['M12 5v14', 'M19 12l-7 7-7-7'],\n },\n 'chart-bar': {\n viewBox: '0 0 24 24',\n paths: ['M12 20V10', 'M18 20V4', 'M6 20v-4'],\n },\n check: {\n viewBox: '0 0 24 24',\n paths: ['M20 6L9 17l-5-5'],\n },\n clock: {\n viewBox: '0 0 24 24',\n paths: ['M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20z', 'M12 6v6l4 2'],\n },\n code: {\n viewBox: '0 0 24 24',\n paths: ['M16 18l6-6-6-6', 'M8 6l-6 6 6 6'],\n },\n globe: {\n viewBox: '0 0 24 24',\n paths: [\n 'M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20z',\n 'M2 12h20',\n 'M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10A15.3 15.3 0 0 1 12 2z',\n ],\n },\n layout: {\n viewBox: '0 0 24 24',\n paths: [\n 'M19 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2z',\n 'M3 9h18',\n 'M9 21V9',\n ],\n },\n lightning: {\n viewBox: '0 0 24 24',\n paths: ['M13 2L3 14h9l-1 8 10-12h-9l1-8'],\n },\n link: {\n viewBox: '0 0 24 24',\n paths: [\n 'M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71',\n 'M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71',\n ],\n },\n lock: {\n viewBox: '0 0 24 24',\n paths: [\n 'M19 11H5a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7a2 2 0 0 0-2-2z',\n 'M7 11V7a5 5 0 0 1 10 0v4',\n ],\n },\n mail: {\n viewBox: '0 0 24 24',\n paths: [\n 'M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z',\n 'M22 6l-10 7L2 6',\n ],\n },\n monitor: {\n viewBox: '0 0 24 24',\n paths: [\n 'M20 3H4a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2z',\n 'M8 21h8',\n 'M12 17v4',\n ],\n },\n palette: {\n viewBox: '0 0 24 24',\n paths: [\n 'M12 2C6.49 2 2 6.49 2 12s4.49 10 10 10a2 2 0 0 0 2-2v-.09a1.65 1.65 0 0 1 .92-1.48A1.65 1.65 0 0 1 16.5 19H19a2 2 0 0 0 2-2 10 10 0 0 0-9-15z',\n 'M6.5 11.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z',\n 'M10 7.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z',\n 'M14 7.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z',\n 'M17.5 11.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z',\n ],\n },\n rocket: {\n viewBox: '0 0 24 24',\n paths: [\n 'M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09z',\n 'M12 15l-3-3a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 0 1-4 2z',\n 'M9 12H4s.55-3.03 2-4c1.62-1.08 5 0 5 0',\n 'M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5',\n ],\n },\n search: {\n viewBox: '0 0 24 24',\n paths: [\n 'M11 19a8 8 0 1 0 0-16 8 8 0 0 0 0 16z',\n 'M21 21l-4.35-4.35',\n ],\n },\n settings: {\n viewBox: '0 0 24 24',\n paths: [\n 'M12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6z',\n 'M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09a1.65 1.65 0 0 0-1.08-1.51 1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09a1.65 1.65 0 0 0 1.51-1.08 1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9c.26.604.852.997 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1.08z',\n ],\n },\n shield: {\n viewBox: '0 0 24 24',\n paths: ['M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z'],\n },\n star: {\n viewBox: '0 0 24 24',\n paths: [\n 'M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z',\n ],\n },\n target: {\n viewBox: '0 0 24 24',\n paths: [\n 'M12 22a10 10 0 1 0 0-20 10 10 0 0 0 0 20z',\n 'M12 18a6 6 0 1 0 0-12 6 6 0 0 0 0 12z',\n 'M12 14a2 2 0 1 0 0-4 2 2 0 0 0 0 4z',\n ],\n },\n 'trending-up': {\n viewBox: '0 0 24 24',\n paths: ['M23 6l-9.5 9.5-5-5L1 18', 'M17 6h6v6'],\n },\n users: {\n viewBox: '0 0 24 24',\n paths: [\n 'M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2',\n 'M9 11a4 4 0 1 0 0-8 4 4 0 0 0 0 8z',\n 'M23 21v-2a4 4 0 0 0-3-3.87',\n 'M16 3.13a4 4 0 0 1 0 7.75',\n ],\n },\n zap: {\n viewBox: '0 0 24 24',\n paths: ['M13 2L3 14h9l-1 8 10-12h-9l1-8'],\n },\n};\n\n/**\n * Returns the icon definition (viewBox + paths) for a given icon name.\n * Returns null if the icon is not found.\n */\nexport function getIconDefinition(name: string): IconDefinition | null {\n return ICON_REGISTRY[name] ?? null;\n}\n\n/**\n * Returns just the SVG path strings for a given icon name.\n * Useful when building custom SVG elements.\n */\nexport function getIconPaths(name: string): string[] | null {\n const def = ICON_REGISTRY[name];\n return def ? def.paths : null;\n}\n\n/**\n * Returns a list of all available icon names.\n */\nexport function getAvailableIcons(): string[] {\n return Object.keys(ICON_REGISTRY);\n}\n\n/**\n * Renders an icon as a React SVG element.\n *\n * @param name Icon name from the registry\n * @param size Width/height in pixels (default 24)\n * @param className Optional CSS class name\n * @returns React SVG element, or null if icon not found\n */\nexport function renderIcon(\n name: string,\n size: number = 24,\n className?: string,\n): React.ReactElement | null {\n const def = ICON_REGISTRY[name];\n if (!def) return null;\n\n return React.createElement(\n 'svg',\n {\n xmlns: 'http://www.w3.org/2000/svg',\n width: size,\n height: size,\n viewBox: def.viewBox,\n fill: 'none',\n stroke: 'currentColor',\n strokeWidth: 2,\n strokeLinecap: 'round' as const,\n strokeLinejoin: 'round' as const,\n className,\n 'aria-hidden': 'true',\n },\n ...def.paths.map((d, i) =>\n React.createElement('path', { key: i, d }),\n ),\n );\n}\n\n/**\n * Icon component for use in JSX.\n *\n * Usage:\n * ```tsx\n * <Icon name=\"trending-up\" size={20} className=\"text-green-500\" />\n * ```\n */\nexport function Icon({\n name,\n size = 24,\n className,\n}: {\n name: string;\n size?: number;\n className?: string;\n}): React.ReactElement | null {\n return renderIcon(name, size, className);\n}\n"]}
|