{"doc":"theming","title":"Theming","markdown":"# Theming\n\nA producer sets your game's colours in Studio, and they reach the page as CSS\ncustom properties named `--gs-<role>`. A game that reads them takes the\nproducer's brand with no code change and no redeploy. The reference scaffolds\nare wired exactly this way, so read one alongside this chapter for a worked\nexample; this is how to do the same in a game of your own.\n\n## Start the palette in Studio\n\nPut the colours your game ships with in `gamestage.settings.json`, beside\n`gamestage.yaml`. A deploy writes each one into your game's Colour Palette in\nStudio where that field is empty, so a producer opens Studio to your real\ncolours rather than a column of blank boxes, and changes them from there.\n\n```json\n{\n  \"primary_colour\": \"#1D428A\",\n  \"css_variables_backgroundMainColour\": \"#0B1620\",\n  \"css_variables_onBackgroundColour\": \"#F4F2F2\",\n  \"css_variables_highlightMainColour\": \"#F2C94C\"\n}\n```\n\nA deploy never overwrites a colour a producer has set. `gamestage create` writes\nthis file for you from the scaffold's own CSS, and `gamestage verify` fails a\ngame whose CSS reads a colour the file leaves out, naming it.\n\n## How the palette reaches the page\n\nThe producer's colour palette is a project setting. The client reads it and\nsets each colour on the document root as a `--gs-*` custom property, so your CSS\ncan read `var(--gs-backgroundMainColour)` and the rest. Nothing per-colour is\nwired by hand: a colour a producer has not set is simply absent, and your CSS\nfalls back to the value you shipped.\n\n## The roles, and where each one goes\n\nThe palette is organised as Material 3 roles. Each role is a set of paired\ncolours: a surface and the `on` colour that sits legibly on it, plus a container\nvariant for quieter surfaces. Map your game's parts to the roles like this.\n\n| Your game's surface | Role | Background | Text on it |\n| --- | --- | --- | --- |\n| Page / app background | Background (container) | `--gs-backgroundContainerColour` | `--gs-onBackgroundContainerColour` |\n| Cards, panels, inputs | Background (element) | `--gs-backgroundMainColour` | `--gs-onBackgroundColour` |\n| Option / choice buttons | Tertiary | `--gs-tertiaryContainerColour` | `--gs-onTertiaryContainerColour` |\n| Primary action, selected state | Highlight | `--gs-highlightMainColour` | `--gs-onHighlightColour` |\n| Secondary action | Secondary | `--gs-secondaryContainerColour` | `--gs-onSecondaryContainerColour` |\n| Header / footer chrome | Primary | `--gs-primaryMainColour` | `--gs-onPrimaryColour` |\n| A correct or successful state | Positive | `--gs-positiveContainerColour` | `--gs-onPositiveContainerColour` |\n| A wrong state or an error | Negative | `--gs-negativeContainerColour` | `--gs-onNegativeContainerColour` |\n\nEach role also carries a `MainColour` for a solid fill or a border, and a\n`VariantColour` for a border or a subtle overlay, e.g. `--gs-positiveMainColour`\nfor the border of a correct answer whose fill is `--gs-positiveContainerColour`.\n\n## The two rules\n\n### Pair a surface with its own `on` colour\n\nThe role colours are designed in pairs. A surface from one role must take its\ntext from the same role, or a producer whose palette is legible everywhere can\nstill end up with white text on a pale tile. Read a background and its text\ntogether:\n\n```css\nbutton.option {\n  background: var(--gs-tertiaryContainerColour);\n  color: var(--gs-onTertiaryContainerColour);\n}\n```\n\nNever mix a surface from one role with the `on` colour of another.\n\n### Keep your colours in Studio, not in the page\n\nRead every variable with no fallback value. Put each colour in\n`gamestage.settings.json` instead; a deploy writes it into Studio, and that is\nthe only place it lives.\n\n```css\n:root {\n  --bg: var(--gs-backgroundContainerColour);\n  --text: var(--gs-onBackgroundContainerColour);\n}\nbutton.primary {\n  background: var(--gs-highlightMainColour);\n  color: var(--gs-onHighlightColour);\n}\n```\n\n| Situation | What the fan sees |\n| --- | --- |\n| Studio answers | The producer's colours. |\n| Returning fan, Studio slow or down | The colours their device remembered from the last visit. |\n| First visit, Studio down | The library's \"Can't load the game right now\" screen with Try again. |\n| `gamestage dev --serve` | The colours in `gamestage.settings.json`. |\n\nA fallback colour in the page is a second copy that drifts: a producer changes\na colour in Studio and fans still see the old one first. `gamestage verify`\nfails a page that reads a colour with a literal fallback like this, and\n`gamestage deploy` refuses it before uploading.\n\n## The title's own typeface\n\nA producer can set the game's name in its own typeface from Studio's Brand\nsettings, rather than the page's default font. Mark the element that shows the\nname:\n\n```html\n<h1 data-gamestage-title>Wage Bill</h1>\n```\n\nNo element marked this way, and the client styles the page's first heading\ninstead. Either way, nothing changes until a producer fills in a setting: an\nempty field leaves your page's own title face exactly as you built it, and\n`gamestage verify` does not ask you to fill any of them in.\n\n| Studio setting | What it changes | Left empty |\n| --- | --- | --- |\n| Title typeface | The Google Font the name is set in, e.g. \"Bebas Neue\" | The page's own font |\n| Title weight | A number from 100 to 900 (400 regular, 700 bold) | The typeface's heaviest weight |\n| Title letter spacing | A fraction of the type size, e.g. `0.08` to spread it out, `-0.02` to tighten it | The typeface's own spacing |\n| Title case | Type \"upper\" to show the name in capitals whatever case it is typed in | As typed |\n| Title colour | The colour of the name | The page's own text colour |\n\nA typeface not on Studio's list is ignored and your page's own font stands, so\na typo can never break a live game. The client loads only the weight actually\nused, after the page has drawn, so the title's own font never delays first\npaint.\n\n**Logo text and its accent colour are separate settings**, for a name styled as\na logo rather than read as a word. \"Logo text\" replaces the game's name on the\npage's title and on its [intro screen](/docs/layout-and-loading#intro-screen)\nonly: the browser tab, the share text and a screen reader still read the game's\nreal name. Put the characters to pick out in square brackets, `WAGE[$]`, and\n\"Logo accent colour\" colours them; leave the accent colour empty and it uses\nthe title colour instead.\n\n## What not to theme\n\nLeave a colour fixed when it means something the brand does not decide. A\nfootball pitch is green and a target marker is gold whatever the brand, so those\nstay as hardcoded accents rather than reading a role. Muted text and hairline\nborders have no role of their own; derive them from the ink colour so they track\nthe palette, e.g. `color-mix(in srgb, var(--gs-onBackgroundColour, #232020) 66%, var(--gs-backgroundContainerColour, #FCFBFB))`.\n"}