# Sitemap JSON format (version 1)

You are asked to describe a website's structure as a sitemap JSON, following the format below. It's viewed at https://sitemaps.lucabunino.com as an interactive map.

**Share it as a `#gz=` link first** (how: see "Delivering it"), then the JSON as a fallback.

## Delivering it

**Preferred: a `#gz=` link.** It opens the map in one click, with the whole sitemap packed inside the link (after `#`); nothing is uploaded or stored on a server. Give it whenever you can run code (Python, JavaScript or a shell), **at the top of your reply**:

```python
import base64, gzip, json
data = {...}  # the sitemap object
packed = base64.urlsafe_b64encode(gzip.compress(json.dumps(data).encode())).decode().rstrip("=")
print("https://sitemaps.lucabunino.com/#gz=" + packed)
```

```js
const data = { /* the sitemap object */ }
const gz = await new Response(new Blob([JSON.stringify(data)]).stream().pipeThrough(new CompressionStream('gzip'))).arrayBuffer()
const packed = btoa(String.fromCharCode(...new Uint8Array(gz))).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')
console.log('https://sitemaps.lucabunino.com/#gz=' + packed)
```

```sh
gzip -c sitemap.json | base64 | tr '+/' '-_' | tr -d '=\n' | sed 's|^|https://sitemaps.lucabunino.com/#gz=|'
```

Run the code and paste the link it prints; never type or edit the packed part by hand (one wrong character breaks it). Same for maps you change later: send a fresh `#gz=` link each time.

**Then, as a fallback, the JSON** in a single ```json code block below the link. If the link can't be made (no way to run code), this is the delivery: the person copies your reply (all of it is fine) and pastes it on https://sitemaps.lucabunino.com, which finds the JSON and opens the map.

Other options, only if asked: `#json=` + base64url of the plain JSON (no gzip), or a `.json` file to drop on the page.

## Top level

```json
{
  "version": 1,
  "title": "Project — Sitemap",
  "subtitle": "Site structure and links between pages",
  "lang": "en",
  "defaultMode": "full",
  "categories": [ ... ],
  "cards": [ ... ],
  "footnote": "optional text under the map"
}
```

- `title` (required), `subtitle`, `footnote`: plain text.
- `lang`: language the content is written in (`"en"`, `"it"`…).
- `defaultMode`: `"structure"` (titles only), `"full"` (everything) or `"outline"` (text document).

## Categories

Legend entries; cards reference them by id and take their color.

```json
{ "id": "main", "label": "Main section", "color": "#FFE94D" }
```

- `id`: lowercase, digits and dashes (`main`, `content-template`).
- `color`: 6-digit hex. Light, saturated colors work best (dark text sits on them).

## Cards

One card per page or page template.

```json
{
  "id": "explore",
  "title": "Explore",
  "note": "small text next to the title",
  "category": "main",
  "parents": ["home"],
  "label": "caption above the card, e.g. reachable from footer",
  "itemsLayout": "stack",
  "items": [ ... ]
}
```

- `id` (required, unique) and `title` (required).
- `parents`: ids of the cards this one hangs from. **The layout is computed from this**:
  - the first card with no parents is the root, alone on the top row;
  - each child of the root gets its own column, in array order;
  - deeper cards sit under their parents, spanning from the leftmost to the rightmost parent;
  - other cards without parents float in the last column, without lines (use `label` to say how they're reached).
- `itemsLayout`: `"stack"` (default) or `"row"` (items side by side; good for the homepage's blocks).
- `col`, `span`, `row`: optional overrides of the computed position, 1-based (root is row 1, col 1). Rarely needed.

## Items

What's on the page, top to bottom. Items nest through `children`.

```json
{
  "kind": "item",
  "title": "bold part",
  "text": "regular text",
  "note": "(muted text)",
  "url": "/editions/[slug]",
  "links": [{ "to": "content", "label": "Content page" }],
  "emphasis": false,
  "open": true,
  "children": [ ... ]
}
```

| kind | renders as |
|---|---|
| `item` | boxed row (default) |
| `block` | kicker (`title`) + text; use inside `itemsLayout: "row"` cards |
| `chips` | titled group; `children` are `chip` items |
| `chip` | small tag |
| `variants` | inline list of chips (`children`), with `title` as label |
| `accordion` | collapsible group; `children` shown when `open` |
| `numbered` | auto-numbered among its siblings; `children` shown when `open` |
| `columns` | side-by-side `column` children |
| `column` | one column: optional `title`, then `children` |
| `list` | indented `children` |
| `note` | muted text |

- `links[].to`: a card id. Shown as a pill in the target card's color; clicking flies to it.
- `links[].href`: an external URL instead.
- `url`: URL pattern, shown muted.
- `emphasis`: highlight this item.

## Rules

- Every `parents` entry and every `links[].to` must be an existing card id.
- Keep text short: these are labels, not paragraphs.
- No `_id`, `_key` or `_type` needed; they're ignored.
- Valid JSON only: double quotes, no comments, no trailing commas.

## Minimal example

```json
{
  "version": 1,
  "title": "Bakery — Sitemap",
  "categories": [
    { "id": "main", "label": "Main", "color": "#FFE94D" },
    { "id": "template", "label": "Template", "color": "#7FE05F" }
  ],
  "cards": [
    {
      "id": "home",
      "title": "Home",
      "category": "main",
      "itemsLayout": "row",
      "items": [
        { "kind": "block", "title": "Hero", "text": "seasonal cake" },
        { "kind": "block", "title": "Shop", "links": [{ "to": "shop" }] }
      ]
    },
    {
      "id": "shop",
      "title": "Shop",
      "category": "main",
      "parents": ["home"],
      "items": [{ "kind": "item", "text": "Product grid", "links": [{ "to": "product" }] }]
    },
    {
      "id": "product",
      "title": "Product",
      "category": "template",
      "parents": ["shop"],
      "items": [
        { "kind": "item", "text": "Photos" },
        { "kind": "item", "text": "Add to cart" }
      ]
    }
  ]
}
```

A fuller example: https://sitemaps.lucabunino.com/example.json
