# Brand layout

`AgencySiteKitLayout` wraps your site in your agency's brand. It fetches the
colours, fonts and radii you set in Sonor and publishes them as `--sk-*` CSS
variables, in light and dark, so your case studies (and the rest of your
site) can style themselves from one source. It also hands site-kit's client
modules the short-lived token they need.

## Add it to your root layout

```tsx
// app/layout.tsx
import { AgencySiteKitLayout } from '@sonordev/agency-site-kit';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <AgencySiteKitLayout>{children}</AgencySiteKitLayout>
      </body>
    </html>
  );
}
```

It's an async server component, exported from the root entry and from
`@sonordev/agency-site-kit/layout`. On the server it fetches your brand with
[`getPortfolioBrandConfig`](https://sonor.dev/agency-site-kit/fetching#getportfoliobrandconfigoptions) (cached for an
hour, or until Sonor revalidates the `portfolio` tag, so a brand edit shows up
with your case studies) and mints the client token.

## Props

| Prop                    | Default                                      | What it does                                                                                   |
| ----------------------- | -------------------------------------------- | ---------------------------------------------------------------------------------------------- |
| `children`              |                                              | Your page                                                                                      |
| `apiKey`                | `SONOR_API_KEY`                              | The key to fetch the brand with and to mint the client token from                              |
| `apiUrl`                | `SONOR_API_URL`, else `https://api.sonor.io` | The API origin                                                                                 |
| `brandConfig`           | Fetched from Sonor                           | A `Partial<BrandConfig>` to use instead of fetching. Missing fields fall back to the defaults. |
| `theme`                 | Follows the visitor's system                 | `'light'` or `'dark'` forces one, by setting `data-theme` on the wrapper                       |
| `className`             |                                              | A class for the wrapper `<div>`                                                                |
| `credential`            | `true`                                       | Mint a short-lived client token for site-kit's client modules. See below.                      |
| `showLlmsTxtFooterLink` | `false`                                      | Render a hidden footer link to `/llms.txt` after your content                                  |

## What it renders

In order:

1. A `preconnect` and a `dns-prefetch` hint for the Sonor API's origin.
2. A `<style data-agency-site-kit="brand">` with the CSS variables.
3. The client token for site-kit's client modules (when `credential` is on and
   a key is set). It renders nothing visible and never wraps your content.
4. A `<div data-agency-site-kit="root">` around your content, with
   `data-theme` when you pass `theme`, your `className`, and inline
   `font-family`, `color` and `background-color` from the variables.

## The CSS variables

| Variable                                          | From `BrandConfig`                    |
| ------------------------------------------------- | ------------------------------------- |
| `--sk-primary`, `--sk-primary-rgb`                | `primary`                             |
| `--sk-secondary`, `--sk-secondary-rgb`            | `secondary`                           |
| `--sk-bg`                                         | `background`                          |
| `--sk-bg-elevated`                                | `backgroundElevated`                  |
| `--sk-surface`                                    | `surface`                             |
| `--sk-surface-hover`                              | `surfaceHover`                        |
| `--sk-border`                                     | `surfaceBorder`                       |
| `--sk-text-primary`                               | `textPrimary`                         |
| `--sk-text-secondary`                             | `textSecondary`                       |
| `--sk-text-tertiary`                              | `textTertiary`                        |
| `--sk-radius-sm`, `--sk-radius`, `--sk-radius-lg` | `radius.sm`, `radius.md`, `radius.lg` |
| `--sk-font-heading`                               | `fontHeading`                         |
| `--sk-font`                                       | `fontBody`                            |

The `-rgb` variables hold `R, G, B`, for colours with transparency:

```css
.card {
  background: var(--sk-surface);
  border: 1px solid var(--sk-border);
  border-radius: var(--sk-radius-lg);
  box-shadow: 0 12px 40px rgba(var(--sk-primary-rgb), 0.15);
}
```

### Light and dark

The light values go on `:root` and `[data-theme="light"]`, the dark values on
`[data-theme="dark"]`, and a `prefers-color-scheme: dark` rule applies the
dark values to `:root` unless it's marked `data-theme="light"`. So by default
the site follows the visitor's system, and `theme` (or your own `data-theme`
attribute) pins it.

Dark mode changes only the colours. Its values come from your brand's
`darkMode` overrides, then the built-in dark defaults (`DEFAULT_DARK_MODE`).
Your `primary` and `secondary` carry over unless `darkMode` sets its own.

If Sonor can't be reached or has no brand set, the layout uses the built-in
light defaults (`DEFAULT_BRAND_CONFIG`) rather than failing the page. Any
field your brand leaves out falls back to those defaults too.

## The client token

site-kit's client modules (analytics, forms, chat, booking) need a credential
in the browser, and your `SONOR_API_KEY` has to stay on the server. So the
layout mints a short-lived token from the key on the server and publishes that
instead, the same way site-kit's own `SiteKitLayout` does. That's why you
don't need a `NEXT_PUBLIC_` copy of your key.

Mount those modules as siblings of your content, not wrappers around it, so
your pages stay server-rendered. See
[site-kit's layout docs](https://sonor.dev/site-kit/layout) and
[analytics docs](https://sonor.dev/site-kit/analytics).

### With site-kit's `SiteKitLayout`

If your site also mounts `SiteKitLayout` from `@sonordev/site-kit`, it
publishes its own token. Two publishers would race, so turn this one off:

```tsx
// app/layout.tsx
import { SiteKitLayout } from '@sonordev/site-kit/layout';
import { AgencySiteKitLayout } from '@sonordev/agency-site-kit';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <SiteKitLayout>
          <AgencySiteKitLayout credential={false}>{children}</AgencySiteKitLayout>
        </SiteKitLayout>
      </body>
    </html>
  );
}
```

Only set `credential={false}` when something else publishes the token.
Without one, site-kit's client modules can't authenticate.

## Brand utilities

The root entry exports the pieces the layout is built from, for when you need
the CSS somewhere else:

| Export                      | What it does                                                                                                                  |
| --------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `generateBrandCSS(config)`  | The full stylesheet above (light, dark and the system-preference rule) as a string. Missing fields fall back to the defaults. |
| `mergeBrandConfig(partial)` | A complete `BrandConfig` from a partial one, over the defaults                                                                |
| `hexToRgb(hex)`             | `'#6366f1'` to `'99, 102, 241'`. Takes 3 or 6 hex digits, returns `null` for anything else.                                   |
| `DEFAULT_BRAND_CONFIG`      | The light defaults: an indigo primary, white background, Inter, radii of `0.375rem`, `0.5rem` and `0.75rem`                   |
| `DEFAULT_DARK_MODE`         | The dark colour defaults                                                                                                      |

```tsx
import { generateBrandCSS } from '@sonordev/agency-site-kit';
import { getPortfolioBrandConfig } from '@sonordev/agency-site-kit/portfolio/server';

const css = generateBrandCSS(await getPortfolioBrandConfig());
```

That's the same fetch the layout makes, from the same cache entry, so the two
always agree, including on the `DEFAULT_BRAND_CONFIG` fallback. See
[Fetching](https://sonor.dev/agency-site-kit/fetching#getportfoliobrandconfigoptions).

## The llms.txt link

`showLlmsTxtFooterLink` renders a visually hidden `<footer>` with a link to
`/llms.txt` after your content. The better way to point AI crawlers at your
`llms.txt` is a response header: see
[site-kit's llms.txt docs](https://sonor.dev/site-kit/llms).
