@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.
Files changed (69) hide show
  1. package/dist/{chunk-4IES23GV.js → chunk-2RW4C42Y.js} +16 -4
  2. package/dist/chunk-2RW4C42Y.js.map +1 -0
  3. package/dist/{chunk-QGSW43TM.cjs → chunk-2Y2RJB3A.cjs} +12 -12
  4. package/dist/{chunk-QGSW43TM.cjs.map → chunk-2Y2RJB3A.cjs.map} +1 -1
  5. package/dist/{chunk-UXUU7FVT.cjs → chunk-6OWFFV6N.cjs} +4 -65
  6. package/dist/chunk-6OWFFV6N.cjs.map +1 -0
  7. package/dist/{chunk-NLGUPJZA.js → chunk-7FFZX7C5.js} +4 -4
  8. package/dist/{chunk-NLGUPJZA.js.map → chunk-7FFZX7C5.js.map} +1 -1
  9. package/dist/{chunk-7ZHRMWJU.cjs → chunk-A5WCMHAE.cjs} +2 -2
  10. package/dist/{chunk-7ZHRMWJU.cjs.map → chunk-A5WCMHAE.cjs.map} +1 -1
  11. package/dist/chunk-FM25LB67.js +50 -0
  12. package/dist/chunk-FM25LB67.js.map +1 -0
  13. package/dist/{chunk-KXLROAO5.js → chunk-FT2SNEYP.js} +2 -2
  14. package/dist/{chunk-KXLROAO5.js.map → chunk-FT2SNEYP.js.map} +1 -1
  15. package/dist/{chunk-5MEZU3OG.cjs → chunk-OG5RBKQJ.cjs} +15 -3
  16. package/dist/chunk-OG5RBKQJ.cjs.map +1 -0
  17. package/dist/chunk-X76U7VLF.cjs +57 -0
  18. package/dist/chunk-X76U7VLF.cjs.map +1 -0
  19. package/dist/{chunk-RWQ675N6.js → chunk-X7UCJ45A.js} +4 -61
  20. package/dist/chunk-X7UCJ45A.js.map +1 -0
  21. package/dist/format-C-axf13G.d.cts +68 -0
  22. package/dist/format-poUpLtQ2.d.ts +68 -0
  23. package/dist/index.cjs +25 -24
  24. package/dist/index.cjs.map +1 -1
  25. package/dist/index.d.cts +5 -3
  26. package/dist/index.d.ts +5 -3
  27. package/dist/index.js +5 -4
  28. package/dist/index.js.map +1 -1
  29. package/dist/layout/index.cjs +2 -2
  30. package/dist/layout/index.d.cts +12 -2
  31. package/dist/layout/index.d.ts +12 -2
  32. package/dist/layout/index.js +1 -1
  33. package/dist/portfolio/client.cjs +2 -17
  34. package/dist/portfolio/client.cjs.map +1 -1
  35. package/dist/portfolio/client.js +1 -16
  36. package/dist/portfolio/client.js.map +1 -1
  37. package/dist/portfolio/index.cjs +18 -17
  38. package/dist/portfolio/index.d.cts +5 -74
  39. package/dist/portfolio/index.d.ts +5 -74
  40. package/dist/portfolio/index.js +4 -3
  41. package/dist/portfolio/sections/ChallengesSection.d.cts +1 -1
  42. package/dist/portfolio/sections/ChallengesSection.d.ts +1 -1
  43. package/dist/portfolio/sections/DetailsSection.d.cts +1 -1
  44. package/dist/portfolio/sections/DetailsSection.d.ts +1 -1
  45. package/dist/portfolio/sections/HeroSection.d.cts +1 -1
  46. package/dist/portfolio/sections/HeroSection.d.ts +1 -1
  47. package/dist/portfolio/sections/ResultsSection.cjs +4 -3
  48. package/dist/portfolio/sections/ResultsSection.cjs.map +1 -1
  49. package/dist/portfolio/sections/ResultsSection.d.cts +1 -1
  50. package/dist/portfolio/sections/ResultsSection.d.ts +1 -1
  51. package/dist/portfolio/sections/ResultsSection.js +2 -1
  52. package/dist/portfolio/sections/ResultsSection.js.map +1 -1
  53. package/dist/portfolio/sections/ServicesSection.d.cts +1 -1
  54. package/dist/portfolio/sections/ServicesSection.d.ts +1 -1
  55. package/dist/portfolio/sections/TechStackSection.d.cts +1 -1
  56. package/dist/portfolio/sections/TechStackSection.d.ts +1 -1
  57. package/dist/portfolio/sections/TestimonialSection.d.cts +1 -1
  58. package/dist/portfolio/sections/TestimonialSection.d.ts +1 -1
  59. package/dist/portfolio/server.cjs +40 -14
  60. package/dist/portfolio/server.d.cts +3 -1
  61. package/dist/portfolio/server.d.ts +3 -1
  62. package/dist/portfolio/server.js +3 -1
  63. package/dist/{types-0tVPy4pU.d.ts → types-He5A9okM.d.cts} +1 -1
  64. package/dist/{types-0tVPy4pU.d.cts → types-He5A9okM.d.ts} +1 -1
  65. package/package.json +3 -3
  66. package/dist/chunk-4IES23GV.js.map +0 -1
  67. package/dist/chunk-5MEZU3OG.cjs.map +0 -1
  68. package/dist/chunk-RWQ675N6.js.map +0 -1
  69. package/dist/chunk-UXUU7FVT.cjs.map +0 -1
package/dist/index.cjs CHANGED
@@ -1,10 +1,11 @@
1
1
  'use strict';
2
2
 
3
- var chunkQGSW43TM_cjs = require('./chunk-QGSW43TM.cjs');
3
+ var chunk2Y2RJB3A_cjs = require('./chunk-2Y2RJB3A.cjs');
4
+ var chunk6OWFFV6N_cjs = require('./chunk-6OWFFV6N.cjs');
5
+ require('./chunk-A5WCMHAE.cjs');
4
6
  var chunkZBPF7TLZ_cjs = require('./chunk-ZBPF7TLZ.cjs');
5
- var chunkUXUU7FVT_cjs = require('./chunk-UXUU7FVT.cjs');
6
- require('./chunk-7ZHRMWJU.cjs');
7
- var chunk5MEZU3OG_cjs = require('./chunk-5MEZU3OG.cjs');
7
+ var chunkX76U7VLF_cjs = require('./chunk-X76U7VLF.cjs');
8
+ var chunkOG5RBKQJ_cjs = require('./chunk-OG5RBKQJ.cjs');
8
9
  require('./chunk-FCKDJ4MJ.cjs');
9
10
  require('./chunk-HQGAX2LI.cjs');
10
11
  var chunkXOWSXZYW_cjs = require('./chunk-XOWSXZYW.cjs');
@@ -200,27 +201,31 @@ function Icon({
200
201
 
201
202
  Object.defineProperty(exports, "PORTFOLIO_SECTION_TYPES", {
202
203
  enumerable: true,
203
- get: function () { return chunkQGSW43TM_cjs.PORTFOLIO_SECTION_TYPES; }
204
+ get: function () { return chunk2Y2RJB3A_cjs.PORTFOLIO_SECTION_TYPES; }
204
205
  });
205
206
  Object.defineProperty(exports, "PortfolioIndex", {
206
207
  enumerable: true,
207
- get: function () { return chunkQGSW43TM_cjs.PortfolioIndex; }
208
+ get: function () { return chunk2Y2RJB3A_cjs.PortfolioIndex; }
208
209
  });
209
210
  Object.defineProperty(exports, "PortfolioIndexSkeleton", {
210
211
  enumerable: true,
211
- get: function () { return chunkQGSW43TM_cjs.PortfolioIndexSkeleton; }
212
+ get: function () { return chunk2Y2RJB3A_cjs.PortfolioIndexSkeleton; }
212
213
  });
213
214
  Object.defineProperty(exports, "PortfolioPage", {
214
215
  enumerable: true,
215
- get: function () { return chunkQGSW43TM_cjs.PortfolioPage; }
216
+ get: function () { return chunk2Y2RJB3A_cjs.PortfolioPage; }
216
217
  });
217
218
  Object.defineProperty(exports, "PortfolioPageSkeleton", {
218
219
  enumerable: true,
219
- get: function () { return chunkQGSW43TM_cjs.PortfolioPageSkeleton; }
220
+ get: function () { return chunk2Y2RJB3A_cjs.PortfolioPageSkeleton; }
220
221
  });
221
222
  Object.defineProperty(exports, "PortfolioSchema", {
222
223
  enumerable: true,
223
- get: function () { return chunkQGSW43TM_cjs.PortfolioSchema; }
224
+ get: function () { return chunk2Y2RJB3A_cjs.PortfolioSchema; }
225
+ });
226
+ Object.defineProperty(exports, "MetricsProvenance", {
227
+ enumerable: true,
228
+ get: function () { return chunk6OWFFV6N_cjs.MetricsProvenance; }
224
229
  });
225
230
  Object.defineProperty(exports, "formatCategoryLabel", {
226
231
  enumerable: true,
@@ -230,49 +235,45 @@ Object.defineProperty(exports, "formatServiceTag", {
230
235
  enumerable: true,
231
236
  get: function () { return chunkZBPF7TLZ_cjs.formatServiceTag; }
232
237
  });
233
- Object.defineProperty(exports, "MetricsProvenance", {
234
- enumerable: true,
235
- get: function () { return chunkUXUU7FVT_cjs.MetricsProvenance; }
236
- });
237
238
  Object.defineProperty(exports, "curateHeroKpis", {
238
239
  enumerable: true,
239
- get: function () { return chunkUXUU7FVT_cjs.curateHeroKpis; }
240
+ get: function () { return chunkX76U7VLF_cjs.curateHeroKpis; }
240
241
  });
241
242
  Object.defineProperty(exports, "curatePortfolioProof", {
242
243
  enumerable: true,
243
- get: function () { return chunkUXUU7FVT_cjs.curatePortfolioProof; }
244
+ get: function () { return chunkX76U7VLF_cjs.curatePortfolioProof; }
244
245
  });
245
246
  Object.defineProperty(exports, "curateResultItems", {
246
247
  enumerable: true,
247
- get: function () { return chunkUXUU7FVT_cjs.curateResultItems; }
248
+ get: function () { return chunkX76U7VLF_cjs.curateResultItems; }
248
249
  });
249
250
  Object.defineProperty(exports, "gateMetricsDeltas", {
250
251
  enumerable: true,
251
- get: function () { return chunkUXUU7FVT_cjs.gateMetricsDeltas; }
252
+ get: function () { return chunkX76U7VLF_cjs.gateMetricsDeltas; }
252
253
  });
253
254
  Object.defineProperty(exports, "AgencySiteKitLayout", {
254
255
  enumerable: true,
255
- get: function () { return chunk5MEZU3OG_cjs.AgencySiteKitLayout; }
256
+ get: function () { return chunkOG5RBKQJ_cjs.AgencySiteKitLayout; }
256
257
  });
257
258
  Object.defineProperty(exports, "DEFAULT_BRAND_CONFIG", {
258
259
  enumerable: true,
259
- get: function () { return chunk5MEZU3OG_cjs.DEFAULT_BRAND_CONFIG; }
260
+ get: function () { return chunkOG5RBKQJ_cjs.DEFAULT_BRAND_CONFIG; }
260
261
  });
261
262
  Object.defineProperty(exports, "DEFAULT_DARK_MODE", {
262
263
  enumerable: true,
263
- get: function () { return chunk5MEZU3OG_cjs.DEFAULT_DARK_MODE; }
264
+ get: function () { return chunkOG5RBKQJ_cjs.DEFAULT_DARK_MODE; }
264
265
  });
265
266
  Object.defineProperty(exports, "generateBrandCSS", {
266
267
  enumerable: true,
267
- get: function () { return chunk5MEZU3OG_cjs.generateBrandCSS; }
268
+ get: function () { return chunkOG5RBKQJ_cjs.generateBrandCSS; }
268
269
  });
269
270
  Object.defineProperty(exports, "hexToRgb", {
270
271
  enumerable: true,
271
- get: function () { return chunk5MEZU3OG_cjs.hexToRgb; }
272
+ get: function () { return chunkOG5RBKQJ_cjs.hexToRgb; }
272
273
  });
273
274
  Object.defineProperty(exports, "mergeBrandConfig", {
274
275
  enumerable: true,
275
- get: function () { return chunk5MEZU3OG_cjs.mergeBrandConfig; }
276
+ get: function () { return chunkOG5RBKQJ_cjs.mergeBrandConfig; }
276
277
  });
277
278
  Object.defineProperty(exports, "apiFetch", {
278
279
  enumerable: true,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/shared/icons.ts"],"names":["React"],"mappings":";;;;;;;;;;;;;;;;AAqBA,IAAM,aAAA,GAAgD;AAAA,EACpD,UAAA,EAAY;AAAA,IACV,OAAA,EAAS,WAAA;AAAA,IACT,KAAA,EAAO,CAAC,UAAA,EAAY,eAAe;AAAA,GACrC;AAAA,EACA,YAAA,EAAc;AAAA,IACZ,OAAA,EAAS,WAAA;AAAA,IACT,KAAA,EAAO,CAAC,UAAA,EAAY,iBAAiB;AAAA,GACvC;AAAA,EACA,WAAA,EAAa;AAAA,IACX,OAAA,EAAS,WAAA;AAAA,IACT,KAAA,EAAO,CAAC,WAAA,EAAa,UAAA,EAAY,UAAU;AAAA,GAC7C;AAAA,EACA,KAAA,EAAO;AAAA,IACL,OAAA,EAAS,WAAA;AAAA,IACT,KAAA,EAAO,CAAC,iBAAiB;AAAA,GAC3B;AAAA,EACA,KAAA,EAAO;AAAA,IACL,OAAA,EAAS,WAAA;AAAA,IACT,KAAA,EAAO,CAAC,0CAAA,EAA4C,aAAa;AAAA,GACnE;AAAA,EACA,IAAA,EAAM;AAAA,IACJ,OAAA,EAAS,WAAA;AAAA,IACT,KAAA,EAAO,CAAC,gBAAA,EAAkB,eAAe;AAAA,GAC3C;AAAA,EACA,KAAA,EAAO;AAAA,IACL,OAAA,EAAS,WAAA;AAAA,IACT,KAAA,EAAO;AAAA,MACL,0CAAA;AAAA,MACA,UAAA;AAAA,MACA;AAAA;AACF,GACF;AAAA,EACA,MAAA,EAAQ;AAAA,IACN,OAAA,EAAS,WAAA;AAAA,IACT,KAAA,EAAO;AAAA,MACL,0EAAA;AAAA,MACA,SAAA;AAAA,MACA;AAAA;AACF,GACF;AAAA,EACA,SAAA,EAAW;AAAA,IACT,OAAA,EAAS,WAAA;AAAA,IACT,KAAA,EAAO,CAAC,gCAAgC;AAAA,GAC1C;AAAA,EACA,IAAA,EAAM;AAAA,IACJ,OAAA,EAAS,WAAA;AAAA,IACT,KAAA,EAAO;AAAA,MACL,6DAAA;AAAA,MACA;AAAA;AACF,GACF;AAAA,EACA,IAAA,EAAM;AAAA,IACJ,OAAA,EAAS,WAAA;AAAA,IACT,KAAA,EAAO;AAAA,MACL,2EAAA;AAAA,MACA;AAAA;AACF,GACF;AAAA,EACA,IAAA,EAAM;AAAA,IACJ,OAAA,EAAS,WAAA;AAAA,IACT,KAAA,EAAO;AAAA,MACL,6EAAA;AAAA,MACA;AAAA;AACF,GACF;AAAA,EACA,OAAA,EAAS;AAAA,IACP,OAAA,EAAS,WAAA;AAAA,IACT,KAAA,EAAO;AAAA,MACL,0EAAA;AAAA,MACA,SAAA;AAAA,MACA;AAAA;AACF,GACF;AAAA,EACA,OAAA,EAAS;AAAA,IACP,OAAA,EAAS,WAAA;AAAA,IACT,KAAA,EAAO;AAAA,MACL,+IAAA;AAAA,MACA,gDAAA;AAAA,MACA,8CAAA;AAAA,MACA,8CAAA;AAAA,MACA;AAAA;AACF,GACF;AAAA,EACA,MAAA,EAAQ;AAAA,IACN,OAAA,EAAS,WAAA;AAAA,IACT,KAAA,EAAO;AAAA,MACL,2FAAA;AAAA,MACA,kGAAA;AAAA,MACA,wCAAA;AAAA,MACA;AAAA;AACF,GACF;AAAA,EACA,MAAA,EAAQ;AAAA,IACN,OAAA,EAAS,WAAA;AAAA,IACT,KAAA,EAAO;AAAA,MACL,uCAAA;AAAA,MACA;AAAA;AACF,GACF;AAAA,EACA,QAAA,EAAU;AAAA,IACR,OAAA,EAAS,WAAA;AAAA,IACT,KAAA,EAAO;AAAA,MACL,qCAAA;AAAA,MACA;AAAA;AACF,GACF;AAAA,EACA,MAAA,EAAQ;AAAA,IACN,OAAA,EAAS,WAAA;AAAA,IACT,KAAA,EAAO,CAAC,6CAA6C;AAAA,GACvD;AAAA,EACA,IAAA,EAAM;AAAA,IACJ,OAAA,EAAS,WAAA;AAAA,IACT,KAAA,EAAO;AAAA,MACL;AAAA;AACF,GACF;AAAA,EACA,MAAA,EAAQ;AAAA,IACN,OAAA,EAAS,WAAA;AAAA,IACT,KAAA,EAAO;AAAA,MACL,2CAAA;AAAA,MACA,uCAAA;AAAA,MACA;AAAA;AACF,GACF;AAAA,EACA,aAAA,EAAe;AAAA,IACb,OAAA,EAAS,WAAA;AAAA,IACT,KAAA,EAAO,CAAC,yBAAA,EAA2B,WAAW;AAAA,GAChD;AAAA,EACA,KAAA,EAAO;AAAA,IACL,OAAA,EAAS,WAAA;AAAA,IACT,KAAA,EAAO;AAAA,MACL,2CAAA;AAAA,MACA,oCAAA;AAAA,MACA,4BAAA;AAAA,MACA;AAAA;AACF,GACF;AAAA,EACA,GAAA,EAAK;AAAA,IACH,OAAA,EAAS,WAAA;AAAA,IACT,KAAA,EAAO,CAAC,gCAAgC;AAAA;AAE5C,CAAA;AAMO,SAAS,kBAAkB,IAAA,EAAqC;AACrE,EAAA,OAAO,aAAA,CAAc,IAAI,CAAA,IAAK,IAAA;AAChC;AAMO,SAAS,aAAa,IAAA,EAA+B;AAC1D,EAAA,MAAM,GAAA,GAAM,cAAc,IAAI,CAAA;AAC9B,EAAA,OAAO,GAAA,GAAM,IAAI,KAAA,GAAQ,IAAA;AAC3B;AAKO,SAAS,iBAAA,GAA8B;AAC5C,EAAA,OAAO,MAAA,CAAO,KAAK,aAAa,CAAA;AAClC;AAUO,SAAS,UAAA,CACd,IAAA,EACA,IAAA,GAAe,EAAA,EACf,SAAA,EAC2B;AAC3B,EAAA,MAAM,GAAA,GAAM,cAAc,IAAI,CAAA;AAC9B,EAAA,IAAI,CAAC,KAAK,OAAO,IAAA;AAEjB,EAAA,OAAOA,sBAAA,CAAM,aAAA;AAAA,IACX,KAAA;AAAA,IACA;AAAA,MACE,KAAA,EAAO,4BAAA;AAAA,MACP,KAAA,EAAO,IAAA;AAAA,MACP,MAAA,EAAQ,IAAA;AAAA,MACR,SAAS,GAAA,CAAI,OAAA;AAAA,MACb,IAAA,EAAM,MAAA;AAAA,MACN,MAAA,EAAQ,cAAA;AAAA,MACR,WAAA,EAAa,CAAA;AAAA,MACb,aAAA,EAAe,OAAA;AAAA,MACf,cAAA,EAAgB,OAAA;AAAA,MAChB,SAAA;AAAA,MACA,aAAA,EAAe;AAAA,KACjB;AAAA,IACA,GAAG,IAAI,KAAA,CAAM,GAAA;AAAA,MAAI,CAAC,CAAA,EAAG,CAAA,KACnBA,sBAAA,CAAM,aAAA,CAAc,QAAQ,EAAE,GAAA,EAAK,CAAA,EAAG,CAAA,EAAG;AAAA;AAC3C,GACF;AACF;AAUO,SAAS,IAAA,CAAK;AAAA,EACnB,IAAA;AAAA,EACA,IAAA,GAAO,EAAA;AAAA,EACP;AACF,CAAA,EAI8B;AAC5B,EAAA,OAAO,UAAA,CAAW,IAAA,EAAM,IAAA,EAAM,SAAS,CAAA;AACzC","file":"index.cjs","sourcesContent":["/**\n * @sonordev/agency-site-kit — Icon registry\n *\n * Lightweight icon system that maps string names to SVG path data.\n * No external icon library dependency — keeps the bundle small.\n * Icons are rendered as inline SVGs.\n */\n\nimport React from 'react';\n\ninterface IconDefinition {\n viewBox: string;\n paths: string[];\n /** Optional fill-rule for complex paths */\n fillRule?: 'evenodd' | 'nonzero';\n}\n\n/**\n * Icon registry — 24x24 viewBox icons (matching Lucide/Heroicons conventions).\n * All paths use stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\".\n */\nconst ICON_REGISTRY: Record<string, IconDefinition> = {\n 'arrow-up': {\n viewBox: '0 0 24 24',\n paths: ['M12 19V5', 'M5 12l7-7 7 7'],\n },\n 'arrow-down': {\n viewBox: '0 0 24 24',\n paths: ['M12 5v14', 'M19 12l-7 7-7-7'],\n },\n 'chart-bar': {\n viewBox: '0 0 24 24',\n paths: ['M12 20V10', 'M18 20V4', 'M6 20v-4'],\n },\n check: {\n viewBox: '0 0 24 24',\n paths: ['M20 6L9 17l-5-5'],\n },\n clock: {\n viewBox: '0 0 24 24',\n paths: ['M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20z', 'M12 6v6l4 2'],\n },\n code: {\n viewBox: '0 0 24 24',\n paths: ['M16 18l6-6-6-6', 'M8 6l-6 6 6 6'],\n },\n globe: {\n viewBox: '0 0 24 24',\n paths: [\n 'M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20z',\n 'M2 12h20',\n 'M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10A15.3 15.3 0 0 1 12 2z',\n ],\n },\n layout: {\n viewBox: '0 0 24 24',\n paths: [\n 'M19 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2z',\n 'M3 9h18',\n 'M9 21V9',\n ],\n },\n lightning: {\n viewBox: '0 0 24 24',\n paths: ['M13 2L3 14h9l-1 8 10-12h-9l1-8'],\n },\n link: {\n viewBox: '0 0 24 24',\n paths: [\n 'M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71',\n 'M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71',\n ],\n },\n lock: {\n viewBox: '0 0 24 24',\n paths: [\n 'M19 11H5a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7a2 2 0 0 0-2-2z',\n 'M7 11V7a5 5 0 0 1 10 0v4',\n ],\n },\n mail: {\n viewBox: '0 0 24 24',\n paths: [\n 'M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z',\n 'M22 6l-10 7L2 6',\n ],\n },\n monitor: {\n viewBox: '0 0 24 24',\n paths: [\n 'M20 3H4a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2z',\n 'M8 21h8',\n 'M12 17v4',\n ],\n },\n palette: {\n viewBox: '0 0 24 24',\n paths: [\n 'M12 2C6.49 2 2 6.49 2 12s4.49 10 10 10a2 2 0 0 0 2-2v-.09a1.65 1.65 0 0 1 .92-1.48A1.65 1.65 0 0 1 16.5 19H19a2 2 0 0 0 2-2 10 10 0 0 0-9-15z',\n 'M6.5 11.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z',\n 'M10 7.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z',\n 'M14 7.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z',\n 'M17.5 11.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z',\n ],\n },\n rocket: {\n viewBox: '0 0 24 24',\n paths: [\n 'M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09z',\n 'M12 15l-3-3a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 0 1-4 2z',\n 'M9 12H4s.55-3.03 2-4c1.62-1.08 5 0 5 0',\n 'M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5',\n ],\n },\n search: {\n viewBox: '0 0 24 24',\n paths: [\n 'M11 19a8 8 0 1 0 0-16 8 8 0 0 0 0 16z',\n 'M21 21l-4.35-4.35',\n ],\n },\n settings: {\n viewBox: '0 0 24 24',\n paths: [\n 'M12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6z',\n 'M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09a1.65 1.65 0 0 0-1.08-1.51 1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09a1.65 1.65 0 0 0 1.51-1.08 1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9c.26.604.852.997 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1.08z',\n ],\n },\n shield: {\n viewBox: '0 0 24 24',\n paths: ['M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z'],\n },\n star: {\n viewBox: '0 0 24 24',\n paths: [\n 'M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z',\n ],\n },\n target: {\n viewBox: '0 0 24 24',\n paths: [\n 'M12 22a10 10 0 1 0 0-20 10 10 0 0 0 0 20z',\n 'M12 18a6 6 0 1 0 0-12 6 6 0 0 0 0 12z',\n 'M12 14a2 2 0 1 0 0-4 2 2 0 0 0 0 4z',\n ],\n },\n 'trending-up': {\n viewBox: '0 0 24 24',\n paths: ['M23 6l-9.5 9.5-5-5L1 18', 'M17 6h6v6'],\n },\n users: {\n viewBox: '0 0 24 24',\n paths: [\n 'M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2',\n 'M9 11a4 4 0 1 0 0-8 4 4 0 0 0 0 8z',\n 'M23 21v-2a4 4 0 0 0-3-3.87',\n 'M16 3.13a4 4 0 0 1 0 7.75',\n ],\n },\n zap: {\n viewBox: '0 0 24 24',\n paths: ['M13 2L3 14h9l-1 8 10-12h-9l1-8'],\n },\n};\n\n/**\n * Returns the icon definition (viewBox + paths) for a given icon name.\n * Returns null if the icon is not found.\n */\nexport function getIconDefinition(name: string): IconDefinition | null {\n return ICON_REGISTRY[name] ?? null;\n}\n\n/**\n * Returns just the SVG path strings for a given icon name.\n * Useful when building custom SVG elements.\n */\nexport function getIconPaths(name: string): string[] | null {\n const def = ICON_REGISTRY[name];\n return def ? def.paths : null;\n}\n\n/**\n * Returns a list of all available icon names.\n */\nexport function getAvailableIcons(): string[] {\n return Object.keys(ICON_REGISTRY);\n}\n\n/**\n * Renders an icon as a React SVG element.\n *\n * @param name Icon name from the registry\n * @param size Width/height in pixels (default 24)\n * @param className Optional CSS class name\n * @returns React SVG element, or null if icon not found\n */\nexport function renderIcon(\n name: string,\n size: number = 24,\n className?: string,\n): React.ReactElement | null {\n const def = ICON_REGISTRY[name];\n if (!def) return null;\n\n return React.createElement(\n 'svg',\n {\n xmlns: 'http://www.w3.org/2000/svg',\n width: size,\n height: size,\n viewBox: def.viewBox,\n fill: 'none',\n stroke: 'currentColor',\n strokeWidth: 2,\n strokeLinecap: 'round' as const,\n strokeLinejoin: 'round' as const,\n className,\n 'aria-hidden': 'true',\n },\n ...def.paths.map((d, i) =>\n React.createElement('path', { key: i, d }),\n ),\n );\n}\n\n/**\n * Icon component for use in JSX.\n *\n * Usage:\n * ```tsx\n * <Icon name=\"trending-up\" size={20} className=\"text-green-500\" />\n * ```\n */\nexport function Icon({\n name,\n size = 24,\n className,\n}: {\n name: string;\n size?: number;\n className?: string;\n}): React.ReactElement | null {\n return renderIcon(name, size, className);\n}\n"]}
1
+ {"version":3,"sources":["../src/shared/icons.ts"],"names":["React"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * @sonordev/agency-site-kit — Icon registry\n *\n * Lightweight icon system that maps string names to SVG path data.\n * No external icon library dependency — keeps the bundle small.\n * Icons are rendered as inline SVGs.\n */\n\nimport React from 'react';\n\ninterface IconDefinition {\n viewBox: string;\n paths: string[];\n /** Optional fill-rule for complex paths */\n fillRule?: 'evenodd' | 'nonzero';\n}\n\n/**\n * Icon registry — 24x24 viewBox icons (matching Lucide/Heroicons conventions).\n * All paths use stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\".\n */\nconst ICON_REGISTRY: Record<string, IconDefinition> = {\n 'arrow-up': {\n viewBox: '0 0 24 24',\n paths: ['M12 19V5', 'M5 12l7-7 7 7'],\n },\n 'arrow-down': {\n viewBox: '0 0 24 24',\n paths: ['M12 5v14', 'M19 12l-7 7-7-7'],\n },\n 'chart-bar': {\n viewBox: '0 0 24 24',\n paths: ['M12 20V10', 'M18 20V4', 'M6 20v-4'],\n },\n check: {\n viewBox: '0 0 24 24',\n paths: ['M20 6L9 17l-5-5'],\n },\n clock: {\n viewBox: '0 0 24 24',\n paths: ['M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20z', 'M12 6v6l4 2'],\n },\n code: {\n viewBox: '0 0 24 24',\n paths: ['M16 18l6-6-6-6', 'M8 6l-6 6 6 6'],\n },\n globe: {\n viewBox: '0 0 24 24',\n paths: [\n 'M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20z',\n 'M2 12h20',\n 'M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10A15.3 15.3 0 0 1 12 2z',\n ],\n },\n layout: {\n viewBox: '0 0 24 24',\n paths: [\n 'M19 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2z',\n 'M3 9h18',\n 'M9 21V9',\n ],\n },\n lightning: {\n viewBox: '0 0 24 24',\n paths: ['M13 2L3 14h9l-1 8 10-12h-9l1-8'],\n },\n link: {\n viewBox: '0 0 24 24',\n paths: [\n 'M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71',\n 'M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71',\n ],\n },\n lock: {\n viewBox: '0 0 24 24',\n paths: [\n 'M19 11H5a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7a2 2 0 0 0-2-2z',\n 'M7 11V7a5 5 0 0 1 10 0v4',\n ],\n },\n mail: {\n viewBox: '0 0 24 24',\n paths: [\n 'M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z',\n 'M22 6l-10 7L2 6',\n ],\n },\n monitor: {\n viewBox: '0 0 24 24',\n paths: [\n 'M20 3H4a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2z',\n 'M8 21h8',\n 'M12 17v4',\n ],\n },\n palette: {\n viewBox: '0 0 24 24',\n paths: [\n 'M12 2C6.49 2 2 6.49 2 12s4.49 10 10 10a2 2 0 0 0 2-2v-.09a1.65 1.65 0 0 1 .92-1.48A1.65 1.65 0 0 1 16.5 19H19a2 2 0 0 0 2-2 10 10 0 0 0-9-15z',\n 'M6.5 11.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z',\n 'M10 7.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z',\n 'M14 7.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z',\n 'M17.5 11.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z',\n ],\n },\n rocket: {\n viewBox: '0 0 24 24',\n paths: [\n 'M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09z',\n 'M12 15l-3-3a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 0 1-4 2z',\n 'M9 12H4s.55-3.03 2-4c1.62-1.08 5 0 5 0',\n 'M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5',\n ],\n },\n search: {\n viewBox: '0 0 24 24',\n paths: [\n 'M11 19a8 8 0 1 0 0-16 8 8 0 0 0 0 16z',\n 'M21 21l-4.35-4.35',\n ],\n },\n settings: {\n viewBox: '0 0 24 24',\n paths: [\n 'M12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6z',\n 'M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09a1.65 1.65 0 0 0-1.08-1.51 1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09a1.65 1.65 0 0 0 1.51-1.08 1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9c.26.604.852.997 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1.08z',\n ],\n },\n shield: {\n viewBox: '0 0 24 24',\n paths: ['M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z'],\n },\n star: {\n viewBox: '0 0 24 24',\n paths: [\n 'M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z',\n ],\n },\n target: {\n viewBox: '0 0 24 24',\n paths: [\n 'M12 22a10 10 0 1 0 0-20 10 10 0 0 0 0 20z',\n 'M12 18a6 6 0 1 0 0-12 6 6 0 0 0 0 12z',\n 'M12 14a2 2 0 1 0 0-4 2 2 0 0 0 0 4z',\n ],\n },\n 'trending-up': {\n viewBox: '0 0 24 24',\n paths: ['M23 6l-9.5 9.5-5-5L1 18', 'M17 6h6v6'],\n },\n users: {\n viewBox: '0 0 24 24',\n paths: [\n 'M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2',\n 'M9 11a4 4 0 1 0 0-8 4 4 0 0 0 0 8z',\n 'M23 21v-2a4 4 0 0 0-3-3.87',\n 'M16 3.13a4 4 0 0 1 0 7.75',\n ],\n },\n zap: {\n viewBox: '0 0 24 24',\n paths: ['M13 2L3 14h9l-1 8 10-12h-9l1-8'],\n },\n};\n\n/**\n * Returns the icon definition (viewBox + paths) for a given icon name.\n * Returns null if the icon is not found.\n */\nexport function getIconDefinition(name: string): IconDefinition | null {\n return ICON_REGISTRY[name] ?? null;\n}\n\n/**\n * Returns just the SVG path strings for a given icon name.\n * Useful when building custom SVG elements.\n */\nexport function getIconPaths(name: string): string[] | null {\n const def = ICON_REGISTRY[name];\n return def ? def.paths : null;\n}\n\n/**\n * Returns a list of all available icon names.\n */\nexport function getAvailableIcons(): string[] {\n return Object.keys(ICON_REGISTRY);\n}\n\n/**\n * Renders an icon as a React SVG element.\n *\n * @param name Icon name from the registry\n * @param size Width/height in pixels (default 24)\n * @param className Optional CSS class name\n * @returns React SVG element, or null if icon not found\n */\nexport function renderIcon(\n name: string,\n size: number = 24,\n className?: string,\n): React.ReactElement | null {\n const def = ICON_REGISTRY[name];\n if (!def) return null;\n\n return React.createElement(\n 'svg',\n {\n xmlns: 'http://www.w3.org/2000/svg',\n width: size,\n height: size,\n viewBox: def.viewBox,\n fill: 'none',\n stroke: 'currentColor',\n strokeWidth: 2,\n strokeLinecap: 'round' as const,\n strokeLinejoin: 'round' as const,\n className,\n 'aria-hidden': 'true',\n },\n ...def.paths.map((d, i) =>\n React.createElement('path', { key: i, d }),\n ),\n );\n}\n\n/**\n * Icon component for use in JSX.\n *\n * Usage:\n * ```tsx\n * <Icon name=\"trending-up\" size={20} className=\"text-green-500\" />\n * ```\n */\nexport function Icon({\n name,\n size = 24,\n className,\n}: {\n name: string;\n size?: number;\n className?: string;\n}): React.ReactElement | null {\n return renderIcon(name, size, className);\n}\n"]}
package/dist/index.d.cts CHANGED
@@ -1,10 +1,12 @@
1
- import { B as BrandConfig } from './types-0tVPy4pU.cjs';
2
- export { j as MetricSource, M as MetricsDelta, k as MetricsSnapshot, l as PORTFOLIO_SECTION_TYPES, m as PortfolioAnnotation, n as PortfolioBeforeAfterData, o as PortfolioCTAData, p as PortfolioChallengeItem, c as PortfolioChallengesData, q as PortfolioConfigResponse, r as PortfolioConversionFunnelData, d as PortfolioDetailsData, s as PortfolioFeatureSpotlightData, t as PortfolioFunnelStage, u as PortfolioGalleryData, v as PortfolioGalleryImage, e as PortfolioHeroData, w as PortfolioItem, P as PortfolioItemFull, x as PortfolioKPI, b as PortfolioListResponse, y as PortfolioMetricsDataPoint, z as PortfolioMetricsTimelineData, A as PortfolioResultItem, f as PortfolioResultsData, a as PortfolioSection, C as PortfolioSectionData, D as PortfolioSectionDataMap, E as PortfolioSectionType, F as PortfolioSeoData, G as PortfolioServiceItem, g as PortfolioServicesData, H as PortfolioStrategyData, I as PortfolioStrategyPhase, J as PortfolioTeamData, K as PortfolioTeamMember, L as PortfolioTechItem, h as PortfolioTechStackData, i as PortfolioTestimonialData, N as PortfolioVideoData, S as SanityImageRef } from './types-0tVPy4pU.cjs';
1
+ import { B as BrandConfig } from './types-He5A9okM.cjs';
2
+ export { k as MetricSource, M as MetricsDelta, l as MetricsSnapshot, m as PORTFOLIO_SECTION_TYPES, n as PortfolioAnnotation, o as PortfolioBeforeAfterData, p as PortfolioCTAData, q as PortfolioChallengeItem, c as PortfolioChallengesData, r as PortfolioConfigResponse, s as PortfolioConversionFunnelData, d as PortfolioDetailsData, t as PortfolioFeatureSpotlightData, u as PortfolioFunnelStage, v as PortfolioGalleryData, w as PortfolioGalleryImage, e as PortfolioHeroData, x as PortfolioItem, P as PortfolioItemFull, y as PortfolioKPI, b as PortfolioListResponse, z as PortfolioMetricsDataPoint, A as PortfolioMetricsTimelineData, j as PortfolioResultItem, f as PortfolioResultsData, a as PortfolioSection, C as PortfolioSectionData, D as PortfolioSectionDataMap, E as PortfolioSectionType, F as PortfolioSeoData, G as PortfolioServiceItem, g as PortfolioServicesData, H as PortfolioStrategyData, I as PortfolioStrategyPhase, J as PortfolioTeamData, K as PortfolioTeamMember, L as PortfolioTechItem, h as PortfolioTechStackData, i as PortfolioTestimonialData, N as PortfolioVideoData, S as SanityImageRef } from './types-He5A9okM.cjs';
3
3
  import { ServerApiConfig } from '@sonordev/site-kit/server';
4
4
  import React from 'react';
5
5
  export { AgencySiteKitLayout } from './layout/index.cjs';
6
- export { MetricsProvenance, PortfolioIndex, PortfolioIndexProps, PortfolioIndexSkeleton, PortfolioPage, PortfolioPageSkeleton, PortfolioSchema, curateHeroKpis, curatePortfolioProof, curateResultItems, formatCategoryLabel, formatServiceTag, gateMetricsDeltas } from './portfolio/index.cjs';
6
+ export { MetricsProvenance, PortfolioIndex, PortfolioIndexProps, PortfolioIndexSkeleton, PortfolioPage, PortfolioPageSkeleton, PortfolioSchema } from './portfolio/index.cjs';
7
+ export { c as curatePortfolioProof, a as curateResultItems, f as formatCategoryLabel, b as formatServiceTag, g as gateMetricsDeltas } from './format-C-axf13G.cjs';
7
8
  export * from '@sonordev/agency-site-kit/portfolio/client';
9
+ export { curateHeroKpis } from '@sonordev/site-kit/portfolio/contract';
8
10
 
9
11
  /**
10
12
  * @sonordev/agency-site-kit — Brand customization system
package/dist/index.d.ts CHANGED
@@ -1,10 +1,12 @@
1
- import { B as BrandConfig } from './types-0tVPy4pU.js';
2
- export { j as MetricSource, M as MetricsDelta, k as MetricsSnapshot, l as PORTFOLIO_SECTION_TYPES, m as PortfolioAnnotation, n as PortfolioBeforeAfterData, o as PortfolioCTAData, p as PortfolioChallengeItem, c as PortfolioChallengesData, q as PortfolioConfigResponse, r as PortfolioConversionFunnelData, d as PortfolioDetailsData, s as PortfolioFeatureSpotlightData, t as PortfolioFunnelStage, u as PortfolioGalleryData, v as PortfolioGalleryImage, e as PortfolioHeroData, w as PortfolioItem, P as PortfolioItemFull, x as PortfolioKPI, b as PortfolioListResponse, y as PortfolioMetricsDataPoint, z as PortfolioMetricsTimelineData, A as PortfolioResultItem, f as PortfolioResultsData, a as PortfolioSection, C as PortfolioSectionData, D as PortfolioSectionDataMap, E as PortfolioSectionType, F as PortfolioSeoData, G as PortfolioServiceItem, g as PortfolioServicesData, H as PortfolioStrategyData, I as PortfolioStrategyPhase, J as PortfolioTeamData, K as PortfolioTeamMember, L as PortfolioTechItem, h as PortfolioTechStackData, i as PortfolioTestimonialData, N as PortfolioVideoData, S as SanityImageRef } from './types-0tVPy4pU.js';
1
+ import { B as BrandConfig } from './types-He5A9okM.js';
2
+ export { k as MetricSource, M as MetricsDelta, l as MetricsSnapshot, m as PORTFOLIO_SECTION_TYPES, n as PortfolioAnnotation, o as PortfolioBeforeAfterData, p as PortfolioCTAData, q as PortfolioChallengeItem, c as PortfolioChallengesData, r as PortfolioConfigResponse, s as PortfolioConversionFunnelData, d as PortfolioDetailsData, t as PortfolioFeatureSpotlightData, u as PortfolioFunnelStage, v as PortfolioGalleryData, w as PortfolioGalleryImage, e as PortfolioHeroData, x as PortfolioItem, P as PortfolioItemFull, y as PortfolioKPI, b as PortfolioListResponse, z as PortfolioMetricsDataPoint, A as PortfolioMetricsTimelineData, j as PortfolioResultItem, f as PortfolioResultsData, a as PortfolioSection, C as PortfolioSectionData, D as PortfolioSectionDataMap, E as PortfolioSectionType, F as PortfolioSeoData, G as PortfolioServiceItem, g as PortfolioServicesData, H as PortfolioStrategyData, I as PortfolioStrategyPhase, J as PortfolioTeamData, K as PortfolioTeamMember, L as PortfolioTechItem, h as PortfolioTechStackData, i as PortfolioTestimonialData, N as PortfolioVideoData, S as SanityImageRef } from './types-He5A9okM.js';
3
3
  import { ServerApiConfig } from '@sonordev/site-kit/server';
4
4
  import React from 'react';
5
5
  export { AgencySiteKitLayout } from './layout/index.js';
6
- export { MetricsProvenance, PortfolioIndex, PortfolioIndexProps, PortfolioIndexSkeleton, PortfolioPage, PortfolioPageSkeleton, PortfolioSchema, curateHeroKpis, curatePortfolioProof, curateResultItems, formatCategoryLabel, formatServiceTag, gateMetricsDeltas } from './portfolio/index.js';
6
+ export { MetricsProvenance, PortfolioIndex, PortfolioIndexProps, PortfolioIndexSkeleton, PortfolioPage, PortfolioPageSkeleton, PortfolioSchema } from './portfolio/index.js';
7
+ export { c as curatePortfolioProof, a as curateResultItems, f as formatCategoryLabel, b as formatServiceTag, g as gateMetricsDeltas } from './format-poUpLtQ2.js';
7
8
  export * from '@sonordev/agency-site-kit/portfolio/client';
9
+ export { curateHeroKpis } from '@sonordev/site-kit/portfolio/contract';
8
10
 
9
11
  /**
10
12
  * @sonordev/agency-site-kit — Brand customization system
package/dist/index.js CHANGED
@@ -1,8 +1,9 @@
1
- export { PORTFOLIO_SECTION_TYPES, PortfolioIndex, PortfolioIndexSkeleton, PortfolioPage, PortfolioPageSkeleton, PortfolioSchema } from './chunk-NLGUPJZA.js';
1
+ export { PORTFOLIO_SECTION_TYPES, PortfolioIndex, PortfolioIndexSkeleton, PortfolioPage, PortfolioPageSkeleton, PortfolioSchema } from './chunk-7FFZX7C5.js';
2
+ export { MetricsProvenance } from './chunk-X7UCJ45A.js';
3
+ import './chunk-FT2SNEYP.js';
2
4
  export { formatCategoryLabel, formatServiceTag } from './chunk-7D4H36UF.js';
3
- export { MetricsProvenance, curateHeroKpis, curatePortfolioProof, curateResultItems, gateMetricsDeltas } from './chunk-RWQ675N6.js';
4
- import './chunk-KXLROAO5.js';
5
- export { AgencySiteKitLayout, DEFAULT_BRAND_CONFIG, DEFAULT_DARK_MODE, generateBrandCSS, hexToRgb, mergeBrandConfig } from './chunk-4IES23GV.js';
5
+ export { curateHeroKpis, curatePortfolioProof, curateResultItems, gateMetricsDeltas } from './chunk-FM25LB67.js';
6
+ export { AgencySiteKitLayout, DEFAULT_BRAND_CONFIG, DEFAULT_DARK_MODE, generateBrandCSS, hexToRgb, mergeBrandConfig } from './chunk-2RW4C42Y.js';
6
7
  import './chunk-N7TVKUYQ.js';
7
8
  import './chunk-L4EOQVRJ.js';
8
9
  export { apiFetch, apiGet, getApiConfig } from './chunk-QL6KVIFR.js';
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/shared/icons.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @sonordev/agency-site-kit — Icon registry\n *\n * Lightweight icon system that maps string names to SVG path data.\n * No external icon library dependency — keeps the bundle small.\n * Icons are rendered as inline SVGs.\n */\n\nimport React from 'react';\n\ninterface IconDefinition {\n viewBox: string;\n paths: string[];\n /** Optional fill-rule for complex paths */\n fillRule?: 'evenodd' | 'nonzero';\n}\n\n/**\n * Icon registry — 24x24 viewBox icons (matching Lucide/Heroicons conventions).\n * All paths use stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\".\n */\nconst ICON_REGISTRY: Record<string, IconDefinition> = {\n 'arrow-up': {\n viewBox: '0 0 24 24',\n paths: ['M12 19V5', 'M5 12l7-7 7 7'],\n },\n 'arrow-down': {\n viewBox: '0 0 24 24',\n paths: ['M12 5v14', 'M19 12l-7 7-7-7'],\n },\n 'chart-bar': {\n viewBox: '0 0 24 24',\n paths: ['M12 20V10', 'M18 20V4', 'M6 20v-4'],\n },\n check: {\n viewBox: '0 0 24 24',\n paths: ['M20 6L9 17l-5-5'],\n },\n clock: {\n viewBox: '0 0 24 24',\n paths: ['M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20z', 'M12 6v6l4 2'],\n },\n code: {\n viewBox: '0 0 24 24',\n paths: ['M16 18l6-6-6-6', 'M8 6l-6 6 6 6'],\n },\n globe: {\n viewBox: '0 0 24 24',\n paths: [\n 'M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20z',\n 'M2 12h20',\n 'M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10A15.3 15.3 0 0 1 12 2z',\n ],\n },\n layout: {\n viewBox: '0 0 24 24',\n paths: [\n 'M19 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2z',\n 'M3 9h18',\n 'M9 21V9',\n ],\n },\n lightning: {\n viewBox: '0 0 24 24',\n paths: ['M13 2L3 14h9l-1 8 10-12h-9l1-8'],\n },\n link: {\n viewBox: '0 0 24 24',\n paths: [\n 'M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71',\n 'M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71',\n ],\n },\n lock: {\n viewBox: '0 0 24 24',\n paths: [\n 'M19 11H5a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7a2 2 0 0 0-2-2z',\n 'M7 11V7a5 5 0 0 1 10 0v4',\n ],\n },\n mail: {\n viewBox: '0 0 24 24',\n paths: [\n 'M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z',\n 'M22 6l-10 7L2 6',\n ],\n },\n monitor: {\n viewBox: '0 0 24 24',\n paths: [\n 'M20 3H4a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2z',\n 'M8 21h8',\n 'M12 17v4',\n ],\n },\n palette: {\n viewBox: '0 0 24 24',\n paths: [\n 'M12 2C6.49 2 2 6.49 2 12s4.49 10 10 10a2 2 0 0 0 2-2v-.09a1.65 1.65 0 0 1 .92-1.48A1.65 1.65 0 0 1 16.5 19H19a2 2 0 0 0 2-2 10 10 0 0 0-9-15z',\n 'M6.5 11.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z',\n 'M10 7.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z',\n 'M14 7.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z',\n 'M17.5 11.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z',\n ],\n },\n rocket: {\n viewBox: '0 0 24 24',\n paths: [\n 'M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09z',\n 'M12 15l-3-3a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 0 1-4 2z',\n 'M9 12H4s.55-3.03 2-4c1.62-1.08 5 0 5 0',\n 'M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5',\n ],\n },\n search: {\n viewBox: '0 0 24 24',\n paths: [\n 'M11 19a8 8 0 1 0 0-16 8 8 0 0 0 0 16z',\n 'M21 21l-4.35-4.35',\n ],\n },\n settings: {\n viewBox: '0 0 24 24',\n paths: [\n 'M12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6z',\n 'M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09a1.65 1.65 0 0 0-1.08-1.51 1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09a1.65 1.65 0 0 0 1.51-1.08 1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9c.26.604.852.997 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1.08z',\n ],\n },\n shield: {\n viewBox: '0 0 24 24',\n paths: ['M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z'],\n },\n star: {\n viewBox: '0 0 24 24',\n paths: [\n 'M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z',\n ],\n },\n target: {\n viewBox: '0 0 24 24',\n paths: [\n 'M12 22a10 10 0 1 0 0-20 10 10 0 0 0 0 20z',\n 'M12 18a6 6 0 1 0 0-12 6 6 0 0 0 0 12z',\n 'M12 14a2 2 0 1 0 0-4 2 2 0 0 0 0 4z',\n ],\n },\n 'trending-up': {\n viewBox: '0 0 24 24',\n paths: ['M23 6l-9.5 9.5-5-5L1 18', 'M17 6h6v6'],\n },\n users: {\n viewBox: '0 0 24 24',\n paths: [\n 'M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2',\n 'M9 11a4 4 0 1 0 0-8 4 4 0 0 0 0 8z',\n 'M23 21v-2a4 4 0 0 0-3-3.87',\n 'M16 3.13a4 4 0 0 1 0 7.75',\n ],\n },\n zap: {\n viewBox: '0 0 24 24',\n paths: ['M13 2L3 14h9l-1 8 10-12h-9l1-8'],\n },\n};\n\n/**\n * Returns the icon definition (viewBox + paths) for a given icon name.\n * Returns null if the icon is not found.\n */\nexport function getIconDefinition(name: string): IconDefinition | null {\n return ICON_REGISTRY[name] ?? null;\n}\n\n/**\n * Returns just the SVG path strings for a given icon name.\n * Useful when building custom SVG elements.\n */\nexport function getIconPaths(name: string): string[] | null {\n const def = ICON_REGISTRY[name];\n return def ? def.paths : null;\n}\n\n/**\n * Returns a list of all available icon names.\n */\nexport function getAvailableIcons(): string[] {\n return Object.keys(ICON_REGISTRY);\n}\n\n/**\n * Renders an icon as a React SVG element.\n *\n * @param name Icon name from the registry\n * @param size Width/height in pixels (default 24)\n * @param className Optional CSS class name\n * @returns React SVG element, or null if icon not found\n */\nexport function renderIcon(\n name: string,\n size: number = 24,\n className?: string,\n): React.ReactElement | null {\n const def = ICON_REGISTRY[name];\n if (!def) return null;\n\n return React.createElement(\n 'svg',\n {\n xmlns: 'http://www.w3.org/2000/svg',\n width: size,\n height: size,\n viewBox: def.viewBox,\n fill: 'none',\n stroke: 'currentColor',\n strokeWidth: 2,\n strokeLinecap: 'round' as const,\n strokeLinejoin: 'round' as const,\n className,\n 'aria-hidden': 'true',\n },\n ...def.paths.map((d, i) =>\n React.createElement('path', { key: i, d }),\n ),\n );\n}\n\n/**\n * Icon component for use in JSX.\n *\n * Usage:\n * ```tsx\n * <Icon name=\"trending-up\" size={20} className=\"text-green-500\" />\n * ```\n */\nexport function Icon({\n name,\n size = 24,\n className,\n}: {\n name: string;\n size?: number;\n className?: string;\n}): React.ReactElement | null {\n return renderIcon(name, size, className);\n}\n"]}
1
+ {"version":3,"sources":["../src/shared/icons.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @sonordev/agency-site-kit — Icon registry\n *\n * Lightweight icon system that maps string names to SVG path data.\n * No external icon library dependency — keeps the bundle small.\n * Icons are rendered as inline SVGs.\n */\n\nimport React from 'react';\n\ninterface IconDefinition {\n viewBox: string;\n paths: string[];\n /** Optional fill-rule for complex paths */\n fillRule?: 'evenodd' | 'nonzero';\n}\n\n/**\n * Icon registry — 24x24 viewBox icons (matching Lucide/Heroicons conventions).\n * All paths use stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\".\n */\nconst ICON_REGISTRY: Record<string, IconDefinition> = {\n 'arrow-up': {\n viewBox: '0 0 24 24',\n paths: ['M12 19V5', 'M5 12l7-7 7 7'],\n },\n 'arrow-down': {\n viewBox: '0 0 24 24',\n paths: ['M12 5v14', 'M19 12l-7 7-7-7'],\n },\n 'chart-bar': {\n viewBox: '0 0 24 24',\n paths: ['M12 20V10', 'M18 20V4', 'M6 20v-4'],\n },\n check: {\n viewBox: '0 0 24 24',\n paths: ['M20 6L9 17l-5-5'],\n },\n clock: {\n viewBox: '0 0 24 24',\n paths: ['M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20z', 'M12 6v6l4 2'],\n },\n code: {\n viewBox: '0 0 24 24',\n paths: ['M16 18l6-6-6-6', 'M8 6l-6 6 6 6'],\n },\n globe: {\n viewBox: '0 0 24 24',\n paths: [\n 'M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20z',\n 'M2 12h20',\n 'M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10A15.3 15.3 0 0 1 12 2z',\n ],\n },\n layout: {\n viewBox: '0 0 24 24',\n paths: [\n 'M19 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2z',\n 'M3 9h18',\n 'M9 21V9',\n ],\n },\n lightning: {\n viewBox: '0 0 24 24',\n paths: ['M13 2L3 14h9l-1 8 10-12h-9l1-8'],\n },\n link: {\n viewBox: '0 0 24 24',\n paths: [\n 'M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71',\n 'M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71',\n ],\n },\n lock: {\n viewBox: '0 0 24 24',\n paths: [\n 'M19 11H5a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7a2 2 0 0 0-2-2z',\n 'M7 11V7a5 5 0 0 1 10 0v4',\n ],\n },\n mail: {\n viewBox: '0 0 24 24',\n paths: [\n 'M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z',\n 'M22 6l-10 7L2 6',\n ],\n },\n monitor: {\n viewBox: '0 0 24 24',\n paths: [\n 'M20 3H4a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2z',\n 'M8 21h8',\n 'M12 17v4',\n ],\n },\n palette: {\n viewBox: '0 0 24 24',\n paths: [\n 'M12 2C6.49 2 2 6.49 2 12s4.49 10 10 10a2 2 0 0 0 2-2v-.09a1.65 1.65 0 0 1 .92-1.48A1.65 1.65 0 0 1 16.5 19H19a2 2 0 0 0 2-2 10 10 0 0 0-9-15z',\n 'M6.5 11.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z',\n 'M10 7.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z',\n 'M14 7.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z',\n 'M17.5 11.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z',\n ],\n },\n rocket: {\n viewBox: '0 0 24 24',\n paths: [\n 'M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09z',\n 'M12 15l-3-3a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 0 1-4 2z',\n 'M9 12H4s.55-3.03 2-4c1.62-1.08 5 0 5 0',\n 'M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5',\n ],\n },\n search: {\n viewBox: '0 0 24 24',\n paths: [\n 'M11 19a8 8 0 1 0 0-16 8 8 0 0 0 0 16z',\n 'M21 21l-4.35-4.35',\n ],\n },\n settings: {\n viewBox: '0 0 24 24',\n paths: [\n 'M12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6z',\n 'M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09a1.65 1.65 0 0 0-1.08-1.51 1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09a1.65 1.65 0 0 0 1.51-1.08 1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9c.26.604.852.997 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1.08z',\n ],\n },\n shield: {\n viewBox: '0 0 24 24',\n paths: ['M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z'],\n },\n star: {\n viewBox: '0 0 24 24',\n paths: [\n 'M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z',\n ],\n },\n target: {\n viewBox: '0 0 24 24',\n paths: [\n 'M12 22a10 10 0 1 0 0-20 10 10 0 0 0 0 20z',\n 'M12 18a6 6 0 1 0 0-12 6 6 0 0 0 0 12z',\n 'M12 14a2 2 0 1 0 0-4 2 2 0 0 0 0 4z',\n ],\n },\n 'trending-up': {\n viewBox: '0 0 24 24',\n paths: ['M23 6l-9.5 9.5-5-5L1 18', 'M17 6h6v6'],\n },\n users: {\n viewBox: '0 0 24 24',\n paths: [\n 'M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2',\n 'M9 11a4 4 0 1 0 0-8 4 4 0 0 0 0 8z',\n 'M23 21v-2a4 4 0 0 0-3-3.87',\n 'M16 3.13a4 4 0 0 1 0 7.75',\n ],\n },\n zap: {\n viewBox: '0 0 24 24',\n paths: ['M13 2L3 14h9l-1 8 10-12h-9l1-8'],\n },\n};\n\n/**\n * Returns the icon definition (viewBox + paths) for a given icon name.\n * Returns null if the icon is not found.\n */\nexport function getIconDefinition(name: string): IconDefinition | null {\n return ICON_REGISTRY[name] ?? null;\n}\n\n/**\n * Returns just the SVG path strings for a given icon name.\n * Useful when building custom SVG elements.\n */\nexport function getIconPaths(name: string): string[] | null {\n const def = ICON_REGISTRY[name];\n return def ? def.paths : null;\n}\n\n/**\n * Returns a list of all available icon names.\n */\nexport function getAvailableIcons(): string[] {\n return Object.keys(ICON_REGISTRY);\n}\n\n/**\n * Renders an icon as a React SVG element.\n *\n * @param name Icon name from the registry\n * @param size Width/height in pixels (default 24)\n * @param className Optional CSS class name\n * @returns React SVG element, or null if icon not found\n */\nexport function renderIcon(\n name: string,\n size: number = 24,\n className?: string,\n): React.ReactElement | null {\n const def = ICON_REGISTRY[name];\n if (!def) return null;\n\n return React.createElement(\n 'svg',\n {\n xmlns: 'http://www.w3.org/2000/svg',\n width: size,\n height: size,\n viewBox: def.viewBox,\n fill: 'none',\n stroke: 'currentColor',\n strokeWidth: 2,\n strokeLinecap: 'round' as const,\n strokeLinejoin: 'round' as const,\n className,\n 'aria-hidden': 'true',\n },\n ...def.paths.map((d, i) =>\n React.createElement('path', { key: i, d }),\n ),\n );\n}\n\n/**\n * Icon component for use in JSX.\n *\n * Usage:\n * ```tsx\n * <Icon name=\"trending-up\" size={20} className=\"text-green-500\" />\n * ```\n */\nexport function Icon({\n name,\n size = 24,\n className,\n}: {\n name: string;\n size?: number;\n className?: string;\n}): React.ReactElement | null {\n return renderIcon(name, size, className);\n}\n"]}
@@ -1,13 +1,13 @@
1
1
  'use strict';
2
2
 
3
- var chunk5MEZU3OG_cjs = require('../chunk-5MEZU3OG.cjs');
3
+ var chunkOG5RBKQJ_cjs = require('../chunk-OG5RBKQJ.cjs');
4
4
  require('../chunk-XOWSXZYW.cjs');
5
5
 
6
6
 
7
7
 
8
8
  Object.defineProperty(exports, "AgencySiteKitLayout", {
9
9
  enumerable: true,
10
- get: function () { return chunk5MEZU3OG_cjs.AgencySiteKitLayout; }
10
+ get: function () { return chunkOG5RBKQJ_cjs.AgencySiteKitLayout; }
11
11
  });
12
12
  //# sourceMappingURL=index.cjs.map
13
13
  //# sourceMappingURL=index.cjs.map
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { B as BrandConfig } from '../types-0tVPy4pU.cjs';
2
+ import { B as BrandConfig } from '../types-He5A9okM.cjs';
3
3
 
4
4
  /**
5
5
  * @sonordev/agency-site-kit — AgencySiteKitLayout
@@ -44,6 +44,16 @@ interface AgencySiteKitLayoutProps {
44
44
  * Prefer `buildAiDiscoveryHeaders` from `@sonordev/site-kit/llms` in root layout.
45
45
  */
46
46
  showLlmsTxtFooterLink?: boolean;
47
+ /**
48
+ * Mint a short-lived client token from `SONOR_API_KEY` and publish it for
49
+ * client-side site-kit modules (Analytics, Forms, Engage, BookingWidget).
50
+ * Default true.
51
+ *
52
+ * Set false ONLY when the app already mounts core `SiteKitLayout`, which
53
+ * publishes its own credential — two publishers would race. Turning it off
54
+ * without another publisher leaves client modules unauthenticated.
55
+ */
56
+ credential?: boolean;
47
57
  }
48
58
  /**
49
59
  * AgencySiteKitLayout — Server component wrapper
@@ -54,6 +64,6 @@ interface AgencySiteKitLayoutProps {
54
64
  * This is an async server component (RSC). It makes one API call at render
55
65
  * time, cached via Next.js ISR (default 1 hour revalidation).
56
66
  */
57
- declare function AgencySiteKitLayout({ children, apiKey, apiUrl, brandConfig: brandConfigOverride, theme, className, showLlmsTxtFooterLink, }: AgencySiteKitLayoutProps): Promise<React.ReactElement>;
67
+ declare function AgencySiteKitLayout({ children, apiKey, apiUrl, brandConfig: brandConfigOverride, theme, className, showLlmsTxtFooterLink, credential, }: AgencySiteKitLayoutProps): Promise<React.ReactElement>;
58
68
 
59
69
  export { AgencySiteKitLayout, type AgencySiteKitLayoutProps };
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { B as BrandConfig } from '../types-0tVPy4pU.js';
2
+ import { B as BrandConfig } from '../types-He5A9okM.js';
3
3
 
4
4
  /**
5
5
  * @sonordev/agency-site-kit — AgencySiteKitLayout
@@ -44,6 +44,16 @@ interface AgencySiteKitLayoutProps {
44
44
  * Prefer `buildAiDiscoveryHeaders` from `@sonordev/site-kit/llms` in root layout.
45
45
  */
46
46
  showLlmsTxtFooterLink?: boolean;
47
+ /**
48
+ * Mint a short-lived client token from `SONOR_API_KEY` and publish it for
49
+ * client-side site-kit modules (Analytics, Forms, Engage, BookingWidget).
50
+ * Default true.
51
+ *
52
+ * Set false ONLY when the app already mounts core `SiteKitLayout`, which
53
+ * publishes its own credential — two publishers would race. Turning it off
54
+ * without another publisher leaves client modules unauthenticated.
55
+ */
56
+ credential?: boolean;
47
57
  }
48
58
  /**
49
59
  * AgencySiteKitLayout — Server component wrapper
@@ -54,6 +64,6 @@ interface AgencySiteKitLayoutProps {
54
64
  * This is an async server component (RSC). It makes one API call at render
55
65
  * time, cached via Next.js ISR (default 1 hour revalidation).
56
66
  */
57
- declare function AgencySiteKitLayout({ children, apiKey, apiUrl, brandConfig: brandConfigOverride, theme, className, showLlmsTxtFooterLink, }: AgencySiteKitLayoutProps): Promise<React.ReactElement>;
67
+ declare function AgencySiteKitLayout({ children, apiKey, apiUrl, brandConfig: brandConfigOverride, theme, className, showLlmsTxtFooterLink, credential, }: AgencySiteKitLayoutProps): Promise<React.ReactElement>;
58
68
 
59
69
  export { AgencySiteKitLayout, type AgencySiteKitLayoutProps };
@@ -1,4 +1,4 @@
1
- export { AgencySiteKitLayout } from '../chunk-4IES23GV.js';
1
+ export { AgencySiteKitLayout } from '../chunk-2RW4C42Y.js';
2
2
  import '../chunk-QL6KVIFR.js';
3
3
  //# sourceMappingURL=index.js.map
4
4
  //# sourceMappingURL=index.js.map
@@ -51,22 +51,7 @@ var import_link = __toESM(require("next/link"), 1);
51
51
  var import_GlassCard = __toESM(require("@sonordev/agency-site-kit/portfolio/primitives/GlassCard"), 1);
52
52
 
53
53
  // src/portfolio/curate-proof.ts
54
- var LIGHTHOUSE_RE = /lighthouse/i;
55
- function isLighthouseKpi(kpi) {
56
- return LIGHTHOUSE_RE.test(kpi.label);
57
- }
58
- function curateHeroKpis(kpis) {
59
- const business = kpis.filter((k) => !isLighthouseKpi(k));
60
- const lighthouse = kpis.filter(isLighthouseKpi);
61
- if (lighthouse.length <= 1) return kpis;
62
- const allPerfect = lighthouse.length >= 4 && lighthouse.every((k) => k.value === 100);
63
- const summary = allPerfect ? {
64
- ...lighthouse[0],
65
- label: "Lighthouse, all 4 categories",
66
- description: "Perfect scores across performance, SEO, accessibility, and best practices."
67
- } : lighthouse.find((k) => /performance/i.test(k.label)) ?? lighthouse[0];
68
- return [...business, summary];
69
- }
54
+ var import_contract = require("@sonordev/site-kit/portfolio/contract");
70
55
 
71
56
  // src/portfolio/format.ts
72
57
  function formatCategoryLabel(slug) {
@@ -84,7 +69,7 @@ function PortfolioCard({ item, className = "", basePath = "/work" }) {
84
69
  const thumbnailSrc = item.hero_screenshots?.desktop || item.hero_image || (item.project_id ? `/portfolio-previews/${item.slug}.png` : void 0);
85
70
  const kpis = Array.isArray(item.kpis) ? item.kpis : [];
86
71
  const services = Array.isArray(item.services) ? item.services : [];
87
- const kpiPreview = curateHeroKpis(kpis).slice(0, 3);
72
+ const kpiPreview = (0, import_contract.curateHeroKpis)(kpis).slice(0, 3);
88
73
  const servicePreview = services.slice(0, 3);
89
74
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_link.default, { href: `${basePath}/${item.slug}`, className: `block group ${className}`, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_GlassCard.default, { padding: "sm", hover: true, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-col gap-4", children: [
90
75
  thumbnailSrc && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/portfolio/client.ts","../../src/portfolio/components/PortfolioGrid.tsx","../../src/portfolio/components/PortfolioCard.tsx","../../src/portfolio/curate-proof.ts","../../src/portfolio/format.ts","../../src/portfolio/components/PortfolioErrorFallback.tsx","../../src/portfolio/components/PortfolioLeadCTA.tsx","../../src/portfolio/components/PortfolioAnalytics.tsx"],"sourcesContent":["'use client';\n\n/**\n * @sonordev/agency-site-kit/portfolio/client — Client-only portfolio components\n *\n * Components in this barrel require browser APIs (DOM, events, animations).\n * The 'use client' directive marks the client boundary; the build also stamps\n * every compiled file in the client bundle with the directive.\n *\n * Individual section components live at\n * `@sonordev/agency-site-kit/portfolio/sections/<Name>` and animation\n * primitives at `@sonordev/agency-site-kit/portfolio/primitives/<Name>` —\n * import those subpaths directly to compose custom layouts.\n */\n\nimport type { PortfolioItem, PortfolioItemFull, PortfolioSection } from '../types';\n\n// Grid/Card are shared (directive-less) components; importing them through\n// this 'use client' barrel yields the client copy for client-side callers.\n// Server code renders them via PortfolioIndex / the /portfolio barrel.\nexport { default as PortfolioGrid } from './components/PortfolioGrid';\nexport { default as PortfolioCard } from './components/PortfolioCard';\n\n// Pure label formatters — client-side copy of the same source module the\n// server barrel exports (e.g. app-side category filter chips).\nexport { formatCategoryLabel, formatServiceTag } from './format';\n\n// Error boundary UI for portfolio routes (error.tsx must be a client file)\nexport { default as PortfolioErrorFallback } from './components/PortfolioErrorFallback';\nexport type { PortfolioErrorFallbackProps } from './components/PortfolioErrorFallback';\n\n// Lead capture CTA — Sonor managed form with case-study attribution\nexport { default as PortfolioLeadCTA } from './components/PortfolioLeadCTA';\nexport type { PortfolioLeadCTAProps } from './components/PortfolioLeadCTA';\n\n// Engagement-tracking island (PortfolioPage mounts this automatically;\n// exported for custom layouts)\nexport { default as PortfolioAnalytics } from './components/PortfolioAnalytics';\n\n// Re-export types for convenience\nexport type {\n PortfolioItem,\n PortfolioItemFull,\n PortfolioSection,\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","// 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","/**\n * curate-proof — single source of truth for which proof points a public case\n * study is allowed to display, and in what shape.\n *\n * Two classes of problem this guards against (both shipped to production on\n * upforge.io before this existed):\n *\n * 1. Repetition: generated items arrive with four \"100/100 Lighthouse X\"\n * KPIs in the hero, the same four restated as Results cards, AND a full\n * Performance section with score gauges. One differentiator repeated\n * twelve times per page stops differentiating. The Performance section is\n * the one home for the Lighthouse breakdown; hero/results/cards collapse\n * to at most one summary tile.\n *\n * 2. Self-harming live metrics: MetricsDelta rows are computed from raw\n * analytics baselines with no editorial judgment — a bounce rate of\n * \"100 → 99.69\" rendered as a green win. Marketing surfaces only show a\n * delta when it's a genuinely flattering, meaningful story.\n *\n * Used by PortfolioPage (hero KPIs + results items), PortfolioCard (KPI\n * preview), and MetricsProvenance (delta gate). Change display policy here,\n * nowhere else.\n */\n\nimport type {\n MetricsDelta,\n PortfolioHeroData,\n PortfolioItemFull,\n PortfolioKPI,\n PortfolioResultItem,\n PortfolioResultsData,\n} from '../types';\n\nconst LIGHTHOUSE_RE = /lighthouse/i;\n\n/** True when a KPI tile is a per-category Lighthouse score. */\nexport function isLighthouseKpi(kpi: PortfolioKPI): boolean {\n return LIGHTHOUSE_RE.test(kpi.label);\n}\n\n/**\n * True when a Results card merely restates a Lighthouse category score\n * (e.g. \"Perfect performance score … measured Lighthouse score of 100/100\").\n * Business outcomes (\"Complete 14-page rebuild\", \"682% session growth\")\n * never match: the title must be about a score AND the card must reference\n * Lighthouse or carry a x/100 metric.\n */\nexport function isLighthouseRestatement(item: PortfolioResultItem): boolean {\n if (!/score/i.test(item.title)) return false;\n if (LIGHTHOUSE_RE.test(`${item.title} ${item.description}`)) return true;\n return item.metric?.suffix === '/100';\n}\n\n/**\n * Collapse per-category Lighthouse KPIs to at most one summary tile, business\n * outcomes first. When every category is present at 100 the summary says so;\n * otherwise the Performance category (the one buyers know) represents the set.\n */\nexport function curateHeroKpis(kpis: PortfolioKPI[]): PortfolioKPI[] {\n const business = kpis.filter((k) => !isLighthouseKpi(k));\n const lighthouse = kpis.filter(isLighthouseKpi);\n if (lighthouse.length <= 1) return kpis;\n\n const allPerfect = lighthouse.length >= 4 && lighthouse.every((k) => k.value === 100);\n const summary: PortfolioKPI = allPerfect\n ? {\n ...lighthouse[0],\n label: 'Lighthouse, all 4 categories',\n description: 'Perfect scores across performance, SEO, accessibility, and best practices.',\n }\n : (lighthouse.find((k) => /performance/i.test(k.label)) ?? lighthouse[0]);\n\n return [...business, summary];\n}\n\n/**\n * Drop Results cards that restate Lighthouse scores when the case study also\n * renders a Performance section (the score breakdown's single home).\n */\nexport function curateResultItems(\n items: PortfolioResultItem[],\n hasPerformanceSection: boolean,\n): PortfolioResultItem[] {\n if (!hasPerformanceSection) return items;\n const curated = items.filter((item) => !isLighthouseRestatement(item));\n // Never curate a Results section into emptiness — if every card was a\n // Lighthouse restatement, keep the strongest single card instead.\n return curated.length > 0 ? curated : items.slice(0, 1);\n}\n\n/**\n * Editorial gate for live metric deltas (\"Verified from live analytics\").\n * `direction` comes from the platform with 'up' meaning IMPROVED (lower-is-\n * better metrics are inverted server-side). A public case study only shows a\n * delta when it's a win worth telling:\n * - improved ('up'), never a regression or flat noise\n * - moved by a double-digit relative margin (small drift isn't a story)\n * - bounce-rate-style metrics must also be respectable in absolute terms —\n * \"100 → 99.69\" is technically an improvement and still self-harm\n */\nexport function gateMetricsDeltas(deltas: MetricsDelta[] | undefined | null): MetricsDelta[] {\n if (!deltas?.length) return [];\n return deltas.filter((d) => {\n if (d.direction !== 'up') return false;\n if (Math.abs(d.deltaPercent) < 10) return false;\n if (/bounce/i.test(d.metric) && d.current > 70) return false;\n return true;\n });\n}\n\n/**\n * Apply the full display policy to a portfolio item before rendering.\n * Pure — returns a new item; the fetched payload is never mutated.\n */\nexport function curatePortfolioProof(item: PortfolioItemFull): PortfolioItemFull {\n const hasPerformanceSection = item.sections.some(\n (s) => s.sectionType === 'portfolioPerformance',\n );\n\n return {\n ...item,\n sections: item.sections.map((section) => {\n if (section.sectionType === 'portfolioHero') {\n const data = section.data as PortfolioHeroData;\n return { ...section, data: { ...data, kpis: curateHeroKpis(data.kpis ?? []) } };\n }\n if (section.sectionType === 'portfolioResults') {\n const data = section.data as PortfolioResultsData;\n return {\n ...section,\n data: { ...data, items: curateResultItems(data.items ?? [], hasPerformanceSection) },\n };\n }\n return section;\n }),\n };\n}\n","/**\n * Presentation formatters shared by portfolio components and consuming apps.\n */\n\n/**\n * Human label for a category slug. Single source of truth — HeroSection,\n * PortfolioCard, and app-side filter chips all render categories through\n * this. Underscores become spaces; hyphens are PRESERVED so compound slugs\n * keep their canonical form (\"e-commerce\" → \"E-Commerce\", never \"E Commerce\"),\n * and every word start (including after a hyphen) is capitalized.\n */\nexport function formatCategoryLabel(slug: string | undefined | null): string {\n if (!slug) return '';\n return slug.replace(/_/g, ' ').replace(/\\b\\w/g, (c: string) => c.toUpperCase());\n}\n\n/**\n * Normalize a service tag's casing for display: the first character of each\n * word is uppercased, the rest left alone (so \"Next.js development\" becomes\n * \"Next.js Development\" and \"Technical SEO implementation\" keeps \"SEO\").\n * Fixes per-item casing drift in stored tags without touching the data.\n */\nexport function formatServiceTag(tag: string | undefined | null): string {\n if (!tag) return '';\n return tag.replace(/(^|[\\s/])([a-z])/g, (_m, sep: string, ch: string) => sep + ch.toUpperCase());\n}\n","'use client';\n\n/**\n * Error boundary UI for portfolio routes — pairs with the PortfolioApiError\n * contract (infrastructure failures throw; Next renders the route's\n * error.tsx). Drop into `error.tsx`:\n *\n * ```tsx\n * 'use client';\n * export { PortfolioErrorFallback as default } from '@sonordev/agency-site-kit/portfolio/client';\n * ```\n */\n\nimport React from 'react';\n\nexport interface PortfolioErrorFallbackProps {\n error: Error & { digest?: string };\n reset: () => void;\n}\n\nexport default function PortfolioErrorFallback({ error, reset }: PortfolioErrorFallbackProps) {\n return (\n <section className=\"w-full py-24\">\n <div\n className=\"max-w-xl mx-auto px-8 py-12 rounded-2xl text-center flex flex-col items-center gap-4\"\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 <h2\n className=\"text-xl font-semibold\"\n style={{ color: 'var(--sk-text-primary, #ffffff)' }}\n >\n Our work is taking a moment to load\n </h2>\n <p className=\"text-sm\" style={{ color: 'var(--sk-text-secondary, #a1a1aa)' }}>\n The portfolio couldn&apos;t be fetched just now. It&apos;s us, not you. Try again in a\n few seconds.\n </p>\n {error?.digest && (\n <p className=\"text-xs\" style={{ color: 'var(--sk-text-tertiary, #71717a)' }}>\n Reference: {error.digest}\n </p>\n )}\n <button\n onClick={reset}\n className=\"mt-2 px-6 py-2.5 rounded-xl text-sm font-semibold transition-opacity hover:opacity-90\"\n style={{ background: 'var(--sk-primary, #6366f1)', color: '#ffffff' }}\n >\n Try again\n </button>\n </div>\n </section>\n );\n}\n","'use client';\n\n/**\n * PortfolioLeadCTA — turn a case study into a lead source.\n *\n * Renders a Sonor managed form (site-kit ManagedForm — never hand-rolled\n * markup) inside a CTA shell. Attribution comes free on two rails:\n *\n * 1. The submission's metadata.pageUrl (sent automatically by site-kit)\n * lands in the CRM's page_url/source_url columns — that IS the case\n * study URL.\n * 2. onSuccess fires a `portfolio_lead` conversion with the case-study\n * slug via the standalone dispatch, so Signal/analytics can report\n * \"leads per case study\" without parsing URLs.\n *\n * If the managed form defines a hidden `portfolio_slug` field, define it in\n * the dashboard and it will arrive pre-filled too (fields JSONB).\n */\n\nimport React from 'react';\nimport { ManagedForm } from '@sonordev/site-kit/forms';\nimport { trackConversion } from '@sonordev/site-kit/client';\n\nexport interface PortfolioLeadCTAProps {\n /** Managed form key from the Sonor dashboard (e.g. \"portfolio-inquiry\"). */\n formId: string;\n /** The case study this CTA sits on — used for lead attribution. */\n item: { slug: string; title: string };\n heading?: string;\n description?: string;\n className?: string;\n}\n\nexport default function PortfolioLeadCTA({\n formId,\n item,\n heading = 'Want results like these?',\n description = \"Tell us about your project and we'll show you what's possible.\",\n className = '',\n}: PortfolioLeadCTAProps) {\n return (\n <section\n className={`w-full py-20 md:py-28 ${className}`}\n style={{ background: 'var(--sk-bg, #0a0a0a)' }}\n data-portfolio-section=\"portfolioLeadForm\"\n >\n <div className=\"max-w-2xl mx-auto px-6\">\n <div\n className=\"rounded-2xl p-8 md:p-12\"\n style={{\n background: 'var(--sk-surface, rgba(255,255,255,0.05))',\n backdropFilter: 'blur(16px)',\n border: '1px solid var(--sk-border, rgba(255,255,255,0.1))',\n }}\n >\n <h2\n className=\"text-2xl md:text-3xl font-bold mb-3\"\n style={{\n color: 'var(--sk-text-primary, #ffffff)',\n fontFamily: 'var(--sk-font-heading, inherit)',\n }}\n >\n {heading}\n </h2>\n <p\n className=\"text-base mb-8\"\n style={{ color: 'var(--sk-text-secondary, #a1a1aa)' }}\n >\n {description}\n </p>\n\n <ManagedForm\n formId={formId}\n onSuccess={() => {\n trackConversion({\n type: 'portfolio_lead',\n metadata: { case_study: item.slug, case_study_title: item.title },\n });\n }}\n />\n </div>\n </div>\n </section>\n );\n}\n","'use client';\n\n/**\n * PortfolioAnalytics — childless engagement-tracking island.\n *\n * Mounted by PortfolioPage as a SIBLING of the content (never a wrapper —\n * house perf doctrine), deferred to idle so it stays out of the hydration\n * path. Observes the server-rendered `data-portfolio-section` markers and\n * emits events through site-kit's standalone dispatch (which queues until\n * the site's AnalyticsProvider mounts and no-ops if it never does):\n *\n * portfolio_section_view once per section type per page view\n * portfolio_live_site_click clicks on [data-portfolio-action=\"live-site\"]\n * portfolio_cta_click clicks on [data-portfolio-action=\"cta\"]\n *\n * This is the raw material for \"your QCR case study converts best\" insights.\n */\n\nimport { useEffect } from 'react';\nimport { trackEvent } from '@sonordev/site-kit/client';\n\nexport default function PortfolioAnalytics({ slug }: { slug: string }) {\n useEffect(() => {\n let cleanup = () => {};\n\n const setup = () => {\n const seen = new Set<string>();\n const io = new IntersectionObserver(\n (entries) => {\n for (const entry of entries) {\n if (!entry.isIntersecting) continue;\n const type = (entry.target as HTMLElement).dataset.portfolioSection;\n if (!type || seen.has(type)) continue;\n seen.add(type);\n trackEvent({\n name: 'portfolio_section_view',\n category: 'portfolio',\n label: type,\n properties: { case_study: slug, section: type },\n });\n }\n },\n { threshold: 0.4 },\n );\n document\n .querySelectorAll<HTMLElement>('[data-portfolio-section]')\n .forEach((el) => io.observe(el));\n\n const onClick = (event: MouseEvent) => {\n const link = (event.target as HTMLElement).closest<HTMLAnchorElement>(\n 'a[data-portfolio-action]',\n );\n if (!link) return;\n const action = link.dataset.portfolioAction;\n trackEvent({\n name: action === 'live-site' ? 'portfolio_live_site_click' : 'portfolio_cta_click',\n category: 'portfolio',\n label: slug,\n properties: { case_study: slug, href: link.href },\n });\n };\n document.addEventListener('click', onClick, { passive: true });\n\n cleanup = () => {\n io.disconnect();\n document.removeEventListener('click', onClick);\n };\n };\n\n // Idle-defer: engagement tracking must never compete with hydration.\n let idleId: number | ReturnType<typeof setTimeout>;\n if ('requestIdleCallback' in window) {\n idleId = window.requestIdleCallback(setup, { timeout: 3000 });\n } else {\n idleId = setTimeout(setup, 1500);\n }\n\n return () => {\n if ('requestIdleCallback' in window) window.cancelIdleCallback(idleId as number);\n else clearTimeout(idleId as ReturnType<typeof setTimeout>);\n cleanup();\n };\n }, [slug]);\n\n return null;\n}\n"],"mappings":"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","names":["Link","GlassCard","Image","import_jsx_runtime","ScrollReveal","import_jsx_runtime","import_jsx_runtime","import_client"]}
1
+ {"version":3,"sources":["../../src/portfolio/client.ts","../../src/portfolio/components/PortfolioGrid.tsx","../../src/portfolio/components/PortfolioCard.tsx","../../src/portfolio/curate-proof.ts","../../src/portfolio/format.ts","../../src/portfolio/components/PortfolioErrorFallback.tsx","../../src/portfolio/components/PortfolioLeadCTA.tsx","../../src/portfolio/components/PortfolioAnalytics.tsx"],"sourcesContent":["'use client';\n\n/**\n * @sonordev/agency-site-kit/portfolio/client — Client-only portfolio components\n *\n * Components in this barrel require browser APIs (DOM, events, animations).\n * The 'use client' directive marks the client boundary; the build also stamps\n * every compiled file in the client bundle with the directive.\n *\n * Individual section components live at\n * `@sonordev/agency-site-kit/portfolio/sections/<Name>` and animation\n * primitives at `@sonordev/agency-site-kit/portfolio/primitives/<Name>` —\n * import those subpaths directly to compose custom layouts.\n */\n\nimport type { PortfolioItem, PortfolioItemFull, PortfolioSection } from '../types';\n\n// Grid/Card are shared (directive-less) components; importing them through\n// this 'use client' barrel yields the client copy for client-side callers.\n// Server code renders them via PortfolioIndex / the /portfolio barrel.\nexport { default as PortfolioGrid } from './components/PortfolioGrid';\nexport { default as PortfolioCard } from './components/PortfolioCard';\n\n// Pure label formatters — client-side copy of the same source module the\n// server barrel exports (e.g. app-side category filter chips).\nexport { formatCategoryLabel, formatServiceTag } from './format';\n\n// Error boundary UI for portfolio routes (error.tsx must be a client file)\nexport { default as PortfolioErrorFallback } from './components/PortfolioErrorFallback';\nexport type { PortfolioErrorFallbackProps } from './components/PortfolioErrorFallback';\n\n// Lead capture CTA — Sonor managed form with case-study attribution\nexport { default as PortfolioLeadCTA } from './components/PortfolioLeadCTA';\nexport type { PortfolioLeadCTAProps } from './components/PortfolioLeadCTA';\n\n// Engagement-tracking island (PortfolioPage mounts this automatically;\n// exported for custom layouts)\nexport { default as PortfolioAnalytics } from './components/PortfolioAnalytics';\n\n// Re-export types for convenience\nexport type {\n PortfolioItem,\n PortfolioItemFull,\n PortfolioSection,\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","// 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","/**\n * curate-proof — single source of truth for which proof points a public case\n * study is allowed to display, and in what shape.\n *\n * Two classes of problem this guards against (both shipped to production on\n * upforge.io before this existed):\n *\n * 1. Repetition: generated items arrive with four \"100/100 Lighthouse X\"\n * KPIs in the hero, the same four restated as Results cards, AND a full\n * Performance section with score gauges. One differentiator repeated\n * twelve times per page stops differentiating. The Performance section is\n * the one home for the Lighthouse breakdown; hero/results/cards collapse\n * to at most one summary tile.\n *\n * 2. Self-harming live metrics: MetricsDelta rows are computed from raw\n * analytics baselines with no editorial judgment — a bounce rate of\n * \"100 → 99.69\" rendered as a green win. Marketing surfaces only show a\n * delta when it's a genuinely flattering, meaningful story.\n *\n * Used by PortfolioPage (hero KPIs + results items), PortfolioCard (KPI\n * preview), and MetricsProvenance (delta gate). Change display policy here,\n * nowhere else.\n */\n\nimport type {\n MetricsDelta,\n PortfolioHeroData,\n PortfolioItemFull,\n PortfolioResultItem,\n PortfolioResultsData,\n} from '../types';\n// The Lighthouse-KPI collapse policy is the shared cross-repo contract\n// (@sonordev/site-kit/portfolio/contract) — the same source signal-api mirrors\n// for content-time collapse. Imported (not re-implemented) so the display side\n// and the content side can never drift; re-exported so this module stays the\n// portfolio's single display-policy entry point.\nimport { curateHeroKpis, isLighthouseKpi } from '@sonordev/site-kit/portfolio/contract';\n\nexport { curateHeroKpis, isLighthouseKpi };\n\nconst LIGHTHOUSE_RE = /lighthouse/i;\n\n/**\n * True when a Results card merely restates a Lighthouse category score\n * (e.g. \"Perfect performance score … measured Lighthouse score of 100/100\").\n * Business outcomes (\"Complete 14-page rebuild\", \"682% session growth\")\n * never match: the title must be about a score AND the card must reference\n * Lighthouse or carry a x/100 metric.\n */\nexport function isLighthouseRestatement(item: PortfolioResultItem): boolean {\n if (!/score/i.test(item.title)) return false;\n if (LIGHTHOUSE_RE.test(`${item.title} ${item.description}`)) return true;\n return item.metric?.suffix === '/100';\n}\n\n/**\n * Drop Results cards that restate Lighthouse scores when the case study also\n * renders a Performance section (the score breakdown's single home).\n */\nexport function curateResultItems(\n items: PortfolioResultItem[],\n hasPerformanceSection: boolean,\n): PortfolioResultItem[] {\n if (!hasPerformanceSection) return items;\n const curated = items.filter((item) => !isLighthouseRestatement(item));\n // Never curate a Results section into emptiness — if every card was a\n // Lighthouse restatement, keep the strongest single card instead.\n return curated.length > 0 ? curated : items.slice(0, 1);\n}\n\n/**\n * Editorial gate for live metric deltas (\"Verified from live analytics\").\n * `direction` comes from the platform with 'up' meaning IMPROVED (lower-is-\n * better metrics are inverted server-side). A public case study only shows a\n * delta when it's a win worth telling:\n * - improved ('up'), never a regression or flat noise\n * - moved by a double-digit relative margin (small drift isn't a story)\n * - bounce-rate-style metrics must also be respectable in absolute terms —\n * \"100 → 99.69\" is technically an improvement and still self-harm\n */\nexport function gateMetricsDeltas(deltas: MetricsDelta[] | undefined | null): MetricsDelta[] {\n if (!deltas?.length) return [];\n return deltas.filter((d) => {\n if (d.direction !== 'up') return false;\n if (Math.abs(d.deltaPercent) < 10) return false;\n if (/bounce/i.test(d.metric) && d.current > 70) return false;\n return true;\n });\n}\n\n/**\n * Apply the full display policy to a portfolio item before rendering.\n * Pure — returns a new item; the fetched payload is never mutated.\n */\nexport function curatePortfolioProof(item: PortfolioItemFull): PortfolioItemFull {\n const hasPerformanceSection = item.sections.some(\n (s) => s.sectionType === 'portfolioPerformance',\n );\n\n return {\n ...item,\n sections: item.sections.map((section) => {\n if (section.sectionType === 'portfolioHero') {\n const data = section.data as PortfolioHeroData;\n return { ...section, data: { ...data, kpis: curateHeroKpis(data.kpis ?? []) } };\n }\n if (section.sectionType === 'portfolioResults') {\n const data = section.data as PortfolioResultsData;\n return {\n ...section,\n data: { ...data, items: curateResultItems(data.items ?? [], hasPerformanceSection) },\n };\n }\n return section;\n }),\n };\n}\n","/**\n * Presentation formatters shared by portfolio components and consuming apps.\n */\n\n/**\n * Human label for a category slug. Single source of truth — HeroSection,\n * PortfolioCard, and app-side filter chips all render categories through\n * this. Underscores become spaces; hyphens are PRESERVED so compound slugs\n * keep their canonical form (\"e-commerce\" → \"E-Commerce\", never \"E Commerce\"),\n * and every word start (including after a hyphen) is capitalized.\n */\nexport function formatCategoryLabel(slug: string | undefined | null): string {\n if (!slug) return '';\n return slug.replace(/_/g, ' ').replace(/\\b\\w/g, (c: string) => c.toUpperCase());\n}\n\n/**\n * Normalize a service tag's casing for display: the first character of each\n * word is uppercased, the rest left alone (so \"Next.js development\" becomes\n * \"Next.js Development\" and \"Technical SEO implementation\" keeps \"SEO\").\n * Fixes per-item casing drift in stored tags without touching the data.\n */\nexport function formatServiceTag(tag: string | undefined | null): string {\n if (!tag) return '';\n return tag.replace(/(^|[\\s/])([a-z])/g, (_m, sep: string, ch: string) => sep + ch.toUpperCase());\n}\n","'use client';\n\n/**\n * Error boundary UI for portfolio routes — pairs with the PortfolioApiError\n * contract (infrastructure failures throw; Next renders the route's\n * error.tsx). Drop into `error.tsx`:\n *\n * ```tsx\n * 'use client';\n * export { PortfolioErrorFallback as default } from '@sonordev/agency-site-kit/portfolio/client';\n * ```\n */\n\nimport React from 'react';\n\nexport interface PortfolioErrorFallbackProps {\n error: Error & { digest?: string };\n reset: () => void;\n}\n\nexport default function PortfolioErrorFallback({ error, reset }: PortfolioErrorFallbackProps) {\n return (\n <section className=\"w-full py-24\">\n <div\n className=\"max-w-xl mx-auto px-8 py-12 rounded-2xl text-center flex flex-col items-center gap-4\"\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 <h2\n className=\"text-xl font-semibold\"\n style={{ color: 'var(--sk-text-primary, #ffffff)' }}\n >\n Our work is taking a moment to load\n </h2>\n <p className=\"text-sm\" style={{ color: 'var(--sk-text-secondary, #a1a1aa)' }}>\n The portfolio couldn&apos;t be fetched just now. It&apos;s us, not you. Try again in a\n few seconds.\n </p>\n {error?.digest && (\n <p className=\"text-xs\" style={{ color: 'var(--sk-text-tertiary, #71717a)' }}>\n Reference: {error.digest}\n </p>\n )}\n <button\n onClick={reset}\n className=\"mt-2 px-6 py-2.5 rounded-xl text-sm font-semibold transition-opacity hover:opacity-90\"\n style={{ background: 'var(--sk-primary, #6366f1)', color: '#ffffff' }}\n >\n Try again\n </button>\n </div>\n </section>\n );\n}\n","'use client';\n\n/**\n * PortfolioLeadCTA — turn a case study into a lead source.\n *\n * Renders a Sonor managed form (site-kit ManagedForm — never hand-rolled\n * markup) inside a CTA shell. Attribution comes free on two rails:\n *\n * 1. The submission's metadata.pageUrl (sent automatically by site-kit)\n * lands in the CRM's page_url/source_url columns — that IS the case\n * study URL.\n * 2. onSuccess fires a `portfolio_lead` conversion with the case-study\n * slug via the standalone dispatch, so Signal/analytics can report\n * \"leads per case study\" without parsing URLs.\n *\n * If the managed form defines a hidden `portfolio_slug` field, define it in\n * the dashboard and it will arrive pre-filled too (fields JSONB).\n */\n\nimport React from 'react';\nimport { ManagedForm } from '@sonordev/site-kit/forms';\nimport { trackConversion } from '@sonordev/site-kit/client';\n\nexport interface PortfolioLeadCTAProps {\n /** Managed form key from the Sonor dashboard (e.g. \"portfolio-inquiry\"). */\n formId: string;\n /** The case study this CTA sits on — used for lead attribution. */\n item: { slug: string; title: string };\n heading?: string;\n description?: string;\n className?: string;\n}\n\nexport default function PortfolioLeadCTA({\n formId,\n item,\n heading = 'Want results like these?',\n description = \"Tell us about your project and we'll show you what's possible.\",\n className = '',\n}: PortfolioLeadCTAProps) {\n return (\n <section\n className={`w-full py-20 md:py-28 ${className}`}\n style={{ background: 'var(--sk-bg, #0a0a0a)' }}\n data-portfolio-section=\"portfolioLeadForm\"\n >\n <div className=\"max-w-2xl mx-auto px-6\">\n <div\n className=\"rounded-2xl p-8 md:p-12\"\n style={{\n background: 'var(--sk-surface, rgba(255,255,255,0.05))',\n backdropFilter: 'blur(16px)',\n border: '1px solid var(--sk-border, rgba(255,255,255,0.1))',\n }}\n >\n <h2\n className=\"text-2xl md:text-3xl font-bold mb-3\"\n style={{\n color: 'var(--sk-text-primary, #ffffff)',\n fontFamily: 'var(--sk-font-heading, inherit)',\n }}\n >\n {heading}\n </h2>\n <p\n className=\"text-base mb-8\"\n style={{ color: 'var(--sk-text-secondary, #a1a1aa)' }}\n >\n {description}\n </p>\n\n <ManagedForm\n formId={formId}\n onSuccess={() => {\n trackConversion({\n type: 'portfolio_lead',\n metadata: { case_study: item.slug, case_study_title: item.title },\n });\n }}\n />\n </div>\n </div>\n </section>\n );\n}\n","'use client';\n\n/**\n * PortfolioAnalytics — childless engagement-tracking island.\n *\n * Mounted by PortfolioPage as a SIBLING of the content (never a wrapper —\n * house perf doctrine), deferred to idle so it stays out of the hydration\n * path. Observes the server-rendered `data-portfolio-section` markers and\n * emits events through site-kit's standalone dispatch (which queues until\n * the site's AnalyticsProvider mounts and no-ops if it never does):\n *\n * portfolio_section_view once per section type per page view\n * portfolio_live_site_click clicks on [data-portfolio-action=\"live-site\"]\n * portfolio_cta_click clicks on [data-portfolio-action=\"cta\"]\n *\n * This is the raw material for \"your QCR case study converts best\" insights.\n */\n\nimport { useEffect } from 'react';\nimport { trackEvent } from '@sonordev/site-kit/client';\n\nexport default function PortfolioAnalytics({ slug }: { slug: string }) {\n useEffect(() => {\n let cleanup = () => {};\n\n const setup = () => {\n const seen = new Set<string>();\n const io = new IntersectionObserver(\n (entries) => {\n for (const entry of entries) {\n if (!entry.isIntersecting) continue;\n const type = (entry.target as HTMLElement).dataset.portfolioSection;\n if (!type || seen.has(type)) continue;\n seen.add(type);\n trackEvent({\n name: 'portfolio_section_view',\n category: 'portfolio',\n label: type,\n properties: { case_study: slug, section: type },\n });\n }\n },\n { threshold: 0.4 },\n );\n document\n .querySelectorAll<HTMLElement>('[data-portfolio-section]')\n .forEach((el) => io.observe(el));\n\n const onClick = (event: MouseEvent) => {\n const link = (event.target as HTMLElement).closest<HTMLAnchorElement>(\n 'a[data-portfolio-action]',\n );\n if (!link) return;\n const action = link.dataset.portfolioAction;\n trackEvent({\n name: action === 'live-site' ? 'portfolio_live_site_click' : 'portfolio_cta_click',\n category: 'portfolio',\n label: slug,\n properties: { case_study: slug, href: link.href },\n });\n };\n document.addEventListener('click', onClick, { passive: true });\n\n cleanup = () => {\n io.disconnect();\n document.removeEventListener('click', onClick);\n };\n };\n\n // Idle-defer: engagement tracking must never compete with hydration.\n let idleId: number | ReturnType<typeof setTimeout>;\n if ('requestIdleCallback' in window) {\n idleId = window.requestIdleCallback(setup, { timeout: 3000 });\n } else {\n idleId = setTimeout(setup, 1500);\n }\n\n return () => {\n if ('requestIdleCallback' in window) window.cancelIdleCallback(idleId as number);\n else clearTimeout(idleId as ReturnType<typeof setTimeout>);\n cleanup();\n };\n }, [slug]);\n\n return null;\n}\n"],"mappings":"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","names":["Link","GlassCard","Image","import_jsx_runtime","ScrollReveal","import_jsx_runtime","import_jsx_runtime","import_client"]}
@@ -19,22 +19,7 @@ import Link from "next/link";
19
19
  import GlassCard from "@sonordev/agency-site-kit/portfolio/primitives/GlassCard";
20
20
 
21
21
  // src/portfolio/curate-proof.ts
22
- var LIGHTHOUSE_RE = /lighthouse/i;
23
- function isLighthouseKpi(kpi) {
24
- return LIGHTHOUSE_RE.test(kpi.label);
25
- }
26
- function curateHeroKpis(kpis) {
27
- const business = kpis.filter((k) => !isLighthouseKpi(k));
28
- const lighthouse = kpis.filter(isLighthouseKpi);
29
- if (lighthouse.length <= 1) return kpis;
30
- const allPerfect = lighthouse.length >= 4 && lighthouse.every((k) => k.value === 100);
31
- const summary = allPerfect ? {
32
- ...lighthouse[0],
33
- label: "Lighthouse, all 4 categories",
34
- description: "Perfect scores across performance, SEO, accessibility, and best practices."
35
- } : lighthouse.find((k) => /performance/i.test(k.label)) ?? lighthouse[0];
36
- return [...business, summary];
37
- }
22
+ import { curateHeroKpis, isLighthouseKpi } from "@sonordev/site-kit/portfolio/contract";
38
23
 
39
24
  // src/portfolio/format.ts
40
25
  function formatCategoryLabel(slug) {