Next-gen visual-production skill for LLMs — ten channels of tool-use recipes (raster, SVG, web, interactive, video, science, gaze, terminal, dimensions, style-guides), a two-tier brand-token schema with motion, anti-patterns per channel, a multi-constraint solver that enforces 8:1 contrast at render time, and a vision-model critique agent grounded in Tufte / Bertin / Gestalt. Use when the user needs any visual artifact for human eyes.
muriel is a free, scriptable skill that teaches an LLM agent to produce every visual artifact a researcher-designer-engineer ships — from text source files that diff in git and regenerate from data. Ten named channels (raster, SVG, web, interactive, video, terminal, heatmaps, gaze, science, dimensions) each with rules and anti-patterns, four aesthetic vocabularies, a two-tier brand schema with motion tokens, a multi-constraint solver that keeps 8:1 contrast and OLED palette active at render time, and a vision-model critique agent grounded in Tufte / Bertin / Gestalt / Reichle / scanpath research that names what's wrong before ship.
Each channel has a dedicated subfile with deep recipes, tooling, and lessons. This top-level index is the map; the subfiles are the territory.
| Channel | Format | Deep dive |
|---|---|---|
| Raster | PNG/JPG | — Pillow, , store dimensions, fonts, inline pattern |
channels/raster.mdtypeset.py| Vector | SVG | channels/svg.md — svgwrite, cairosvg, Mermaid, Excalidraw, viewBox theming |
| Web | HTML + CSS | channels/web.md — marginalia, pandoc filter, Playwright/weasyprint capture, data-URI |
| Interactive | WebGL / Canvas / D3 | channels/interactive.md — single-file scaffold, PermalinkManager, CodePen, Observable |
| Video | MP4 / GIF | channels/video.md — ffmpeg + desktop-control + tooltip burn + editing recipes |
| Terminal | Unicode | channels/terminal.md — chart.py bar charts, sparklines, tables |
| Density viz | PNG | channels/heatmaps.md — Tobii-style Gaussian overlays |
| Gaze plots | PNG / SVG / JS | channels/gaze.md — scanpath, bubble, AOI timeline, saccade rose |
| Science | matplotlib + LaTeX | channels/science.md — rcparams, stats reporting, notebook editorial, paper figures |
| Dimensions | cross-channel reference | channels/dimensions.md — social cards, device footprints, viewport tiers, video, paper/print, favicons, scale factors |
| Style guides | brand schema | channels/style-guides.md — brand.toml schema, loader, rule enforcement, CSS/matplotlibrc derivation, example brand.toml files |
Design grammars worth naming explicitly when a project's visual register calls for something specific. A menu of established traditions — borrow their conventions, don't reinvent them.
vocabularies/fui.md — Fantasy / Fictional User Interface. Sci-fi HUDs. Perception NYC, Territory Studio, Ash Thorp, GMUNK lineage. Thin strokes, mono numerics, staggered reveals, radial geometry, restrained palettes.vocabularies/visible-language.md — Visible Language Workshop. The MIT Media Lab design tradition (Cooper, Small, Ishizaki, Maeda → Processing → pretext). Information landscapes, multi-scale typography, typography as data structure. Contemporary substrate: @chenglou/pretext. See also channels/interactive.md for the pretext API and the pretext-coachella reference exemplar.Additional vocabularies (Swiss grid, editorial, brutalist, newsprint) can be added here without restructuring the skill.
Codified from per-project bug fixes — apply to every channel:
viewBox / getBoundingClientRect first; auto-shrink on overflow.(230,228,210) cream on (10,10,15) near-black. Pure white is too harsh.For data-driven channels (raster plots, SVG, interactive JS, science), apply Tufte/Bertin/CRAP via three high-leverage patterns:
Reference: docs/PERMUTE.md — full Tufte/Bertin/Gestalt/CRAP framing.
When building interactive demos or UI affordances around the visuals, the design choices have empirical anchors:
a + b·log₂(D/W + 1). Big targets close to the cursor are fast; small targets far away are slow. Implication: primary controls go large and near the user's current attention point. Fisheye expansion is Fitts's Law made visible.a + b·log₂(n + 1). Decision time grows logarithmically with options. Implication: collapse n>7 options into hierarchy or progressive disclosure.vocabularies/visible-language.md for the full MIT Media Lab lineage this is drawn from.Use these as design rationale in figure captions and blog posts — the vocabulary is precise, the laws are quantified, and the lineage runs from psychophysics through typography to interaction design.
Whenever the user needs a visual artifact for human eyes — store assets, paper figures, blog post explainers, video demos, terminal output, scientific plots, infographics, screenshots, gaze visualizations. Invoke with /muriel followed by what's needed.
On the first render of any new visual artifact, offer to invoke the muriel-critique agent before the user reviews by eye. The agent (see agents/muriel-critique.md) reads the artifact, checks against universal rules + channel rules + optional brand tokens, and returns PASS / NEEDS REVISION / FAIL with cited evidence — catching the kind of pedantic fixes (double-letters, ornament/letter ratio, text-over-decoration, baseline drift, invisible-against-background) that would otherwise cost an iteration each.
The offer is one line:
"Want me to run muriel-critique on this first render? It checks against the universal rules + this artifact's channel rules before you commit."
Accept → spawn a subagent with subagent_type: muriel-critique and pass artifact: <path> (plus channel: and brand: if known). Decline → proceed; user can request critique explicitly later. Only offered on first renders, not subsequent iterations on the same artifact.
Keep critique OUT of per-tool generators (gen_dropcap.py, gen_animated_gif.py, etc.). The agent invocation is a separate step, not a pipeline stage — tools stay single-purpose, the user stays in the loop on when critique fires, and the agent's rubric can evolve independently of the tools it reviews.
When the task lands in a specific channel, read the corresponding subfile first before executing:
| If the task is… | Read |
|---|---|
| App store assets, icons, banners, wordmarks, Pillow compositing | channels/raster.md |
| Paper figures, data-driven diagrams, SVG theming, Mermaid, Excalidraw | channels/svg.md |
| Blog posts, marginalia pages, pandoc → HTML/PDF, web capture, data-URI | channels/web.md |
| Interactive demos, WebGL/Canvas/D3, CodePen, Observable, permalink state | channels/interactive.md |
| Product demo videos, ffmpeg, tooltip burn, GIF generation | channels/video.md |
| Unicode bar charts, sparklines, terminal output, README tables | channels/terminal.md |
| Tobii-style density heatmaps from fixation data | channels/heatmaps.md |
| Scanpath plots, AOI timelines, bubble scanpaths, saccade roses | channels/gaze.md |
| matplotlib figures, stats reporting, notebook editorial, LaTeX hooks | channels/science.md |
| "What size should this be?" — social card / device / viewport / paper / video dimensions | channels/dimensions.md |
| Loading a brand's design tokens, enforcing brand ownership rules, deriving CSS / rcparams from a brand | channels/style-guides.md |
| Sci-fi HUD aesthetic, FUI grammar, Territory/Perception lineage | vocabularies/fui.md |
| Multi-scale typography, information landscapes, pretext, Cooper/Small lineage | vocabularies/visible-language.md |
For a multi-channel task (e.g., a blog post with an interactive demo captured as a paper figure), read the relevant subfiles in order of primary channel first.
ImageFilter.GaussianBlur in render_text() shadow effect.muriel/typeset.py ships amazon-icon, amazon-small-icon, tvos-topshelf, play-feature templates via render_asset(template=...).generate_from_manifest("assets.json") in typeset.py.Device-framed product shots for app stores, blog heroes, social cards, case studies. The "classic marketing hero" channel that Photoshop and tools like Shottr / Xnapper own. muriel's version is brand-token-driven, reproducible, and composable.
muriel/tools/heroshot.py — inputs: source screenshot(s), target dimension (via muriel.dimensions), brand.toml, title text. Output: ship-ready PNG that composites: isotropic scaling (no anamorphic distortion), optional 3D CSS-skew for product-at-angle effect, title typography from the brand, device bezel / browser chrome / generic frame, brand-consistent border.brand.toml typography and 8:1 contrast on the title vs. the hero's dominant luminance.templates/bezels/.skew='none' | 'gentle' | 'aggressive' | 'product-3d'. Gentle = ±3° rotation; aggressive = perspective transform with vanishing point; product-3d = Mercury-ad-style with drop shadow. All respect motion_preference if the hero is animated.muriel.capture as a static PNG. Same tokens drive both paths.heroshot.app_store('iphone', screenshot, title, brand), heroshot.blog_hero('browser', screenshot, title, brand), heroshot.social_card('tight', screenshot, caption, brand).typeset.svg.gaze_ribbon(fixations) reusable across gaze studies.roughness:0, Helvetica, solid fills.--mg-* palette.permalink.js — Standalone PermalinkManager implementation for demos (URL hash ↔ parameter state).<mg-demo src="..."> custom element that lazy-loads an iframe..html.muriel/capture.py. capture_responsive(url, tiers=..., output_dir=...) writes retina PNGs for every tier in one call. CLI: python -m muriel.capture <url>. Playwright optional dependency.capture.py but captures parameter sweeps, not viewport sweeps.)@page rules for A4 + letter, figure captions, bibliography.marginalia/pandoc/marginalia.lua filter — Shipped as commit 4c66c16 on the marginalia repo.marginalia/pandoc/template.html — Shipped alongside filter.marginalia/pandoc/examples/us-constitution.md.channels/science.md subsection — rcparams defaults, stats reporting, notebook editorial, LaTeX hooks, worked recipes.muriel/matplotlibrc_dark.py + _light.py — Both rcparams blocks shipped as importable modules with graceful fallback when matplotlib is absent. Light variant matches the F explainer warm editorial palette.muriel/stats.py ships format_comparison, format_null, format_correlation, format_auc, format_chi2, format_exploratory, cohens_d, cohens_d_paired, fisher_ci, apa_number, format_p, format_ci. Standard library only. Enforces U+2212 minus signs, APA leading-zero stripping, detection-limit phrasing for nulls.muriel/contrast.py WCAG audit — Standard-library-only module with contrast_ratio, check_text_pair, audit_svg, and a python -m muriel.contrast file.svg CLI (exit 0 / 1 / 2 for CI use). Classifies CSS selectors as text / decorative / ambiguous via substring hints. Used for the 8:1 compliance audit that caught three failing text roles in word-fingerprints and four failing tokens in marginalia's light theme.muriel/dimensions.py screen-size constants — Size / Device / PaperSize NamedTuples with aspect labels and scale methods. 34 dotted-name registry entries (social cards, video, viewports), 17 device footprints with physical + CSS + scale factor, 5 paper sizes with DPI-aware pixel conversion, figsize_for() helper for 7 academic venues (CHI/ACM/IUI/IEEE/PNAS/Nature/LNCS), CLI self-test. Paired with channels/dimensions.md.muriel/contrast.py inline-fill pass — Current audit only walks <style> CSS; add a second pass that walks <text fill="…"> attributes for SVGs that set fills inline.muriel/contrast.py marginalia-token audit — Add a audit_marginalia_tokens() helper that reads marginalia.css and verifies every --mg-* custom property against both theme backgrounds.PERMUTE currently lives as docs/PERMUTE.md (Tufte/Bertin/Gestalt/CRAP principles applied as transformation operators). That framing is channel-agnostic, not ASCII-specific — it should sit alongside FUI / Visible Language / PixiJS / Kinetic Typography as a first-class grammar.
vocabularies/permute.md — PERMUTE as a named design grammar: data artifacts iterate through principled transformations (chart-type remap, sort-order remap, small-multiples, linked displays, semantic zoom, categorical ↔ sequential ↔ diverging color, aspect-ratio remap, medium remap).muriel/permute.py — Python helper that takes a data spec + permutation axis and emits multiple variants. Channel-agnostic; each channel's renderer is a backend. permute(data, axis="chart_type") → one output per viable chart type; permute(data, axis="medium") → terminal / raster / SVG / interactive D3.## Permutations section per channel — enumerate which permutations each channel supports, as testable assertions.muriel-permute agent (optional) — given a data artifact, rank alternative presentations against the communicative intent. Complements muriel-critique: one names what's wrong, the other proposes what would be better.muriel doesn't currently cover technical-architecture diagrams as a first-class channel. SVG is general; diagrams have their own conventions (lanes, swimlanes, node styles, arrow semantics). yizhiyanhua-ai/fireworks-tech-graph (MIT) is the reference implementation to borrow from.
channels/diagrams.md — UML (class / sequence / state / component / deployment), system-architecture (RAG pipelines, multi-agent orchestration, tool-call flows, data pipelines), flowcharts. Each pattern with muriel-voiced anti-patterns (don't draw boxes-and-arrows without a stated semantic for what the arrow means; don't use more than 3 shape types in one diagram; etc.).muriel/tools/diagram.py — input is a YAML/JSON system spec; output is brand-themed SVG via the active brand.toml (stroke = accent, fill = background_2, labels in typography.body_family, etc.). Inspiration: fireworks-tech-graph's NL-to-SVG pipeline, rewritten to consume the structured spec directly so it's deterministic and diff-able.muriel.tools.diagram. Keeps the render step deterministic while allowing prose authoring.brand.toml. Today colors.accent = "#50b4c8" only parses hex. Add an OKLCH path so brands can write colors.accent = "oklch(65% 0.15 220)" — hex stays supported; OKLCH becomes recommended for palettes where lightness ramps matter (muted → vibrant at constant hue). Same StyleGuide object internally; emit via to_css_vars(color_space='oklch'|'hex'). Inspiration from pbakaus/impeccable (Apache-2.0), which recommends OKLCH for perceptual uniformity in brand-palette design.channels/web.md now has a section on the F-explainer pattern, with the .outer-note / .stats-detail / .has-dropcap / staged-h2 extensions catalogued..outer-note and .stats-detail back into marginalia — Currently F-explainer-only; worth promoting to the main library if a second project adopts them.marginalia-md.js for projects that prefer browser-side conversion over pandoc.The canonical "raster without hand-compositing" path — not built yet; roadmap target. Pipeline shape:
prompt → [imagegen + brand.toml] → PNG → [enhance + target-tier] → [contrast.audit] → ship
channels/imagegen.md — dedicated channel for LLM-generated raster: multi-provider wrapper (Gemini, DALL-E, Flux, Nano Banana, Ideogram), brand-constraint-aware prompting, contrast validation on output, reproducibility (prompt+seed stored alongside PNG).muriel/imagegen.py — provider abstraction behind one API. Injects the active brand.toml tokens (palette, typography, rules) into the system prompt as generation constraints. Returns a dataclass with path, prompt, seed, provider, model, timestamp so every output is fully reproducible.muriel.contrast.audit_svg-equivalent on raster outputs (LLM imagegen rarely clears 8:1 without prompting); surface a "regenerate or accept" decision to the caller.muriel/tools/enhance.py — upscaling + artifact removal wrapper (Real-ESRGAN or equivalent). Platform-aware via the dimensions registry: enhance(img, target='twitter.instream') both resizes and enhances in one call. Pairs with muriel.dimensions named tiers.python -m muriel.imagegen "A foveated gaze heatmap over a SERP" --brand examples/muriel-brand.toml --dims twitter.instream --enhancechannels/raster.md — full worked pipeline from prompt through ship-ready PNG, citing each step's helper.muriel is the constraint layer; the engine that produces pixels is swappable. Each adapter injects the active brand.toml into the engine's prompt/config, polls async jobs, and routes the output back through muriel.contrast.audit before ship.
Default engines (free, already part of muriel):
muriel.typeset — Pillow compositing with brand tokens. Ships today; no TODO.muriel.chart — Unicode terminal charts. Ships today.Local / free engine adapters (TODO, high priority):
muriel/engines/flux_local.py — Flux via a local runtime (ComfyUI, diffusers, or ollama when supported). Zero cloud cost.muriel/engines/sdxl_local.py — Stable Diffusion XL via diffusers or ComfyUI. Zero cloud cost.muriel/engines/real_esrgan.py — local upscaling (2×/4×) via Real-ESRGAN PyTorch checkpoints. Free alternative to commercial upsamplers.muriel/engines/gemini.py — Google Gemini image gen via free tier + pay-as-you-go. Lowest activation energy for a cloud engine.Opt-in paid engine adapters (TODO, subscription required):
muriel/engines/firefly.py — Adobe Firefly API. Real capability (image5, creative upsampler, precise/adaptive composite, custom-brand models) but requires Adobe CC + Firefly credits per call. Ships as an optional engine; users without subs fall through to a free engine.muriel/engines/photoshop.py — local PS automation (UXP / ExtendScript / batch-actions). Free for users who already have Photoshop; still paid in aggregate (CC subscription).muriel/engines/dalle.py / muriel/engines/ideogram.py — optional commercial alternatives.Engine selection in brand.toml:
[engine] block: preferred = "pillow", fallback = ["flux_local", "gemini"], optional paid_ok = false. Let the brand declare its defaults; respect paid_ok = false to never call a metered endpoint.Positioning: muriel stays free-first and engine-agnostic. The differentiator is not "we integrate with Firefly" (many will); it's that the brand-tokens + contrast-audit + critique-agent layer is the same whether the engine is Pillow locally, Flux in a GPU container, Gemini's free tier, or a paid Firefly custom model. The constraint discipline is the product.
Different direction from render engines — these produce files the user can refine further in their preferred tool, with muriel's brand tokens already applied to layer names, groups, styles, type properties, and export settings.
muriel/authoring/psd.py — emit a layered .psd from a brief + brand.toml. Uses psd-tools or a similar library. Standalone (does not require PS to be running). Brand colors become fill layers, type layers use the brand's typography stack, groups follow the brand's semantic taxonomy. Open the output in Photoshop for manual refinement.muriel/authoring/photoshop_live.py — script an already-running Photoshop instance via UXP or ExtendScript. Requires PS to be open; useful when the user is actively working and wants muriel to inject a brand-compliant composition into their current document.muriel/authoring/figma.py — Figma Files API to create / update designs. Brand tokens map to Figma variables; muriel writes a starter file the team can iterate on. Free tier has limits; works for individual users and small teams.muriel/authoring/canva.py — Canva Connect API. Produces a Canva design with brand palette applied. Useful for marketing collateral workflows where the team continues editing in Canva.muriel/authoring/affinity.py — Affinity scripting (.afdesign, .afphoto). Free-to-own alternative to PSD.muriel/authoring/excalidraw.py — emit a .excalidraw JSON file (Excalidraw's native format — open, diff-able, editable). Brand colors become element fills/strokes; typography maps to Excalidraw's font stack. Pairs with yctimlin/mcp_excalidraw (MIT MCP server) for the refinement loop: muriel emits the source → agent refines in mcp_excalidraw's live canvas → muriel re-audits on re-export. Free-to-own, no subscription gate, natural authoring format for system-architecture diagrams.Authoring engines are complementary to render engines: render when you want a final PNG; author when you want a source file the user will continue to edit. Brand tokens apply identically across both paths.
Reference material (not direct swipes — shape inspiration):