# Panel

> The pages the platform mounts, and the functions that draw its panel in a page of your own.

`toolaby.js` carries the client and the panel in one file. Most extensions never call anything here: the wiring puts the platform's own page in front of yours and it draws itself. These are for a page of your own that draws the panel — a popup the platform cannot stand in front of ([Plasmo](https://toolaby.app/docs/frameworks.md#plasmo)), or a layout you want to own. [Surfaces](https://toolaby.app/docs/surfaces.md) says which case is yours.

Everything on this page is drawn from what the platform sends: the plans as your checkout words them, your brand's tokens, the buyer's language. None of it is yours to design — change **Pricing** or **Branding** in the dashboard and every panel follows.

## mountPopup()

```ts
mountPopup(): void
```

Mounts the platform's popup page. `toolaby-popup.html` — the shell the wiring writes — is two lines that call this. At every open it asks the gate and either opens your popup (`appPopup`) or draws the panel. Expects `#app`, `#mark`, `#name`, `#status` and `#panel` in the page.

## mountSidePanel()

```ts
mountSidePanel(): void
```

The same page on the side panel, at the panel's width: `toolaby-sidepanel.html` calls it, and it hands over to `appSidePanel`.

## loadPaywall()

```ts
loadPaywall(config: PanelConfig): Promise<Paywall | null>
```

The panel's facts for this tool in the buyer's language — the plans, the words, your brand's tokens. Cached on the device for a day, so it draws at once and offline; fetched again at the open after you change Pricing or Branding. `config` is what the background answers to `{ action: 'toolaby.config' }`.

## applyBrand()

```ts
applyBrand(paywall: Paywall | null): void
```

Puts your brand's tokens — accent, tint, corners, colour mode — on the document, where the panel's stylesheet turns them into the `--pw-*` variables below. A value of any other shape than Toolaby sends — a colour as `#rrggbb`, corners in `px` — is left out, and the stylesheet's own stands. Call it once the facts have loaded; your own CSS can use the same variables.

## ensureStyles()

```ts
ensureStyles(doc?: Document): void
```

The panel's stylesheet, once. Call it before your own CSS so its tokens are there from the first paint.

It sizes the page as a **336px popup** unless the page says otherwise:

```html
<html data-toolaby-surface="sidepanel">
```

which makes it fill a side panel instead. That attribute is the only difference between the two surfaces.

Your own CSS can use the panel's tokens, in your brand and in dark mode alike:

| Tokens                                                   |                                                           |
| -------------------------------------------------------- | --------------------------------------------------------- |
| `--pw-bg`, `--pw-frame`, `--pw-panel`                    | The grounds: the page, the frame around a panel, a panel. |
| `--pw-line`, `--pw-line-soft`                            | Rules and borders.                                        |
| `--pw-ink`, `--pw-body`, `--pw-muted`, `--pw-faint`      | Text, from the strongest.                                 |
| `--pw-on`, `--pw-on-fg`                                  | Your brand's accent, and the text on it.                  |
| `--pw-ok`, `--pw-ok-soft`, `--pw-warn`, `--pw-warn-soft` | States.                                                   |
| `--pw-r-frame`, `--pw-r-panel`, `--pw-r-ctl`             | Corner radii: a frame, a panel, a control.                |
| `--pw-shadow`, `--pw-ease`, `--pw-font`                  | The lift, the easing and the font.                        |

## renderMark()

```ts
renderMark(node: HTMLElement, name: string): void
```

The header's mark: the extension's own icon from its manifest, else the initial of the name.

## refusalOf()

```ts
refusalOf(user: User, permit: Permit | null): Refusal
```

Why this device may not continue, from the user and the permit that refused: one of the [Refusal](#refusal) kinds. Hand it to `renderPaywall`.

## renderPaywall()

```ts
renderPaywall(root: HTMLElement, paywall: Paywall | null, user: User, config: PanelConfig, actions: PanelActions, refusal?: Refusal): void
```

The panel for a device that is not unlocked: the sign-in doors, the free-use meter, the plans, the plan that holds a refused feature, a failed payment, or a device past its plan's device limit.

## renderUnlocked()

```ts
renderUnlocked(root: HTMLElement, paywall: Paywall | null, user: User, config: PanelConfig, actions: PanelActions, opts?: { celebrate?: boolean }): void
```

The panel once unlocked: the plan held and how it stands — *Renews 22 Oct*, *Ends 22 Oct*, *Lifetime licence*, *Trial until 29 Sep* — the account it is on, and the way to the account page. `celebrate` pops the mark once, for the moment a purchase lands while the page is open.

On the Free plan of a tool whose plans sell a feature, it says which — *Export to CSV is in Pro* — with a button to that plan, rather than *Unlocked* beside a locked button.

## renderNotice()

```ts
renderNotice(root: HTMLElement, title: string, body: string): void
```

A notice in place of everything else. What a build below the minimum version shows. See [Versions](https://toolaby.app/docs/versions.md).

## plansWith()

```ts
plansWith(paywall: Paywall | null, feature: string, needs?: string[] | null): PaywallPlan[]
```

The plans that hold a feature, the ones `gate()` named first, each `{ id, name, tier, features }`; the price is in the paywall's options, as the checkout page words it. For a page that sells a feature in its own words rather than through `renderPaywall`.

## renderAccountBar()

```ts
renderAccountBar(host: HTMLElement, paywall: Paywall | null, user: User, actions: PanelActions): void
```

The [account bar](https://toolaby.app/docs/toolaby-popup.md#the-account-bar) drawn into an element of your own, when the `<toolaby-account>` tag does not sit where you want it. One strip: the buyer's initial and email, the plan they hold, their account page, sign out.

## defineAccountBar()

```ts
defineAccountBar(): void
```

Defines `<toolaby-account>`. The bundle calls it as soon as it is on a page, so the tag works with no call of yours; this is for a page that imports nothing else from the file.

## Paywall

```ts
type Paywall = { brand: { accent, accentFg, darkAccent, darkAccentFg, radiusPx, mode, iconUrl } | null; words: Record<string, string>; … }
```

What the platform sends for the panel. Opaque: hand it to the functions above. `words` is the panel's language; `brand` is **Configure → Branding**.

## PanelConfig

```ts
type PanelConfig = { name: string; freeLimit?: number; slug?: string; siteUrl?: string }
```

The tool as the background resolved it from your key — `{ action: 'toolaby.config' }` answers with it, and it also carries `appPopup` and `appSidePanel`.

## PanelActions

```ts
type PanelActions = { buy: (planId: string) => void; signIn: () => void; account?: () => void }
```

What the panel's buttons do. The panel's **Continue with Google** calls `signIn('google')`: pass the argument on to `openLoginPage()`, as below. In TypeScript, declare the argument optional, `(provider?: 'google') => …`, so the function fits the type. The usual three are the client's own:

```js
const actions = {
  buy: (plan) => toolaby.openPaymentPage(plan),
  signIn: (provider) => toolaby.openLoginPage(provider),
  account: () => toolaby.openAccountPage(),
};
```

## Refusal

```ts
type Refusal =
  | { kind: 'sign_in'; bought?: boolean }
  | { kind: 'paid' }
  | { kind: 'limit' }
  | { kind: 'feature'; feature: string; needs: string[] | null }
  | { kind: 'past_due' }
  | { kind: 'device_limit'; allowed: number; held: number | null }
```

Which screen the panel draws. `refusalOf()` works it out; you rarely build one by hand.

| Kind           | The panel                                                                                                                            |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `sign_in`      | The sign-in doors. With `bought`, the device holds a purchase made signed out, and is asked to sign in with the address that bought. |
| `paid`         | The plans.                                                                                                                           |
| `limit`        | The free-use meter, then the plans.                                                                                                  |
| `feature`      | The plans that hold `feature`, those in `needs` first.                                                                               |
| `past_due`     | *Payment failed — update your card*, and the account page. The account's subscription is past due.                                   |
| `device_limit` | *In use on 2 devices already*, and the account page: the plan unlocks `allowed` devices, and `held` hold it.                         |
