> ## Documentation Index
> Fetch the complete documentation index at: https://docs.wpos.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Media and fonts

> The media manifest: hash-cached image sideloading, custom font embedding, and the media map pages read from.

Production assets go through the media manifest, not hardcoded URLs. The
manifest sideloads images into the WordPress media library and embeds custom
fonts, then writes a map file your theme and pages read at build time.

```bash theme={null}
npx exjsx media data/media.manifest.mjs
```

## The manifest contract

`data/media.manifest.mjs` default-exports an array of entries:

```js theme={null}
export default [
  { slot: 'hero',      src: 'https://example.com/hero.jpg', alt: 'Hero shot' },   // remote image
  { slot: 'logo',      file: 'assets/logo.svg', alt: 'Brand logo' },              // local image
  { slot: 'head-font', type: 'font', file: 'assets/Fraunces.woff2',
    family: 'Fraunces', role: 'head', weight: 600 },                              // local font
];
```

* **`{ slot, src }`** fetches a remote image and uploads it to the target site's
  media library. Rights note: this fetches third-party URLs verbatim; run it
  only for assets you own or license.
* **`{ slot, file }`** uploads a local file. Supported image formats: jpg, jpeg,
  png, webp, svg, gif.
* **`{ slot, type: 'font', file, family, role, weight }`** embeds a local font
  file (woff2 or woff) as a base64 data-URI in the map. WordPress blocks woff2
  uploads by default; a data-URI `@font-face` needs no server configuration.
  The compiler emits the `@font-face` carrier for you; this path is for
  custom licensed font files only. Google fonts load natively via style props
  (see [Components](/ultra/components)).
* `alt` on image entries is set as the attachment's alt text.

## Idempotency by slot

Every entry is keyed by its `slot`; attachments are slugged `exjsx-<slot>`.
Re-running the command is safe:

* A local file whose bytes match the map's hash is skipped with zero network
  calls (`SAME`).
* A `src` entry whose slug already exists in the library is kept (`KEEP`).
* A local file with **changed bytes** replaces the stale attachment (`REUP`),
  so a recolored logo actually reaches the site instead of silently serving the
  old one.

```text theme={null}
  FONT  head-font -> Fraunces (48 KB, embedded)
  UP    hero -> id 231 (184 KB)
  SAME  logo -> id 187 (hash match, skipped)
media-map -> data/media-map.json (3 slots). Rebuild + redeploy to use it.
```

## The map: data/media-map.json

The command writes `data/media-map.json` (the optional second CLI argument
overrides the path): `{ slot: { id, url, alt } }` for images and
`{ slot: { type: 'font', family, role, weight, url } }` for fonts. **Read ids
and URLs from the map; never hardcode them.**

```jsx theme={null}
import map from '../data/media-map.json' with { type: 'json' };

<img src={map.hero.id} />
```

Two alt rules, both enforced:

* URL-src `<img>` takes inline `alt`; never leave it empty (lint: `img-alt`).
* Attachment-id images take alt from the attachment only; the compiler throws
  if you pass `alt` there, and the error carries the recipe.

<Warning>
  Never bake dev-host URLs (`localhost:...`, `127.0.0.1:...`) into content that
  will deploy elsewhere. Sideload to the target site, or key URLs off `WP_URL`
  at build time. `exjsx lint` catches this (`env-baked-url`); a portability
  audit once found three hardcoded stack URLs this way.
</Warning>

## Next

<CardGroup cols={2}>
  <Card title="Deploy" icon="upload" href="/ultra/deploy">
    Build, lint, and ship the bundle.
  </Card>

  <Card title="Styling" icon="palette" href="/ultra/styling">
    bgImage, bgOpts, and image fitting.
  </Card>
</CardGroup>
