# The source of truth for the UI's colour and length tokens. # # `build.rs` reads this file and generates `theme.slint` into OUT_DIR at # compile time; nothing here is read at runtime unless the `live-style` # feature is on. Edit this file, never the generated one. # # Leaf values only. A token that binds several values into one concept — a # panel heading's colour *and* size *and* weight — is a Slint component, not # a row in a YAML file (see widgets.slint). # # Structure: # preamble — prose emitted at the top of the generated file # colors: — name -> "#RRGGBB", or `alias:` naming another colour token # lengths: — name -> pixels # Any token may carry `note:` (a `//` comment above it) or `doc:` (a `///` # doc comment, which Slint surfaces to editors). # # Two entries are dividers rather than tokens, because YAML discards the blank # lines between map entries and the generated file's grouping has to survive: # `section: ` — a headed run, optionally with its own `note:` # `break: true` — a bare blank line between related tokens preamble: | Near-neutral dark palette, achromatic signalling. Two commitments, both about not lying to the photographer. **Dark ground.** A light UI surrounding an image biases how that image is judged — the eye adapts to the brightest thing in view, and a white panel makes a correctly-exposed photograph look dark. **Near-neutral greys.** The earlier palette was a warm "darkroom safelight" brown (ground #14120F, R twelve points above B). That is worse than it sounds: simultaneous contrast pushes perception of the image *away* from the surround, so a warm chrome makes a neutral photograph read cool, and the photographer corrects toward warm to compensate. Every export drifts yellow. It is the same reason a print viewing booth is neutral grey rather than whatever colour the room happens to be. These greys carry a 2–3 point blue lift rather than being flatly achromatic. Pure R=G=B reads as dead to most eyes; a trace of cool reads as instrument rather than absence, and biases far less than warmth because the eye is more tolerant of a cool surround. The lift is small enough not to matter perceptually and deliberate enough not to be mistaken for drift. **No hue in the chrome.** Active and modified states are signalled by brightness alone. An accent sitting beside the image competes with it for attention and shifts the perception of nearby colours; a photo editor cannot afford either. The one exception is `warn-ink` — a caution is genuinely a different kind of thing from an active state, and hue is the fastest way to say so. colors: ground: "#121314" surface: "#1B1C1E" surface-raised: "#252629" rule: "#323438" _inks: { break: true } ink: "#EDEEF0" ink-dim: "#9EA1A6" ink-faint: "#71747A" hover: value: "#2E3034" note: | Interactive states for surfaces. Named rather than written inline so a button, a section header and a list row cannot drift apart: hover lifts toward the light, press sinks back past the resting surface so the control reads as depressed rather than merely lit. pressed: "#17181A" _signalling: section: signalling note: | Achromatic, so the separation has to come from luminance. These are spaced further apart than a coloured palette would need: with hue unavailable, a two-step brightness difference is invisible, and a modified marker that cannot be spotted at a glance is not a marker. active: value: "#FFFFFF" doc: | An active or engaged control: a slider fill, a curve line, a checked box. Brighter than `ink` so it reads as lit rather than merely present. active-dim: value: "#C6C9CE" doc: Active, at rest — a filled control that is not under the pointer. active-pressed: value: "#8E9298" doc: Active, pressed. Sinks rather than lifts, matching `pressed`. modified: alias: active note: | "This differs from its default." Deliberately the brightest thing in the chrome: it is the one piece of state the photographer scans for, and with no hue to carry it, brightness is all there is. selected: value: "#383B40" doc: | A selected grid cell. A lifted neutral rather than a tint — distinct from `hover` because a multi-selection must stay legible after the pointer has moved on, which is the whole point of selecting several before dragging them. selected-ring: value: "#D5D8DD" doc: | The ring around a selected cell. Brighter than the fill so selection survives against a pale thumbnail, where the fill alone would vanish. warn-ink: value: "#C9A05A" note: | Semantic, and the only hue in the palette. A caution is not an active state, and it is worth the one exception to say that instantly. Muted rather than saturated so it does not shift perception of a nearby image. lengths: _gaps: { break: true } gap-sm: 6 gap: 12 gap-lg: 20 _text: { break: true } text-sm: 11 text: 13 text-lg: 17 text-xl: 24 radius-sm: value: 3 note: | Corner radii. Two steps only: `radius-sm` for things that sit inside other things, `radius` for the controls themselves. radius: 4 touch-target: value: 44 note: "FR-UI-3: minimum 44pt hit target under touch." row-height: value: 26 note: | One row of the collections tree, and one level of nesting. Both are tokens because a tree's indentation has to stay proportional to its row height; hard-coding either makes the hierarchy read wrong when the other changes. indent: 14 control-height: value: 28 note: | The drawn height of a button or section header. Deliberately shorter than `touch-target` — chrome this tall in every row would crowd the photograph — so controls grow their TouchArea past their own bounds to meet FR-UI-3 rather than growing their ink. control-min-width: value: 88 doc: | Floor on button width, so a one-word label is still a comfortable target and a row of buttons has an even rhythm.