@sonordev/agency-site-kit 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{BeforeAfterSection-6QUJOBO2.js → BeforeAfterSection-6BHFLY4Y.js} +6 -6
- package/dist/BeforeAfterSection-6BHFLY4Y.js.map +1 -0
- package/dist/{BeforeAfterSection-DVAWWE4K.cjs → BeforeAfterSection-JTORBR3A.cjs} +6 -6
- package/dist/BeforeAfterSection-JTORBR3A.cjs.map +1 -0
- package/dist/DesignSystemSection-2R5BRBGO.js +172 -0
- package/dist/DesignSystemSection-2R5BRBGO.js.map +1 -0
- package/dist/DesignSystemSection-KXIQXITF.cjs +174 -0
- package/dist/DesignSystemSection-KXIQXITF.cjs.map +1 -0
- package/dist/{DetailsSection-FB763FS7.js → DetailsSection-A6PZQUQL.js} +14 -5
- package/dist/DetailsSection-A6PZQUQL.js.map +1 -0
- package/dist/{DetailsSection-OACJFGH7.cjs → DetailsSection-TTUZAPZZ.cjs} +14 -5
- package/dist/DetailsSection-TTUZAPZZ.cjs.map +1 -0
- package/dist/PerformanceSection-24TVVFZA.cjs +356 -0
- package/dist/PerformanceSection-24TVVFZA.cjs.map +1 -0
- package/dist/PerformanceSection-MGCEIXDX.js +351 -0
- package/dist/PerformanceSection-MGCEIXDX.js.map +1 -0
- package/dist/SiteArchitectureSection-EE6VQSXM.cjs +349 -0
- package/dist/SiteArchitectureSection-EE6VQSXM.cjs.map +1 -0
- package/dist/SiteArchitectureSection-PBBRTARV.js +344 -0
- package/dist/SiteArchitectureSection-PBBRTARV.js.map +1 -0
- package/dist/SpeedComparisonSection-EZKFQVGW.cjs +174 -0
- package/dist/SpeedComparisonSection-EZKFQVGW.cjs.map +1 -0
- package/dist/SpeedComparisonSection-Y3K7OFZQ.js +172 -0
- package/dist/SpeedComparisonSection-Y3K7OFZQ.js.map +1 -0
- package/dist/{StrategySection-3ED3QW4R.cjs → StrategySection-CJ7Y6OFQ.cjs} +18 -24
- package/dist/StrategySection-CJ7Y6OFQ.cjs.map +1 -0
- package/dist/{StrategySection-VUWMIYYP.js → StrategySection-DI5RSCJU.js} +18 -24
- package/dist/StrategySection-DI5RSCJU.js.map +1 -0
- package/dist/TechStackSection-2AQ7RGY3.js +93 -0
- package/dist/TechStackSection-2AQ7RGY3.js.map +1 -0
- package/dist/TechStackSection-VTNNZR5V.cjs +95 -0
- package/dist/TechStackSection-VTNNZR5V.cjs.map +1 -0
- package/dist/{chunk-XMC4DN6G.js → chunk-APG2QSMB.js} +8 -8
- package/dist/chunk-APG2QSMB.js.map +1 -0
- package/dist/chunk-JTI3F3QY.cjs +619 -0
- package/dist/chunk-JTI3F3QY.cjs.map +1 -0
- package/dist/{chunk-NAS4K5UR.cjs → chunk-OA5ZM4OA.cjs} +8 -8
- package/dist/chunk-OA5ZM4OA.cjs.map +1 -0
- package/dist/{chunk-QIC6JFFD.js → chunk-OMOF4VR5.js} +14 -14
- package/dist/chunk-OMOF4VR5.js.map +1 -0
- package/dist/chunk-RLVW7WEK.js +612 -0
- package/dist/chunk-RLVW7WEK.js.map +1 -0
- package/dist/{chunk-5FKOLIV6.cjs → chunk-XM2QD3AK.cjs} +14 -14
- package/dist/chunk-XM2QD3AK.cjs.map +1 -0
- package/dist/index.cjs +13 -13
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +3 -3
- package/dist/layout/index.cjs +2 -2
- package/dist/layout/index.d.cts +1 -1
- package/dist/layout/index.d.ts +1 -1
- package/dist/layout/index.js +1 -1
- package/dist/portfolio/client.cjs +3 -3
- package/dist/portfolio/client.d.cts +7 -3
- package/dist/portfolio/client.d.ts +7 -3
- package/dist/portfolio/client.js +1 -1
- package/dist/portfolio/index.cjs +6 -6
- package/dist/portfolio/index.d.cts +2 -2
- package/dist/portfolio/index.d.ts +2 -2
- package/dist/portfolio/index.js +2 -2
- package/dist/portfolio/sections.d.cts +1 -1
- package/dist/portfolio/sections.d.ts +1 -1
- package/dist/portfolio/server.cjs +1 -0
- package/dist/portfolio/server.cjs.map +1 -1
- package/dist/portfolio/server.d.cts +1 -1
- package/dist/portfolio/server.d.ts +1 -1
- package/dist/portfolio/server.js +1 -0
- package/dist/portfolio/server.js.map +1 -1
- package/dist/{types-BMUhBhWx.d.cts → types-DL4t_Cfa.d.cts} +3 -1
- package/dist/{types-BMUhBhWx.d.ts → types-DL4t_Cfa.d.ts} +3 -1
- package/package.json +1 -1
- package/dist/BeforeAfterSection-6QUJOBO2.js.map +0 -1
- package/dist/BeforeAfterSection-DVAWWE4K.cjs.map +0 -1
- package/dist/DetailsSection-FB763FS7.js.map +0 -1
- package/dist/DetailsSection-OACJFGH7.cjs.map +0 -1
- package/dist/StrategySection-3ED3QW4R.cjs.map +0 -1
- package/dist/StrategySection-VUWMIYYP.js.map +0 -1
- package/dist/TechStackSection-OCUYG4XT.js +0 -90
- package/dist/TechStackSection-OCUYG4XT.js.map +0 -1
- package/dist/TechStackSection-VKJK4KQB.cjs +0 -91
- package/dist/TechStackSection-VKJK4KQB.cjs.map +0 -1
- package/dist/chunk-2VNNFAG6.js +0 -415
- package/dist/chunk-2VNNFAG6.js.map +0 -1
- package/dist/chunk-5FKOLIV6.cjs.map +0 -1
- package/dist/chunk-NAS4K5UR.cjs.map +0 -1
- package/dist/chunk-QIC6JFFD.js.map +0 -1
- package/dist/chunk-TAPNXT7X.cjs +0 -422
- package/dist/chunk-TAPNXT7X.cjs.map +0 -1
- package/dist/chunk-XMC4DN6G.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/portfolio/server.ts"],"names":["apiGet"],"mappings":"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","file":"server.cjs","sourcesContent":["/**\n * @sonordev/agency-site-kit — Portfolio server-side data fetching\n *\n * RSC (React Server Component) data layer for portfolio pages.\n * All functions are async and designed for use in Next.js App Router\n * server components, generateMetadata(), and generateStaticParams().\n *\n * This file is server-only — no 'use client' directive.\n */\n\nimport { apiGet } from '../shared/api';\nimport type {\n BrandConfig,\n MetricsDelta,\n PortfolioConfigResponse,\n PortfolioItemFull,\n PortfolioListResponse,\n PortfolioSeoData,\n PortfolioSection,\n} from '../types';\n\nconst LOG_PREFIX = '[@sonordev/agency-site-kit/portfolio]';\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Builds a query string from an object, omitting undefined/null values.\n */\nfunction buildQuery(params: Record<string, string | number | boolean | undefined | null>): string {\n const entries = Object.entries(params).filter(\n (entry): entry is [string, string | number | boolean] => entry[1] != null,\n );\n if (entries.length === 0) return '';\n const qs = new URLSearchParams(\n entries.map(([k, v]) => [k, String(v)]),\n ).toString();\n return `?${qs}`;\n}\n\n// ---------------------------------------------------------------------------\n// Public API — List items\n// ---------------------------------------------------------------------------\n\n/**\n * Fetches published portfolio items from the public API.\n * Cached via Next.js ISR (revalidate: 3600).\n */\nexport async function getPortfolioItems(options?: {\n category?: string;\n featured?: boolean;\n limit?: number;\n offset?: number;\n}): Promise<PortfolioListResponse> {\n const fallback: PortfolioListResponse = { items: [], total: 0, limit: 0, offset: 0 };\n\n const query = buildQuery({\n category: options?.category,\n featured: options?.featured,\n limit: options?.limit,\n offset: options?.offset,\n });\n\n const data = await apiGet<PortfolioListResponse>(\n `/api/public/portfolio/items${query}`,\n );\n\n if (!data) {\n console.warn(`${LOG_PREFIX} Failed to fetch portfolio items.`);\n return fallback;\n }\n\n return data;\n}\n\n// ---------------------------------------------------------------------------\n// Public API — Single item\n// ---------------------------------------------------------------------------\n\n/** Shape returned by the single-item API endpoint. */\ninterface PortfolioItemApiResponse {\n found: boolean;\n item: {\n id: string;\n slug: string;\n title: string;\n subtitle: string;\n category: string;\n services: string[];\n description: string;\n hero_image: string;\n hero_image_alt: string;\n live_url: string | null;\n kpis: PortfolioItemFull['kpis'];\n details: PortfolioItemFull['details'];\n seo: PortfolioItemFull['seo'];\n featured: boolean;\n order?: number;\n published_at: string | null;\n hero_screenshots: PortfolioItemFull['hero_screenshots'];\n baseline_metrics: PortfolioItemFull['baseline_metrics'];\n current_metrics: PortfolioItemFull['current_metrics'];\n metrics_last_refreshed_at: string | null;\n sections: PortfolioSection[];\n metricsDelta: MetricsDelta[];\n };\n}\n\n/**\n * Fetches a single portfolio item by slug, including full Sanity sections\n * and live metrics delta. This is the main data function for portfolio detail pages.\n *\n * Returns null when the item is not found or the API call fails.\n */\nexport async function getPortfolioItem(slug: string): Promise<PortfolioItemFull | null> {\n if (!slug) {\n console.warn(`${LOG_PREFIX} getPortfolioItem called with empty slug.`);\n return null;\n }\n\n const data = await apiGet<PortfolioItemApiResponse>(\n `/api/public/portfolio/items/${encodeURIComponent(slug)}`,\n );\n\n if (!data || !data.found || !data.item) {\n console.warn(`${LOG_PREFIX} Portfolio item not found for slug: \"${slug}\".`);\n return null;\n }\n\n // Map the API response to the PortfolioItemFull type\n const { item } = data;\n return {\n id: item.id,\n slug: item.slug,\n title: item.title,\n subtitle: item.subtitle,\n category: item.category,\n services: item.services,\n description: item.description,\n hero_image: item.hero_image,\n hero_image_alt: item.hero_image_alt,\n live_url: item.live_url,\n kpis: item.kpis,\n details: item.details,\n seo: item.seo,\n featured: item.featured,\n order: item.order,\n published_at: item.published_at,\n hero_screenshots: item.hero_screenshots,\n baseline_metrics: item.baseline_metrics,\n current_metrics: item.current_metrics,\n metrics_last_refreshed_at: item.metrics_last_refreshed_at,\n sections: item.sections ?? [],\n metricsDelta: item.metricsDelta ?? [],\n };\n}\n\n// ---------------------------------------------------------------------------\n// Public API — Categories\n// ---------------------------------------------------------------------------\n\n/**\n * Fetches available portfolio categories.\n * Returns an empty array on failure.\n */\nexport async function getPortfolioCategories(): Promise<string[]> {\n const data = await apiGet<string[]>('/api/public/portfolio/categories');\n\n if (!data) {\n console.warn(`${LOG_PREFIX} Failed to fetch portfolio categories.`);\n return [];\n }\n\n return data;\n}\n\n// ---------------------------------------------------------------------------\n// Public API — Brand config\n// ---------------------------------------------------------------------------\n\n/**\n * Fetches brand configuration for the agency (colors, fonts, logo).\n * Returns sensible defaults when the API call fails.\n */\nexport async function getPortfolioBrandConfig(): Promise<BrandConfig> {\n const fallback: BrandConfig = {\n primary: '#6366f1',\n secondary: '#8b5cf6',\n background: '#0a0a0f',\n backgroundElevated: '#12121a',\n surface: '#1a1a2e',\n surfaceHover: '#22223a',\n surfaceBorder: '#2a2a3e',\n textPrimary: '#f0f0f5',\n textSecondary: '#a0a0b5',\n textTertiary: '#6b6b80',\n fontHeading: 'Inter, system-ui, sans-serif',\n fontBody: 'Inter, system-ui, sans-serif',\n };\n\n const data = await apiGet<PortfolioConfigResponse>(\n '/api/public/portfolio/config',\n );\n\n if (!data || !data.brand) {\n console.warn(`${LOG_PREFIX} Failed to fetch brand config, using defaults.`);\n return fallback;\n }\n\n return data.brand;\n}\n\n// ---------------------------------------------------------------------------\n// Next.js Metadata helper\n// ---------------------------------------------------------------------------\n\n/**\n * Generates Next.js Metadata for a portfolio item page.\n * For use in generateMetadata() in the App Router.\n *\n * Returns an empty object when the item cannot be found, which is safe\n * to spread into the metadata return value.\n */\nexport async function generatePortfolioMetadata(slug: string): Promise<{\n title?: string;\n description?: string;\n keywords?: string[];\n openGraph?: {\n title?: string;\n description?: string;\n images?: string[];\n type?: string;\n };\n}> {\n const item = await getPortfolioItem(slug);\n\n if (!item) {\n return {};\n }\n\n // Prefer dedicated SEO data if available, fall back to item fields\n const seo: PortfolioSeoData | null = item.seo;\n const title = seo?.metaTitle || item.title;\n const description = seo?.metaDescription || item.description;\n const keywords = seo?.keywords ?? [];\n\n // Collect OG images: prefer SEO og image, then hero image\n const ogImages: string[] = [];\n if (item.hero_image) {\n ogImages.push(item.hero_image);\n }\n\n return {\n title,\n description,\n keywords: keywords.length > 0 ? keywords : undefined,\n openGraph: {\n title,\n description,\n images: ogImages.length > 0 ? ogImages : undefined,\n type: 'article',\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Next.js Static Params helper\n// ---------------------------------------------------------------------------\n\n/**\n * Generates static params for portfolio pages.\n * For use in generateStaticParams() in the App Router.\n *\n * Fetches all published portfolio items and returns their slugs.\n */\nexport async function generatePortfolioStaticParams(): Promise<Array<{ slug: string }>> {\n // Fetch a large batch to cover all published items\n const data = await getPortfolioItems({ limit: 500, offset: 0 });\n\n return data.items.map((item) => ({ slug: item.slug }));\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/portfolio/server.ts"],"names":["apiGet"],"mappings":"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","file":"server.cjs","sourcesContent":["/**\n * @sonordev/agency-site-kit — Portfolio server-side data fetching\n *\n * RSC (React Server Component) data layer for portfolio pages.\n * All functions are async and designed for use in Next.js App Router\n * server components, generateMetadata(), and generateStaticParams().\n *\n * This file is server-only — no 'use client' directive.\n */\n\nimport { apiGet } from '../shared/api';\nimport type {\n BrandConfig,\n MetricsDelta,\n PortfolioConfigResponse,\n PortfolioItemFull,\n PortfolioListResponse,\n PortfolioSeoData,\n PortfolioSection,\n} from '../types';\n\nconst LOG_PREFIX = '[@sonordev/agency-site-kit/portfolio]';\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Builds a query string from an object, omitting undefined/null values.\n */\nfunction buildQuery(params: Record<string, string | number | boolean | undefined | null>): string {\n const entries = Object.entries(params).filter(\n (entry): entry is [string, string | number | boolean] => entry[1] != null,\n );\n if (entries.length === 0) return '';\n const qs = new URLSearchParams(\n entries.map(([k, v]) => [k, String(v)]),\n ).toString();\n return `?${qs}`;\n}\n\n// ---------------------------------------------------------------------------\n// Public API — List items\n// ---------------------------------------------------------------------------\n\n/**\n * Fetches published portfolio items from the public API.\n * Cached via Next.js ISR (revalidate: 3600).\n */\nexport async function getPortfolioItems(options?: {\n category?: string;\n featured?: boolean;\n limit?: number;\n offset?: number;\n}): Promise<PortfolioListResponse> {\n const fallback: PortfolioListResponse = { items: [], total: 0, limit: 0, offset: 0 };\n\n const query = buildQuery({\n category: options?.category,\n featured: options?.featured,\n limit: options?.limit,\n offset: options?.offset,\n });\n\n const data = await apiGet<PortfolioListResponse>(\n `/api/public/portfolio/items${query}`,\n );\n\n if (!data) {\n console.warn(`${LOG_PREFIX} Failed to fetch portfolio items.`);\n return fallback;\n }\n\n return data;\n}\n\n// ---------------------------------------------------------------------------\n// Public API — Single item\n// ---------------------------------------------------------------------------\n\n/** Shape returned by the single-item API endpoint. */\ninterface PortfolioItemApiResponse {\n found: boolean;\n item: {\n id: string;\n slug: string;\n title: string;\n subtitle: string;\n category: string;\n services: string[];\n description: string;\n hero_image: string;\n hero_image_alt: string;\n live_url: string | null;\n project_id: string | null;\n kpis: PortfolioItemFull['kpis'];\n details: PortfolioItemFull['details'];\n seo: PortfolioItemFull['seo'];\n featured: boolean;\n order?: number;\n published_at: string | null;\n hero_screenshots: PortfolioItemFull['hero_screenshots'];\n baseline_metrics: PortfolioItemFull['baseline_metrics'];\n current_metrics: PortfolioItemFull['current_metrics'];\n metrics_last_refreshed_at: string | null;\n sections: PortfolioSection[];\n metricsDelta: MetricsDelta[];\n };\n}\n\n/**\n * Fetches a single portfolio item by slug, including full Sanity sections\n * and live metrics delta. This is the main data function for portfolio detail pages.\n *\n * Returns null when the item is not found or the API call fails.\n */\nexport async function getPortfolioItem(slug: string): Promise<PortfolioItemFull | null> {\n if (!slug) {\n console.warn(`${LOG_PREFIX} getPortfolioItem called with empty slug.`);\n return null;\n }\n\n const data = await apiGet<PortfolioItemApiResponse>(\n `/api/public/portfolio/items/${encodeURIComponent(slug)}`,\n );\n\n if (!data || !data.found || !data.item) {\n console.warn(`${LOG_PREFIX} Portfolio item not found for slug: \"${slug}\".`);\n return null;\n }\n\n // Map the API response to the PortfolioItemFull type\n const { item } = data;\n return {\n id: item.id,\n slug: item.slug,\n title: item.title,\n subtitle: item.subtitle,\n category: item.category,\n services: item.services,\n description: item.description,\n hero_image: item.hero_image,\n hero_image_alt: item.hero_image_alt,\n live_url: item.live_url,\n project_id: item.project_id ?? null,\n kpis: item.kpis,\n details: item.details,\n seo: item.seo,\n featured: item.featured,\n order: item.order,\n published_at: item.published_at,\n hero_screenshots: item.hero_screenshots,\n baseline_metrics: item.baseline_metrics,\n current_metrics: item.current_metrics,\n metrics_last_refreshed_at: item.metrics_last_refreshed_at,\n sections: item.sections ?? [],\n metricsDelta: item.metricsDelta ?? [],\n };\n}\n\n// ---------------------------------------------------------------------------\n// Public API — Categories\n// ---------------------------------------------------------------------------\n\n/**\n * Fetches available portfolio categories.\n * Returns an empty array on failure.\n */\nexport async function getPortfolioCategories(): Promise<string[]> {\n const data = await apiGet<string[]>('/api/public/portfolio/categories');\n\n if (!data) {\n console.warn(`${LOG_PREFIX} Failed to fetch portfolio categories.`);\n return [];\n }\n\n return data;\n}\n\n// ---------------------------------------------------------------------------\n// Public API — Brand config\n// ---------------------------------------------------------------------------\n\n/**\n * Fetches brand configuration for the agency (colors, fonts, logo).\n * Returns sensible defaults when the API call fails.\n */\nexport async function getPortfolioBrandConfig(): Promise<BrandConfig> {\n const fallback: BrandConfig = {\n primary: '#6366f1',\n secondary: '#8b5cf6',\n background: '#0a0a0f',\n backgroundElevated: '#12121a',\n surface: '#1a1a2e',\n surfaceHover: '#22223a',\n surfaceBorder: '#2a2a3e',\n textPrimary: '#f0f0f5',\n textSecondary: '#a0a0b5',\n textTertiary: '#6b6b80',\n fontHeading: 'Inter, system-ui, sans-serif',\n fontBody: 'Inter, system-ui, sans-serif',\n };\n\n const data = await apiGet<PortfolioConfigResponse>(\n '/api/public/portfolio/config',\n );\n\n if (!data || !data.brand) {\n console.warn(`${LOG_PREFIX} Failed to fetch brand config, using defaults.`);\n return fallback;\n }\n\n return data.brand;\n}\n\n// ---------------------------------------------------------------------------\n// Next.js Metadata helper\n// ---------------------------------------------------------------------------\n\n/**\n * Generates Next.js Metadata for a portfolio item page.\n * For use in generateMetadata() in the App Router.\n *\n * Returns an empty object when the item cannot be found, which is safe\n * to spread into the metadata return value.\n */\nexport async function generatePortfolioMetadata(slug: string): Promise<{\n title?: string;\n description?: string;\n keywords?: string[];\n openGraph?: {\n title?: string;\n description?: string;\n images?: string[];\n type?: string;\n };\n}> {\n const item = await getPortfolioItem(slug);\n\n if (!item) {\n return {};\n }\n\n // Prefer dedicated SEO data if available, fall back to item fields\n const seo: PortfolioSeoData | null = item.seo;\n const title = seo?.metaTitle || item.title;\n const description = seo?.metaDescription || item.description;\n const keywords = seo?.keywords ?? [];\n\n // Collect OG images: prefer SEO og image, then hero image\n const ogImages: string[] = [];\n if (item.hero_image) {\n ogImages.push(item.hero_image);\n }\n\n return {\n title,\n description,\n keywords: keywords.length > 0 ? keywords : undefined,\n openGraph: {\n title,\n description,\n images: ogImages.length > 0 ? ogImages : undefined,\n type: 'article',\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Next.js Static Params helper\n// ---------------------------------------------------------------------------\n\n/**\n * Generates static params for portfolio pages.\n * For use in generateStaticParams() in the App Router.\n *\n * Fetches all published portfolio items and returns their slugs.\n */\nexport async function generatePortfolioStaticParams(): Promise<Array<{ slug: string }>> {\n // Fetch a large batch to cover all published items\n const data = await getPortfolioItems({ limit: 500, offset: 0 });\n\n return data.items.map((item) => ({ slug: item.slug }));\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { B as BrandConfig, P as PortfolioItemFull, a as PortfolioListResponse } from '../types-
|
|
1
|
+
import { B as BrandConfig, P as PortfolioItemFull, a as PortfolioListResponse } from '../types-DL4t_Cfa.cjs';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @sonordev/agency-site-kit — Portfolio server-side data fetching
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { B as BrandConfig, P as PortfolioItemFull, a as PortfolioListResponse } from '../types-
|
|
1
|
+
import { B as BrandConfig, P as PortfolioItemFull, a as PortfolioListResponse } from '../types-DL4t_Cfa.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @sonordev/agency-site-kit — Portfolio server-side data fetching
|
package/dist/portfolio/server.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/portfolio/server.ts"],"names":[],"mappings":"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","file":"server.js","sourcesContent":["/**\n * @sonordev/agency-site-kit — Portfolio server-side data fetching\n *\n * RSC (React Server Component) data layer for portfolio pages.\n * All functions are async and designed for use in Next.js App Router\n * server components, generateMetadata(), and generateStaticParams().\n *\n * This file is server-only — no 'use client' directive.\n */\n\nimport { apiGet } from '../shared/api';\nimport type {\n BrandConfig,\n MetricsDelta,\n PortfolioConfigResponse,\n PortfolioItemFull,\n PortfolioListResponse,\n PortfolioSeoData,\n PortfolioSection,\n} from '../types';\n\nconst LOG_PREFIX = '[@sonordev/agency-site-kit/portfolio]';\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Builds a query string from an object, omitting undefined/null values.\n */\nfunction buildQuery(params: Record<string, string | number | boolean | undefined | null>): string {\n const entries = Object.entries(params).filter(\n (entry): entry is [string, string | number | boolean] => entry[1] != null,\n );\n if (entries.length === 0) return '';\n const qs = new URLSearchParams(\n entries.map(([k, v]) => [k, String(v)]),\n ).toString();\n return `?${qs}`;\n}\n\n// ---------------------------------------------------------------------------\n// Public API — List items\n// ---------------------------------------------------------------------------\n\n/**\n * Fetches published portfolio items from the public API.\n * Cached via Next.js ISR (revalidate: 3600).\n */\nexport async function getPortfolioItems(options?: {\n category?: string;\n featured?: boolean;\n limit?: number;\n offset?: number;\n}): Promise<PortfolioListResponse> {\n const fallback: PortfolioListResponse = { items: [], total: 0, limit: 0, offset: 0 };\n\n const query = buildQuery({\n category: options?.category,\n featured: options?.featured,\n limit: options?.limit,\n offset: options?.offset,\n });\n\n const data = await apiGet<PortfolioListResponse>(\n `/api/public/portfolio/items${query}`,\n );\n\n if (!data) {\n console.warn(`${LOG_PREFIX} Failed to fetch portfolio items.`);\n return fallback;\n }\n\n return data;\n}\n\n// ---------------------------------------------------------------------------\n// Public API — Single item\n// ---------------------------------------------------------------------------\n\n/** Shape returned by the single-item API endpoint. */\ninterface PortfolioItemApiResponse {\n found: boolean;\n item: {\n id: string;\n slug: string;\n title: string;\n subtitle: string;\n category: string;\n services: string[];\n description: string;\n hero_image: string;\n hero_image_alt: string;\n live_url: string | null;\n kpis: PortfolioItemFull['kpis'];\n details: PortfolioItemFull['details'];\n seo: PortfolioItemFull['seo'];\n featured: boolean;\n order?: number;\n published_at: string | null;\n hero_screenshots: PortfolioItemFull['hero_screenshots'];\n baseline_metrics: PortfolioItemFull['baseline_metrics'];\n current_metrics: PortfolioItemFull['current_metrics'];\n metrics_last_refreshed_at: string | null;\n sections: PortfolioSection[];\n metricsDelta: MetricsDelta[];\n };\n}\n\n/**\n * Fetches a single portfolio item by slug, including full Sanity sections\n * and live metrics delta. This is the main data function for portfolio detail pages.\n *\n * Returns null when the item is not found or the API call fails.\n */\nexport async function getPortfolioItem(slug: string): Promise<PortfolioItemFull | null> {\n if (!slug) {\n console.warn(`${LOG_PREFIX} getPortfolioItem called with empty slug.`);\n return null;\n }\n\n const data = await apiGet<PortfolioItemApiResponse>(\n `/api/public/portfolio/items/${encodeURIComponent(slug)}`,\n );\n\n if (!data || !data.found || !data.item) {\n console.warn(`${LOG_PREFIX} Portfolio item not found for slug: \"${slug}\".`);\n return null;\n }\n\n // Map the API response to the PortfolioItemFull type\n const { item } = data;\n return {\n id: item.id,\n slug: item.slug,\n title: item.title,\n subtitle: item.subtitle,\n category: item.category,\n services: item.services,\n description: item.description,\n hero_image: item.hero_image,\n hero_image_alt: item.hero_image_alt,\n live_url: item.live_url,\n kpis: item.kpis,\n details: item.details,\n seo: item.seo,\n featured: item.featured,\n order: item.order,\n published_at: item.published_at,\n hero_screenshots: item.hero_screenshots,\n baseline_metrics: item.baseline_metrics,\n current_metrics: item.current_metrics,\n metrics_last_refreshed_at: item.metrics_last_refreshed_at,\n sections: item.sections ?? [],\n metricsDelta: item.metricsDelta ?? [],\n };\n}\n\n// ---------------------------------------------------------------------------\n// Public API — Categories\n// ---------------------------------------------------------------------------\n\n/**\n * Fetches available portfolio categories.\n * Returns an empty array on failure.\n */\nexport async function getPortfolioCategories(): Promise<string[]> {\n const data = await apiGet<string[]>('/api/public/portfolio/categories');\n\n if (!data) {\n console.warn(`${LOG_PREFIX} Failed to fetch portfolio categories.`);\n return [];\n }\n\n return data;\n}\n\n// ---------------------------------------------------------------------------\n// Public API — Brand config\n// ---------------------------------------------------------------------------\n\n/**\n * Fetches brand configuration for the agency (colors, fonts, logo).\n * Returns sensible defaults when the API call fails.\n */\nexport async function getPortfolioBrandConfig(): Promise<BrandConfig> {\n const fallback: BrandConfig = {\n primary: '#6366f1',\n secondary: '#8b5cf6',\n background: '#0a0a0f',\n backgroundElevated: '#12121a',\n surface: '#1a1a2e',\n surfaceHover: '#22223a',\n surfaceBorder: '#2a2a3e',\n textPrimary: '#f0f0f5',\n textSecondary: '#a0a0b5',\n textTertiary: '#6b6b80',\n fontHeading: 'Inter, system-ui, sans-serif',\n fontBody: 'Inter, system-ui, sans-serif',\n };\n\n const data = await apiGet<PortfolioConfigResponse>(\n '/api/public/portfolio/config',\n );\n\n if (!data || !data.brand) {\n console.warn(`${LOG_PREFIX} Failed to fetch brand config, using defaults.`);\n return fallback;\n }\n\n return data.brand;\n}\n\n// ---------------------------------------------------------------------------\n// Next.js Metadata helper\n// ---------------------------------------------------------------------------\n\n/**\n * Generates Next.js Metadata for a portfolio item page.\n * For use in generateMetadata() in the App Router.\n *\n * Returns an empty object when the item cannot be found, which is safe\n * to spread into the metadata return value.\n */\nexport async function generatePortfolioMetadata(slug: string): Promise<{\n title?: string;\n description?: string;\n keywords?: string[];\n openGraph?: {\n title?: string;\n description?: string;\n images?: string[];\n type?: string;\n };\n}> {\n const item = await getPortfolioItem(slug);\n\n if (!item) {\n return {};\n }\n\n // Prefer dedicated SEO data if available, fall back to item fields\n const seo: PortfolioSeoData | null = item.seo;\n const title = seo?.metaTitle || item.title;\n const description = seo?.metaDescription || item.description;\n const keywords = seo?.keywords ?? [];\n\n // Collect OG images: prefer SEO og image, then hero image\n const ogImages: string[] = [];\n if (item.hero_image) {\n ogImages.push(item.hero_image);\n }\n\n return {\n title,\n description,\n keywords: keywords.length > 0 ? keywords : undefined,\n openGraph: {\n title,\n description,\n images: ogImages.length > 0 ? ogImages : undefined,\n type: 'article',\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Next.js Static Params helper\n// ---------------------------------------------------------------------------\n\n/**\n * Generates static params for portfolio pages.\n * For use in generateStaticParams() in the App Router.\n *\n * Fetches all published portfolio items and returns their slugs.\n */\nexport async function generatePortfolioStaticParams(): Promise<Array<{ slug: string }>> {\n // Fetch a large batch to cover all published items\n const data = await getPortfolioItems({ limit: 500, offset: 0 });\n\n return data.items.map((item) => ({ slug: item.slug }));\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/portfolio/server.ts"],"names":[],"mappings":"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","file":"server.js","sourcesContent":["/**\n * @sonordev/agency-site-kit — Portfolio server-side data fetching\n *\n * RSC (React Server Component) data layer for portfolio pages.\n * All functions are async and designed for use in Next.js App Router\n * server components, generateMetadata(), and generateStaticParams().\n *\n * This file is server-only — no 'use client' directive.\n */\n\nimport { apiGet } from '../shared/api';\nimport type {\n BrandConfig,\n MetricsDelta,\n PortfolioConfigResponse,\n PortfolioItemFull,\n PortfolioListResponse,\n PortfolioSeoData,\n PortfolioSection,\n} from '../types';\n\nconst LOG_PREFIX = '[@sonordev/agency-site-kit/portfolio]';\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Builds a query string from an object, omitting undefined/null values.\n */\nfunction buildQuery(params: Record<string, string | number | boolean | undefined | null>): string {\n const entries = Object.entries(params).filter(\n (entry): entry is [string, string | number | boolean] => entry[1] != null,\n );\n if (entries.length === 0) return '';\n const qs = new URLSearchParams(\n entries.map(([k, v]) => [k, String(v)]),\n ).toString();\n return `?${qs}`;\n}\n\n// ---------------------------------------------------------------------------\n// Public API — List items\n// ---------------------------------------------------------------------------\n\n/**\n * Fetches published portfolio items from the public API.\n * Cached via Next.js ISR (revalidate: 3600).\n */\nexport async function getPortfolioItems(options?: {\n category?: string;\n featured?: boolean;\n limit?: number;\n offset?: number;\n}): Promise<PortfolioListResponse> {\n const fallback: PortfolioListResponse = { items: [], total: 0, limit: 0, offset: 0 };\n\n const query = buildQuery({\n category: options?.category,\n featured: options?.featured,\n limit: options?.limit,\n offset: options?.offset,\n });\n\n const data = await apiGet<PortfolioListResponse>(\n `/api/public/portfolio/items${query}`,\n );\n\n if (!data) {\n console.warn(`${LOG_PREFIX} Failed to fetch portfolio items.`);\n return fallback;\n }\n\n return data;\n}\n\n// ---------------------------------------------------------------------------\n// Public API — Single item\n// ---------------------------------------------------------------------------\n\n/** Shape returned by the single-item API endpoint. */\ninterface PortfolioItemApiResponse {\n found: boolean;\n item: {\n id: string;\n slug: string;\n title: string;\n subtitle: string;\n category: string;\n services: string[];\n description: string;\n hero_image: string;\n hero_image_alt: string;\n live_url: string | null;\n project_id: string | null;\n kpis: PortfolioItemFull['kpis'];\n details: PortfolioItemFull['details'];\n seo: PortfolioItemFull['seo'];\n featured: boolean;\n order?: number;\n published_at: string | null;\n hero_screenshots: PortfolioItemFull['hero_screenshots'];\n baseline_metrics: PortfolioItemFull['baseline_metrics'];\n current_metrics: PortfolioItemFull['current_metrics'];\n metrics_last_refreshed_at: string | null;\n sections: PortfolioSection[];\n metricsDelta: MetricsDelta[];\n };\n}\n\n/**\n * Fetches a single portfolio item by slug, including full Sanity sections\n * and live metrics delta. This is the main data function for portfolio detail pages.\n *\n * Returns null when the item is not found or the API call fails.\n */\nexport async function getPortfolioItem(slug: string): Promise<PortfolioItemFull | null> {\n if (!slug) {\n console.warn(`${LOG_PREFIX} getPortfolioItem called with empty slug.`);\n return null;\n }\n\n const data = await apiGet<PortfolioItemApiResponse>(\n `/api/public/portfolio/items/${encodeURIComponent(slug)}`,\n );\n\n if (!data || !data.found || !data.item) {\n console.warn(`${LOG_PREFIX} Portfolio item not found for slug: \"${slug}\".`);\n return null;\n }\n\n // Map the API response to the PortfolioItemFull type\n const { item } = data;\n return {\n id: item.id,\n slug: item.slug,\n title: item.title,\n subtitle: item.subtitle,\n category: item.category,\n services: item.services,\n description: item.description,\n hero_image: item.hero_image,\n hero_image_alt: item.hero_image_alt,\n live_url: item.live_url,\n project_id: item.project_id ?? null,\n kpis: item.kpis,\n details: item.details,\n seo: item.seo,\n featured: item.featured,\n order: item.order,\n published_at: item.published_at,\n hero_screenshots: item.hero_screenshots,\n baseline_metrics: item.baseline_metrics,\n current_metrics: item.current_metrics,\n metrics_last_refreshed_at: item.metrics_last_refreshed_at,\n sections: item.sections ?? [],\n metricsDelta: item.metricsDelta ?? [],\n };\n}\n\n// ---------------------------------------------------------------------------\n// Public API — Categories\n// ---------------------------------------------------------------------------\n\n/**\n * Fetches available portfolio categories.\n * Returns an empty array on failure.\n */\nexport async function getPortfolioCategories(): Promise<string[]> {\n const data = await apiGet<string[]>('/api/public/portfolio/categories');\n\n if (!data) {\n console.warn(`${LOG_PREFIX} Failed to fetch portfolio categories.`);\n return [];\n }\n\n return data;\n}\n\n// ---------------------------------------------------------------------------\n// Public API — Brand config\n// ---------------------------------------------------------------------------\n\n/**\n * Fetches brand configuration for the agency (colors, fonts, logo).\n * Returns sensible defaults when the API call fails.\n */\nexport async function getPortfolioBrandConfig(): Promise<BrandConfig> {\n const fallback: BrandConfig = {\n primary: '#6366f1',\n secondary: '#8b5cf6',\n background: '#0a0a0f',\n backgroundElevated: '#12121a',\n surface: '#1a1a2e',\n surfaceHover: '#22223a',\n surfaceBorder: '#2a2a3e',\n textPrimary: '#f0f0f5',\n textSecondary: '#a0a0b5',\n textTertiary: '#6b6b80',\n fontHeading: 'Inter, system-ui, sans-serif',\n fontBody: 'Inter, system-ui, sans-serif',\n };\n\n const data = await apiGet<PortfolioConfigResponse>(\n '/api/public/portfolio/config',\n );\n\n if (!data || !data.brand) {\n console.warn(`${LOG_PREFIX} Failed to fetch brand config, using defaults.`);\n return fallback;\n }\n\n return data.brand;\n}\n\n// ---------------------------------------------------------------------------\n// Next.js Metadata helper\n// ---------------------------------------------------------------------------\n\n/**\n * Generates Next.js Metadata for a portfolio item page.\n * For use in generateMetadata() in the App Router.\n *\n * Returns an empty object when the item cannot be found, which is safe\n * to spread into the metadata return value.\n */\nexport async function generatePortfolioMetadata(slug: string): Promise<{\n title?: string;\n description?: string;\n keywords?: string[];\n openGraph?: {\n title?: string;\n description?: string;\n images?: string[];\n type?: string;\n };\n}> {\n const item = await getPortfolioItem(slug);\n\n if (!item) {\n return {};\n }\n\n // Prefer dedicated SEO data if available, fall back to item fields\n const seo: PortfolioSeoData | null = item.seo;\n const title = seo?.metaTitle || item.title;\n const description = seo?.metaDescription || item.description;\n const keywords = seo?.keywords ?? [];\n\n // Collect OG images: prefer SEO og image, then hero image\n const ogImages: string[] = [];\n if (item.hero_image) {\n ogImages.push(item.hero_image);\n }\n\n return {\n title,\n description,\n keywords: keywords.length > 0 ? keywords : undefined,\n openGraph: {\n title,\n description,\n images: ogImages.length > 0 ? ogImages : undefined,\n type: 'article',\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Next.js Static Params helper\n// ---------------------------------------------------------------------------\n\n/**\n * Generates static params for portfolio pages.\n * For use in generateStaticParams() in the App Router.\n *\n * Fetches all published portfolio items and returns their slugs.\n */\nexport async function generatePortfolioStaticParams(): Promise<Array<{ slug: string }>> {\n // Fetch a large batch to cover all published items\n const data = await getPortfolioItems({ limit: 500, offset: 0 });\n\n return data.items.map((item) => ({ slug: item.slug }));\n}\n"]}
|
|
@@ -274,7 +274,7 @@ interface PortfolioCTAData {
|
|
|
274
274
|
buttonUrl: string;
|
|
275
275
|
style?: 'primary' | 'glass';
|
|
276
276
|
}
|
|
277
|
-
declare const PORTFOLIO_SECTION_TYPES: readonly ["portfolioHero", "portfolioChallenges", "portfolioStrategy", "portfolioResults", "portfolioTechStack", "portfolioServices", "portfolioTestimonial", "portfolioGallery", "portfolioVideo", "portfolioTeam", "portfolioFeatureSpotlight", "portfolioBeforeAfter", "portfolioMetricsTimeline", "portfolioConversionFunnel", "portfolioDetails", "portfolioSeo", "portfolioCTA"];
|
|
277
|
+
declare const PORTFOLIO_SECTION_TYPES: readonly ["portfolioHero", "portfolioChallenges", "portfolioStrategy", "portfolioResults", "portfolioTechStack", "portfolioServices", "portfolioTestimonial", "portfolioGallery", "portfolioVideo", "portfolioTeam", "portfolioFeatureSpotlight", "portfolioBeforeAfter", "portfolioMetricsTimeline", "portfolioConversionFunnel", "portfolioPerformance", "portfolioSpeedComparison", "portfolioSiteArchitecture", "portfolioDesignSystem", "portfolioDetails", "portfolioSeo", "portfolioCTA"];
|
|
278
278
|
type PortfolioSectionType = (typeof PORTFOLIO_SECTION_TYPES)[number];
|
|
279
279
|
type PortfolioSectionData = PortfolioHeroData | PortfolioChallengesData | PortfolioStrategyData | PortfolioResultsData | PortfolioTechStackData | PortfolioServicesData | PortfolioTestimonialData | PortfolioGalleryData | PortfolioVideoData | PortfolioTeamData | PortfolioFeatureSpotlightData | PortfolioBeforeAfterData | PortfolioMetricsTimelineData | PortfolioConversionFunnelData | PortfolioDetailsData | PortfolioSeoData | PortfolioCTAData;
|
|
280
280
|
interface PortfolioSectionDataMap {
|
|
@@ -318,6 +318,8 @@ interface PortfolioItem {
|
|
|
318
318
|
featured: boolean;
|
|
319
319
|
order?: number;
|
|
320
320
|
published_at: string | null;
|
|
321
|
+
/** Sonor project ID (present for items generated from a managed project) */
|
|
322
|
+
project_id: string | null;
|
|
321
323
|
hero_screenshots: {
|
|
322
324
|
desktop?: string;
|
|
323
325
|
tablet?: string;
|
|
@@ -274,7 +274,7 @@ interface PortfolioCTAData {
|
|
|
274
274
|
buttonUrl: string;
|
|
275
275
|
style?: 'primary' | 'glass';
|
|
276
276
|
}
|
|
277
|
-
declare const PORTFOLIO_SECTION_TYPES: readonly ["portfolioHero", "portfolioChallenges", "portfolioStrategy", "portfolioResults", "portfolioTechStack", "portfolioServices", "portfolioTestimonial", "portfolioGallery", "portfolioVideo", "portfolioTeam", "portfolioFeatureSpotlight", "portfolioBeforeAfter", "portfolioMetricsTimeline", "portfolioConversionFunnel", "portfolioDetails", "portfolioSeo", "portfolioCTA"];
|
|
277
|
+
declare const PORTFOLIO_SECTION_TYPES: readonly ["portfolioHero", "portfolioChallenges", "portfolioStrategy", "portfolioResults", "portfolioTechStack", "portfolioServices", "portfolioTestimonial", "portfolioGallery", "portfolioVideo", "portfolioTeam", "portfolioFeatureSpotlight", "portfolioBeforeAfter", "portfolioMetricsTimeline", "portfolioConversionFunnel", "portfolioPerformance", "portfolioSpeedComparison", "portfolioSiteArchitecture", "portfolioDesignSystem", "portfolioDetails", "portfolioSeo", "portfolioCTA"];
|
|
278
278
|
type PortfolioSectionType = (typeof PORTFOLIO_SECTION_TYPES)[number];
|
|
279
279
|
type PortfolioSectionData = PortfolioHeroData | PortfolioChallengesData | PortfolioStrategyData | PortfolioResultsData | PortfolioTechStackData | PortfolioServicesData | PortfolioTestimonialData | PortfolioGalleryData | PortfolioVideoData | PortfolioTeamData | PortfolioFeatureSpotlightData | PortfolioBeforeAfterData | PortfolioMetricsTimelineData | PortfolioConversionFunnelData | PortfolioDetailsData | PortfolioSeoData | PortfolioCTAData;
|
|
280
280
|
interface PortfolioSectionDataMap {
|
|
@@ -318,6 +318,8 @@ interface PortfolioItem {
|
|
|
318
318
|
featured: boolean;
|
|
319
319
|
order?: number;
|
|
320
320
|
published_at: string | null;
|
|
321
|
+
/** Sonor project ID (present for items generated from a managed project) */
|
|
322
|
+
project_id: string | null;
|
|
321
323
|
hero_screenshots: {
|
|
322
324
|
desktop?: string;
|
|
323
325
|
tablet?: string;
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/portfolio/components/sections/BeforeAfterSection.tsx"],"names":[],"mappings":";;;;;;AAYe,SAAR,kBAAA,CAAoC,EAAE,IAAA,EAAK,EAA4B;AAC5E,EAAA,MAAM,YAAA,GAAe,OAAuB,IAAI,CAAA;AAChD,EAAA,MAAM,SAAA,GAAY,OAAuB,IAAI,CAAA;AAC7C,EAAA,MAAM,CAAC,QAAA,EAAU,WAAW,IAAI,QAAA,CAAS,IAAA,CAAK,mBAAmB,EAAE,CAAA;AACnE,EAAA,MAAM,UAAA,GAAa,OAAO,KAAK,CAAA;AAE/B,EAAA,MAAM,SAAA,GAAY,IAAA,CAAK,MAAA,EAAQ,KAAA,EAAO,IAAA,IAAQ,EAAA;AAC9C,EAAA,MAAM,QAAA,GAAW,IAAA,CAAK,KAAA,EAAO,KAAA,EAAO,IAAA,IAAQ,EAAA;AAG5C,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,MAAM,oBAAA,GAAuB,MAAA,CAAO,UAAA,CAAW,kCAAkC,CAAA,CAAE,OAAA;AACnF,IAAA,IAAI,oBAAA,IAAwB,CAAC,SAAA,CAAU,OAAA,EAAS;AAEhD,IAAA,IAAA,CAAK,MAAA;AAAA,MACH,SAAA,CAAU,OAAA;AAAA,MACV,EAAE,OAAA,EAAS,CAAA,EAAG,KAAA,EAAO,GAAA,EAAI;AAAA,MACzB,EAAE,OAAA,EAAS,CAAA,EAAG,KAAA,EAAO,CAAA,EAAG,UAAU,GAAA,EAAK,KAAA,EAAO,GAAA,EAAK,IAAA,EAAM,aAAA;AAAc,KACzE;AAAA,EACF,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,cAAA,GAAiB,WAAA,CAAY,CAAC,OAAA,KAAoB;AACtD,IAAA,MAAM,YAAY,YAAA,CAAa,OAAA;AAC/B,IAAA,IAAI,CAAC,SAAA,EAAW;AAEhB,IAAA,MAAM,IAAA,GAAO,UAAU,qBAAA,EAAsB;AAC7C,IAAA,MAAM,CAAA,GAAI,UAAU,IAAA,CAAK,IAAA;AACzB,IAAA,MAAM,OAAA,GAAU,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,IAAA,CAAK,GAAA,CAAI,GAAA,EAAM,CAAA,GAAI,IAAA,CAAK,KAAA,GAAS,GAAG,CAAC,CAAA;AACjE,IAAA,WAAA,CAAY,OAAO,CAAA;AAAA,EACrB,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,iBAAA,GAAoB,WAAA;AAAA,IACxB,CAAC,CAAA,KAA0B;AACzB,MAAA,UAAA,CAAW,OAAA,GAAU,IAAA;AACrB,MAAC,CAAA,CAAE,MAAA,CAAuB,iBAAA,CAAkB,CAAA,CAAE,SAAS,CAAA;AACvD,MAAA,cAAA,CAAe,EAAE,OAAO,CAAA;AAAA,IAC1B,CAAA;AAAA,IACA,CAAC,cAAc;AAAA,GACjB;AAEA,EAAA,MAAM,iBAAA,GAAoB,WAAA;AAAA,IACxB,CAAC,CAAA,KAA0B;AACzB,MAAA,IAAI,CAAC,WAAW,OAAA,EAAS;AACzB,MAAA,cAAA,CAAe,EAAE,OAAO,CAAA;AAAA,IAC1B,CAAA;AAAA,IACA,CAAC,cAAc;AAAA,GACjB;AAEA,EAAA,MAAM,eAAA,GAAkB,YAAY,MAAM;AACxC,IAAA,UAAA,CAAW,OAAA,GAAU,KAAA;AAAA,EACvB,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,uBACE,GAAA;AAAA,IAAC,SAAA;AAAA,IAAA;AAAA,MACC,SAAA,EAAU,uBAAA;AAAA,MACV,KAAA,EAAO,EAAE,UAAA,EAAY,uBAAA,EAAwB;AAAA,MAE7C,QAAA,kBAAA,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,wBAAA,EACX,QAAA,EAAA;AAAA,QAAA,CAAA,IAAA,CAAK,SAAS,IAAA,CAAK,WAAA,qBACnB,IAAA,CAAC,YAAA,EAAA,EAAa,GAAG,EAAA,EACd,QAAA,EAAA;AAAA,UAAA,IAAA,CAAK,KAAA,oBACJ,GAAA;AAAA,YAAC,IAAA;AAAA,YAAA;AAAA,cACC,SAAA,EAAU,qCAAA;AAAA,cACV,KAAA,EAAO;AAAA,gBACL,KAAA,EAAO,iCAAA;AAAA,gBACP,UAAA,EAAY;AAAA,eACd;AAAA,cAEC,QAAA,EAAA,IAAA,CAAK;AAAA;AAAA,WACR;AAAA,UAED,KAAK,WAAA,oBACJ,GAAA;AAAA,YAAC,GAAA;AAAA,YAAA;AAAA,cACC,SAAA,EAAU,yBAAA;AAAA,cACV,KAAA,EAAO,EAAE,KAAA,EAAO,mCAAA,EAAoC;AAAA,cAEnD,QAAA,EAAA,IAAA,CAAK;AAAA;AAAA;AACR,SAAA,EAEJ,CAAA;AAAA,wBAGF,GAAA,CAAC,gBAAa,CAAA,EAAG,EAAA,EACf,8BAAC,SAAA,EAAA,EAAU,OAAA,EAAQ,IAAA,EAAK,KAAA,EAAO,KAAA,EAC7B,QAAA,kBAAA,IAAA;AAAA,UAAC,KAAA;AAAA,UAAA;AAAA,YACC,GAAA,EAAK,YAAA;AAAA,YACL,SAAA,EAAU,0EAAA;AAAA,YACV,KAAA,EAAO,EAAE,WAAA,EAAa,OAAA,EAAQ;AAAA,YAC9B,aAAA,EAAe,iBAAA;AAAA,YACf,aAAA,EAAe,iBAAA;AAAA,YACf,WAAA,EAAa,eAAA;AAAA,YACb,eAAA,EAAiB,eAAA;AAAA,YAGjB,QAAA,EAAA;AAAA,8BAAA,GAAA;AAAA,gBAAC,KAAA;AAAA,gBAAA;AAAA,kBACC,GAAA,EAAK,QAAA;AAAA,kBACL,GAAA,EAAI,OAAA;AAAA,kBACJ,SAAA,EAAU,6CAAA;AAAA,kBACV,SAAA,EAAW;AAAA;AAAA,eACb;AAAA,8BAGA,GAAA;AAAA,gBAAC,KAAA;AAAA,gBAAA;AAAA,kBACC,SAAA,EAAU,kCAAA;AAAA,kBACV,KAAA,EAAO,EAAE,KAAA,EAAO,CAAA,EAAG,QAAQ,CAAA,CAAA,CAAA,EAAI;AAAA,kBAE/B,QAAA,kBAAA,GAAA;AAAA,oBAAC,KAAA;AAAA,oBAAA;AAAA,sBACC,GAAA,EAAK,SAAA;AAAA,sBACL,GAAA,EAAI,QAAA;AAAA,sBACJ,SAAA,EAAU,6CAAA;AAAA,sBACV,OAAO,EAAE,QAAA,EAAU,YAAA,CAAa,OAAA,EAAS,eAAe,MAAA,EAAO;AAAA,sBAC/D,SAAA,EAAW;AAAA;AAAA;AACb;AAAA,eACF;AAAA,8BAGA,GAAA;AAAA,gBAAC,KAAA;AAAA,gBAAA;AAAA,kBACC,SAAA,EAAU,+BAAA;AAAA,kBACV,KAAA,EAAO;AAAA,oBACL,IAAA,EAAM,GAAG,QAAQ,CAAA,CAAA,CAAA;AAAA,oBACjB,SAAA,EAAW,kBAAA;AAAA,oBACX,UAAA,EAAY,SAAA;AAAA,oBACZ,SAAA,EAAW;AAAA;AACb;AAAA,eACF;AAAA,8BAGA,GAAA;AAAA,gBAAC,KAAA;AAAA,gBAAA;AAAA,kBACC,GAAA,EAAK,SAAA;AAAA,kBACL,SAAA,EAAU,0EAAA;AAAA,kBACV,KAAA,EAAO;AAAA,oBACL,IAAA,EAAM,GAAG,QAAQ,CAAA,CAAA,CAAA;AAAA,oBACjB,SAAA,EAAW,uBAAA;AAAA,oBACX,UAAA,EAAY,4BAAA;AAAA,oBACZ,SAAA,EAAW,4BAAA;AAAA,oBACX,MAAA,EAAQ,mBAAA;AAAA,oBACR,MAAA,EAAQ;AAAA,mBACV;AAAA,kBAEA,QAAA,kBAAA,GAAA,CAAC,SAAI,KAAA,EAAM,IAAA,EAAK,QAAO,IAAA,EAAK,OAAA,EAAQ,WAAA,EAAY,IAAA,EAAK,MAAA,EACnD,QAAA,kBAAA,GAAA,CAAC,UAAK,CAAA,EAAE,4BAAA,EAA6B,QAAO,SAAA,EAAU,WAAA,EAAY,KAAI,aAAA,EAAc,OAAA,EAAQ,cAAA,EAAe,OAAA,EAAQ,CAAA,EACrH;AAAA;AAAA,eACF;AAAA,8BAGA,GAAA;AAAA,gBAAC,MAAA;AAAA,gBAAA;AAAA,kBACC,SAAA,EAAU,2FAAA;AAAA,kBACV,KAAA,EAAO;AAAA,oBACL,UAAA,EAAY,iBAAA;AAAA,oBACZ,KAAA,EAAO,SAAA;AAAA,oBACP,cAAA,EAAgB;AAAA,mBAClB;AAAA,kBACD,QAAA,EAAA;AAAA;AAAA,eAED;AAAA,8BACA,GAAA;AAAA,gBAAC,MAAA;AAAA,gBAAA;AAAA,kBACC,SAAA,EAAU,4FAAA;AAAA,kBACV,KAAA,EAAO;AAAA,oBACL,UAAA,EAAY,iBAAA;AAAA,oBACZ,KAAA,EAAO,SAAA;AAAA,oBACP,cAAA,EAAgB;AAAA,mBAClB;AAAA,kBACD,QAAA,EAAA;AAAA;AAAA;AAED;AAAA;AAAA,WAEJ,CAAA,EACF;AAAA,OAAA,EACF;AAAA;AAAA,GACF;AAEJ","file":"BeforeAfterSection-6QUJOBO2.js","sourcesContent":["'use client';\n\nimport React, { useRef, useState, useCallback, useEffect } from 'react';\nimport gsap from 'gsap';\nimport type { PortfolioBeforeAfterData } from '../../../types';\nimport ScrollReveal from '../primitives/ScrollReveal';\nimport GlassCard from '../primitives/GlassCard';\n\ninterface BeforeAfterSectionProps {\n data: PortfolioBeforeAfterData;\n}\n\nexport default function BeforeAfterSection({ data }: BeforeAfterSectionProps) {\n const containerRef = useRef<HTMLDivElement>(null);\n const sliderRef = useRef<HTMLDivElement>(null);\n const [position, setPosition] = useState(data.defaultPosition ?? 50);\n const isDragging = useRef(false);\n\n const beforeUrl = data.before?.asset?._ref || '';\n const afterUrl = data.after?.asset?._ref || '';\n\n // Animate slider handle entrance\n useEffect(() => {\n const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n if (prefersReducedMotion || !sliderRef.current) return;\n\n gsap.fromTo(\n sliderRef.current,\n { opacity: 0, scale: 0.8 },\n { opacity: 1, scale: 1, duration: 0.6, delay: 0.5, ease: 'back.out(2)' },\n );\n }, []);\n\n const updatePosition = useCallback((clientX: number) => {\n const container = containerRef.current;\n if (!container) return;\n\n const rect = container.getBoundingClientRect();\n const x = clientX - rect.left;\n const percent = Math.max(0, Math.min(100, (x / rect.width) * 100));\n setPosition(percent);\n }, []);\n\n const handlePointerDown = useCallback(\n (e: React.PointerEvent) => {\n isDragging.current = true;\n (e.target as HTMLElement).setPointerCapture(e.pointerId);\n updatePosition(e.clientX);\n },\n [updatePosition],\n );\n\n const handlePointerMove = useCallback(\n (e: React.PointerEvent) => {\n if (!isDragging.current) return;\n updatePosition(e.clientX);\n },\n [updatePosition],\n );\n\n const handlePointerUp = useCallback(() => {\n isDragging.current = false;\n }, []);\n\n return (\n <section\n className=\"w-full py-20 md:py-28\"\n style={{ background: 'var(--sk-bg, #0a0a0a)' }}\n >\n <div className=\"max-w-5xl mx-auto px-6\">\n {(data.title || data.description) && (\n <ScrollReveal y={30}>\n {data.title && (\n <h2\n className=\"text-3xl md:text-4xl font-bold mb-4\"\n style={{\n color: 'var(--sk-text-primary, #ffffff)',\n fontFamily: 'var(--sk-font-heading, inherit)',\n }}\n >\n {data.title}\n </h2>\n )}\n {data.description && (\n <p\n className=\"text-lg mb-12 max-w-2xl\"\n style={{ color: 'var(--sk-text-secondary, #a1a1aa)' }}\n >\n {data.description}\n </p>\n )}\n </ScrollReveal>\n )}\n\n <ScrollReveal y={40}>\n <GlassCard padding=\"sm\" hover={false}>\n <div\n ref={containerRef}\n className=\"relative w-full overflow-hidden rounded-xl cursor-col-resize select-none\"\n style={{ aspectRatio: '16/10' }}\n onPointerDown={handlePointerDown}\n onPointerMove={handlePointerMove}\n onPointerUp={handlePointerUp}\n onPointerCancel={handlePointerUp}\n >\n {/* After image (full background) */}\n <img\n src={afterUrl}\n alt=\"After\"\n className=\"absolute inset-0 w-full h-full object-cover\"\n draggable={false}\n />\n\n {/* Before image (clipped) */}\n <div\n className=\"absolute inset-0 overflow-hidden\"\n style={{ width: `${position}%` }}\n >\n <img\n src={beforeUrl}\n alt=\"Before\"\n className=\"absolute inset-0 w-full h-full object-cover\"\n style={{ minWidth: containerRef.current?.offsetWidth || '100%' }}\n draggable={false}\n />\n </div>\n\n {/* Slider line */}\n <div\n className=\"absolute top-0 bottom-0 w-0.5\"\n style={{\n left: `${position}%`,\n transform: 'translateX(-50%)',\n background: '#ffffff',\n boxShadow: '0 0 8px rgba(0,0,0,0.5)',\n }}\n />\n\n {/* Slider handle */}\n <div\n ref={sliderRef}\n className=\"absolute top-1/2 flex items-center justify-center w-10 h-10 rounded-full\"\n style={{\n left: `${position}%`,\n transform: 'translate(-50%, -50%)',\n background: 'var(--sk-primary, #6366f1)',\n boxShadow: '0 4px 16px rgba(0,0,0,0.3)',\n border: '3px solid #ffffff',\n zIndex: 5,\n }}\n >\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\">\n <path d=\"M5 3l-4 5 4 5M11 3l4 5-4 5\" stroke=\"#ffffff\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n </div>\n\n {/* Before / After labels */}\n <span\n className=\"absolute top-4 left-4 px-3 py-1 rounded-lg text-xs font-semibold uppercase tracking-wider\"\n style={{\n background: 'rgba(0,0,0,0.6)',\n color: '#ffffff',\n backdropFilter: 'blur(4px)',\n }}\n >\n Before\n </span>\n <span\n className=\"absolute top-4 right-4 px-3 py-1 rounded-lg text-xs font-semibold uppercase tracking-wider\"\n style={{\n background: 'rgba(0,0,0,0.6)',\n color: '#ffffff',\n backdropFilter: 'blur(4px)',\n }}\n >\n After\n </span>\n </div>\n </GlassCard>\n </ScrollReveal>\n </div>\n </section>\n );\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/portfolio/components/sections/BeforeAfterSection.tsx"],"names":["useRef","useState","useEffect","gsap","useCallback","jsx","jsxs","ScrollReveal","GlassCard"],"mappings":"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","file":"BeforeAfterSection-DVAWWE4K.cjs","sourcesContent":["'use client';\n\nimport React, { useRef, useState, useCallback, useEffect } from 'react';\nimport gsap from 'gsap';\nimport type { PortfolioBeforeAfterData } from '../../../types';\nimport ScrollReveal from '../primitives/ScrollReveal';\nimport GlassCard from '../primitives/GlassCard';\n\ninterface BeforeAfterSectionProps {\n data: PortfolioBeforeAfterData;\n}\n\nexport default function BeforeAfterSection({ data }: BeforeAfterSectionProps) {\n const containerRef = useRef<HTMLDivElement>(null);\n const sliderRef = useRef<HTMLDivElement>(null);\n const [position, setPosition] = useState(data.defaultPosition ?? 50);\n const isDragging = useRef(false);\n\n const beforeUrl = data.before?.asset?._ref || '';\n const afterUrl = data.after?.asset?._ref || '';\n\n // Animate slider handle entrance\n useEffect(() => {\n const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n if (prefersReducedMotion || !sliderRef.current) return;\n\n gsap.fromTo(\n sliderRef.current,\n { opacity: 0, scale: 0.8 },\n { opacity: 1, scale: 1, duration: 0.6, delay: 0.5, ease: 'back.out(2)' },\n );\n }, []);\n\n const updatePosition = useCallback((clientX: number) => {\n const container = containerRef.current;\n if (!container) return;\n\n const rect = container.getBoundingClientRect();\n const x = clientX - rect.left;\n const percent = Math.max(0, Math.min(100, (x / rect.width) * 100));\n setPosition(percent);\n }, []);\n\n const handlePointerDown = useCallback(\n (e: React.PointerEvent) => {\n isDragging.current = true;\n (e.target as HTMLElement).setPointerCapture(e.pointerId);\n updatePosition(e.clientX);\n },\n [updatePosition],\n );\n\n const handlePointerMove = useCallback(\n (e: React.PointerEvent) => {\n if (!isDragging.current) return;\n updatePosition(e.clientX);\n },\n [updatePosition],\n );\n\n const handlePointerUp = useCallback(() => {\n isDragging.current = false;\n }, []);\n\n return (\n <section\n className=\"w-full py-20 md:py-28\"\n style={{ background: 'var(--sk-bg, #0a0a0a)' }}\n >\n <div className=\"max-w-5xl mx-auto px-6\">\n {(data.title || data.description) && (\n <ScrollReveal y={30}>\n {data.title && (\n <h2\n className=\"text-3xl md:text-4xl font-bold mb-4\"\n style={{\n color: 'var(--sk-text-primary, #ffffff)',\n fontFamily: 'var(--sk-font-heading, inherit)',\n }}\n >\n {data.title}\n </h2>\n )}\n {data.description && (\n <p\n className=\"text-lg mb-12 max-w-2xl\"\n style={{ color: 'var(--sk-text-secondary, #a1a1aa)' }}\n >\n {data.description}\n </p>\n )}\n </ScrollReveal>\n )}\n\n <ScrollReveal y={40}>\n <GlassCard padding=\"sm\" hover={false}>\n <div\n ref={containerRef}\n className=\"relative w-full overflow-hidden rounded-xl cursor-col-resize select-none\"\n style={{ aspectRatio: '16/10' }}\n onPointerDown={handlePointerDown}\n onPointerMove={handlePointerMove}\n onPointerUp={handlePointerUp}\n onPointerCancel={handlePointerUp}\n >\n {/* After image (full background) */}\n <img\n src={afterUrl}\n alt=\"After\"\n className=\"absolute inset-0 w-full h-full object-cover\"\n draggable={false}\n />\n\n {/* Before image (clipped) */}\n <div\n className=\"absolute inset-0 overflow-hidden\"\n style={{ width: `${position}%` }}\n >\n <img\n src={beforeUrl}\n alt=\"Before\"\n className=\"absolute inset-0 w-full h-full object-cover\"\n style={{ minWidth: containerRef.current?.offsetWidth || '100%' }}\n draggable={false}\n />\n </div>\n\n {/* Slider line */}\n <div\n className=\"absolute top-0 bottom-0 w-0.5\"\n style={{\n left: `${position}%`,\n transform: 'translateX(-50%)',\n background: '#ffffff',\n boxShadow: '0 0 8px rgba(0,0,0,0.5)',\n }}\n />\n\n {/* Slider handle */}\n <div\n ref={sliderRef}\n className=\"absolute top-1/2 flex items-center justify-center w-10 h-10 rounded-full\"\n style={{\n left: `${position}%`,\n transform: 'translate(-50%, -50%)',\n background: 'var(--sk-primary, #6366f1)',\n boxShadow: '0 4px 16px rgba(0,0,0,0.3)',\n border: '3px solid #ffffff',\n zIndex: 5,\n }}\n >\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\">\n <path d=\"M5 3l-4 5 4 5M11 3l4 5-4 5\" stroke=\"#ffffff\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n </div>\n\n {/* Before / After labels */}\n <span\n className=\"absolute top-4 left-4 px-3 py-1 rounded-lg text-xs font-semibold uppercase tracking-wider\"\n style={{\n background: 'rgba(0,0,0,0.6)',\n color: '#ffffff',\n backdropFilter: 'blur(4px)',\n }}\n >\n Before\n </span>\n <span\n className=\"absolute top-4 right-4 px-3 py-1 rounded-lg text-xs font-semibold uppercase tracking-wider\"\n style={{\n background: 'rgba(0,0,0,0.6)',\n color: '#ffffff',\n backdropFilter: 'blur(4px)',\n }}\n >\n After\n </span>\n </div>\n </GlassCard>\n </ScrollReveal>\n </div>\n </section>\n );\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/portfolio/components/sections/DetailsSection.tsx"],"names":[],"mappings":"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","file":"DetailsSection-FB763FS7.js","sourcesContent":["'use client';\n\nimport React from 'react';\nimport type { PortfolioDetailsData } from '../../../types';\nimport ScrollReveal from '../primitives/ScrollReveal';\nimport GlassCard from '../primitives/GlassCard';\n\ninterface DetailsSectionProps {\n data: PortfolioDetailsData;\n}\n\ninterface DetailRow {\n icon: React.ReactNode;\n label: string;\n value: string;\n}\n\nexport default function DetailsSection({ data }: DetailsSectionProps) {\n const rows: DetailRow[] = [];\n\n if (data.industry) {\n rows.push({\n label: 'Industry',\n value: data.industry,\n icon: (\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 18 18\" fill=\"none\">\n <path d=\"M3 16.5h12M4.5 1.5h9l1.5 6H3l1.5-6zM6 7.5v9M12 7.5v9M9 7.5v9\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n ),\n });\n }\n\n if (data.location) {\n rows.push({\n label: 'Location',\n value: data.location,\n icon: (\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 18 18\" fill=\"none\">\n <path d=\"M15 7.5c0 4.5-6 9-6 9s-6-4.5-6-9a6 6 0 1112 0z\" stroke=\"currentColor\" strokeWidth=\"1.5\" />\n <circle cx=\"9\" cy=\"7.5\" r=\"2\" stroke=\"currentColor\" strokeWidth=\"1.5\" />\n </svg>\n ),\n });\n }\n\n if (data.website) {\n rows.push({\n label: 'Website',\n value: data.website,\n icon: (\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 18 18\" fill=\"none\">\n <circle cx=\"9\" cy=\"9\" r=\"7.5\" stroke=\"currentColor\" strokeWidth=\"1.5\" />\n <path d=\"M1.5 9h15M9 1.5a11.25 11.25 0 013 7.5 11.25 11.25 0 01-3 7.5 11.25 11.25 0 01-3-7.5 11.25 11.25 0 013-7.5z\" stroke=\"currentColor\" strokeWidth=\"1.5\" />\n </svg>\n ),\n });\n }\n\n if (data.timeline) {\n rows.push({\n label: 'Timeline',\n value: data.timeline,\n icon: (\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 18 18\" fill=\"none\">\n <circle cx=\"9\" cy=\"9\" r=\"7.5\" stroke=\"currentColor\" strokeWidth=\"1.5\" />\n <path d=\"M9 4.5V9l3 1.5\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n ),\n });\n }\n\n if (data.launchDate) {\n rows.push({\n label: 'Launch Date',\n value: data.launchDate,\n icon: (\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 18 18\" fill=\"none\">\n <rect x=\"2.25\" y=\"3\" width=\"13.5\" height=\"12.75\" rx=\"2\" stroke=\"currentColor\" strokeWidth=\"1.5\" />\n <path d=\"M12 1.5v3M6 1.5v3M2.25 7.5h13.5\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" />\n </svg>\n ),\n });\n }\n\n if (data.budgetRange) {\n rows.push({\n label: 'Budget Range',\n value: data.budgetRange,\n icon: (\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 18 18\" fill=\"none\">\n <path d=\"M9 1.5v15M13.5 4.5H6.75a2.625 2.625 0 000 5.25h4.5a2.625 2.625 0 010 5.25H4.5\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n ),\n });\n }\n\n if (rows.length === 0) return null;\n\n return (\n <section\n className=\"w-full py-20 md:py-28\"\n style={{ background: 'var(--sk-bg, #0a0a0a)' }}\n >\n <div className=\"max-w-4xl mx-auto px-6\">\n <ScrollReveal y={30}>\n <h2\n className=\"text-3xl md:text-4xl font-bold mb-8\"\n style={{\n color: 'var(--sk-text-primary, #ffffff)',\n fontFamily: 'var(--sk-font-heading, inherit)',\n }}\n >\n Project Details\n </h2>\n </ScrollReveal>\n\n <ScrollReveal y={40} delay={0.1}>\n <GlassCard padding=\"lg\" hover={false}>\n <div className=\"flex flex-col divide-y\" style={{ borderColor: 'var(--sk-border, rgba(255,255,255,0.1))' }}>\n {rows.map((row, index) => (\n <div\n key={index}\n className=\"flex items-center gap-4 py-4 first:pt-0 last:pb-0\"\n style={{\n borderColor: 'var(--sk-border, rgba(255,255,255,0.1))',\n }}\n >\n <span\n className=\"flex items-center justify-center w-9 h-9 rounded-lg shrink-0\"\n style={{\n background: 'color-mix(in srgb, var(--sk-primary, #6366f1) 12%, transparent)',\n color: 'var(--sk-primary, #6366f1)',\n }}\n >\n {row.icon}\n </span>\n <span\n className=\"text-sm font-medium w-32 shrink-0\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n >\n {row.label}\n </span>\n {row.label === 'Website' ? (\n <a\n href={row.value.startsWith('http') ? row.value : `https://${row.value}`}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"text-sm font-medium hover:underline\"\n style={{ color: 'var(--sk-primary, #6366f1)' }}\n >\n {row.value}\n </a>\n ) : (\n <span\n className=\"text-sm font-medium\"\n style={{ color: 'var(--sk-text-primary, #ffffff)' }}\n >\n {row.value}\n </span>\n )}\n </div>\n ))}\n </div>\n </GlassCard>\n </ScrollReveal>\n </div>\n </section>\n );\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/portfolio/components/sections/DetailsSection.tsx"],"names":["jsxs","jsx","ScrollReveal","GlassCard"],"mappings":"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","file":"DetailsSection-OACJFGH7.cjs","sourcesContent":["'use client';\n\nimport React from 'react';\nimport type { PortfolioDetailsData } from '../../../types';\nimport ScrollReveal from '../primitives/ScrollReveal';\nimport GlassCard from '../primitives/GlassCard';\n\ninterface DetailsSectionProps {\n data: PortfolioDetailsData;\n}\n\ninterface DetailRow {\n icon: React.ReactNode;\n label: string;\n value: string;\n}\n\nexport default function DetailsSection({ data }: DetailsSectionProps) {\n const rows: DetailRow[] = [];\n\n if (data.industry) {\n rows.push({\n label: 'Industry',\n value: data.industry,\n icon: (\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 18 18\" fill=\"none\">\n <path d=\"M3 16.5h12M4.5 1.5h9l1.5 6H3l1.5-6zM6 7.5v9M12 7.5v9M9 7.5v9\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n ),\n });\n }\n\n if (data.location) {\n rows.push({\n label: 'Location',\n value: data.location,\n icon: (\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 18 18\" fill=\"none\">\n <path d=\"M15 7.5c0 4.5-6 9-6 9s-6-4.5-6-9a6 6 0 1112 0z\" stroke=\"currentColor\" strokeWidth=\"1.5\" />\n <circle cx=\"9\" cy=\"7.5\" r=\"2\" stroke=\"currentColor\" strokeWidth=\"1.5\" />\n </svg>\n ),\n });\n }\n\n if (data.website) {\n rows.push({\n label: 'Website',\n value: data.website,\n icon: (\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 18 18\" fill=\"none\">\n <circle cx=\"9\" cy=\"9\" r=\"7.5\" stroke=\"currentColor\" strokeWidth=\"1.5\" />\n <path d=\"M1.5 9h15M9 1.5a11.25 11.25 0 013 7.5 11.25 11.25 0 01-3 7.5 11.25 11.25 0 01-3-7.5 11.25 11.25 0 013-7.5z\" stroke=\"currentColor\" strokeWidth=\"1.5\" />\n </svg>\n ),\n });\n }\n\n if (data.timeline) {\n rows.push({\n label: 'Timeline',\n value: data.timeline,\n icon: (\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 18 18\" fill=\"none\">\n <circle cx=\"9\" cy=\"9\" r=\"7.5\" stroke=\"currentColor\" strokeWidth=\"1.5\" />\n <path d=\"M9 4.5V9l3 1.5\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n ),\n });\n }\n\n if (data.launchDate) {\n rows.push({\n label: 'Launch Date',\n value: data.launchDate,\n icon: (\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 18 18\" fill=\"none\">\n <rect x=\"2.25\" y=\"3\" width=\"13.5\" height=\"12.75\" rx=\"2\" stroke=\"currentColor\" strokeWidth=\"1.5\" />\n <path d=\"M12 1.5v3M6 1.5v3M2.25 7.5h13.5\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" />\n </svg>\n ),\n });\n }\n\n if (data.budgetRange) {\n rows.push({\n label: 'Budget Range',\n value: data.budgetRange,\n icon: (\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 18 18\" fill=\"none\">\n <path d=\"M9 1.5v15M13.5 4.5H6.75a2.625 2.625 0 000 5.25h4.5a2.625 2.625 0 010 5.25H4.5\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n ),\n });\n }\n\n if (rows.length === 0) return null;\n\n return (\n <section\n className=\"w-full py-20 md:py-28\"\n style={{ background: 'var(--sk-bg, #0a0a0a)' }}\n >\n <div className=\"max-w-4xl mx-auto px-6\">\n <ScrollReveal y={30}>\n <h2\n className=\"text-3xl md:text-4xl font-bold mb-8\"\n style={{\n color: 'var(--sk-text-primary, #ffffff)',\n fontFamily: 'var(--sk-font-heading, inherit)',\n }}\n >\n Project Details\n </h2>\n </ScrollReveal>\n\n <ScrollReveal y={40} delay={0.1}>\n <GlassCard padding=\"lg\" hover={false}>\n <div className=\"flex flex-col divide-y\" style={{ borderColor: 'var(--sk-border, rgba(255,255,255,0.1))' }}>\n {rows.map((row, index) => (\n <div\n key={index}\n className=\"flex items-center gap-4 py-4 first:pt-0 last:pb-0\"\n style={{\n borderColor: 'var(--sk-border, rgba(255,255,255,0.1))',\n }}\n >\n <span\n className=\"flex items-center justify-center w-9 h-9 rounded-lg shrink-0\"\n style={{\n background: 'color-mix(in srgb, var(--sk-primary, #6366f1) 12%, transparent)',\n color: 'var(--sk-primary, #6366f1)',\n }}\n >\n {row.icon}\n </span>\n <span\n className=\"text-sm font-medium w-32 shrink-0\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n >\n {row.label}\n </span>\n {row.label === 'Website' ? (\n <a\n href={row.value.startsWith('http') ? row.value : `https://${row.value}`}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"text-sm font-medium hover:underline\"\n style={{ color: 'var(--sk-primary, #6366f1)' }}\n >\n {row.value}\n </a>\n ) : (\n <span\n className=\"text-sm font-medium\"\n style={{ color: 'var(--sk-text-primary, #ffffff)' }}\n >\n {row.value}\n </span>\n )}\n </div>\n ))}\n </div>\n </GlassCard>\n </ScrollReveal>\n </div>\n </section>\n );\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/portfolio/components/sections/StrategySection.tsx"],"names":["useRef","useEffect","gsap","ScrollTrigger","jsx","jsxs","ScrollReveal","GlassCard"],"mappings":"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","file":"StrategySection-3ED3QW4R.cjs","sourcesContent":["'use client';\n\nimport React, { useRef, useEffect } from 'react';\nimport gsap from 'gsap';\nimport { ScrollTrigger } from 'gsap/ScrollTrigger';\nimport type { PortfolioStrategyData } from '../../../types';\nimport ScrollReveal from '../primitives/ScrollReveal';\nimport GlassCard from '../primitives/GlassCard';\n\ninterface StrategySectionProps {\n data: PortfolioStrategyData;\n}\n\nexport default function StrategySection({ data }: StrategySectionProps) {\n const timelineRef = useRef<SVGSVGElement>(null);\n const sectionRef = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n gsap.registerPlugin(ScrollTrigger);\n\n const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n if (prefersReducedMotion) return;\n\n const ctx = gsap.context(() => {\n // Animate the vertical timeline line drawing in\n const line = timelineRef.current?.querySelector('.timeline-line');\n if (line) {\n const length = (line as SVGLineElement).getTotalLength?.()\n || sectionRef.current?.offsetHeight\n || 800;\n\n gsap.set(line, {\n strokeDasharray: length,\n strokeDashoffset: length,\n });\n\n gsap.to(line, {\n strokeDashoffset: 0,\n ease: 'none',\n scrollTrigger: {\n trigger: sectionRef.current,\n start: 'top 60%',\n end: 'bottom 40%',\n scrub: 1,\n },\n });\n }\n });\n\n return () => ctx.revert();\n }, [data.phases.length]);\n\n return (\n <section\n className=\"w-full py-20 md:py-28\"\n style={{ background: 'var(--sk-bg, #0a0a0a)' }}\n >\n <div className=\"max-w-5xl mx-auto px-6\">\n <ScrollReveal y={30}>\n <h2\n className=\"text-3xl md:text-4xl font-bold mb-4\"\n style={{\n color: 'var(--sk-text-primary, #ffffff)',\n fontFamily: 'var(--sk-font-heading, inherit)',\n }}\n >\n Our Strategy\n </h2>\n <p\n className=\"text-lg mb-16 max-w-2xl\"\n style={{ color: 'var(--sk-text-secondary, #a1a1aa)' }}\n >\n A phased approach to delivering measurable results.\n </p>\n </ScrollReveal>\n\n <div ref={sectionRef} className=\"relative\">\n {/* SVG vertical timeline line */}\n <svg\n ref={timelineRef}\n className=\"absolute left-6 md:left-8 top-0 bottom-0 hidden md:block\"\n width=\"2\"\n height=\"100%\"\n style={{ overflow: 'visible' }}\n >\n <line\n className=\"timeline-line\"\n x1=\"1\"\n y1=\"0\"\n x2=\"1\"\n y2=\"100%\"\n stroke=\"var(--sk-primary, #6366f1)\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n />\n </svg>\n\n <div className=\"flex flex-col gap-12\">\n {data.phases.map((phase, index) => (\n <ScrollReveal key={index} y={40} delay={index * 0.15}>\n <div className=\"flex gap-6 md:gap-8\">\n {/* Number circle on the timeline */}\n <div className=\"flex flex-col items-center shrink-0\">\n <div\n className=\"flex items-center justify-center w-12 h-12 md:w-16 md:h-16 rounded-full text-lg md:text-xl font-bold z-10\"\n style={{\n background: 'var(--sk-bg, #0a0a0a)',\n border: '3px solid var(--sk-primary, #6366f1)',\n color: 'var(--sk-primary, #6366f1)',\n boxShadow: '0 0 20px color-mix(in srgb, var(--sk-primary, #6366f1) 30%, transparent)',\n }}\n >\n {phase.number}\n </div>\n </div>\n\n {/* Phase content */}\n <GlassCard padding=\"lg\" hover={false} className=\"flex-1\">\n <div className=\"flex flex-col gap-3\">\n <div className=\"flex items-center gap-3 flex-wrap\">\n <h3\n className=\"text-xl font-semibold\"\n style={{ color: 'var(--sk-text-primary, #ffffff)' }}\n >\n {phase.title}\n </h3>\n {phase.timeline && (\n <span\n className=\"px-3 py-0.5 rounded-full text-xs font-medium\"\n style={{\n background: 'color-mix(in srgb, var(--sk-primary, #6366f1) 12%, transparent)',\n color: 'var(--sk-primary, #6366f1)',\n border: '1px solid color-mix(in srgb, var(--sk-primary, #6366f1) 20%, transparent)',\n }}\n >\n {phase.timeline}\n </span>\n )}\n </div>\n\n <p\n className=\"text-base leading-relaxed\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n >\n {phase.description}\n </p>\n\n {/* Deliverables */}\n {phase.deliverables && phase.deliverables.length > 0 && (\n <ul className=\"flex flex-col gap-2 mt-2\">\n {phase.deliverables.map((deliverable, di) => (\n <li key={di} className=\"flex items-start gap-2\">\n <svg\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 18 18\"\n fill=\"none\"\n className=\"shrink-0 mt-0.5\"\n style={{ color: '#10b981' }}\n >\n <path\n d=\"M15 4.5L6.75 12.75 3 9\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n <span\n className=\"text-sm\"\n style={{ color: 'var(--sk-text-secondary, #a1a1aa)' }}\n >\n {deliverable}\n </span>\n </li>\n ))}\n </ul>\n )}\n </div>\n </GlassCard>\n </div>\n </ScrollReveal>\n ))}\n </div>\n </div>\n </div>\n </section>\n );\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/portfolio/components/sections/StrategySection.tsx"],"names":[],"mappings":"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","file":"StrategySection-VUWMIYYP.js","sourcesContent":["'use client';\n\nimport React, { useRef, useEffect } from 'react';\nimport gsap from 'gsap';\nimport { ScrollTrigger } from 'gsap/ScrollTrigger';\nimport type { PortfolioStrategyData } from '../../../types';\nimport ScrollReveal from '../primitives/ScrollReveal';\nimport GlassCard from '../primitives/GlassCard';\n\ninterface StrategySectionProps {\n data: PortfolioStrategyData;\n}\n\nexport default function StrategySection({ data }: StrategySectionProps) {\n const timelineRef = useRef<SVGSVGElement>(null);\n const sectionRef = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n gsap.registerPlugin(ScrollTrigger);\n\n const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n if (prefersReducedMotion) return;\n\n const ctx = gsap.context(() => {\n // Animate the vertical timeline line drawing in\n const line = timelineRef.current?.querySelector('.timeline-line');\n if (line) {\n const length = (line as SVGLineElement).getTotalLength?.()\n || sectionRef.current?.offsetHeight\n || 800;\n\n gsap.set(line, {\n strokeDasharray: length,\n strokeDashoffset: length,\n });\n\n gsap.to(line, {\n strokeDashoffset: 0,\n ease: 'none',\n scrollTrigger: {\n trigger: sectionRef.current,\n start: 'top 60%',\n end: 'bottom 40%',\n scrub: 1,\n },\n });\n }\n });\n\n return () => ctx.revert();\n }, [data.phases.length]);\n\n return (\n <section\n className=\"w-full py-20 md:py-28\"\n style={{ background: 'var(--sk-bg, #0a0a0a)' }}\n >\n <div className=\"max-w-5xl mx-auto px-6\">\n <ScrollReveal y={30}>\n <h2\n className=\"text-3xl md:text-4xl font-bold mb-4\"\n style={{\n color: 'var(--sk-text-primary, #ffffff)',\n fontFamily: 'var(--sk-font-heading, inherit)',\n }}\n >\n Our Strategy\n </h2>\n <p\n className=\"text-lg mb-16 max-w-2xl\"\n style={{ color: 'var(--sk-text-secondary, #a1a1aa)' }}\n >\n A phased approach to delivering measurable results.\n </p>\n </ScrollReveal>\n\n <div ref={sectionRef} className=\"relative\">\n {/* SVG vertical timeline line */}\n <svg\n ref={timelineRef}\n className=\"absolute left-6 md:left-8 top-0 bottom-0 hidden md:block\"\n width=\"2\"\n height=\"100%\"\n style={{ overflow: 'visible' }}\n >\n <line\n className=\"timeline-line\"\n x1=\"1\"\n y1=\"0\"\n x2=\"1\"\n y2=\"100%\"\n stroke=\"var(--sk-primary, #6366f1)\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n />\n </svg>\n\n <div className=\"flex flex-col gap-12\">\n {data.phases.map((phase, index) => (\n <ScrollReveal key={index} y={40} delay={index * 0.15}>\n <div className=\"flex gap-6 md:gap-8\">\n {/* Number circle on the timeline */}\n <div className=\"flex flex-col items-center shrink-0\">\n <div\n className=\"flex items-center justify-center w-12 h-12 md:w-16 md:h-16 rounded-full text-lg md:text-xl font-bold z-10\"\n style={{\n background: 'var(--sk-bg, #0a0a0a)',\n border: '3px solid var(--sk-primary, #6366f1)',\n color: 'var(--sk-primary, #6366f1)',\n boxShadow: '0 0 20px color-mix(in srgb, var(--sk-primary, #6366f1) 30%, transparent)',\n }}\n >\n {phase.number}\n </div>\n </div>\n\n {/* Phase content */}\n <GlassCard padding=\"lg\" hover={false} className=\"flex-1\">\n <div className=\"flex flex-col gap-3\">\n <div className=\"flex items-center gap-3 flex-wrap\">\n <h3\n className=\"text-xl font-semibold\"\n style={{ color: 'var(--sk-text-primary, #ffffff)' }}\n >\n {phase.title}\n </h3>\n {phase.timeline && (\n <span\n className=\"px-3 py-0.5 rounded-full text-xs font-medium\"\n style={{\n background: 'color-mix(in srgb, var(--sk-primary, #6366f1) 12%, transparent)',\n color: 'var(--sk-primary, #6366f1)',\n border: '1px solid color-mix(in srgb, var(--sk-primary, #6366f1) 20%, transparent)',\n }}\n >\n {phase.timeline}\n </span>\n )}\n </div>\n\n <p\n className=\"text-base leading-relaxed\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n >\n {phase.description}\n </p>\n\n {/* Deliverables */}\n {phase.deliverables && phase.deliverables.length > 0 && (\n <ul className=\"flex flex-col gap-2 mt-2\">\n {phase.deliverables.map((deliverable, di) => (\n <li key={di} className=\"flex items-start gap-2\">\n <svg\n width=\"18\"\n height=\"18\"\n viewBox=\"0 0 18 18\"\n fill=\"none\"\n className=\"shrink-0 mt-0.5\"\n style={{ color: '#10b981' }}\n >\n <path\n d=\"M15 4.5L6.75 12.75 3 9\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n <span\n className=\"text-sm\"\n style={{ color: 'var(--sk-text-secondary, #a1a1aa)' }}\n >\n {deliverable}\n </span>\n </li>\n ))}\n </ul>\n )}\n </div>\n </GlassCard>\n </div>\n </ScrollReveal>\n ))}\n </div>\n </div>\n </div>\n </section>\n );\n}\n"]}
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
import { GlassCard } from './chunk-YB4B3OMC.js';
|
|
3
|
-
import { ScrollReveal } from './chunk-7CFFAKDM.js';
|
|
4
|
-
import { useMemo } from 'react';
|
|
5
|
-
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
6
|
-
|
|
7
|
-
function TechStackSection({ data }) {
|
|
8
|
-
const grouped = useMemo(() => {
|
|
9
|
-
const groups = {};
|
|
10
|
-
for (const tech of data.technologies) {
|
|
11
|
-
const cat = tech.category || "Other";
|
|
12
|
-
if (!groups[cat]) groups[cat] = [];
|
|
13
|
-
groups[cat].push(tech);
|
|
14
|
-
}
|
|
15
|
-
return Object.entries(groups);
|
|
16
|
-
}, [data.technologies]);
|
|
17
|
-
return /* @__PURE__ */ jsx(
|
|
18
|
-
"section",
|
|
19
|
-
{
|
|
20
|
-
className: "w-full py-20 md:py-28",
|
|
21
|
-
style: { background: "var(--sk-bg, #0a0a0a)" },
|
|
22
|
-
children: /* @__PURE__ */ jsxs("div", { className: "max-w-7xl mx-auto px-6", children: [
|
|
23
|
-
/* @__PURE__ */ jsxs(ScrollReveal, { y: 30, children: [
|
|
24
|
-
/* @__PURE__ */ jsx(
|
|
25
|
-
"h2",
|
|
26
|
-
{
|
|
27
|
-
className: "text-3xl md:text-4xl font-bold mb-4",
|
|
28
|
-
style: {
|
|
29
|
-
color: "var(--sk-text-primary, #ffffff)",
|
|
30
|
-
fontFamily: "var(--sk-font-heading, inherit)"
|
|
31
|
-
},
|
|
32
|
-
children: "Tech Stack"
|
|
33
|
-
}
|
|
34
|
-
),
|
|
35
|
-
/* @__PURE__ */ jsx(
|
|
36
|
-
"p",
|
|
37
|
-
{
|
|
38
|
-
className: "text-lg mb-12 max-w-2xl",
|
|
39
|
-
style: { color: "var(--sk-text-secondary, #a1a1aa)" },
|
|
40
|
-
children: "The technologies and tools powering this project."
|
|
41
|
-
}
|
|
42
|
-
)
|
|
43
|
-
] }),
|
|
44
|
-
/* @__PURE__ */ jsx("div", { className: "flex flex-col gap-10", children: grouped.map(([category, techs], gi) => /* @__PURE__ */ jsx(ScrollReveal, { y: 30, delay: gi * 0.1, children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4", children: [
|
|
45
|
-
/* @__PURE__ */ jsx(
|
|
46
|
-
"h3",
|
|
47
|
-
{
|
|
48
|
-
className: "text-sm font-semibold uppercase tracking-wider",
|
|
49
|
-
style: { color: "var(--sk-text-tertiary, #71717a)" },
|
|
50
|
-
children: category
|
|
51
|
-
}
|
|
52
|
-
),
|
|
53
|
-
/* @__PURE__ */ jsx(ScrollReveal, { stagger: 0.06, children: /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-3", children: techs.map((tech, ti) => /* @__PURE__ */ jsxs(
|
|
54
|
-
GlassCard,
|
|
55
|
-
{
|
|
56
|
-
padding: "sm",
|
|
57
|
-
hover: true,
|
|
58
|
-
className: "inline-flex items-center gap-2",
|
|
59
|
-
children: [
|
|
60
|
-
tech.icon && /* @__PURE__ */ jsx(
|
|
61
|
-
"img",
|
|
62
|
-
{
|
|
63
|
-
src: tech.icon,
|
|
64
|
-
alt: tech.name,
|
|
65
|
-
width: 20,
|
|
66
|
-
height: 20,
|
|
67
|
-
className: "w-5 h-5 object-contain"
|
|
68
|
-
}
|
|
69
|
-
),
|
|
70
|
-
/* @__PURE__ */ jsx(
|
|
71
|
-
"span",
|
|
72
|
-
{
|
|
73
|
-
className: "text-sm font-medium whitespace-nowrap",
|
|
74
|
-
style: { color: "var(--sk-text-primary, #ffffff)" },
|
|
75
|
-
children: tech.name
|
|
76
|
-
}
|
|
77
|
-
)
|
|
78
|
-
]
|
|
79
|
-
},
|
|
80
|
-
ti
|
|
81
|
-
)) }) })
|
|
82
|
-
] }) }, category)) })
|
|
83
|
-
] })
|
|
84
|
-
}
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export { TechStackSection as default };
|
|
89
|
-
//# sourceMappingURL=TechStackSection-OCUYG4XT.js.map
|
|
90
|
-
//# sourceMappingURL=TechStackSection-OCUYG4XT.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/portfolio/components/sections/TechStackSection.tsx"],"names":[],"mappings":"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","file":"TechStackSection-OCUYG4XT.js","sourcesContent":["'use client';\n\nimport React, { useMemo } from 'react';\nimport type { PortfolioTechStackData } from '../../../types';\nimport ScrollReveal from '../primitives/ScrollReveal';\nimport GlassCard from '../primitives/GlassCard';\n\ninterface TechStackSectionProps {\n data: PortfolioTechStackData;\n}\n\nexport default function TechStackSection({ data }: TechStackSectionProps) {\n // Group technologies by category\n const grouped = useMemo(() => {\n const groups: Record<string, typeof data.technologies> = {};\n for (const tech of data.technologies) {\n const cat = tech.category || 'Other';\n if (!groups[cat]) groups[cat] = [];\n groups[cat].push(tech);\n }\n return Object.entries(groups);\n }, [data.technologies]);\n\n return (\n <section\n className=\"w-full py-20 md:py-28\"\n style={{ background: 'var(--sk-bg, #0a0a0a)' }}\n >\n <div className=\"max-w-7xl mx-auto px-6\">\n <ScrollReveal y={30}>\n <h2\n className=\"text-3xl md:text-4xl font-bold mb-4\"\n style={{\n color: 'var(--sk-text-primary, #ffffff)',\n fontFamily: 'var(--sk-font-heading, inherit)',\n }}\n >\n Tech Stack\n </h2>\n <p\n className=\"text-lg mb-12 max-w-2xl\"\n style={{ color: 'var(--sk-text-secondary, #a1a1aa)' }}\n >\n The technologies and tools powering this project.\n </p>\n </ScrollReveal>\n\n <div className=\"flex flex-col gap-10\">\n {grouped.map(([category, techs], gi) => (\n <ScrollReveal key={category} y={30} delay={gi * 0.1}>\n <div className=\"flex flex-col gap-4\">\n <h3\n className=\"text-sm font-semibold uppercase tracking-wider\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n >\n {category}\n </h3>\n <ScrollReveal stagger={0.06}>\n <div className=\"flex flex-wrap gap-3\">\n {techs.map((tech, ti) => (\n <GlassCard\n key={ti}\n padding=\"sm\"\n hover\n className=\"inline-flex items-center gap-2\"\n >\n {tech.icon && (\n <img\n src={tech.icon}\n alt={tech.name}\n width={20}\n height={20}\n className=\"w-5 h-5 object-contain\"\n />\n )}\n <span\n className=\"text-sm font-medium whitespace-nowrap\"\n style={{ color: 'var(--sk-text-primary, #ffffff)' }}\n >\n {tech.name}\n </span>\n </GlassCard>\n ))}\n </div>\n </ScrollReveal>\n </div>\n </ScrollReveal>\n ))}\n </div>\n </div>\n </section>\n );\n}\n"]}
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
var chunkKEOHORIH_cjs = require('./chunk-KEOHORIH.cjs');
|
|
4
|
-
var chunkIKBK7HYX_cjs = require('./chunk-IKBK7HYX.cjs');
|
|
5
|
-
var react = require('react');
|
|
6
|
-
var jsxRuntime = require('react/jsx-runtime');
|
|
7
|
-
|
|
8
|
-
function TechStackSection({ data }) {
|
|
9
|
-
const grouped = react.useMemo(() => {
|
|
10
|
-
const groups = {};
|
|
11
|
-
for (const tech of data.technologies) {
|
|
12
|
-
const cat = tech.category || "Other";
|
|
13
|
-
if (!groups[cat]) groups[cat] = [];
|
|
14
|
-
groups[cat].push(tech);
|
|
15
|
-
}
|
|
16
|
-
return Object.entries(groups);
|
|
17
|
-
}, [data.technologies]);
|
|
18
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
19
|
-
"section",
|
|
20
|
-
{
|
|
21
|
-
className: "w-full py-20 md:py-28",
|
|
22
|
-
style: { background: "var(--sk-bg, #0a0a0a)" },
|
|
23
|
-
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "max-w-7xl mx-auto px-6", children: [
|
|
24
|
-
/* @__PURE__ */ jsxRuntime.jsxs(chunkIKBK7HYX_cjs.ScrollReveal, { y: 30, children: [
|
|
25
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
26
|
-
"h2",
|
|
27
|
-
{
|
|
28
|
-
className: "text-3xl md:text-4xl font-bold mb-4",
|
|
29
|
-
style: {
|
|
30
|
-
color: "var(--sk-text-primary, #ffffff)",
|
|
31
|
-
fontFamily: "var(--sk-font-heading, inherit)"
|
|
32
|
-
},
|
|
33
|
-
children: "Tech Stack"
|
|
34
|
-
}
|
|
35
|
-
),
|
|
36
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
37
|
-
"p",
|
|
38
|
-
{
|
|
39
|
-
className: "text-lg mb-12 max-w-2xl",
|
|
40
|
-
style: { color: "var(--sk-text-secondary, #a1a1aa)" },
|
|
41
|
-
children: "The technologies and tools powering this project."
|
|
42
|
-
}
|
|
43
|
-
)
|
|
44
|
-
] }),
|
|
45
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-10", children: grouped.map(([category, techs], gi) => /* @__PURE__ */ jsxRuntime.jsx(chunkIKBK7HYX_cjs.ScrollReveal, { y: 30, delay: gi * 0.1, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-4", children: [
|
|
46
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
47
|
-
"h3",
|
|
48
|
-
{
|
|
49
|
-
className: "text-sm font-semibold uppercase tracking-wider",
|
|
50
|
-
style: { color: "var(--sk-text-tertiary, #71717a)" },
|
|
51
|
-
children: category
|
|
52
|
-
}
|
|
53
|
-
),
|
|
54
|
-
/* @__PURE__ */ jsxRuntime.jsx(chunkIKBK7HYX_cjs.ScrollReveal, { stagger: 0.06, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-3", children: techs.map((tech, ti) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
55
|
-
chunkKEOHORIH_cjs.GlassCard,
|
|
56
|
-
{
|
|
57
|
-
padding: "sm",
|
|
58
|
-
hover: true,
|
|
59
|
-
className: "inline-flex items-center gap-2",
|
|
60
|
-
children: [
|
|
61
|
-
tech.icon && /* @__PURE__ */ jsxRuntime.jsx(
|
|
62
|
-
"img",
|
|
63
|
-
{
|
|
64
|
-
src: tech.icon,
|
|
65
|
-
alt: tech.name,
|
|
66
|
-
width: 20,
|
|
67
|
-
height: 20,
|
|
68
|
-
className: "w-5 h-5 object-contain"
|
|
69
|
-
}
|
|
70
|
-
),
|
|
71
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
72
|
-
"span",
|
|
73
|
-
{
|
|
74
|
-
className: "text-sm font-medium whitespace-nowrap",
|
|
75
|
-
style: { color: "var(--sk-text-primary, #ffffff)" },
|
|
76
|
-
children: tech.name
|
|
77
|
-
}
|
|
78
|
-
)
|
|
79
|
-
]
|
|
80
|
-
},
|
|
81
|
-
ti
|
|
82
|
-
)) }) })
|
|
83
|
-
] }) }, category)) })
|
|
84
|
-
] })
|
|
85
|
-
}
|
|
86
|
-
);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
module.exports = TechStackSection;
|
|
90
|
-
//# sourceMappingURL=TechStackSection-VKJK4KQB.cjs.map
|
|
91
|
-
//# sourceMappingURL=TechStackSection-VKJK4KQB.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/portfolio/components/sections/TechStackSection.tsx"],"names":["useMemo","jsx","jsxs","ScrollReveal","GlassCard"],"mappings":"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","file":"TechStackSection-VKJK4KQB.cjs","sourcesContent":["'use client';\n\nimport React, { useMemo } from 'react';\nimport type { PortfolioTechStackData } from '../../../types';\nimport ScrollReveal from '../primitives/ScrollReveal';\nimport GlassCard from '../primitives/GlassCard';\n\ninterface TechStackSectionProps {\n data: PortfolioTechStackData;\n}\n\nexport default function TechStackSection({ data }: TechStackSectionProps) {\n // Group technologies by category\n const grouped = useMemo(() => {\n const groups: Record<string, typeof data.technologies> = {};\n for (const tech of data.technologies) {\n const cat = tech.category || 'Other';\n if (!groups[cat]) groups[cat] = [];\n groups[cat].push(tech);\n }\n return Object.entries(groups);\n }, [data.technologies]);\n\n return (\n <section\n className=\"w-full py-20 md:py-28\"\n style={{ background: 'var(--sk-bg, #0a0a0a)' }}\n >\n <div className=\"max-w-7xl mx-auto px-6\">\n <ScrollReveal y={30}>\n <h2\n className=\"text-3xl md:text-4xl font-bold mb-4\"\n style={{\n color: 'var(--sk-text-primary, #ffffff)',\n fontFamily: 'var(--sk-font-heading, inherit)',\n }}\n >\n Tech Stack\n </h2>\n <p\n className=\"text-lg mb-12 max-w-2xl\"\n style={{ color: 'var(--sk-text-secondary, #a1a1aa)' }}\n >\n The technologies and tools powering this project.\n </p>\n </ScrollReveal>\n\n <div className=\"flex flex-col gap-10\">\n {grouped.map(([category, techs], gi) => (\n <ScrollReveal key={category} y={30} delay={gi * 0.1}>\n <div className=\"flex flex-col gap-4\">\n <h3\n className=\"text-sm font-semibold uppercase tracking-wider\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n >\n {category}\n </h3>\n <ScrollReveal stagger={0.06}>\n <div className=\"flex flex-wrap gap-3\">\n {techs.map((tech, ti) => (\n <GlassCard\n key={ti}\n padding=\"sm\"\n hover\n className=\"inline-flex items-center gap-2\"\n >\n {tech.icon && (\n <img\n src={tech.icon}\n alt={tech.name}\n width={20}\n height={20}\n className=\"w-5 h-5 object-contain\"\n />\n )}\n <span\n className=\"text-sm font-medium whitespace-nowrap\"\n style={{ color: 'var(--sk-text-primary, #ffffff)' }}\n >\n {tech.name}\n </span>\n </GlassCard>\n ))}\n </div>\n </ScrollReveal>\n </div>\n </ScrollReveal>\n ))}\n </div>\n </div>\n </section>\n );\n}\n"]}
|