();
+ }
+}
diff --git a/MarkupString.slnx b/MarkupString.slnx
index f5a6ed7..e30a8b9 100644
--- a/MarkupString.slnx
+++ b/MarkupString.slnx
@@ -2,6 +2,7 @@
+
diff --git a/README.md b/README.md
index ca911d8..757af30 100644
--- a/README.md
+++ b/README.md
@@ -31,6 +31,7 @@ layout engine: wrap, justify, fill, and assemble columns into aligned rows.
dotnet add package MarkupString
dotnet add package MarkupString.Ansi
dotnet add package MarkupString.Html
+dotnet add package MarkupString.Mxp
```
| Package | What it gives you |
@@ -38,6 +39,7 @@ dotnet add package MarkupString.Html
| [`MarkupString`](https://www.nuget.org/packages/MarkupString) | The `MarkupText` type, runs, formats, the registry, the emitter/codec contracts, the JSON serializer, grapheme and display-width helpers. No rendering opinions. |
| [`MarkupString.Ansi`](https://www.nuget.org/packages/MarkupString.Ansi) | Terminal styling: colours (16 / xterm-256 / truecolor), attributes, links; an `ansi()` code parser and an escape-sequence parser; emitters for ANSI, HTML, Pueblo, MXP and BBCode; and `AnsiCss`, the stylesheet for the `ms-*` classes the HTML emitters write. |
| [`MarkupString.Html`](https://www.nuget.org/packages/MarkupString.Html) | Raw HTML tag markup — an anchor, a ``, a `` — with checked construction and tag policies for untrusted input. |
+| [`MarkupString.Mxp`](https://www.nuget.org/packages/MarkupString.Mxp) | MXP's own elements — sounds, images, gauges, status text, frames — as MXP tags, as the nearest HTML, and as nothing for a client with neither. |
The core package renders nothing on its own: emitters live in the kind packages, so a consumer
that only needs one of them pays for one of them, and a kind of your own is a first-class peer
diff --git a/docs/formats.md b/docs/formats.md
index 66ecb1c..763ac4a 100644
--- a/docs/formats.md
+++ b/docs/formats.md
@@ -102,6 +102,34 @@ var registry = MarkupRegistry.Empty.WithAnsi().WithHtml().With(new PreFramer());
One framer per format; the last one registered wins.
+## MXP's own elements
+
+`MarkupString.Mxp` carries the elements MXP defines beyond styling and links — `SOUND`, `MUSIC`,
+`IMAGE`, `GAUGE`, `STAT`, `FRAME`, `VAR`, `EXPIRE`, `RELOCATE`, `USER`, `PASSWORD`, `NOBR`, `SBR` — and
+anything else through `MxpElement` itself.
+
+```csharp
+MarkupRegistry.Default = MarkupRegistry.Empty.WithAnsi().WithHtml().WithMxp();
+
+var line = MarkupText.Concat(
+ MxpElements.Sound("door.wav", volume: 80, url: "https://example.test/sounds/"),
+ MarkupText.Plain("The door creaks open."));
+
+line.Render(MarkupFormat.Mxp); // The door creaks open.
+line.Render(MarkupFormat.Html); // The door creaks open.
+line.Render(MarkupFormat.Ansi); // The door creaks open.
+```
+
+An element that wraps nothing is a point in the text; one that wraps content (`FRAME`, `VAR`) marks the
+text it applies to. **A format with no MXP writes nothing at all**, including the zero-width carrier a
+standalone element rides on, so one piece of text is safe to send to every client — a terminal is sent
+neither the tag nor the character it rode on.
+
+`WithMxp(element => ...)` holds what is written to what the client said it can render, from MXP's
+`` exchange at the telnet layer. A refused element is written as a format without MXP writes it
+— nothing, or the content alone for one that wraps, so a `FRAME` a client cannot open does not take the
+text inside it with it. With no predicate every element is written: never asked is not refused.
+
## Line framers
An `ILineFramer` writes a prefix at the start of every line that has content — a line holding