@sonordev/agency-site-kit 0.4.1 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/dist/{chunk-QGSW43TM.cjs → chunk-2Y2RJB3A.cjs} +12 -12
  2. package/dist/{chunk-QGSW43TM.cjs.map → chunk-2Y2RJB3A.cjs.map} +1 -1
  3. package/dist/{chunk-UXUU7FVT.cjs → chunk-6OWFFV6N.cjs} +4 -65
  4. package/dist/chunk-6OWFFV6N.cjs.map +1 -0
  5. package/dist/{chunk-NLGUPJZA.js → chunk-7FFZX7C5.js} +4 -4
  6. package/dist/{chunk-NLGUPJZA.js.map → chunk-7FFZX7C5.js.map} +1 -1
  7. package/dist/{chunk-7ZHRMWJU.cjs → chunk-A5WCMHAE.cjs} +2 -2
  8. package/dist/{chunk-7ZHRMWJU.cjs.map → chunk-A5WCMHAE.cjs.map} +1 -1
  9. package/dist/chunk-FM25LB67.js +50 -0
  10. package/dist/chunk-FM25LB67.js.map +1 -0
  11. package/dist/{chunk-KXLROAO5.js → chunk-FT2SNEYP.js} +2 -2
  12. package/dist/{chunk-KXLROAO5.js.map → chunk-FT2SNEYP.js.map} +1 -1
  13. package/dist/chunk-X76U7VLF.cjs +57 -0
  14. package/dist/chunk-X76U7VLF.cjs.map +1 -0
  15. package/dist/{chunk-RWQ675N6.js → chunk-X7UCJ45A.js} +4 -61
  16. package/dist/chunk-X7UCJ45A.js.map +1 -0
  17. package/dist/format-C-axf13G.d.cts +68 -0
  18. package/dist/format-poUpLtQ2.d.ts +68 -0
  19. package/dist/index.cjs +18 -17
  20. package/dist/index.cjs.map +1 -1
  21. package/dist/index.d.cts +5 -3
  22. package/dist/index.d.ts +5 -3
  23. package/dist/index.js +4 -3
  24. package/dist/index.js.map +1 -1
  25. package/dist/layout/index.d.cts +1 -1
  26. package/dist/layout/index.d.ts +1 -1
  27. package/dist/portfolio/client.cjs +2 -17
  28. package/dist/portfolio/client.cjs.map +1 -1
  29. package/dist/portfolio/client.js +1 -16
  30. package/dist/portfolio/client.js.map +1 -1
  31. package/dist/portfolio/index.cjs +18 -17
  32. package/dist/portfolio/index.d.cts +5 -74
  33. package/dist/portfolio/index.d.ts +5 -74
  34. package/dist/portfolio/index.js +4 -3
  35. package/dist/portfolio/sections/ChallengesSection.d.cts +1 -1
  36. package/dist/portfolio/sections/ChallengesSection.d.ts +1 -1
  37. package/dist/portfolio/sections/DetailsSection.d.cts +1 -1
  38. package/dist/portfolio/sections/DetailsSection.d.ts +1 -1
  39. package/dist/portfolio/sections/HeroSection.d.cts +1 -1
  40. package/dist/portfolio/sections/HeroSection.d.ts +1 -1
  41. package/dist/portfolio/sections/ResultsSection.cjs +4 -3
  42. package/dist/portfolio/sections/ResultsSection.cjs.map +1 -1
  43. package/dist/portfolio/sections/ResultsSection.d.cts +1 -1
  44. package/dist/portfolio/sections/ResultsSection.d.ts +1 -1
  45. package/dist/portfolio/sections/ResultsSection.js +2 -1
  46. package/dist/portfolio/sections/ResultsSection.js.map +1 -1
  47. package/dist/portfolio/sections/ServicesSection.d.cts +1 -1
  48. package/dist/portfolio/sections/ServicesSection.d.ts +1 -1
  49. package/dist/portfolio/sections/TechStackSection.d.cts +1 -1
  50. package/dist/portfolio/sections/TechStackSection.d.ts +1 -1
  51. package/dist/portfolio/sections/TestimonialSection.d.cts +1 -1
  52. package/dist/portfolio/sections/TestimonialSection.d.ts +1 -1
  53. package/dist/portfolio/server.cjs +40 -14
  54. package/dist/portfolio/server.d.cts +3 -1
  55. package/dist/portfolio/server.d.ts +3 -1
  56. package/dist/portfolio/server.js +3 -1
  57. package/dist/{types-0tVPy4pU.d.ts → types-He5A9okM.d.cts} +1 -1
  58. package/dist/{types-0tVPy4pU.d.cts → types-He5A9okM.d.ts} +1 -1
  59. package/package.json +3 -3
  60. package/dist/chunk-RWQ675N6.js.map +0 -1
  61. package/dist/chunk-UXUU7FVT.cjs.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/portfolio/curate-proof.ts","../src/portfolio/components/MetricsProvenance.tsx"],"names":["jsxs","jsx"],"mappings":"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","file":"chunk-UXUU7FVT.cjs","sourcesContent":["/**\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 * MetricsProvenance — \"these numbers are real\" strip for case studies.\n *\n * Server component, zero JS. Renders the live metric deltas the platform\n * computes from GSC/analytics baselines (MetricsDelta[]) plus the refresh\n * timestamp — the trust signal a static agency portfolio can't fake.\n *\n * `direction` semantics come from the API: 'up' ALWAYS means \"improved\"\n * (lower-is-better metrics like Avg Position/LCP are inverted server-side\n * in metrics-baseline.service), so 'up' renders green. The ARROW glyph\n * follows the raw numbers instead — an improved bounce rate shows a green ▼,\n * never a green ▲ next to a value that visibly went down.\n *\n * Deltas pass through gateMetricsDeltas (curate-proof.ts) before display:\n * raw platform deltas carry no editorial judgment, and this strip only\n * exists to flatter — see that module for the policy.\n */\n\nimport React from 'react';\nimport type { MetricsDelta } from '../../types';\nimport { gateMetricsDeltas } from '../curate-proof';\n\ninterface MetricsProvenanceProps {\n deltas: MetricsDelta[];\n /** ISO timestamp of the last live-metrics refresh. */\n refreshedAt?: string | null;\n}\n\nconst DIRECTION_COLOR: Record<MetricsDelta['direction'], string> = {\n up: '#10b981',\n down: '#ef4444',\n flat: 'var(--sk-text-tertiary, #71717a)',\n};\n\nfunction formatValue(v: number): string {\n return Number.isInteger(v)\n ? v.toLocaleString('en-US')\n : v.toLocaleString('en-US', { maximumFractionDigits: 2 });\n}\n\nfunction formatDate(iso: string): string {\n try {\n return new Intl.DateTimeFormat('en-US', {\n year: 'numeric',\n month: 'long',\n day: 'numeric',\n }).format(new Date(iso));\n } catch {\n return iso;\n }\n}\n\nexport default function MetricsProvenance({ deltas, refreshedAt }: MetricsProvenanceProps) {\n const shown = gateMetricsDeltas(deltas);\n if (!shown.length) return null;\n\n return (\n <div\n className=\"mt-10 rounded-2xl overflow-hidden\"\n style={{\n background: 'var(--sk-surface, rgba(255,255,255,0.05))',\n border: '1px solid var(--sk-border, rgba(255,255,255,0.1))',\n }}\n >\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3\">\n {shown.map((d, i) => {\n const color = DIRECTION_COLOR[d.direction] ?? DIRECTION_COLOR.flat;\n // Arrow tracks the raw numbers; color tracks improved/worsened.\n const arrow = d.current === d.baseline ? '—' : d.current > d.baseline ? '▲' : '▼';\n return (\n <div\n key={`${d.metric}-${i}`}\n className=\"flex flex-col gap-1 p-5\"\n style={{ borderBottom: '1px solid var(--sk-border, rgba(255,255,255,0.06))' }}\n >\n <span\n className=\"text-xs font-semibold uppercase tracking-wider\"\n style={{ color: 'var(--sk-text-tertiary, #71717a)' }}\n >\n {d.metric}\n </span>\n <span className=\"flex items-baseline gap-2\">\n <span\n className=\"text-lg font-bold\"\n style={{ color: 'var(--sk-text-primary, #ffffff)' }}\n >\n {formatValue(d.baseline)} → {formatValue(d.current)}\n </span>\n <span className=\"text-sm font-semibold\" style={{ color }}>\n {arrow} {Math.abs(d.deltaPercent)}%\n </span>\n </span>\n <span className=\"text-xs\" style={{ color: 'var(--sk-text-tertiary, #71717a)' }}>\n {d.timespan}\n </span>\n </div>\n );\n })}\n </div>\n\n <div\n className=\"flex items-center gap-2 px-5 py-3\"\n style={{\n background: 'color-mix(in srgb, #10b981 6%, transparent)',\n color: 'var(--sk-text-secondary, #a1a1aa)',\n }}\n >\n <span\n className=\"inline-block w-2 h-2 rounded-full shrink-0\"\n style={{ background: '#10b981' }}\n />\n <span className=\"text-xs font-medium\">\n Verified from live analytics\n {refreshedAt ? ` · Refreshed ${formatDate(refreshedAt)}` : ''}\n </span>\n </div>\n </div>\n );\n}\n"]}