Files
2026-09-25 17:02:24 +03:00

25 KiB
Raw Permalink Blame History

name, description, colors, typography, rounded, spacing, components
name description colors typography rounded spacing components
terrain studio A dark instrument panel for painting a planet's geology and reading back the ground it makes.
hot hot-ink ok ok-ink warn warn-ink void well bg panel row-hover row-live raised line ink dim scrim scrim-hud chip-edge
#d6a03a #17181a #6fbf73 #bfe6c1 #b4553f #f0b4a4 #0b0e11 #11151a #14181d #1b2027 #222831 #252b34 #262d36 #2b323b #d8dee6 #8b95a1 #0d1014ee #0d1014dd #0006
body data note label
fontFamily fontSize fontWeight lineHeight letterSpacing
ui-monospace, "Cascadia Mono", Menlo, Consolas, monospace 13px 400 1.45 normal
fontFamily fontSize fontWeight lineHeight letterSpacing
ui-monospace, "Cascadia Mono", Menlo, Consolas, monospace 11.5px 400 1.45 normal
fontFamily fontSize fontWeight lineHeight letterSpacing
ui-monospace, "Cascadia Mono", Menlo, Consolas, monospace 11px 400 1.45 normal
fontFamily fontSize fontWeight lineHeight letterSpacing
ui-monospace, "Cascadia Mono", Menlo, Consolas, monospace 11px 600 1.45 0.09em
sm md lg
2px 3px 4px
xs sm md lg xl
4px 6px 8px 10px 16px
button button-hover button-disabled button-primary button-warn button-toggle-on swatch-row swatch-row-hover swatch-row-selected chip chip-key input-number toast toast-error toast-ok hud progress-track progress-fill ramp
backgroundColor textColor typography rounded padding
{colors.raised} {colors.ink} {typography.body} {rounded.lg} 6px 10px
backgroundColor textColor
{colors.raised} {colors.ink}
backgroundColor textColor
{colors.raised} {colors.dim}
backgroundColor textColor typography rounded padding
{colors.hot} {colors.hot-ink} {typography.body} {rounded.lg} 6px 10px
backgroundColor textColor typography rounded padding
{colors.raised} {colors.warn-ink} {typography.body} {rounded.lg} 6px 10px
backgroundColor textColor typography rounded padding
{colors.raised} {colors.hot} {typography.body} {rounded.lg} 6px 10px
backgroundColor textColor typography rounded padding
transparent {colors.ink} {typography.body} {rounded.lg} 4px 6px
backgroundColor textColor
{colors.row-hover} {colors.ink}
backgroundColor textColor
{colors.row-live} {colors.ink}
rounded size
{rounded.md} 18px
rounded size
{rounded.md} 12px
backgroundColor textColor typography rounded padding width
{colors.well} {colors.ink} {typography.body} {rounded.md} 2px 5px 78px
backgroundColor textColor typography rounded padding
{colors.scrim} {colors.ink} {typography.body} {rounded.lg} 6px 9px
backgroundColor textColor
{colors.scrim} {colors.warn-ink}
backgroundColor textColor
{colors.scrim} {colors.ok-ink}
backgroundColor textColor typography rounded padding
{colors.scrim-hud} {colors.dim} {typography.body} {rounded.lg} 6px 9px
backgroundColor rounded height
{colors.well} {rounded.md} 6px
backgroundColor height
{colors.hot} 6px
rounded height
{rounded.sm} 12px

Design System: terrain studio

Overview

Creative North Star: "The Instrument Panel"

terrain studio is a gauge with a world in it. The left rail is calibrated measurement — a rate typed in mm/yr comes back as the hillslope angle it buys, a brush size comes back as metres on the ground, a bake comes back as minutes — and the rest of the window is the specimen those numbers describe. Everything in the chrome is either a reading, a control, or the hairline that separates the two. There is no third category, and nothing in the frame exists to be looked at.

The room is dark because the work is coloured. Every hue that means anything in this product belongs to the painting: a class colour is an uplift rate, a ramp is a field in mm/yr, a region colour is a landmass. So the instrument around it is made of iron and ink, with exactly one warm colour — brass — reserved for the two things the machine can tell you about itself: what you can act on, and what is currently live. A second hue in the chrome would be a colour that looks like data and isn't.

It is patient, and it explains itself. Under a control that takes a number nobody has an intuition for sits an 11px note in graphite saying what that number does to the ground, and the notes say why rather than what — "a mark you cannot see is one you will paint over". That voice is part of the system, not commentary bolted to it. The tool assumes a reader who will read, and it is written for someone who opens it every few weeks rather than every day.

Key Characteristics:

  • One monospace family, three sizes (13px / 11.5px / 11px), no display type anywhere.
  • A fixed 300px rail against a fluid canvas; the canvas absorbs every pixel of a resize.
  • Flat by doctrine: not one box-shadow in the system. Depth is tonal value plus a 1px hairline.
  • Brass (#d6a03a) is the only warm colour and never describes data.
  • State changes are instant. The single animation in the product measures something that actually takes time.
  • Monospace alignment is load-bearing — reports are white-space: pre and the columns are the layout.

Colors

An iron room lit by one brass lamp, with verdigris and rust kept in reserve for the two things the machine needs to say out loud.

Primary

  • Brass (#d6a03a): The only warm colour in the product, and the only colour in the chrome that isn't data. It marks exactly two conditions. Filled (brass background, #17181a text) means "this is the action" — the Plan button, the Bake button. Outlined (brass border, brass text, surface unchanged) means "this is the one that is live" — the selected class swatch, the open tab, the map currently drawn over the painting, the border that appears under the cursor. It is also the fill of the bake progress bar, which is the same statement in a third grammar: this is the thing currently happening.
  • Brass Ink (#17181a): Text on a filled brass surface. Near-black rather than the room's own background, so a primary button reads as a lit plate and not as a hole.

Secondary

  • Verdigris (#6fbf73): Success, and only success — the border of a confirmation toast. Patina on brass: the same metal, cooled. It appears for four seconds and then removes itself.
  • Verdigris Light (#bfe6c1): Text inside a success toast, lifted so the message reads at 13px against a near-black scrim.

Tertiary

  • Rust (#b4553f): Failure and danger — the border of an error toast, and the border of the Cancel button that stops a running bake. Oxidised iron: the room's own material gone wrong.
  • Rust Light (#f0b4a4): Text on a rust-bordered surface. Warm enough to be legible at 11.5px, desaturated enough that it never competes with brass for "act on this".

Neutral

The neutrals are one continuous ladder of the same cold iron, and their order is the depth system:

  • Void (#0b0e11): The canvas clear colour, behind and around the world. The darkest value in the product, because it is the only surface that is genuinely nothing.
  • Well (#11151a): Recessed surfaces — number inputs, the progress track. Below the panel: things you put values into.
  • Cold Iron (#14181d): The page and the stage. The default ground.
  • Panel Iron (#1b2027): The 300px rail. One step up from the stage, which is the whole reason the rail reads as a separate instrument.
  • Iron Hover (#222831): A selectable row under the cursor.
  • Iron Live (#252b34): The selected row, paired with a brass border.
  • Raised Iron (#262d36): Buttons. Above the panel: things you press.
  • Hairline (#2b323b): Every border in the product, always exactly 1px.
  • Cold Ink (#d8dee6): Primary text and every value the user typed or chose.
  • Graphite (#8b95a1): Labels, notes, HUD readouts, report bodies, section headings. The voice of the instrument, as distinct from the values it holds.
  • Scrim (#0d1014ee) and Scrim HUD (#0d1014dd): Floating chrome over the canvas — the toast at 93% and the cursor readout at 87%. Darker than any inline surface and deliberately translucent, so a floating thing is legible without ever hiding the world underneath it.
  • Chip Edge (#0006): The 40%-black hairline around a colour chip, so a swatch keeps its edge against both a pale class colour and a dark one.

Named Rules

The One Warm Thing Rule. Brass is the only warm colour on screen. It marks what you can act on (filled) or what is currently live (outlined), and it never describes data. If a new element needs attention and is neither of those two things, it does not get brass — it gets a note.

The Data Owns Colour Rule. Hue inside the canvas belongs to the painting, the legend and the map ramps. The chrome is iron, ink, and one accent. Nothing in the frame may be a colour that could be mistaken for something the solve will read.

The Ladder Rule. The neutrals are ordered by depth, not by taste. A new surface picks its value by answering whether it sits below the panel (a well), on it (a row), or above it (a control). There is no free choice of grey.

Typography

Display Font: none. This system has no display role and does not want one. Body Font: ui-monospace, with "Cascadia Mono", Menlo and Consolas behind it Label/Mono Font: the same family, at 11px and weight 600

Character: One monospace family does every job in the product, because half of what the interface shows is a column-aligned report and the other half is numbers that must line up under each other while you edit them. The result reads as instrument labelling rather than as prose: terse, evenly coloured, and impossible to mistake for a document. There is no font loading at all — the stack is the platform's own, which is also what keeps the tool a single offline binary.

Hierarchy

  • Body (400, 13px, 1.45): Everything the user typed or chose. Class names, button labels, input values, toast messages. font: inherit on every control, so a number in a field is the same size as the same number in a sentence.
  • Data (400, 11.5px, 1.45, white-space: pre): Plan reports, bake status, map-key items. Set in graphite, with <b> in cold ink for the one word that matters (**baking**, **done**).
  • Note (400, 11px, 1.45): Annotations under a control, in graphite. This is where the system explains itself, and it is a first-class role rather than a caption.
  • Label (600, 11px, 0.09em, uppercase): Section headings in the rail — brush, this class, planet, bake — and map-key titles. Uppercase and tracked at 11px so a heading reads as a divider without needing a rule above it. 16px of space above, 6px below; 2px above when it is first in the rail.

Named Rules

The Alignment Is Layout Rule. Reports are white-space: pre, and their columns come from padEnd/padStart in the code that generates them. Never rebuild one as a table, a grid, or flex rows. The monospace font is structural here, and switching families would silently break every report in the product.

The Lowercase Rule. Headings are lowercase in the source and uppercased by CSS; labels and buttons are sentence case at most. Nothing in this interface is Title Case. The one exception is a proper noun the domain already capitalises.

Layout

A two-column grid at full viewport height: a 300px fixed rail and a fluid stage (grid-template-columns: 300px 1fr; height: 100%). The rail scrolls on overflow and owns a 1px right hairline; the stage clips and holds the canvas at 100% × 100% with a crosshair cursor. There are no breakpoints and no media queries — this is a desktop instrument, and PRODUCT.md records that as deliberate rather than unfinished.

Rail padding is 10px on every side. Floating chrome is inset 10px from the stage edges: the message toast top-left at max-width: 70%, the cursor readout bottom-left with pointer-events: none so it can never intercept a brush stroke.

Rhythm. The spacing scale is 4 / 6 / 8 / 10 / 16px. 6px is the workhorse — the gap in a label/value row, the gap in a button bar, the vertical padding of a button, the space under a section heading. 4px separates tabs and map buttons; 8px separates a colour chip from its name; 10px is panel padding and overlay inset; 16px opens a new section. Two values sit off the scale and are load-bearing anyway: rows carry margin: 3px 0 so a stack of eight number fields stays dense, and <hr> carries margin: 14px 0 0 so a rule sits closer to what follows it than to what precedes it.

Row grammar. Every parameter is one .row: a graphite label at flex: 1 on the left, its control on the right. Labels for nested parameters are indented with leading spaces in the label text itself (" floor mm/yr", " fraction"), which is a monospace trick and entirely in keeping — the indent is exactly two characters because the font makes that a reliable measurement.

Named Rules

The Rail Is Fixed Rule. 300px, never a percentage, and number inputs are 78px. A label and its value must not reflow when the window changes size; the canvas absorbs the entire delta. An author resizing the window is looking at the world, not at the panel.

The Canvas Is Never Chrome Rule. Nothing in the stage has a background, a border or a panel of its own except the two floating readouts, and both are translucent. The world goes edge to edge.

Elevation & Depth

There is no box-shadow in this system, and there will not be one. Depth is carried entirely by tonal value and a single 1px hairline, and this is doctrine rather than an accident of the first implementation.

The ladder runs in both directions off the panel. Inputs and the progress track sink below it to Well (#11151a). Rows lift to Iron Hover (#222831) and then Iron Live (#252b34). Buttons sit above it at Raised Iron (#262d36). Every one of those surfaces carries the same 1px Hairline (#2b323b) border, so edges are declared rather than implied. Things that genuinely float over the canvas — the toast, the cursor readout — do not rise in value at all; they go darker than anything inline, to a translucent near-black scrim with the same hairline. Floating is expressed as separation from the world, not as height above it.

This is the right answer for this product specifically. The canvas is a data image whose values the author is trying to read honestly, and a drop shadow spilling onto it would imply a physical stack that does not exist. The one surface allowed to sit over the painting does so by dimming it, which is what a real instrument overlay does.

Named Rules

The No-Shadow Rule. No box-shadow, no filter: drop-shadow, no glow. If something needs to separate, it changes value or gains a hairline.

The Two-Direction Rule. A surface declares its role by which way it moves off the panel: down means you type into it, up means you press it, level means you select it. A new surface that is the same value as the panel is undecided, and undecided is a bug.

The Scrim Rule. Anything floating over the canvas is darker than the panel and translucent (#0d1014dd–ee). It never becomes opaque and it never gets lighter, because the point of a floating readout is that you can still see what it is describing.

Shapes

Three radii, each with a job. 2px ({rounded.sm}) is for a gradient strip — the map-key ramp, which is almost a rectangle because it represents a continuous scale and a rounded end would misdescribe it. 3px ({rounded.md}) is for small inlaid things: colour chips, number inputs, the progress track. 4px ({rounded.lg}) is for anything you press or that can become selected: buttons, swatch rows, the floating readouts. Nothing in the product is more rounded than 4px and nothing is a pill.

Every edge is a 1px hairline. Colour chips carry a 40%-black edge (#0006) instead so the chip reads against both a pale class colour and a dark one. Chips are 18px in the rail and 12px in a map key — the same square at two scales, never a circle.

The one deliberately hard-edged form in the product is the brush disc, which is written straight into ImageData with no antialiasing because a blended pixel would classify as a third class. It is worth stating here as form language: this system does not soften an edge that carries meaning.

Named Rules

The Reserved Border Rule. Anything that can become selected carries border: 1px solid transparent at rest, so selection changes colour and never geometry. Nothing in this rail is allowed to shift, grow or reflow when you click it.

The Square Chip Rule. A colour sample is a 3px-radius square with its own dark edge, at 18px in the rail and 12px in a key. Never a circle, never borderless, never a swatch of the class colour used as a background behind text.

Components

Buttons

  • Shape: Barely rounded plates (4px radius), 1px hairline, padding: 6px 10px, font: inherit so a button label is the same 13px as everything else.
  • Default: Raised Iron surface (#262d36), cold ink text, hairline border. Used for everything that saves, toggles or opens — Save legend, Save painting, Re-roll.
  • Primary: Filled brass (#d6a03a) with near-black text (#17181a) and font-weight: 600. There is at most one of these visible per group: Plan (4 s) in the main bar, Bake in the bake block.
  • Hover: The border alone turns brass. The surface does not change and nothing moves. Instant, no transition.
  • Disabled: opacity: .5 and cursor: default, applied to whatever variant it already was — a disabled primary is a dimmed brass plate, not a grey one.
  • Toggle (on): Brass border and brass text on the unchanged Raised Iron surface. This is how tabs and map buttons show which one is showing. Filled means act; outlined means live — the distinction is the whole button system.
  • Danger: Rust border (#b4553f) and rust-light text (#f0b4a4) on Raised Iron. Exactly one exists — Cancel on a running bake — and it appears only while there is something to cancel.

Swatch rows

The signature control of the rail, and a gauge as much as a picker. One row is: an 18px colour chip, the class name at flex: 1, and a right-aligned graphite reading that says what the class does — 3.4° rolling, 120 m deep, coast pinned. Selecting a class and reading its consequence are the same glance.

  • Rest: transparent background, border: 1px solid transparent, padding: 4px 6px, 4px radius, 8px gap.
  • Hover: Iron Hover background (#222831). No border change.
  • Selected: Iron Live background (#252b34) and a brass border.
  • Erase variant: the chip becomes a 45° hatch (repeating-linear-gradient(45deg, #3a424c 0 4px, #232931 4px 8px)). Absence is drawn, never left blank — a blank chip would read as a colour.

Inputs / Fields

  • Number: Well background (#11151a), cold ink text, hairline border, 3px radius, padding: 2px 5px, fixed 78px width. Recessed against the panel, which is how you can tell at a glance which things in the rail accept a value.
  • Range: the native control at width: 100%, unstyled. Used once, for brush size, directly under its own readout.
  • Checkbox: native, right-aligned in its row like any other control.
  • Focus: not currently styled. The browser default is doing this job, which is the one hole in the hairline system — see Do's and Don'ts.

Navigation

Two full-width tabs at the top of the rail (flex: 1 each, 4px gap, padding: 4px 6px) switching the brush between the class painting and the overlay. The active tab uses the toggle-on treatment — brass border, brass text, font-weight: 600. A tab that cannot apply (a planet with no overlay legend) is disabled rather than hidden, and carries a title explaining what would enable it. Duplicated on the o key.

Message toast

Floating at the stage's top-left, 10px inset, Scrim background (#0d1014ee), hairline border, 4px radius, padding: 6px 9px, max-width: 70%. Three states: neutral (hairline border, cold ink), error (rust border, rust-light text) and ok (verdigris border, verdigris-light text). Success auto-dismisses after 4 seconds and only if the message is still the one that was posted; errors stay until replaced. Preserves newlines, because this is where multi-line guidance appears — including the file:// rescue message that explains why every request failed.

Cursor readout

Bottom-left, 10px inset, Scrim HUD (#0d1014dd), graphite, white-space: pre, pointer-events: none. Three facts, continuously: pixel position and the same position in kilometres east, then zoom percentage and the current brush name. The one piece of chrome that never waits to be asked.

Bake progress

A 6px Well track (#11151a, 3px radius, overflow: hidden) with a brass fill whose width carries transition: width .3s — the only animation in the product. Below it, the status block in Data type with the state word bolded to cold ink, then the last few landed regions and a dimmed tail of the last four log lines at opacity: .65.

Map key (signature)

A key is published with every map view and generated by the code that drew that map. Two shapes: a ramp (12px gradient strip, 2px radius, chip-edge border, with its two end values in graphite beneath at space-between) for continuous fields, and a two-column grid (gap: 1px 8px) of 12px chips with names for categorical ones. Under either, a note paragraph in graphite explaining what the map means and what it does not mean.

Monospace report (signature)

A white-space: pre block in Data type, graphite, column-aligned by padEnd/padStart at generation time. It mixes a table, warnings and prose in one flow — a seam warning, a per-class share table, the fault and lithology summary, an overlay tally, a region estimate. Its density is the point: a plan report is the four-second answer the whole product is built around, and it is meant to be read whole rather than scanned.

Do's and Don'ts

Do:

  • Do use filled brass for the one action in a group and outlined brass for the one item that is live. Never both in the same role.
  • Do reserve border: 1px solid transparent on anything selectable, so selection never changes geometry.
  • Do put an 11px graphite note under any control whose raw value has no obvious consequence, and write it to say why. The note is part of the control.
  • Do report a number back in the unit the author thinks in — degrees, metres, kilometres, minutes — beside the field that takes it.
  • Do publish a key with every map, generated by the code that drew the map, so the two cannot drift apart.
  • Do pick a new surface's value off the ladder by asking whether it sinks, sits or rises relative to the panel (#11151a / #1b2027 / #262d36), and give it the 1px hairline.
  • Do keep reports in white-space: pre with their columns padded in code.
  • Do disable a control that cannot apply and give it a title saying what would enable it, rather than hiding it.

Don't:

  • Don't add a box-shadow, a glow or a drop-shadow anywhere, at any elevation.
  • Don't add a transition to a state change. Hover and selection are instant. The only animation in this system is the bake progress width (.3s), and it earns that because it measures a two-hour process.
  • Don't introduce a second font family or a fourth type size. 13px / 11.5px / 11px in one monospace stack is the entire scale, and a family change would break every column-aligned report.
  • Don't put a hue in the chrome. Colour in this product means an uplift rate, a depth, a rock type or a region; a decorative hue would be indistinguishable from one of those.
  • Don't use brass on anything that is neither actionable nor live — not on a heading, not on a value, not to emphasise a word in a note.
  • Don't let a control change size, padding or position between states.
  • Don't round anything past 4px, and don't make a colour sample a circle.
  • Don't leave a control with only the browser's default focus ring. Number inputs currently do, and it is the one gap in the hairline system: a keyboard user gets a focus treatment this design never specified.
  • Don't ship a second letter-spacing for the label role. The map-key title is at 0.08em against the section heading's 0.09em; that is drift, and 0.09em is the value.