# Widgets on your site

## 2.1 Two pieces of markup

**The resolver** — one script tag, anywhere on the page:

```html
<script src="https://ludarium.rexplay.site/resolver.js" data-client-id="<client_id>" async></script>
```

**A placeholder per widget** — an element with `data-ig-slot`, wherever the widget should appear:

```html
<div data-ig-slot="promo-l" data-ig-placement="homepage"></div>
```

The resolver loads the configuration of your installation (which widget goes to which slot, your
brand's look, how games are launched, where your token endpoint is), finds every placeholder —
including those added later — and renders the widget inside it, isolated in a Shadow DOM, so your
styles and the widget's do not touch. You install nothing else: no npm package, no build step. The
configuration is kept by us; changing it needs no change on your pages.

Rules of the placeholder:

- `data-ig-slot` names the widget (§2.2). Several elements with one slot all get the widget.
- `data-ig-placement` names **where** it stands (`homepage`, `cashier`, …): one widget can be
  configured differently per placement. Without it the default placement applies.
- Other `data-*` attributes on the placeholder override the widget's defaults for that element;
  values are strings.

**Or no placeholder at all.** A widget of your installation can name its place on the page instead:
Operator Portal → **Embed** → your installation → the widget's slot → *Place on the page without
markup*, then **Publish Live**. Either a CSS selector — the resolver puts the placeholder itself at the
first element the selector finds, inside it at the end (default) or at the start, before it or after
it — or a floating panel pinned to a corner of the window (bottom right, bottom left, top right, top
left), above your page, with a close button; a closed panel stays closed until the page is loaded
again. Your page carries only the resolver script.

- Name an element that keeps its place across your releases — an `id` or a `data-` attribute of your
  own (`#promo-area`, `[data-ga-promo]`), not a layout class: when nothing matches, the widget is not
  shown.
- On a single-page site the widget follows the element: it appears when the element does and is
  removed when the element leaves the page.
- A placeholder you put on the page yourself for the same slot and placement wins over the selector
  or the floating panel: the widget is never shown twice.
- A floating panel covers a corner of your page: keep it clear of your own chat or cookie banner.

## 2.2 Widgets

| Slot | Shows |
| :--- | :--- |
| `game-sections` | rows of game categories from GA's catalogue for your operator (`GET /public/v1/catalog/lobby?operator_id=…`) |
| `game-section` | one category of GA's catalogue |
| `promo-l`, `promo-m`, `promo-s` | a collection promotion banner — large (desktop), medium, small (mobile) |
| `collection-event` | the page of a collection event: the album, its items and the player's progress |
| `event-page` | a full event page: the album, the player's collected items, the next item to collect, the countdown |
| `in-game` | a compact card over a game: the next item to collect, progress, the countdown |

Which widgets and placements you use is agreed at onboarding (form, `widgets`).

`collection-event` shows the campaign set in your configuration. To show another campaign on one
page, add `data-campaign-id` with the id we give you. Put one `collection-event` on a page:

```html
<div data-ig-slot="collection-event" data-campaign-id="<campaign_id>"></div>
```

`promo-l`, `promo-m`, `promo-s`, `event-page` and `in-game` advertise the same campaign as
`collection-event` and take `data-campaign-id` the same way:

```html
<div data-ig-slot="event-page" data-ig-placement="homepage"></div>
<div data-ig-slot="in-game" data-ig-placement="homepage"></div>
```

A signed-in player sees their own progress — items collected out of the album, the next item to
collect, and the countdown to the campaign's end. A guest sees the campaign and how many items it
has, with a sign-in button (§3.4), no personal numbers. Without a running campaign the widgets show
the states of §2.5, never sample data.

The countdown on `event-page`, `in-game` and the promo banners reads `hh:mm:ss` under a day left;
from 24 hours it puts the whole days in front, e.g. `104d 05:08:50` (`104д …` for a `ru` locale).

## 2.3 Events and game launch

Widgets tell your page what happened with ordinary DOM events raised on the placeholder element.
They bubble, so one listener on `document` catches all of them. Listening is optional.

| Event | `event.detail` | When |
| :--- | :--- | :--- |
| `ig:game-launch` | §4.2 | a player chose a game; with `sdk-modal`, after the game's modal has opened |
| `ig:login-required` | `{reason}` | an action needs a signed-in player; show your sign-in. A game launch gives `{reason: 'launch'}` |
| `ig:game-launch-error` | the game's fields of §4.2, `status`, `reason` | `sdk-modal` could not launch the game (§4.3) |

```js
document.addEventListener('ig:login-required', () => showLogin());
document.addEventListener('ig:game-launch', (e) => track(e.detail.gameId));
document.addEventListener('ig:game-launch-error', (e) => showError(e.detail.reason));
```

The events reach your page only when the manifest declares the capability `host-events`.

An event is a notification: the state stays with GA Promo, your page reacts to it.

### Launching a game from a widget click

With the launch mode `sdk-modal` (§4.1) the SDK launches the game itself: your page writes no
launch code. You set up three things:

```html
<script>
  // The operator JWT of the signed-in player (§3.2). Called for every launch.
  window.LudariumConfig = { tokenProvider: () => auth.getOperatorToken() };
</script>
<script src="https://ludarium.rexplay.site/resolver.js" data-client-id="<client_id>" async></script>
```

1. **The script.** `resolver.js` with your `data-client-id`, as in §2.1.
2. **The token.** `tokenProvider` returns the operator JWT (§3.2.1). The token is the only proof of
   who the player is: the widget never gives your page a user id.
3. **The place.** The manifest's `mount.selector` puts the widget where you need it (§2.1), with no
   placeholder on your page.

On a click on a game, the SDK:

1. takes a fresh token from `tokenProvider` (for a `real` launch; a `demo` launch needs none);
2. calls `POST https://<api host>/public/v1/embed/launch` (§4.3): `real` with `operatorToken`, `demo`
   without a token;
3. shows the game in its own full-screen modal over your page; the player closes it with the cross
   or `Esc`;
4. raises `ig:game-launch` (§4.2).

If there is no token, or GA answers `401`, the SDK raises `ig:login-required` with
`{reason: 'launch'}` on your page: show your sign-in. A `real` launch is never downgraded to `demo`.
Any other failure raises `ig:game-launch-error` (§4.3).

Limits:

- the events reach your page only when the manifest declares `host-events`;
- with the manifest setting `isolation: 'iframe'` a `real` launch for now gives
  `ig:login-required`; `demo` works.

What your side does:

- Issue the operator token (§3.2). Optional `ext_param` in it carries your context (SoftGaming); it
  reaches your wallet calls as `i_extparam` and is read only from the signed token.
- Accept the wallet callbacks `bet` and `win` for this player (Operator API v1 or v2).

Nothing else: no `fetch` to `embed/launch`, no iframe, no popup.

The modes `redirect`, `popup` and `host-handled` stay (§4.1). With `host-handled` your page launches
the game itself, as in §4.3.

What happens at GA and your wallet on a `real` launch:

1. GA checks the signature of `operatorToken` and takes the player from it (`external_player_id`).
2. GA calls your wallet once, with that exact token string as `launch_token` (`Authenticate`, or
   `GetBalance` when your wallet has no `Authenticate`). Your wallet validates the token. If it
   refuses, the launch fails with `401` `login_required` and no session opens.
3. Every bet, win and refund of the session reaches your wallet as a wallet call for this player, with the
   same `launch_token`.

This needs your wallet connected (Operator API v1 or v2) and the `embed_launch_operator_token`
setting turned on for your operator; both are in §4.3. A game with `has_demo: true` also launches
with `mode: "demo"` and no token. All fields, statuses and the demo rules are in §4.3.

## 2.4 Single-page sites

Remove the placeholder from the page as you normally do: the widget stops its timers and
connections. Put it back and the widget is rendered again, as many times as it happens. Moving the
element within the page does not remove the widget.

## 2.5 When something fails

A failure on our side never breaks your page:

| What happened | What the player sees |
| :--- | :--- |
| the configuration could not be loaded | the placeholders stay empty |
| one widget failed to load or to start | that placeholder stays empty; the others work |
| the placeholder names a slot or placement we have not configured | that placeholder stays empty |
| the CSS selector of a widget (§2.1) finds no element, or is not valid CSS | the widget is not shown; the page is untouched |
| your token endpoint failed or took longer than 1.5 s | the widgets show what a guest sees (§3.4) |
| `collection-event`, `event-page`, `in-game` or a promo banner has no campaign set | the widget says the campaign is not set up |
| the campaign is unknown or not running | the widget says the event is unavailable |
| `game-sections`/`game-section` has no category to show | the widget says no games are available |
| our API answered an error | a short message inside the widget; `collection-event` adds a retry button |

`game-sections`/`game-section` show your lobby categories; `collection-event`/`event-page`/`in-game`/
`promo-l`/`promo-m`/`promo-s` show your promo campaigns. Both are set up by your team in the
**Operator Portal**
(`https://operator.game-alligator.com`, DEV sandbox `https://operator.rexplay.site`) — Catalog Engine
for categories (create, add games, publish) and Promo Studio for campaigns (create, set dates, activate).
Nothing to configure on GA's side beyond onboarding; an empty widget almost always means a category or
campaign is still in `draft` there.

A campaign's collection has exactly four rarity tiers, from most to least common (e.g. common, rare,
epic, legendary) — the widgets draw four. Promo Studio starts a new collection with four and refuses
to publish one with any other number, naming the collection's buckets.

## 2.6 Your site's settings

- **Allowed origins.** The widgets call our API from your pages; list every origin of your site in
  the form (`site.origins`) — scheme and host exactly, e.g. `https://www.example-casino.com`.
- **Language.** The widgets take their language from the locale of your installation, published in
  your manifest as `display.locale` (e.g. `en-US`). We set it at onboarding; you change it yourself
  in the Operator Portal: **Embed** → your installation → **Default Locale** → save, then
  **Publish** — pages opened after the publish use the new locale. One placeholder can use another
  locale than the rest of the page with `data-locale`, e.g.
  `<div data-ig-slot="collection-event" data-locale="ru"></div>`. The player token's `locale`
  (§3.2) does not change the widgets' language. `collection-event` shows its texts in Russian for a
  `ru` locale and in English for any other; the other widgets' texts are in English. Item names
  follow the locale when the collection has them in it.
- **Content-Security-Policy**, if your site sends one:

| Directive | Add |
| :--- | :--- |
| `script-src` | the resolver host |
| `connect-src` | the resolver host and the API host |
| `img-src` | the resolver host, the API host, `data:`, and the image hosts of game providers we send you at onboarding |
| `style-src` | `'unsafe-inline'`, `https://fonts.googleapis.com` |
| `font-src` | the resolver host, `https://fonts.gstatic.com` |

Game images come from the API host; some of them redirect to the image host of their provider, and
that list follows your catalogue.

We collect only whether each widget started (widget, placement, error code, versions) — never the
player, the page address or anything the player typed.
