# The .pikko format (pikko/1)

This page is everything an AI assistant needs to make a Pikko presentation. Read it once, write the file, and give it to the person. No account, prompt template or other page is needed. Everything you need to write a deck comes before the section Examples; that last section holds three complete decks to learn from.

- Human page: https://pikko.no/format
- This page as Markdown: https://pikko.no/format/pikko-1.md
- JSON Schema for `deck.json`: https://pikko.no/format/pikko-1.schema.json
- Example files: https://pikko.no/format/examples/lesson-nb.pikko, https://pikko.no/format/examples/workshop-en.pikko, https://pikko.no/format/examples/pitch-en.pikko (each also as `.json`)

## What Pikko is

Pikko (https://pikko.no) is a presentation app for classrooms, workshops and talks. The audience joins on their phones with a short code and answers live polls that sit on the slides themselves, next to the content they are about.

Pikko has no built-in AI. You, the assistant, design the deck. Pikko stores exactly what the file says and draws it: it never moves, resizes, fixes or grades anything, and it sends no feedback about the design. The only thing it ever reports about a file is that it cannot read it, with the path of the problem. So the slides will look exactly as good as the boxes, sizes and colours you write.

A `.pikko` file holds the design of a deck: its slides, and on each slide its objects (text, shapes, images, icons, lines, charts, tables, equations, media and polls), each in an exact box. It never holds what happened in the room: votes, words, names and sessions stay in Pikko.

## Quick start

This is a complete deck with one slide. It opens in Pikko as it is.

```json
{
  "format": "pikko/1",
  "title": "Coffee or tea?",
  "language": "en",
  "styles": {
    "colors": { "ink": "#1d2433", "paper": "#f6f3ee", "accent": "#c2410c", "muted": "#5b6170" },
    "text": {
      "title": { "font": "sans", "size": 52, "weight": 800, "color": "ink", "lineHeight": 1.1 },
      "body": { "font": "sans", "size": 28, "weight": 400, "color": "ink", "lineHeight": 1.35 },
      "label": { "font": "sans", "size": 18, "weight": 700, "color": "accent", "letterSpacing": 2 }
    }
  },
  "slides": [
    {
      "id": "warm-up",
      "title": "Warm-up vote",
      "background": "paper",
      "notes": "Give people a minute to join before you start.",
      "objects": [
        { "type": "text", "id": "warm-up-label", "x": 88, "y": 72, "w": 400, "h": 28, "style": "label", "text": "WARM-UP" },
        { "type": "text", "id": "warm-up-title", "x": 88, "y": 108, "w": 520, "h": 128, "style": "title", "text": "A quick vote to start" },
        { "type": "text", "id": "warm-up-body", "x": 88, "y": 268, "w": 520, "h": 120, "style": "body", "color": "muted",
          "text": "Vote on your phone. The results grow on the slide as you answer." },
        { "type": "poll", "id": "warm-up-poll", "x": 688, "y": 176, "w": 504, "h": 452, "kind": "choice",
          "question": "What do you drink in the morning?",
          "options": [{ "id": "coffee", "text": "Coffee" }, { "id": "tea", "text": "Tea" }, { "id": "neither", "text": "Neither" }],
          "results": "live", "fill": "ink", "color": "paper", "size": 26, "weight": 700 }
      ]
    }
  ]
}
```

What makes it work:

1. `"format": "pikko/1"` comes first, and every object has a `type` and a box (`x`, `y`, `w`, `h`) in px on a 1280×720 slide.
2. Colours and text styles are named once in `styles` and used by name. A text object without a `style` is body text, so it would use `body` too.
3. The slide sets its own `background`, and the text styles are dark on it.
4. The poll has a dark `fill` and a light `color` (Pikko draws its question, options and results in that colour on the card), sits on the right with the top-right corner left free for the join code, and has room for its results.
5. Every slide, object and poll option has a readable `id`.

## How a file gets into Pikko

There are two ways to package a deck. The content is the same.

| Form | What it is | When to use it |
|---|---|---|
| `lesson.pikko` | A zip file with `deck.json` at the top and a `media/` folder for images, audio and video. Refer to a file in it as `"src": "media/leaf.png"`. | When you can run code and create files, or the deck has its own images. |
| `deck.json` | The same JSON on its own. Images are `https://` links. | When you can only write text: put the JSON in one code block for the person to save as `deck.json` or paste into Pikko. |

Pikko keeps the files in `media/` inside the deck itself, and a deck is saved as one request, so the images, audio and video in a `.pikko` file can add up to at most **3.0 MB** (a file used twice counts twice). For more, or for large video, use `https://` links.

The person brings the file into Pikko on the page for a new presentation (open the `.pikko` or `.json` file, or paste the JSON). Pikko first shows the slides with sample answers, then creates the deck. To change a deck they already have, they open your file from that deck. Pikko matches slides, objects and options by `id` (see Ids), so earlier answers stay where they belong:

- A deck that has never been presented is replaced, and Pikko keeps its previous state as a revision the person can restore.
- A deck that has been presented is never edited. Pikko creates a new version linked to the old one and moves the join code to it, unless the old version is being presented right now: then the room keeps its code and the new version gets its own when it is first presented. The old version and its answers stay as they were.

If you are connected to Pikko through its connector (the MCP server at `https://pikko.no/mcp`), you do not need files at all: `read_format` returns this page, `list_decks` and `read_deck` read the person's decks as `deck.json`, `create_deck` and `update_deck` write them, `render` returns a PNG of each slide, and `read_results` returns aggregated answers when the person allows it. Through the connector, `read_deck` also returns the deck's `version` (pass it back as `expectedVersion` to `update_deck`, so a change someone made in between is never overwritten) and shows images stored in the deck as `media/<name>` references: keep those strings unchanged to keep the images, and use https links for new ones. `update_deck` can take just the slides you changed (`slides`: their ids).

## The canvas

Every slide is a canvas 1280 px wide and 720 px high. The origin is the top-left corner: `x` grows to the right and `y` grows downwards.

- An object's box is `x`, `y` (its top-left corner), `w` and `h`, all in px. A square is a square. Values may have decimals.
- Font sizes and other type measures are px on the same canvas, so `"size": 28` means the same thing on every slide and every screen.
- `objects` are drawn in order: the first is at the back, the last is in front. There is no z-index; to put text on a card, list the card first.
- `rotation` turns an object clockwise around its centre, in degrees.
- `x` can be 0–1280 and `y` 0–720. `w` can be 12.8–1280 and `h` 7.2–720, for every object, even a thin line: a box smaller than 12.8×7.2 px refuses the file. A box may run off the right or bottom edge (useful for a shape that bleeds off the slide), but keep anything people must read inside the safe area (see the design guide).
- A slide's `title` is never drawn. It names the slide in Pikko's slide list, on phones and in the session record. Put a `text` object on the slide for a visible heading.

## Ids

Pikko matches what comes back by id, never by content. Three things have ids:

| Thing | Field | Rules |
|---|---|---|
| Slide | `id` on the slide | 1–100 characters. Pikko generates `s1`, `s2`, … when absent. |
| Object | `id` on the object | 1–100 characters. Pikko generates `<slide id>-o1`, `-o2`, … when absent. |
| Poll option | `id` on each option | Starts with a letter or digit, then letters, digits, `_`, `.`, `:` or `-`, at most 64 characters. Pikko generates `o1`, `o2`, … when absent. |

Write short, readable ids that say what the thing is, such as `"intro"`, `"intro-title"` and `"light"`.

When you edit a file Pikko gave you (or one you wrote earlier):

- **Same id**: the same thing, changed. Its earlier answers stay with it.
- **New id**: something new.
- **An id you removed**: removed from this version. Answers from earlier sessions stay in those sessions' records.
- **The same id twice**: Pikko keeps the first and renames the second (`-2`), with a warning.

So keep every id you were given, even when you reword an option or move a slide, and never reuse an old id for something different. Answers to a poll are stored per option id: rewording "Water" to "Rain" keeps its votes; deleting it and adding a new option with the same id would attach the old votes to the new text.

## The deck

| Field | Type | Default | Meaning |
|---|---|---|---|
| `format` | `"pikko/1"` | required | Always the first field. Pikko refuses other versions. |
| `title` | text, 1–200 | "Untitled deck" | The deck's name in Pikko. |
| `language` | `en`, `nb`, `nn` | the person's language | The language of the slides (English, Norwegian Bokmål, Norwegian Nynorsk). Set it to the language you write in. |
| `theme` | `studio`, `chalkboard`, `ocean`, `sunset` | `studio` | Gives slides without a `background` a dark gradient: studio midnight blue, chalkboard green, ocean blue, sunset red-brown. |
| `pace` | `live`, `self` | `live` | `live`: the presenter moves through the slides. `self`: people go through the deck at their own pace on their phones. |
| `styles` | object | none | Named colours (`colors`) and text styles (`text`). See Styles. |
| `slides` | list of slides | required | 1–150 slides, in order. |
| `master` | object | none | Objects drawn on every slide that sets `"useMaster": true` (a logo, a frame). See The master. |
| `results` | object | none | Read-only answers Pikko may add when the person asks. Pikko ignores it when a file comes in. See Results. |
| `design`, `sections`, `customShows` | object, lists | none | Keep as is: Pikko's own settings. Leave them out of new decks; keep them unchanged when editing a file Pikko gave you. |

## Styles

### Named colours

`styles.colors` maps names to hex values. Any colour field in the deck then accepts the name instead of a value.

```json
{ "colors": { "ink": "#18302e", "paper": "#f5f0e6", "leaf": "#4a6417", "lime": "#d9e98b" } }
```

- A name starts with a letter and uses letters, digits, `-` and `_` (at most 40 characters). It cannot be a CSS colour word such as `red`, `white` or `navy`.
- A value is a hex colour: `#rgb`, `#rrggbb` or `#rrggbbaa`. At most 40 colours.
- Every colour field also accepts a hex value or any CSS colour (`"#ffffff"`, `"rgba(0,0,0,.5)"`, `"white"`) instead of a name. A bare word that is neither a name in `styles.colors` nor a CSS colour is kept as written, with a warning, and draws as nothing useful: check your spelling.
- Effect colours (`effects.borderColor`, `effects.gradient`, `shadow.color`) must end up as six-digit hex: use a name whose value is `#rrggbb`, or write `#rrggbb`.
- Colours inside `html` (a `span` with `style="color:#c2410c"`) must be hex. Names are not looked up inside `html`.

### Text styles

`styles.text` maps names to text styles. A `text`, `shape` or `poll` object uses one with `"style": "body"`, and can override any field on the object itself.

| Field | Type | Meaning |
|---|---|---|
| `name` | text | Shown in Pikko's editor. Defaults to the style's key. |
| `font` | `sans`, `serif`, `mono` or a font name | `sans` is Inter. A font name (for example `"Lora"`) is used only if the viewer's device has it; otherwise the browser falls back. Use `sans`, `serif` or `mono`. |
| `fontFace` | text | A specific typeface, with the same caveat. |
| `size` | px, 8–160 | Font size on the 1280-wide canvas. |
| `weight` | 300–900 | 400 regular, 700 bold, 800–900 heavy. |
| `italic` | true/false | |
| `align` | `left`, `center`, `right` | Horizontal alignment in the box. |
| `color` | colour | Text colour. |
| `lineHeight` | 0.5–4 | Line height as a multiple of the size (1.1 for titles, 1.3–1.4 for body text). |
| `letterSpacing` | px, -5–30 | Extra space between letters (2 suits small uppercase labels). |
| `fit` | `clip`, `shrink`, `visible` | What text longer than its box does: cut off (the default), shrink to fit, or run over. |
| `typography` | object | Finer control in px: `paddingTop`, `paddingRight`, `paddingBottom`, `paddingLeft`, `paragraphSpacing` (space between paragraphs and list items), `indent`, `firstLineIndent`, `lineSpacingPx` (an exact line height in px, instead of `lineHeight`), `minFontSize` (the smallest size `fit: "shrink"` may use; 8 by default). |

Two styles always exist, even if you do not define them: `title` (sans, 52 px, weight 900, white, line height 1.1) and `body` (sans, 28 px, weight 500, light grey `#e8eaf2`, line height 1.3). Their defaults suit dark slides only. Define both in `styles.text` with your own colours, and add the styles your design needs (`label`, `caption`, `stat`, …). Every other style starts from the built-in `body` style and changes what it lists. A deck can have at most 40 text styles. Using a style name that does not exist refuses the file.

A `text` object without `style` is body text: it uses `body`, like plain text in a slide app. So define `body` for your slides' main text, give headings and labels their own styles (`"style": "title"`, `"style": "label"`), and change single fields on the object itself.

## Slides

| Field | Type | Default | Meaning |
|---|---|---|---|
| `id` | text, 1–100 | generated | The slide's identity. Keep it when editing. |
| `title` | text, 1–200 | the first text on the slide | Names the slide in the slide list, on phones and in the session record. Not drawn. |
| `objects` | list of objects | none | The slide's content, back to front. At most 100. |
| `background` | colour or object | the theme's gradient | See below. Set it on every slide. |
| `notes` | text, up to 4000 | none | Speaker notes, shown to the presenter only. |
| `transition` | `none`, `fade`, `push`, `wipe`, `morph` | `fade` | How the slide replaces the one before. |
| `entrance` | `none`, `fade`, `rise`, `spotlight`, `zoom`, `wipe` | `fade` | How the slide's objects appear. |
| `branches` | list | none | Where the presenter goes next, depending on the answers. Rarely needed. See Branches. |
| `useMaster`, `masterLayout` | true/false, layout id | false | Draw the master's objects on this slide. See The master. |
| `activity` | object | none | A whole-slide activity. Keep as is; new decks use `poll` objects. See Activities. |
| `layout`, `body`, `image`, `keep` | | | Keep as is: older slides Pikko draws itself, and Pikko's stored details. Leave them out of new slides; keep them unchanged when editing. |

### Backgrounds

`background` is one of:

- a colour: `"background": "paper"` or `"background": "#f5f0e6"`;
- a gradient: `"background": { "gradient": { "from": "ink", "to": "leaf", "angle": 135 } }` with colours `from` and `to` and an `angle` in degrees (135 when absent);
- an image filling the slide: `"background": { "color": "ink", "image": { "src": "https://example.com/forest.jpg", "focus": { "x": 50, "y": 40 } } }`. The image covers the slide; `focus` is the point (in percent of the image) to keep in view. Use only one of `color`, `gradient` and `css` in the object. A background image is decoration: put an image people need to see on the slide as an `image` object with `alt`.

A slide with no `background` gets the theme's dark gradient. If your text styles are dark, a slide without a light `background` will show dark text on dark: set `background` on every slide.

## Objects

Every object takes these fields.

| Field | Type | Default | Meaning |
|---|---|---|---|
| `type` | see below | required | `text`, `shape`, `image`, `icon`, `line`, `chart`, `table`, `equation`, `media`, `poll` (and `smartart`, `drawing`: keep as is). |
| `x`, `y`, `w`, `h` | px | required | The box. See The canvas. |
| `id` | text, 1–100 | generated | The object's identity. Keep it when editing. |
| `name` | text | none | A name in the editor's layer list. Not drawn, and not the icon's picture. |
| `rotation` | degrees, -360–360 | 0 | Clockwise. |
| `opacity` | 0–1 | 1 | 0 is invisible. |
| `hidden` | true/false | false | Hidden objects are kept but not drawn. |
| `locked` | true/false | false | The editor will not move it. |
| `group` | text | none | Objects with the same group move together in the editor. |
| `animation` | effect, or object | none | How the object enters: `none`, `fade`, `rise`, `spotlight`, `zoom`, `wipe`, or an object with `effect`, `start`, `order`, `delay` and `duration`, such as `{ "effect": "rise", "start": "onClick", "order": 1, "delay": 0, "duration": 700 }`. `start` is `withPrevious` (the default: with the slide), `afterPrevious` or `onClick` (waits for the presenter); `order` sets the sequence (0 first); `delay` and `duration` are milliseconds. Previews show every object. |
| `shadow` | object or null | none | `{ "color": "#000000", "opacity": 30, "blur": 24, "x": 0, "y": 8 }`: a six-digit hex `color`, `opacity` 0–100, `blur` and the offsets `x` and `y` in px. `null` removes it. |
| `effects` | object | none | `opacity` (0–100), `borderWidth` (px), `borderColor`, `radius` (corner radius in px), `shadow` (as above) and `gradient` (`{ "from": "#123a5f", "to": "#14b8a6", "angle": 90 }`, a gradient fill). |
| `component`, `importInfo`, `keep` | | | Keep as is. |

### What a field you leave out means

A field you leave out is what Pikko's editor gives a new object of that type, so a short object looks like one a person adds in the editor. The exception is text, which is body text: a `text` object without `style` uses the deck's `body` style (the built-in one if `styles.text` has none). Only an object's look has defaults. Its box is required, and content you leave out (`text`, `html`, `question`, `options`, `src`, `latex`, a chart's `labels` and `series`, a table's `rows`) is empty.

| Object | A field you leave out is |
|---|---|
| `text` | `style` `body` |
| `shape` | `shape` `circle`, `fill` `#d5ff3f` |
| `image` | none |
| `icon` | `icon` `star`, `iconColor` `#d5ff3f`, `iconStyle` `stroke`, `iconStrokeWidth` 2 |
| `line` | `stroke` `#d5ff3f`, `strokeWidth` 4, `route` `straight`, `arrows` `end` |
| `chart` | in `chart`: `chartType` `bar`, `style` `studio`, `showLegend` `true`, `showValues` `false`, `showGrid` `true`, `showCategoryLabels` `true`, `showAxis` `true`, `legendPosition` `bottom` |
| `table` | in `table`: `headerRows` 1, `headerFill` `#d5ff3f`, `cellFill` `rgba(255,255,255,.08)`, `borderColor` `rgba(255,255,255,.28)` (with a `styleId`, the preset's colours) |
| `equation` | `color` `#ffffff` |
| `media` | `mediaType` `video` |
| `poll` | `kind` `choice`, `results` `live`, `fill` `rgba(255,255,255,.12)`, `color` `#ffffff`, `size` 26, `weight` 700, `font` `sans`, `align` `left` |
| `smartart` | none |
| `drawing` | `stroke` `#d5ff3f`, `strokeWidth` 4 |

These defaults are Pikko's colours for its dark themes: lime and white. On a light slide, or in your own colours, set `fill`, `color`, `stroke` and `iconColor` yourself.

### text

A block of text in its box.

```json
{ "type": "text", "id": "points", "x": 88, "y": 212, "w": 520, "h": 300, "style": "body",
  "html": "<ul><li>They need light to grow</li><li>They take up water through the roots</li></ul>" }
```

| Field | Type | Meaning |
|---|---|---|
| `text` | text, up to 1000 | Plain text. A line break is `\n`. |
| `html` | text | Rich text instead of `text`. Allowed tags: `b`, `strong`, `i`, `em`, `u`, `br`, `p`, `ul`, `ol`, `li`, `a` (with an `https:` or `mailto:` `href`) and `span` (with `style="color:#rrggbb"` or `background-color`). Other tags are dropped and their text kept. |
| `style` | style name | A text style from `styles.text`; `body` when absent. |
| `font`, `fontFace`, `size`, `weight`, `italic`, `align`, `color`, `lineHeight`, `letterSpacing`, `fit`, `typography` | | Overrides of the style, as in Text styles. |
| `underline` | true/false | |
| `valign` | `top`, `middle`, `bottom` | Vertical alignment in the box. `top` when absent. |
| `background` | colour | A colour behind the whole box (a simple highlight). For a card with padding, put a `shape` behind the text instead. |

Text starts at the top-left of its box (or as `align` and `valign` say), wraps at the box's width, and is cut off at the bottom of the box (`fit: "clip"`) unless you choose `"shrink"` or `"visible"`. Make the box big enough for its text at its real size (see the design guide); treat `"shrink"` as a safety net, not a layout tool. Write either `text` or `html`, not both.

### shape

A filled shape, for cards, panels, rules and decoration.

```json
{ "type": "shape", "id": "card", "x": 88, "y": 232, "w": 336, "h": 360, "shape": "rectangle", "fill": "sand", "radius": 24 }
```

| Field | Type | Meaning |
|---|---|---|
| `shape` | `rectangle`, `circle`, `pill`, `line`, `arrow`, `triangle`, `diamond`, `pentagon`, `hexagon`, `star`, `burst`, `cloud`, `chevron`, `parallelogram` | The outline: a `circle` when absent, like a new shape in the editor. Write it. |
| `fill` | colour | Lime (`#d5ff3f`) when absent. Set it. |
| `stroke`, `strokeWidth` | colour, px | An outline around the shape, drawn inside its box (1 px when only `stroke` is set). |
| `radius` | px | Corner radius of a rectangle (12 when absent, `0` for square corners). A `pill` is fully round unless it has a `radius`. |
| `text` or `html` | text | Words inside the shape, centred across and down unless `align` and `valign` say otherwise. |
| `style`, `size`, `weight`, `color`, `align`, … | | The text's look, as on a `text` object: 24 px, bold and white when absent. A shape uses a text style only when you name one, and a style brings its own `align` (`left` unless the style says otherwise), so write `"align": "center"` next to it to keep the words centred. |

Every outline is drawn as named, in the editor, on the presenter's screen, in previews, in print and in the PowerPoint export. A `circle` in a box that is not square is an ellipse; `arrow` is a filled block arrow pointing right (rotate it for other directions); `line` is a horizontal rule through the middle of its box in its `stroke` (or `fill`), `strokeWidth` thick (4 when absent). `opacity`, `shadow` and `effects` paint a shape and never change its outline: the shadow follows it, `effects.borderWidth` outlines it when it has no `stroke`, and `effects.radius` (above 0) rounds a rectangle's corners but no other outline. For a thin divider, use a `rectangle` a few px high (`"h": 8`); for a line or arrow between two points, use a `line` object.

A shape's words stay inside its outline, in the room this table gives (as shares of the shape's box), and wrap there; a word longer than the room is split mid-word:

| Outline | Room for its words |
|---|---|
| `rectangle` | all of the width, all of the height |
| `circle` | the middle 71% of the width, the middle 71% of the height |
| `pill` | all of the width, all of the height |
| `line` | no words |
| `arrow` | the left 70% of the width, the middle 50% of the height |
| `triangle` | the middle 50% of the width, the bottom 50% of the height |
| `diamond` | the middle 50% of the width, the middle 50% of the height |
| `pentagon` | the middle 64% of the width, the bottom 70% of the height |
| `hexagon` | the middle 70% of the width, all of the height |
| `star` | the middle 40% of the width, 45% of the height, starting 35% from the top |
| `burst` | the middle 50% of the width, the middle 50% of the height |
| `cloud` | the middle 64% of the width, the middle 60% of the height |
| `chevron` | the middle 40% of the width, all of the height |
| `parallelogram` | the middle 64% of the width, all of the height |

Inside that room the words keep 16 px from the sides and 12 px from the top and bottom (more beside rounded corners, pill ends and a `stroke`).

So a 276 px wide `chevron` has about 78 px for its words (40% of 276, less 2 × 16): one short word at 24 px. Give a process step a chevron at least 340 px wide for one or two words, or put the step's words in a `text` object beside or under the outline.

### image

```json
{ "type": "image", "id": "leaf-photo", "x": 88, "y": 72, "w": 520, "h": 576,
  "src": "https://upload.wikimedia.org/wikipedia/commons/f/f4/Leaf_1_web.jpg", "alt": "A green leaf against the light, with its veins showing" }
```

| Field | Type | Meaning |
|---|---|---|
| `src` | text | `media/<file>` inside a `.pikko` file, or an `https://` link to a PNG, JPEG, WebP, GIF, SVG or AVIF image (or that image as a `data:` URL). |
| `alt` | text | What the image shows, for people who cannot see it. Always write it. |
| `crop` | object | `{ "left": 10, "top": 0, "right": 10, "bottom": 0 }`: percent of the image removed from each edge. |

An image covers its whole box and is cropped to it, centred (like a photo in a frame), with slightly rounded corners. Choose a box with roughly the image's proportions if nothing may be cut off. Only link images you know exist and may be used; a broken link draws an empty box. Pikko does not generate images.

### icon

A simple line icon from Pikko's set.

```json
{ "type": "icon", "id": "sun", "x": 980, "y": 260, "w": 120, "h": 120, "icon": "sun", "iconColor": "ink", "iconStyle": "stroke", "iconStrokeWidth": 2 }
```

| Field | Type | Meaning |
|---|---|---|
| `icon` | icon name | The picture: one of the names below. A star when absent; an unknown name also draws a star. |
| `iconColor` | colour | A palette name or a hex value (CSS colour words other than `black` and `white` are not used for icons). Lime when absent. |
| `iconStyle` | `stroke`, `filled` | Outline (the default) or filled. |
| `iconStrokeWidth` | 0.75–6 | Line thickness of outline icons (2 when absent). |

The icon is drawn as large as fits its box, centred, so give it a square box. Icon names:

`check`, `x`, `plus`, `arrow-right`, `arrow-up`, `lightbulb`, `target`, `people`, `person`, `chat`, `question`, `warning`, `calendar`, `clock`, `chart`, `document`, `lock`, `globe`, `heart`, `star`, `rocket`, `search`, `mail`, `settings`, `play`, `pause`, `check-circle`, `plus-circle`, `minus`, `arrow-left`, `arrow-down`, `chevron-right`, `chevron-down`, `external`, `upload`, `download`, `link`, `phone`, `laptop`, `building`, `briefcase`, `wallet`, `location`, `pin`, `megaphone`, `sparkles`, `flame`, `flag`, `trophy`, `thumbs-up`, `thumbs-down`, `eye`, `bookmark`, `filter`, `home`, `pie-chart`, `trend-up`, `hand`, `microphone`, `video`, `image`, `code`, `database`, `shield`, `bell`, `sun`, `moon`.

### line

A straight or elbowed line between two points or two objects, optionally with arrowheads.

```json
{ "type": "line", "id": "step-arrow", "x": 424, "y": 400, "w": 48, "h": 16, "from": "step-1-card", "to": "step-2-card", "arrows": "end", "stroke": "leaf", "strokeWidth": 4 }
```

| Field | Type | Meaning |
|---|---|---|
| `from`, `to` | object id, or point | Each end is the `id` of an object on the same slide (the line attaches to its edge), or a point `{ "x": 424, "y": 408 }` in px, optionally with `target` (an object id to attach to) and `side` (`auto`, `top`, `right`, `bottom`, `left`). An end left out is the middle of the box's left or right edge. |
| `route` | `straight`, `elbow` | `straight` when absent. |
| `arrows` | `none`, `end`, `both` | `end` when absent (an arrowhead at `to`, as the editor's line tool draws). Write `"arrows": "none"` for a plain line. |
| `stroke`, `strokeWidth` | colour, px | Line colour (lime when absent) and thickness (4 when absent). |

The box (`x`, `y`, `w`, `h`) is required, at least 12.8 px wide and 7.2 px high like every box (use `"h": 8` or more for a horizontal line), but the ends decide where the line is drawn.

### chart

```json
{ "type": "chart", "id": "growth", "x": 88, "y": 208, "w": 680, "h": 440,
  "chart": { "chartType": "line", "labels": ["2 h", "4 h", "8 h", "12 h"], "series": [{ "name": "Growth (cm)", "values": [1.2, 2.5, 4.1, 5], "color": "lime" }], "showLegend": false, "style": "minimal" } }
```

`chart` holds:

| Field | Type | Meaning |
|---|---|---|
| `chartType` | `bar`, `line`, `pie`, `area`, `doughnut`, `radar` | `bar` when absent. |
| `title` | text | A title drawn in the chart. |
| `labels` | list of text, up to 30 | The categories (x axis, or pie slices). |
| `series` | list, up to 8 | Each has a `name`, `values` (numbers, one per label) and a `color`. Pie and doughnut charts use the first series. |
| `showLegend`, `showValues`, `stacked`, `showGrid`, `showCategoryLabels`, `showAxis` | true/false | Display options. A new chart shows its legend, grid, category labels and axis, and no values or stacking. |
| `legendPosition` | `top`, `bottom`, `right`, `none` | `bottom` when absent. |
| `style` | `studio`, `editorial`, `minimal` | The chart's panel; `studio` when absent. |
| `emphasisIndex` | 0–29 | Highlights one category (0 is the first). |

A chart draws on its own dark panel with light lines and labels, whatever the slide background, so it reads as a dark card on a light slide. Keep it simple: a few categories with short labels, a large box (at least about 600×400), and the one number that matters in a big `text` object next to it.

### table

```json
{ "type": "table", "id": "prices", "x": 88, "y": 208, "w": 720, "h": 240,
  "table": { "headerRows": 1, "headerFill": "ink", "cellFill": "#ffffff", "borderColor": "#d6d3cc",
    "rows": [[{ "text": "Plan", "color": "paper" }, { "text": "Price", "color": "paper" }], [{ "text": "Basic", "color": "ink" }, { "text": "Free", "color": "ink" }]] } }
```

`table` holds `rows` (1–20 rows of 1–20 cells) and optionally `headerRows` (0–5), `headerFill`, `cellFill`, `borderColor`, `columnWidths` and `rowHeights` (relative numbers), `cellPadding` (px) and `styleId` (`studio`, `paper`, `ocean`, `minimal`: a colour preset for the header, the cells, the borders and the text, as choosing a table style in the editor). Each cell is `{ "text": "..." }` with optional `fill`, `color`, `bold`, `align` (`left`, `center`, `right`), `rowSpan`, `colSpan`, `padding` and `borders` (`top`, `right`, `bottom`, `left`, each with a `color`, a `width` in px and a `style`: `solid`, `dashed` or `none`).

Table text is drawn at the table object's `size` (16 px when absent), in each cell's `color`, else the table object's `color`, else the preset's text colour, else white; header rows are bold. With a `styleId`, the header, cells and borders take the preset's colours: `paper` (light grey header, white cells, dark text) and `minimal` suit a light slide, `studio` (violet header, dark cells, white text) a dark one, and `ocean` is light blue with a dark blue header. `headerFill`, `cellFill` and `borderColor` override the preset, and a cell's `fill` and `color` override both. Without a `styleId` or colours a table is a new table from the editor: one header row filled lime (`#d5ff3f`), cells in translucent white (`rgba(255,255,255,.08)`) and white text, which suits only a dark slide. At 16 px a table is hard to read from the back of a room: set `"size": 24` or more, keep it to a few short rows, or show the numbers as text instead.

### equation

```json
{ "type": "equation", "id": "formula", "x": 88, "y": 236, "w": 1104, "h": 160, "latex": "6\\,CO_2 + 6\\,H_2O \\rightarrow C_6H_{12}O_6 + 6\\,O_2", "color": "paper" }
```

`latex` is the equation in LaTeX (up to 2000 characters; remember that a backslash is `\\` inside JSON). `color` is its colour (white when absent). The equation is scaled to fit its box, so a wide, low box gives a large single-line equation.

### media

```json
{ "type": "media", "id": "clip", "x": 88, "y": 208, "w": 720, "h": 405, "mediaType": "video", "src": "https://example.com/clip.mp4", "poster": "https://example.com/clip.jpg" }
```

`mediaType` is `audio` or `video` (`video` when absent); `src` is `media/<file>` or an `https://` link to an MP4, WebM, MP3, M4A, WAV or OGG file; `poster` is an image shown before a video plays. The presenter plays it with the player's controls.

### poll

A live question on the slide. See Polls.

### smartart and drawing (keep as is)

Diagrams imported from PowerPoint (`smartart`) and freehand drawings (`drawing`) come out of Pikko as they are stored. Do not write new ones; keep them unchanged when editing a file Pikko gave you.

## Polls

A poll is an object on a slide. You design its box and look; Pikko draws the question, the options and the live results inside it as answers arrive, like a chart that fills itself. People answer on their phones, where Pikko shows the same question and options; the phone screen is not designed in the file.

```json
{ "type": "poll", "id": "guess-poll", "x": 688, "y": 176, "w": 504, "h": 452, "kind": "choice",
  "question": "What do plants need most?",
  "options": [{ "id": "light", "text": "Light" }, { "id": "water", "text": "Water" }, { "id": "soil", "text": "Soil" }],
  "results": "live", "fill": "ink", "color": "paper", "size": 26, "weight": 700 }
```

| Field | Type | Default | Meaning |
|---|---|---|---|
| `kind` | `choice`, `ranked`, `wordcloud`, `scale` | `choice` | `choice`: pick one option, bars show the votes. `ranked`: put the options in order. `wordcloud`: everyone types a word or two; the most common words grow. `scale`: rate from 1 to 5. |
| `question` | text, up to 1000 | none | The question, shown on the slide and on phones. Keep it short and concrete. |
| `options` | list of `{ "id", "text" }`, up to 8 | none | For `choice` and `ranked`: 2–8 options (each text 1–200 characters, short is better). For `scale`, labels for the steps: two label 1 and 5 (for example "Not at all" and "Completely"), three label 1, 3 and 5, five label every step. Not used for `wordcloud`. An option may also be written as plain text, and Pikko gives it an id. |
| `results` | `live`, `hidden` | `live` | `live`: results grow on the slide as people answer. `hidden`: the slide says results are hidden until the presenter reveals them (good for quizzes and for votes that should not sway each other). |
| `fill` | colour | `rgba(255,255,255,.12)` (translucent white) | The poll's card. Give it a solid colour that contrasts with `color` (see below). |
| `color` | colour | white | The colour of the question, the options and the results. Pikko draws the bar tracks and the outline in it too, and picks the accent for bars and option letters (lime, or violet where lime would not read on the fill). |
| `size`, `weight`, `font`, `align`, `style` | | 26 px, weight 700, `sans`, `left` | The question's type. 24–32 px reads well. A poll uses a text style only when you name one. |
| `effects` | object | none | `gradient` fills the card with a gradient, and `radius` sets its corner radius in px (18 when the poll has no `effects`, `shadow` or `opacity`; otherwise `effects.radius`, 0 when absent). |

How Pikko draws a poll, so you can plan its box:

- The card has 22 px of padding. At the top it shows a small label (the kind of poll, in the accent colour) and a live counter, then the question in the poll's `size`, then the options or results. Option texts are 18 px (15 px when there are more than 4), whatever the `size`.
- The question, options and results (option text, percentages, scale numbers, words) are drawn in the poll's `color`, so `color` and `fill` must contrast: a light `color` on a dark fill (as dark as `#4a6417`, `#503950` or `#123a5f`, or darker), or a dark `color` on a light fill. A poll with neither is white text on translucent white, which reads only on a dark slide. On a dark slide, pick a fill a little lighter or darker than the background so the card stands out.
- `choice` and `ranked`: one row per option (a letter or number, the option text, a bar and a percentage; for `ranked`, the option's share of all ranking points, first place earning the most), about 40 px per option, 30 px when there are more than 4. The option text gets only about 38% of the card's inner width and is cut off with "…" beyond that: in a 504 px wide poll about 17 characters fit. Keep options to one to three words, or make the poll wider.
- `wordcloud`: the 10 most common words, 16–38 px, wrapped in the card. Give it room to fill up: a wide box, at least 600×300 px; a full-width poll (`"x": 88, "w": 1104`) under a heading works best.
- `scale`: five round buttons (1–5), 46 px each, with the labels under them (14 px at the ends; smaller when every step has one) and the average once people answer. It needs at least 360 px of width but little height: about 432×300 px is enough, and a taller box only adds empty space. Keep labels to a word or two.
- A choice poll with 4 options and a two-line question needs at least 320 px of height; 420–460 px leaves room to breathe.
- While a poll on the slide is open, the projector shows a join badge (QR code and join code) in the slide's **top-right corner**, over whatever is there. See the design guide.

Put one poll on a slide, next to the content it is about. A quiz question works as a `choice` poll with `"results": "hidden"`; Pikko does not mark a correct answer on in-slide polls, so say the answer in the notes or on the next slide.

## Branches

A slide can send the presenter to a different next slide depending on the answers. Most decks do not need this.

```json
{ "id": "check", "title": "Check", "objects": [
  { "type": "poll", "id": "check-poll", "x": 688, "y": 176, "w": 504, "h": 452, "question": "Where does the sugar's carbon come from?",
    "options": [{ "id": "air", "text": "The air" }, { "id": "soil", "text": "The soil" }], "fill": "#18302e" }
], "branches": [
  { "label": "Most got it", "target": "next-topic", "condition": { "kind": "block-option-share", "blockId": "check-poll", "option": "air", "operator": "gte", "value": 70 } }
] }
```

Each branch has a `target` (a slide id in this deck, not the slide itself), an optional `label` and `id`, and a `condition`:

- `{ "kind": "block-option-share", "blockId": <poll id>, "option": <option id>, "operator": "gte" | "lt", "value": <percent> }`: the share of answers that chose one option of a poll on this slide. The option is named by its `id`, so the rule still counts it when you reorder the options. A file from Pikko may hold `optionIndex` (a position) instead, where the rule matches no option of the poll; keep it as it is.
- `{ "kind": "block-scale-average", "blockId": <poll id>, "operator": "gte" | "lt", "value": <1–5> }`
- `{ "kind": "response-count", "operator": "gte" | "lt", "value": <number> }`
- `{ "kind": "no-response" }`
- for a whole-slide activity, whose options have no ids: `{ "kind": "option-share", "optionIndex": <0-based>, "operator": "gte" | "lt", "value": <percent> }`.

The first matching branch wins.

## The master

`master` holds things drawn on every slide that sets `"useMaster": true`:

- `objects`: `text`, `shape` and `image` objects (a logo, a frame, a footer), drawn behind the slide's own objects;
- `background`: the background of those slides (a colour or gradient, as for slides). A slide that uses the master takes its background from here, not from its own `background`.

`titleStyle`, `bodyStyle`, `layouts`, `components`, `guides`, `brandKit` and `keep` inside `master` are Pikko's own (keep as is). Most decks need no master: repeating a small object on each slide works just as well.

## Activities (keep as is)

Older and special slides are whole-slide activities: `"activity": { "type": "choice", "options": [...], "correct": 1, "timer": 30 }` (a `type`, its `options`, the index of the `correct` option and a `timer` in seconds), and the types `slider`, `allocate`, `board`, `calculation` and `simulation` with their settings in `config`. The other types are `choice`, `ranked`, `wordcloud` and `scale` as full-slide questions. They exist so every Pikko deck can be written out. New decks use `poll` objects on canvas slides instead. When you edit a file that has activities, keep each `activity` (and its slide's `layout`, `body` and `keep`) unchanged; you may edit its options' wording.

## keep sections

Some objects and slides carry a `keep` section, and some fields are described as "keep as is" (`layout`, `body`, `image`, `design`, `sections`, `customShows`, `component`, `importInfo`, the master's `layouts`). These are details only Pikko needs, so that a deck that goes out to an assistant and comes back is unchanged.

- Do not write them in a new deck.
- When you edit a file Pikko gave you, keep them exactly as they are on the slides and objects you keep. Remove them along with the slide or object they belong to.

## Results

A file Pikko gives an assistant may include a read-only `results` section, but only when the person asked for it: a list of `runs`, and for each run (session) of the deck, the number of responses per slide and poll, votes per option (first preferences for ranked polls), how many chose each point of a scale and its average, and word cloud words with how often each was given. It never holds names, per-person answers or audience questions.

```json
{ "runs": [{ "id": "run-1", "startedAt": "2026-10-14T08:15:00Z", "slides": [
  { "slide": "guess", "polls": [{ "object": "guess-poll", "kind": "choice", "responses": 24, "options": [{ "id": "light", "text": "Light", "count": 15 }, { "id": "water", "text": "Water", "count": 9 }] }] }
] }] }
```

Use it to write a summary slide ("15 of 24 said light"). Pikko ignores `results` when a file comes back, so you never need to remove or update it.

## Design guide

Pikko draws exactly what you write, so the design is yours. These rules prevent the mistakes that show on a projector.

### Keep inside the safe area

Keep all text, polls, charts, tables, icons and images inside **x 64–1216, y 48–672**. A margin of 88 px on the left and right (content from x 88 to x 1192) and 72 px at the top looks calm. Only decorative shapes may run to or past the edges.

### Use sizes a room can read

- Body text **24 px or more**; 28 px is a good default. Titles 44–64 px. A single big number or word 72–120 px.
- Labels, captions and credits may be smaller, but **never below 16 px** (18–20 px is better).
- One font (`sans`) in two or three weights is enough.
- Choose a palette for the topic and the audience: a dark colour for text, a light one for backgrounds, one or two accents and a card colour. The colours on this page are examples, not Pikko's.

### Fit the text to its box

Text never resizes its box, so do the arithmetic before you write the box:

- A line of text is `size × lineHeight` px high: 28 px at line height 1.35 is about 38 px; 52 px at 1.1 is about 57 px.
- One character is about `0.55 × size` px wide in `sans`, bold or not (capitals and digits about 0.65; wide letters such as W and M more). So a 520 px wide box holds about 33 characters per line at 28 px (520 ÷ 15.4), and about 18 at 52 px.
- Leave slack: plan each line to fill at most about 85% of its box by this estimate, because a line that fills more may wrap one line earlier than you counted. This matters most for large headings: "Welcome aboard" at 88 px estimates at 92% of a 720 px box and wraps.
- The box's height must be at least lines × line height, plus about 10%. Three short bullets at 28 px need a box about 140 px high; two lines of a 52 px title need 128 px.
- Text that wraps further than its box allows is cut off at the bottom: in a render the cut line shows as a few stray marks (the tops of tall letters) under the last full line. Widen the box, make it taller, or shorten the text. If a title does not fit on two lines, shorten the title.

### Leave room and avoid overlaps

- Before you write a slide, list each object's box as x to x+w and y to y+h. Boxes that hold text must not overlap, unless you mean text to sit on a card (list the card first, and keep the text 24–32 px inside the card's edges).
- Keep at least 24 px between unrelated boxes, and align edges: give objects in a column the same `x` and objects in a row the same `y`.
- Put the same kind of thing in the same place on every slide (the title at the same `x` and `y`), so the deck feels designed.
- Size a card to what it holds: a heading and three short bullets need about 260 px of height, not the 360 px of a three-card row with longer text. A card that is half empty looks unfinished.

### Contrast

- Dark text on a light background or light text on a dark background, with a contrast ratio of at least 4.5:1 for body text (3:1 for text 32 px and larger). Avoid mid-tone backgrounds such as saturated orange or medium grey under text.
- Set `background` on every slide, and choose text colours for that background. Text on a card is judged against the card.
- **Give every poll a `fill` and a `color` that contrast**, whatever the slide's background: Pikko draws a poll's question, options and results in its `color` (white when absent) on its `fill` (translucent white when absent).

### Leave the top-right corner free on a slide with a poll

While a poll is open, the projector shows a white join badge with a QR code and the join code in the top-right corner of the slide: the area **right of x = 880 and above y = 160** (it is 128 px high, 24 px from the top and right edges, and up to about 370 px wide). It covers anything there. On any slide with a poll, end headings before x = 880 (at `"x": 88`, a width of 760 or less), and before the poll where the poll sits beside them (520 wide when the poll starts at x 688), and start the poll at `"y": 176` or lower if it reaches the right side. A decorative shape may run under the badge. Previews and renders with sample answers show the results, and the badge disappears once results are shown, so you will not see it there: keep the corner free anyway.

### Give a poll room to fill up

A poll looks different with 30 answers than with 3. Size it for a full room: a choice poll about 504×452 px next to text, a word cloud across the slide (1104×380 px) under a short heading, a scale about 432×300 px. Keep option texts short (see Polls).

### One idea per slide

- One message per slide, said in the heading. Three bullets at most, a few words each; move the rest to `notes`.
- Prefer a big number, a short statement or a picture over a paragraph.
- Ask a question where it makes the audience think, roughly every three to five slides, and put the poll next to what it is about.

### Layouts that work

Coordinates for common slides (heading styles as in the Quick start: a `label` at 18 px, a `title` at 52 px, `body` at 28 px).

| Slide | Objects |
|---|---|
| Title | label `88, 200, 640×32`; title `88, 248, 720×192` at 80 px; subtitle `88, 464, 680×80`; decoration on the right half. |
| Heading and text | label `88, 72, 400×28`; title `88, 108, 1104×72`; body `88, 208, 1104×400` (or two columns `88, 208, 520×400` and `672, 208, 520×400`). |
| Text and a poll | label `88, 72, 480×28`; title `88, 108, 520×128`; body `88, 268, 520×200`; poll `688, 176, 504×452`. |
| Word cloud | label `88, 72, 400×28`; title `88, 108, 760×72`; hint `88, 188, 760×36`; poll `88, 256, 1104×384`. |
| Three cards | title `88, 108, 1104×72`; cards at x `88`, `472` and `856`, `y 232, 336×360`; text inside each card from x + 32, 272 px wide. |
| Chart and numbers | title `88, 108, 1104×72`; chart `88, 208, 720×440`; big numbers at `856, 240, 336×96` and `856, 448, 336×96`, each with a caption under it. |
| Picture and text | image `88, 72, 520×576`; label `672, 120, 520×28`; title `672, 156, 520×128`; body `672, 316, 520×240`. |

### Render and look before you hand it over

You cannot see a slide by reading its JSON. Look at it:

- With the connector, call `render` after `create_deck` or `update_deck`, look at every slide, fix what is wrong with `update_deck`, and render again. Renders fill polls with sample answers, so you see a full word cloud and long option texts.
- Without the connector, ask the person to open the file in Pikko and look at the preview (it also shows sample answers), and to tell you what to change.
- Check every slide for: text cut off at the bottom of its box, overlapping boxes, text too small or too faint, anything in the top-right corner of a poll slide, options cut off with "…", and anything outside the safe area.

## When Pikko refuses a file

Pikko refuses a file it cannot read and says why, each problem with its path, and creates nothing. It refuses a file when:

- it is not JSON, not a JSON object, or has no `"format": "pikko/1"` (or another version);
- a required field is missing: the deck's `slides`, an object's `type`, `x`, `y`, `w` or `h`, a branch's `target`;
- a value has the wrong type or is outside its range (a box past 1280×720, a font size over 160, a text over its length, more than 8 poll options, more than 100 objects on a slide);
- a value is not one of the allowed words (a `shape`, `kind`, `theme` or `chartType` not listed on this page);
- `style` names a text style that is not in `styles.text`;
- a `src` or `poster` is not an `https://` link, a `media/` file or an image, audio or video `data:` URL (`javascript:` and `file:` links, for example, refuse the file);
- a `src` is `media/…` but the file is not in the `.pikko` zip (or the deck came as bare JSON), or the images and media add up to more than a deck can hold;
- a branch points to a slide id that does not exist, or names a poll or option that is not on its slide;
- a colour name in `styles.colors` is a CSS word, or an effect colour is not six-digit hex;
- a slide `background` object has more than one of `color`, `gradient` and `css`.

The messages look like this:

```text
format: Add "format": "pikko/1" at the top of the file.
format: Pikko reads pikko/1 files; this one says “pikko/2”.
slides[0].objects[1].h: Required.
slides[0].objects[0].x: Must be at most 1280.
slides[0].objects[2].shape: Must be one of: rectangle, circle, pill, line, arrow, triangle, diamond, pentagon, hexagon, star, burst, cloud, chevron, parallelogram.
slides[0].objects[1].style: There is no text style “subtitle” in styles.text.
slides[1].objects[1].options: Can have at most 8 items.
slides[0].objects[2].src: media/leaf.png is not in this file. Open the .pikko file with its media folder, or use an https link.
slides[1].branches[0].target: There is no slide “summary”.
```

Some things are not fatal. Pikko reads the file and lists them as warnings:

- a field this page does not describe is ignored (so `"fontSize": 28` instead of `"size": 28` silently does nothing visible: use the names on this page);
- a repeated id is renamed;
- a colour word that is neither in `styles.colors` nor a CSS colour is kept as written.

```text
slides[0].objects[1].fontSize: Not part of pikko/1, so it was ignored.
slides[1].objects[1].id: The id “vote-poll” is used twice; this one is now “vote-poll-2”.
slides[0].background: “sand” is not in styles.colors, so it was kept as written.
```

## Checklist

Before you hand a deck over, check that:

1. The file starts with `"format": "pikko/1"`, and `language` is the language of the slides.
2. `styles.colors` names your palette, and `styles.text` defines `title`, `body` and every other style you use, with colours that suit your backgrounds.
3. Every slide has an `id`, a `title`, a `background` and its own heading as a `text` object.
4. Every object has an `id`, a `type` and a box inside the safe area (x 64–1216, y 48–672), and text boxes are big enough for their text.
5. Body text is 24 px or more and nothing is under 16 px.
6. Every poll has a `fill` and a `color` that contrast, short options with ids, and room to fill up, and nothing on its slide sits right of x = 880 and above y = 160.
7. When you edited a file from Pikko, every id and every `keep` section you were given is still there.
8. You have rendered the slides (or asked the person to open the preview) and looked at every one.

## Examples

Three complete decks written the way this page describes. Each is also available as a `.pikko` file and as `deck.json`, at the links under its heading.

### Lesson (Norwegian): Fotosyntese

A Norwegian science lesson with nine slides: a warm-up poll, cards, an equation, a photo, a word cloud, a quiz question with hidden results, a chart and a confidence scale. Files: https://pikko.no/format/examples/lesson-nb.pikko and https://pikko.no/format/examples/lesson-nb.json.

```json
{
  "format": "pikko/1",
  "title": "Fotosyntese",
  "language": "nb",
  "theme": "chalkboard",
  "styles": {
    "colors": { "ink": "#18302e", "paper": "#f5f0e6", "sand": "#e6e1cf", "leaf": "#4a6417", "lime": "#d9e98b", "muted": "#49645e" },
    "text": {
      "title": { "font": "sans", "size": 52, "weight": 800, "color": "ink", "lineHeight": 1.1 },
      "body": { "font": "sans", "size": 28, "weight": 400, "color": "ink", "lineHeight": 1.35 },
      "label": { "font": "sans", "size": 18, "weight": 700, "color": "leaf", "letterSpacing": 2 },
      "caption": { "font": "sans", "size": 20, "weight": 400, "color": "muted", "lineHeight": 1.3 }
    }
  },
  "slides": [
    {
      "id": "cover",
      "title": "Fotosyntese",
      "background": "ink",
      "notes": "Spør klassen: hvor kommer maten til en plante fra?",
      "objects": [
        { "type": "shape", "id": "cover-sun-disc", "x": 860, "y": 140, "w": 360, "h": 360, "shape": "circle", "fill": "lime" },
        { "type": "icon", "id": "cover-sun", "x": 980, "y": 260, "w": 120, "h": 120, "icon": "sun", "iconColor": "ink",
          "iconStyle": "stroke", "iconStrokeWidth": 2 },
        { "type": "text", "id": "cover-label", "x": 88, "y": 176, "w": 640, "h": 32, "style": "label", "color": "lime",
          "text": "NATURFAG · 8. TRINN" },
        { "type": "text", "id": "cover-title", "x": 88, "y": 224, "w": 720, "h": 120, "style": "title", "size": 96, "color": "paper",
          "text": "Fotosyntese" },
        { "type": "text", "id": "cover-subtitle", "x": 88, "y": 360, "w": 680, "h": 96, "style": "body", "size": 32, "color": "sand",
          "text": "Hvordan planter lager mat av lys, vann og luft" },
        { "type": "shape", "id": "cover-rule", "x": 88, "y": 600, "w": 120, "h": 8, "shape": "rectangle", "fill": "lime" },
        { "type": "text", "id": "cover-footer", "x": 88, "y": 624, "w": 600, "h": 32, "style": "caption", "color": "sand",
          "text": "Naturfag · uke 41" }
      ]
    },
    {
      "id": "guess",
      "title": "Det vi vet fra før",
      "background": "paper",
      "notes": "La elevene stemme før dere går gjennom svaret.",
      "objects": [
        { "type": "text", "id": "guess-label", "x": 88, "y": 72, "w": 400, "h": 28, "style": "label", "text": "OPPSTART" },
        { "type": "text", "id": "guess-title", "x": 88, "y": 108, "w": 560, "h": 72, "style": "title", "text": "Det vi vet fra før" },
        { "type": "text", "id": "guess-points", "x": 88, "y": 212, "w": 520, "h": 300, "style": "body", "fit": "shrink",
          "html": "<ul><li>De trenger lys for å vokse</li><li>De tar opp vann gjennom røttene</li><li>Bladene er ofte grønne</li></ul>" },
        { "type": "text", "id": "guess-hint", "x": 88, "y": 560, "w": 520, "h": 64, "style": "caption",
          "text": "Stem før vi går gjennom svaret. Det er lov å gjette." },
        { "type": "poll", "id": "guess-poll", "x": 688, "y": 176, "w": 504, "h": 452, "kind": "choice",
          "question": "Hva tror du planter trenger mest?",
          "options": [
            { "id": "light", "text": "Lys" },
            { "id": "water", "text": "Vann" },
            { "id": "soil", "text": "Jord" },
            { "id": "air", "text": "Luft" }
          ],
          "results": "live", "fill": "ink", "color": "paper", "size": 26, "weight": 700 }
      ]
    },
    {
      "id": "ingredients",
      "title": "Tre ting planten trenger",
      "background": "paper",
      "objects": [
        { "type": "text", "id": "ingredients-label", "x": 88, "y": 72, "w": 400, "h": 28, "style": "label", "text": "INGREDIENSENE" },
        { "type": "text", "id": "ingredients-title", "x": 88, "y": 108, "w": 1104, "h": 72, "style": "title",
          "text": "Tre ting planten trenger" },
        { "type": "shape", "id": "ingredients-light-card", "x": 88, "y": 232, "w": 336, "h": 360, "shape": "rectangle", "fill": "sand",
          "radius": 24 },
        { "type": "shape", "id": "ingredients-light-dot", "x": 120, "y": 264, "w": 72, "h": 72, "shape": "circle", "fill": "lime" },
        { "type": "text", "id": "ingredients-light-number", "x": 120, "y": 264, "w": 72, "h": 72, "style": "title", "size": 32,
          "align": "center", "valign": "middle", "text": "1" },
        { "type": "text", "id": "ingredients-light-name", "x": 120, "y": 368, "w": 272, "h": 44, "style": "title", "size": 32,
          "text": "Lys" },
        { "type": "text", "id": "ingredients-light-text", "x": 120, "y": 424, "w": 272, "h": 136, "style": "body", "size": 24,
          "text": "Energien kommer fra sola." },
        { "type": "shape", "id": "ingredients-water-card", "x": 472, "y": 232, "w": 336, "h": 360, "shape": "rectangle", "fill": "sand",
          "radius": 24 },
        { "type": "shape", "id": "ingredients-water-dot", "x": 504, "y": 264, "w": 72, "h": 72, "shape": "circle", "fill": "lime" },
        { "type": "text", "id": "ingredients-water-number", "x": 504, "y": 264, "w": 72, "h": 72, "style": "title", "size": 32,
          "align": "center", "valign": "middle", "text": "2" },
        { "type": "text", "id": "ingredients-water-name", "x": 504, "y": 368, "w": 272, "h": 44, "style": "title", "size": 32,
          "text": "Vann" },
        { "type": "text", "id": "ingredients-water-text", "x": 504, "y": 424, "w": 272, "h": 136, "style": "body", "size": 24,
          "text": "Røttene henter vann fra jorda." },
        { "type": "shape", "id": "ingredients-co2-card", "x": 856, "y": 232, "w": 336, "h": 360, "shape": "rectangle", "fill": "sand",
          "radius": 24 },
        { "type": "shape", "id": "ingredients-co2-dot", "x": 888, "y": 264, "w": 72, "h": 72, "shape": "circle", "fill": "lime" },
        { "type": "text", "id": "ingredients-co2-number", "x": 888, "y": 264, "w": 72, "h": 72, "style": "title", "size": 32,
          "align": "center", "valign": "middle", "text": "3" },
        { "type": "text", "id": "ingredients-co2-name", "x": 888, "y": 368, "w": 272, "h": 44, "style": "title", "size": 32,
          "text": "Karbondioksid" },
        { "type": "text", "id": "ingredients-co2-text", "x": 888, "y": 424, "w": 272, "h": 136, "style": "body", "size": 24,
          "text": "Bladene tar opp CO₂ fra lufta." }
      ]
    },
    {
      "id": "formula",
      "title": "Formelen",
      "background": "ink",
      "notes": "Pek på at karbonet i sukkeret kommer fra lufta.",
      "objects": [
        { "type": "text", "id": "formula-label", "x": 88, "y": 72, "w": 400, "h": 28, "style": "label", "color": "lime",
          "text": "FORMELEN" },
        { "type": "text", "id": "formula-title", "x": 88, "y": 108, "w": 1104, "h": 72, "style": "title", "color": "paper",
          "text": "Slik ser det ut i kjemi" },
        { "type": "equation", "id": "formula-equation", "x": 88, "y": 236, "w": 1104, "h": 160,
          "latex": "6\\,CO_2 + 6\\,H_2O \\xrightarrow{\\text{lys}} C_6H_{12}O_6 + 6\\,O_2", "color": "paper" },
        { "type": "shape", "id": "formula-in-rule", "x": 88, "y": 448, "w": 520, "h": 8, "shape": "rectangle", "fill": "lime" },
        { "type": "text", "id": "formula-in", "x": 88, "y": 476, "w": 520, "h": 120, "style": "body", "size": 26, "color": "sand",
          "text": "Inn: karbondioksid fra lufta og vann fra jorda" },
        { "type": "shape", "id": "formula-out-rule", "x": 672, "y": 448, "w": 520, "h": 8, "shape": "rectangle", "fill": "lime" },
        { "type": "text", "id": "formula-out", "x": 672, "y": 476, "w": 520, "h": 120, "style": "body", "size": 26, "color": "sand",
          "text": "Ut: sukker (glukose) som planten bruker, og oksygen" }
      ]
    },
    {
      "id": "where",
      "title": "Hvor skjer det?",
      "background": "paper",
      "objects": [
        { "type": "image", "id": "where-photo", "x": 88, "y": 72, "w": 520, "h": 576,
          "src": "https://upload.wikimedia.org/wikipedia/commons/f/f4/Leaf_1_web.jpg",
          "alt": "Et grønt blad i motlys der bladårene synes tydelig" },
        { "type": "text", "id": "where-label", "x": 672, "y": 120, "w": 520, "h": 28, "style": "label", "text": "HVOR SKJER DET?" },
        { "type": "text", "id": "where-title", "x": 672, "y": 156, "w": 520, "h": 128, "style": "title", "text": "I bladets grønne korn" },
        { "type": "text", "id": "where-points", "x": 672, "y": 316, "w": 520, "h": 240, "style": "body", "size": 24, "fit": "shrink",
          "html": "<ul><li>Grønnkornene fanger lyset</li><li>Klorofyll gir bladet den grønne fargen</li><li>Små åpninger slipper CO₂ inn og oksygen ut</li></ul>" },
        { "type": "text", "id": "where-credit", "x": 672, "y": 600, "w": 520, "h": 32, "style": "caption", "size": 16,
          "text": "Foto: Jon Sullivan, fri bruk" }
      ]
    },
    {
      "id": "words",
      "title": "Ett ord om fotosyntese",
      "background": "leaf",
      "objects": [
        { "type": "text", "id": "words-label", "x": 88, "y": 72, "w": 400, "h": 28, "style": "label", "color": "lime", "text": "ORDSKY" },
        { "type": "text", "id": "words-title", "x": 88, "y": 108, "w": 760, "h": 72, "style": "title", "color": "paper",
          "text": "Ett ord om fotosyntese" },
        { "type": "text", "id": "words-hint", "x": 88, "y": 188, "w": 760, "h": 36, "style": "caption", "color": "sand",
          "text": "Skriv det første ordet du tenker på." },
        { "type": "poll", "id": "words-poll", "x": 88, "y": 256, "w": 1104, "h": 384, "kind": "wordcloud",
          "question": "Hvilket ord tenker du på?", "results": "live", "fill": "ink", "color": "paper", "size": 26, "weight": 700 }
      ]
    },
    {
      "id": "check",
      "title": "Sjekk forståelsen",
      "background": "paper",
      "notes": "Vis svaret først når alle har stemt.",
      "objects": [
        { "type": "text", "id": "check-label", "x": 88, "y": 72, "w": 480, "h": 28, "style": "label", "text": "SJEKK FORSTÅELSEN" },
        { "type": "text", "id": "check-title", "x": 88, "y": 108, "w": 480, "h": 128, "style": "title", "text": "Hva lager planten?" },
        { "type": "text", "id": "check-hint", "x": 88, "y": 268, "w": 480, "h": 160, "style": "body",
          "text": "Tenk på hva som står på høyre side av formelen." },
        { "type": "poll", "id": "check-poll", "x": 648, "y": 176, "w": 544, "h": 452, "kind": "choice",
          "question": "Hva lager planten under fotosyntesen?",
          "options": [
            { "id": "sugar", "text": "Sukker og oksygen" },
            { "id": "water", "text": "Vann og jord" },
            { "id": "co2", "text": "Karbondioksid" },
            { "id": "nitrogen", "text": "Nitrogen" }
          ],
          "results": "hidden", "fill": "ink", "color": "paper", "size": 26, "weight": 700 }
      ]
    },
    {
      "id": "growth",
      "title": "Mer lys gir mer vekst",
      "background": "paper",
      "objects": [
        { "type": "text", "id": "growth-label", "x": 88, "y": 72, "w": 400, "h": 28, "style": "label", "text": "FORSØK" },
        { "type": "text", "id": "growth-title", "x": 88, "y": 108, "w": 1104, "h": 72, "style": "title", "text": "Mer lys gir mer vekst" },
        { "type": "chart", "id": "growth-chart", "x": 88, "y": 208, "w": 680, "h": 440,
          "chart": {
            "chartType": "line",
            "labels": ["2 timer", "4 timer", "8 timer", "12 timer"],
            "series": [{ "name": "Vekst (cm)", "values": [1.2, 2.5, 4.1, 5], "color": "lime" }],
            "showLegend": false,
            "showValues": true,
            "style": "minimal"
          } },
        { "type": "text", "id": "growth-method", "x": 816, "y": 232, "w": 376, "h": 200, "style": "body", "size": 24,
          "text": "Vi målte karseplanter etter én uke med ulikt antall timer lys per dag." },
        { "type": "text", "id": "growth-stat", "x": 816, "y": 464, "w": 376, "h": 96, "style": "title", "size": 80, "color": "leaf",
          "text": "4×" },
        { "type": "text", "id": "growth-stat-text", "x": 816, "y": 568, "w": 376, "h": 64, "style": "caption",
          "text": "så mye vekst med 12 timer lys som med 2" }
      ]
    },
    {
      "id": "summary",
      "title": "Oppsummering",
      "background": "ink",
      "objects": [
        { "type": "text", "id": "summary-label", "x": 88, "y": 72, "w": 400, "h": 28, "style": "label", "color": "lime",
          "text": "OPPSUMMERING" },
        { "type": "text", "id": "summary-title", "x": 88, "y": 108, "w": 640, "h": 72, "style": "title", "color": "paper",
          "text": "Dette tar vi med oss" },
        { "type": "text", "id": "summary-point-1-number", "x": 88, "y": 224, "w": 56, "h": 56, "style": "title", "size": 32,
          "color": "lime", "text": "1" },
        { "type": "text", "id": "summary-point-1", "x": 160, "y": 224, "w": 520, "h": 80, "style": "body", "size": 26, "color": "paper",
          "text": "Planter lager sukker av lys, vann og CO₂." },
        { "type": "text", "id": "summary-point-2-number", "x": 88, "y": 336, "w": 56, "h": 56, "style": "title", "size": 32,
          "color": "lime", "text": "2" },
        { "type": "text", "id": "summary-point-2", "x": 160, "y": 336, "w": 520, "h": 80, "style": "body", "size": 26, "color": "paper",
          "text": "Oksygenet vi puster inn, kommer fra fotosyntesen." },
        { "type": "text", "id": "summary-point-3-number", "x": 88, "y": 448, "w": 56, "h": 56, "style": "title", "size": 32,
          "color": "lime", "text": "3" },
        { "type": "text", "id": "summary-point-3", "x": 160, "y": 448, "w": 520, "h": 80, "style": "body", "size": 26, "color": "paper",
          "text": "Uten lys stopper fotosyntesen." },
        { "type": "poll", "id": "summary-poll", "x": 760, "y": 224, "w": 432, "h": 304, "kind": "scale",
          "question": "Hvor sikker er du nå?", "options": [{ "id": "unsure", "text": "Usikker" }, { "id": "sure", "text": "Helt sikker" }],
          "results": "live", "fill": "leaf", "color": "paper", "size": 24, "weight": 700 },
        { "type": "text", "id": "summary-next", "x": 88, "y": 600, "w": 600, "h": 40, "style": "caption", "color": "sand",
          "text": "Neste time: celleånding" }
      ]
    }
  ]
}
```

### Workshop (English): Meetings worth having

A 45-minute workshop with a word cloud, a scale and a choice poll, each next to the content it belongs to. Files: https://pikko.no/format/examples/workshop-en.pikko and https://pikko.no/format/examples/workshop-en.json.

```json
{
  "format": "pikko/1",
  "title": "Meetings worth having",
  "language": "en",
  "theme": "sunset",
  "styles": {
    "colors": { "plum": "#32233d", "panel": "#503950", "cream": "#fff6eb", "peach": "#ffb39f", "mauve": "#decadb" },
    "text": {
      "title": { "font": "sans", "size": 52, "weight": 800, "color": "cream", "lineHeight": 1.1 },
      "body": { "font": "sans", "size": 28, "weight": 400, "color": "cream", "lineHeight": 1.35 },
      "label": { "font": "sans", "size": 18, "weight": 700, "color": "peach", "letterSpacing": 2 },
      "caption": { "font": "sans", "size": 20, "weight": 400, "color": "mauve", "lineHeight": 1.3 }
    }
  },
  "slides": [
    {
      "id": "welcome",
      "title": "Meetings worth having",
      "background": "plum",
      "notes": "Welcome everyone and ask them to open the join link on their phones.",
      "objects": [
        { "type": "shape", "id": "welcome-panel", "x": 896, "y": 0, "w": 384, "h": 720, "shape": "rectangle", "fill": "panel" },
        { "type": "shape", "id": "welcome-disc", "x": 976, "y": 400, "w": 224, "h": 224, "shape": "circle", "fill": "peach" },
        { "type": "icon", "id": "welcome-icon", "x": 1040, "y": 464, "w": 96, "h": 96, "icon": "people", "iconColor": "plum",
          "iconStyle": "stroke", "iconStrokeWidth": 2 },
        { "type": "text", "id": "welcome-label", "x": 88, "y": 200, "w": 640, "h": 32, "style": "label", "text": "WORKSHOP · 45 MINUTES" },
        { "type": "text", "id": "welcome-title", "x": 88, "y": 248, "w": 720, "h": 192, "style": "title", "size": 80,
          "text": "Meetings worth having" },
        { "type": "text", "id": "welcome-subtitle", "x": 88, "y": 464, "w": 680, "h": 80, "style": "body", "color": "mauve",
          "text": "Fewer, shorter meetings that end with a decision" }
      ]
    },
    {
      "id": "plan",
      "title": "Today",
      "background": "plum",
      "objects": [
        { "type": "text", "id": "plan-label", "x": 88, "y": 72, "w": 400, "h": 28, "style": "label", "text": "THE PLAN" },
        { "type": "text", "id": "plan-title", "x": 88, "y": 108, "w": 1104, "h": 72, "style": "title",
          "text": "Three steps, fifteen minutes each" },
        { "type": "shape", "id": "plan-share-card", "x": 88, "y": 232, "w": 336, "h": 360, "shape": "rectangle", "fill": "panel",
          "radius": 24 },
        { "type": "text", "id": "plan-share-number", "x": 120, "y": 264, "w": 272, "h": 64, "style": "title", "size": 48, "color": "peach",
          "text": "01" },
        { "type": "text", "id": "plan-share-name", "x": 120, "y": 352, "w": 272, "h": 48, "style": "title", "size": 36, "text": "Share" },
        { "type": "text", "id": "plan-share-text", "x": 120, "y": 416, "w": 272, "h": 136, "style": "body", "size": 24, "color": "mauve",
          "text": "One word for how meetings feel today." },
        { "type": "shape", "id": "plan-rate-card", "x": 472, "y": 232, "w": 336, "h": 360, "shape": "rectangle", "fill": "panel",
          "radius": 24 },
        { "type": "text", "id": "plan-rate-number", "x": 504, "y": 264, "w": 272, "h": 64, "style": "title", "size": 48, "color": "peach",
          "text": "02" },
        { "type": "text", "id": "plan-rate-name", "x": 504, "y": 352, "w": 272, "h": 48, "style": "title", "size": 36, "text": "Rate" },
        { "type": "text", "id": "plan-rate-text", "x": 504, "y": 416, "w": 272, "h": 136, "style": "body", "size": 24, "color": "mauve",
          "text": "How useful is your weekly meeting?" },
        { "type": "shape", "id": "plan-commit-card", "x": 856, "y": 232, "w": 336, "h": 360, "shape": "rectangle", "fill": "panel",
          "radius": 24 },
        { "type": "text", "id": "plan-commit-number", "x": 888, "y": 264, "w": 272, "h": 64, "style": "title", "size": 48,
          "color": "peach", "text": "03" },
        { "type": "text", "id": "plan-commit-name", "x": 888, "y": 352, "w": 272, "h": 48, "style": "title", "size": 36, "text": "Commit" },
        { "type": "text", "id": "plan-commit-text", "x": 888, "y": 416, "w": 272, "h": 136, "style": "body", "size": 24, "color": "mauve",
          "text": "Pick one habit to try next week." }
      ]
    },
    {
      "id": "share",
      "title": "Your last meeting",
      "background": "plum",
      "objects": [
        { "type": "text", "id": "share-label", "x": 88, "y": 72, "w": 400, "h": 28, "style": "label", "text": "STEP 1 · SHARE" },
        { "type": "text", "id": "share-title", "x": 88, "y": 108, "w": 760, "h": 72, "style": "title", "text": "Your last meeting" },
        { "type": "text", "id": "share-hint", "x": 88, "y": 188, "w": 760, "h": 36, "style": "caption", "text": "Sum it up in one word." },
        { "type": "poll", "id": "share-words", "x": 88, "y": 256, "w": 1104, "h": 392, "kind": "wordcloud",
          "question": "Describe your last meeting in one word", "results": "live", "fill": "panel", "color": "cream", "size": 26,
          "weight": 700 }
      ]
    },
    {
      "id": "rate",
      "title": "How useful are your meetings?",
      "background": "plum",
      "objects": [
        { "type": "text", "id": "rate-label", "x": 88, "y": 72, "w": 480, "h": 28, "style": "label", "text": "STEP 2 · RATE" },
        { "type": "text", "id": "rate-title", "x": 88, "y": 108, "w": 480, "h": 192, "style": "title",
          "text": "How useful is your weekly meeting?" },
        { "type": "text", "id": "rate-hint", "x": 88, "y": 332, "w": 480, "h": 120, "style": "body", "color": "mauve",
          "text": "Think of the last four weeks, not the best one." },
        { "type": "poll", "id": "rate-scale", "x": 648, "y": 176, "w": 544, "h": 420, "kind": "scale", "question": "How useful is it?",
          "options": [{ "id": "waste", "text": "Waste of time" }, { "id": "essential", "text": "Essential" }], "results": "live",
          "fill": "panel", "color": "cream", "size": 26, "weight": 700 }
      ]
    },
    {
      "id": "habits",
      "title": "Three habits",
      "background": "cream",
      "objects": [
        { "type": "text", "id": "habits-label", "x": 88, "y": 72, "w": 400, "h": 28, "style": "label", "color": "panel",
          "text": "WHAT WORKS" },
        { "type": "text", "id": "habits-title", "x": 88, "y": 108, "w": 1104, "h": 72, "style": "title", "color": "plum",
          "text": "Three habits of good meetings" },
        { "type": "shape", "id": "habits-decision-card", "x": 88, "y": 232, "w": 336, "h": 384, "shape": "rectangle", "fill": "#ffe3d8",
          "radius": 24 },
        { "type": "icon", "id": "habits-decision-icon", "x": 120, "y": 264, "w": 64, "h": 64, "icon": "target", "iconColor": "plum",
          "iconStyle": "stroke", "iconStrokeWidth": 2 },
        { "type": "text", "id": "habits-decision-name", "x": 120, "y": 352, "w": 272, "h": 96, "style": "title", "size": 32,
          "color": "plum", "text": "Start with the decision" },
        { "type": "text", "id": "habits-decision-text", "x": 120, "y": 464, "w": 272, "h": 120, "style": "body", "size": 22,
          "color": "panel", "text": "Write the question you need answered at the top of the invite." },
        { "type": "shape", "id": "habits-people-card", "x": 472, "y": 232, "w": 336, "h": 384, "shape": "rectangle", "fill": "#ffe3d8",
          "radius": 24 },
        { "type": "icon", "id": "habits-people-icon", "x": 504, "y": 264, "w": 64, "h": 64, "icon": "people", "iconColor": "plum",
          "iconStyle": "stroke", "iconStrokeWidth": 2 },
        { "type": "text", "id": "habits-people-name", "x": 504, "y": 352, "w": 272, "h": 96, "style": "title", "size": 32, "color": "plum",
          "text": "Invite fewer people" },
        { "type": "text", "id": "habits-people-text", "x": 504, "y": 464, "w": 272, "h": 120, "style": "body", "size": 22,
          "color": "panel", "text": "Everyone else gets the notes. Five is plenty." },
        { "type": "shape", "id": "habits-owners-card", "x": 856, "y": 232, "w": 336, "h": 384, "shape": "rectangle", "fill": "#ffe3d8",
          "radius": 24 },
        { "type": "icon", "id": "habits-owners-icon", "x": 888, "y": 264, "w": 64, "h": 64, "icon": "check-circle", "iconColor": "plum",
          "iconStyle": "stroke", "iconStrokeWidth": 2 },
        { "type": "text", "id": "habits-owners-name", "x": 888, "y": 352, "w": 272, "h": 96, "style": "title", "size": 32, "color": "plum",
          "text": "End with owners" },
        { "type": "text", "id": "habits-owners-text", "x": 888, "y": 464, "w": 272, "h": 120, "style": "body", "size": 22,
          "color": "panel", "text": "Every next step has a name and a date." }
      ]
    },
    {
      "id": "commit",
      "title": "Which habit first?",
      "background": "plum",
      "objects": [
        { "type": "text", "id": "commit-label", "x": 88, "y": 72, "w": 480, "h": 28, "style": "label", "text": "STEP 3 · COMMIT" },
        { "type": "text", "id": "commit-title", "x": 88, "y": 108, "w": 480, "h": 192, "style": "title",
          "text": "Which habit will you try first?" },
        { "type": "text", "id": "commit-hint", "x": 88, "y": 332, "w": 480, "h": 120, "style": "body", "color": "mauve",
          "text": "Pick the one you can start on Monday." },
        { "type": "poll", "id": "commit-poll", "x": 648, "y": 176, "w": 544, "h": 452, "kind": "choice", "question": "I will try first:",
          "options": [
            { "id": "decision", "text": "Decision first" },
            { "id": "people", "text": "Fewer people" },
            { "id": "owners", "text": "Name owners" }
          ],
          "results": "live", "fill": "panel", "color": "cream", "size": 26, "weight": 700 }
      ]
    },
    {
      "id": "thanks",
      "title": "Thank you",
      "background": "plum",
      "objects": [
        { "type": "shape", "id": "thanks-rule", "x": 88, "y": 248, "w": 120, "h": 8, "shape": "rectangle", "fill": "peach" },
        { "type": "text", "id": "thanks-title", "x": 88, "y": 280, "w": 1104, "h": 104, "style": "title", "size": 80, "text": "Thank you" },
        { "type": "text", "id": "thanks-next", "x": 88, "y": 408, "w": 880, "h": 80, "style": "body", "color": "mauve",
          "text": "Try your habit for two weeks, then we compare notes." }
      ]
    }
  ]
}
```

### Pitch (English): Tidepool

A four-slide investor pitch with a big number, a chart and an audience poll. Files: https://pikko.no/format/examples/pitch-en.pikko and https://pikko.no/format/examples/pitch-en.json.

```json
{
  "format": "pikko/1",
  "title": "Tidepool",
  "language": "en",
  "theme": "ocean",
  "styles": {
    "colors": { "navy": "#0b2540", "deep": "#123a5f", "foam": "#eaf6f6", "teal": "#14b8a6", "sky": "#7dd3fc", "slate": "#4b6178" },
    "text": {
      "title": { "font": "sans", "size": 56, "weight": 800, "color": "navy", "lineHeight": 1.08 },
      "body": { "font": "sans", "size": 28, "weight": 400, "color": "navy", "lineHeight": 1.35 },
      "label": { "font": "sans", "size": 18, "weight": 700, "color": "teal", "letterSpacing": 2 }
    }
  },
  "slides": [
    {
      "id": "cover",
      "title": "Tidepool",
      "background": "navy",
      "notes": "Thirty seconds: who we are and why now.",
      "objects": [
        { "type": "shape", "id": "cover-wave-large", "x": 820, "y": 120, "w": 400, "h": 400, "shape": "circle", "fill": "teal" },
        { "type": "shape", "id": "cover-wave-small", "x": 760, "y": 420, "w": 200, "h": 200, "shape": "circle", "fill": "sky" },
        { "type": "text", "id": "cover-label", "x": 88, "y": 208, "w": 600, "h": 32, "style": "label", "text": "SEED ROUND · 2026" },
        { "type": "text", "id": "cover-title", "x": 88, "y": 256, "w": 640, "h": 120, "style": "title", "size": 104, "color": "foam",
          "text": "Tidepool" },
        { "type": "text", "id": "cover-subtitle", "x": 88, "y": 400, "w": 600, "h": 96, "style": "body", "color": "sky",
          "text": "Refill stations for shampoo and soap in every grocery store" }
      ]
    },
    {
      "id": "problem",
      "title": "The problem",
      "background": "foam",
      "objects": [
        { "type": "text", "id": "problem-label", "x": 88, "y": 72, "w": 400, "h": 28, "style": "label", "text": "THE PROBLEM" },
        { "type": "text", "id": "problem-title", "x": 88, "y": 108, "w": 1104, "h": 128, "style": "title",
          "text": "A shampoo bottle is used for a month, then thrown away" },
        { "type": "text", "id": "problem-points", "x": 88, "y": 288, "w": 560, "h": 280, "style": "body",
          "html": "<ul><li>Refill shops are few and far from where people shop</li><li>People want less plastic, but not an extra trip</li><li>Stores have no simple way to offer refills</li></ul>" },
        { "type": "shape", "id": "problem-card", "x": 712, "y": 288, "w": 480, "h": 312, "shape": "rectangle", "fill": "navy",
          "radius": 24 },
        { "type": "text", "id": "problem-stat", "x": 752, "y": 320, "w": 400, "h": 128, "style": "title", "size": 112, "color": "teal",
          "text": "73%" },
        { "type": "text", "id": "problem-stat-text", "x": 752, "y": 464, "w": 400, "h": 104, "style": "body", "size": 24, "color": "foam",
          "text": "of pilot customers refilled again within a month" }
      ]
    },
    {
      "id": "traction",
      "title": "Traction",
      "background": "foam",
      "objects": [
        { "type": "text", "id": "traction-label", "x": 88, "y": 72, "w": 400, "h": 28, "style": "label", "text": "TRACTION" },
        { "type": "text", "id": "traction-title", "x": 88, "y": 108, "w": 1104, "h": 72, "style": "title",
          "text": "Refills grew every month" },
        { "type": "chart", "id": "traction-chart", "x": 88, "y": 208, "w": 720, "h": 440,
          "chart": {
            "chartType": "line",
            "labels": ["Mar", "Apr", "May", "Jun", "Jul", "Aug"],
            "series": [{ "name": "Refills per week", "values": [120, 210, 340, 480, 690, 910], "color": "teal" }],
            "showLegend": false,
            "showValues": true,
            "style": "minimal"
          } },
        { "type": "text", "id": "traction-stores", "x": 856, "y": 240, "w": 336, "h": 96, "style": "title", "size": 72, "color": "teal",
          "text": "6" },
        { "type": "text", "id": "traction-stores-text", "x": 856, "y": 336, "w": 336, "h": 64, "style": "body", "size": 24,
          "text": "stores in the Bergen pilot" },
        { "type": "text", "id": "traction-bottles", "x": 856, "y": 448, "w": 336, "h": 96, "style": "title", "size": 72, "color": "teal",
          "text": "11k" },
        { "type": "text", "id": "traction-bottles-text", "x": 856, "y": 544, "w": 336, "h": 64, "style": "body", "size": 24,
          "text": "bottles not thrown away" }
      ]
    },
    {
      "id": "ask",
      "title": "The ask",
      "background": "navy",
      "objects": [
        { "type": "text", "id": "ask-label", "x": 88, "y": 72, "w": 400, "h": 28, "style": "label", "text": "THE ASK" },
        { "type": "text", "id": "ask-title", "x": 88, "y": 108, "w": 520, "h": 192, "style": "title", "color": "foam",
          "text": "NOK 8 million for 60 stores" },
        { "type": "text", "id": "ask-detail", "x": 88, "y": 328, "w": 520, "h": 200, "style": "body", "color": "sky",
          "text": "Eighteen months to reach the whole Bergen region and break even." },
        { "type": "poll", "id": "ask-poll", "x": 688, "y": 176, "w": 504, "h": 452, "kind": "choice",
          "question": "Would you use a refill station in your store?",
          "options": [
            { "id": "weekly", "text": "Yes, every week" },
            { "id": "sometimes", "text": "Sometimes" },
            { "id": "no", "text": "Probably not" }
          ],
          "results": "live", "fill": "deep", "color": "foam", "size": 26, "weight": 700 }
      ]
    }
  ]
}
```
