{"doc":"consent","title":"Ask fans for consent","markdown":"# Ask fans for consent\n\nEvery Gamestage game asks a fan before anything records them. The Gamestage\nclient draws the banner for you, stores the answer, and holds every analytics\nand storage call until the fan says yes. `gamestage deploy` refuses a game that\nwould record fans without asking.\n\n## Quickstart\n\nName who is asking and link your privacy policy, in `gamestage.settings.json`\nbeside `gamestage.yaml`. A deploy writes both into Studio:\n\n```json\n{\n  \"consent_brand_name\": \"Arsenal\",\n  \"privacy_policy_url\": \"https://www.arsenal.com/privacy-policy\"\n}\n```\n\nThen load the client and start the game. The banner appears on its own the\nfirst time a fan opens the game:\n\n```js\nconst game = await gamestage.start({\n  consentBanner: { settingsIn: document.querySelector(\"footer\") },\n});\n```\n\n`settingsIn` puts a \"Privacy settings\" button in your footer, so a fan can\nchange their mind later. Leave it out and the banner pins a small \"Privacy\nsettings\" pill in the bottom corner of the screen, outside the page's layout,\nso a game sized to fit the screen does not gain a scrollbar. Pass `null` only\nwhen your own control calls `game.consent.openSettings()`.\n\nCheck it the way a new fan would meet it:\n\n```sh\nnpx gamestage@latest verify\n```\n\nThe `consent` line passes when a consent control appears before anything is\nsent, and nothing is sent after the fan says no. The `consent-owner` line\npasses when the brand name and the privacy policy link are both set.\n\n## What a fan sees\n\nA compact card floating near the foot of the screen, clear of the game's own\npill nav and pinned main button: two lines naming who is asking and why, an\nunderlined **Privacy policy** link, **Accept** (filled in the game's accent)\nand **Reject** (outlined) at the same size side by side, a small **Manage\npreferences** link for choosing one category at a time, and a **×** in the\ncorner. It takes the game's colours and font from its Studio palette when\nthey are readable together. The game stays playable underneath it.\n\n**×** closes the card without answering. Nothing is recorded, nothing is\nstored, and the card comes back on the next visit.\n\nThe fan is asked once per game, on each device, until the producer changes the\nprivacy policy version in Studio.\n\n| Category | What it allows | On by default |\n| --- | --- | --- |\n| `necessary` | Playing the game: a fan's session and their round in progress. | Always on |\n| `analytics` | Monterosa Analytics, and PostHog or Google if the creator turns them on. | No |\n| `marketing` | Offers and news from the game's owner. | No |\n| `functional` | Remembering a fan's preferences on the device. | No |\n\nThese are the Monterosa SDK's own category names, so an app's answer needs no\ntranslating on the way in.\n\n## Change the words\n\nEvery word on the banner is the producer's, in Studio under **Wording**. An\nempty field shows the default.\n\n| Studio setting | Default |\n| --- | --- |\n| `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…\". |\n| `consent_title` | Your privacy |\n| `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. |\n| `consent_accept_label` | Accept |\n| `consent_reject_label` | Reject |\n| `consent_choose_label` | Manage preferences |\n| `consent_save_label` | Save my choices |\n| `consent_analytics_label` | Measure how the game is played |\n| `consent_marketing_label` | Offers and news from the game's owner |\n| `consent_functional_label` | Remember my preferences |\n| `consent_settings_label` | Privacy settings |\n| `privacy_policy_url` | None. Required, and must be https: `verify` fails and `deploy` refuses while it is empty. |\n| `consent_policy_version` | `1`. Change it when your privacy policy changes, and every fan is asked again. |\n\n## Pass consent in from an app that embeds the game\n\nA club app that embeds a game has usually asked its fan already. Pass that\nanswer in and the game never shows its own banner. A host's answer always beats\nthe game's.\n\nWith the Monterosa SDK, set it in the host page. It reaches the game on its own:\n\n```js\nimport { setConsentState } from \"@monterosa/sdk-consent-kit\";\n\nsetConsentState({ necessary: true, analytics: true, marketing: false, functional: true });\n```\n\nWithout the SDK, post it into the game's frame:\n\n```js\nframe.contentWindow.postMessage(\n  { type: \"gamestage:consent\", categories: { analytics: true, marketing: false, functional: true } },\n  new URL(frame.src).origin,\n);\n```\n\nOnly a message from the frame's own parent is read. A missing category is a no.\n\n## Read the answer in your game\n\n`game.consent` holds the answer. You rarely need it: the client's gates already\nread it.\n\n| Member | Returns | What it is for |\n| --- | --- | --- |\n| `record()` | `ConsentRecord` or `null` | The full answer: `categories`, `source` (`host`, `fan` or `default`), `decidedAt`, `policyVersion`, `jurisdiction`. `null` while nobody has answered. |\n| `needed()` | `boolean` | True when the game must still ask. |\n| `hostDecides()` | `boolean` | True when the embedding app answered. Hide your own privacy link when it is. |\n| `decide(categories)` | `boolean` | Records the fan's answer. False where the host decides. |\n| `openSettings()` | nothing | Opens the choices again. |\n| `onChange(listener)` | an unsubscribe function | Called whenever any authority changes the answer. |\n\n## Draw your own consent screen\n\nA Gamestage UI game uses the `consent` component, which reads and writes the\nsame record. Turn the client's banner off so there are not two:\n\n```js\nconst game = await gamestage.start({ consentBanner: false });\n```\n\nAny screen of your own must carry `data-gs-component=\"consent\"`, offer reject\nas plainly as accept, and call `game.consent.decide`. `verify` and `deploy`\ncheck it by loading the game as a new fan, so a page that turns the banner off\nand asks nobody is refused.\n\n## When the check fails\n\n| `verify` or `deploy` says | What to do |\n| --- | --- |\n| 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. |\n| The banner would not say who is asking | Add `consent_brand_name` and `privacy_policy_url` to `gamestage.settings.json`. |\n| 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()`. |\n| The fan said no and the page still sent analytics | Something in the page tracks without the client. Remove it. |\n| 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. |\n\nIf your game records nothing about fans at all, set\n`identity.consent.analytics_requires_consent: false` in `gamestage.yaml` and no\nbanner is required. That is your declaration to answer for.\n"}