Malevich

AI-Things / Skills / Skill/

Malevich canvas import

Translate the visual taste of a reference — an existing site, a set of screenshots, or a brand book — into a Malevich `canvas.md`, so the system generates a faithful token set instead of hand-tuned CSS.

foundations · advanced · updated 2026-05-21

What this skill does

A canvas.md is a markdown file that describes a project's visual taste — palette, typography, surfaces, motion, default effects, icons — and from which Malevich generates the foundations and semantic token tiers. Change the canvas, and every visual surface re-flows with no component edits.

This skill is about importing taste from a reference into a canvas.md. The input is a thing that already has a look — a live site, screenshots, a Figma file, a brand guideline PDF — and the output is a canvas that, when parsed by @malevich/canvas, reproduces that look as closely as the system allows. Read llm-wiki/canvas-spec.md in full before starting; it is authoritative for the file format, section layout, parsing rules, and validation. This skill is the procedure; the spec is the contract.

The mindset — describe taste, not CSS

A canvas is a brand book a machine can act on: opinionated, declarative, short — typically 50–200 lines. Your job when importing is to read the reference and express its decisions at the highest level that still captures them. Resist transcribing pixel values. If the reference uses a 1.5 type ratio, say hierarchy: dramatic, don't list every heading size. The whole point of the system is that you describe intent and the parser derives the scale.

Things a canvas must NOT do (anti-patterns from the spec): define raw component CSS, list every color shade, set a component-specific token for every component, duplicate the default canvas, or encode application logic. When you catch yourself doing any of these, step back up a level.

Step 0 — Read the reference deliberately

Before writing anything, extract the reference's decisions in each of these dimensions. Note what is intentional vs. incidental:

If the reference is screenshots or a PDF, sample real values (use a color picker for hex, measure type sizes). If it's a live site, read the computed styles. Capture decisions, not every declaration.

Step 1 — Scaffold the sections

A canvas.md uses headings as section boundaries. Lay out the skeleton, then fill it. Section order aids readability but doesn't affect parsing; omit any section to inherit the default canvas.

# <Project name>

<one-line description>

## Identity
## Palette
## Typography
## Surfaces
## Motion
## Effects
## Icons
## Overrides

Inside sections, use key: value lines for flat fields and fenced ```yaml blocks where structure is needed. Both are valid.

Step 2 — Palette (required)

At minimum a canvas defines ink, canvas, and accent; everything else derives from these three (the parser auto-generates a tonal scale via OKLCH). Override status and surface keys only where the reference is specific.

## Palette

### Core
ink: #0A0A0A
canvas: #FAFAFA

### Accent
accent: #D32F1E
accent-strong: #B02817
accent-muted: #F5C4BE

### Status
danger: #D32F1E
warning: #E8B547
success: #2A8A3E
info: #1A4FB5

If the reference has a real dark mode, declare both under ### Light and ### Dark subsections; otherwise give a single set. Prefer oklch() for new values — it is perceptually uniform. The parser validates contrast (ink-strong on canvas must hit AAA, ink-regular AA, etc.) and warns without blocking; honor those warnings rather than overriding blindly.

Step 3 — Typography and the three axes (required)

This is where "the same fonts arranged differently" is controlled. Declare the three font families, then set the three voice axes — these are the import's most important judgement calls.

## Typography

### Families
display: NAMU 1930
regular: NAMU Pro
mono: JuliaMono

### Voice
tone: tight
character: standard
hierarchy: dramatic

### Cases
heading-case: sentence
overline-case: uppercase
action-case: sentence

The three axes:

Font names must resolve to an @font-face in @malevich/core/fonts/ — the parser does not fetch fonts from the internet. If the reference uses a font Malevich doesn't ship, either map to the closest available family or flag it for the human; don't invent a web-font load.

Step 4 — Surfaces (optional)

Radii, shadows, and spacing, expressed as named styles rather than per-token values:

## Surfaces

### Radii
style: sharp        # sharp | soft | round

### Shadows
style: subtle       # none | subtle | pronounced | dramatic

### Spacing
density: regular    # compact | regular | comfortable

Reach for a per-token override (radius-card: 6px) only when a named style gets one specific surface wrong — not as the default mode of working.

Step 5 — Motion (optional)

## Motion

style: subtle       # none | subtle | standard | expressive
respects-reduced-motion: true

style is shorthand for a fast/base/slow/easing set; individual fields override it. Keep respects-reduced-motion: true unless the reference has a deliberate reason otherwise.

Step 6 — Effects (optional)

Declares default decorative treatments applied to components or variants. In the v1.0 model these are modifiers from the seven categories (background, surface, effect, shader) — so an effects default is really "apply this modifier by default to this target." Express the reference's recurring treatments here, sparingly:

## Effects

### Defaults

- target: button.-accent
  apply: effect=glow

- target: card.-feature
  apply: background=gradient

Each entry names a target (component + optional variant) and the modifier to apply by default. Only declare a default when the treatment is genuinely systemic in the reference; one-off flourishes belong in the markup, not the canvas. Confirm the target component accepts that modifier category in packages/components/modifiers/applicability.json — a default that the matrix forbids will warn.

Step 7 — Icons (optional)

## Icons

weight: regular
stroke-width: 2
fallback: static

Map the reference's icon feel to weight and stroke. Malevich icons are a pluggable pack (ADR-0011); the canvas expresses preference, the pack supplies the glyphs.

Step 8 — Overrides (optional, last resort)

Explicit token-level overrides for the rare case where higher-level configuration can't express a decision. They are the last word, applied after generation.

## Overrides

### Semantic
radius-card: 12px

### Component-specific
button-background-accent: linear-gradient(135deg, #D32F1E, #B02817)

Use sparingly. A canvas heavy with overrides is a signal the taste wasn't captured at the right level — go back to the axes and styles first.

Step 9 — Validate and report

Run the canvas through @malevich/cli (apply-canvas) and read both lists it returns:

Resolve every error. For each warning, decide consciously: fix it, or record why you're accepting it. Then produce a short import report: which reference decisions mapped cleanly, which were approximated (and why), and which couldn't be expressed and need a human call.

Output checklist