# Ask fans for consent

Every Gamestage game asks a fan before anything records them. The Gamestage
client draws the banner for you, stores the answer, and holds every analytics
and storage call until the fan says yes. `gamestage deploy` refuses a game that
would record fans without asking.

## Quickstart

Name who is asking and link your privacy policy, in `gamestage.settings.json`
beside `gamestage.yaml`. A deploy writes both into Studio:

```json
{
  "consent_brand_name": "Arsenal",
  "privacy_policy_url": "https://www.arsenal.com/privacy-policy"
}
```

Then load the client and start the game. The banner appears on its own the
first time a fan opens the game:

```js
const game = await gamestage.start({
  consentBanner: { settingsIn: document.querySelector("footer") },
});
```

`settingsIn` puts a "Privacy settings" button in your footer, so a fan can
change their mind later. Leave it out and the banner pins a small "Privacy
settings" pill in the bottom corner of the screen, outside the page's layout,
so a game sized to fit the screen does not gain a scrollbar. Pass `null` only
when your own control calls `game.consent.openSettings()`.

Check it the way a new fan would meet it:

```sh
npx gamestage@latest verify
```

The `consent` line passes when a consent control appears before anything is
sent, and nothing is sent after the fan says no. The `consent-owner` line
passes when the brand name and the privacy policy link are both set.

## What a fan sees

A compact card floating near the foot of the screen, clear of the game's own
pill nav and pinned main button: two lines naming who is asking and why, an
underlined **Privacy policy** link, **Accept** (filled in the game's accent)
and **Reject** (outlined) at the same size side by side, a small **Manage
preferences** link for choosing one category at a time, and a **×** in the
corner. It takes the game's colours and font from its Studio palette when
they are readable together. The game stays playable underneath it.

**×** closes the card without answering. Nothing is recorded, nothing is
stored, and the card comes back on the next visit.

The fan is asked once per game, on each device, until the producer changes the
privacy policy version in Studio.

| Category | What it allows | On by default |
| --- | --- | --- |
| `necessary` | Playing the game: a fan's session and their round in progress. | Always on |
| `analytics` | Monterosa Analytics, and PostHog or Google if the creator turns them on. | No |
| `marketing` | Offers and news from the game's owner. | No |
| `functional` | Remembering a fan's preferences on the device. | No |

These are the Monterosa SDK's own category names, so an app's answer needs no
translating on the way in.

## Change the words

Every word on the banner is the producer's, in Studio under **Wording**. An
empty field shows the default.

| Studio setting | Default |
| --- | --- |
| `consent_brand_name` | None. Required: `verify` fails and `deploy` refuses while it is empty. Name your own brand, the one fans know: the banner reads "Arsenal and Monterosa, who run this game…". Only Monterosa's own sample games name Monterosa, and the banner then reads "Monterosa, who runs this game…". |
| `consent_title` | Your privacy |
| `consent_detail` | {brand} would like to record analytics on how you play, so we can improve the game. We only do this if you agree. Data needed to run the game, such as your scores, is processed on the basis of legitimate interests. |
| `consent_accept_label` | Accept |
| `consent_reject_label` | Reject |
| `consent_choose_label` | Manage preferences |
| `consent_save_label` | Save my choices |
| `consent_analytics_label` | Measure how the game is played |
| `consent_marketing_label` | Offers and news from the game's owner |
| `consent_functional_label` | Remember my preferences |
| `consent_settings_label` | Privacy settings |
| `privacy_policy_url` | None. Required, and must be https: `verify` fails and `deploy` refuses while it is empty. |
| `consent_policy_version` | `1`. Change it when your privacy policy changes, and every fan is asked again. |

## Pass consent in from an app that embeds the game

A club app that embeds a game has usually asked its fan already. Pass that
answer in and the game never shows its own banner. A host's answer always beats
the game's.

With the Monterosa SDK, set it in the host page. It reaches the game on its own:

```js
import { setConsentState } from "@monterosa/sdk-consent-kit";

setConsentState({ necessary: true, analytics: true, marketing: false, functional: true });
```

Without the SDK, post it into the game's frame:

```js
frame.contentWindow.postMessage(
  { type: "gamestage:consent", categories: { analytics: true, marketing: false, functional: true } },
  new URL(frame.src).origin,
);
```

Only a message from the frame's own parent is read. A missing category is a no.

## Read the answer in your game

`game.consent` holds the answer. You rarely need it: the client's gates already
read it.

| Member | Returns | What it is for |
| --- | --- | --- |
| `record()` | `ConsentRecord` or `null` | The full answer: `categories`, `source` (`host`, `fan` or `default`), `decidedAt`, `policyVersion`, `jurisdiction`. `null` while nobody has answered. |
| `needed()` | `boolean` | True when the game must still ask. |
| `hostDecides()` | `boolean` | True when the embedding app answered. Hide your own privacy link when it is. |
| `decide(categories)` | `boolean` | Records the fan's answer. False where the host decides. |
| `openSettings()` | nothing | Opens the choices again. |
| `onChange(listener)` | an unsubscribe function | Called whenever any authority changes the answer. |

## Draw your own consent screen

A Gamestage UI game uses the `consent` component, which reads and writes the
same record. Turn the client's banner off so there are not two:

```js
const game = await gamestage.start({ consentBanner: false });
```

Any screen of your own must carry `data-gs-component="consent"`, offer reject
as plainly as accept, and call `game.consent.decide`. `verify` and `deploy`
check it by loading the game as a new fan, so a page that turns the banner off
and asks nobody is refused.

## When the check fails

| `verify` or `deploy` says | What to do |
| --- | --- |
| The page sent analytics before the fan answered | Send events through `game.track`, which waits for consent. Remove any tracking script loaded directly in the page. |
| The banner would not say who is asking | Add `consent_brand_name` and `privacy_policy_url` to `gamestage.settings.json`. |
| No consent banner appeared | Load the Gamestage client and leave `consentBanner` on, or draw the `consent` component. Check the console for an error that stopped `gamestage.start()`. |
| The fan said no and the page still sent analytics | Something in the page tracks without the client. Remove it. |
| Consent checked by reading the page, not in a browser | No Chrome on this machine. Install Chrome, or set `GAMESTAGE_CHROME`, for the full check. |

If your game records nothing about fans at all, set
`identity.consent.analytics_requires_consent: false` in `gamestage.yaml` and no
banner is required. That is your declaration to answer for.
