Follow this order: site configuration → backend launch → iframe or module → wallet → acceptance checks. Replace example domains and `SITE_SLUG` with your deployment values.

## 1. Configure the site and server key
Your administrator sets the site slug, exact allowed origins, game/buy-in limits and wallet mode. Keep the key only on your backend. Use launch scope for sessions and read for catalog/reports. wallet is only for transfer-wallet mutations; seamless callbacks use a separate signing secret.

Set `SLOT_BATTLES_ORIGIN` (public provider origin), `SLOT_BATTLES_KEY` (server key), and `SLOT_BATTLES_SITE_ORIGIN` (allowed customer origin) on your server. `ADMIN_ORIGIN` / `OPERATOR_ORIGIN` are panel addresses; do not send game API keys to them.

## 2. Create a launch for an authenticated member
This `Node.js` 22.16+ module sits beside the SDK. Your `POST /integration/launch` endpoint must validate the site session and CSRF token, resolve the member on the server and return `createLaunchForMember` as JSON with Cache-Control: no-store. Never trust browser-supplied member identity or balance.

```javascript launch-service.mjs
import { SlotBattlesClient } from './slot-battles-sdk.mjs';

const client = new SlotBattlesClient({
  origin: process.env.SLOT_BATTLES_ORIGIN,
  apiKey: process.env.SLOT_BATTLES_KEY
});

export async function createLaunchForMember(member, mode = 'iframe') {
  if (!member?.id) throw new Error('Authenticated member required.');

  return client.launch({
    externalId: String(member.id),
    name: member.displayName,
    siteUsername: member.username,
    origin: process.env.SLOT_BATTLES_SITE_ORIGIN,
    route: 'lobby',
    mode
  });
}
```

member.id is the immutable site member key. member.username is the original site login, visible only to authorized panels/server APIs. `member.displayName` supplies a new account name when no alias prefix is configured. The same tenant + `externalId` always reuses the same Arena Battle member. Launch does not create another opening balance.

## 3. Embed an iframe
Your page needs a #game container and a csrf-token meta element supplied by your authenticated backend. Run this code from an external type="module" script. Your launch endpoint uses mode=iframe. Replace provider.example with your public provider origin.

```javascript mount-arena.mjs
import { mountSlotBattles } from './slot-battles-browser.mjs';

const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content;
if (!csrfToken) throw new Error('CSRF token is missing.');

const response = await fetch('/integration/launch', {
  method: 'POST',
  credentials: 'same-origin',
  headers: { 'X-CSRF-Token': csrfToken }
});
if (!response.ok) throw new Error('Launch request failed.');

const launch = await response.json();
const game = mountSlotBattles({
  container: document.querySelector('#game'),
  providerOrigin: 'https://provider.example',
  launch
});

// Call game.destroy() when your page or component is removed.
```

A ticket expires after 90 seconds and is single-use. Request a fresh launch each time. Give the iframe container a visible height. Audible autoplay depends on browser user-interaction rules.

## 4. Host the module client
Extract the entire slot-battles-client.zip into your site's `/arena/` directory. Deploy CSS, fonts, artwork and JavaScript together. Update `slot-battles-config.js` with the following configuration; it must load before `integration-context.js`.

```javascript slot-battles-config.js
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content;
if (!csrfToken) throw new Error('CSRF token is missing.');

window.SlotBattlesConfig = {
  apiBase: 'https://provider.example/play/SITE_SLUG/',
  launchEndpoint: '/integration/launch',
  launchMethod: 'POST',
  launchCsrfToken: csrfToken,
  shareBase: '/arena/'
};
```

`SITE_SLUG` is your assigned site code. Your server must supply the csrf-token meta value from the current member session. `POST /integration/launch` stays on the customer origin, uses mode=module and returns the provider response containing `launchTicket`, `apiBase` and `expiresAt`. This snippet is not a replacement for your authentication backend.

Allow provider HTTPS/WSS sources in connect-src, img-src and media-src. An iframe also requires frame-src. The provider's frame-ancestors policy only allows registered site origins.

## Player interface language

The player interface supports Turkish and English. Selection priority is a valid `?lang=tr` or `?lang=en` URL parameter, the player's saved manual choice, optional `window.SlotBattlesConfig.locale`, the browser's ordered language preferences, then Turkish. Regional tags such as `en-GB` and `tr-TR` are recognized. The footer language menu saves a manual choice; Automatic removes it. Automatic detection does not store a permanent preference. This uses browser preferences, not IP geolocation.

For modules, set `locale: 'en'` or `locale: 'tr'` in the public `slot-battles-config.js` object when an operator default is wanted. Omit it to follow the browser. This setting changes interface copy only, not authentication or API permissions. Update the full client ZIP to receive these assets. Existing iframe installations receive the updated player interface from the provider.

Provider games inside the shared stream retain their existing language. Interface language does not alter the room, the selected game, money amounts or currency. Player names and chat messages are not translated. The corporate website separately supports English, Turkish, Armenian, Russian and German, with English as its fallback.

## 5. Use the existing site wallet
For matching site and game balances, follow SEAMLESS-WALLET.en.txt and WALLET-CONFIG.en.txt. Your ledger owns the single spendable balance. reserve locks funds, refund releases that hold, and settle consumes the hold and credits the prize. Do not debit the entry cost again at settlement.

Amounts use integer minor units: 20000 = TRY 200.00. Repeating the same transaction ID and content must never create a second financial effect. After a timeout, reconcile using the original `requestId` and durable receipt. Transfer mode remains separate; do not combine it with seamless mutations on the same account.

## 6. Sharing and wallet events
The iframe helper supports `onShare({route})`, `onIdentity({player})` and `onWalletChange()`. A share URL points to your own page carrying watch/ID or join/CODE. Each visitor obtains a fresh launch for their own session. Never include keys, player tokens or launch tickets in share links.

`onWalletChange` is only a change hint. Fetch the authoritative balance from your authenticated backend; do not accept a balance from a browser message. Module `shareBase` must stay on the customer origin. Call `game.destroy()` when removing the page/component.

## 7. Reference apps and acceptance
`examples/operator-seamless.cjs` is the working seamless reference. `examples/operator-authenticated.cjs` demonstrates transfer mode. These files stay on the backend, outside the web root. Local test accounts do not replace your identity system. Server SDKs, signing secrets and keys never enter the browser bundle.

- Relaunch the same member and verify stable identity and balance.
- A second launch must revoke the previous player session.
- Reserve exactly once; repeating the request must not debit twice.
- Cancel a waiting room and refund its hold exactly once.
- Distinguish completed from `settlementStatus`=complete; pending prizes are not credited yet.
- Verify iframe/module on desktop, physical mobile and external networks separately.

## Packages and result authority
Downloads contain public client code and examples without keys. Possession of the files alone grants no API rights. Revoke and rotate any leaked key; use minimum scopes, IP restrictions and exact origins.

The current game adapters use public demo sessions. Working wallet accounting does not make public demo observations an authenticated real-money result source. See RESULT-AUTHORITY.en.txt for result and deployment boundaries.

## Period battles — site reporting

Track daily, weekly and monthly entries in Operator / Period battles. Only your site's participants, private member identities, amounts and prizes are visible; ranks are computed across all sites. Staff require `rooms.read`, `players.read` and `finance.read`. From your backend use `GET /provider/v1/competitions?kind=daily&limit=50&offset=0` (`read` scope) or `client.competitions({kind: 'daily'})`. Currency is TRY and amounts are minor units. Paid and pending awards are separate. Follow pagination, event-context and package-update guidance in [PERIOD-WARS.en.txt](PERIOD-WARS.en.txt). Existing iframe integration remains valid; modules require the updated client ZIP.
