> ## Knowledge Base Index
> Fetch the complete knowledge base index at: https://documentation.celestory.io/sitemap.xml
> Use this file to discover available pages before exploring further.
> Pure-Markdown content can be obtained by appending a '.md' suffix to the content URLs listed in the sitemap (without the trailing slash).

# Writing an HTML5 page in Celestory

# 🧑‍💻 Writing an HTML5 page in Celestory

The **HTML5** block is Celestory's escape hatch. You paste a complete web page into it — HTML, CSS, JavaScript — and Celestory shows it full screen at that point of the journey. A mini-game, a bespoke form, a simulation, a hand-drawn canvas: anything there is no block for.

But a page on its own is useless. What matters is the **contract**: how the page receives values from the graph, and how it hands values back. It comes down to three things. 🔌

🔺 The **HTML5** block requires a **Business** subscription.

---

## 📝 Where you write, and when it is saved

Open the **HTML5** block (double-click). Its **content** input hosts a full code editor — the Visual Studio Code engine — with syntax highlighting and folding.

🔺 **Your code only reaches the graph when the editor loses focus.** While the caret is still blinking in the editor, Celestory sees nothing. Click outside the editor, or close the block's edit window: **that** is the moment the code is saved and the input and output points appear on, or disappear from, the block.

This is confusion number one. If you have just pasted a `{{firstname}}` and no input shows up, you simply have not left the editor yet. 🙂

The page must be **self-contained**: nothing is bundled next to it and no neighbouring file is served. Everything it needs lives inside the block, or loads from a public URL.

---

## 📥 Receiving values: double curly braces

Write `{{name}}` **anywhere in the code** and Celestory creates an input of that name on the block.

```html
<h1>Hello {{firstname}}!</h1>
<p>You are on {{score}} points.</p>
```

Two inputs appear: `firstname` and `score`. Wire a variable to them, or another block's output, or type a fixed value. They accept **Text**, a **Number** or a **Boolean**.

🔺 **Only unaccented letters, digits and the underscore are accepted**: `a-z`, `A-Z`, `0-9`, `_`. `{{Prénom}}`, `{{my-score}}` and `{{ firstname }}` are **not** detected. No input is created, no error is raised, and the marker is displayed on screen as written. Case matters. These are exactly the rules covered in the double curly braces article.

🔺 **The substitution is purely textual**, performed on the source before the page runs. In JavaScript, a text value therefore needs its quotes:

```html
<script>
  const points = {{score}};          // ✅ a number, as is
  const player = "{{firstname}}";    // ✅ a text, in quotes
  const broken = {{firstname}};      // ❌ syntax error at runtime
</script>
```

🔺 An input left **unwired** is replaced by **nothing at all**. `const points = ;` — your page never starts, and nothing warns you.

---

## 📤 Handing values back: declare, then post

This is the least obvious part, and it happens in two steps.

### 1. Declare what the page will return

Write this comment anywhere in the page:

```html
<!-- celestory-outputs: score, verdict -->
```

Each listed name becomes an **output** on the block, which you can read, test or wire like any other. Separate them with commas or spaces, on one line or several. These outputs carry **Text**, a **Number** or a **Boolean**.

🔺 The same naming rules apply: unaccented letters, digits, underscore. Nothing else.

### 2. Post the value while the page is running

```html
<script>
  parent.document.dispatchEvent(
    new CustomEvent('CelestoryOutput', {detail: {key: 'score', value: 42}})
  );
</script>
```

`key` names the output, `value` carries the value. Post as often as you like: for a given key, the last value posted is the one the graph reads.

There is a **second route**, older but still live: if the key is written **literally** in the call, as above, Celestory spots it while reading the code and creates the output even without a declaration comment.

🔺 But as soon as the key is computed — `key: outputName` — nothing is detected any more. **Always declare your outputs in the comment.** It is the only method that does not depend on how you happen to write your code.

---

## 🏁 Giving control back: CelestoryEnd

As long as the page is on screen, the journey is halted. To restart it, the page sends:

```html
<script>
  parent.document.dispatchEvent(new CustomEvent('CelestoryEnd'));
</script>
```

The flow then leaves through the block's **out** output. The event is **idempotent**: send it ten times and the graph still advances once.

**Post your values before you end.** Order matters: `CelestoryEnd` advances the flow, and the blocks downstream read whatever was posted up to that moment.

🔺 **A page that never sends `CelestoryEnd` can strand the experience.** The close button is your remaining way out, but it only appears if the **out** output is wired *and* the **close button** input is true. With neither, the player is stuck.

---

## ⚠️ The trap: the listeners sit on the *document*

This is confirmed in the code, and it is the most common cause of failure: **Celestory listens on `document` objects, never on a `window`.**

Your page runs inside an isolated frame. Two listeners are attached: one on the host application's document, one on the page's own document.

```html
<script>
  // ✅ always correct — the host application's document
  parent.document.dispatchEvent(new CustomEvent('CelestoryEnd'));

  // ✅ also correct — your own page's document
  document.dispatchEvent(new CustomEvent('CelestoryEnd'));

  // ❌ reaches nothing, silently
  window.dispatchEvent(new CustomEvent('CelestoryEnd'));
  parent.dispatchEvent(new CustomEvent('CelestoryEnd'));
  dispatchEvent(new CustomEvent('CelestoryEnd'));
</script>
```

🔺 Dispatching the event on a `window` triggers **absolutely nothing**, whatever the rest of your page does. No console error, no message: the page simply sits there.

🔺 Posting it on an **element** (a button, a `div`) does not work either, unless you make it bubble explicitly — and a `CustomEvent` does not bubble by default. **Get into the habit of `parent.document`**; it works in every case.

---

## 🧩 A complete page, end to end

```html
<!-- celestory-outputs: answer, correct -->
<!DOCTYPE html>
<html>
  <body style="font-family: sans-serif; text-align: center; padding: 40px">
    <h1>Hello {{firstname}}!</h1>
    <p>What is 7 × 6?</p>
    <input id="entry" type="number" />
    <button id="submit">Submit</button>

    <script>
      document.getElementById('submit').addEventListener('click', () => {
        const value = Number(document.getElementById('entry').value);

        parent.document.dispatchEvent(new CustomEvent('CelestoryOutput',
          {detail: {key: 'answer', value: value}}));
        parent.document.dispatchEvent(new CustomEvent('CelestoryOutput',
          {detail: {key: 'correct', value: value === 42}}));

        parent.document.dispatchEvent(new CustomEvent('CelestoryEnd'));
      });
    </script>
  </body>
</html>
```

Once you leave the editor, the block shows:

- one input, **firstname**, waiting to be wired;
- two outputs, **answer** (a number) and **correct** (a boolean);
- the **out** stream output, to wire onwards — into a **Condition** block reading `correct`, for instance.

The output's type follows the posted value: a number arrives as a **Number**, a boolean as a **Boolean**, everything else as **Text**.

---

## 🧷 The rest of the block

The **close button** input shows a cross in the top-right corner of the page. It is on by default and is only a safety net: clicking it does exactly what `CelestoryEnd` would do, with no value posted. Set it to false for a mini-game nobody should be able to walk out of — provided your page knows how to end itself.

---

## ✅ The checklist

Before you test, check these five things:

1. You have **left the editor**: the points really did appear on the block.
1. Every `{{name}}` is **unaccented** and **wired**.
1. Your outputs are **declared** in `<!-- celestory-outputs: … -->`.
1. All your events go to **`parent.document`**.
1. Your page sends **`CelestoryEnd`** — after posting its values.

---

→ Next step: take the complete page above, paste it into an **HTML5** block, leave the editor, wire `firstname`, and drop a **Condition** block on the `correct` output. That is the pattern behind every advanced use of Celestory.
