# Keystone in the player's game: screens, HUD and themes

> What the client half draws from 0.3: the hub on K, Keystone Settings, themes as a small subset of CSS, a HUD that covers nothing, notices, a modpack's look for the game's own screens, and the public ClientToolkit for addons.

Source: https://stygion.eu/docs/keystone/client
Updated: 2026-10-25

From 0.3 the client half - the same jar in the player's `mods/`, see [the client half](/docs/keystone/client-half) for what the client and the server say - draws real screens and a HUD. The module `client` on the server is **off by default**. A player without the client half loses nothing: every screen has its chat or chest version. Nothing here depends on STYGION.

## The hub (K)

**K** opens the player's hub: a tile for each part of Keystone this server has, each with a live number — **quests and contracts** (done today, when they reset; `/quests`, `/jobs`), **lists** (`/lists`), **market and crates** (your keys and coins; `/market`, `/crate`, the exchange), **badges and news** (your badges, today's vote and streak, `/changelog`). A tile is there only when the server has that part on for this player: the hub reads the server's own command list, so a module that is off, or a permission the player lacks, simply leaves the tile out. Badges come from the server's own `badges` module (its rules, or the owner's own source), never only from STYGION. The numbers are the HUD values the server already sends; a button runs the server's command, which opens its screen. The gear in the corner opens Keystone Settings.

## Keystone Settings

Opened with the hub's gear, the small **Keystone button** on the title and pause screens, `/keystone settings`, or the Config button in the mod list; their own key is unbound until the player binds one in Controls. Tabs **Chat, HUD, Ultimine, Notifications and Theme**, each with a live preview. Everything is kept on the player's own machine (`config/keystone-client.json`) and never sent anywhere; what the server locked is shown as locked.

The menu button (20×20, a hexagon with the keystone) places itself beside the screen's column of buttons, on the first spot no widget of the game or another mod takes (read after the screen is built), else in a free corner. A pack moves or hides it in its file: `"menu-button": {"title": "auto", "pause": "auto"}` — `auto`, `off`, or `x,y` in GUI pixels (negative from the right or bottom).

## Themes

A theme is **data, never code**: a small subset of CSS that the client draws natively - no browser, no script - so a shared theme can change colours and nothing else.

```
:root { --accent: #e8913a; }
panel { background: #0f0d0b / 88%; border: 1px solid #5c2e14; radius: 3px; shadow: 2px 2px 0 #000000; }
tab:selected { background: var(--accent); color: #24110a; }
badge[legend] { background: linear-gradient(90deg, #a8204a, #ffca36); }
```

A selector is a word with optional `.part`, `[key]` and `:state`; a property falls back from the most specific selector to the bare word, then to the theme's default. Anything not understood is skipped, never an error; a file is read up to 16 KB.

- Four come with the jar: **Night** (the default), **STYGION**, **Mist** and **High contrast**. A server adds its own as `config/keystone/themes/<name>.kcss`.
- `theme.default` is the look a new player starts with; `theme.offered` the themes a player may pick.
- `theme.own` (on) lets a player edit a theme of their own and share it as a code starting `KS1:`, which another player pastes in Settings.

## The HUD

A small panel that covers nothing. **By default only the quests are shown**, in the top-right corner: a heading such as `TODAY · 2 / 3` with when they reset, at most three quests (`hud.quests.rows`), the count on the right and one thin bar under the first unfinished quest. A quest finished while you play says *done* in green for a few seconds and folds away. A panel is never wider than `hud.width` percent of the screen (22 by default); a long name wraps onto a second line, it is never cut off. The HUD is about the same size on the screen at every GUI scale.

The other elements are off until the player turns them on (Keystone Settings → HUD): **position** (coordinates, biome, facing, time, world, whose claim), **server** (ping, TPS, players, the next restart, today's vote and streak), **player** (coins, keys, rank and time to the next, badges), **lists** (pinned lists) and **panels** (the owner's own, [menus](/docs/keystone/menus)). The player drags, scales and hides what the server did not lock (Settings → HUD → Move panels).

**It keeps out of the way.** Each panel takes the first free place, starting at its own corner and sliding along the edge, then trying the other corners, and moves again when something appears: the effect icons, boss bars, Jade's tooltip, the chat (more of it while it is open), the scoreboard, the hotbar, the ultimine panel, and the **JourneyMap** and **FTB Chunks** minimaps where their own settings put them (read from their config files; neither mod is needed). For any other mod, `hud.avoid` names a place to keep clear: a corner and a size in GUI pixels or percent of the screen's height, optionally moved in from the corner — `top-right 120x90`, `top-right 120x90+0+20`, `top-left 30%x35%`. A pack can add the same lines to its own file (below). A panel the player dragged stays exactly where they put it.

`client.toml`: `hud.enabled`, `hud.every` (2 s; values are sent only when they changed), `hud.elements` (what exists), `hud.shown` (what a new player sees; `quests`), `hud.locked` (what may not be moved or hidden), `hud.anchor.<element>`, `hud.quests.rows` (3), `hud.width` (22), `hud.avoid`, `hud.position.claim`, `hud.position.biome`. The values and their keys: [HUD values](/docs/keystone/hud-values).

## Notices

Short notices slide in at a corner for a badge, a quest done, a mention, a team list, a job, the restart or a vote. `toasts.kinds` says which kinds the server sends at all; each player picks which they see.

## Chat

`chat.images` (on): pictures and GIFs posted on Discord show in the game chat, fetched by the server from Discord's own addresses only, never by the players. `chat.heads` (on): chat lines show the speaker's head.

## A modpack's look for the game's own screens

`config/keystone-pack.json` belongs to the **pack**, not the player, and changes nothing when missing:

```json
{"hud": {
  "background": "#E61A0C06", "border": "#FF5C2E14", "title": "#FFFFCA36", "text": "#FFF8E9CC",
  "muted": "#FFB9A184", "bar": "#FFE8913A", "track": "#FF2F170A", "done": "#FFA9C25A",
  "avoid": ["top-left 120x60"]
 },
 "menu-button": {"title": "auto", "pause": "auto"},
 "gui": {
  "container-label-color": "#F8E9CC",
  "container-label-screens": ["com.example.SomeScreen"],
  "tooltip": {"background": "#F01A0C06", "border-top": "#FFFFCA36", "border-bottom": "#FFA8204A"}
}}
```

Colours are written the way the game writes them: `#AARRGGBB` (alpha first) or `#RRGGBB`. `hud` sets the HUD's colours over the player's theme (each key optional) and adds places to keep clear; `gui` colours the labels of container screens (every screen of the game's own, and the classes listed) and tooltips.

The label colour applies to the game's own container screens and to the screen classes listed, so a mod's screen with light textures keeps its dark labels. Tooltip colours apply on Minecraft 1.21.x; 26.x draws tooltips from sprites, which a resource pack retextures instead. Other mods' screens are never restyled.

## For addons: `ClientToolkit`

`ModuleContext.client()` gives a module or an addon the client half as data, never code:

- `send(player, topic, data)` - data under a topic (lower-case words with dots), at most 512 KiB.
- `open(player, screen, data)` - one of the client's screens (`quests`, `market`, `crate`, `changelog`, `menu`, `lists`, `jobs`, `settings`, `vote`); returns false for a player without the client half, so the caller answers in chat.
- `notify(player, kind, title, text, icon)` - a notice.
- `onAction(topic, handler)` - what client halves send back, already size-limited (8 KiB) and parsed; every value is to be checked like a command argument.
- `hud(key, value)` - a HUD value, asked every refresh on the server thread; it must be a cheap look-up.

Every call is a no-op for a player without the client half. `Player.runCommand` runs a command as a player with their own permissions. `GET /v1/client/hud` (scope `players.read`) lists the HUD keys and the layout the server suggests.
