@sonordev/agency-site-kit 0.4.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-4IES23GV.js → chunk-2RW4C42Y.js} +16 -4
- package/dist/chunk-2RW4C42Y.js.map +1 -0
- package/dist/{chunk-QGSW43TM.cjs → chunk-2Y2RJB3A.cjs} +12 -12
- package/dist/{chunk-QGSW43TM.cjs.map → chunk-2Y2RJB3A.cjs.map} +1 -1
- package/dist/{chunk-UXUU7FVT.cjs → chunk-6OWFFV6N.cjs} +4 -65
- package/dist/chunk-6OWFFV6N.cjs.map +1 -0
- package/dist/{chunk-NLGUPJZA.js → chunk-7FFZX7C5.js} +4 -4
- package/dist/{chunk-NLGUPJZA.js.map → chunk-7FFZX7C5.js.map} +1 -1
- package/dist/{chunk-7ZHRMWJU.cjs → chunk-A5WCMHAE.cjs} +2 -2
- package/dist/{chunk-7ZHRMWJU.cjs.map → chunk-A5WCMHAE.cjs.map} +1 -1
- package/dist/chunk-FM25LB67.js +50 -0
- package/dist/chunk-FM25LB67.js.map +1 -0
- package/dist/{chunk-KXLROAO5.js → chunk-FT2SNEYP.js} +2 -2
- package/dist/{chunk-KXLROAO5.js.map → chunk-FT2SNEYP.js.map} +1 -1
- package/dist/{chunk-5MEZU3OG.cjs → chunk-OG5RBKQJ.cjs} +15 -3
- package/dist/chunk-OG5RBKQJ.cjs.map +1 -0
- package/dist/chunk-X76U7VLF.cjs +57 -0
- package/dist/chunk-X76U7VLF.cjs.map +1 -0
- package/dist/{chunk-RWQ675N6.js → chunk-X7UCJ45A.js} +4 -61
- package/dist/chunk-X7UCJ45A.js.map +1 -0
- package/dist/format-C-axf13G.d.cts +68 -0
- package/dist/format-poUpLtQ2.d.ts +68 -0
- package/dist/index.cjs +25 -24
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -3
- package/dist/index.d.ts +5 -3
- package/dist/index.js +5 -4
- package/dist/index.js.map +1 -1
- package/dist/layout/index.cjs +2 -2
- package/dist/layout/index.d.cts +12 -2
- package/dist/layout/index.d.ts +12 -2
- package/dist/layout/index.js +1 -1
- package/dist/portfolio/client.cjs +2 -17
- package/dist/portfolio/client.cjs.map +1 -1
- package/dist/portfolio/client.js +1 -16
- package/dist/portfolio/client.js.map +1 -1
- package/dist/portfolio/index.cjs +18 -17
- package/dist/portfolio/index.d.cts +5 -74
- package/dist/portfolio/index.d.ts +5 -74
- package/dist/portfolio/index.js +4 -3
- package/dist/portfolio/sections/ChallengesSection.d.cts +1 -1
- package/dist/portfolio/sections/ChallengesSection.d.ts +1 -1
- package/dist/portfolio/sections/DetailsSection.d.cts +1 -1
- package/dist/portfolio/sections/DetailsSection.d.ts +1 -1
- package/dist/portfolio/sections/HeroSection.d.cts +1 -1
- package/dist/portfolio/sections/HeroSection.d.ts +1 -1
- package/dist/portfolio/sections/ResultsSection.cjs +4 -3
- package/dist/portfolio/sections/ResultsSection.cjs.map +1 -1
- package/dist/portfolio/sections/ResultsSection.d.cts +1 -1
- package/dist/portfolio/sections/ResultsSection.d.ts +1 -1
- package/dist/portfolio/sections/ResultsSection.js +2 -1
- package/dist/portfolio/sections/ResultsSection.js.map +1 -1
- package/dist/portfolio/sections/ServicesSection.d.cts +1 -1
- package/dist/portfolio/sections/ServicesSection.d.ts +1 -1
- package/dist/portfolio/sections/TechStackSection.d.cts +1 -1
- package/dist/portfolio/sections/TechStackSection.d.ts +1 -1
- package/dist/portfolio/sections/TestimonialSection.d.cts +1 -1
- package/dist/portfolio/sections/TestimonialSection.d.ts +1 -1
- package/dist/portfolio/server.cjs +40 -14
- package/dist/portfolio/server.d.cts +3 -1
- package/dist/portfolio/server.d.ts +3 -1
- package/dist/portfolio/server.js +3 -1
- package/dist/{types-0tVPy4pU.d.ts → types-He5A9okM.d.cts} +1 -1
- package/dist/{types-0tVPy4pU.d.cts → types-He5A9okM.d.ts} +1 -1
- package/package.json +3 -3
- package/dist/chunk-4IES23GV.js.map +0 -1
- package/dist/chunk-5MEZU3OG.cjs.map +0 -1
- package/dist/chunk-RWQ675N6.js.map +0 -1
- package/dist/chunk-UXUU7FVT.cjs.map +0 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { apiGet } from './chunk-QL6KVIFR.js';
|
|
2
2
|
import React from 'react';
|
|
3
|
-
import { resolveApiOrigin } from '@sonordev/site-kit/server';
|
|
3
|
+
import { resolveApiOrigin, resolveServerApiConfig, resolveClientCredential } from '@sonordev/site-kit/server';
|
|
4
|
+
import { SiteKitCredential } from '@sonordev/site-kit/client';
|
|
4
5
|
|
|
5
6
|
// src/shared/brand.ts
|
|
6
7
|
function hexToRgb(hex) {
|
|
@@ -161,12 +162,15 @@ async function AgencySiteKitLayout({
|
|
|
161
162
|
brandConfig: brandConfigOverride,
|
|
162
163
|
theme,
|
|
163
164
|
className,
|
|
164
|
-
showLlmsTxtFooterLink = false
|
|
165
|
+
showLlmsTxtFooterLink = false,
|
|
166
|
+
credential = true
|
|
165
167
|
}) {
|
|
166
168
|
const brandConfig = brandConfigOverride ? mergeBrandConfig(brandConfigOverride) : await fetchBrandConfig(apiKey, apiUrl);
|
|
167
169
|
const cssString = generateBrandCSS(brandConfig);
|
|
168
170
|
const resolvedApiUrl = apiUrl ?? process.env.SONOR_API_URL ?? process.env.UPTRADE_API_URL ?? "https://api.sonor.io";
|
|
169
171
|
const preconnectHref = resolveApiOrigin(resolvedApiUrl);
|
|
172
|
+
const rawKey = credential ? resolveServerApiConfig({ apiKey, apiUrl })?.apiKey ?? "" : "";
|
|
173
|
+
const clientCredential = rawKey ? await resolveClientCredential(rawKey, resolvedApiUrl) : "";
|
|
170
174
|
return React.createElement(
|
|
171
175
|
React.Fragment,
|
|
172
176
|
null,
|
|
@@ -183,6 +187,14 @@ async function AgencySiteKitLayout({
|
|
|
183
187
|
dangerouslySetInnerHTML: { __html: cssString },
|
|
184
188
|
"data-agency-site-kit": "brand"
|
|
185
189
|
}),
|
|
190
|
+
// Publish the minted token + __SITE_KIT_* globals. A childless sibling
|
|
191
|
+
// rendering null — it never encloses page content, so it can't drag the
|
|
192
|
+
// tree into a client boundary (CLAUDE.md's cardinal SSR rule).
|
|
193
|
+
clientCredential ? React.createElement(SiteKitCredential, {
|
|
194
|
+
key: "sk-credential",
|
|
195
|
+
apiKey: clientCredential,
|
|
196
|
+
apiUrl: resolvedApiUrl
|
|
197
|
+
}) : null,
|
|
186
198
|
React.createElement(
|
|
187
199
|
"div",
|
|
188
200
|
{
|
|
@@ -206,5 +218,5 @@ async function AgencySiteKitLayout({
|
|
|
206
218
|
}
|
|
207
219
|
|
|
208
220
|
export { AgencySiteKitLayout, DEFAULT_BRAND_CONFIG, DEFAULT_DARK_MODE, generateBrandCSS, hexToRgb, mergeBrandConfig };
|
|
209
|
-
//# sourceMappingURL=chunk-
|
|
210
|
-
//# sourceMappingURL=chunk-
|
|
221
|
+
//# sourceMappingURL=chunk-2RW4C42Y.js.map
|
|
222
|
+
//# sourceMappingURL=chunk-2RW4C42Y.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/shared/brand.ts","../src/layout/AgencySiteKitLayout.tsx"],"names":[],"mappings":"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","file":"chunk-2RW4C42Y.js","sourcesContent":["/**\n * @sonordev/agency-site-kit — Brand customization system\n *\n * Generates CSS custom properties from a BrandConfig object.\n * Supports light/dark mode with automatic fallbacks.\n */\n\nimport type { BrandConfig } from '../types';\n\n// ---------------------------------------------------------------------------\n// Hex to RGB helper\n// ---------------------------------------------------------------------------\n\n/**\n * Converts a hex color string to an \"R, G, B\" string for use in rgb()/rgba().\n * Supports 3-digit (#abc) and 6-digit (#aabbcc) hex values.\n * Returns null for invalid inputs.\n */\nexport function hexToRgb(hex: string): string | null {\n if (!hex || typeof hex !== 'string') return null;\n\n const cleaned = hex.replace(/^#/, '');\n\n let r: number;\n let g: number;\n let b: number;\n\n if (cleaned.length === 3) {\n r = parseInt(cleaned[0] + cleaned[0], 16);\n g = parseInt(cleaned[1] + cleaned[1], 16);\n b = parseInt(cleaned[2] + cleaned[2], 16);\n } else if (cleaned.length === 6) {\n r = parseInt(cleaned.substring(0, 2), 16);\n g = parseInt(cleaned.substring(2, 4), 16);\n b = parseInt(cleaned.substring(4, 6), 16);\n } else {\n return null;\n }\n\n if (isNaN(r) || isNaN(g) || isNaN(b)) return null;\n\n return `${r}, ${g}, ${b}`;\n}\n\n// ---------------------------------------------------------------------------\n// Default theme values\n// ---------------------------------------------------------------------------\n\nexport const DEFAULT_BRAND_CONFIG: BrandConfig = {\n primary: '#6366f1',\n secondary: '#8b5cf6',\n background: '#ffffff',\n backgroundElevated: '#f8fafc',\n surface: '#f1f5f9',\n surfaceHover: '#e2e8f0',\n surfaceBorder: '#e2e8f0',\n textPrimary: '#0f172a',\n textSecondary: '#475569',\n textTertiary: '#94a3b8',\n fontHeading: \"'Inter', system-ui, -apple-system, sans-serif\",\n fontBody: \"'Inter', system-ui, -apple-system, sans-serif\",\n radius: {\n sm: '0.375rem',\n md: '0.5rem',\n lg: '0.75rem',\n },\n};\n\nexport const DEFAULT_DARK_MODE: Partial<BrandConfig> = {\n background: '#1a1a1f',\n backgroundElevated: '#222228',\n surface: '#2a2a30',\n surfaceHover: '#333338',\n surfaceBorder: '#334155',\n textPrimary: '#f8fafc',\n textSecondary: '#cbd5e1',\n textTertiary: '#64748b',\n};\n\n// ---------------------------------------------------------------------------\n// CSS variable generation\n// ---------------------------------------------------------------------------\n\nfunction buildVariables(config: BrandConfig): string {\n const primaryRgb = hexToRgb(config.primary) || '99, 102, 241';\n const secondaryRgb = hexToRgb(config.secondary) || '139, 92, 246';\n\n const radius = config.radius || DEFAULT_BRAND_CONFIG.radius!;\n\n return [\n // Primary tokens (--sk-*)\n `--sk-primary: ${config.primary};`,\n `--sk-primary-rgb: ${primaryRgb};`,\n `--sk-secondary: ${config.secondary};`,\n `--sk-secondary-rgb: ${secondaryRgb};`,\n `--sk-bg: ${config.background};`,\n `--sk-bg-elevated: ${config.backgroundElevated};`,\n `--sk-surface: ${config.surface};`,\n `--sk-surface-hover: ${config.surfaceHover};`,\n `--sk-border: ${config.surfaceBorder};`,\n `--sk-text-primary: ${config.textPrimary};`,\n `--sk-text-secondary: ${config.textSecondary};`,\n `--sk-text-tertiary: ${config.textTertiary};`,\n `--sk-radius-sm: ${radius.sm};`,\n `--sk-radius: ${radius.md};`,\n `--sk-radius-lg: ${radius.lg};`,\n `--sk-font-heading: ${config.fontHeading};`,\n `--sk-font: ${config.fontBody};`,\n ].join('\\n ');\n}\n\nfunction buildDarkVariables(base: BrandConfig): string {\n const dark = {\n ...DEFAULT_DARK_MODE,\n ...base.darkMode,\n // Primary/secondary carry over unless explicitly overridden\n primary: base.darkMode?.primary || base.primary,\n secondary: base.darkMode?.secondary || base.secondary,\n };\n\n const primaryRgb = hexToRgb(dark.primary!) || hexToRgb(base.primary) || '99, 102, 241';\n const secondaryRgb = hexToRgb(dark.secondary!) || hexToRgb(base.secondary) || '139, 92, 246';\n\n return [\n // Primary tokens (--sk-*)\n `--sk-primary: ${dark.primary};`,\n `--sk-primary-rgb: ${primaryRgb};`,\n `--sk-secondary: ${dark.secondary};`,\n `--sk-secondary-rgb: ${secondaryRgb};`,\n `--sk-bg: ${dark.background || base.background || DEFAULT_DARK_MODE.background};`,\n `--sk-bg-elevated: ${dark.backgroundElevated || base.backgroundElevated || DEFAULT_DARK_MODE.backgroundElevated};`,\n `--sk-surface: ${dark.surface || base.surface || DEFAULT_DARK_MODE.surface};`,\n `--sk-surface-hover: ${dark.surfaceHover || base.surfaceHover || DEFAULT_DARK_MODE.surfaceHover};`,\n `--sk-border: ${dark.surfaceBorder || base.surfaceBorder || DEFAULT_DARK_MODE.surfaceBorder};`,\n `--sk-text-primary: ${dark.textPrimary || base.textPrimary || DEFAULT_DARK_MODE.textPrimary};`,\n `--sk-text-secondary: ${dark.textSecondary || base.textSecondary || DEFAULT_DARK_MODE.textSecondary};`,\n `--sk-text-tertiary: ${dark.textTertiary || base.textTertiary || DEFAULT_DARK_MODE.textTertiary};`,\n ].join('\\n ');\n}\n\n/**\n * Generates a complete CSS string with all --sk-* custom properties.\n *\n * Produces:\n * - `:root` / `[data-theme=\"light\"]` block with light mode values\n * - `[data-theme=\"dark\"]` block with dark mode values\n * - `@media (prefers-color-scheme: dark)` fallback for auto detection\n *\n * Usage: inject the returned string into a `<style>` tag.\n */\nexport function generateBrandCSS(config: BrandConfig): string {\n const merged: BrandConfig = {\n ...DEFAULT_BRAND_CONFIG,\n ...config,\n radius: {\n ...DEFAULT_BRAND_CONFIG.radius!,\n ...config.radius,\n },\n };\n\n const lightVars = buildVariables(merged);\n const darkVars = buildDarkVariables(merged);\n\n return `\n:root, [data-theme=\"light\"] {\n ${lightVars}\n}\n\n[data-theme=\"dark\"] {\n ${darkVars}\n}\n\n@media (prefers-color-scheme: dark) {\n :root:not([data-theme=\"light\"]) {\n ${darkVars}\n }\n}\n`.trim();\n}\n\n/**\n * Merges a partial brand config with defaults, returning a complete BrandConfig.\n */\nexport function mergeBrandConfig(partial: Partial<BrandConfig>): BrandConfig {\n return {\n ...DEFAULT_BRAND_CONFIG,\n ...partial,\n radius: {\n ...DEFAULT_BRAND_CONFIG.radius!,\n ...partial.radius,\n },\n };\n}\n","/**\n * @sonordev/agency-site-kit — AgencySiteKitLayout\n *\n * Server component that wraps children with brand CSS variables.\n * Fetches the brand configuration from the Portal API and injects\n * a <style> tag with all --sk-* custom properties (plus --agency-* aliases for backwards compatibility).\n *\n * Usage in app/layout.tsx:\n *\n * ```tsx\n * import { AgencySiteKitLayout } from '@sonordev/agency-site-kit';\n *\n * export default function RootLayout({ children }) {\n * return (\n * <html lang=\"en\">\n * <body>\n * <AgencySiteKitLayout>\n * {children}\n * </AgencySiteKitLayout>\n * </body>\n * </html>\n * );\n * }\n * ```\n */\n\nimport React from 'react';\nimport {\n resolveApiOrigin,\n resolveClientCredential,\n resolveServerApiConfig,\n} from '@sonordev/site-kit/server';\nimport { SiteKitCredential } from '@sonordev/site-kit/client';\nimport { apiGet } from '../shared/api';\nimport { generateBrandCSS, mergeBrandConfig } from '../shared/brand';\nimport type { BrandConfig, PortfolioConfigResponse } from '../types';\n\nexport interface AgencySiteKitLayoutProps {\n children: React.ReactNode;\n /** Override the API key (defaults to process.env.SONOR_API_KEY) */\n apiKey?: string;\n /** Override the API URL (defaults to process.env.SONOR_API_URL or https://api.sonor.io) */\n apiUrl?: string;\n /** Override brand config instead of fetching from API */\n brandConfig?: Partial<BrandConfig>;\n /** Force a specific theme: \"light\" | \"dark\". Omit for system preference. */\n theme?: 'light' | 'dark';\n /** Additional class name for the wrapper div */\n className?: string;\n /**\n * When true, screen-reader-only link to `/llms.txt` after children. Default false.\n * Prefer `buildAiDiscoveryHeaders` from `@sonordev/site-kit/llms` in root layout.\n */\n showLlmsTxtFooterLink?: boolean;\n /**\n * Mint a short-lived client token from `SONOR_API_KEY` and publish it for\n * client-side site-kit modules (Analytics, Forms, Engage, BookingWidget).\n * Default true.\n *\n * Set false ONLY when the app already mounts core `SiteKitLayout`, which\n * publishes its own credential — two publishers would race. Turning it off\n * without another publisher leaves client modules unauthenticated.\n */\n credential?: boolean;\n}\n\n/**\n * Fetches brand configuration from the Portal API.\n * Returns a merged BrandConfig with defaults applied.\n * Falls back to defaults if the API call fails.\n */\nasync function fetchBrandConfig(\n apiKey?: string,\n apiUrl?: string,\n): Promise<BrandConfig> {\n const response = await apiGet<PortfolioConfigResponse>(\n '/api/public/portfolio/config',\n 3600,\n apiKey,\n apiUrl,\n );\n\n if (response?.brand) {\n return mergeBrandConfig(response.brand);\n }\n\n return mergeBrandConfig({});\n}\n\n/**\n * AgencySiteKitLayout — Server component wrapper\n *\n * Fetches the agency's brand configuration and injects CSS custom properties\n * so all child components can use --sk-* variables for theming.\n *\n * This is an async server component (RSC). It makes one API call at render\n * time, cached via Next.js ISR (default 1 hour revalidation).\n */\nexport async function AgencySiteKitLayout({\n children,\n apiKey,\n apiUrl,\n brandConfig: brandConfigOverride,\n theme,\n className,\n showLlmsTxtFooterLink = false,\n credential = true,\n}: AgencySiteKitLayoutProps): Promise<React.ReactElement> {\n // Use override brand config if provided, otherwise fetch from API\n const brandConfig = brandConfigOverride\n ? mergeBrandConfig(brandConfigOverride)\n : await fetchBrandConfig(apiKey, apiUrl);\n\n const cssString = generateBrandCSS(brandConfig);\n\n const resolvedApiUrl =\n apiUrl ??\n process.env.SONOR_API_URL ??\n process.env.UPTRADE_API_URL ??\n 'https://api.sonor.io';\n // Origin-resolution shared with core SiteKitLayout (one source of truth).\n // The preconnect itself stays — upforge relies on AgencySiteKitLayout to emit\n // it (it doesn't mount core's SiteKitLayout).\n const preconnectHref = resolveApiOrigin(resolvedApiUrl);\n\n /**\n * Client credential. This layout does NOT mount core analytics, so agency\n * sites compose client modules (AnalyticsProvider, BookingWidget, forms)\n * themselves — and before this they had no minted token to hand them. Every\n * one of them hand-rolled the same workaround: read a `NEXT_PUBLIC_*` copy of\n * the project key from a client component, which inlines the long-lived\n * `sonor_` secret straight into the browser bundle.\n *\n * So mint here exactly as core `SiteKitLayout` does. `resolveClientCredential`\n * is server-only (the raw key never crosses the client boundary) and falls\n * back to the raw key only if minting is genuinely unreachable.\n */\n // resolveServerApiConfig is the single source of truth for override → env\n // key resolution; don't re-implement the fallback chain here.\n const rawKey = credential\n ? (resolveServerApiConfig({ apiKey, apiUrl })?.apiKey ?? '')\n : '';\n const clientCredential = rawKey\n ? await resolveClientCredential(rawKey, resolvedApiUrl)\n : '';\n\n return React.createElement(\n React.Fragment,\n null,\n React.createElement('link', {\n rel: 'preconnect',\n href: preconnectHref,\n crossOrigin: 'anonymous',\n }),\n React.createElement('link', {\n rel: 'dns-prefetch',\n href: preconnectHref,\n }),\n React.createElement('style', {\n dangerouslySetInnerHTML: { __html: cssString },\n 'data-agency-site-kit': 'brand',\n }),\n // Publish the minted token + __SITE_KIT_* globals. A childless sibling\n // rendering null — it never encloses page content, so it can't drag the\n // tree into a client boundary (CLAUDE.md's cardinal SSR rule).\n clientCredential\n ? React.createElement(SiteKitCredential, {\n key: 'sk-credential',\n apiKey: clientCredential,\n apiUrl: resolvedApiUrl,\n })\n : null,\n React.createElement(\n 'div',\n {\n 'data-theme': theme || undefined,\n 'data-agency-site-kit': 'root',\n className,\n style: {\n fontFamily: 'var(--sk-font)',\n color: 'var(--sk-text-primary)',\n backgroundColor: 'var(--sk-bg)',\n },\n },\n children,\n showLlmsTxtFooterLink\n ? React.createElement(\n 'footer',\n { className: 'sr-only', 'aria-hidden': true },\n React.createElement('a', { href: '/llms.txt' }, 'llms.txt'),\n )\n : null,\n ),\n );\n}\n"]}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var chunkA5WCMHAE_cjs = require('./chunk-A5WCMHAE.cjs');
|
|
3
4
|
var chunkZBPF7TLZ_cjs = require('./chunk-ZBPF7TLZ.cjs');
|
|
4
|
-
var
|
|
5
|
-
var chunk7ZHRMWJU_cjs = require('./chunk-7ZHRMWJU.cjs');
|
|
5
|
+
var chunkX76U7VLF_cjs = require('./chunk-X76U7VLF.cjs');
|
|
6
6
|
var dynamic = require('next/dynamic');
|
|
7
7
|
var HeroSection = require('@sonordev/agency-site-kit/portfolio/sections/HeroSection');
|
|
8
8
|
var ChallengesSection = require('@sonordev/agency-site-kit/portfolio/sections/ChallengesSection');
|
|
@@ -102,7 +102,7 @@ function renderSection(section, item, index) {
|
|
|
102
102
|
case "portfolioSpeedComparison":
|
|
103
103
|
return /* @__PURE__ */ jsxRuntime.jsx(SpeedComparisonSection__default.default, { data: section.data }, key);
|
|
104
104
|
case "portfolioSiteArchitecture":
|
|
105
|
-
return /* @__PURE__ */ jsxRuntime.jsx(SiteArchitectureSection, { data:
|
|
105
|
+
return /* @__PURE__ */ jsxRuntime.jsx(SiteArchitectureSection, { data: chunkA5WCMHAE_cjs.sanitizeSiteArchitectureData(section.data) }, key);
|
|
106
106
|
case "portfolioDesignSystem":
|
|
107
107
|
return /* @__PURE__ */ jsxRuntime.jsx(DesignSystemSection__default.default, { data: section.data }, key);
|
|
108
108
|
case "portfolioDetails":
|
|
@@ -135,7 +135,7 @@ var SECTION_ORDER = {
|
|
|
135
135
|
portfolioCTA: 16
|
|
136
136
|
};
|
|
137
137
|
function PortfolioPage({ item: rawItem }) {
|
|
138
|
-
const item =
|
|
138
|
+
const item = chunkX76U7VLF_cjs.curatePortfolioProof(rawItem);
|
|
139
139
|
const sortedSections = item.sectionsOrdered ? [...item.sections].sort(
|
|
140
140
|
(a, b) => (a.sectionType === "portfolioHero" ? 0 : 1) - (b.sectionType === "portfolioHero" ? 0 : 1)
|
|
141
141
|
) : [...item.sections].sort(
|
|
@@ -201,7 +201,7 @@ function PortfolioCard({ item, className = "", basePath = "/work" }) {
|
|
|
201
201
|
const thumbnailSrc = item.hero_screenshots?.desktop || item.hero_image || (item.project_id ? `/portfolio-previews/${item.slug}.png` : void 0);
|
|
202
202
|
const kpis = Array.isArray(item.kpis) ? item.kpis : [];
|
|
203
203
|
const services = Array.isArray(item.services) ? item.services : [];
|
|
204
|
-
const kpiPreview =
|
|
204
|
+
const kpiPreview = chunkX76U7VLF_cjs.curateHeroKpis(kpis).slice(0, 3);
|
|
205
205
|
const servicePreview = services.slice(0, 3);
|
|
206
206
|
return /* @__PURE__ */ jsxRuntime.jsx(Link__default.default, { href: `${basePath}/${item.slug}`, className: `block group ${className}`, children: /* @__PURE__ */ jsxRuntime.jsx(GlassCard__default.default, { padding: "sm", hover: true, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-4", children: [
|
|
207
207
|
thumbnailSrc && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -379,16 +379,16 @@ async function PortfolioIndex({
|
|
|
379
379
|
className = ""
|
|
380
380
|
}) {
|
|
381
381
|
const currentPage = Math.max(1, Math.floor(page));
|
|
382
|
-
const categories = await
|
|
383
|
-
const activeCategory = category ? categories.find((c) =>
|
|
384
|
-
const { items, total } = await
|
|
382
|
+
const categories = await chunkA5WCMHAE_cjs.getPortfolioCategories();
|
|
383
|
+
const activeCategory = category ? categories.find((c) => chunkA5WCMHAE_cjs.slugifyCategory(c) === category) ?? null : null;
|
|
384
|
+
const { items, total } = await chunkA5WCMHAE_cjs.getPortfolioItems({
|
|
385
385
|
category: activeCategory ?? void 0,
|
|
386
386
|
limit: pageSize,
|
|
387
387
|
offset: (currentPage - 1) * pageSize
|
|
388
388
|
});
|
|
389
389
|
const totalPages = Math.max(1, Math.ceil(total / pageSize));
|
|
390
390
|
const pageHref = (p) => {
|
|
391
|
-
const base = activeCategory ? `${basePath}/category/${
|
|
391
|
+
const base = activeCategory ? `${basePath}/category/${chunkA5WCMHAE_cjs.slugifyCategory(activeCategory)}` : basePath;
|
|
392
392
|
return p <= 1 ? base : `${base}?page=${p}`;
|
|
393
393
|
};
|
|
394
394
|
return /* @__PURE__ */ jsxRuntime.jsx("section", { className: `w-full ${className}`, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "max-w-7xl mx-auto px-6", children: [
|
|
@@ -398,7 +398,7 @@ async function PortfolioIndex({
|
|
|
398
398
|
categories.map((c) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
399
399
|
CategoryLink,
|
|
400
400
|
{
|
|
401
|
-
href: `${basePath}/category/${
|
|
401
|
+
href: `${basePath}/category/${chunkA5WCMHAE_cjs.slugifyCategory(c)}`,
|
|
402
402
|
label: c,
|
|
403
403
|
active: activeCategory === c
|
|
404
404
|
},
|
|
@@ -534,5 +534,5 @@ Object.keys(client).forEach(function (k) {
|
|
|
534
534
|
get: function () { return client[k]; }
|
|
535
535
|
});
|
|
536
536
|
});
|
|
537
|
-
//# sourceMappingURL=chunk-
|
|
538
|
-
//# sourceMappingURL=chunk-
|
|
537
|
+
//# sourceMappingURL=chunk-2Y2RJB3A.cjs.map
|
|
538
|
+
//# sourceMappingURL=chunk-2Y2RJB3A.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/portfolio/components/PortfolioPage.tsx","../src/portfolio/components/PortfolioSchema.tsx","../src/portfolio/components/PortfolioCard.tsx","../src/portfolio/components/PortfolioGrid.tsx","../src/portfolio/components/PortfolioIndex.tsx","../src/portfolio/components/PortfolioSkeletons.tsx","../src/types.ts"],"names":["dynamic","jsx","HeroSection","ChallengesSection","ResultsSection","TechStackSection","ServicesSection","TestimonialSection","SpeedComparisonSection","sanitizeSiteArchitectureData","DesignSystemSection","DetailsSection","curatePortfolioProof","jsxs","PortfolioAnalytics","curateHeroKpis","Link","GlassCard","Image","formatCategoryLabel","ScrollReveal","getPortfolioCategories","slugifyCategory","getPortfolioItems"],"mappings":"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","file":"chunk-QGSW43TM.cjs","sourcesContent":["/**\n * PortfolioPage — Server component that renders a full portfolio case study.\n *\n * Maps each section in item.sections to the correct renderer component.\n * All sections below the hero are loaded lazily via next/dynamic.\n *\n * NOTE: This file is intentionally NOT a client component — it is designed\n * to be used as an RSC in a Next.js app directory page.\n *\n * Sections MUST be imported via `@sonordev/agency-site-kit/portfolio/sections/*`\n * (self-referencing subpaths, external in tsup's server build), never via\n * relative paths: a relative import would bundle the client sections into\n * this server chunk, the 'use client' boundary would be lost, and the whole\n * PortfolioItemFull would serialize into the RSC flight payload again.\n *\n * Whether a section is a server or client component is decided by the\n * section file itself (its 'use client' directive; tsup routes it to the\n * matching build). Server sections are imported statically — they ship zero\n * client JS, so there is nothing to lazy-load. Client sections load through\n * next/dynamic so each becomes its own browser chunk, fetched only when the\n * case study actually renders that section type.\n */\n\nimport React from 'react';\nimport dynamic from 'next/dynamic';\nimport type {\n PortfolioItemFull,\n PortfolioSection,\n PortfolioHeroData,\n PortfolioChallengesData,\n PortfolioStrategyData,\n PortfolioResultsData,\n PortfolioTechStackData,\n PortfolioServicesData,\n PortfolioTestimonialData,\n PortfolioGalleryData,\n PortfolioVideoData,\n PortfolioTeamData,\n PortfolioFeatureSpotlightData,\n PortfolioBeforeAfterData,\n PortfolioMetricsTimelineData,\n PortfolioConversionFunnelData,\n PortfolioDetailsData,\n PortfolioCTAData,\n} from '../../types';\n\n// Hero is loaded eagerly (above the fold, client — it animates)\nimport HeroSection from '@sonordev/agency-site-kit/portfolio/sections/HeroSection';\n\n// Server sections — no 'use client' in their files, so they render on the\n// server and ship ZERO component JS to the browser. Static imports: there is\n// no client chunk to defer.\nimport ChallengesSection from '@sonordev/agency-site-kit/portfolio/sections/ChallengesSection';\nimport ResultsSection from '@sonordev/agency-site-kit/portfolio/sections/ResultsSection';\nimport TechStackSection from '@sonordev/agency-site-kit/portfolio/sections/TechStackSection';\nimport ServicesSection from '@sonordev/agency-site-kit/portfolio/sections/ServicesSection';\nimport TestimonialSection from '@sonordev/agency-site-kit/portfolio/sections/TestimonialSection';\nimport SpeedComparisonSection from '@sonordev/agency-site-kit/portfolio/sections/SpeedComparisonSection';\nimport DesignSystemSection from '@sonordev/agency-site-kit/portfolio/sections/DesignSystemSection';\nimport DetailsSection from '@sonordev/agency-site-kit/portfolio/sections/DetailsSection';\n\n// Client sections — animated/interactive; lazily loaded so each becomes its\n// own browser chunk, fetched only when rendered.\nconst StrategySection = dynamic(() => import('@sonordev/agency-site-kit/portfolio/sections/StrategySection'), { ssr: true });\nconst GallerySection = dynamic(() => import('@sonordev/agency-site-kit/portfolio/sections/GallerySection'), { ssr: true });\nconst VideoSection = dynamic(() => import('@sonordev/agency-site-kit/portfolio/sections/VideoSection'), { ssr: true });\nconst TeamSection = dynamic(() => import('@sonordev/agency-site-kit/portfolio/sections/TeamSection'), { ssr: true });\nconst FeatureSpotlightSection = dynamic(() => import('@sonordev/agency-site-kit/portfolio/sections/FeatureSpotlightSection'), { ssr: true });\nconst BeforeAfterSection = dynamic(() => import('@sonordev/agency-site-kit/portfolio/sections/BeforeAfterSection'), { ssr: true });\nconst MetricsTimelineSection = dynamic(() => import('@sonordev/agency-site-kit/portfolio/sections/MetricsTimelineSection'), { ssr: true });\nconst ConversionFunnelSection = dynamic(() => import('@sonordev/agency-site-kit/portfolio/sections/ConversionFunnelSection'), { ssr: true });\nconst PerformanceSection = dynamic(() => import('@sonordev/agency-site-kit/portfolio/sections/PerformanceSection'), { ssr: true });\nconst SiteArchitectureSection = dynamic(() => import('@sonordev/agency-site-kit/portfolio/sections/SiteArchitectureSection'), { ssr: true });\nconst CTASection = dynamic(() => import('@sonordev/agency-site-kit/portfolio/sections/CTASection'), { ssr: true });\n\ninterface PortfolioPageProps {\n item: PortfolioItemFull;\n}\n\n// Defense-in-depth only — the enforcing sanitize pass runs in\n// getPortfolioItem (portfolio/server.ts), the last genuinely-server-side hop.\n// See src/portfolio/sanitize.ts for the single source of truth and the story.\nimport { sanitizeSiteArchitectureData } from '../sanitize';\n\n// Display policy for proof points (Lighthouse dedupe, results curation) —\n// see src/portfolio/curate-proof.ts for the single source of truth.\nimport { curatePortfolioProof } from '../curate-proof';\n\n// Engagement-tracking island (client) — crosses the boundary via subpath.\nimport PortfolioAnalytics from '@sonordev/agency-site-kit/portfolio/components/PortfolioAnalytics';\n\nfunction renderSection(section: PortfolioSection, item: PortfolioItemFull, index: number) {\n const key = `${section.sectionType}-${index}`;\n\n switch (section.sectionType) {\n case 'portfolioHero':\n return (\n <HeroSection\n key={key}\n data={section.data as PortfolioHeroData}\n screenshots={item.hero_screenshots ?? undefined}\n liveUrl={item.live_url ?? undefined}\n />\n );\n case 'portfolioChallenges':\n return <ChallengesSection key={key} data={section.data as PortfolioChallengesData} />;\n case 'portfolioStrategy':\n return <StrategySection key={key} data={section.data as PortfolioStrategyData} />;\n case 'portfolioResults':\n return (\n <ResultsSection\n key={key}\n data={section.data as PortfolioResultsData}\n metricsDelta={item.metricsDelta}\n metricsRefreshedAt={item.metrics_last_refreshed_at}\n />\n );\n case 'portfolioTechStack':\n return <TechStackSection key={key} data={section.data as PortfolioTechStackData} />;\n case 'portfolioServices':\n return <ServicesSection key={key} data={section.data as PortfolioServicesData} />;\n case 'portfolioTestimonial':\n return <TestimonialSection key={key} data={section.data as PortfolioTestimonialData} />;\n case 'portfolioGallery':\n return <GallerySection key={key} data={section.data as PortfolioGalleryData} />;\n case 'portfolioVideo':\n return <VideoSection key={key} data={section.data as PortfolioVideoData} />;\n case 'portfolioTeam':\n return <TeamSection key={key} data={section.data as PortfolioTeamData} />;\n case 'portfolioFeatureSpotlight':\n return <FeatureSpotlightSection key={key} data={section.data as PortfolioFeatureSpotlightData} />;\n case 'portfolioBeforeAfter':\n return <BeforeAfterSection key={key} data={section.data as PortfolioBeforeAfterData} />;\n case 'portfolioMetricsTimeline':\n return <MetricsTimelineSection key={key} data={section.data as PortfolioMetricsTimelineData} />;\n case 'portfolioConversionFunnel':\n return <ConversionFunnelSection key={key} data={section.data as PortfolioConversionFunnelData} />;\n case 'portfolioPerformance':\n return <PerformanceSection key={key} data={section.data as any} />;\n case 'portfolioSpeedComparison':\n return <SpeedComparisonSection key={key} data={section.data as any} />;\n case 'portfolioSiteArchitecture':\n return <SiteArchitectureSection key={key} data={sanitizeSiteArchitectureData(section.data) as any} />;\n case 'portfolioDesignSystem':\n return <DesignSystemSection key={key} data={section.data as any} />;\n case 'portfolioDetails':\n return <DetailsSection key={key} data={section.data as PortfolioDetailsData} />;\n case 'portfolioCTA':\n return <CTASection key={key} data={section.data as PortfolioCTAData} />;\n case 'portfolioSeo':\n // SEO section is metadata-only; nothing to render visually.\n return null;\n default:\n return null;\n }\n}\n\n// Canonical display order — ensures consistent rendering regardless of API\n// order. Keys must match PortfolioSectionType exactly (note 'portfolioCTA' —\n// a lowercased 'portfolioCta' key silently fell through to the ?? 99 fallback\n// for years). Unknown/future types sort last via the fallback.\nconst SECTION_ORDER: Record<string, number> = {\n portfolioHero: 0,\n portfolioChallenges: 1,\n portfolioStrategy: 2,\n portfolioResults: 3,\n portfolioPerformance: 4,\n portfolioSpeedComparison: 5,\n portfolioSiteArchitecture: 6,\n portfolioTechStack: 7,\n portfolioDesignSystem: 8,\n portfolioServices: 9,\n portfolioBeforeAfter: 10,\n portfolioTestimonial: 11,\n portfolioMetricsTimeline: 12,\n portfolioConversionFunnel: 13,\n portfolioDetails: 14,\n portfolioSeo: 15,\n portfolioCTA: 16,\n};\n\nexport default function PortfolioPage({ item: rawItem }: PortfolioPageProps) {\n const item = curatePortfolioProof(rawItem);\n\n // Section order: once an agency has deliberately reordered in the\n // dashboard (item.sectionsOrdered), the array order IS the narrative —\n // respect it, with the hero pinned first (it's the LCP shell). Items\n // never reordered keep the canonical order below, so nothing shifts\n // out from under existing case studies.\n const sortedSections = item.sectionsOrdered\n ? [...item.sections].sort(\n (a, b) =>\n (a.sectionType === 'portfolioHero' ? 0 : 1) -\n (b.sectionType === 'portfolioHero' ? 0 : 1),\n )\n : [...item.sections].sort(\n (a, b) => (SECTION_ORDER[a.sectionType] ?? 99) - (SECTION_ORDER[b.sectionType] ?? 99)\n );\n\n return (\n <article className=\"flex w-full min-w-0 max-w-full flex-col overflow-x-clip\">\n {sortedSections.map((section, index) => {\n const rendered = renderSection(section, item, index);\n if (!rendered) return null;\n return (\n <div\n key={`wrapper-${section.sectionType}-${index}`}\n className=\"w-full\"\n data-portfolio-section={section.sectionType}\n >\n {rendered}\n </div>\n );\n })}\n {/* Engagement tracking — childless idle-deferred sibling, never a wrapper */}\n <PortfolioAnalytics slug={item.slug} />\n </article>\n );\n}\n","/**\n * PortfolioSchema — JSON-LD structured data for a case study page.\n *\n * Server component: renders a single <script type=\"application/ld+json\">\n * describing the case study as a schema.org Article (there is no dedicated\n * CaseStudy type; Article is what Google documents for editorial content).\n * Ships zero client JS and adds rich-result eligibility + entity clarity for\n * the agency's portfolio pages.\n *\n * Usage (in the case study page, next to <PortfolioPage />):\n *\n * ```tsx\n * <PortfolioSchema\n * item={item}\n * url={`https://youragency.com/work/${item.slug}`}\n * publisher={{ name: 'Your Agency', url: 'https://youragency.com' }}\n * />\n * ```\n *\n * Only reads fields from the already-sanitized PortfolioItemFull (see\n * getPortfolioItem / sanitize.ts) — never section internals.\n */\n\nimport React from 'react';\nimport type { PortfolioItemFull } from '../../types';\n\nexport interface PortfolioSchemaProps {\n item: PortfolioItemFull;\n /** Canonical URL of this case study page. */\n url: string;\n /** The agency, emitted as author + publisher Organization. */\n publisher?: {\n name: string;\n url?: string;\n /** Absolute URL to a logo image. */\n logo?: string;\n };\n}\n\n/** JSON-LD-safe serialization — escape `<` so `</script>` can never break out. */\nfunction jsonLd(value: unknown): string {\n return JSON.stringify(value).replace(/</g, '\\\\u003c');\n}\n\nexport default function PortfolioSchema({ item, url, publisher }: PortfolioSchemaProps) {\n const headline = item.seo?.metaTitle || item.title;\n const description = item.seo?.metaDescription || item.description;\n\n const keywords = [\n ...(item.seo?.keywords ?? []),\n item.category,\n ...item.services,\n ].filter(Boolean);\n\n const org = publisher\n ? {\n '@type': 'Organization',\n name: publisher.name,\n ...(publisher.url ? { url: publisher.url } : {}),\n ...(publisher.logo\n ? { logo: { '@type': 'ImageObject', url: publisher.logo } }\n : {}),\n }\n : undefined;\n\n const schema = {\n '@context': 'https://schema.org',\n '@type': 'Article',\n headline,\n ...(description ? { description } : {}),\n ...(item.hero_image ? { image: [item.hero_image] } : {}),\n url,\n mainEntityOfPage: { '@type': 'WebPage', '@id': url },\n ...(item.published_at ? { datePublished: item.published_at } : {}),\n ...(item.metrics_last_refreshed_at || item.published_at\n ? { dateModified: item.metrics_last_refreshed_at || item.published_at }\n : {}),\n ...(keywords.length ? { keywords: keywords.join(', ') } : {}),\n ...(org ? { author: org, publisher: org } : {}),\n ...(item.details?.industry ? { about: item.details.industry } : {}),\n };\n\n return (\n <script\n type=\"application/ld+json\"\n dangerouslySetInnerHTML={{ __html: jsonLd(schema) }}\n />\n );\n}\n","// Shared component — no directive on purpose: hook-free, so it renders\n// server-side (zero JS) from PortfolioIndex and stays usable from client\n// code via the /portfolio/client barrel.\nimport React from 'react';\nimport Image from 'next/image';\nimport Link from 'next/link';\nimport type { PortfolioItem } from '../../types';\nimport GlassCard from '@sonordev/agency-site-kit/portfolio/primitives/GlassCard';\nimport { curateHeroKpis } from '../curate-proof';\nimport { formatCategoryLabel, formatServiceTag } from '../format';\n\ninterface PortfolioCardProps {\n item: PortfolioItem;\n className?: string;\n /** Base path for portfolio detail links (default: '/work') */\n basePath?: string;\n}\n\nexport default function PortfolioCard({ item, className = '', basePath = '/work' }: PortfolioCardProps) {\n // Use hero_screenshots desktop → hero_image → trifolio screenshot convention\n const thumbnailSrc =\n item.hero_screenshots?.desktop ||\n item.hero_image ||\n (item.project_id ? `/portfolio-previews/${item.slug}.png` : undefined);\n const kpis = Array.isArray(item.kpis) ? item.kpis : [];\n const services = Array.isArray(item.services) ? item.services : [];\n // Collapse per-category Lighthouse KPIs so a card never reads\n // \"100/100 · 100/100 · 100/100\" — business outcomes lead the preview.\n const kpiPreview = curateHeroKpis(kpis).slice(0, 3);\n const servicePreview = services.slice(0, 3);\n\n return (\n <Link href={`${basePath}/${item.slug}`} className={`block group ${className}`}>\n <GlassCard padding=\"sm\" hover>\n <div className=\"flex flex-col gap-4\">\n {/* Thumbnail */}\n {thumbnailSrc && (\n <div\n className=\"relative w-full overflow-hidden rounded-xl\"\n style={{ aspectRatio: '16/10' }}\n >\n <Image\n src={thumbnailSrc}\n alt={item.hero_image_alt || item.title}\n fill\n sizes=\"(max-width: 768px) 100vw, (max-width: 1024px) 50vw, 33vw\"\n className=\"object-cover object-top transition-transform duration-500 group-hover:scale-105\"\n />\n {/* Category badge */}\n <span\n className=\"absolute top-3 left-3 px-3 py-1 rounded-full text-[11px] uppercase tracking-wider font-semibold\"\n style={{\n background: 'color-mix(in srgb, var(--sk-primary, #6366f1) 80%, transparent)',\n color: '#ffffff',\n backdropFilter: 'blur(8px)',\n }}\n >\n {formatCategoryLabel(item.category)}\n </span>\n </div>\n )}\n\n {/* Text content */}\n <div className=\"flex flex-col gap-2 px-2 pb-2\">\n <h3\n className=\"text-lg font-semibold line-clamp-1\"\n style={{ color: 'var(--sk-text-primary, #ffffff)' }}\n >\n {item.title}\n </h3>\n\n {item.subtitle && (\n <p\n className=\"text-sm line-clamp-2\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n >\n {item.subtitle}\n </p>\n )}\n\n {/* KPI preview */}\n {kpiPreview.length > 0 && (\n <div className=\"flex items-center gap-4 mt-1\">\n {kpiPreview.map((kpi, i) => (\n <div key={i} className=\"flex flex-col\">\n <span\n className=\"text-base font-bold\"\n style={{ color: 'var(--sk-primary, #6366f1)' }}\n >\n {kpi.prefix || ''}\n {kpi.value.toLocaleString()}\n {kpi.suffix}\n </span>\n <span\n className=\"text-[10px] uppercase tracking-wider font-medium\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n >\n {kpi.label}\n </span>\n </div>\n ))}\n </div>\n )}\n\n {/* Services tags */}\n {servicePreview.length > 0 && (\n <div className=\"flex flex-wrap gap-1.5 mt-2\">\n {servicePreview.map((service) => (\n <span\n key={service}\n className=\"px-2.5 py-0.5 rounded-full text-[11px] font-medium\"\n style={{\n background: 'var(--sk-surface, rgba(255,255,255,0.05))',\n color: 'var(--sk-text-secondary, #a1a1aa)',\n border: '1px solid var(--sk-border, rgba(255,255,255,0.1))',\n }}\n >\n {formatServiceTag(service)}\n </span>\n ))}\n {services.length > 3 && (\n <span\n className=\"px-2.5 py-0.5 rounded-full text-[11px] font-medium\"\n style={{\n background: 'var(--sk-surface, rgba(255,255,255,0.05))',\n color: 'var(--sk-text-tertiary, #71717a)',\n }}\n >\n +{item.services.length - 3}\n </span>\n )}\n </div>\n )}\n </div>\n </div>\n </GlassCard>\n </Link>\n );\n}\n","// Shared component — no directive on purpose: hook-free, so it renders\n// server-side (zero JS) from PortfolioIndex and stays usable from client\n// code via the /portfolio/client barrel.\nimport React from 'react';\nimport type { PortfolioItem } from '../../types';\nimport ScrollReveal from '@sonordev/agency-site-kit/portfolio/primitives/ScrollReveal';\nimport PortfolioCard from './PortfolioCard';\n\ninterface PortfolioGridProps {\n items: PortfolioItem[];\n className?: string;\n /** Base path for portfolio detail links (default: '/work') */\n basePath?: string;\n}\n\nexport default function PortfolioGrid({ items, className = '', basePath = '/work' }: PortfolioGridProps) {\n return (\n <ScrollReveal stagger={0.1}>\n <div\n className={`grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 ${className}`}\n style={{\n gap: 'var(--sk-grid-gap, 24px)',\n }}\n >\n {items.map((item) => (\n <PortfolioCard key={item.id} item={item} basePath={basePath} />\n ))}\n </div>\n </ScrollReveal>\n );\n}\n","/**\n * PortfolioIndex — turnkey, server-rendered /work index.\n *\n * Fetches items + categories and renders: category nav (real links —\n * crawlable, no client state), the portfolio grid, pagination, and an\n * intentional empty state. Ships zero component JS.\n *\n * Category filtering is route-based for SEO:\n * /work → all items\n * /work/category/[category] → one category (slug via slugifyCategory)\n * Pagination via ?page= on either route.\n */\n\nimport React from 'react';\nimport {\n getPortfolioCategories,\n getPortfolioItems,\n slugifyCategory,\n} from '../server';\nimport PortfolioGrid from './PortfolioGrid';\n\nexport interface PortfolioIndexProps {\n /** Route prefix for case studies + category links. Defaults to \"/work\". */\n basePath?: string;\n /** Active category SLUG (from the [category] route param). */\n category?: string | null;\n /** 1-based page number (from ?page=). */\n page?: number;\n /** Items per page. Defaults to 12. */\n pageSize?: number;\n /** Optional heading block rendered above the category nav. */\n heading?: React.ReactNode;\n /** Override the empty state. */\n emptyState?: React.ReactNode;\n /** Hide the category nav (e.g. single-category portfolios). */\n showCategories?: boolean;\n className?: string;\n}\n\nfunction CategoryLink({\n href,\n label,\n active,\n}: {\n href: string;\n label: string;\n active: boolean;\n}) {\n return (\n <a\n href={href}\n aria-current={active ? 'page' : undefined}\n className=\"px-4 py-1.5 rounded-full text-sm font-medium transition-colors\"\n style={{\n background: active\n ? 'color-mix(in srgb, var(--sk-primary, #6366f1) 15%, transparent)'\n : 'var(--sk-surface, rgba(255,255,255,0.05))',\n color: active ? 'var(--sk-primary, #6366f1)' : 'var(--sk-text-secondary, #a1a1aa)',\n border: `1px solid ${\n active\n ? 'color-mix(in srgb, var(--sk-primary, #6366f1) 25%, transparent)'\n : 'var(--sk-border, rgba(255,255,255,0.1))'\n }`,\n }}\n >\n {label}\n </a>\n );\n}\n\nfunction PageLink({ href, label, disabled }: { href: string; label: string; disabled: boolean }) {\n if (disabled) {\n return (\n <span\n aria-disabled=\"true\"\n className=\"px-4 py-2 rounded-xl text-sm font-medium opacity-40\"\n style={{\n background: 'var(--sk-surface, rgba(255,255,255,0.05))',\n color: 'var(--sk-text-tertiary, #71717a)',\n border: '1px solid var(--sk-border, rgba(255,255,255,0.1))',\n }}\n >\n {label}\n </span>\n );\n }\n return (\n <a\n href={href}\n className=\"px-4 py-2 rounded-xl text-sm font-medium transition-colors\"\n style={{\n background: 'var(--sk-surface, rgba(255,255,255,0.05))',\n color: 'var(--sk-text-secondary, #a1a1aa)',\n border: '1px solid var(--sk-border, rgba(255,255,255,0.1))',\n }}\n >\n {label}\n </a>\n );\n}\n\nexport default async function PortfolioIndex({\n basePath = '/work',\n category = null,\n page = 1,\n pageSize = 12,\n heading,\n emptyState,\n showCategories = true,\n className = '',\n}: PortfolioIndexProps) {\n const currentPage = Math.max(1, Math.floor(page));\n const categories = await getPortfolioCategories();\n\n // Route param is a slug; the API filter wants the original category value.\n const activeCategory = category\n ? categories.find((c) => slugifyCategory(c) === category) ?? null\n : null;\n\n const { items, total } = await getPortfolioItems({\n category: activeCategory ?? undefined,\n limit: pageSize,\n offset: (currentPage - 1) * pageSize,\n });\n\n const totalPages = Math.max(1, Math.ceil(total / pageSize));\n const pageHref = (p: number) => {\n const base = activeCategory\n ? `${basePath}/category/${slugifyCategory(activeCategory)}`\n : basePath;\n return p <= 1 ? base : `${base}?page=${p}`;\n };\n\n return (\n <section className={`w-full ${className}`}>\n <div className=\"max-w-7xl mx-auto px-6\">\n {heading}\n\n {/* Category nav — real links, crawlable */}\n {showCategories && categories.length > 1 && (\n <nav aria-label=\"Portfolio categories\" className=\"flex flex-wrap gap-2 mb-10\">\n <CategoryLink href={basePath} label=\"All\" active={!activeCategory} />\n {categories.map((c) => (\n <CategoryLink\n key={c}\n href={`${basePath}/category/${slugifyCategory(c)}`}\n label={c}\n active={activeCategory === c}\n />\n ))}\n </nav>\n )}\n\n {items.length > 0 ? (\n <PortfolioGrid items={items} basePath={basePath} />\n ) : (\n emptyState ?? (\n <div\n className=\"flex flex-col items-center gap-3 py-24 rounded-2xl text-center\"\n style={{\n background: 'var(--sk-surface, rgba(255,255,255,0.03))',\n border: '1px dashed var(--sk-border, rgba(255,255,255,0.1))',\n }}\n >\n <span\n className=\"text-lg font-semibold\"\n style={{ color: 'var(--sk-text-primary, #ffffff)' }}\n >\n Case studies coming soon\n </span>\n <span className=\"text-sm\" style={{ color: 'var(--sk-text-tertiary, #71717a)' }}>\n {activeCategory\n ? `Nothing published in ${activeCategory} yet.`\n : \"We're writing up our latest work right now.\"}\n </span>\n </div>\n )\n )}\n\n {/* Pagination */}\n {totalPages > 1 && (\n <nav\n aria-label=\"Portfolio pages\"\n className=\"flex items-center justify-center gap-3 mt-12\"\n >\n <PageLink\n href={pageHref(currentPage - 1)}\n label=\"← Previous\"\n disabled={currentPage <= 1}\n />\n <span className=\"text-sm\" style={{ color: 'var(--sk-text-tertiary, #71717a)' }}>\n Page {currentPage} of {totalPages}\n </span>\n <PageLink\n href={pageHref(currentPage + 1)}\n label=\"Next →\"\n disabled={currentPage >= totalPages}\n />\n </nav>\n )}\n </div>\n </section>\n );\n}\n","/**\n * Loading skeletons for portfolio routes — pure CSS pulse, server-safe,\n * zero JS. Drop into `loading.tsx`:\n *\n * ```tsx\n * export { PortfolioIndexSkeleton as default } from '@sonordev/agency-site-kit/portfolio';\n * ```\n */\n\nimport React from 'react';\n\nconst PULSE_CSS = `\n.sk-skeleton{animation:sk-skeleton-pulse 1.6s ease-in-out infinite;background:var(--sk-surface,rgba(255,255,255,0.06));border:1px solid var(--sk-border,rgba(255,255,255,0.08));border-radius:16px}\n@keyframes sk-skeleton-pulse{0%,100%{opacity:.55}50%{opacity:1}}\n@media (prefers-reduced-motion:reduce){.sk-skeleton{animation:none}}\n`;\n\nfunction SkeletonStyles() {\n return (\n <style href=\"sk-skeletons\" precedence=\"default\">\n {PULSE_CSS}\n </style>\n );\n}\n\n/** Grid of pulsing card placeholders for the /work index. */\nexport function PortfolioIndexSkeleton({ cards = 6 }: { cards?: number }) {\n return (\n <section className=\"w-full py-16\">\n <SkeletonStyles />\n <div className=\"max-w-7xl mx-auto px-6\">\n <div className=\"sk-skeleton h-9 w-56 mb-4\" />\n <div className=\"sk-skeleton h-5 w-80 mb-10\" />\n <div className=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6\">\n {Array.from({ length: cards }).map((_, i) => (\n <div key={i} className=\"flex flex-col gap-3\">\n <div className=\"sk-skeleton w-full\" style={{ aspectRatio: '16/10' }} />\n <div className=\"sk-skeleton h-5 w-3/4\" />\n <div className=\"sk-skeleton h-4 w-1/2\" />\n </div>\n ))}\n </div>\n </div>\n </section>\n );\n}\n\n/** Hero-shaped placeholder for /work/[slug] case study pages. */\nexport function PortfolioPageSkeleton() {\n return (\n <article className=\"w-full\">\n <SkeletonStyles />\n <div className=\"max-w-7xl mx-auto px-6 py-24\">\n <div className=\"grid grid-cols-1 lg:grid-cols-2 gap-12 items-start\">\n <div className=\"flex flex-col gap-5\">\n <div className=\"sk-skeleton h-7 w-32\" style={{ borderRadius: '9999px' }} />\n <div className=\"sk-skeleton h-12 w-full\" />\n <div className=\"sk-skeleton h-12 w-4/5\" />\n <div className=\"sk-skeleton h-5 w-full\" />\n <div className=\"sk-skeleton h-5 w-2/3\" />\n </div>\n <div className=\"sk-skeleton w-full\" style={{ aspectRatio: '11/5' }} />\n </div>\n </div>\n </article>\n );\n}\n","/**\n * @sonordev/agency-site-kit — Type definitions\n *\n * These types mirror the Portal API's cms/types.ts definitions for portfolio\n * content. Keep them in sync when modifying the API response shapes.\n */\n\n// ---------------------------------------------------------------------------\n// Brand Configuration\n// ---------------------------------------------------------------------------\n\nexport interface BrandConfig {\n /** Primary brand color (hex) */\n primary: string;\n /** Secondary/accent color (hex) */\n secondary: string;\n /** Background color (hex) */\n background: string;\n /** Elevated background — cards, modals (hex) */\n backgroundElevated: string;\n /** Surface color — interactive containers (hex) */\n surface: string;\n /** Surface hover state (hex) */\n surfaceHover: string;\n /** Surface border color (hex) */\n surfaceBorder: string;\n /** Primary text color (hex) */\n textPrimary: string;\n /** Secondary text color (hex) */\n textSecondary: string;\n /** Tertiary/muted text color (hex) */\n textTertiary: string;\n /** Heading font family */\n fontHeading: string;\n /** Body font family */\n fontBody: string;\n /** Logo URL (absolute or relative) */\n logoUrl?: string;\n /** Logo alt text */\n logoAlt?: string;\n /** Dark mode overrides — partial, merged with base values */\n darkMode?: Partial<Omit<BrandConfig, 'fontHeading' | 'fontBody' | 'logoUrl' | 'logoAlt' | 'darkMode'>>;\n /** Border radius tokens */\n radius?: {\n sm: string;\n md: string;\n lg: string;\n };\n}\n\n// ---------------------------------------------------------------------------\n// Sanity Image Reference\n// ---------------------------------------------------------------------------\n\nexport interface SanityImageRef {\n _type: 'image';\n asset: { _type: 'reference'; _ref: string };\n hotspot?: { x: number; y: number; height: number; width: number };\n crop?: { top: number; bottom: number; left: number; right: number };\n alt?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Metrics\n// ---------------------------------------------------------------------------\n\nexport type MetricSource = 'measured' | 'estimated';\n\nexport interface PortfolioKPI {\n label: string;\n value: number;\n suffix: string;\n prefix?: string;\n description: string;\n source: MetricSource;\n}\n\nexport interface MetricsDelta {\n metric: string;\n baseline: number;\n current: number;\n delta: number;\n deltaPercent: number;\n direction: 'up' | 'down' | 'flat';\n timespan: string;\n}\n\nexport interface MetricsSnapshot {\n seo: {\n clicks_28d?: number;\n impressions_28d?: number;\n avg_position?: number;\n ctr?: number;\n };\n cwv: {\n lcp_ms?: number;\n inp_ms?: number;\n cls?: number;\n performance_score?: number;\n };\n analytics: {\n sessions_28d?: number;\n page_views_28d?: number;\n conversion_rate?: number;\n bounce_rate?: number;\n };\n pages: {\n total_indexed?: number;\n total_crawled?: number;\n total_pages?: number;\n };\n}\n\n// ---------------------------------------------------------------------------\n// Portfolio Section Data Types\n// ---------------------------------------------------------------------------\n\nexport interface PortfolioHeroData {\n headline: string;\n subheadline: string;\n description: string;\n category: string;\n services: string[];\n liveUrl?: string;\n screenshots: {\n desktop?: string;\n tablet?: string;\n mobile?: string;\n };\n kpis: PortfolioKPI[];\n}\n\nexport interface PortfolioChallengeItem {\n title: string;\n description: string;\n solution: string;\n result?: string;\n icon?: string;\n}\n\nexport interface PortfolioChallengesData {\n items: PortfolioChallengeItem[];\n}\n\nexport interface PortfolioStrategyPhase {\n number: number;\n title: string;\n description: string;\n deliverables?: string[];\n icon?: string;\n timeline?: string;\n}\n\nexport interface PortfolioStrategyData {\n phases: PortfolioStrategyPhase[];\n}\n\nexport interface PortfolioResultItem {\n title: string;\n description: string;\n metric?: {\n value: number;\n suffix: string;\n prefix?: string;\n };\n source: MetricSource;\n icon?: string;\n}\n\nexport interface PortfolioResultsData {\n items: PortfolioResultItem[];\n}\n\nexport interface PortfolioTechItem {\n name: string;\n category: string;\n icon?: string;\n description?: string;\n}\n\nexport interface PortfolioTechStackData {\n technologies: PortfolioTechItem[];\n}\n\nexport interface PortfolioServiceItem {\n title: string;\n description: string;\n features: string[];\n icon?: string;\n}\n\nexport interface PortfolioServicesData {\n items: PortfolioServiceItem[];\n}\n\nexport interface PortfolioTestimonialData {\n quote: string;\n author: string;\n title: string;\n company: string;\n avatar?: SanityImageRef;\n rating?: number;\n}\n\nexport interface PortfolioGalleryImage {\n image: SanityImageRef;\n caption?: string;\n type?: 'screenshot' | 'before' | 'after' | 'design';\n}\n\nexport interface PortfolioGalleryData {\n images: PortfolioGalleryImage[];\n layout?: 'grid' | 'masonry';\n}\n\nexport interface PortfolioVideoData {\n url?: string;\n embedUrl?: string;\n thumbnail: SanityImageRef;\n title: string;\n duration?: string;\n platform?: 'youtube' | 'vimeo' | 'custom';\n}\n\nexport interface PortfolioTeamMember {\n name: string;\n role: string;\n avatar?: SanityImageRef;\n}\n\nexport interface PortfolioTeamData {\n members: PortfolioTeamMember[];\n}\n\nexport interface PortfolioAnnotation {\n x: number;\n y: number;\n label: string;\n description: string;\n icon?: string;\n}\n\nexport interface PortfolioFeatureSpotlightData {\n image: SanityImageRef;\n title: string;\n description: string;\n layout: 'left' | 'right' | 'full';\n annotations: PortfolioAnnotation[];\n}\n\nexport interface PortfolioBeforeAfterData {\n before: SanityImageRef;\n after: SanityImageRef;\n title?: string;\n description?: string;\n defaultPosition?: number;\n}\n\nexport interface PortfolioMetricsDataPoint {\n month: string;\n label: string;\n metrics: {\n traffic?: number;\n conversions?: number;\n revenue?: number;\n rankings?: number;\n };\n}\n\nexport interface PortfolioMetricsTimelineData {\n dataPoints: PortfolioMetricsDataPoint[];\n baselineDate: string;\n title?: string;\n description?: string;\n}\n\nexport interface PortfolioFunnelStage {\n label: string;\n value: number;\n icon?: string;\n color?: string;\n}\n\nexport interface PortfolioConversionFunnelData {\n stages: PortfolioFunnelStage[];\n title?: string;\n description?: string;\n}\n\nexport interface PortfolioDetailsData {\n industry: string;\n location?: string;\n website?: string;\n timeline?: string;\n launchDate?: string;\n budgetRange?: string;\n}\n\nexport interface PortfolioSeoData {\n metaTitle: string;\n metaDescription: string;\n keywords: string[];\n ogImage?: SanityImageRef;\n}\n\nexport interface PortfolioCTAData {\n headline: string;\n description: string;\n buttonText: string;\n buttonUrl: string;\n style?: 'primary' | 'glass';\n}\n\n// ---------------------------------------------------------------------------\n// Portfolio Sections\n// ---------------------------------------------------------------------------\n\nexport const PORTFOLIO_SECTION_TYPES = [\n 'portfolioHero',\n 'portfolioChallenges',\n 'portfolioStrategy',\n 'portfolioResults',\n 'portfolioTechStack',\n 'portfolioServices',\n 'portfolioTestimonial',\n 'portfolioGallery',\n 'portfolioVideo',\n 'portfolioTeam',\n 'portfolioFeatureSpotlight',\n 'portfolioBeforeAfter',\n 'portfolioMetricsTimeline',\n 'portfolioConversionFunnel',\n 'portfolioPerformance',\n 'portfolioSpeedComparison',\n 'portfolioSiteArchitecture',\n 'portfolioDesignSystem',\n 'portfolioDetails',\n 'portfolioSeo',\n 'portfolioCTA',\n] as const;\n\nexport type PortfolioSectionType = (typeof PORTFOLIO_SECTION_TYPES)[number];\n\nexport type PortfolioSectionData =\n | PortfolioHeroData\n | PortfolioChallengesData\n | PortfolioStrategyData\n | PortfolioResultsData\n | PortfolioTechStackData\n | PortfolioServicesData\n | PortfolioTestimonialData\n | PortfolioGalleryData\n | PortfolioVideoData\n | PortfolioTeamData\n | PortfolioFeatureSpotlightData\n | PortfolioBeforeAfterData\n | PortfolioMetricsTimelineData\n | PortfolioConversionFunnelData\n | PortfolioDetailsData\n | PortfolioSeoData\n | PortfolioCTAData;\n\nexport interface PortfolioSectionDataMap {\n portfolioHero: PortfolioHeroData;\n portfolioChallenges: PortfolioChallengesData;\n portfolioStrategy: PortfolioStrategyData;\n portfolioResults: PortfolioResultsData;\n portfolioTechStack: PortfolioTechStackData;\n portfolioServices: PortfolioServicesData;\n portfolioTestimonial: PortfolioTestimonialData;\n portfolioGallery: PortfolioGalleryData;\n portfolioVideo: PortfolioVideoData;\n portfolioTeam: PortfolioTeamData;\n portfolioFeatureSpotlight: PortfolioFeatureSpotlightData;\n portfolioBeforeAfter: PortfolioBeforeAfterData;\n portfolioMetricsTimeline: PortfolioMetricsTimelineData;\n portfolioConversionFunnel: PortfolioConversionFunnelData;\n portfolioDetails: PortfolioDetailsData;\n portfolioSeo: PortfolioSeoData;\n portfolioCTA: PortfolioCTAData;\n}\n\nexport interface PortfolioSection<T extends PortfolioSectionType = PortfolioSectionType> {\n sectionType: T;\n displayName: string;\n data: T extends keyof PortfolioSectionDataMap ? PortfolioSectionDataMap[T] : PortfolioSectionData;\n}\n\n// ---------------------------------------------------------------------------\n// Portfolio Item (mirrors public API response)\n// ---------------------------------------------------------------------------\n\nexport interface PortfolioItem {\n id: string;\n slug: string;\n title: string;\n subtitle: string;\n category: string;\n services: string[];\n description: string;\n hero_image: string;\n hero_image_alt: string;\n live_url: string | null;\n kpis: PortfolioKPI[];\n details: PortfolioDetailsData | null;\n seo: PortfolioSeoData | null;\n featured: boolean;\n order?: number;\n published_at: string | null;\n /** Sonor project ID (present for items generated from a managed project) */\n project_id: string | null;\n hero_screenshots: {\n desktop?: string;\n tablet?: string;\n mobile?: string;\n } | null;\n baseline_metrics: MetricsSnapshot | null;\n current_metrics: MetricsSnapshot | null;\n metrics_last_refreshed_at: string | null;\n}\n\nexport interface PortfolioItemFull extends PortfolioItem {\n sections: PortfolioSection[];\n metricsDelta: MetricsDelta[];\n /**\n * True once the agency deliberately reordered sections in the dashboard.\n * PortfolioPage respects the sections array order when set (hero pinned\n * first); otherwise it applies the canonical order.\n */\n sectionsOrdered?: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// API Responses\n// ---------------------------------------------------------------------------\n\nexport interface PortfolioListResponse {\n items: PortfolioItem[];\n total: number;\n limit: number;\n offset: number;\n}\n\nexport interface PortfolioConfigResponse {\n brand: BrandConfig;\n orgName: string;\n orgSlug: string;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/portfolio/components/PortfolioPage.tsx","../src/portfolio/components/PortfolioSchema.tsx","../src/portfolio/components/PortfolioCard.tsx","../src/portfolio/components/PortfolioGrid.tsx","../src/portfolio/components/PortfolioIndex.tsx","../src/portfolio/components/PortfolioSkeletons.tsx","../src/types.ts"],"names":["dynamic","jsx","HeroSection","ChallengesSection","ResultsSection","TechStackSection","ServicesSection","TestimonialSection","SpeedComparisonSection","sanitizeSiteArchitectureData","DesignSystemSection","DetailsSection","curatePortfolioProof","jsxs","PortfolioAnalytics","curateHeroKpis","Link","GlassCard","Image","formatCategoryLabel","ScrollReveal","getPortfolioCategories","slugifyCategory","getPortfolioItems"],"mappings":"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","file":"chunk-2Y2RJB3A.cjs","sourcesContent":["/**\n * PortfolioPage — Server component that renders a full portfolio case study.\n *\n * Maps each section in item.sections to the correct renderer component.\n * All sections below the hero are loaded lazily via next/dynamic.\n *\n * NOTE: This file is intentionally NOT a client component — it is designed\n * to be used as an RSC in a Next.js app directory page.\n *\n * Sections MUST be imported via `@sonordev/agency-site-kit/portfolio/sections/*`\n * (self-referencing subpaths, external in tsup's server build), never via\n * relative paths: a relative import would bundle the client sections into\n * this server chunk, the 'use client' boundary would be lost, and the whole\n * PortfolioItemFull would serialize into the RSC flight payload again.\n *\n * Whether a section is a server or client component is decided by the\n * section file itself (its 'use client' directive; tsup routes it to the\n * matching build). Server sections are imported statically — they ship zero\n * client JS, so there is nothing to lazy-load. Client sections load through\n * next/dynamic so each becomes its own browser chunk, fetched only when the\n * case study actually renders that section type.\n */\n\nimport React from 'react';\nimport dynamic from 'next/dynamic';\nimport type {\n PortfolioItemFull,\n PortfolioSection,\n PortfolioHeroData,\n PortfolioChallengesData,\n PortfolioStrategyData,\n PortfolioResultsData,\n PortfolioTechStackData,\n PortfolioServicesData,\n PortfolioTestimonialData,\n PortfolioGalleryData,\n PortfolioVideoData,\n PortfolioTeamData,\n PortfolioFeatureSpotlightData,\n PortfolioBeforeAfterData,\n PortfolioMetricsTimelineData,\n PortfolioConversionFunnelData,\n PortfolioDetailsData,\n PortfolioCTAData,\n} from '../../types';\n\n// Hero is loaded eagerly (above the fold, client — it animates)\nimport HeroSection from '@sonordev/agency-site-kit/portfolio/sections/HeroSection';\n\n// Server sections — no 'use client' in their files, so they render on the\n// server and ship ZERO component JS to the browser. Static imports: there is\n// no client chunk to defer.\nimport ChallengesSection from '@sonordev/agency-site-kit/portfolio/sections/ChallengesSection';\nimport ResultsSection from '@sonordev/agency-site-kit/portfolio/sections/ResultsSection';\nimport TechStackSection from '@sonordev/agency-site-kit/portfolio/sections/TechStackSection';\nimport ServicesSection from '@sonordev/agency-site-kit/portfolio/sections/ServicesSection';\nimport TestimonialSection from '@sonordev/agency-site-kit/portfolio/sections/TestimonialSection';\nimport SpeedComparisonSection from '@sonordev/agency-site-kit/portfolio/sections/SpeedComparisonSection';\nimport DesignSystemSection from '@sonordev/agency-site-kit/portfolio/sections/DesignSystemSection';\nimport DetailsSection from '@sonordev/agency-site-kit/portfolio/sections/DetailsSection';\n\n// Client sections — animated/interactive; lazily loaded so each becomes its\n// own browser chunk, fetched only when rendered.\nconst StrategySection = dynamic(() => import('@sonordev/agency-site-kit/portfolio/sections/StrategySection'), { ssr: true });\nconst GallerySection = dynamic(() => import('@sonordev/agency-site-kit/portfolio/sections/GallerySection'), { ssr: true });\nconst VideoSection = dynamic(() => import('@sonordev/agency-site-kit/portfolio/sections/VideoSection'), { ssr: true });\nconst TeamSection = dynamic(() => import('@sonordev/agency-site-kit/portfolio/sections/TeamSection'), { ssr: true });\nconst FeatureSpotlightSection = dynamic(() => import('@sonordev/agency-site-kit/portfolio/sections/FeatureSpotlightSection'), { ssr: true });\nconst BeforeAfterSection = dynamic(() => import('@sonordev/agency-site-kit/portfolio/sections/BeforeAfterSection'), { ssr: true });\nconst MetricsTimelineSection = dynamic(() => import('@sonordev/agency-site-kit/portfolio/sections/MetricsTimelineSection'), { ssr: true });\nconst ConversionFunnelSection = dynamic(() => import('@sonordev/agency-site-kit/portfolio/sections/ConversionFunnelSection'), { ssr: true });\nconst PerformanceSection = dynamic(() => import('@sonordev/agency-site-kit/portfolio/sections/PerformanceSection'), { ssr: true });\nconst SiteArchitectureSection = dynamic(() => import('@sonordev/agency-site-kit/portfolio/sections/SiteArchitectureSection'), { ssr: true });\nconst CTASection = dynamic(() => import('@sonordev/agency-site-kit/portfolio/sections/CTASection'), { ssr: true });\n\ninterface PortfolioPageProps {\n item: PortfolioItemFull;\n}\n\n// Defense-in-depth only — the enforcing sanitize pass runs in\n// getPortfolioItem (portfolio/server.ts), the last genuinely-server-side hop.\n// See src/portfolio/sanitize.ts for the single source of truth and the story.\nimport { sanitizeSiteArchitectureData } from '../sanitize';\n\n// Display policy for proof points (Lighthouse dedupe, results curation) —\n// see src/portfolio/curate-proof.ts for the single source of truth.\nimport { curatePortfolioProof } from '../curate-proof';\n\n// Engagement-tracking island (client) — crosses the boundary via subpath.\nimport PortfolioAnalytics from '@sonordev/agency-site-kit/portfolio/components/PortfolioAnalytics';\n\nfunction renderSection(section: PortfolioSection, item: PortfolioItemFull, index: number) {\n const key = `${section.sectionType}-${index}`;\n\n switch (section.sectionType) {\n case 'portfolioHero':\n return (\n <HeroSection\n key={key}\n data={section.data as PortfolioHeroData}\n screenshots={item.hero_screenshots ?? undefined}\n liveUrl={item.live_url ?? undefined}\n />\n );\n case 'portfolioChallenges':\n return <ChallengesSection key={key} data={section.data as PortfolioChallengesData} />;\n case 'portfolioStrategy':\n return <StrategySection key={key} data={section.data as PortfolioStrategyData} />;\n case 'portfolioResults':\n return (\n <ResultsSection\n key={key}\n data={section.data as PortfolioResultsData}\n metricsDelta={item.metricsDelta}\n metricsRefreshedAt={item.metrics_last_refreshed_at}\n />\n );\n case 'portfolioTechStack':\n return <TechStackSection key={key} data={section.data as PortfolioTechStackData} />;\n case 'portfolioServices':\n return <ServicesSection key={key} data={section.data as PortfolioServicesData} />;\n case 'portfolioTestimonial':\n return <TestimonialSection key={key} data={section.data as PortfolioTestimonialData} />;\n case 'portfolioGallery':\n return <GallerySection key={key} data={section.data as PortfolioGalleryData} />;\n case 'portfolioVideo':\n return <VideoSection key={key} data={section.data as PortfolioVideoData} />;\n case 'portfolioTeam':\n return <TeamSection key={key} data={section.data as PortfolioTeamData} />;\n case 'portfolioFeatureSpotlight':\n return <FeatureSpotlightSection key={key} data={section.data as PortfolioFeatureSpotlightData} />;\n case 'portfolioBeforeAfter':\n return <BeforeAfterSection key={key} data={section.data as PortfolioBeforeAfterData} />;\n case 'portfolioMetricsTimeline':\n return <MetricsTimelineSection key={key} data={section.data as PortfolioMetricsTimelineData} />;\n case 'portfolioConversionFunnel':\n return <ConversionFunnelSection key={key} data={section.data as PortfolioConversionFunnelData} />;\n case 'portfolioPerformance':\n return <PerformanceSection key={key} data={section.data as any} />;\n case 'portfolioSpeedComparison':\n return <SpeedComparisonSection key={key} data={section.data as any} />;\n case 'portfolioSiteArchitecture':\n return <SiteArchitectureSection key={key} data={sanitizeSiteArchitectureData(section.data) as any} />;\n case 'portfolioDesignSystem':\n return <DesignSystemSection key={key} data={section.data as any} />;\n case 'portfolioDetails':\n return <DetailsSection key={key} data={section.data as PortfolioDetailsData} />;\n case 'portfolioCTA':\n return <CTASection key={key} data={section.data as PortfolioCTAData} />;\n case 'portfolioSeo':\n // SEO section is metadata-only; nothing to render visually.\n return null;\n default:\n return null;\n }\n}\n\n// Canonical display order — ensures consistent rendering regardless of API\n// order. Keys must match PortfolioSectionType exactly (note 'portfolioCTA' —\n// a lowercased 'portfolioCta' key silently fell through to the ?? 99 fallback\n// for years). Unknown/future types sort last via the fallback.\nconst SECTION_ORDER: Record<string, number> = {\n portfolioHero: 0,\n portfolioChallenges: 1,\n portfolioStrategy: 2,\n portfolioResults: 3,\n portfolioPerformance: 4,\n portfolioSpeedComparison: 5,\n portfolioSiteArchitecture: 6,\n portfolioTechStack: 7,\n portfolioDesignSystem: 8,\n portfolioServices: 9,\n portfolioBeforeAfter: 10,\n portfolioTestimonial: 11,\n portfolioMetricsTimeline: 12,\n portfolioConversionFunnel: 13,\n portfolioDetails: 14,\n portfolioSeo: 15,\n portfolioCTA: 16,\n};\n\nexport default function PortfolioPage({ item: rawItem }: PortfolioPageProps) {\n const item = curatePortfolioProof(rawItem);\n\n // Section order: once an agency has deliberately reordered in the\n // dashboard (item.sectionsOrdered), the array order IS the narrative —\n // respect it, with the hero pinned first (it's the LCP shell). Items\n // never reordered keep the canonical order below, so nothing shifts\n // out from under existing case studies.\n const sortedSections = item.sectionsOrdered\n ? [...item.sections].sort(\n (a, b) =>\n (a.sectionType === 'portfolioHero' ? 0 : 1) -\n (b.sectionType === 'portfolioHero' ? 0 : 1),\n )\n : [...item.sections].sort(\n (a, b) => (SECTION_ORDER[a.sectionType] ?? 99) - (SECTION_ORDER[b.sectionType] ?? 99)\n );\n\n return (\n <article className=\"flex w-full min-w-0 max-w-full flex-col overflow-x-clip\">\n {sortedSections.map((section, index) => {\n const rendered = renderSection(section, item, index);\n if (!rendered) return null;\n return (\n <div\n key={`wrapper-${section.sectionType}-${index}`}\n className=\"w-full\"\n data-portfolio-section={section.sectionType}\n >\n {rendered}\n </div>\n );\n })}\n {/* Engagement tracking — childless idle-deferred sibling, never a wrapper */}\n <PortfolioAnalytics slug={item.slug} />\n </article>\n );\n}\n","/**\n * PortfolioSchema — JSON-LD structured data for a case study page.\n *\n * Server component: renders a single <script type=\"application/ld+json\">\n * describing the case study as a schema.org Article (there is no dedicated\n * CaseStudy type; Article is what Google documents for editorial content).\n * Ships zero client JS and adds rich-result eligibility + entity clarity for\n * the agency's portfolio pages.\n *\n * Usage (in the case study page, next to <PortfolioPage />):\n *\n * ```tsx\n * <PortfolioSchema\n * item={item}\n * url={`https://youragency.com/work/${item.slug}`}\n * publisher={{ name: 'Your Agency', url: 'https://youragency.com' }}\n * />\n * ```\n *\n * Only reads fields from the already-sanitized PortfolioItemFull (see\n * getPortfolioItem / sanitize.ts) — never section internals.\n */\n\nimport React from 'react';\nimport type { PortfolioItemFull } from '../../types';\n\nexport interface PortfolioSchemaProps {\n item: PortfolioItemFull;\n /** Canonical URL of this case study page. */\n url: string;\n /** The agency, emitted as author + publisher Organization. */\n publisher?: {\n name: string;\n url?: string;\n /** Absolute URL to a logo image. */\n logo?: string;\n };\n}\n\n/** JSON-LD-safe serialization — escape `<` so `</script>` can never break out. */\nfunction jsonLd(value: unknown): string {\n return JSON.stringify(value).replace(/</g, '\\\\u003c');\n}\n\nexport default function PortfolioSchema({ item, url, publisher }: PortfolioSchemaProps) {\n const headline = item.seo?.metaTitle || item.title;\n const description = item.seo?.metaDescription || item.description;\n\n const keywords = [\n ...(item.seo?.keywords ?? []),\n item.category,\n ...item.services,\n ].filter(Boolean);\n\n const org = publisher\n ? {\n '@type': 'Organization',\n name: publisher.name,\n ...(publisher.url ? { url: publisher.url } : {}),\n ...(publisher.logo\n ? { logo: { '@type': 'ImageObject', url: publisher.logo } }\n : {}),\n }\n : undefined;\n\n const schema = {\n '@context': 'https://schema.org',\n '@type': 'Article',\n headline,\n ...(description ? { description } : {}),\n ...(item.hero_image ? { image: [item.hero_image] } : {}),\n url,\n mainEntityOfPage: { '@type': 'WebPage', '@id': url },\n ...(item.published_at ? { datePublished: item.published_at } : {}),\n ...(item.metrics_last_refreshed_at || item.published_at\n ? { dateModified: item.metrics_last_refreshed_at || item.published_at }\n : {}),\n ...(keywords.length ? { keywords: keywords.join(', ') } : {}),\n ...(org ? { author: org, publisher: org } : {}),\n ...(item.details?.industry ? { about: item.details.industry } : {}),\n };\n\n return (\n <script\n type=\"application/ld+json\"\n dangerouslySetInnerHTML={{ __html: jsonLd(schema) }}\n />\n );\n}\n","// Shared component — no directive on purpose: hook-free, so it renders\n// server-side (zero JS) from PortfolioIndex and stays usable from client\n// code via the /portfolio/client barrel.\nimport React from 'react';\nimport Image from 'next/image';\nimport Link from 'next/link';\nimport type { PortfolioItem } from '../../types';\nimport GlassCard from '@sonordev/agency-site-kit/portfolio/primitives/GlassCard';\nimport { curateHeroKpis } from '../curate-proof';\nimport { formatCategoryLabel, formatServiceTag } from '../format';\n\ninterface PortfolioCardProps {\n item: PortfolioItem;\n className?: string;\n /** Base path for portfolio detail links (default: '/work') */\n basePath?: string;\n}\n\nexport default function PortfolioCard({ item, className = '', basePath = '/work' }: PortfolioCardProps) {\n // Use hero_screenshots desktop → hero_image → trifolio screenshot convention\n const thumbnailSrc =\n item.hero_screenshots?.desktop ||\n item.hero_image ||\n (item.project_id ? `/portfolio-previews/${item.slug}.png` : undefined);\n const kpis = Array.isArray(item.kpis) ? item.kpis : [];\n const services = Array.isArray(item.services) ? item.services : [];\n // Collapse per-category Lighthouse KPIs so a card never reads\n // \"100/100 · 100/100 · 100/100\" — business outcomes lead the preview.\n const kpiPreview = curateHeroKpis(kpis).slice(0, 3);\n const servicePreview = services.slice(0, 3);\n\n return (\n <Link href={`${basePath}/${item.slug}`} className={`block group ${className}`}>\n <GlassCard padding=\"sm\" hover>\n <div className=\"flex flex-col gap-4\">\n {/* Thumbnail */}\n {thumbnailSrc && (\n <div\n className=\"relative w-full overflow-hidden rounded-xl\"\n style={{ aspectRatio: '16/10' }}\n >\n <Image\n src={thumbnailSrc}\n alt={item.hero_image_alt || item.title}\n fill\n sizes=\"(max-width: 768px) 100vw, (max-width: 1024px) 50vw, 33vw\"\n className=\"object-cover object-top transition-transform duration-500 group-hover:scale-105\"\n />\n {/* Category badge */}\n <span\n className=\"absolute top-3 left-3 px-3 py-1 rounded-full text-[11px] uppercase tracking-wider font-semibold\"\n style={{\n background: 'color-mix(in srgb, var(--sk-primary, #6366f1) 80%, transparent)',\n color: '#ffffff',\n backdropFilter: 'blur(8px)',\n }}\n >\n {formatCategoryLabel(item.category)}\n </span>\n </div>\n )}\n\n {/* Text content */}\n <div className=\"flex flex-col gap-2 px-2 pb-2\">\n <h3\n className=\"text-lg font-semibold line-clamp-1\"\n style={{ color: 'var(--sk-text-primary, #ffffff)' }}\n >\n {item.title}\n </h3>\n\n {item.subtitle && (\n <p\n className=\"text-sm line-clamp-2\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n >\n {item.subtitle}\n </p>\n )}\n\n {/* KPI preview */}\n {kpiPreview.length > 0 && (\n <div className=\"flex items-center gap-4 mt-1\">\n {kpiPreview.map((kpi, i) => (\n <div key={i} className=\"flex flex-col\">\n <span\n className=\"text-base font-bold\"\n style={{ color: 'var(--sk-primary, #6366f1)' }}\n >\n {kpi.prefix || ''}\n {kpi.value.toLocaleString()}\n {kpi.suffix}\n </span>\n <span\n className=\"text-[10px] uppercase tracking-wider font-medium\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n >\n {kpi.label}\n </span>\n </div>\n ))}\n </div>\n )}\n\n {/* Services tags */}\n {servicePreview.length > 0 && (\n <div className=\"flex flex-wrap gap-1.5 mt-2\">\n {servicePreview.map((service) => (\n <span\n key={service}\n className=\"px-2.5 py-0.5 rounded-full text-[11px] font-medium\"\n style={{\n background: 'var(--sk-surface, rgba(255,255,255,0.05))',\n color: 'var(--sk-text-secondary, #a1a1aa)',\n border: '1px solid var(--sk-border, rgba(255,255,255,0.1))',\n }}\n >\n {formatServiceTag(service)}\n </span>\n ))}\n {services.length > 3 && (\n <span\n className=\"px-2.5 py-0.5 rounded-full text-[11px] font-medium\"\n style={{\n background: 'var(--sk-surface, rgba(255,255,255,0.05))',\n color: 'var(--sk-text-tertiary, #71717a)',\n }}\n >\n +{item.services.length - 3}\n </span>\n )}\n </div>\n )}\n </div>\n </div>\n </GlassCard>\n </Link>\n );\n}\n","// Shared component — no directive on purpose: hook-free, so it renders\n// server-side (zero JS) from PortfolioIndex and stays usable from client\n// code via the /portfolio/client barrel.\nimport React from 'react';\nimport type { PortfolioItem } from '../../types';\nimport ScrollReveal from '@sonordev/agency-site-kit/portfolio/primitives/ScrollReveal';\nimport PortfolioCard from './PortfolioCard';\n\ninterface PortfolioGridProps {\n items: PortfolioItem[];\n className?: string;\n /** Base path for portfolio detail links (default: '/work') */\n basePath?: string;\n}\n\nexport default function PortfolioGrid({ items, className = '', basePath = '/work' }: PortfolioGridProps) {\n return (\n <ScrollReveal stagger={0.1}>\n <div\n className={`grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 ${className}`}\n style={{\n gap: 'var(--sk-grid-gap, 24px)',\n }}\n >\n {items.map((item) => (\n <PortfolioCard key={item.id} item={item} basePath={basePath} />\n ))}\n </div>\n </ScrollReveal>\n );\n}\n","/**\n * PortfolioIndex — turnkey, server-rendered /work index.\n *\n * Fetches items + categories and renders: category nav (real links —\n * crawlable, no client state), the portfolio grid, pagination, and an\n * intentional empty state. Ships zero component JS.\n *\n * Category filtering is route-based for SEO:\n * /work → all items\n * /work/category/[category] → one category (slug via slugifyCategory)\n * Pagination via ?page= on either route.\n */\n\nimport React from 'react';\nimport {\n getPortfolioCategories,\n getPortfolioItems,\n slugifyCategory,\n} from '../server';\nimport PortfolioGrid from './PortfolioGrid';\n\nexport interface PortfolioIndexProps {\n /** Route prefix for case studies + category links. Defaults to \"/work\". */\n basePath?: string;\n /** Active category SLUG (from the [category] route param). */\n category?: string | null;\n /** 1-based page number (from ?page=). */\n page?: number;\n /** Items per page. Defaults to 12. */\n pageSize?: number;\n /** Optional heading block rendered above the category nav. */\n heading?: React.ReactNode;\n /** Override the empty state. */\n emptyState?: React.ReactNode;\n /** Hide the category nav (e.g. single-category portfolios). */\n showCategories?: boolean;\n className?: string;\n}\n\nfunction CategoryLink({\n href,\n label,\n active,\n}: {\n href: string;\n label: string;\n active: boolean;\n}) {\n return (\n <a\n href={href}\n aria-current={active ? 'page' : undefined}\n className=\"px-4 py-1.5 rounded-full text-sm font-medium transition-colors\"\n style={{\n background: active\n ? 'color-mix(in srgb, var(--sk-primary, #6366f1) 15%, transparent)'\n : 'var(--sk-surface, rgba(255,255,255,0.05))',\n color: active ? 'var(--sk-primary, #6366f1)' : 'var(--sk-text-secondary, #a1a1aa)',\n border: `1px solid ${\n active\n ? 'color-mix(in srgb, var(--sk-primary, #6366f1) 25%, transparent)'\n : 'var(--sk-border, rgba(255,255,255,0.1))'\n }`,\n }}\n >\n {label}\n </a>\n );\n}\n\nfunction PageLink({ href, label, disabled }: { href: string; label: string; disabled: boolean }) {\n if (disabled) {\n return (\n <span\n aria-disabled=\"true\"\n className=\"px-4 py-2 rounded-xl text-sm font-medium opacity-40\"\n style={{\n background: 'var(--sk-surface, rgba(255,255,255,0.05))',\n color: 'var(--sk-text-tertiary, #71717a)',\n border: '1px solid var(--sk-border, rgba(255,255,255,0.1))',\n }}\n >\n {label}\n </span>\n );\n }\n return (\n <a\n href={href}\n className=\"px-4 py-2 rounded-xl text-sm font-medium transition-colors\"\n style={{\n background: 'var(--sk-surface, rgba(255,255,255,0.05))',\n color: 'var(--sk-text-secondary, #a1a1aa)',\n border: '1px solid var(--sk-border, rgba(255,255,255,0.1))',\n }}\n >\n {label}\n </a>\n );\n}\n\nexport default async function PortfolioIndex({\n basePath = '/work',\n category = null,\n page = 1,\n pageSize = 12,\n heading,\n emptyState,\n showCategories = true,\n className = '',\n}: PortfolioIndexProps) {\n const currentPage = Math.max(1, Math.floor(page));\n const categories = await getPortfolioCategories();\n\n // Route param is a slug; the API filter wants the original category value.\n const activeCategory = category\n ? categories.find((c) => slugifyCategory(c) === category) ?? null\n : null;\n\n const { items, total } = await getPortfolioItems({\n category: activeCategory ?? undefined,\n limit: pageSize,\n offset: (currentPage - 1) * pageSize,\n });\n\n const totalPages = Math.max(1, Math.ceil(total / pageSize));\n const pageHref = (p: number) => {\n const base = activeCategory\n ? `${basePath}/category/${slugifyCategory(activeCategory)}`\n : basePath;\n return p <= 1 ? base : `${base}?page=${p}`;\n };\n\n return (\n <section className={`w-full ${className}`}>\n <div className=\"max-w-7xl mx-auto px-6\">\n {heading}\n\n {/* Category nav — real links, crawlable */}\n {showCategories && categories.length > 1 && (\n <nav aria-label=\"Portfolio categories\" className=\"flex flex-wrap gap-2 mb-10\">\n <CategoryLink href={basePath} label=\"All\" active={!activeCategory} />\n {categories.map((c) => (\n <CategoryLink\n key={c}\n href={`${basePath}/category/${slugifyCategory(c)}`}\n label={c}\n active={activeCategory === c}\n />\n ))}\n </nav>\n )}\n\n {items.length > 0 ? (\n <PortfolioGrid items={items} basePath={basePath} />\n ) : (\n emptyState ?? (\n <div\n className=\"flex flex-col items-center gap-3 py-24 rounded-2xl text-center\"\n style={{\n background: 'var(--sk-surface, rgba(255,255,255,0.03))',\n border: '1px dashed var(--sk-border, rgba(255,255,255,0.1))',\n }}\n >\n <span\n className=\"text-lg font-semibold\"\n style={{ color: 'var(--sk-text-primary, #ffffff)' }}\n >\n Case studies coming soon\n </span>\n <span className=\"text-sm\" style={{ color: 'var(--sk-text-tertiary, #71717a)' }}>\n {activeCategory\n ? `Nothing published in ${activeCategory} yet.`\n : \"We're writing up our latest work right now.\"}\n </span>\n </div>\n )\n )}\n\n {/* Pagination */}\n {totalPages > 1 && (\n <nav\n aria-label=\"Portfolio pages\"\n className=\"flex items-center justify-center gap-3 mt-12\"\n >\n <PageLink\n href={pageHref(currentPage - 1)}\n label=\"← Previous\"\n disabled={currentPage <= 1}\n />\n <span className=\"text-sm\" style={{ color: 'var(--sk-text-tertiary, #71717a)' }}>\n Page {currentPage} of {totalPages}\n </span>\n <PageLink\n href={pageHref(currentPage + 1)}\n label=\"Next →\"\n disabled={currentPage >= totalPages}\n />\n </nav>\n )}\n </div>\n </section>\n );\n}\n","/**\n * Loading skeletons for portfolio routes — pure CSS pulse, server-safe,\n * zero JS. Drop into `loading.tsx`:\n *\n * ```tsx\n * export { PortfolioIndexSkeleton as default } from '@sonordev/agency-site-kit/portfolio';\n * ```\n */\n\nimport React from 'react';\n\nconst PULSE_CSS = `\n.sk-skeleton{animation:sk-skeleton-pulse 1.6s ease-in-out infinite;background:var(--sk-surface,rgba(255,255,255,0.06));border:1px solid var(--sk-border,rgba(255,255,255,0.08));border-radius:16px}\n@keyframes sk-skeleton-pulse{0%,100%{opacity:.55}50%{opacity:1}}\n@media (prefers-reduced-motion:reduce){.sk-skeleton{animation:none}}\n`;\n\nfunction SkeletonStyles() {\n return (\n <style href=\"sk-skeletons\" precedence=\"default\">\n {PULSE_CSS}\n </style>\n );\n}\n\n/** Grid of pulsing card placeholders for the /work index. */\nexport function PortfolioIndexSkeleton({ cards = 6 }: { cards?: number }) {\n return (\n <section className=\"w-full py-16\">\n <SkeletonStyles />\n <div className=\"max-w-7xl mx-auto px-6\">\n <div className=\"sk-skeleton h-9 w-56 mb-4\" />\n <div className=\"sk-skeleton h-5 w-80 mb-10\" />\n <div className=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6\">\n {Array.from({ length: cards }).map((_, i) => (\n <div key={i} className=\"flex flex-col gap-3\">\n <div className=\"sk-skeleton w-full\" style={{ aspectRatio: '16/10' }} />\n <div className=\"sk-skeleton h-5 w-3/4\" />\n <div className=\"sk-skeleton h-4 w-1/2\" />\n </div>\n ))}\n </div>\n </div>\n </section>\n );\n}\n\n/** Hero-shaped placeholder for /work/[slug] case study pages. */\nexport function PortfolioPageSkeleton() {\n return (\n <article className=\"w-full\">\n <SkeletonStyles />\n <div className=\"max-w-7xl mx-auto px-6 py-24\">\n <div className=\"grid grid-cols-1 lg:grid-cols-2 gap-12 items-start\">\n <div className=\"flex flex-col gap-5\">\n <div className=\"sk-skeleton h-7 w-32\" style={{ borderRadius: '9999px' }} />\n <div className=\"sk-skeleton h-12 w-full\" />\n <div className=\"sk-skeleton h-12 w-4/5\" />\n <div className=\"sk-skeleton h-5 w-full\" />\n <div className=\"sk-skeleton h-5 w-2/3\" />\n </div>\n <div className=\"sk-skeleton w-full\" style={{ aspectRatio: '11/5' }} />\n </div>\n </div>\n </article>\n );\n}\n","/**\n * @sonordev/agency-site-kit — Type definitions\n *\n * These types mirror the Portal API's cms/types.ts definitions for portfolio\n * content. Keep them in sync when modifying the API response shapes.\n */\n\n// ---------------------------------------------------------------------------\n// Brand Configuration\n// ---------------------------------------------------------------------------\n\nexport interface BrandConfig {\n /** Primary brand color (hex) */\n primary: string;\n /** Secondary/accent color (hex) */\n secondary: string;\n /** Background color (hex) */\n background: string;\n /** Elevated background — cards, modals (hex) */\n backgroundElevated: string;\n /** Surface color — interactive containers (hex) */\n surface: string;\n /** Surface hover state (hex) */\n surfaceHover: string;\n /** Surface border color (hex) */\n surfaceBorder: string;\n /** Primary text color (hex) */\n textPrimary: string;\n /** Secondary text color (hex) */\n textSecondary: string;\n /** Tertiary/muted text color (hex) */\n textTertiary: string;\n /** Heading font family */\n fontHeading: string;\n /** Body font family */\n fontBody: string;\n /** Logo URL (absolute or relative) */\n logoUrl?: string;\n /** Logo alt text */\n logoAlt?: string;\n /** Dark mode overrides — partial, merged with base values */\n darkMode?: Partial<Omit<BrandConfig, 'fontHeading' | 'fontBody' | 'logoUrl' | 'logoAlt' | 'darkMode'>>;\n /** Border radius tokens */\n radius?: {\n sm: string;\n md: string;\n lg: string;\n };\n}\n\n// ---------------------------------------------------------------------------\n// Sanity Image Reference\n// ---------------------------------------------------------------------------\n\nexport interface SanityImageRef {\n _type: 'image';\n asset: { _type: 'reference'; _ref: string };\n hotspot?: { x: number; y: number; height: number; width: number };\n crop?: { top: number; bottom: number; left: number; right: number };\n alt?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Metrics\n// ---------------------------------------------------------------------------\n\nexport type MetricSource = 'measured' | 'estimated';\n\nexport interface PortfolioKPI {\n label: string;\n value: number;\n suffix: string;\n prefix?: string;\n description: string;\n source: MetricSource;\n}\n\nexport interface MetricsDelta {\n metric: string;\n baseline: number;\n current: number;\n delta: number;\n deltaPercent: number;\n direction: 'up' | 'down' | 'flat';\n timespan: string;\n}\n\nexport interface MetricsSnapshot {\n seo: {\n clicks_28d?: number;\n impressions_28d?: number;\n avg_position?: number;\n ctr?: number;\n };\n cwv: {\n lcp_ms?: number;\n inp_ms?: number;\n cls?: number;\n performance_score?: number;\n };\n analytics: {\n sessions_28d?: number;\n page_views_28d?: number;\n conversion_rate?: number;\n bounce_rate?: number;\n };\n pages: {\n total_indexed?: number;\n total_crawled?: number;\n total_pages?: number;\n };\n}\n\n// ---------------------------------------------------------------------------\n// Portfolio Section Data Types\n// ---------------------------------------------------------------------------\n\nexport interface PortfolioHeroData {\n headline: string;\n subheadline: string;\n description: string;\n category: string;\n services: string[];\n liveUrl?: string;\n screenshots: {\n desktop?: string;\n tablet?: string;\n mobile?: string;\n };\n kpis: PortfolioKPI[];\n}\n\nexport interface PortfolioChallengeItem {\n title: string;\n description: string;\n solution: string;\n result?: string;\n icon?: string;\n}\n\nexport interface PortfolioChallengesData {\n items: PortfolioChallengeItem[];\n}\n\nexport interface PortfolioStrategyPhase {\n number: number;\n title: string;\n description: string;\n deliverables?: string[];\n icon?: string;\n timeline?: string;\n}\n\nexport interface PortfolioStrategyData {\n phases: PortfolioStrategyPhase[];\n}\n\nexport interface PortfolioResultItem {\n title: string;\n description: string;\n metric?: {\n value: number;\n suffix: string;\n prefix?: string;\n };\n source: MetricSource;\n icon?: string;\n}\n\nexport interface PortfolioResultsData {\n items: PortfolioResultItem[];\n}\n\nexport interface PortfolioTechItem {\n name: string;\n category: string;\n icon?: string;\n description?: string;\n}\n\nexport interface PortfolioTechStackData {\n technologies: PortfolioTechItem[];\n}\n\nexport interface PortfolioServiceItem {\n title: string;\n description: string;\n features: string[];\n icon?: string;\n}\n\nexport interface PortfolioServicesData {\n items: PortfolioServiceItem[];\n}\n\nexport interface PortfolioTestimonialData {\n quote: string;\n author: string;\n title: string;\n company: string;\n avatar?: SanityImageRef;\n rating?: number;\n}\n\nexport interface PortfolioGalleryImage {\n image: SanityImageRef;\n caption?: string;\n type?: 'screenshot' | 'before' | 'after' | 'design';\n}\n\nexport interface PortfolioGalleryData {\n images: PortfolioGalleryImage[];\n layout?: 'grid' | 'masonry';\n}\n\nexport interface PortfolioVideoData {\n url?: string;\n embedUrl?: string;\n thumbnail: SanityImageRef;\n title: string;\n duration?: string;\n platform?: 'youtube' | 'vimeo' | 'custom';\n}\n\nexport interface PortfolioTeamMember {\n name: string;\n role: string;\n avatar?: SanityImageRef;\n}\n\nexport interface PortfolioTeamData {\n members: PortfolioTeamMember[];\n}\n\nexport interface PortfolioAnnotation {\n x: number;\n y: number;\n label: string;\n description: string;\n icon?: string;\n}\n\nexport interface PortfolioFeatureSpotlightData {\n image: SanityImageRef;\n title: string;\n description: string;\n layout: 'left' | 'right' | 'full';\n annotations: PortfolioAnnotation[];\n}\n\nexport interface PortfolioBeforeAfterData {\n before: SanityImageRef;\n after: SanityImageRef;\n title?: string;\n description?: string;\n defaultPosition?: number;\n}\n\nexport interface PortfolioMetricsDataPoint {\n month: string;\n label: string;\n metrics: {\n traffic?: number;\n conversions?: number;\n revenue?: number;\n rankings?: number;\n };\n}\n\nexport interface PortfolioMetricsTimelineData {\n dataPoints: PortfolioMetricsDataPoint[];\n baselineDate: string;\n title?: string;\n description?: string;\n}\n\nexport interface PortfolioFunnelStage {\n label: string;\n value: number;\n icon?: string;\n color?: string;\n}\n\nexport interface PortfolioConversionFunnelData {\n stages: PortfolioFunnelStage[];\n title?: string;\n description?: string;\n}\n\nexport interface PortfolioDetailsData {\n industry: string;\n location?: string;\n website?: string;\n timeline?: string;\n launchDate?: string;\n budgetRange?: string;\n}\n\nexport interface PortfolioSeoData {\n metaTitle: string;\n metaDescription: string;\n keywords: string[];\n ogImage?: SanityImageRef;\n}\n\nexport interface PortfolioCTAData {\n headline: string;\n description: string;\n buttonText: string;\n buttonUrl: string;\n style?: 'primary' | 'glass';\n}\n\n// ---------------------------------------------------------------------------\n// Portfolio Sections\n// ---------------------------------------------------------------------------\n\nexport const PORTFOLIO_SECTION_TYPES = [\n 'portfolioHero',\n 'portfolioChallenges',\n 'portfolioStrategy',\n 'portfolioResults',\n 'portfolioTechStack',\n 'portfolioServices',\n 'portfolioTestimonial',\n 'portfolioGallery',\n 'portfolioVideo',\n 'portfolioTeam',\n 'portfolioFeatureSpotlight',\n 'portfolioBeforeAfter',\n 'portfolioMetricsTimeline',\n 'portfolioConversionFunnel',\n 'portfolioPerformance',\n 'portfolioSpeedComparison',\n 'portfolioSiteArchitecture',\n 'portfolioDesignSystem',\n 'portfolioDetails',\n 'portfolioSeo',\n 'portfolioCTA',\n] as const;\n\nexport type PortfolioSectionType = (typeof PORTFOLIO_SECTION_TYPES)[number];\n\nexport type PortfolioSectionData =\n | PortfolioHeroData\n | PortfolioChallengesData\n | PortfolioStrategyData\n | PortfolioResultsData\n | PortfolioTechStackData\n | PortfolioServicesData\n | PortfolioTestimonialData\n | PortfolioGalleryData\n | PortfolioVideoData\n | PortfolioTeamData\n | PortfolioFeatureSpotlightData\n | PortfolioBeforeAfterData\n | PortfolioMetricsTimelineData\n | PortfolioConversionFunnelData\n | PortfolioDetailsData\n | PortfolioSeoData\n | PortfolioCTAData;\n\nexport interface PortfolioSectionDataMap {\n portfolioHero: PortfolioHeroData;\n portfolioChallenges: PortfolioChallengesData;\n portfolioStrategy: PortfolioStrategyData;\n portfolioResults: PortfolioResultsData;\n portfolioTechStack: PortfolioTechStackData;\n portfolioServices: PortfolioServicesData;\n portfolioTestimonial: PortfolioTestimonialData;\n portfolioGallery: PortfolioGalleryData;\n portfolioVideo: PortfolioVideoData;\n portfolioTeam: PortfolioTeamData;\n portfolioFeatureSpotlight: PortfolioFeatureSpotlightData;\n portfolioBeforeAfter: PortfolioBeforeAfterData;\n portfolioMetricsTimeline: PortfolioMetricsTimelineData;\n portfolioConversionFunnel: PortfolioConversionFunnelData;\n portfolioDetails: PortfolioDetailsData;\n portfolioSeo: PortfolioSeoData;\n portfolioCTA: PortfolioCTAData;\n}\n\nexport interface PortfolioSection<T extends PortfolioSectionType = PortfolioSectionType> {\n sectionType: T;\n displayName: string;\n data: T extends keyof PortfolioSectionDataMap ? PortfolioSectionDataMap[T] : PortfolioSectionData;\n}\n\n// ---------------------------------------------------------------------------\n// Portfolio Item (mirrors public API response)\n// ---------------------------------------------------------------------------\n\nexport interface PortfolioItem {\n id: string;\n slug: string;\n title: string;\n subtitle: string;\n category: string;\n services: string[];\n description: string;\n hero_image: string;\n hero_image_alt: string;\n live_url: string | null;\n kpis: PortfolioKPI[];\n details: PortfolioDetailsData | null;\n seo: PortfolioSeoData | null;\n featured: boolean;\n order?: number;\n published_at: string | null;\n /** Sonor project ID (present for items generated from a managed project) */\n project_id: string | null;\n hero_screenshots: {\n desktop?: string;\n tablet?: string;\n mobile?: string;\n } | null;\n baseline_metrics: MetricsSnapshot | null;\n current_metrics: MetricsSnapshot | null;\n metrics_last_refreshed_at: string | null;\n}\n\nexport interface PortfolioItemFull extends PortfolioItem {\n sections: PortfolioSection[];\n metricsDelta: MetricsDelta[];\n /**\n * True once the agency deliberately reordered sections in the dashboard.\n * PortfolioPage respects the sections array order when set (hero pinned\n * first); otherwise it applies the canonical order.\n */\n sectionsOrdered?: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// API Responses\n// ---------------------------------------------------------------------------\n\nexport interface PortfolioListResponse {\n items: PortfolioItem[];\n total: number;\n limit: number;\n offset: number;\n}\n\nexport interface PortfolioConfigResponse {\n brand: BrandConfig;\n orgName: string;\n orgSlug: string;\n}\n"]}
|
|
@@ -1,65 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var chunkX76U7VLF_cjs = require('./chunk-X76U7VLF.cjs');
|
|
3
4
|
var jsxRuntime = require('react/jsx-runtime');
|
|
4
5
|
|
|
5
|
-
// src/portfolio/curate-proof.ts
|
|
6
|
-
var LIGHTHOUSE_RE = /lighthouse/i;
|
|
7
|
-
function isLighthouseKpi(kpi) {
|
|
8
|
-
return LIGHTHOUSE_RE.test(kpi.label);
|
|
9
|
-
}
|
|
10
|
-
function isLighthouseRestatement(item) {
|
|
11
|
-
if (!/score/i.test(item.title)) return false;
|
|
12
|
-
if (LIGHTHOUSE_RE.test(`${item.title} ${item.description}`)) return true;
|
|
13
|
-
return item.metric?.suffix === "/100";
|
|
14
|
-
}
|
|
15
|
-
function curateHeroKpis(kpis) {
|
|
16
|
-
const business = kpis.filter((k) => !isLighthouseKpi(k));
|
|
17
|
-
const lighthouse = kpis.filter(isLighthouseKpi);
|
|
18
|
-
if (lighthouse.length <= 1) return kpis;
|
|
19
|
-
const allPerfect = lighthouse.length >= 4 && lighthouse.every((k) => k.value === 100);
|
|
20
|
-
const summary = allPerfect ? {
|
|
21
|
-
...lighthouse[0],
|
|
22
|
-
label: "Lighthouse, all 4 categories",
|
|
23
|
-
description: "Perfect scores across performance, SEO, accessibility, and best practices."
|
|
24
|
-
} : lighthouse.find((k) => /performance/i.test(k.label)) ?? lighthouse[0];
|
|
25
|
-
return [...business, summary];
|
|
26
|
-
}
|
|
27
|
-
function curateResultItems(items, hasPerformanceSection) {
|
|
28
|
-
if (!hasPerformanceSection) return items;
|
|
29
|
-
const curated = items.filter((item) => !isLighthouseRestatement(item));
|
|
30
|
-
return curated.length > 0 ? curated : items.slice(0, 1);
|
|
31
|
-
}
|
|
32
|
-
function gateMetricsDeltas(deltas) {
|
|
33
|
-
if (!deltas?.length) return [];
|
|
34
|
-
return deltas.filter((d) => {
|
|
35
|
-
if (d.direction !== "up") return false;
|
|
36
|
-
if (Math.abs(d.deltaPercent) < 10) return false;
|
|
37
|
-
if (/bounce/i.test(d.metric) && d.current > 70) return false;
|
|
38
|
-
return true;
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
function curatePortfolioProof(item) {
|
|
42
|
-
const hasPerformanceSection = item.sections.some(
|
|
43
|
-
(s) => s.sectionType === "portfolioPerformance"
|
|
44
|
-
);
|
|
45
|
-
return {
|
|
46
|
-
...item,
|
|
47
|
-
sections: item.sections.map((section) => {
|
|
48
|
-
if (section.sectionType === "portfolioHero") {
|
|
49
|
-
const data = section.data;
|
|
50
|
-
return { ...section, data: { ...data, kpis: curateHeroKpis(data.kpis ?? []) } };
|
|
51
|
-
}
|
|
52
|
-
if (section.sectionType === "portfolioResults") {
|
|
53
|
-
const data = section.data;
|
|
54
|
-
return {
|
|
55
|
-
...section,
|
|
56
|
-
data: { ...data, items: curateResultItems(data.items ?? [], hasPerformanceSection) }
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
return section;
|
|
60
|
-
})
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
6
|
var DIRECTION_COLOR = {
|
|
64
7
|
up: "#10b981",
|
|
65
8
|
down: "#ef4444",
|
|
@@ -80,7 +23,7 @@ function formatDate(iso) {
|
|
|
80
23
|
}
|
|
81
24
|
}
|
|
82
25
|
function MetricsProvenance({ deltas, refreshedAt }) {
|
|
83
|
-
const shown = gateMetricsDeltas(deltas);
|
|
26
|
+
const shown = chunkX76U7VLF_cjs.gateMetricsDeltas(deltas);
|
|
84
27
|
if (!shown.length) return null;
|
|
85
28
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
86
29
|
"div",
|
|
@@ -163,9 +106,5 @@ function MetricsProvenance({ deltas, refreshedAt }) {
|
|
|
163
106
|
}
|
|
164
107
|
|
|
165
108
|
exports.MetricsProvenance = MetricsProvenance;
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
exports.curateResultItems = curateResultItems;
|
|
169
|
-
exports.gateMetricsDeltas = gateMetricsDeltas;
|
|
170
|
-
//# sourceMappingURL=chunk-UXUU7FVT.cjs.map
|
|
171
|
-
//# sourceMappingURL=chunk-UXUU7FVT.cjs.map
|
|
109
|
+
//# sourceMappingURL=chunk-6OWFFV6N.cjs.map
|
|
110
|
+
//# sourceMappingURL=chunk-6OWFFV6N.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/portfolio/components/MetricsProvenance.tsx"],"names":["gateMetricsDeltas","jsxs","jsx"],"mappings":"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","file":"chunk-6OWFFV6N.cjs","sourcesContent":["/**\n * MetricsProvenance — \"these numbers are real\" strip for case studies.\n *\n * Server component, zero JS. Renders the live metric deltas the platform\n * computes from GSC/analytics baselines (MetricsDelta[]) plus the refresh\n * timestamp — the trust signal a static agency portfolio can't fake.\n *\n * `direction` semantics come from the API: 'up' ALWAYS means \"improved\"\n * (lower-is-better metrics like Avg Position/LCP are inverted server-side\n * in metrics-baseline.service), so 'up' renders green. The ARROW glyph\n * follows the raw numbers instead — an improved bounce rate shows a green ▼,\n * never a green ▲ next to a value that visibly went down.\n *\n * Deltas pass through gateMetricsDeltas (curate-proof.ts) before display:\n * raw platform deltas carry no editorial judgment, and this strip only\n * exists to flatter — see that module for the policy.\n */\n\nimport React from 'react';\nimport type { MetricsDelta } from '../../types';\nimport { gateMetricsDeltas } from '../curate-proof';\n\ninterface MetricsProvenanceProps {\n deltas: MetricsDelta[];\n /** ISO timestamp of the last live-metrics refresh. */\n refreshedAt?: string | null;\n}\n\nconst DIRECTION_COLOR: Record<MetricsDelta['direction'], string> = {\n up: '#10b981',\n down: '#ef4444',\n flat: 'var(--sk-text-tertiary, #71717a)',\n};\n\nfunction formatValue(v: number): string {\n return Number.isInteger(v)\n ? v.toLocaleString('en-US')\n : v.toLocaleString('en-US', { maximumFractionDigits: 2 });\n}\n\nfunction formatDate(iso: string): string {\n try {\n return new Intl.DateTimeFormat('en-US', {\n year: 'numeric',\n month: 'long',\n day: 'numeric',\n }).format(new Date(iso));\n } catch {\n return iso;\n }\n}\n\nexport default function MetricsProvenance({ deltas, refreshedAt }: MetricsProvenanceProps) {\n const shown = gateMetricsDeltas(deltas);\n if (!shown.length) return null;\n\n return (\n <div\n className=\"mt-10 rounded-2xl overflow-hidden\"\n style={{\n background: 'var(--sk-surface, rgba(255,255,255,0.05))',\n border: '1px solid var(--sk-border, rgba(255,255,255,0.1))',\n }}\n >\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3\">\n {shown.map((d, i) => {\n const color = DIRECTION_COLOR[d.direction] ?? DIRECTION_COLOR.flat;\n // Arrow tracks the raw numbers; color tracks improved/worsened.\n const arrow = d.current === d.baseline ? '—' : d.current > d.baseline ? '▲' : '▼';\n return (\n <div\n key={`${d.metric}-${i}`}\n className=\"flex flex-col gap-1 p-5\"\n style={{ borderBottom: '1px solid var(--sk-border, rgba(255,255,255,0.06))' }}\n >\n <span\n className=\"text-xs font-semibold uppercase tracking-wider\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n >\n {d.metric}\n </span>\n <span className=\"flex items-baseline gap-2\">\n <span\n className=\"text-lg font-bold\"\n style={{ color: 'var(--sk-text-primary, #ffffff)' }}\n >\n {formatValue(d.baseline)} → {formatValue(d.current)}\n </span>\n <span className=\"text-sm font-semibold\" style={{ color }}>\n {arrow} {Math.abs(d.deltaPercent)}%\n </span>\n </span>\n <span className=\"text-xs\" style={{ color: 'var(--sk-text-tertiary, #71717a)' }}>\n {d.timespan}\n </span>\n </div>\n );\n })}\n </div>\n\n <div\n className=\"flex items-center gap-2 px-5 py-3\"\n style={{\n background: 'color-mix(in srgb, #10b981 6%, transparent)',\n color: 'var(--sk-text-secondary, #a1a1aa)',\n }}\n >\n <span\n className=\"inline-block w-2 h-2 rounded-full shrink-0\"\n style={{ background: '#10b981' }}\n />\n <span className=\"text-xs font-medium\">\n Verified from live analytics\n {refreshedAt ? ` · Refreshed ${formatDate(refreshedAt)}` : ''}\n </span>\n </div>\n </div>\n );\n}\n"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { getPortfolioCategories, slugifyCategory, getPortfolioItems, sanitizeSiteArchitectureData } from './chunk-FT2SNEYP.js';
|
|
1
2
|
import { formatCategoryLabel, formatServiceTag } from './chunk-7D4H36UF.js';
|
|
2
|
-
import { curatePortfolioProof, curateHeroKpis } from './chunk-
|
|
3
|
-
import { getPortfolioCategories, slugifyCategory, getPortfolioItems, sanitizeSiteArchitectureData } from './chunk-KXLROAO5.js';
|
|
3
|
+
import { curatePortfolioProof, curateHeroKpis } from './chunk-FM25LB67.js';
|
|
4
4
|
import dynamic from 'next/dynamic';
|
|
5
5
|
import HeroSection from '@sonordev/agency-site-kit/portfolio/sections/HeroSection';
|
|
6
6
|
import ChallengesSection from '@sonordev/agency-site-kit/portfolio/sections/ChallengesSection';
|
|
@@ -503,5 +503,5 @@ var PORTFOLIO_SECTION_TYPES = [
|
|
|
503
503
|
];
|
|
504
504
|
|
|
505
505
|
export { PORTFOLIO_SECTION_TYPES, PortfolioIndex, PortfolioIndexSkeleton, PortfolioPage, PortfolioPageSkeleton, PortfolioSchema };
|
|
506
|
-
//# sourceMappingURL=chunk-
|
|
507
|
-
//# sourceMappingURL=chunk-
|
|
506
|
+
//# sourceMappingURL=chunk-7FFZX7C5.js.map
|
|
507
|
+
//# sourceMappingURL=chunk-7FFZX7C5.js.map
|