# Editor Design System Blacksite's editor uses the Penpot **Inspector Material Slot** as its visual source of truth. This guide defines the reusable visual and interaction language; domain behavior remains in its owning editor subsystem. See [ADR 0049](../adr/0049-penpot-led-editor-visual-system.md). ## Semantic palette The default theme mirrors Blacksite's established palette. These values are published under Penpot Assets / Colors and represented by `EditorVisualPalette`; domain widgets consume semantic roles rather than literals. Theme selection and persistence are intentionally deferred to Editor Settings, but the renderer-facing UI seam already accepts a different palette. | Role | Default | |------|-------| | Canvas / panel / recessed | `#07080A` / `#0D0F12` / `#090B0D` | | Control / elevated | `#15181C` / `#1F2328` | | Brand accent / hover | `#E5A433` / `#F9C14F` | | Selection and valid drop | `#41C6CF` | | Border / strong border | `#262B31` / `#40474F` | | Primary / secondary / muted text | `#DCE1E5` / `#9BA3AB` / `#606972` | | Healthy / warning / error | `#54BE7F` / `#EBB448` / `#E25E5E` | Source Sans Pro Regular and Bold are bundled in `crates/editor/assets/fonts/source-sans/`. Type roles use the Penpot 9–16 px scale; weight is selected through separate egui font families rather than simulated styling. Spacing follows 4/8/12 px steps. Common controls are 22–31 px tall with 4/5/6/7/8 px radii. Use semantic tokens from `ui/design_system`; do not copy literal colors into a domain card. The selection color is reserved for focus, valid drag targets, selection, and active values. Health, unsaved, processing, failed, read-only, inherited, and built-in states keep the same geometry. ## Composition contract Reusable controls render a supplied view model and return actions. They do not query unrelated Bevy resources, save files, schedule processing, or directly mutate scene/world state. The owning domain resolves actions after rendering. This keeps the visual system reusable in the Inspector, Content Browser, Material Library, and future editor panels. The material implementation is split as follows: - `ui/design_system` owns tokens, typography, control chrome, property-grid geometry, and the color popup. - `ui/materials/panel.rs` owns the material-slot view model and action contract, while `ui/materials/pickers.rs` owns the compact current/recent Material menu and the richer Texture asset-picker presentation. - `ui/materials/inputs.rs` renders schema-declared material controls. - Inspector and asset editors provide state and apply returned actions through authored documents. ## Reference layout At the current 620 px wide reference, 12 px outer padding produces a 596 px slot and a 569 px parameter body. A 32 px **Materials** heading precedes repeated slots. Expanded headers are 82 px with a 64 px preview; collapsed headers are 52 px with a 32 px preview. Surface is 46 px, Inputs is 216 px (24 px header plus six 32 px rows), UV is 54 px, and Advanced is 30 px collapsed or 178 px expanded. An expanded wide row keeps its label/value/channel geometry fixed while only the texture field grows above the reference width. | Column | Width | |--------|-------| | Parameter label | 82 px | | Value | 132 px | | Value/channel gap | 6 px | | Channel | 48 px | | Channel/texture gap | 16.5 px | | Texture field | 198.5 px at the reference width | | Locate / Clear | 24 px each, separated by 6 px | At the 420 px Inspector floor, the slot is 396 px and parameter sections are 369 px. Surface remains 46 px and Inputs is 372 px: each of the six rows uses a 58 px two-line layout with parameter, value, and channel above a 345 px asset picker. UV reflows from 54 px to 94 px. The layout must not introduce nested or horizontal scrolling, clipped buttons, or height-dependent width changes. These reference dimensions are implemented as owning rectangles, not nested `horizontal` flow. At 569 px, each input row begins its value at x96, channel at x234, and texture group at x298.5. At 369 px, the parameter line is `x12/y5/w345` and the texture line is `x12/y31/w345`. Texture names are clipped and ellipsized inside the field, so content cannot move the fixed Locate/Clear anchors. A transient width below 372 px retains finite clipped rectangles and replaces the two low-frequency texture actions with one overflow control until the Inspector minimum is restored. ## Material controls Primitive, static-mesh, and skinned-mesh surfaces use the same ordered `MaterialsSection` and slot-keyed `MaterialSlotPanel` actions. Static/skinned geometry cards do not embed duplicate material presentations. The slot header and asset zone include the material sphere, bounded name and project-relative path, shader, health, assignment interaction, and responsive actions. The entire zone is the drop target; there is no second “Drop material asset” box. Preview/name/path and Browse open the picker, while Shader, Locate, Clear, and overflow remain isolated. Direct assignments do not show an “Explicit” badge. Inherited identities are limited to Model Source, Model Default, Project Default, and Default Grid. Expanded content shows supported controls only: - Surface: Opaque/Cutout, cutoff when Cutout, Separate/ORM, and Double Sided. - Inputs: six Standard Lit rows. Emissive color owns the independently stored intensity companion and edits/restores both through the Emissive popup. - UV: two equal Offset/Tiling groups with fixed X/Y controls and independent resets; Material Instances reset to inheritance. - Advanced Inputs: only evaluator-supported schema declarations. Imported source content is read-only with Extract Editable in overflow. DefaultGrid is immutable. Broken explicit references keep their failed identity and diagnostics rather than exposing a weaker layer. Stable IDs, fingerprints, orphan state, and provenance live in overflow diagnostics, not in the parameter grid. ## Pickers and direct manipulation The compact Material menu shows the current assignment and editor-local recent choices, then offers Browse Asset Library and Clear. The richer Texture picker uses bounded thumbnail rows for current, recent, and project textures. A popup dismissal or invalid choice does not assign anything. Material and Instance drops use the active theme's selection highlight; Texture drops show an explicit invalid state. Handoff v2.3.1 has one Shader selector in the asset identity block. Surface contains the blend mode and Double Sided without a visible `Blend` label; it does not mirror Shader as a second "Shading Model" control. UV is followed by a collapsed Advanced row. Its culling, render queue, depth-write, and receive-shadows mock controls are explicitly painted muted previews until those renderer contracts exist, and therefore never return actions. Standard egui disabled opacity is not compounded over these controls. The Inspector actor identity remains fixed. The remaining dock-tab body is one actor-salted component scroll region whose clip is intersected with the dock body, so scrolled Transform or component cards cannot paint or receive input beneath the actor header. Component-local children must preserve that inherited clip rather than replacing it with their own maximum rectangle. The vertical scrollbar floats over a stable four-pixel interaction rail, so crossing the height overflow threshold cannot change the content width or trigger a responsive-layout flip. Texture fields open their picker when the field itself is clicked. They show a thumbnail or typed fallback icon plus Locate and Clear actions. A ready typed thumbnail replaces the generic Texture glyph rather than appearing beside a redundant icon; a redundant folder button is not used. The color popup is a fixed 420 x 350 px modal centered over the owning Inspector region, with a dimmed Inspector-local scrim. Its exported geometry includes a 38 px header, 250 x 26 mode tabs, 178 x 202 wheel editor, 198 x 186 values region, byte RGBA fields, degree/percentage HSV fields, functional HEX copy, a 156 x 14 checkerboard alpha control, 396 x 28 recents row, and 396 x 26 footer. Wheel, Sliders, and Presets share that fixed frame. The exported eyedropper lane is a muted, non-interactive preview labelled `Planned — screen sampling pending`. Every preview updates the live material document. Apply keeps the dirty in-memory value; Cancel, Escape, Close, and outside dismissal restore the exact value captured on open. No picker action writes source or queues derived processing. Material scalar inputs use the reusable 130 x 22 Penpot control: a 70 x 14 track, 10 px thumb, 8 px separation, and 48 x 22 numeric field. Expanding Advanced scrolls its complete 178 px disabled-preview body into view rather than revealing a clipped partial section. ## Review checklist - Verify the 620 px geometry numerically and in a native capture. - Verify the narrow reflow, long and Unicode names, invalid drops, read-only/imported/built-in states, and popup cancellation. - Verify handle identity and dirty-document behavior during live interaction. - Run `python scripts/codex/architecture_audit.py check`; new design-system work does not earn an exception from the module-size ratchet.