markgraphy 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.txt +21 -0
- package/README.md +154 -0
- package/dist/animated/GraphAgni.svelte +430 -0
- package/dist/animated/GraphAgni.svelte.d.ts +22 -0
- package/dist/animated/GraphAum.svelte +294 -0
- package/dist/animated/GraphAum.svelte.d.ts +20 -0
- package/dist/animated/GraphBoot.svelte +325 -0
- package/dist/animated/GraphBoot.svelte.d.ts +28 -0
- package/dist/animated/GraphCron.svelte +476 -0
- package/dist/animated/GraphCron.svelte.d.ts +42 -0
- package/dist/animated/GraphDamru.svelte +341 -0
- package/dist/animated/GraphDamru.svelte.d.ts +18 -0
- package/dist/animated/GraphDeps.svelte +245 -0
- package/dist/animated/GraphDeps.svelte.d.ts +29 -0
- package/dist/animated/GraphDiya.svelte +330 -0
- package/dist/animated/GraphDiya.svelte.d.ts +22 -0
- package/dist/animated/GraphFire.svelte +246 -0
- package/dist/animated/GraphFire.svelte.d.ts +22 -0
- package/dist/animated/GraphFlame.svelte +258 -0
- package/dist/animated/GraphFlame.svelte.d.ts +14 -0
- package/dist/animated/GraphFlowPlayer.svelte +346 -0
- package/dist/animated/GraphFlowPlayer.svelte.d.ts +27 -0
- package/dist/animated/GraphGanga.svelte +305 -0
- package/dist/animated/GraphGanga.svelte.d.ts +20 -0
- package/dist/animated/GraphHash.svelte +251 -0
- package/dist/animated/GraphHash.svelte.d.ts +20 -0
- package/dist/animated/GraphJapa.svelte +253 -0
- package/dist/animated/GraphJapa.svelte.d.ts +20 -0
- package/dist/animated/GraphLife.svelte +312 -0
- package/dist/animated/GraphLife.svelte.d.ts +27 -0
- package/dist/animated/GraphMandala.svelte +262 -0
- package/dist/animated/GraphMandala.svelte.d.ts +24 -0
- package/dist/animated/GraphMandel.svelte +290 -0
- package/dist/animated/GraphMandel.svelte.d.ts +27 -0
- package/dist/animated/GraphPulse.svelte +248 -0
- package/dist/animated/GraphPulse.svelte.d.ts +24 -0
- package/dist/animated/GraphRain.svelte +280 -0
- package/dist/animated/GraphRain.svelte.d.ts +22 -0
- package/dist/animated/GraphScatter.svelte +395 -0
- package/dist/animated/GraphScatter.svelte.d.ts +31 -0
- package/dist/animated/GraphScope.svelte +197 -0
- package/dist/animated/GraphScope.svelte.d.ts +22 -0
- package/dist/animated/GraphSequence.svelte +247 -0
- package/dist/animated/GraphSequence.svelte.d.ts +18 -0
- package/dist/animated/GraphSpinners.svelte +109 -0
- package/dist/animated/GraphSpinners.svelte.d.ts +22 -0
- package/dist/animated/GraphState.svelte +270 -0
- package/dist/animated/GraphState.svelte.d.ts +14 -0
- package/dist/animated/GraphStream.svelte +223 -0
- package/dist/animated/GraphStream.svelte.d.ts +24 -0
- package/dist/animated/GraphSurya.svelte +268 -0
- package/dist/animated/GraphSurya.svelte.d.ts +22 -0
- package/dist/animated/GraphTerminal.svelte +238 -0
- package/dist/animated/GraphTerminal.svelte.d.ts +28 -0
- package/dist/animated/GraphTicker.svelte +163 -0
- package/dist/animated/GraphTicker.svelte.d.ts +21 -0
- package/dist/animated/GraphTypewriter.svelte +130 -0
- package/dist/animated/GraphTypewriter.svelte.d.ts +22 -0
- package/dist/animated/GraphWorkflow.svelte +570 -0
- package/dist/animated/GraphWorkflow.svelte.d.ts +29 -0
- package/dist/assets/favicon.svg +21 -0
- package/dist/diagram/AmplifierDiagram.svelte +206 -0
- package/dist/diagram/AmplifierDiagram.svelte.d.ts +12 -0
- package/dist/diagram/AsciiDiagram.svelte +212 -0
- package/dist/diagram/AsciiDiagram.svelte.d.ts +24 -0
- package/dist/diagram/DiagramEditor.svelte +414 -0
- package/dist/diagram/DiagramEditor.svelte.d.ts +3 -0
- package/dist/diagram/MetricsTableDiagram.svelte +125 -0
- package/dist/diagram/MetricsTableDiagram.svelte.d.ts +18 -0
- package/dist/diagram/NestedRadiiDiagram.svelte +218 -0
- package/dist/diagram/NestedRadiiDiagram.svelte.d.ts +12 -0
- package/dist/diagram/PromptLoopDiagram.svelte +331 -0
- package/dist/diagram/PromptLoopDiagram.svelte.d.ts +12 -0
- package/dist/diagram/ascii.d.ts +35 -0
- package/dist/diagram/ascii.js +83 -0
- package/dist/diagram/parser.d.ts +16 -0
- package/dist/diagram/parser.js +71 -0
- package/dist/frame/Graph.svelte +141 -0
- package/dist/frame/Graph.svelte.d.ts +14 -0
- package/dist/frame/GraphArrow.svelte +49 -0
- package/dist/frame/GraphArrow.svelte.d.ts +9 -0
- package/dist/frame/GraphBody.svelte +39 -0
- package/dist/frame/GraphBody.svelte.d.ts +10 -0
- package/dist/frame/GraphRule.svelte +21 -0
- package/dist/frame/GraphRule.svelte.d.ts +6 -0
- package/dist/frame/GraphTick.svelte +23 -0
- package/dist/frame/GraphTick.svelte.d.ts +9 -0
- package/dist/frame/GraphTrack.svelte +29 -0
- package/dist/frame/GraphTrack.svelte.d.ts +14 -0
- package/dist/frame/clock.d.ts +10 -0
- package/dist/frame/clock.js +62 -0
- package/dist/frame/glyphs.d.ts +22 -0
- package/dist/frame/glyphs.js +53 -0
- package/dist/frame/motion.css +27 -0
- package/dist/frame/motion.d.ts +24 -0
- package/dist/frame/motion.js +47 -0
- package/dist/frame/tone.d.ts +11 -0
- package/dist/frame/tone.js +64 -0
- package/dist/graphs/GraphActivity.svelte +387 -0
- package/dist/graphs/GraphActivity.svelte.d.ts +22 -0
- package/dist/graphs/GraphBars.svelte +194 -0
- package/dist/graphs/GraphBars.svelte.d.ts +20 -0
- package/dist/graphs/GraphBullet.svelte +208 -0
- package/dist/graphs/GraphBullet.svelte.d.ts +22 -0
- package/dist/graphs/GraphCalendar.svelte +203 -0
- package/dist/graphs/GraphCalendar.svelte.d.ts +20 -0
- package/dist/graphs/GraphCells.svelte +181 -0
- package/dist/graphs/GraphCells.svelte.d.ts +18 -0
- package/dist/graphs/GraphCompare.svelte +192 -0
- package/dist/graphs/GraphCompare.svelte.d.ts +18 -0
- package/dist/graphs/GraphCountdown.svelte +96 -0
- package/dist/graphs/GraphCountdown.svelte.d.ts +14 -0
- package/dist/graphs/GraphDiff.svelte +181 -0
- package/dist/graphs/GraphDiff.svelte.d.ts +18 -0
- package/dist/graphs/GraphFlow.svelte +137 -0
- package/dist/graphs/GraphFlow.svelte.d.ts +21 -0
- package/dist/graphs/GraphFunnel.svelte +190 -0
- package/dist/graphs/GraphFunnel.svelte.d.ts +21 -0
- package/dist/graphs/GraphGantt.svelte +258 -0
- package/dist/graphs/GraphGantt.svelte.d.ts +25 -0
- package/dist/graphs/GraphHeatmap.svelte +287 -0
- package/dist/graphs/GraphHeatmap.svelte.d.ts +22 -0
- package/dist/graphs/GraphInvoice.svelte +294 -0
- package/dist/graphs/GraphInvoice.svelte.d.ts +34 -0
- package/dist/graphs/GraphKpi.svelte +166 -0
- package/dist/graphs/GraphKpi.svelte.d.ts +17 -0
- package/dist/graphs/GraphLatency.svelte +340 -0
- package/dist/graphs/GraphLatency.svelte.d.ts +28 -0
- package/dist/graphs/GraphMeter.svelte +121 -0
- package/dist/graphs/GraphMeter.svelte.d.ts +16 -0
- package/dist/graphs/GraphPlot.svelte +226 -0
- package/dist/graphs/GraphPlot.svelte.d.ts +18 -0
- package/dist/graphs/GraphQuota.svelte +303 -0
- package/dist/graphs/GraphQuota.svelte.d.ts +27 -0
- package/dist/graphs/GraphRank.svelte +141 -0
- package/dist/graphs/GraphRank.svelte.d.ts +21 -0
- package/dist/graphs/GraphSlo.svelte +325 -0
- package/dist/graphs/GraphSlo.svelte.d.ts +31 -0
- package/dist/graphs/GraphSlope.svelte +197 -0
- package/dist/graphs/GraphSlope.svelte.d.ts +19 -0
- package/dist/graphs/GraphSpark.svelte +125 -0
- package/dist/graphs/GraphSpark.svelte.d.ts +15 -0
- package/dist/graphs/GraphSpec.svelte +69 -0
- package/dist/graphs/GraphSpec.svelte.d.ts +14 -0
- package/dist/graphs/GraphStack.svelte +266 -0
- package/dist/graphs/GraphStack.svelte.d.ts +25 -0
- package/dist/graphs/GraphStat.svelte +103 -0
- package/dist/graphs/GraphStat.svelte.d.ts +16 -0
- package/dist/graphs/GraphTable.svelte +133 -0
- package/dist/graphs/GraphTable.svelte.d.ts +15 -0
- package/dist/graphs/GraphTimeline.svelte +164 -0
- package/dist/graphs/GraphTimeline.svelte.d.ts +18 -0
- package/dist/graphs/GraphTimer.svelte +117 -0
- package/dist/graphs/GraphTimer.svelte.d.ts +15 -0
- package/dist/graphs/GraphTree.svelte +147 -0
- package/dist/graphs/GraphTree.svelte.d.ts +16 -0
- package/dist/graphs/GraphUptime.svelte +210 -0
- package/dist/graphs/GraphUptime.svelte.d.ts +18 -0
- package/dist/graphs/GraphWaffle.svelte +131 -0
- package/dist/graphs/GraphWaffle.svelte.d.ts +17 -0
- package/dist/graphs/GraphWaterfall.svelte +258 -0
- package/dist/graphs/GraphWaterfall.svelte.d.ts +22 -0
- package/dist/index.d.ts +147 -0
- package/dist/index.js +87 -0
- package/dist/themes.css +54 -0
- package/package.json +67 -0
package/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 [Fractalmandala]
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# Markgraphy
|
|
2
|
+
|
|
3
|
+
Svelte components for tables, charts, and diagrams in markdown. Each graph sits in a dashed frame with a `[ TITLE ]` on the top edge. Glyphs draw the chart — no SVG, no canvas, no dependencies. One accent color by default; drawing graphs can take `palette="duo"` or `palette="multi"`.
|
|
4
|
+
|
|
5
|
+
[Docs](https://markgraphy.vercel.app/docs) · [Examples](https://markgraphy.vercel.app/docs/examples) · [Install](https://markgraphy.vercel.app/docs/installation) · [Skill](https://markgraphy.vercel.app/docs/skill) · [GitHub](https://github.com/fractalmandala/markgraphy)
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm add markgraphy
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Svelte 5 is the only peer. One optional CSS import (`markgraphy/themes.css`), no config — theming is CSS variables (below).
|
|
14
|
+
|
|
15
|
+
## Quickstart
|
|
16
|
+
|
|
17
|
+
In a `+page.svelte` (or any component):
|
|
18
|
+
|
|
19
|
+
```svelte
|
|
20
|
+
<script>
|
|
21
|
+
import { GraphKpi, GraphTimeline } from 'markgraphy';
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<GraphTimeline
|
|
25
|
+
title="PLAN"
|
|
26
|
+
events={[
|
|
27
|
+
{ date: 'w1', label: 'freeze features', state: 'done' },
|
|
28
|
+
{ date: 'w2', label: 'ship the rc', state: 'now' },
|
|
29
|
+
{ date: 'w3', label: 'general availability', state: 'next' }
|
|
30
|
+
]}
|
|
31
|
+
/>
|
|
32
|
+
|
|
33
|
+
<GraphKpi
|
|
34
|
+
title="SIGNUPS"
|
|
35
|
+
value="1.2M"
|
|
36
|
+
label="this week"
|
|
37
|
+
hint="+18%"
|
|
38
|
+
data={[2, 3, 3, 5, 8, 9, 11, 12, 14, 16, 18, 21]}
|
|
39
|
+
/>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
In `.svx` markdown files (mdsvex), the same imports go in the script block at the top, then the components sit next to the prose.
|
|
43
|
+
|
|
44
|
+
## Components
|
|
45
|
+
|
|
46
|
+
| Component | Import | Use for |
|
|
47
|
+
| --------- | ---------------- | ------------------------------------------ |
|
|
48
|
+
| Activity | `GraphActivity` | Daily counts over months, contribution grid |
|
|
49
|
+
| Bars | `GraphBars` | Two bar groups, side by side |
|
|
50
|
+
| Bullet | `GraphBullet` | Actual versus target on one track |
|
|
51
|
+
| Calendar | `GraphCalendar` | One month, marked days |
|
|
52
|
+
| Cells | `GraphCells` | Filled / empty grids |
|
|
53
|
+
| Compare | `GraphCompare` | Feature matrix (`✓` / `–`) |
|
|
54
|
+
| Countdown | `GraphCountdown` | Time left until a date |
|
|
55
|
+
| Diff | `GraphDiff` | Add / remove / keep rows |
|
|
56
|
+
| Flow | `GraphFlow` | Process diagrams on a dashed arrow |
|
|
57
|
+
| Funnel | `GraphFunnel` | Steps that get narrower |
|
|
58
|
+
| Gantt | `GraphGantt` | Schedule on a character track |
|
|
59
|
+
| Heatmap | `GraphHeatmap` | Labeled 2d intensity matrix |
|
|
60
|
+
| Invoice | `GraphInvoice` | From, bill-to, line items, totals |
|
|
61
|
+
| KPI | `GraphKpi` | One number with a sparkline under it |
|
|
62
|
+
| Meter | `GraphMeter` | Progress as `=` and `-` |
|
|
63
|
+
| Plot | `GraphPlot` | Line or area from columns of glyphs |
|
|
64
|
+
| Rank | `GraphRank` | A ranked list, one bar per row |
|
|
65
|
+
| Slope | `GraphSlope` | Two figures per row, before → after |
|
|
66
|
+
| Spark | `GraphSpark` | Sparkline from block characters |
|
|
67
|
+
| Spec | `GraphSpec` | Label / value sheets |
|
|
68
|
+
| Stack | `GraphStack` | Parts of a whole, glyphs instead of colors |
|
|
69
|
+
| Stat | `GraphStat` | Large figures with labels |
|
|
70
|
+
| Table | `GraphTable` | Data tables with optional footer totals |
|
|
71
|
+
| Timeline | `GraphTimeline` | Dated events, one row current |
|
|
72
|
+
| Timer | `GraphTimer` | Elapsed time, how long ago, or the clock |
|
|
73
|
+
| Tree | `GraphTree` | File or org trees |
|
|
74
|
+
| Uptime | `GraphUptime` | One glyph per day, percent up |
|
|
75
|
+
| Waffle | `GraphWaffle` | Share of 100 cells |
|
|
76
|
+
| Waterfall | `GraphWaterfall` | Running total as floating bars |
|
|
77
|
+
| Frame | `Graph`, `GraphBody`, `GraphRule`, `GraphTrack`, `GraphTick`, `GraphArrow` | Shared dashed frame primitives |
|
|
78
|
+
|
|
79
|
+
Composed write-ups (refactor, incident, tradeoff, PR, sprint, migration) live on [Examples](https://markgraphy.vercel.app/docs/examples). Every graph has its own page under [Docs](https://markgraphy.vercel.app/docs) with the full props.
|
|
80
|
+
|
|
81
|
+
## Theming
|
|
82
|
+
|
|
83
|
+
Set CSS variables on any container — the components pick them up wherever they are scoped. The site renders dark-first, and so do the fallbacks.
|
|
84
|
+
|
|
85
|
+
| Variable | Fallback | Use |
|
|
86
|
+
| -------------------- | ------------------------ | ------------------------------------ |
|
|
87
|
+
| `--graph-foreground` | `oklch(0.93 0 0)` | primary text |
|
|
88
|
+
| `--graph-muted` | `oklch(0.62 0 0)` | secondary text |
|
|
89
|
+
| `--graph-accent` | `oklch(0.78 0.17 155)` | the one highlight |
|
|
90
|
+
| `--graph-accent-2` | `oklch(0.78 0.12 70)` | second series (`palette="duo"`) |
|
|
91
|
+
| `--graph-accent-3` | `oklch(0.75 0.1 200)` | third series (`palette="multi"`) |
|
|
92
|
+
| `--graph-frame` | `oklch(0.6 0 0 / 0.5)` | frame dashes and corner marks |
|
|
93
|
+
| `--graph-faint` | `oklch(0.3 0 0)` | empty cells and tracks |
|
|
94
|
+
| `--graph-font` | mono stack | the font — anything monospace works |
|
|
95
|
+
|
|
96
|
+
```css
|
|
97
|
+
:root {
|
|
98
|
+
--graph-accent: oklch(0.78 0.17 155);
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Design
|
|
103
|
+
|
|
104
|
+
- Geist Mono by default. Dashed frame, `+` corners (swap with `corner`), title as `[ TITLE ]`.
|
|
105
|
+
- One accent: `--graph-accent`. Unused rows recede with opacity. `palette="duo"` / `"multi"` opt into `--graph-accent-2` and `--graph-accent-3`.
|
|
106
|
+
- Glyphs do the drawing (`█ ▓ ▒ ░ · - = + | ├ └ ✓`). Tracks that represent a range (meter, stack, bullet, rank) span the frame. Spark, bars, cells, and uptime stay packed at 1ch. Pass `glyphs` — `shade`, `ascii`, `hash`, `bar`, or your own characters. No SVG.
|
|
107
|
+
- Numbers use `tabular-nums`. Amounts sit right-aligned.
|
|
108
|
+
- Motion is transform and opacity only, 220ms, no loops. `prefers-reduced-motion` sets duration to 0.
|
|
109
|
+
|
|
110
|
+
## Agent skill
|
|
111
|
+
|
|
112
|
+
A `SKILL.md` in [`skills/markdown-graphs/`](skills/markdown-graphs/SKILL.md) teaches an agent when to put a graph next to the prose, which component to pick, and how to write the usage. Drop the two files into the skills folder your agent already reads (`.claude/skills/markdown-graphs`, `.cursor/skills/markdown-graphs`, …), or fetch them from the site:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
curl -fsSL https://markgraphy.vercel.app/skill.md -o <dir>/markdown-graphs/SKILL.md
|
|
116
|
+
curl -fsSL https://markgraphy.vercel.app/skill/recipes.md -o <dir>/markdown-graphs/recipes.md
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
- [`/skill.md`](https://markgraphy.vercel.app/skill.md) and [`/skill/recipes.md`](https://markgraphy.vercel.app/skill/recipes.md) — the raw skill files
|
|
120
|
+
- [`/llms.txt`](https://markgraphy.vercel.app/llms.txt) — the whole library on one page for agents
|
|
121
|
+
- [`/docs/skill`](https://markgraphy.vercel.app/docs/skill) — install prompts and the chooser
|
|
122
|
+
|
|
123
|
+
## Development
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
pnpm install
|
|
127
|
+
pnpm dev
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Then check types across the package and site:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
pnpm check
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
The package build (`pnpm package`) outputs to `dist/` via `svelte-package`.
|
|
137
|
+
|
|
138
|
+
### Docs development in fractalsvelte
|
|
139
|
+
|
|
140
|
+
The site (`../fractalsvelte`) imports this package through a local link while it is unpublished — its dependency is declared as `"markgraphy": "link:../markgraphy"`. To work on the library and its docs at the same time:
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
# terminal 1 — rebuild dist/ on every change
|
|
144
|
+
pnpm package:watch
|
|
145
|
+
|
|
146
|
+
# terminal 2 — the site's dev server
|
|
147
|
+
pnpm --dir ../fractalsvelte dev
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Components edited here recompile into `dist/` and hot-reload in the site. Restart the site's dev server after changing the link or dependency versions. Until this package is on npm, pages that import it cannot build in the site's Vercel deploys — publish, then flip the link to a version range.
|
|
151
|
+
|
|
152
|
+
## License
|
|
153
|
+
|
|
154
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
export interface GraphAgniProps {
|
|
3
|
+
/** Caption drawn on the top edge as `[ TITLE ]`. Uppercase, 1–2 words. */
|
|
4
|
+
title: string;
|
|
5
|
+
/** Animate the burn. Default true. Reduced motion freezes a mid-burn frame. */
|
|
6
|
+
animated?: boolean;
|
|
7
|
+
/** Width of the scene in characters. Default 56. */
|
|
8
|
+
cols?: number;
|
|
9
|
+
/** Height of the scene in rows. Default 16. */
|
|
10
|
+
rows?: number;
|
|
11
|
+
/** Milliseconds per simulation step. Default 90. */
|
|
12
|
+
speedMs?: number;
|
|
13
|
+
/** Random decay strength; higher burns out sooner. Default 0.6. */
|
|
14
|
+
cooling?: number;
|
|
15
|
+
/** Seed for the reproducible burn. Default 3. */
|
|
16
|
+
seedNum?: number;
|
|
17
|
+
/** Short caption under the art; the art itself is decorative. */
|
|
18
|
+
label?: string;
|
|
19
|
+
class?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Deterministic mulberry32 PRNG so a seed replays the same burn. */
|
|
23
|
+
function mulberry32(seed: number): () => number {
|
|
24
|
+
let a = seed >>> 0;
|
|
25
|
+
|
|
26
|
+
return () => {
|
|
27
|
+
a = (a + 0x6d2b79f5) | 0;
|
|
28
|
+
let t = Math.imul(a ^ (a >>> 15), 1 | a);
|
|
29
|
+
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
|
|
30
|
+
|
|
31
|
+
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** One-time reduced-motion check, for choosing the opening frame only. */
|
|
36
|
+
function prefersReduced(): boolean {
|
|
37
|
+
return (
|
|
38
|
+
typeof window !== 'undefined' &&
|
|
39
|
+
window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface Seg {
|
|
44
|
+
text: string;
|
|
45
|
+
cls?: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface Spark {
|
|
49
|
+
x: number;
|
|
50
|
+
y: number;
|
|
51
|
+
age: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const HOT = 0.75;
|
|
55
|
+
const MID = 0.5;
|
|
56
|
+
const LOW = 0.3;
|
|
57
|
+
const EMBER = 0.12;
|
|
58
|
+
|
|
59
|
+
/** Run-length encode one row of heat into classed segments, never per-cell spans. */
|
|
60
|
+
function heatSegs(row: number[]): Seg[] {
|
|
61
|
+
const segs: Seg[] = [];
|
|
62
|
+
let buf = '';
|
|
63
|
+
let cls: string | undefined;
|
|
64
|
+
|
|
65
|
+
for (const heat of row) {
|
|
66
|
+
let ch = ' ';
|
|
67
|
+
let next: string | undefined;
|
|
68
|
+
|
|
69
|
+
if (heat >= HOT) {
|
|
70
|
+
ch = '█';
|
|
71
|
+
next = 'hot';
|
|
72
|
+
} else if (heat >= MID) {
|
|
73
|
+
ch = '▓';
|
|
74
|
+
} else if (heat >= LOW) {
|
|
75
|
+
ch = '▒';
|
|
76
|
+
next = 'mid';
|
|
77
|
+
} else if (heat >= EMBER) {
|
|
78
|
+
ch = '░';
|
|
79
|
+
next = 'faint';
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (next === cls) {
|
|
83
|
+
buf += ch;
|
|
84
|
+
} else {
|
|
85
|
+
if (buf.length > 0) {
|
|
86
|
+
segs.push({ text: buf, cls });
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
cls = next;
|
|
90
|
+
buf = ch;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (buf.length > 0) {
|
|
95
|
+
segs.push({ text: buf, cls });
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return segs;
|
|
99
|
+
}
|
|
100
|
+
</script>
|
|
101
|
+
|
|
102
|
+
<script lang="ts">
|
|
103
|
+
import Graph from '../frame/Graph.svelte';
|
|
104
|
+
import GraphBody from '../frame/GraphBody.svelte';
|
|
105
|
+
|
|
106
|
+
let {
|
|
107
|
+
title,
|
|
108
|
+
animated = true,
|
|
109
|
+
cols = 56,
|
|
110
|
+
rows = 16,
|
|
111
|
+
speedMs = 90,
|
|
112
|
+
cooling = 0.6,
|
|
113
|
+
seedNum = 3,
|
|
114
|
+
label = 'the carrier of offerings',
|
|
115
|
+
class: className = ''
|
|
116
|
+
}: GraphAgniProps = $props();
|
|
117
|
+
|
|
118
|
+
// svelte-ignore state_referenced_locally
|
|
119
|
+
const rand = mulberry32(seedNum);
|
|
120
|
+
// svelte-ignore state_referenced_locally
|
|
121
|
+
const width = Math.max(24, cols);
|
|
122
|
+
// svelte-ignore state_referenced_locally
|
|
123
|
+
const height = Math.max(10, rows);
|
|
124
|
+
|
|
125
|
+
// The havan kund: a shallow pit at the bottom center. Fire rises from the
|
|
126
|
+
// mouth between the slanted walls; everything above is flame column.
|
|
127
|
+
const mouth = Math.max(7, Math.round(width * 0.24));
|
|
128
|
+
const centerX = Math.floor(width / 2);
|
|
129
|
+
const pitLeft = centerX - Math.floor(mouth / 2);
|
|
130
|
+
const pitRight = pitLeft + mouth - 1;
|
|
131
|
+
const pitRow = height - 2;
|
|
132
|
+
|
|
133
|
+
// Flame column narrows toward the top so the burn reads as a tongue, not a wall.
|
|
134
|
+
function coneMask(y: number, x: number): number {
|
|
135
|
+
const flameRows = pitRow;
|
|
136
|
+
const t = 1 - y / flameRows;
|
|
137
|
+
const spread = mouth / 2;
|
|
138
|
+
const allow = spread * (0.4 + 0.6 * (1 - t));
|
|
139
|
+
const dx = Math.abs(x - centerX) / spread;
|
|
140
|
+
|
|
141
|
+
if (dx > 1) {
|
|
142
|
+
return 0;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return dx <= allow / spread ? 1 - 0.3 * dx : 0;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function initialGrid(): number[][] {
|
|
149
|
+
const grid: number[][] = [];
|
|
150
|
+
|
|
151
|
+
for (let y = 0; y < pitRow; y++) {
|
|
152
|
+
grid.push(new Array<number>(width).fill(0));
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
for (let x = pitLeft; x <= pitRight; x++) {
|
|
156
|
+
grid[pitRow - 1][x] = 1;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return grid;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Heat climbs from the mouth, shaped by the cone, breathing on the cooling term.
|
|
163
|
+
function stepFlame(grid: number[][], tick: number): number[][] {
|
|
164
|
+
const next: number[][] = [];
|
|
165
|
+
const breath = 1 + 0.3 * Math.sin(tick / 12);
|
|
166
|
+
|
|
167
|
+
for (let y = 0; y < pitRow; y++) {
|
|
168
|
+
next.push(new Array<number>(width).fill(0));
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
for (let y = 0; y < pitRow - 1; y++) {
|
|
172
|
+
for (let x = pitLeft - 1; x <= pitRight + 1; x++) {
|
|
173
|
+
const below =
|
|
174
|
+
(grid[y + 1][x - 1] + grid[y + 1][x] + grid[y + 1][x + 1]) / 3;
|
|
175
|
+
|
|
176
|
+
next[y][x] = Math.max(
|
|
177
|
+
0,
|
|
178
|
+
below * (1 - rand() * cooling * breath * 0.5) * coneMask(y, x)
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
for (let x = pitLeft; x <= pitRight; x++) {
|
|
184
|
+
next[pitRow - 1][x] = 1;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return next;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function stepSparks(sparks: Spark[], grid: number[][]): Spark[] {
|
|
191
|
+
const alive: Spark[] = [];
|
|
192
|
+
|
|
193
|
+
for (const s of sparks) {
|
|
194
|
+
const y = s.y - 1;
|
|
195
|
+
const x = s.x + (rand() < 0.3 ? (rand() < 0.5 ? -1 : 1) : 0);
|
|
196
|
+
const age = s.age + 1;
|
|
197
|
+
|
|
198
|
+
if (y >= 0 && age < height + 2) {
|
|
199
|
+
alive.push({ x, y, age });
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (sparks.length < 7 && rand() < 0.22) {
|
|
204
|
+
const x = pitLeft + Math.floor(rand() * mouth);
|
|
205
|
+
const y = pitRow - 2;
|
|
206
|
+
|
|
207
|
+
if (grid[pitRow - 1][x] >= 1) {
|
|
208
|
+
alive.push({ x, y, age: 0 });
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return alive;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const moving = $derived(
|
|
216
|
+
animated &&
|
|
217
|
+
(typeof window === 'undefined' ||
|
|
218
|
+
!window.matchMedia('(prefers-reduced-motion: reduce)').matches)
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
let sim = initialGrid();
|
|
222
|
+
let embers: Spark[] = [];
|
|
223
|
+
|
|
224
|
+
// svelte-ignore state_referenced_locally
|
|
225
|
+
const preTicks = animated && !prefersReduced() ? 16 : pitRow * 2 + 6;
|
|
226
|
+
|
|
227
|
+
// Just lit when animated; a developed mid-burn when frozen.
|
|
228
|
+
for (let i = 0; i < preTicks; i++) {
|
|
229
|
+
sim = stepFlame(sim, i);
|
|
230
|
+
embers = stepSparks(embers, sim);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// svelte-ignore state_referenced_locally
|
|
234
|
+
let tick = $state(preTicks);
|
|
235
|
+
// svelte-ignore state_referenced_locally
|
|
236
|
+
let grid = $state<number[][]>(sim);
|
|
237
|
+
// svelte-ignore state_referenced_locally
|
|
238
|
+
let sparks = $state<Spark[]>(embers);
|
|
239
|
+
|
|
240
|
+
$effect(() => {
|
|
241
|
+
if (!moving) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const timer = window.setInterval(() => {
|
|
246
|
+
grid = stepFlame(grid, tick);
|
|
247
|
+
sparks = stepSparks(sparks, grid);
|
|
248
|
+
tick += 1;
|
|
249
|
+
}, speedMs);
|
|
250
|
+
|
|
251
|
+
return () => window.clearInterval(timer);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
const view = $derived.by((): Seg[][] => {
|
|
255
|
+
const out: Seg[][] = [];
|
|
256
|
+
|
|
257
|
+
for (let y = 0; y < height; y++) {
|
|
258
|
+
if (y === pitRow - 1 || y === pitRow) {
|
|
259
|
+
// Flame rows: heat segments with the slanted pit walls overlaid.
|
|
260
|
+
const segs: Seg[] = [];
|
|
261
|
+
let buf = '';
|
|
262
|
+
let cls: string | undefined;
|
|
263
|
+
|
|
264
|
+
const flush = () => {
|
|
265
|
+
if (buf.length > 0) {
|
|
266
|
+
segs.push({ text: buf, cls });
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
buf = '';
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
for (let x = 0; x < width; x++) {
|
|
273
|
+
let ch: string;
|
|
274
|
+
let next: string | undefined;
|
|
275
|
+
|
|
276
|
+
if (y === pitRow && (x === pitLeft - 1 || x === pitRight + 1)) {
|
|
277
|
+
ch = x === pitLeft - 1 ? '╲' : '╱';
|
|
278
|
+
} else if (x >= pitLeft && x <= pitRight) {
|
|
279
|
+
const heat = y === pitRow ? 1 : grid[y]?.[x] ?? 0;
|
|
280
|
+
const spark = sparks.find((s) => s.x === x && s.y === y);
|
|
281
|
+
|
|
282
|
+
if (heat >= HOT) {
|
|
283
|
+
ch = '█';
|
|
284
|
+
next = spark ? 'faint' : 'hot';
|
|
285
|
+
} else if (heat >= MID) {
|
|
286
|
+
ch = '▓';
|
|
287
|
+
} else if (heat >= LOW) {
|
|
288
|
+
ch = '▒';
|
|
289
|
+
next = 'mid';
|
|
290
|
+
} else if (heat >= EMBER) {
|
|
291
|
+
ch = '░';
|
|
292
|
+
next = 'faint';
|
|
293
|
+
} else {
|
|
294
|
+
ch = ' ';
|
|
295
|
+
}
|
|
296
|
+
} else {
|
|
297
|
+
ch = ' ';
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (next === cls) {
|
|
301
|
+
buf += ch;
|
|
302
|
+
} else {
|
|
303
|
+
flush();
|
|
304
|
+
cls = next;
|
|
305
|
+
buf = ch;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
flush();
|
|
310
|
+
out.push(segs);
|
|
311
|
+
} else if (y === pitRow + 1) {
|
|
312
|
+
// Pit floor line under the mouth.
|
|
313
|
+
const pad = Math.max(0, pitLeft - 1);
|
|
314
|
+
const line = '─'.repeat(Math.min(mouth + 2, width - pad));
|
|
315
|
+
out.push([
|
|
316
|
+
{ text: ' '.repeat(pad) },
|
|
317
|
+
{ text: line, cls: 'pit' },
|
|
318
|
+
{ text: ' '.repeat(Math.max(0, width - pad - line.length)) }
|
|
319
|
+
]);
|
|
320
|
+
} else {
|
|
321
|
+
// Open flame rows, with sparks drifting above.
|
|
322
|
+
const segs: Seg[] = [];
|
|
323
|
+
let buf = '';
|
|
324
|
+
let cls: string | undefined;
|
|
325
|
+
|
|
326
|
+
const flush = () => {
|
|
327
|
+
if (buf.length > 0) {
|
|
328
|
+
segs.push({ text: buf, cls });
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
buf = '';
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
for (let x = 0; x < width; x++) {
|
|
335
|
+
const spark = sparks.find((s) => s.x === x && s.y === y);
|
|
336
|
+
const heat = grid[y]?.[x] ?? 0;
|
|
337
|
+
let ch = ' ';
|
|
338
|
+
let next: string | undefined;
|
|
339
|
+
|
|
340
|
+
if (spark) {
|
|
341
|
+
ch = '·';
|
|
342
|
+
next = spark.age < 2 ? 'mid' : 'faint';
|
|
343
|
+
} else if (heat >= HOT) {
|
|
344
|
+
ch = '█';
|
|
345
|
+
next = 'hot';
|
|
346
|
+
} else if (heat >= MID) {
|
|
347
|
+
ch = '▓';
|
|
348
|
+
} else if (heat >= LOW) {
|
|
349
|
+
ch = '▒';
|
|
350
|
+
next = 'mid';
|
|
351
|
+
} else if (heat >= EMBER) {
|
|
352
|
+
ch = '░';
|
|
353
|
+
next = 'faint';
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (next === cls) {
|
|
357
|
+
buf += ch;
|
|
358
|
+
} else {
|
|
359
|
+
flush();
|
|
360
|
+
cls = next;
|
|
361
|
+
buf = ch;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
flush();
|
|
366
|
+
out.push(segs);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
return out;
|
|
371
|
+
});
|
|
372
|
+
</script>
|
|
373
|
+
|
|
374
|
+
<Graph {title} class={className}>
|
|
375
|
+
<GraphBody>
|
|
376
|
+
<div class="agni">
|
|
377
|
+
<div class="viewport">
|
|
378
|
+
<pre class="art" aria-hidden="true"><code>{#each view as line, i (i)}{#each line as seg, j (j)}{#if seg.cls}<span class={seg.cls}>{seg.text}</span>{:else}{seg.text}{/if}{/each}{#if i < view.length - 1}{'\n'}{/if}{/each}</code></pre>
|
|
379
|
+
</div>
|
|
380
|
+
{#if label}
|
|
381
|
+
<p class="caption">{label}</p>
|
|
382
|
+
{/if}
|
|
383
|
+
</div>
|
|
384
|
+
</GraphBody>
|
|
385
|
+
</Graph>
|
|
386
|
+
|
|
387
|
+
<style>
|
|
388
|
+
.agni {
|
|
389
|
+
display: flex;
|
|
390
|
+
flex-direction: column;
|
|
391
|
+
gap: 1rem;
|
|
392
|
+
min-width: 0;
|
|
393
|
+
font-size: 0.875rem;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
.viewport {
|
|
397
|
+
overflow-x: auto;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
.art {
|
|
401
|
+
margin: 0;
|
|
402
|
+
font-size: 0.85rem;
|
|
403
|
+
line-height: 1.15;
|
|
404
|
+
color: var(--graph-foreground, oklch(0.93 0 0));
|
|
405
|
+
white-space: pre;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
.hot {
|
|
409
|
+
color: var(--graph-accent, oklch(0.78 0.17 155));
|
|
410
|
+
font-weight: 600;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
.mid {
|
|
414
|
+
color: var(--graph-muted, oklch(0.62 0 0));
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
.faint {
|
|
418
|
+
color: var(--graph-faint, oklch(0.3 0 0));
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
.pit {
|
|
422
|
+
color: var(--graph-muted, oklch(0.62 0 0));
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
.caption {
|
|
426
|
+
margin: 0;
|
|
427
|
+
font-size: 0.8rem;
|
|
428
|
+
color: var(--graph-muted, oklch(0.62 0 0));
|
|
429
|
+
}
|
|
430
|
+
</style>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface GraphAgniProps {
|
|
2
|
+
/** Caption drawn on the top edge as `[ TITLE ]`. Uppercase, 1–2 words. */
|
|
3
|
+
title: string;
|
|
4
|
+
/** Animate the burn. Default true. Reduced motion freezes a mid-burn frame. */
|
|
5
|
+
animated?: boolean;
|
|
6
|
+
/** Width of the scene in characters. Default 56. */
|
|
7
|
+
cols?: number;
|
|
8
|
+
/** Height of the scene in rows. Default 16. */
|
|
9
|
+
rows?: number;
|
|
10
|
+
/** Milliseconds per simulation step. Default 90. */
|
|
11
|
+
speedMs?: number;
|
|
12
|
+
/** Random decay strength; higher burns out sooner. Default 0.6. */
|
|
13
|
+
cooling?: number;
|
|
14
|
+
/** Seed for the reproducible burn. Default 3. */
|
|
15
|
+
seedNum?: number;
|
|
16
|
+
/** Short caption under the art; the art itself is decorative. */
|
|
17
|
+
label?: string;
|
|
18
|
+
class?: string;
|
|
19
|
+
}
|
|
20
|
+
declare const GraphAgni: import("svelte").Component<GraphAgniProps, {}, "">;
|
|
21
|
+
type GraphAgni = ReturnType<typeof GraphAgni>;
|
|
22
|
+
export default GraphAgni;
|