chore(skill): Own tttk-authoring in the repo
Move the skill to .agents/skills/tttk-authoring and point gen:skill-reference at it. Activate it by symlinking ~/.agents/skills/tttk-authoring to the in-repo copy. Extend the reference drift guard to cover the skill copy now that it is versioned, and document activation in docs/development.md.
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
---
|
||||
name: tttk-authoring
|
||||
description: Author and edit TTRPG game design documents for the tttk (Tabletop Toolkit) CLI. Use when writing or modifying markdown content that uses tttk custom elements (:md-dice, :md-table, :md-deck, :md-pins, :md-commander, etc.), custom fenced code blocks (role=spark-table / declare / file / tag), companion CSV data files, or when previewing content with `tttk serve` and validating it with `tttk check`.
|
||||
---
|
||||
|
||||
# Authoring tttk game design documents
|
||||
|
||||
`tttk` (Tabletop Toolkit) renders a folder of markdown into an interactive TTRPG
|
||||
web app. A game design document is **ordinary GFM markdown** plus exactly two
|
||||
extensions:
|
||||
|
||||
1. **Custom elements** written with `marked-directive` syntax: `:name[content]{attrs}`.
|
||||
2. **Custom fenced code blocks** whose info string carries a `role=` attribute.
|
||||
|
||||
Everything else — headings, lists, tables, blockquotes, alerts, mermaid — is
|
||||
standard markdown. Do not invent element names, props, or roles; check
|
||||
`reference.md` when unsure. Deeper topics have their own files: `yaml-tag.md`
|
||||
(deck config), `csv.md` (data files), `layout.md` (columns), and `markdown.md`
|
||||
(alerts and mermaid).
|
||||
|
||||
## Workflow
|
||||
|
||||
- Content lives in a directory (commonly `content/`). Preview with hot reload:
|
||||
`npx tttk serve ./content` (default port 3000, override with `-p 8080`).
|
||||
- **Validate after every edit:** `npx tttk check ./content`. It exits `1` when
|
||||
there are errors and prints `file:line` diagnostics. Use `--json` for
|
||||
machine-readable output and `--quiet` for errors only. This is the primary
|
||||
verification loop: write → check → fix.
|
||||
- The scanner picks up `.md`, `.csv`, `.yarn`, and `.svg` files.
|
||||
- All paths in directives are **relative to the current markdown file**
|
||||
(`./data.csv`, `../shared/npcs.csv`, `./images/map.png`).
|
||||
- A typical game/adventure folder:
|
||||
```
|
||||
my-adventure/
|
||||
├── design.md # design notes (plain markdown)
|
||||
├── adventure.md # the document players/GM read
|
||||
├── locations.csv # data referenced by :md-table
|
||||
├── cast.csv
|
||||
└── route.csv
|
||||
```
|
||||
|
||||
## Directive syntax
|
||||
|
||||
```
|
||||
:name[content]{key="value" .class #id}
|
||||
```
|
||||
|
||||
- `[content]` is the directive body — a path, dice formula, color, etc.
|
||||
- `{key="value"}` sets attributes. `{.class}` and `{#id}` are shorthands.
|
||||
- Container directives use `::: name` … `:::`; nest deeper with `::::`, `:::::`.
|
||||
|
||||
Use the **registered element names**: `:md-dice`, not `:dice`. The full list is
|
||||
in `reference.md`.
|
||||
|
||||
## Code block roles
|
||||
|
||||
Fenced blocks are dispatched by the `role=` attribute in the info string:
|
||||
|
||||
| Info string | Behavior |
|
||||
|---|---|
|
||||
| ` ```csv role=spark-table ` | Body is CSV; replaced with `:md-table[./csv_<hash>]`. First column header must be a dice formula (`d6`, `2d6+1`, …). |
|
||||
| ` ```markdown role=spark-table ` | Body is a GFM pipe table; converted to CSV, then same as above. |
|
||||
| ` ```csv role=declare ` | Defines reactive variables / tag modifiers. Block is stripped. |
|
||||
| ` ```<lang> role=file ` | Body stored as text content, block stripped, referenced by its derived id. |
|
||||
| ` ```yaml role=tag ` | Kept intact; rendered as a custom element from YAML (`tag:`, `body:`, …). |
|
||||
| ` ```<lang> ` (no role) | Rendered as a normal visible code block. |
|
||||
| unknown role | Warned about and stripped. |
|
||||
|
||||
## Rules that prevent broken output
|
||||
|
||||
- Quote any attribute value containing spaces or `"`:
|
||||
`{pins="A:30,40 B:10,30"}`.
|
||||
- In CSV, wrap fields containing `,`, `"`, or a newline in double quotes and
|
||||
escape `"` as `""`. Lines starting with `#` are comments.
|
||||
- `label` and `body` are the conventional CSV columns. Put titles inside `body`
|
||||
as markdown headings; use **bold labels** for sub-fields, not headings.
|
||||
- A `group` column in a table CSV automatically produces group tabs — no
|
||||
attribute needed.
|
||||
- Reference each CSV once per document; don't repeat the same `:md-table`.
|
||||
- For card decks, use the `yaml role=tag` form with `tag: md-deck` (see
|
||||
`yaml-tag.md`). The `:md-deck[...]{...}` directive is legacy and can't express
|
||||
template layers or card backs.
|
||||
- Keep CSV files clean — header row plus data. Put deck config and templates in
|
||||
the YAML block's `data-config`, not in CSV front matter (see `csv.md`).
|
||||
- `{{prop}}` in a CSV `body` interpolates another column (or front-matter key)
|
||||
from the same row; with `{remix}` it pulls from a random row instead.
|
||||
|
||||
## Validation
|
||||
|
||||
`npx tttk check ./content` validates against the same content registry the app
|
||||
uses, so a clean check means the app resolves the content too. Errors exit `1`:
|
||||
|
||||
| Code | Meaning |
|
||||
|---|---|
|
||||
| `TTTK-S002` | container directive `:::` never closed |
|
||||
| `TTTK-D001` | unknown `:md-*` element name |
|
||||
| `TTTK-D002` | malformed attribute block (e.g. an unquoted value with a space) |
|
||||
| `TTTK-R001` | unknown code-block `role=` (the block is silently dropped) |
|
||||
| `TTTK-R003` | `role=declare` failed to parse |
|
||||
| `TTTK-R005` | `role=tag` YAML is invalid |
|
||||
| `TTTK-R006` | `role=tag` missing or naming an unknown element |
|
||||
| `TTTK-C001` | content reference does not resolve |
|
||||
| `TTTK-C004` | `:md-embed` cycle |
|
||||
| `TTTK-L001` | link target file does not exist |
|
||||
| `TTTK-V001` | CSV failed to parse (e.g. an unclosed quote) |
|
||||
|
||||
Warnings are usually real too:
|
||||
|
||||
| Code | Meaning |
|
||||
|---|---|
|
||||
| `TTTK-R002` | spark-table first header is not a dice formula |
|
||||
| `TTTK-R004` | `role=declare` row skipped (empty `key` or `expr`) |
|
||||
| `TTTK-C002` | reference resolves to an unexpected file type |
|
||||
| `TTTK-L002` | `#section` matches no heading |
|
||||
| `TTTK-A001` | icon missing under `assets/` |
|
||||
| `TTTK-A002` | image missing |
|
||||
| `TTTK-V002` | CSV row column count differs from the header |
|
||||
| `TTTK-V003` | duplicate `label` values |
|
||||
| `TTTK-V004` | `{{prop}}` is not a CSV column or front-matter key |
|
||||
| `TTTK-V005` | spark-table roll value is not a number or range |
|
||||
|
||||
`TTTK-C003` (info) flags the same CSV referenced twice by `:md-table` in one
|
||||
document. Icon checks assume no `assets/` directory exists above the checked
|
||||
directory. The full code catalog lives in `reference.md`.
|
||||
|
||||
## Reference
|
||||
|
||||
- `reference.md` — **generated** catalog of every custom element, inline
|
||||
directive, code block role, and dice notation. Do not edit by hand;
|
||||
regenerate from the repo with `npm run gen:skill-reference`.
|
||||
- `yaml-tag.md` — `role=tag` blocks, `data-config` layers, card backs, and the
|
||||
legacy `:md-deck` layer syntax.
|
||||
- `csv.md` — CSV authoring: columns, `{{prop}}` interpolation, quoting, and the
|
||||
discouraged front-matter form.
|
||||
- `layout.md` — column layout markers.
|
||||
- `markdown.md` — GitHub alerts and mermaid diagrams.
|
||||
- `examples/adventure.md` and `examples/encounters.csv` — a complete minimal
|
||||
document and its data file.
|
||||
@@ -0,0 +1,42 @@
|
||||
# CSV data files
|
||||
|
||||
**Keep CSV files clean** — a header row and data rows, nothing else. Put deck
|
||||
configuration and templates in the `yaml role=tag` block's `data-config`, not in
|
||||
the CSV. This keeps data portable and presentation in one place. See
|
||||
`reference.md` for the element and role catalog.
|
||||
|
||||
```csv
|
||||
label,body,name,type
|
||||
1,"### 火球术卷轴
|
||||
|
||||
一张泛黄的羊皮纸。",火球术卷轴,消耗品
|
||||
```
|
||||
|
||||
- `label` — unique row id (number or text).
|
||||
- `body` — main content, supports markdown.
|
||||
- Any other columns are custom fields.
|
||||
- `{{prop}}` in `body` interpolates another column from the same row (or a
|
||||
random row when the table uses `{remix}`).
|
||||
- Wrap values containing `,`, `"`, or newlines in double quotes; escape `"` as
|
||||
`""`. Lines beginning with `#` are comments.
|
||||
|
||||
## Front matter (avoid)
|
||||
|
||||
The parser also accepts YAML front matter delimited by `---`, which can declare
|
||||
`fields`, a `deck:` block, and arbitrary keys merged into every row. **Prefer
|
||||
not to use it** — it mixes presentation into the data file. Use the YAML tag
|
||||
block instead.
|
||||
|
||||
```csv
|
||||
---
|
||||
fields:
|
||||
- name: name
|
||||
description: 卡牌名称
|
||||
deck:
|
||||
size: 63x88
|
||||
grid: 5x5
|
||||
game_system: D&D 5e
|
||||
---
|
||||
label,body,name,type
|
||||
1,"### 火球术卷轴",火球术卷轴,消耗品
|
||||
```
|
||||
@@ -0,0 +1,68 @@
|
||||
# The Hollow Mill
|
||||
|
||||
## Adventure brief
|
||||
|
||||
The mill on the village edge has stood silent for a year. Last week its wheel
|
||||
began turning again — on its own.
|
||||
|
||||
> [!WARNING]
|
||||
> The mill's guardian is not hostile by default. Attacking it forfeits the
|
||||
> peaceful resolution.
|
||||
|
||||
## Locations
|
||||
|
||||
:md-table[./locations.csv]
|
||||
|
||||
## Random encounters
|
||||
|
||||
:md-table[./encounters.csv]{roll=true}
|
||||
|
||||
## The mill guardian
|
||||
|
||||
:md-dice[1d20+3]{key="guardian-attack"}
|
||||
|
||||
The guardian strikes with :[sword] for :md-dice[2d6+2]{key="guardian-damage"}.
|
||||
|
||||
## Encounter table (inline)
|
||||
|
||||
```csv role=spark-table roll=true
|
||||
d6,Encounter
|
||||
1,A crow watches from the rafters.
|
||||
2,The floorboards groan underfoot.
|
||||
3,A cold draft carries the smell of grain.
|
||||
4,The wheel outside stops for one breath.
|
||||
5,A sack of flour splits open.
|
||||
6,The guardian's lantern flickers to life.
|
||||
```
|
||||
|
||||
## Route
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[Village] --> B[Mill Road]
|
||||
B --> C{Mill Door}
|
||||
C -->|Force| D[Ground Floor]
|
||||
C -->|Listen| E[Secret Cellar]
|
||||
D --> F[Wheel Room]
|
||||
E --> F
|
||||
```
|
||||
|
||||
## Guardian card
|
||||
|
||||
```yaml role=tag
|
||||
tag: md-deck
|
||||
body: ./guardian.csv
|
||||
data-config:
|
||||
size: 63x88
|
||||
grid: 5x5
|
||||
layers:
|
||||
- prop: name
|
||||
pos: 1,1-5,1
|
||||
font: 12
|
||||
- template: |
|
||||
**{{type}}** — {{hp}} HP
|
||||
{{description}}
|
||||
pos: 1,3-5,8
|
||||
font: 3
|
||||
align: l
|
||||
```
|
||||
@@ -0,0 +1,36 @@
|
||||
group,label,body
|
||||
Mill Road,1,"### A crow on the fence
|
||||
|
||||
A crow fixes you with one eye, then flies toward the mill.
|
||||
|
||||
**Interaction**: Follow it (Perception 3/1)
|
||||
|
||||
**Risk**: None"
|
||||
Mill Road,2,"### Broken cart
|
||||
|
||||
An overturned cart blocks half the road.
|
||||
|
||||
**Interaction**: Clear it (Strength 3/2)
|
||||
|
||||
**Risk**: Failure costs 1 Stamina"
|
||||
Mill Road,3,"### Cold spot
|
||||
|
||||
The air turns suddenly cold.
|
||||
|
||||
**Interaction**: Press on (Constitution 3/1)
|
||||
|
||||
**Risk**: Failure deals 1 Constitution damage"
|
||||
Wheel Room,1,"### The turning wheel
|
||||
|
||||
The wheel grinds without water.
|
||||
|
||||
**Interaction**: Jam the gears (Strength 3/3)
|
||||
|
||||
**Risk**: Failure deals 1 Agility damage"
|
||||
Wheel Room,2,"### Flour dust
|
||||
|
||||
Thick dust fills the air.
|
||||
|
||||
**Interaction**: Cover your mouth (Constitution 3/1)
|
||||
|
||||
**Risk**: Failure deals 1 Constitution damage"
|
||||
|
@@ -0,0 +1,2 @@
|
||||
label,name,type,hp,description
|
||||
1,The Hollow Miller,Spirit,10,"Bound to the wheel by an old promise. Speaks only in the sound of grinding stone."
|
||||
|
@@ -0,0 +1,47 @@
|
||||
label,body
|
||||
Village,"### Village
|
||||
|
||||
**Type**: Starting location
|
||||
|
||||
A quiet village at the edge of the marsh. The miller's daughter asks after her
|
||||
father.
|
||||
|
||||
**Interaction**: Ask about the mill (Charisma 3/1)
|
||||
|
||||
**Supplies**: Rope, torches, one healing draught"
|
||||
Mill Road,"### Mill Road
|
||||
|
||||
**Type**: Route
|
||||
|
||||
A muddy track winding between hedgerows toward the mill.
|
||||
|
||||
**Interaction**: Spot tracks (Perception 3/1)
|
||||
|
||||
**Encounters**: 3 random encounters"
|
||||
Ground Floor,"### Ground Floor
|
||||
|
||||
**Type**: Hub
|
||||
|
||||
Sacks of grain, a cold hearth, and a ladder to the wheel room.
|
||||
|
||||
**Interaction**:
|
||||
- Search the hearth (Perception 3/2) — find a brass key
|
||||
- Climb the ladder (Agility 3/1)"
|
||||
Secret Cellar,"### Secret Cellar
|
||||
|
||||
**Type**: Archive
|
||||
|
||||
A low cellar beneath the millstones. Ledgers line the walls.
|
||||
|
||||
**Interaction**: Read the ledgers (Intelligence 3/2) — learn the guardian's name
|
||||
|
||||
**Clue**: The last entry is dated one year ago, in the miller's hand"
|
||||
Wheel Room,"### Wheel Room
|
||||
|
||||
**Type**: Finale
|
||||
|
||||
The great wheel turns in silence, though no water reaches it.
|
||||
|
||||
**Interaction**:
|
||||
- Speak the guardian's name (Charisma 3/3) — peaceful resolution
|
||||
- Break the wheel (Strength 3/4) — hostile resolution"
|
||||
|
@@ -0,0 +1,20 @@
|
||||
# Column layout
|
||||
|
||||
Wrap content in columns using line markers. This is a tttk extension on top of
|
||||
standard markdown; see `reference.md` for the element and role catalog.
|
||||
|
||||
```markdown
|
||||
|---
|
||||
Left column
|
||||
-|-
|
||||
Right column
|
||||
--|
|
||||
```
|
||||
|
||||
| Marker | Meaning |
|
||||
|---|---|
|
||||
| `\|---` | Start the column container |
|
||||
| `-\|-` | Divider between columns |
|
||||
| `--\|` | End the container |
|
||||
|
||||
Extra dashes add a `-N` class suffix (e.g. `-|--` → `col-2`) for custom widths.
|
||||
@@ -0,0 +1,39 @@
|
||||
# Alerts and mermaid
|
||||
|
||||
Standard markdown features that `tttk` renders. They are not tttk syntax — see
|
||||
`reference.md` for the element and role catalog.
|
||||
|
||||
## GitHub-style alerts
|
||||
|
||||
```markdown
|
||||
> [!NOTE]
|
||||
> 这是一条备注
|
||||
|
||||
> [!TIP]
|
||||
> 提示内容
|
||||
|
||||
> [!IMPORTANT]
|
||||
> 重要信息
|
||||
|
||||
> [!WARNING]
|
||||
> 警告信息
|
||||
|
||||
> [!CAUTION]
|
||||
> 危险警告
|
||||
```
|
||||
|
||||
## Mermaid diagrams
|
||||
|
||||
Fenced `mermaid` blocks render as diagrams:
|
||||
|
||||
````markdown
|
||||
```mermaid
|
||||
graph TD
|
||||
A[开始] --> B{选择}
|
||||
B -->|选项 1| C[结果 1]
|
||||
B -->|选项 2| D[结果 2]
|
||||
```
|
||||
````
|
||||
|
||||
Supported: flowchart, sequenceDiagram, classDiagram, stateDiagram, erDiagram,
|
||||
pie.
|
||||
@@ -0,0 +1,365 @@
|
||||
# tttk syntax reference
|
||||
|
||||
Generated from `src/cli/validate/known.ts` by `npm run gen:reference`.
|
||||
Do not edit by hand.
|
||||
|
||||
Complete catalog of the custom elements, inline directives, and code block
|
||||
roles that `tttk` understands. Everything not listed here is standard GFM
|
||||
markdown. Element names are prefixed `md-` and must match exactly.
|
||||
|
||||
## Contents
|
||||
|
||||
- [Directive syntax](#directive-syntax)
|
||||
- [Custom elements](#custom-elements)
|
||||
- [Inline directives](#inline-directives)
|
||||
- [Code block roles](#code-block-roles)
|
||||
- [Dice notation](#dice-notation)
|
||||
- [CSV data files](#csv-data-files)
|
||||
|
||||
## Directive syntax
|
||||
|
||||
```text
|
||||
:name[content]{key="value" .class #id}
|
||||
```
|
||||
|
||||
| Part | Meaning | Example |
|
||||
|---|---|---|
|
||||
| `:name` | Registered element name | `:md-dice` |
|
||||
| `[content]` | Directive body | `[2d6+d8]` |
|
||||
| `{attrs}` | Attributes | `{key="attack"}` |
|
||||
| `{.class}` | Shorthand for `class` | `{.text-red-500}` |
|
||||
| `{#id}` | Shorthand for `id` | `{#my-dice}` |
|
||||
|
||||
Container directives:
|
||||
|
||||
```markdown
|
||||
::: container
|
||||
Content here, including nested directives.
|
||||
:dice[1d20]
|
||||
:::
|
||||
```
|
||||
|
||||
Nest with more colons (`::::`, `:::::`).
|
||||
|
||||
## Custom elements
|
||||
|
||||
| Directive | Body | Key attributes |
|
||||
|---|---|---|
|
||||
| `:md-dice` | dice formula | `key` |
|
||||
| `:md-table` | CSV path or inline id | `roll`, `remix` |
|
||||
| `:md-deck` | CSV path | `size`, `grid`, `layers` |
|
||||
| `:md-link` | path, optional #section | — |
|
||||
| `:md-embed` | path, optional #section | `headingBase` |
|
||||
| `:md-bg` | image path or CSS color | `fit` |
|
||||
| `:md-border` | color or border image | `width`, `slice` |
|
||||
| `:md-font` | font family | `source`, `weight`, `color` |
|
||||
| `:md-pins` | image path | `pins`, `fixed`, `labelStart` |
|
||||
| `:md-commander` | command template CSV | `placeholder`, `class`, `height` |
|
||||
| `:md-yarn-spinner` | Yarn Spinner file | `start` |
|
||||
|
||||
### `:md-dice`
|
||||
|
||||
Clickable dice roller; `key` stores the result in the URL.
|
||||
|
||||
Clicking the text rolls; clicking the 🎲 resets to the formula. With `key`, the result is stored in the URL as `?dice-<key>=<value>`.
|
||||
|
||||
- Body: dice formula
|
||||
- Attributes: `key`
|
||||
|
||||
```markdown
|
||||
:md-dice[1d20+5]{key="attack"}
|
||||
```
|
||||
|
||||
```markdown
|
||||
:md-dice[2d6+3]
|
||||
```
|
||||
|
||||
```markdown
|
||||
:md-dice[3d6kh1]
|
||||
```
|
||||
|
||||
### `:md-table`
|
||||
|
||||
Tab-separated view of a CSV with optional group tabs.
|
||||
|
||||
`roll` adds a 🎲 random-tab button; `remix` makes `{{prop}}` references pull from a random row. A `group` column in the CSV produces group tabs automatically.
|
||||
|
||||
- Body: CSV path or inline id
|
||||
- Attributes: `roll`, `remix`
|
||||
|
||||
```markdown
|
||||
:md-table[./npcs.csv]
|
||||
```
|
||||
|
||||
```markdown
|
||||
:md-table[./encounters.csv]{roll=true}
|
||||
```
|
||||
|
||||
```markdown
|
||||
:md-table[./quests.csv]{roll=true remix=true}
|
||||
```
|
||||
|
||||
### `:md-deck`
|
||||
|
||||
Card layout from a CSV (legacy; prefer `yaml role=tag`).
|
||||
|
||||
Prefer the `yaml role=tag` form — it is the only form that supports template layers, per-layer alignment, and card backs. The directive form is legacy and only supports the compact `layers` string.
|
||||
|
||||
- Body: CSV path
|
||||
- Attributes: `size`, `grid`, `layers`
|
||||
|
||||
```markdown
|
||||
:md-deck[./cards.csv]{size="63x88" grid="5x5" layers="title:1,1-5,1f8 body:1,5-5,8f3"}
|
||||
```
|
||||
|
||||
### `:md-link`
|
||||
|
||||
Clickable link that expands the target article inline.
|
||||
|
||||
- Body: path, optional #section
|
||||
|
||||
```markdown
|
||||
:md-link[./rules.md]
|
||||
```
|
||||
|
||||
```markdown
|
||||
:md-link[./rules.md#combat]
|
||||
```
|
||||
|
||||
### `:md-embed`
|
||||
|
||||
Renders the target article or section inline.
|
||||
|
||||
`headingBase` shifts the heading levels of the embedded content.
|
||||
|
||||
- Body: path, optional #section
|
||||
- Attributes: `headingBase`
|
||||
|
||||
```markdown
|
||||
:md-embed[./rules.md#combat]{headingBase=1}
|
||||
```
|
||||
|
||||
### `:md-bg`
|
||||
|
||||
Sets the surrounding article card background.
|
||||
|
||||
`fit` is one of `cover` (default), `contain`, `fill`, `none`, `scale-down`.
|
||||
|
||||
- Body: image path or CSS color
|
||||
- Attributes: `fit`
|
||||
- Body may also be a CSS color (e.g. `#2a1a3a`, `crimson`).
|
||||
|
||||
```markdown
|
||||
:md-bg[./images/dungeon-bg.jpg]{fit="cover"}
|
||||
```
|
||||
|
||||
```markdown
|
||||
:md-bg[#2a1a3a]
|
||||
```
|
||||
|
||||
### `:md-border`
|
||||
|
||||
Applies a border to the article card.
|
||||
|
||||
Body is a CSS color or a border-image path. Class flags select sides (`t b l r all`), style (`solid dashed dotted double groove ridge inset outset none`), and image repeat (`stretch repeat round space`).
|
||||
|
||||
- Body: color or border image
|
||||
- Attributes: `width`, `slice`
|
||||
- Body may also be a CSS color (e.g. `#2a1a3a`, `crimson`).
|
||||
|
||||
```markdown
|
||||
:md-border[crimson]{.b}
|
||||
```
|
||||
|
||||
```markdown
|
||||
:md-border[./frame.png]{width="2mm" slice="10" .all.round}
|
||||
```
|
||||
|
||||
### `:md-font`
|
||||
|
||||
Sets the article font.
|
||||
|
||||
`source` is `google`, `emfont`, or `local` (default).
|
||||
|
||||
- Body: font family
|
||||
- Attributes: `source`, `weight`, `color`
|
||||
|
||||
```markdown
|
||||
:md-font[Noto Sans SC]{source="google" weight="700" color="#1a1a2e"}
|
||||
```
|
||||
|
||||
### `:md-pins`
|
||||
|
||||
Numbered markers on an image.
|
||||
|
||||
`pins` is a space-separated list of `label:x,y` (percent coordinates). `fixed` makes it read-only; otherwise click to add and click a pin to remove. `labelStart` sets the first label (letter or number).
|
||||
|
||||
- Body: image path
|
||||
- Attributes: `pins`, `fixed`, `labelStart`
|
||||
|
||||
```markdown
|
||||
:md-pins[./images/battle-map.png]{pins="A:25,50 B:75,30 C:50,80" fixed}
|
||||
```
|
||||
|
||||
```markdown
|
||||
:md-pins[./images/dungeon.png]{labelStart="1"}
|
||||
```
|
||||
|
||||
### `:md-commander`
|
||||
|
||||
Interactive command history / tracker panel.
|
||||
|
||||
The body is a CSV of command templates (`command,parameter,label,description,insertedText`).
|
||||
|
||||
- Body: command template CSV
|
||||
- Attributes: `placeholder`, `class`, `height`
|
||||
|
||||
```markdown
|
||||
:md-commander[./commands.csv]{placeholder="输入命令…" height="400px"}
|
||||
```
|
||||
|
||||
### `:md-yarn-spinner`
|
||||
|
||||
Runs a Yarn Spinner dialogue file.
|
||||
|
||||
`start` names the entry node (default `start`).
|
||||
|
||||
- Body: Yarn Spinner file
|
||||
- Attributes: `start`
|
||||
|
||||
```markdown
|
||||
:md-yarn-spinner[./story.yarn]{start="start"}
|
||||
```
|
||||
|
||||
## Inline directives
|
||||
|
||||
### `:[name]`
|
||||
|
||||
Inserts an icon looked up under `assets/` (searching upward from the file).
|
||||
|
||||
Default extension is `.png`; `.svg`, `.gif`, `.jpg`, `.jpeg`, and `.webp` are also supported. `label` overlays text, `repeat` duplicates N times, and `join` places a tag name between repeats.
|
||||
|
||||
- Body: icon name
|
||||
- Attributes: `label`, `repeat`, `join`, `class`
|
||||
|
||||
```markdown
|
||||
:[attack] :[shield.svg] :[potion]
|
||||
```
|
||||
|
||||
```markdown
|
||||
:[attack]{label=ATK repeat=3 join=br}
|
||||
```
|
||||
|
||||
### `:cmd[command]`
|
||||
|
||||
Renders a clickable span that dispatches a journal command.
|
||||
|
||||
- Body: journal command
|
||||
- Attributes: `label`
|
||||
|
||||
```markdown
|
||||
:cmd[roll 1d20+5]{label=Roll initiative}
|
||||
```
|
||||
|
||||
```markdown
|
||||
:cmd[spark dungeon room-type]{label=Random room}
|
||||
```
|
||||
|
||||
## Code block roles
|
||||
|
||||
The `role=` attribute on the info string determines how a fenced block is
|
||||
handled.
|
||||
|
||||
### `role=spark-table`
|
||||
|
||||
CSV (or markdown pipe table) rendered as `:md-table`.
|
||||
|
||||
The first column header must be a dice formula; each row's first cell is the roll value (a number, or a range like `1-3`). Extra fence attributes are forwarded to the generated `:md-table`, e.g. ` ```csv role=spark-table roll=true `.
|
||||
|
||||
````markdown
|
||||
```csv role=spark-table
|
||||
d6,结果
|
||||
1,遭遇强盗
|
||||
2,平安无事
|
||||
```
|
||||
````
|
||||
|
||||
````markdown
|
||||
```markdown role=spark-table
|
||||
| d6 | 结果 |
|
||||
|----|------|
|
||||
| 1 | 遭遇强盗 |
|
||||
| 2 | 平安无事 |
|
||||
```
|
||||
````
|
||||
|
||||
### `role=declare`
|
||||
|
||||
Reactive variable / tag-modifier definitions.
|
||||
|
||||
Columns (any order): `tag`, `threshold`, `key`, `expr`. A row with an empty `tag` declares a variable; a row with a `#tag` applies a modifier when that tag is active.
|
||||
|
||||
````markdown
|
||||
```csv role=declare
|
||||
tag,threshold,key,expr
|
||||
,,$hp,$con*5+$mod_hp
|
||||
#warrior,2,$mod_hp,20
|
||||
```
|
||||
````
|
||||
|
||||
### `role=file`
|
||||
|
||||
Stored as inline text content and stripped.
|
||||
|
||||
Referenced by the derived inline id, e.g. `:md-embed[./text_<hash>]`.
|
||||
|
||||
````markdown
|
||||
```text role=file
|
||||
Stored as inline content.
|
||||
```
|
||||
````
|
||||
|
||||
### `role=tag`
|
||||
|
||||
YAML rendered as a custom element.
|
||||
|
||||
`tag:` names the element, `body:` becomes its content, and any other key becomes an attribute. Fence-line attributes override the YAML body. See `yaml-tag.md`.
|
||||
|
||||
````markdown
|
||||
```yaml role=tag
|
||||
tag: md-deck
|
||||
body: ./cards.csv
|
||||
size: 63x88
|
||||
grid: 5x5
|
||||
```
|
||||
````
|
||||
|
||||
### No role / unknown role
|
||||
|
||||
No role → a normal visible code block. Unknown role → warned about and
|
||||
stripped.
|
||||
|
||||
## Dice notation
|
||||
|
||||
Used by `:md-dice`, spark-table headers, and the journal `/roll`:
|
||||
|
||||
| Notation | Meaning |
|
||||
|---|---|
|
||||
| `2d6` | two six-sided dice |
|
||||
| `1d20+5` | d20 plus modifier |
|
||||
| `3d6kh1` | keep highest 1 |
|
||||
| `3d6dl2-1` | drop lowest 2, minus 1 |
|
||||
| `{d4, d8}` | choose one of the listed dice |
|
||||
|
||||
## CSV data files
|
||||
|
||||
Keep CSV files clean — a header row and data rows. Put deck configuration
|
||||
and templates in a `yaml role=tag` block's `data-config`.
|
||||
|
||||
- `label` — unique row id (number or text).
|
||||
- `body` — main content, supports markdown.
|
||||
- Any other columns are custom fields.
|
||||
- `{{prop}}` in `body` interpolates another column (or front-matter key)
|
||||
from the same row; with `{remix}` it pulls from a random row.
|
||||
- Wrap values containing `,`, `"`, or newlines in double quotes; escape
|
||||
`"` as `""`. Lines beginning with `#` are comments.
|
||||
@@ -0,0 +1,66 @@
|
||||
# YAML tag blocks
|
||||
|
||||
`` ```yaml role=tag `` renders a custom element from YAML. `tag` names the
|
||||
element, `body` becomes its content, and any other key becomes an attribute.
|
||||
Fence-line attributes override the YAML body. See `reference.md` for the
|
||||
element and role catalog.
|
||||
|
||||
````markdown
|
||||
```yaml role=tag
|
||||
tag: md-deck
|
||||
body: ./cards.csv
|
||||
data-config:
|
||||
size: 63x88
|
||||
grid: 5x5
|
||||
layers:
|
||||
- prop: title
|
||||
pos: 1,1-5,1
|
||||
font: 12
|
||||
- template: |
|
||||
**{{name}}** — {{type}}
|
||||
{{description}}
|
||||
pos: 1,3-5,8
|
||||
font: 3
|
||||
align: l
|
||||
```
|
||||
````
|
||||
|
||||
Simple cases can stay on the fence line:
|
||||
|
||||
````markdown
|
||||
```yaml role=tag tag=md-dice body=3d6
|
||||
```
|
||||
````
|
||||
|
||||
## `data-config` layers
|
||||
|
||||
Layers accept:
|
||||
|
||||
- `prop` — read a CSV column, or `template` — markdown with `{{var}}`
|
||||
substitution.
|
||||
- `pos` — `x1,y1-x2,y2` grid span (1-based).
|
||||
- `font` / `fontSize` — font size in mm.
|
||||
- `orientation` — `n` / `s` / `e` / `w`.
|
||||
- `align` — `l` / `c` / `r`, optionally prefixed `t` / `b`.
|
||||
- `visible` — show or hide the layer.
|
||||
|
||||
Use `back_layers` for card backs.
|
||||
|
||||
## Legacy `:md-deck` directive
|
||||
|
||||
`:md-deck[./cards.csv]{...}` still works but is **legacy** — prefer the YAML tag
|
||||
block above. It only supports the compact `layers` string and cannot express
|
||||
template layers or backs.
|
||||
|
||||
```markdown
|
||||
:md-deck[./cards.csv]{size="63x88" grid="5x5" layers="title:1,1-5,1f8 body:1,5-5,8f3"}
|
||||
```
|
||||
|
||||
Compact layer syntax: `prop:x1,y1-x2,y2[f<size>][nsew][[t|b]lcr]`
|
||||
|
||||
- `prop` — CSV column to render.
|
||||
- `x1,y1-x2,y2` — grid span (1-based).
|
||||
- `f<size>` — font size in mm, e.g. `f8`.
|
||||
- `nsew` — text orientation (default `n`).
|
||||
- `l` / `c` / `r` — horizontal align, optionally prefixed `t` / `b` for vertical
|
||||
(`tl`, `bc`, …).
|
||||
Reference in New Issue
Block a user