Creative North Star: “The Companion Command Center”
Maka is a desktop workspace for directing, supervising, and completing real work with agents. The task stays central; activity, permissions, failures, recovery, and generated work remain inspectable without turning the window into a monitoring dashboard.
The system is calm, native, and compact: spacious around reading and decisions, dense where comparison matters. Humanity comes from useful language and continuity, not simulated personality.
This document governs the default light and dark themes. Optional palettes may change canvas, ink, accent, and semantic colors, but must preserve their roles, contrast, and hierarchy.
Authority: apps/desktop/src/renderer/astryx-theme/makaTheme.ts owns type, neutral remaps, and theme-level component overrides; apps/desktop/src/renderer/maka-tokens.css owns product palettes, spacing, radii, product motion, and the Astryx bridge; Astryx owns primitive geometry, states, and internal motion; product source owns Maka-specific compositions. Generated apps/desktop/src/renderer/astryx-theme/maka.css is not an editing authority.
Frontmatter is a snapshot of the current default theme. When it diverges from source or contract tests, source and tests win and this document must be refreshed.
Depth is a ladder, not a decoration. Every background in the app resolves to one of four semantic tiers, each derived from --background with cumulative offsets (palettes override only --background; the ladder follows).
| Tier | Token | Role | Light | Dark |
|---|---|---|---|---|
| sunken | --surface-sunken | sidebar rail, recessed chrome | 237 | 9 |
| base | --surface-base | shell canvas behind plates | 247 | 17 |
| raised | --surface-raised | cards, content plates, reading surfaces | 255 | 23 |
| overlay | --surface-overlay | menus, popovers, dialogs, toasts | 255 | 27 |
(Values are measured rendered pixels, not aspirations; the ink/surface contract tests hold them.)
The Height Rule. Height maps monotonically to lightness in both modes, and reading surfaces always occupy the brightest tier of their mode. In light mode the ladder tops out at pure white, so raised and overlay share the fill and overlay separation hands off to the floating recipe (§5). Light mode “higher = darker” is permanently forbidden — it makes elevation shadows contradict the fill.
The Canvas Recedes Rule (owner decision 2026-06-20). The canvas is gray; content surfaces are white. The sidebar sits on sunken, the shell on base, and content plates on raised. Contrast between canvas and plate — not hairlines — is the primary separator of the shell.
Paper. --surface-paper sits outside the ladder on purpose and has no dark override. It backs content whose contrast we neither author nor may invert: the sandboxed HTML-artifact iframe and the PDF embed, and QR codes, where dark-on-light is a scanning requirement rather than a preference. It is not a fifth tier and app chrome never uses it.
Legacy names. The semantic tiers are canonical. Old names are aliases and their resolved values never change out from under consumers: --surface-canvas → base, --background → raised (it is the card fill, not the page color), --background-elevated, --color-background-card, --color-background-popover → overlay, --card-bg, --color-background-surface → raised.
Prose uses exactly three tiers, spaced at an even ~2× contrast rhythm, all above WCAG AA. Measured against --surface-raised:
| Tier | Token | Light | Dark |
|---|---|---|---|
| primary | --foreground | 19.1:1 | 15.5:1 |
| secondary | --foreground-secondary | 9.8:1 | 9.2:1 |
| muted | --muted-foreground | 4.8:1 | 4.7:1 |
--foreground-dimmed is retired — its call sites name secondary directly — and must never come back with a definition of its own (contract-tested).oklch for ink, contract-tested). Mixing srgb and oklch derivations produces “same literal, different value” drift. This held one exception, dark --surface-overlay's srgb mix, until T4 unified it onto oklch at its measured value; the ladder now derives every rung in one space.--accent-solid), never raw --accent — the accent identifies interaction; the solid tier is the only accent variant that clears text contrast on every palette.Three strengths, each a job, spaced at ~1.6× like the ink ladder:
--border-soft (6% ink): quiet separation inside a plate — rails, row dividers that fills can't carry.--border (10% ink): structural boundaries between regions.--border-strong (16% ink): emphasis chrome only. Its legitimate jobs, from the live inventory: selected/active outlines and emphasized boundaries (onboarding, plan-mode, chat turn/quote chrome, the Astryx --color-border-emphasized mapping). Two call sites borrow it as a strong neutral tint rather than a border — a scrollbar thumb color and a separator glyph color — and are queued to migrate onto ink-derived tokens in T2–T4. It is not “the border for when you're unsure.”--ring-soft is a 1px ring drawn with box-shadow (0 0 0 1px) at the soft tier's own 6% alpha, not an elevation step. It was called --shadow-minimal-flat until T4, which is why it kept attracting call sites that wanted lift; the name now states the job, and it belongs to this chapter rather than §5.The One Means Rule. Each boundary picks one separator: a fill step, a line, or a shadow — never stacked on the same edge.
Default surfaces are flat. Depth comes first from the surface ladder, then a line, then shadow only when an element genuinely floats above the plane.
--elevation-raised (low), --elevation-overlay (med), --elevation-drag (high). A scale only gets used when product code can name it — the theme shipped three shadows for months and product CSS consumed one, because the names meant nothing at a call site.--surface-overlay fill + --border-soft ring + --elevation-overlay + overflow: hidden + container radius. No portal invents its own mix. The recipe is currently enforced on Astryx's shared layer surface through a vendor patch that adds a hook class and decides no values — a transitional implementation with a stated exit condition (patches/README.md), not a second styling path. Tooltips are out of scope: a transient label is not a surface that holds content.The One Working Plane Rule. Dividers separate responsibilities; cards and shadows do not fragment the workspace into a dashboard grid.
Nothing interactive is square. One ladder, assigned monotonically by box height:
| Radius | Tier | Assign to |
|---|---|---|
| 4px | inner | chips, keycaps, nested inlays inside a control |
| 6px | control | buttons, inputs, segmented items (≤ 36px tall) |
| 10px | card | cards, rows-as-cards, list containers (the single card value — 8/10/12 coexistence is over) |
| 12px | container | modals, panels, portal surfaces |
| 16px | page | page-level plates and hero surfaces |
| full | pill | badges, pills, circular controls |
border-radius: 0 is legal only on true full-bleed rows — an element flush with its container on both sides. Radius and gap move together: if it has breathing room, it has corners.Use the system UI stack with explicit platform CJK fallbacks; Geist Variable is a late fallback. Code uses Geist Mono Variable, JetBrains Mono, then platform monospace. Chinese and Latin must read as one interface.
The Role, Not Axes Rule. Choose an Astryx text role or a Maka role composed from it. Never assemble literal family, size, weight, or line height at a product call site.
The Four-Pixel Line Rule. Text line boxes land on the 4px grid. Mono is technical, never decorative.
The palette is cool-neutral and quiet; color is generated to spec, not picked by eye.
#71a8fd; it identifies Maka and is never the general CTA color.oklch() status washes at call sites are forbidden — consume the family. The family is --{status}-wash (0.08 fill) and --{status}-wash-border (0.24, ~3x the fill), every member derived with oklch(from var(--{status}) ...) so a status regeneration flows through it. A strong tier (0.12 / 0.40) exists for warnings about data destruction or an action the user cannot undo, and for nothing else — it is not the loud option for a notice that wants attention. Palette swatches are not washes: a swatch‘s job is to show a palette’s real colour, so its literals stay. The family is kept complete even where a rung has no consumer yet: a family with holes in it sends the next author back to hand-rolling an alpha, which is the etiology of the fourteen that drifted.The Signal, Not Texture Rule. Accent communicates action or state. Never use it as a background flood, gradient, glow, or substitute for hierarchy.
Use Astryx primitives as the default seam. New work composes product meaning through published props, tokens, and stable themeProps extension points; internal-DOM overrides are acknowledged transitional states, not precedent.
scale(0.98); keyboard focus is always visible. At most one inverted (filled) element per control. Hover washes come in exactly two lanes: product rows and controls take --state-hover-bg; chrome that must stay in lockstep with Astryx internals takes --color-overlay-hover. Hand-mixed hover alphas are drift.tabular-nums) and follows its parent's active state back to full ink. Never bolder than the label it counts.--border-strong, one step darker on hover, no painted track. Surfaces may hide their own bars; none may restyle them.An absence and a wait are both states of real content, and both are composed from Astryx primitives — never hand-rolled.
Every empty state is Astryx EmptyState; the tiers are parameter combinations, not new components, wrappers, or product CSS re-creating its layout. The tier count is three and not arbitrary, because the component‘s props offer exactly two real steps — adding a description and adding an action. That is the spec’s anti-inflation lock: a fourth tier is a component-change proposal, not a casual decision.
| Tier | Use | icon | description | actions | isCompact |
|---|---|---|---|---|---|
| 1 inline | a section's local absence | no | no | no | yes |
| 2 panel | a whole list/panel/inspector is empty | yes | yes | no | no |
| 3 first-run | the page's reason to exist has not happened yet | yes | yes | exactly one | no |
description.icon slot takes an icon glyph only — never an illustration, large graphic, or brand mark.ghost + sm), on any tier. This is usability, not decoration: the user is in a state they caused themselves and must be able to exit. The canonical example is the MCP market‘s no-match state. Ghost/small because clearing is an exit, not the page’s main action.description holds the full sentence and may echo the user‘s query. headingLevel follows the document-outline ladder (§9-adjacent; ratified in the typography chapter’s hierarchy), never a pinned number.The loading state occupies the same box the ready content will occupy — loading is the same box in an unready state, not filler. The hard criterion: switching from loading to ready causes zero layout shift. This is screenshot-verifiable and reviewed as such.
Three mutually exclusive forms, chosen by structural predictability — never by expected speed:
| Form | When | Geometry |
|---|---|---|
| skeleton | structure predictable (known rows/cards) | bar heights encode type (10/12/16); row count is that surface's measured ready-state constant, never a global default — a one-row skeleton grows the page on arrival, which violates the criterion above |
| spinner | structure unpredictable (single result, unknown-size body) | three placements only: inline (metadata icon size), page-centered large, or one quiet muted line inside a card (block-level unpredictable content only — lists always take skeletons) |
isLoading | busy buttons and pressable controls | the Astryx prop, always — no hand-swapped labels, icons, or disable-plus-spinner recreations |
data-pending and kin) to visible feedback or delete them.oklch() status color or wash at a call site — consume the generated families (§8).border-radius: 0 off a full-bleed row (§6).background: white or any literal surface color — resolve a ladder tier (§2), or --surface-paper when the content's own contrast is not ours to control (foreign documents, QR codes) and inverting it would break the content rather than restyle it.srgb and oklch derivations inside one token family (§3).