# The frame bridge

> facia-app.js, the theme variables, safe-area insets and the postMessage protocol between your page and Facia.

# The frame bridge

Your page runs in an `<iframe>` inside Facia. The bridge lets it get the token and theme, and tell Facia when it's ready, when to open the chat, and when to close.

## The frame

```html
<iframe sandbox="allow-scripts allow-forms allow-same-origin"
        allow="fullscreen" referrerpolicy="no-referrer" src="https://your-app…/?facia_token=…">
```

- **No popups and no top-level navigation.** Open links inside your page, or use `Facia.openChat()`.
- Fullscreen is allowed (for games).
- Facia shows a loading skeleton over the business's background until you call `ready()`.
- The frame **stays alive** while the client switches tabs and comes back (up to 3 apps at once), so a game or form doesn't restart.
- Facia accepts messages only from your frame's window and your page's origin, and posts only to your origin.

## facia-app.js

```html
<script src="https://facia.chat/app/facia-app.js"></script>
```

| API | What it does |
| --- | --- |
| `Facia.inFacia` | `true` when the page is inside Facia |
| `Facia.ready()` | Removes Facia's loading skeleton. Call it once your content is drawn |
| `Facia.token()` | The latest token (posted by Facia, or `?facia_token=` from the address) |
| `Facia.onToken(fn)` | Called with each new token, and right away if there is one |
| `Facia.onTheme(fn)` | Called with `{ scheme, vars, radius, style }` now and on every theme change |
| `Facia.applyTheme(t)` | Writes the theme's CSS variables onto `<html>` and sets `color-scheme`, `data-facia-scheme`, `data-facia-radius` and the [style attributes](#matching-the-style) |
| `Facia.onInsets(fn)` | Called with `{ top, bottom, left, right }` safe-area insets in px |
| `Facia.openChat()` | Opens the client's support chat with the business |
| `Facia.close()` | Goes back to where the client came from |
| `Facia.resize(h)` | Accepted and ignored: the frame always fills the tab |

The usual start:

```js
Facia.onTheme((t) => Facia.applyTheme(t));
Facia.onInsets((i) => (document.body.style.paddingBottom = i.bottom + "px"));
const token = Facia.token();
// …draw…
Facia.ready();
```

## Theme variables

`applyTheme` sets these on `<html>`, so your CSS can use `var(--accent)`:

| Variable | Use for |
| --- | --- |
| `--bg` | Page background |
| `--panel`, `--surface`, `--elev` | Panels, cards, raised surfaces |
| `--hover`, `--press` | Hover and pressed states |
| `--border` | Lines and outlines |
| `--text`, `--text-2`, `--text-3` | Main, secondary and faint text |
| `--accent`, `--accent-2`, `--accent-fg`, `--accent-soft` | Primary actions, a second accent, text on accent, soft accent backgrounds |
| `--success`, `--warn`, `--danger` | Good, careful and bad states |
| `--chat-bg` | The chat background |

`radius` is `soft` or `sharp`. Style for it with `[data-facia-radius="sharp"]`. `scheme` is `light` or `dark`.

### Matching the style

The theme also says which [style](https://docs.facia.chat/business/look#styles) the client is in: `style: { id, base, outline, grid }`. `id` is `classic`, `futuristic` or `custom:<id>`, and `base` is `classic` or `future`. In Futuristic, and in custom styles built on it, Facia draws everything as lit outlines on a faint grid. These extra variables carry it:

| Variable | Use for |
| --- | --- |
| `--fx-deep` | The style's background |
| `--fx-surface` | Cards |
| `--fx-line` | Lines and outlines |
| `--fx-grid` | The background grid's lines |
| `--gold-500`, `--win-500` | Prizes and wins |

`applyTheme` sets `data-facia-style` and `data-facia-base` on `<html>`, plus `data-facia-outline` and `data-facia-grid` when they apply. A few rules make your page match:

```css
html[data-facia-outline] .card { background: transparent; box-shadow: inset 0 0 0 1px var(--fx-line); }
html[data-facia-outline] .primary { background: transparent; color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); }
html[data-facia-grid] body {
  background-image: linear-gradient(var(--fx-grid) 1px, transparent 1px), linear-gradient(90deg, var(--fx-grid) 1px, transparent 1px);
  background-size: 30px 30px;
}
```

In Classic none of these attributes are set, so these rules change nothing.

### Making it feel native

- **Paint `--bg` from the first frame** (`html { background: var(--bg) }` and a sensible fallback) to avoid a white flash.
- The frame fills the tab and owns the scroll. Don't add outer scrollbars.
- Keep the first load small. A plain or Phaser page is instant, while a Unity WebGL build (10–30 MB) shows the skeleton for a while on mobile data.
- Use `--accent` for your main action and neutral surfaces for the rest. **Gold means VIP in Facia**, so avoid it unless you mean VIP.
- Show the client's alias. Never ask for their name.

## Raw messages

If you'd rather not load the script, speak `postMessage` directly:

| Direction | `type` | Payload |
| --- | --- | --- |
| page → Facia | `facia:ready` | – |
| page → Facia | `facia:openChat` | – |
| page → Facia | `facia:close` | – |
| page → Facia | `facia:resize` | `{ height }` (ignored) |
| Facia → page | `facia:token` | `{ token }` |
| Facia → page | `facia:theme` | `{ scheme, vars: { "--bg": "…", … }, radius, style }` |
| Facia → page | `facia:insets` | `{ top, bottom, left, right }` |

```js
const send = (type, extra) => parent.postMessage({ type, ...extra }, "*");
addEventListener("message", (e) => {
  if (e.source !== parent) return;
  if (e.data?.type === "facia:token") useToken(e.data.token);
  if (e.data?.type === "facia:theme") applyVars(e.data.vars);
});
send("facia:ready");
```

Facia sends `facia:token`, `facia:theme` and `facia:insets` right after `facia:ready`, and sends `facia:theme` again whenever the theme changes.

## Testing outside Facia

Open your page directly in a browser. `Facia.inFacia` is `false`, there's no theme, and `Facia.token()` reads `?facia_token=` if you add one. For a real token, register the app with a `http://localhost` address and open it from inside your business.
