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.
Files changed (166) hide show
  1. package/LICENSE.txt +21 -0
  2. package/README.md +154 -0
  3. package/dist/animated/GraphAgni.svelte +430 -0
  4. package/dist/animated/GraphAgni.svelte.d.ts +22 -0
  5. package/dist/animated/GraphAum.svelte +294 -0
  6. package/dist/animated/GraphAum.svelte.d.ts +20 -0
  7. package/dist/animated/GraphBoot.svelte +325 -0
  8. package/dist/animated/GraphBoot.svelte.d.ts +28 -0
  9. package/dist/animated/GraphCron.svelte +476 -0
  10. package/dist/animated/GraphCron.svelte.d.ts +42 -0
  11. package/dist/animated/GraphDamru.svelte +341 -0
  12. package/dist/animated/GraphDamru.svelte.d.ts +18 -0
  13. package/dist/animated/GraphDeps.svelte +245 -0
  14. package/dist/animated/GraphDeps.svelte.d.ts +29 -0
  15. package/dist/animated/GraphDiya.svelte +330 -0
  16. package/dist/animated/GraphDiya.svelte.d.ts +22 -0
  17. package/dist/animated/GraphFire.svelte +246 -0
  18. package/dist/animated/GraphFire.svelte.d.ts +22 -0
  19. package/dist/animated/GraphFlame.svelte +258 -0
  20. package/dist/animated/GraphFlame.svelte.d.ts +14 -0
  21. package/dist/animated/GraphFlowPlayer.svelte +346 -0
  22. package/dist/animated/GraphFlowPlayer.svelte.d.ts +27 -0
  23. package/dist/animated/GraphGanga.svelte +305 -0
  24. package/dist/animated/GraphGanga.svelte.d.ts +20 -0
  25. package/dist/animated/GraphHash.svelte +251 -0
  26. package/dist/animated/GraphHash.svelte.d.ts +20 -0
  27. package/dist/animated/GraphJapa.svelte +253 -0
  28. package/dist/animated/GraphJapa.svelte.d.ts +20 -0
  29. package/dist/animated/GraphLife.svelte +312 -0
  30. package/dist/animated/GraphLife.svelte.d.ts +27 -0
  31. package/dist/animated/GraphMandala.svelte +262 -0
  32. package/dist/animated/GraphMandala.svelte.d.ts +24 -0
  33. package/dist/animated/GraphMandel.svelte +290 -0
  34. package/dist/animated/GraphMandel.svelte.d.ts +27 -0
  35. package/dist/animated/GraphPulse.svelte +248 -0
  36. package/dist/animated/GraphPulse.svelte.d.ts +24 -0
  37. package/dist/animated/GraphRain.svelte +280 -0
  38. package/dist/animated/GraphRain.svelte.d.ts +22 -0
  39. package/dist/animated/GraphScatter.svelte +395 -0
  40. package/dist/animated/GraphScatter.svelte.d.ts +31 -0
  41. package/dist/animated/GraphScope.svelte +197 -0
  42. package/dist/animated/GraphScope.svelte.d.ts +22 -0
  43. package/dist/animated/GraphSequence.svelte +247 -0
  44. package/dist/animated/GraphSequence.svelte.d.ts +18 -0
  45. package/dist/animated/GraphSpinners.svelte +109 -0
  46. package/dist/animated/GraphSpinners.svelte.d.ts +22 -0
  47. package/dist/animated/GraphState.svelte +270 -0
  48. package/dist/animated/GraphState.svelte.d.ts +14 -0
  49. package/dist/animated/GraphStream.svelte +223 -0
  50. package/dist/animated/GraphStream.svelte.d.ts +24 -0
  51. package/dist/animated/GraphSurya.svelte +268 -0
  52. package/dist/animated/GraphSurya.svelte.d.ts +22 -0
  53. package/dist/animated/GraphTerminal.svelte +238 -0
  54. package/dist/animated/GraphTerminal.svelte.d.ts +28 -0
  55. package/dist/animated/GraphTicker.svelte +163 -0
  56. package/dist/animated/GraphTicker.svelte.d.ts +21 -0
  57. package/dist/animated/GraphTypewriter.svelte +130 -0
  58. package/dist/animated/GraphTypewriter.svelte.d.ts +22 -0
  59. package/dist/animated/GraphWorkflow.svelte +570 -0
  60. package/dist/animated/GraphWorkflow.svelte.d.ts +29 -0
  61. package/dist/assets/favicon.svg +21 -0
  62. package/dist/diagram/AmplifierDiagram.svelte +206 -0
  63. package/dist/diagram/AmplifierDiagram.svelte.d.ts +12 -0
  64. package/dist/diagram/AsciiDiagram.svelte +212 -0
  65. package/dist/diagram/AsciiDiagram.svelte.d.ts +24 -0
  66. package/dist/diagram/DiagramEditor.svelte +414 -0
  67. package/dist/diagram/DiagramEditor.svelte.d.ts +3 -0
  68. package/dist/diagram/MetricsTableDiagram.svelte +125 -0
  69. package/dist/diagram/MetricsTableDiagram.svelte.d.ts +18 -0
  70. package/dist/diagram/NestedRadiiDiagram.svelte +218 -0
  71. package/dist/diagram/NestedRadiiDiagram.svelte.d.ts +12 -0
  72. package/dist/diagram/PromptLoopDiagram.svelte +331 -0
  73. package/dist/diagram/PromptLoopDiagram.svelte.d.ts +12 -0
  74. package/dist/diagram/ascii.d.ts +35 -0
  75. package/dist/diagram/ascii.js +83 -0
  76. package/dist/diagram/parser.d.ts +16 -0
  77. package/dist/diagram/parser.js +71 -0
  78. package/dist/frame/Graph.svelte +141 -0
  79. package/dist/frame/Graph.svelte.d.ts +14 -0
  80. package/dist/frame/GraphArrow.svelte +49 -0
  81. package/dist/frame/GraphArrow.svelte.d.ts +9 -0
  82. package/dist/frame/GraphBody.svelte +39 -0
  83. package/dist/frame/GraphBody.svelte.d.ts +10 -0
  84. package/dist/frame/GraphRule.svelte +21 -0
  85. package/dist/frame/GraphRule.svelte.d.ts +6 -0
  86. package/dist/frame/GraphTick.svelte +23 -0
  87. package/dist/frame/GraphTick.svelte.d.ts +9 -0
  88. package/dist/frame/GraphTrack.svelte +29 -0
  89. package/dist/frame/GraphTrack.svelte.d.ts +14 -0
  90. package/dist/frame/clock.d.ts +10 -0
  91. package/dist/frame/clock.js +62 -0
  92. package/dist/frame/glyphs.d.ts +22 -0
  93. package/dist/frame/glyphs.js +53 -0
  94. package/dist/frame/motion.css +27 -0
  95. package/dist/frame/motion.d.ts +24 -0
  96. package/dist/frame/motion.js +47 -0
  97. package/dist/frame/tone.d.ts +11 -0
  98. package/dist/frame/tone.js +64 -0
  99. package/dist/graphs/GraphActivity.svelte +387 -0
  100. package/dist/graphs/GraphActivity.svelte.d.ts +22 -0
  101. package/dist/graphs/GraphBars.svelte +194 -0
  102. package/dist/graphs/GraphBars.svelte.d.ts +20 -0
  103. package/dist/graphs/GraphBullet.svelte +208 -0
  104. package/dist/graphs/GraphBullet.svelte.d.ts +22 -0
  105. package/dist/graphs/GraphCalendar.svelte +203 -0
  106. package/dist/graphs/GraphCalendar.svelte.d.ts +20 -0
  107. package/dist/graphs/GraphCells.svelte +181 -0
  108. package/dist/graphs/GraphCells.svelte.d.ts +18 -0
  109. package/dist/graphs/GraphCompare.svelte +192 -0
  110. package/dist/graphs/GraphCompare.svelte.d.ts +18 -0
  111. package/dist/graphs/GraphCountdown.svelte +96 -0
  112. package/dist/graphs/GraphCountdown.svelte.d.ts +14 -0
  113. package/dist/graphs/GraphDiff.svelte +181 -0
  114. package/dist/graphs/GraphDiff.svelte.d.ts +18 -0
  115. package/dist/graphs/GraphFlow.svelte +137 -0
  116. package/dist/graphs/GraphFlow.svelte.d.ts +21 -0
  117. package/dist/graphs/GraphFunnel.svelte +190 -0
  118. package/dist/graphs/GraphFunnel.svelte.d.ts +21 -0
  119. package/dist/graphs/GraphGantt.svelte +258 -0
  120. package/dist/graphs/GraphGantt.svelte.d.ts +25 -0
  121. package/dist/graphs/GraphHeatmap.svelte +287 -0
  122. package/dist/graphs/GraphHeatmap.svelte.d.ts +22 -0
  123. package/dist/graphs/GraphInvoice.svelte +294 -0
  124. package/dist/graphs/GraphInvoice.svelte.d.ts +34 -0
  125. package/dist/graphs/GraphKpi.svelte +166 -0
  126. package/dist/graphs/GraphKpi.svelte.d.ts +17 -0
  127. package/dist/graphs/GraphLatency.svelte +340 -0
  128. package/dist/graphs/GraphLatency.svelte.d.ts +28 -0
  129. package/dist/graphs/GraphMeter.svelte +121 -0
  130. package/dist/graphs/GraphMeter.svelte.d.ts +16 -0
  131. package/dist/graphs/GraphPlot.svelte +226 -0
  132. package/dist/graphs/GraphPlot.svelte.d.ts +18 -0
  133. package/dist/graphs/GraphQuota.svelte +303 -0
  134. package/dist/graphs/GraphQuota.svelte.d.ts +27 -0
  135. package/dist/graphs/GraphRank.svelte +141 -0
  136. package/dist/graphs/GraphRank.svelte.d.ts +21 -0
  137. package/dist/graphs/GraphSlo.svelte +325 -0
  138. package/dist/graphs/GraphSlo.svelte.d.ts +31 -0
  139. package/dist/graphs/GraphSlope.svelte +197 -0
  140. package/dist/graphs/GraphSlope.svelte.d.ts +19 -0
  141. package/dist/graphs/GraphSpark.svelte +125 -0
  142. package/dist/graphs/GraphSpark.svelte.d.ts +15 -0
  143. package/dist/graphs/GraphSpec.svelte +69 -0
  144. package/dist/graphs/GraphSpec.svelte.d.ts +14 -0
  145. package/dist/graphs/GraphStack.svelte +266 -0
  146. package/dist/graphs/GraphStack.svelte.d.ts +25 -0
  147. package/dist/graphs/GraphStat.svelte +103 -0
  148. package/dist/graphs/GraphStat.svelte.d.ts +16 -0
  149. package/dist/graphs/GraphTable.svelte +133 -0
  150. package/dist/graphs/GraphTable.svelte.d.ts +15 -0
  151. package/dist/graphs/GraphTimeline.svelte +164 -0
  152. package/dist/graphs/GraphTimeline.svelte.d.ts +18 -0
  153. package/dist/graphs/GraphTimer.svelte +117 -0
  154. package/dist/graphs/GraphTimer.svelte.d.ts +15 -0
  155. package/dist/graphs/GraphTree.svelte +147 -0
  156. package/dist/graphs/GraphTree.svelte.d.ts +16 -0
  157. package/dist/graphs/GraphUptime.svelte +210 -0
  158. package/dist/graphs/GraphUptime.svelte.d.ts +18 -0
  159. package/dist/graphs/GraphWaffle.svelte +131 -0
  160. package/dist/graphs/GraphWaffle.svelte.d.ts +17 -0
  161. package/dist/graphs/GraphWaterfall.svelte +258 -0
  162. package/dist/graphs/GraphWaterfall.svelte.d.ts +22 -0
  163. package/dist/index.d.ts +147 -0
  164. package/dist/index.js +87 -0
  165. package/dist/themes.css +54 -0
  166. 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;