# Types

Every shape the kit hands you is typed, from a list item to each section's
data. This page covers where to import the types from and what the main ones
hold. The declarations in the package are the full reference; this is the
map.

## Where to import them

| From                                         | You get                                                                                                                                                                                                              |
| -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `@sonordev/agency-site-kit/portfolio`        | The case study types: items, sections, each section's data, KPIs, metrics, `ProofRecord` and `PORTFOLIO_SECTION_TYPES`, plus `HeadlineKpi`, `SoloDeviceInput`, `PortfolioJsonLdOptions` and `ResolvedPortfolioImage` |
| `@sonordev/agency-site-kit`                  | All of the above, plus the item-level types inside sections (`PortfolioChallengeItem`, `PortfolioResultItem`, `PortfolioGalleryImage` and so on), `BrandConfig`, `SanityImageRef` and `PortfolioConfigResponse`      |
| `@sonordev/agency-site-kit/layout`           | `AgencySiteKitLayoutProps`                                                                                                                                                                                           |
| `@sonordev/agency-site-kit/portfolio/server` | `PortfolioSitemapEntry`                                                                                                                                                                                              |
| `@sonordev/agency-site-kit/revalidate`       | `CreateRevalidateHandlerOptions`                                                                                                                                                                                     |
| `@sonordev/agency-site-kit/preview`          | `PortfolioPreviewHandlerOptions`                                                                                                                                                                                     |

The root entry is server only, but a type import is erased at build time, so
`import type { PortfolioResultItem } from '@sonordev/agency-site-kit'` is fine
in a client component.

## Case studies

### `PortfolioItem`

A published case study as the list returns it.

| Field                                 | Type                                       | What it is                                                                                                                                                           |
| ------------------------------------- | ------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`, `slug`, `title`, `subtitle`     | `string`                                   |                                                                                                                                                                      |
| `description`                         | `string`                                   |                                                                                                                                                                      |
| `category`                            | `string`                                   | Stored as a slug. Print it with `formatCategoryLabel`.                                                                                                               |
| `industry_tags`                       | `string[]`, optional                       | Industry categories for industry pages, primary first                                                                                                                |
| `services`                            | `string[]`                                 | Service tags. Print them with `formatServiceTag`.                                                                                                                    |
| `hero_image`, `hero_image_alt`        | `string`                                   |                                                                                                                                                                      |
| `hero_screenshots`                    | `{ desktop?, tablet?, mobile? }` or `null` | Device screenshots, as URLs                                                                                                                                          |
| `showcase_sites`                      | array or `null`, optional                  | One entry per site when the case study covers several (see [Sections and devices](https://sonor.dev/agency-site-kit/sections#case-studies-that-cover-several-sites)) |
| `live_url`                            | `string` or `null`                         |                                                                                                                                                                      |
| `kpis`                                | `PortfolioKPI[]`                           | The headline numbers                                                                                                                                                 |
| `details`                             | `PortfolioDetailsData` or `null`           | Industry, location, website, timeline, launch date, budget range                                                                                                     |
| `seo`                                 | `PortfolioSeoData` or `null`               | Meta title, meta description, keywords                                                                                                                               |
| `featured`                            | `boolean`                                  |                                                                                                                                                                      |
| `order`                               | `number`, optional                         |                                                                                                                                                                      |
| `published_at`                        | `string` or `null`                         | ISO timestamp                                                                                                                                                        |
| `project_id`                          | `string` or `null`                         | The Sonor project it was built from, if any                                                                                                                          |
| `baseline_metrics`, `current_metrics` | `MetricsSnapshot` or `null`                | Search, Core Web Vitals, analytics and page counts, before and now                                                                                                   |
| `metrics_last_refreshed_at`           | `string` or `null`                         | When the live metrics last updated                                                                                                                                   |

### `PortfolioItemFull`

What `getPortfolioItem` returns: a `PortfolioItem` plus

| Field             | Type                 | What it is                                                                             |
| ----------------- | -------------------- | -------------------------------------------------------------------------------------- |
| `sections`        | `PortfolioSection[]` | The case study's content (see below)                                                   |
| `metricsDelta`    | `MetricsDelta[]`     | Live analytics changes. Run them through `gateMetricsDeltas` before showing any.       |
| `sectionsOrdered` | `boolean`, optional  | True once someone reordered the sections in the dashboard. `orderedSections` reads it. |

### `PortfolioListResponse`

`{ items: PortfolioItem[]; total: number; limit: number; offset: number }`,
from `getPortfolioItems`.

## Numbers

### `PortfolioKPI`

```ts
interface PortfolioKPI {
  label: string;
  value: number;
  suffix: string;
  prefix?: string;
  description: string;
  source: MetricSource;            // 'measured' | 'reported' | 'estimated'
  reportedBy?: MetricAttribution;  // required when source is 'reported'
}
```

A results card (`PortfolioResultItem`) carries the same `source` and
`reportedBy`, with its figure in `metric: { value, suffix, prefix? }`.
`MetricAttribution` is `{ name, role?, organization, date }`, with `date` as
`YYYY-MM-DD`. [Proof and provenance](https://sonor.dev/agency-site-kit/proof) covers what each source may
claim.

### `MetricsDelta`

```ts
interface MetricsDelta {
  metric: string;
  baseline: number;
  current: number;
  delta: number;
  deltaPercent: number;
  direction: 'up' | 'down' | 'flat'; // 'up' means improved, even for lower-is-better metrics
  timespan: string;
}
```

### `MetricsSnapshot`

Four groups of optional numbers: `seo` (`clicks_28d`, `impressions_28d`,
`avg_position`, `ctr`), `cwv` (`lcp_ms`, `inp_ms`, `cls`,
`performance_score`), `analytics` (`sessions_28d`, `page_views_28d`,
`conversion_rate`, `bounce_rate`) and `pages` (`total_indexed`,
`total_crawled`, `total_pages`).

## Sections

`PortfolioSection<T>` is `{ sectionType, displayName, data }`, where `data`
is typed by `PortfolioSectionDataMap[sectionType]`. `PortfolioSectionType` is
the union of the names in `PORTFOLIO_SECTION_TYPES`, and
`PortfolioSectionData` is the union of every data shape.

| `sectionType`               | Data type                       | Main fields                                                                                                               |
| --------------------------- | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `portfolioHero`             | `PortfolioHeroData`             | `headline`, `subheadline`, `description`, `category`, `services`, `liveUrl?`, `screenshots`, `kpis`                       |
| `portfolioChallenges`       | `PortfolioChallengesData`       | `items`: `{ title, description, solution, result?, icon? }`                                                               |
| `portfolioStrategy`         | `PortfolioStrategyData`         | `phases`: `{ number, title, description, deliverables?, icon?, timeline? }`                                               |
| `portfolioResults`          | `PortfolioResultsData`          | `items`: `PortfolioResultItem`                                                                                            |
| `portfolioTechStack`        | `PortfolioTechStackData`        | `technologies`: `{ name, category, icon?, description? }`                                                                 |
| `portfolioServices`         | `PortfolioServicesData`         | `items`: `{ title, description, features, icon? }`                                                                        |
| `portfolioTestimonial`      | `PortfolioTestimonialData`      | `quote`, `author`, `title`, `company`, `avatar?`, `rating?`                                                               |
| `portfolioGallery`          | `PortfolioGalleryData`          | `images`: `{ image, caption?, type? }`, `layout?`                                                                         |
| `portfolioVideo`            | `PortfolioVideoData`            | `url?`, `embedUrl?`, `thumbnail`, `title`, `duration?`, `platform?`                                                       |
| `portfolioTeam`             | `PortfolioTeamData`             | `members`: `{ name, role, avatar? }`                                                                                      |
| `portfolioFeatureSpotlight` | `PortfolioFeatureSpotlightData` | `image`, `title`, `description`, `layout`, `annotations`                                                                  |
| `portfolioBeforeAfter`      | `PortfolioBeforeAfterData`      | `before`, `after`, `beforeUrl?`, `afterUrl?`, `beforeLabel?`, `afterLabel?`, `archiveUrl?`, `defaultPosition?`            |
| `portfolioMetricsTimeline`  | `PortfolioMetricsTimelineData`  | `dataPoints`, `baselineDate`, `title?`, `description?`                                                                    |
| `portfolioConversionFunnel` | `PortfolioConversionFunnelData` | `stages`: `{ label, value, icon?, color? }`                                                                               |
| `portfolioPerformance`      | `PortfolioPerformanceData`      | `before?`, `after?` (Lighthouse scores), `cwv?`, `formFactor?`, `method?`, `measuredAt?`, `measuredUrl?`, `beforeMethod?` |
| `portfolioDetails`          | `PortfolioDetailsData`          | `industry`, `location?`, `website?`, `timeline?`, `launchDate?`, `budgetRange?`                                           |
| `portfolioSeo`              | `PortfolioSeoData`              | `metaTitle`, `metaDescription`, `keywords`, `ogImage?`                                                                    |
| `portfolioCTA`              | `PortfolioCTAData`              | `headline`, `description`, `buttonText`, `buttonUrl`, `style?`                                                            |

`portfolioSpeedComparison`, `portfolioSiteArchitecture` and
`portfolioDesignSystem` have no data type yet.

A few notes that save a bug:

- **Image fields carry URLs.** `avatar`, `image`, `thumbnail`, `before`,
  `after` and `ogImage` are typed as `SanityImageRef`, the older image object,
  but hold plain URL strings at runtime. Pass them to `resolvePortfolioImage`,
  which takes either. The type stays so existing code keeps compiling.
- **Performance captions.** `PortfolioPerformanceData`'s `formFactor`,
  `method`, `measuredAt` and `measuredUrl` say how the scores were taken.
  State them in the section's caption when they're present (print
  `measuredAt` with `formatIsoDate`, and show only the host of
  `measuredUrl`). `beforeMethod` belongs next to before-and-after scores.
- **Before and after.** Prefer `beforeUrl` and `afterUrl` when they're set.
  `beforeLabel` says where the old image came from (for example an archive
  capture) and belongs in its badge and alt text.

## Evidence: `ProofRecord`

What `getProofRecords` returns, one per published piece of evidence.

| Field                                              | Type                           | What it is                                                                            |
| -------------------------------------------------- | ------------------------------ | ------------------------------------------------------------------------------------- |
| `id`                                               | `string`                       |                                                                                       |
| `site`                                             | `string`                       | The site the record is published on                                                   |
| `case_slug`                                        | `string`                       | The case study it belongs to                                                          |
| `metric`, `definition`                             | `string`                       | What was counted, and how it's defined                                                |
| `result_value`, `unit`                             | `number`, `string`             | The result                                                                            |
| `window_start`, `window_end`                       | `string`                       | The period it covers                                                                  |
| `baseline_value`, `baseline_start`, `baseline_end` | `number` / `string`, or `null` | What it's compared against, when there's a baseline                                   |
| `source_url`, `source_label`                       | `string`                       | Where it came from. A client-reported figure names who reported it in `source_label`. |
| `limitations`                                      | `string`                       | What it doesn't prove                                                                 |
| `reviewed_at`                                      | `string`                       | When it was last reviewed                                                             |

## Brand

`BrandConfig` is the agency brand `AgencySiteKitLayout` turns into CSS
variables: `primary`, `secondary`, `background`, `backgroundElevated`,
`surface`, `surfaceHover`, `surfaceBorder`, `textPrimary`, `textSecondary`,
`textTertiary`, `fontHeading` and `fontBody`, plus optional `logoUrl`,
`logoAlt`, `radius` (`{ sm, md, lg }`) and `darkMode` (colour overrides for
dark mode). See [Brand layout](https://sonor.dev/agency-site-kit/layout).

`PortfolioConfigResponse` is the raw brand endpoint's answer:
`{ brand: BrandConfig; orgName: string; orgSlug: string }`.

## Helper types

| Type                                                               | Used by                                                                                             |
| ------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------- |
| `HeadlineKpi`                                                      | `headlineKpi()`'s result: `{ value, label, source, credit, kpi }`                                   |
| `SoloDeviceInput`                                                  | `shouldRenderSoloDevice()` and `resolveSoloScreenshot()`: `{ category?, screenshots?, heroImage? }` |
| `PortfolioJsonLdOptions`                                           | `buildPortfolioJsonLd()`'s options: `{ url, publisher? }`                                           |
| `ResolvedPortfolioImage`                                           | `resolvePortfolioImage()`'s result: `{ url, width?, height? }`                                      |
| `PortfolioSitemapEntry`                                            | `generatePortfolioSitemapEntries()`'s entries                                                       |
| `AgencySiteKitLayoutProps`                                         | [Brand layout](https://sonor.dev/agency-site-kit/layout)                                            |
| `CreateRevalidateHandlerOptions`, `PortfolioPreviewHandlerOptions` | [Live updates and preview](https://sonor.dev/agency-site-kit/live-updates)                          |
