Чекпойнт: Rust-меню, настройки, Play, пауза, ресурспаки, кейбинды, RSX-протокол, шрифты и иконки. Перед финальным пушем история будет разбита на логические коммиты.
15 KiB
Horizon's design system
One design language for the launcher, the website and the few overlays the game draws that vanilla has no screen for (the on-screen keyboard a controller types with). Every game screen and the HUD are not part of it: they are vanilla's, because parity is the point of the game, and Horizon's own settings are pages built from vanilla's widgets, the way mods add theirs.
The tokens live in one place, crates/horizon-theme. The launcher (launcher/src/theme.rs)
and the game (crates/horizon-client/src/ui_theme.rs) read them as Rust constants, and the
website reads them as CSS custom properties generated from the same crate
(cargo run -p xtask -- tokens writes crates/horizon-theme/tokens.css, which the website's build
reads from main; a test fails when the file is out of date). Change a value in the crate and all three follow.
Where it comes from
The direction was set by mkdir.directory, a directory site that is almost nothing but type. What we take from it is its restraint rather than its content:
- Type is the interface. One family, Geist, and its mono. Large, tight display type (medium weight, -2 % tracking) carries the hierarchy, so there is no need for boxes, icons or colour to say what matters. Instrument Serif is gone.
- Lists, not cards. Content is a numbered list: a small mono index on the left (
01,02…), the name large, a mono detail on the right, and a 1 px rule between rows. Versions, settings, worlds, servers, release notes and the parity table are all this one component. - One accent, used once. Neutrals do the work. The violet marks the single main action on a screen (Play, Download), focus, links and progress; selection is shown in ink, not in colour.
- Space instead of borders. Generous margins (64 px on wide screens, 16 px on phones), no shadows, no gradients, no outlines around groups. A surface tint appears only under the pointer or behind something that must stand apart (a console, a dialog).
- Quiet motion. Things arrive with a short fade and an 8 px rise, a few tens of milliseconds apart, eased out, never bouncy. Hovers fade a soft plate in behind the row.
- A signature. A giant, faint wordmark: at the foot of the website, behind the launcher's opening and the game's title screen.
Tokens
Colour
Cool, slightly blue neutrals in two appearances with the same restraint, and Horizon's violet.
Every text colour passes WCAG AA (4.5:1) on every background it is used on; the tests in
horizon-theme check each pair in both palettes.
| Token | Light | Dark | Use |
|---|---|---|---|
page |
#fafafc |
#0d0e13 |
the background |
plate_soft |
#f3f3f8 |
#181a22 |
hover plate, fields, segmented track, console |
plate |
#ebecf4 |
#20222c |
pressed plate, switch off, the faint wordmark |
surface |
#ffffff |
#16181f |
dialogs and menus that sit above the page |
rule |
#e2e3ea |
#24262f |
1 px rules between rows, tracks |
ink |
#16181f |
#ecedf3 |
text, selection, slider fill, the inverted segment |
ink_soft |
#3b3e49 |
#c5c7d1 |
secondary text, values |
muted |
#646875 |
#8b8f9c |
labels, descriptions, mono indices and details |
accent |
#5b3fd6 |
#9a86ff |
the main action, links, focus, progress |
on_accent |
#ffffff |
#0d0e13 |
text on the accent |
danger |
#b4372a |
#f07a68 |
errors, the crash notice's dot |
success |
#1d7a48 |
#45c27a |
"Matches vanilla", "Ready" |
The game's menus draw over the live title backdrop, always in the dark palette, with the page
colour as a flat veil so text keeps its contrast whatever the world shows: VEIL (0.90) on
every page and in a world, HOME_VEIL (0.80) on the title's home page, which shows more of the
world and keeps to ink and ink_soft. Muted text over the backdrop is BACKDROP_MUTED
(#9fa3ae). A test holds every pair to AA over a backdrop of luminance 0.77 (the snowiest title
panorama measured reaches 0.49), on the bare veil and on a selected row's plate. Their plates
are the dark ink at 8 % and 14 % rather than plate_soft and plate: over the veil they come
out the same, and they stay visible over a bright backdrop.
Type
Geist (400, 500, 600) and Geist Mono (400), bundled under the SIL Open Font License.
| Token | App px | Web (min–max px) | Line | Tracking | Weight | Use |
|---|---|---|---|---|---|---|
micro |
11 | 11 | 1.5 | +0.04em | mono | indices (01), counts, timestamps |
caption |
12 | 12 | 1.5 | 0 | 400 | footnotes, help under a control |
label |
13 | 13 | 1.5 | 0 | 400 | row labels, nav, buttons |
ui |
14 | 14 | 1.5 | 0 | 400 | body text in the apps |
body |
15 | 15–17 | 1.6 | 0 | 400 | paragraphs |
heading |
20 | 20–24 | 1.3 | -0.01em | 500 | section heads |
title |
28 | 32–48 | 1.05 | -0.02em | 500 | list item names |
display |
44 | 34–64 | 1.05 | -0.025em | 500 | page titles |
wordmark |
220 | 18vw | 0.8 | -0.04em | 500 | the faint signature |
Space, shape and lines
- Spacing scale: 4, 8, 12, 16, 24, 32, 48, 64, 96. Page gutter 64 (apps and wide web), 16 on phones. Text measure at most 56 characters for paragraphs.
- Radii: 8 for fields and small controls, 12 for plates (hover rows, console, dialogs), full for buttons, segmented controls and switches.
- Lines: 1 px,
rule. No other borders. - Heights: control 32, large control (Play) 40, segment 28, list row 64 (apps), 86 (web).
Motion
| Token | Duration | Easing | Use |
|---|---|---|---|
quick |
150 ms | cubic-bezier(.4, 0, .2, 1) |
hovers, presses, colour |
move |
240 ms | cubic-bezier(.23, 1, .32, 1) |
selection sliding, switches, page changes |
enter |
600 ms | cubic-bezier(.23, 1, .32, 1) |
content arriving: opacity 0→1, rise 8 px |
stagger |
40 ms | delay between list rows arriving |
prefers-reduced-motion (web) turns enter into a plain fade.
Components
- Index list (the signature):
micromono index inmuted(left, 40 px column), the name intitle(orui/labelfor dense lists such as settings), details inmicromonomutedon the right, aruleunder every row. Hover: aplate_softplate with radius 12 fades in, extending 16 px past the row on each side, and the index turnsink. - Buttons: pill shaped,
labelsize, medium weight, 32 high (40 for the one large action). Primary is accent withon_accenttext, at most one per screen. Secondary isplate_softwithinktext. Ghost ismutedtext that turnsinkwith aplate_softplate under the pointer. Pressed:plate(or the accent at 85 %). Disabled: 40 % opacity. - Fields: 36 high,
plate_soft, radius 8, no border; focus draws a 1 px accent ring. Monospace for paths and commands. - Switch: 36×20 pill,
platewhen off, accent when on, a white 16 px knob that slides overmove. - Slider: a 2 px
ruletrack, the filled partink, a 14 pxinkknob. The value is written in mono beside the label, not on the track. - Segmented control / tabs: a
plate_softpill; the selected segment is aninkpill withpagetext that slides overmove. - Navigation: text only. Launcher: a top bar with the mark and "Horizon" on the left and
the pages on the right,
labelsize,muted, the current oneinkon a soft pill. Game menus keep their sidebar layout (it suits a controller), but as a plain column of text items under the mark, with no panel tint and no border: the current oneinkon a soft pill that slides to it. - Page header:
displaytitle, then onebodyline inmuted, 56 characters at most. - Notices and dialogs: a
plate_softplate, radius 12, 16–24 padding, no border; a 6 px dot indanger(oraccent) leads the title. Actions are buttons in a row below. - Progress: a 2 px
ruletrack and anaccentfill; indeterminate work shuttles a 30 % segment. - Console:
plate_softplate, radius 12,micro/captionmono inink_soft, newest line at the bottom, the command field inside the plate under arule. - Empty states: a short
mutedsentence and one secondary button, centred in the space.
Surfaces
- Launcher (
launcher/): drawn in the Blocks look below. A top bar (wordmark, the pages as tabs, the account chip, Settings), a sidebar (the instances, the hosted servers, the launcher's own version and channel at its foot), and the page over a stone texture. Home opens on a pixel landscape banner with the hero, the version picker and Play; Mods holds the mod, resource pack and modpack tabs; Versions and Patch notes are lists of panels; Settings is grouped panels of rows. Dark by default, Light or System in Settings (Appearance). - Website (horizon-rust/horizon-site): the Blocks look below. The same page structure as before (what it is, screenshots, parity, launcher, patch notes, community), headings in Pixelify Sans, lists as stacks of panels, the stone texture behind the page and Horizon's pixel landscape (a dawn or a sunset) behind the hero. Light and dark follow the system, with a toggle. A giant wordmark in pixels sits in the footer.
- Game: vanilla's screens, except the main menu.
rustui(horizon-client/src/rustui) draws Horizon's Home in the Rust 2025 look: a veiled panorama under charcoal plates with one Rust-orange accent, the top bar carrying the wordmark, the tabs (ГЛАВНАЯ, ИГРАТЬ, two locked???chips that explain themselves on hover) and the actions, quick connect and the devblog card on the right, the chat strip and the account plate along the bottom. Its palette isrustui::theme, WCAG-tested over the veil; panels paint procedurally until a Figma export sits in the resource root asgui/sprites/rustui/<name>. The menu lays out in dp (the launcher's 1040x700 frame scaled to the window) and hit-tests the same layout it draws.horizon.txt:rustMenu:falserestores vanilla's main menu. The settings screens use the same language (rustui::settings): a category rail on the left, one row per setting with its value, bar or button on the right, the hovered setting's explanation in a panel, and a footer with the version, the credit (Horizon, the Rust client this is based on, MIT) and the way back. Their rows are vanilla's own widgets, so every setting behaves as before; the Graphics and pack screens keep vanilla's own layouts for now.horizon.txt:rustSettings:falserestores vanilla's options screens. The on-screen keyboard is a rounded plate of keys in the page colour, the focused key in the accent.
Blocks
A blocky, Minecraft-like look in Horizon's purple, for the website and the redesigned launcher:
Minecraft's inventory rather than a document. Its colours are horizon_theme::Blocks
(BLOCKS_LIGHT, BLOCKS_DARK), which tokens.css carries under the same names as before plus
--panel, --raised, --button, --well, --bevel, --lip, --outline and the --face
of the main action. The game's menus keep LIGHT and DARK.
- Pixel type for what names and what you act on. Headings, navigation, buttons and the wordmark use Pixelify Sans (OFL; Medium, SemiBold, Bold); paragraphs and numbers stay in Geist and Geist Mono so long text reads easily. The bundled copies add the two uppercase Cyrillic letters upstream leaves out, О and П: the Latin O's glyph and the lowercase п lifted to that weight's cap height, so a Russian heading stays in the pixel face.
- Blocks, not rounded cards. Every surface is square: a 2 px dark outline, a 2 px light bevel along the top and a darker lip along the bottom. The main action is the violet face with its own bevel and a 4 px lip; it sinks a pixel when pressed. Code and fields are sunk wells.
- Inventory tabs switch the kinds in Mods: the open tab joins the panel under it.
- Slots hold pictures: a mod's or pack's own icon and gallery images from its source,
downloaded when first shown and kept on disk (
launcher/src/browser/images.rs); the launcher ships no third-party pictures. The Horizon instance shows a grass block drawn from the player's own prepared Minecraft textures. Sources are named in text ("from Modrinth"): Modrinth's logo is all rights reserved, so it is not shown. - Partner banner: the one exception to shipping no third-party pictures. Sculk, a server host,
sponsors Horizon: its banner (
launcher/assets/sculk-banner.webp,launcher/src/partner.rs) sits under a muted mono "Partner" label at the end of the Servers page and opens Sculk's page in the browser. The sidebar of the other pages shows a card made for its width instead: Sculk's characters and logo cropped from the same banner, and the text drawn by the launcher in its language, so it stays readable; it keeps Sculk's dark card, cyan and rounded corners in both themes, as an ad does, and turns compact (characters beside the text) when the window is too short. The website shows the banner the same way on Home and the hosting docs, as arel="sponsored"link. - Pixel art drawn in code. The stone behind the pages is close shades of the page colour
(
TILE_SHADES), never Minecraft's stone; the landscape is a banded sky, a sun cut in slices like the mark, hills and square trees (a sunset in dark, a dawn in light). The launcher bakes them inlauncher/src/art.rsat the window's scale so each texel covers whole device pixels; the website draws them in itsscripts/art.mjs. - Icons: Pixelarticons (MIT), 24 unit pixel SVGs in
crates/horizon-theme/icons/pixel/, tinted with the text colour. - Contrast: every text colour meets WCAG AA (4.5:1) on the panels, the page and the lightest
stone shade in both palettes (
blocks_text_meets_wcag_aa_in_both_appearances).
The mark
Horizon line. A sun half set below a horizon, drawn as three horizontal bars of 4, 8 and 10 units over a 12 unit line, on a 16 unit grid. The bars are the sun cut by the horizon into slices, which also reads as layers of terrain and as a nod to voxels without drawing a block. It is built on whole units of the 16 px grid so it stays sharp at 16 px, and it scales without detail to 1024 px.
- Monochrome: the mark in
inkon light, inink(dark) on dark; the line can take the accent. - App icon: the mark in
#ecedf3with the line in#9a86ff, on a#16181frounded square (macOS grid: 824 px tile with 185 px corners in a 1024 canvas; Windows and Linux use the full square with 22 % corners). - Exports and where they go:
docs/media/brand/README.md.