# Player settings

Every player can restyle their own HUD from the settings menu (`/settings`).
`editable/shared/js.json` decides what a player starts with before they change anything, adds your Discord link, logo and key hints, and describes the settings menu itself.

:::hint{type="danger" title="js.json is compiled into the UI"}
The React UI reads `js.json` when it is **built**, not when the server starts.
Editing the file and restarting the resource changes nothing until you rebuild the UI:

```bash
cd koja-hud/web
pnpm install
pnpm build
```

This regenerates `web/build`, which is what players download.
Details, and how to build without pnpm installed globally, are in [UI development](/free/hud/ui-development#building-the-ui).
:::

The file has three parts:

| Key | What it holds |
| --- | --- |
| `default_settings` | What a new player's HUD looks like |
| `ui` | Server branding: Discord, logo, key hints, built-in binds |
| `options_settings` | Which options appear in the settings menu, grouped into windows |

## Where player choices are stored

A player's choices are saved in their own game client (the NUI browser storage), not on the server.
They follow the player's PC, not their character, and survive server restarts and resource updates.

What a player sees is your `default_settings` with their own changes laid on top.
So when you change a default, everybody who has not touched that particular option gets the new value; players who changed it keep theirs.

Two defaults come from `config.lua` instead and win over `js.json`:

| Setting | Comes from |
| --- | --- |
| `minimap.visibility` | `KOJA.HideMinimapOnFoot` — `true` gives `vehicle`, `false` gives `always` |
| `killfeed.enabled`, `killfeed.anyDistance`, `killfeed.distance` | `KOJA.Killfeed` |

## `default_settings`

### HUD, status and speedometer

```json title="editable/shared/js.json"
"hud": { "visibility": true },
"status": {
    "visibility": true,
    "style": "default",
    "colors": { "health": "#ff4444", "shield": "#e2e2e2", "food": "#d8932b", "water": "#60b7ff",
                "stamina": "#8dd82b", "oxygen": "#4b7dad", "voice": "#ffb74d", "stress": "#ff6666" },
    "percent": { "health": { "value": 50, "action": "below", "visibility": true }, "...": {} },
    "enabled": { "health": true, "shield": true, "food": true, "water": true,
                 "stamina": true, "oxygen": true, "voice": true, "stress": true },
    "borderradius": 0.5,
    "scale": "1.5"
},
"carhud": {
    "visibility": true,
    "style": "gauge",
    "metertype": "kmh",
    "elements": { "gear": true, "fuel": true, "nitro": true, "seatbelt": true, "engine": true,
                  "rpm": false, "engineHealth": false, "lights": false, "indicators": false, "cruise": false }
}
```

| Setting | Values | What it does |
| --- | --- | --- |
| `hud.visibility` | `true` / `false` | The whole HUD |
| `status.visibility` | `true` / `false` | The status bars |
| `status.style` | `default`, `circle`, `echo`, `minimal`, `hexagon` | Status bar style |
| `status.colors.<id>` | hex colour | Colour of each bar |
| `status.enabled.<id>` | `true` / `false` | Show or hide a single bar |
| `status.percent.<id>` | `value`, `action`, `visibility` | Show a bar only above or below a value — see below |
| `status.borderradius` | `0.01`–`1.5` | Corner rounding |
| `status.scale` | `"0.5"`, `"1.5"`, `"3"` | Small, Default, Big. A string, not a number |
| `carhud.visibility` | `true` / `false` | The speedometer |
| `carhud.style` | `gauge`, `default`, `compact`, `minimal`, `pvp` | Speedometer style |
| `carhud.metertype` | `kmh`, `mph` | Speed unit |
| `carhud.elements.<id>` | `true` / `false` | `gear`, `fuel`, `nitro`, `seatbelt`, `engine`, `rpm`, `engineHealth`, `lights`, `indicators`, `cruise` |

The status ids are `health`, `shield` (armour), `food` (hunger), `water` (thirst), `stamina`, `oxygen`, `voice` and `stress`.

**Conditional bars.** `status.percent` can hide a bar until it matters.
`"visibility": true` — the default for every bar — means **always show**, and the rule is ignored.
Set it to `false` and the rule applies: with `"action": "below"` and `"value": 50` the bar shows only while it is under 50%, so hunger appears when the player gets hungry.
`"above"` is the opposite, which suits oxygen and stress.
In the menu this is **Settings → STATUS → Status Percents Settings**: **Show**, **Show if over**, **Show if under**.

### Messages and information

```json title="editable/shared/js.json"
"notify": { "visibility": true, "position": "top-right", "style": "default" },
"informations": {
    "visibility": true,
    "opacity": 1,
    "items": { "logo": false, "voice": true, "id": true, "time": true,
               "cash": true, "bank": true, "job": true, "discord": true }
},
"keybinds": { "visibility": true },
"compass": { "visibility": "always", "style": "bar" },
"weapon": { "visibility": false },
"minimap": { "visibility": "always" }
```

| Setting | Values | What it does |
| --- | --- | --- |
| `notify.visibility` | `true` / `false` | Notifications |
| `notify.position` | `top-right`, `bottom-left` | Where they stack |
| `notify.style` | `default`, `minimalistic`, `modern` | Notification style |
| `informations.visibility` | `true` / `false` | The information panel |
| `informations.opacity` | `0`–`1` | Its opacity |
| `informations.items.<id>` | `true` / `false` | `logo`, `voice`, `id`, `time`, `cash`, `bank`, `job`, `discord` |
| `keybinds.visibility` | `true` / `false` | The key hints list |
| `compass.visibility` | `always`, `vehicle`, `foot`, `never` | When the compass shows |
| `compass.style` | `bar`, `minimal`, `cardinal` | Compass style |
| `weapon.visibility` | `true` / `false` | Weapon and ammo panel |
| `minimap.visibility` | `always`, `vehicle`, `foot`, `never` | Overridden by `KOJA.HideMinimapOnFoot`, see above |

### Watermark, PVP, warnings, killfeed, effects

```json title="editable/shared/js.json"
"watermark": { "visibility": true, "style": "default", "font": "Poppins", "size": 0.95,
               "color": { "text": "#f2f2f5" } },
"pvp": { "enabled": false },
"warnings": { "lowHealth": true, "lowFuel": true, "sound": true,
              "healthThreshold": 20, "fuelThreshold": 15 },
"killfeed": { "enabled": false, "style": "default", "anyDistance": true, "distance": 100 },
"statuseffects": { "enabled": true }
```

| Setting | Values | What it does |
| --- | --- | --- |
| `watermark.visibility` | `true` / `false` | Only matters when `KOJA.Watermark.Enabled` is `true` |
| `watermark.style` | `default`, `line`, `bracket` | Watermark style |
| `watermark.font` | `Poppins`, `Inter`, `Montserrat`, `Proxima`, `SF Pro` | Font |
| `watermark.size` | `0.6`–`1.8` | Text size |
| `watermark.color.text` | hex colour | Text colour |
| `pvp.enabled` | `true` / `false` | PVP mode — see [Features](/free/hud/features#pvp-mode) |
| `warnings.lowHealth`, `warnings.lowFuel` | `true` / `false` | Low health and low fuel warnings |
| `warnings.healthThreshold`, `warnings.fuelThreshold` | `5`–`50` | Percent at which each warning starts |
| `warnings.sound` | `true` / `false` | Beep with the warnings |
| `killfeed.style` | `default`, `minimal`, `modern` | Killfeed style |
| `statuseffects.enabled` | `true` / `false` | The status effects row |

### Binds and layout

```json title="editable/shared/js.json"
"customBinds": [],
"builtinBinds": [
    { "id": "engine", "key": "B", "command": "+toggle_engine" },
    { "id": "seatbelt", "key": "L", "command": "+toggle_seatbelt" },
    { "id": "cruise", "key": "T", "command": "+toggle_cruisemode" },
    { "id": "nitro", "key": "N", "command": "+toggle_nitro" },
    { "id": "vehicleMenu", "key": "U", "command": "koja_vehiclemenu" }
],
"layout": {
    "status": { "x": 0, "y": 0 },
    "minimap": { "x": 0, "y": 0, "scale": 1 }
}
```

- `customBinds` — key-to-command binds every new player starts with, for example `{ "key": "F5", "command": "e sit" }`. The command runs as if typed in chat, without the slash.
- `builtinBinds` — the starting keys of the HUD's own actions. Keep these in line with the `Key` values in `config.lua`.
- `layout` — starting offsets for each element, in percent of the screen width (`x`) and height (`y`). Players set these by dragging in **Edit layout**; you rarely need to touch them.

## `ui`

```json title="editable/shared/js.json"
"ui": {
    "discord": "discord.gg/hexelstore",
    "logo": "",
    "keybinds": {
        "position": "right",
        "hints": [
            { "key": "M", "label": "Main menu" },
            { "key": "F3", "label": "Reload voice" }
        ]
    },
    "activeBinds": [
        { "id": "engine", "key": "B", "command": "+toggle_engine",
          "label": "Engine", "description": "Toggle the vehicle engine" }
    ]
}
```

| Key | What it does |
| --- | --- |
| `discord` | Invite shown in the information panel. Empty string hides it. |
| `logo` | URL of your server logo, shown at the top of the information panel when the player enables **Server Logo**. Use an `https://` image link. |
| `keybinds.position` | `right` or `left` — which side of the screen the key hints sit on. |
| `keybinds.hints` | The list of key hints. They are only labels: changing them does not bind anything. Put your server's real keys here. |
| `activeBinds` | The HUD's built-in actions as listed in **Settings → BINDS**, with the label and description players see. |

## `options_settings`

This part builds the settings menu.
It has three categories — `hud` (shown as **MAIN**), `status` and `carhud` (**SPEEDOMETER**) — and each is a list of windows:

```json title="editable/shared/js.json"
{
    "window": "notify",
    "icon": "fa-solid fa-bell",
    "title": "notify_settings.title",
    "description": "notify_settings.description",
    "color": "#ff6666",
    "settings": [
        {
            "type": "dropdown",
            "id": "notify.position",
            "title": "notify_settings.settings.position_title",
            "description": "notify_settings.settings.position_description",
            "options": [
                { "id": "top-right", "label": "notify_settings.options.position.top-right" },
                { "id": "bottom-left", "label": "notify_settings.options.position.bottom-left" }
            ]
        }
    ]
}
```

| Field | Meaning |
| --- | --- |
| `window` | Window id |
| `icon` | A Font Awesome class |
| `title`, `description`, `label` | Keys into `ui.settings` of the locale file — see [Translations](/free/hud/translations) |
| `color` | Accent colour of the window |
| `settings[].id` | The `default_settings` path the control changes, for example `notify.position` |
| `settings[].type` | `switch`, `dropdown`, `slider` (with `min` and `max`), `color` (one picker per option), `toggles` (one switch per option) or `percent` (value, above/below, on/off) |

What you can safely do here:

- **Remove** a setting or a whole window, to stop players changing it. The default still applies.
- **Reorder** windows and settings.
- **Remove options** from a dropdown — for example offer only two speedometer styles.

What does not work: adding a setting whose `id` the UI does not read, or a dropdown value the component has no style for.
New behaviour needs a change in `web/src` — see [UI development](/free/hud/ui-development).

## Resetting a player

A player resets their own settings from the settings menu.
There is no server-side reset, because the settings are not stored on the server.
