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
@@ -0,0 +1,226 @@
1
+ <script module lang="ts">
2
+ import type { Glyphs } from '../frame/glyphs';
3
+ import type { GraphPalette } from '../frame/tone';
4
+
5
+ /** Line or area plot drawn from columns of glyphs, with y-scale labels. */
6
+ export interface GraphPlotProps {
7
+ title: string;
8
+ data: number[];
9
+ labels?: string[];
10
+ height?: number;
11
+ variant?: 'line' | 'area';
12
+ progress?: number;
13
+ glyphs?: Glyphs;
14
+ palette?: GraphPalette;
15
+ corner?: string;
16
+ class?: string;
17
+ }
18
+ </script>
19
+
20
+ <script lang="ts">
21
+ import Graph from '../frame/Graph.svelte';
22
+ import GraphBody from '../frame/GraphBody.svelte';
23
+ import GraphRule from '../frame/GraphRule.svelte';
24
+ import { clamp01, trackMarks } from '../frame/glyphs';
25
+ import { isMonoPalette } from '../frame/tone';
26
+ import { reveal, stagger } from '../frame/motion';
27
+
28
+ let {
29
+ title,
30
+ data,
31
+ labels,
32
+ height = 7,
33
+ variant = 'area',
34
+ progress = 1,
35
+ glyphs,
36
+ palette,
37
+ corner,
38
+ class: className = ''
39
+ }: GraphPlotProps = $props();
40
+
41
+ const mono = $derived(isMonoPalette(palette));
42
+
43
+ function formatTick(value: number) {
44
+ if (Number.isInteger(value)) {
45
+ return String(value);
46
+ }
47
+
48
+ return value.toFixed(1);
49
+ }
50
+
51
+ const view = $derived.by(() => {
52
+ const max = Math.max(...data, 0);
53
+ const min = Math.min(0, ...data);
54
+ const range = max - min || 1;
55
+ const end = labels?.[labels.length - 1];
56
+ const start = labels?.[0];
57
+ const yLabel = formatTick(max);
58
+ const revealed = Math.round(clamp01(progress) * data.length);
59
+ const lastLive = Math.max(0, revealed - 1);
60
+ const marks = trackMarks(glyphs);
61
+ return { max, min, range, end, start, yLabel, revealed, lastLive, marks };
62
+ });
63
+ </script>
64
+
65
+ <Graph {title} {corner} class={className}>
66
+ <GraphBody>
67
+ <div class="wrap">
68
+ <div class="plotrow" style:--plot-h={height}>
69
+ <div class="yscale">
70
+ <span>{view.yLabel}</span>
71
+ <span>{formatTick(view.min)}</span>
72
+ </div>
73
+ <div class="plot" aria-hidden="true">
74
+ {#each data as value, column}
75
+ {@const level = Math.round(((value - view.min) / view.range) * (height - 1))}
76
+ {@const live = column === view.lastLive && column < view.revealed}
77
+ {@const shown = column < view.revealed}
78
+ <span class="col" use:reveal={{ delay: stagger(column, 40), amount: 0.4 }}>
79
+ {#each Array.from({ length: height }) as _, row}
80
+ {@const fromBottom = height - 1 - row}
81
+ {@const isCap = shown && fromBottom === level}
82
+ {@const isFill = shown && variant === 'area' && fromBottom < level}
83
+ <span
84
+ class="cellrow"
85
+ class:c-accent={isCap && live}
86
+ class:c-fg={isCap && !live}
87
+ class:c-accent2={isFill && !mono}
88
+ class:c-muted={isFill && mono}
89
+ class:transparent={!isCap && !isFill}
90
+ >
91
+ {isCap ? view.marks.fill : isFill ? view.marks.rest : ' '}
92
+ </span>
93
+ {/each}
94
+ </span>
95
+ {/each}
96
+ </div>
97
+ </div>
98
+ {#if view.start || view.end}
99
+ <div class="axisrow">
100
+ <span class="spacer" aria-hidden="true">{view.yLabel}</span>
101
+ <div class="rulewrap">
102
+ <GraphRule />
103
+ </div>
104
+ </div>
105
+ <div class="axisrow">
106
+ <span class="spacer" aria-hidden="true">{view.yLabel}</span>
107
+ <div class="axislabels">
108
+ <span>{view.start}</span>
109
+ {#if view.end && view.end !== view.start}
110
+ <span>{view.end}</span>
111
+ {/if}
112
+ </div>
113
+ </div>
114
+ {/if}
115
+ <span class="sr-only">
116
+ {variant} plot, {data.length} points, min {formatTick(view.min)}, max
117
+ {formatTick(view.max)}
118
+ </span>
119
+ </div>
120
+ </GraphBody>
121
+ </Graph>
122
+
123
+ <style>
124
+ .wrap {
125
+ display: flex;
126
+ flex-direction: column;
127
+ gap: 0.75rem;
128
+ }
129
+
130
+ .plotrow {
131
+ display: flex;
132
+ gap: 0.75rem;
133
+ }
134
+
135
+ .yscale {
136
+ display: flex;
137
+ width: 4ch;
138
+ flex-shrink: 0;
139
+ flex-direction: column;
140
+ align-items: flex-end;
141
+ justify-content: space-between;
142
+ padding: 1px 0;
143
+ height: calc(var(--plot-h, 7) * 1em);
144
+ text-align: right;
145
+ color: var(--graph-muted, oklch(0.62 0 0));
146
+ }
147
+
148
+ .plot {
149
+ display: flex;
150
+ min-width: 0;
151
+ align-items: flex-end;
152
+ justify-content: center;
153
+ gap: 0.125rem;
154
+ height: calc(var(--plot-h, 7) * 1em);
155
+ user-select: none;
156
+ }
157
+
158
+ .col {
159
+ display: flex;
160
+ height: 100%;
161
+ min-width: 1ch;
162
+ flex-direction: column;
163
+ justify-content: flex-end;
164
+ }
165
+
166
+ .cellrow {
167
+ width: 100%;
168
+ height: 1em;
169
+ text-align: center;
170
+ }
171
+
172
+ .transparent {
173
+ color: transparent;
174
+ }
175
+
176
+ .axisrow {
177
+ display: flex;
178
+ gap: 0.75rem;
179
+ }
180
+
181
+ .spacer {
182
+ width: 4ch;
183
+ flex-shrink: 0;
184
+ visibility: hidden;
185
+ }
186
+
187
+ .rulewrap {
188
+ flex: 1;
189
+ min-width: 0;
190
+ }
191
+
192
+ .axislabels {
193
+ display: flex;
194
+ flex: 1;
195
+ justify-content: space-between;
196
+ color: var(--graph-muted, oklch(0.62 0 0));
197
+ }
198
+
199
+ .c-accent {
200
+ color: var(--graph-accent, oklch(0.78 0.17 155));
201
+ }
202
+
203
+ .c-accent2 {
204
+ color: var(--graph-accent-2, oklch(0.78 0.12 70));
205
+ }
206
+
207
+ .c-muted {
208
+ color: var(--graph-muted, oklch(0.62 0 0));
209
+ }
210
+
211
+ .c-fg {
212
+ color: var(--graph-foreground, oklch(0.93 0 0));
213
+ }
214
+
215
+ .sr-only {
216
+ position: absolute;
217
+ width: 1px;
218
+ height: 1px;
219
+ padding: 0;
220
+ margin: -1px;
221
+ overflow: hidden;
222
+ clip-path: inset(50%);
223
+ white-space: nowrap;
224
+ border-width: 0;
225
+ }
226
+ </style>
@@ -0,0 +1,18 @@
1
+ import type { Glyphs } from '../frame/glyphs';
2
+ import type { GraphPalette } from '../frame/tone';
3
+ /** Line or area plot drawn from columns of glyphs, with y-scale labels. */
4
+ export interface GraphPlotProps {
5
+ title: string;
6
+ data: number[];
7
+ labels?: string[];
8
+ height?: number;
9
+ variant?: 'line' | 'area';
10
+ progress?: number;
11
+ glyphs?: Glyphs;
12
+ palette?: GraphPalette;
13
+ corner?: string;
14
+ class?: string;
15
+ }
16
+ declare const GraphPlot: import("svelte").Component<GraphPlotProps, {}, "">;
17
+ type GraphPlot = ReturnType<typeof GraphPlot>;
18
+ export default GraphPlot;
@@ -0,0 +1,303 @@
1
+ <script module lang="ts">
2
+ /**
3
+ * Quota usage bar: used against a hard limit, with reset date and an end-
4
+ * of-window projection. Billing-style: surfaces the headline figure, the
5
+ * "X of Y" copy, and pace so a customer can see whether they will run out.
6
+ */
7
+ export interface GraphQuotaProps {
8
+ title: string;
9
+ used: number;
10
+ limit: number;
11
+ /** Unit label, e.g. "API calls" or "GB". */
12
+ unit?: string;
13
+ /** Optional reset label, e.g. "Oct 1" or "in 4 days". */
14
+ resets?: string;
15
+ /** Days elapsed in the current window. Enables the projection. */
16
+ daysInto?: number;
17
+ /** Window length in days. */
18
+ daysTotal?: number;
19
+ /** Optional number formatter. Defaults to thousands-grouped. */
20
+ format?: (n: number) => string;
21
+ /** Optional caption below the panel. */
22
+ caption?: string;
23
+ corner?: string;
24
+ class?: string;
25
+ }
26
+ </script>
27
+
28
+ <script lang="ts">
29
+ import Graph from '../frame/Graph.svelte';
30
+ import GraphBody from '../frame/GraphBody.svelte';
31
+ import GraphTrack from '../frame/GraphTrack.svelte';
32
+ import GraphTick from '../frame/GraphTick.svelte';
33
+ import { reveal, stagger } from '../frame/motion';
34
+ import { trackMarks } from '../frame/glyphs';
35
+
36
+ let {
37
+ title,
38
+ used,
39
+ limit,
40
+ unit,
41
+ resets,
42
+ daysInto,
43
+ daysTotal,
44
+ format,
45
+ caption,
46
+ corner,
47
+ class: className = ''
48
+ }: GraphQuotaProps = $props();
49
+
50
+ const ticks = 24;
51
+ const fmt = $derived(format ?? defaultFormat);
52
+
53
+ function defaultFormat(n: number): string {
54
+ if (!Number.isFinite(n)) {
55
+ return '0';
56
+ }
57
+ return Math.round(n).toLocaleString('en-US');
58
+ }
59
+
60
+ const view = $derived.by(() => {
61
+ const safeLimit = limit > 0 ? limit : 0;
62
+ const ratio = safeLimit > 0 ? Math.min(1, Math.max(0, used / safeLimit)) : 0;
63
+ const filled = Math.round(ratio * ticks);
64
+ const remaining = Math.max(0, safeLimit - used);
65
+ const percent = Math.round(ratio * 100);
66
+ const projection =
67
+ daysInto != null && daysTotal != null && daysInto > 0 && daysTotal > daysInto
68
+ ? (used / daysInto) * daysTotal
69
+ : null;
70
+ const overBudget = projection != null && projection > safeLimit;
71
+ const projectedPct =
72
+ projection != null && safeLimit > 0 ? (projection / safeLimit) * 100 : null;
73
+ const daysLeft = daysTotal != null && daysInto != null ? Math.max(0, daysTotal - daysInto) : null;
74
+ const perDay = daysInto != null && daysInto > 0 ? used / daysInto : null;
75
+ const pace = projectedPct == null
76
+ ? '—'
77
+ : overBudget
78
+ ? 'over budget'
79
+ : projectedPct >= 95
80
+ ? 'tight'
81
+ : projectedPct >= 75
82
+ ? 'on track'
83
+ : 'ahead';
84
+ const willExceed = overBudget;
85
+ return {
86
+ ratio,
87
+ filled,
88
+ remaining,
89
+ percent,
90
+ projection,
91
+ projectedPct,
92
+ daysLeft,
93
+ perDay,
94
+ pace,
95
+ willExceed
96
+ };
97
+ });
98
+
99
+ const marks = $derived(trackMarks(undefined, { empty: '·', rest: '·', fill: '█' }));
100
+ const indexes = $derived(Array.from({ length: ticks }, (_, i) => i));
101
+ </script>
102
+
103
+ <Graph {title} {corner} class={className}>
104
+ <GraphBody>
105
+ <div class="quota" use:reveal={{ amount: 0.5 }}>
106
+ <p class="headline">
107
+ <span class="used" class:warn={view.willExceed}>{fmt(used)}</span>
108
+ <span class="of">/</span>
109
+ <span class="limit">{fmt(limit)}</span>
110
+ {#if unit}<span class="unit">{unit}</span>{/if}
111
+ </p>
112
+ <p class="bar">
113
+ <span class="brace" aria-hidden="true">[</span>
114
+ <GraphTrack>
115
+ {#each indexes as index, i (index)}
116
+ <GraphTick>
117
+ <span
118
+ class="g"
119
+ class:fill={index < view.filled}
120
+ class:warn={index < view.filled && view.willExceed}
121
+ use:reveal={{ delay: stagger(i, 14), amount: 0.4 }}
122
+ >
123
+ {index < view.filled ? marks.fill : marks.empty}
124
+ </span>
125
+ </GraphTick>
126
+ {/each}
127
+ </GraphTrack>
128
+ <span class="brace" aria-hidden="true">]</span>
129
+ <span class="pct" class:warn={view.willExceed}>{view.percent}%</span>
130
+ </p>
131
+ <dl class="rows">
132
+ {#if resets}
133
+ <div class="row">
134
+ <dt>resets</dt>
135
+ <dd>{resets}</dd>
136
+ </div>
137
+ {/if}
138
+ {#if view.daysLeft != null}
139
+ <div class="row">
140
+ <dt>days left</dt>
141
+ <dd>{view.daysLeft}{daysTotal != null ? ` / ${daysTotal}` : ''}</dd>
142
+ </div>
143
+ {/if}
144
+ {#if view.projection != null && view.projectedPct != null}
145
+ <div class="row">
146
+ <dt>projected</dt>
147
+ <dd class:warn={view.willExceed}>
148
+ {fmt(view.projection)} ({Math.round(view.projectedPct)}% of limit)
149
+ </dd>
150
+ </div>
151
+ {/if}
152
+ {#if view.perDay != null}
153
+ <div class="row">
154
+ <dt>avg / day</dt>
155
+ <dd>{fmt(view.perDay)}</dd>
156
+ </div>
157
+ {/if}
158
+ <div class="row">
159
+ <dt>pace</dt>
160
+ <dd class:warn={view.willExceed}>{view.pace}</dd>
161
+ </div>
162
+ </dl>
163
+ {#if view.willExceed}
164
+ <p class="flag">↗ projected to exceed the limit before reset</p>
165
+ {/if}
166
+ {#if caption}
167
+ <p class="muted">{caption}</p>
168
+ {/if}
169
+ <span class="sr-only">
170
+ Used {fmt(used)} of {fmt(limit)}{unit ? ` ${unit}` : ''}, {view.percent} percent of quota
171
+ {#if resets}, resets {resets}{/if}
172
+ {#if view.projection != null}, projected to use {fmt(view.projection)} by end of window{/if}.
173
+ </span>
174
+ </div>
175
+ </GraphBody>
176
+ </Graph>
177
+
178
+ <style>
179
+ .quota {
180
+ display: flex;
181
+ flex-direction: column;
182
+ gap: 0.85rem;
183
+ }
184
+
185
+ .headline {
186
+ display: flex;
187
+ flex-wrap: wrap;
188
+ justify-content: center;
189
+ align-items: baseline;
190
+ gap: 0.4rem;
191
+ margin: 0;
192
+ font-size: 1.4rem;
193
+ line-height: 1.1;
194
+ letter-spacing: -0.015em;
195
+ }
196
+
197
+ .used {
198
+ color: var(--graph-accent, oklch(0.78 0.17 155));
199
+ }
200
+
201
+ .used.warn {
202
+ color: var(--graph-accent-3, oklch(0.72 0.13 30));
203
+ }
204
+
205
+ .of {
206
+ color: var(--graph-muted, oklch(0.62 0 0));
207
+ }
208
+
209
+ .limit {
210
+ color: var(--graph-foreground, oklch(0.93 0 0));
211
+ }
212
+
213
+ .unit {
214
+ color: var(--graph-muted, oklch(0.62 0 0));
215
+ font-size: 0.95rem;
216
+ }
217
+
218
+ .bar {
219
+ display: flex;
220
+ width: 100%;
221
+ align-items: center;
222
+ column-gap: 0.75rem;
223
+ margin: 0;
224
+ }
225
+
226
+ .brace {
227
+ user-select: none;
228
+ color: var(--graph-frame, oklch(0.6 0 0 / 0.5));
229
+ }
230
+
231
+ .g {
232
+ color: var(--graph-frame, oklch(0.6 0 0 / 0.5));
233
+ }
234
+
235
+ .g.fill {
236
+ color: var(--graph-accent, oklch(0.78 0.17 155));
237
+ }
238
+
239
+ .g.fill.warn {
240
+ color: var(--graph-accent-3, oklch(0.72 0.13 30));
241
+ }
242
+
243
+ .pct {
244
+ width: 5ch;
245
+ flex-shrink: 0;
246
+ text-align: right;
247
+ color: var(--graph-accent, oklch(0.78 0.17 155));
248
+ }
249
+
250
+ .pct.warn {
251
+ color: var(--graph-accent-3, oklch(0.72 0.13 30));
252
+ }
253
+
254
+ .rows {
255
+ display: flex;
256
+ flex-wrap: wrap;
257
+ justify-content: center;
258
+ gap: 0.35rem 1.25rem;
259
+ margin: 0;
260
+ }
261
+
262
+ .row {
263
+ display: flex;
264
+ align-items: baseline;
265
+ gap: 0.4rem;
266
+ }
267
+
268
+ .row dt {
269
+ margin: 0;
270
+ color: var(--graph-muted, oklch(0.62 0 0));
271
+ }
272
+
273
+ .row dd {
274
+ margin: 0;
275
+ }
276
+
277
+ .warn {
278
+ color: var(--graph-accent-3, oklch(0.72 0.13 30));
279
+ }
280
+
281
+ .flag {
282
+ margin: 0;
283
+ text-align: center;
284
+ color: var(--graph-accent-3, oklch(0.72 0.13 30));
285
+ }
286
+
287
+ .muted {
288
+ margin: 0;
289
+ color: var(--graph-muted, oklch(0.62 0 0));
290
+ }
291
+
292
+ .sr-only {
293
+ position: absolute;
294
+ width: 1px;
295
+ height: 1px;
296
+ padding: 0;
297
+ margin: -1px;
298
+ overflow: hidden;
299
+ clip-path: inset(50%);
300
+ white-space: nowrap;
301
+ border-width: 0;
302
+ }
303
+ </style>
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Quota usage bar: used against a hard limit, with reset date and an end-
3
+ * of-window projection. Billing-style: surfaces the headline figure, the
4
+ * "X of Y" copy, and pace so a customer can see whether they will run out.
5
+ */
6
+ export interface GraphQuotaProps {
7
+ title: string;
8
+ used: number;
9
+ limit: number;
10
+ /** Unit label, e.g. "API calls" or "GB". */
11
+ unit?: string;
12
+ /** Optional reset label, e.g. "Oct 1" or "in 4 days". */
13
+ resets?: string;
14
+ /** Days elapsed in the current window. Enables the projection. */
15
+ daysInto?: number;
16
+ /** Window length in days. */
17
+ daysTotal?: number;
18
+ /** Optional number formatter. Defaults to thousands-grouped. */
19
+ format?: (n: number) => string;
20
+ /** Optional caption below the panel. */
21
+ caption?: string;
22
+ corner?: string;
23
+ class?: string;
24
+ }
25
+ declare const GraphQuota: import("svelte").Component<GraphQuotaProps, {}, "">;
26
+ type GraphQuota = ReturnType<typeof GraphQuota>;
27
+ export default GraphQuota;
@@ -0,0 +1,141 @@
1
+ <script module lang="ts">
2
+ import type { Glyphs } from '../frame/glyphs';
3
+ import type { GraphPalette } from '../frame/tone';
4
+
5
+ /** One ranked row: label, a full-width glyph bar, and the value. */
6
+ export interface RankItem {
7
+ label: string;
8
+ value: number;
9
+ display?: string;
10
+ }
11
+
12
+ export interface GraphRankProps {
13
+ title: string;
14
+ items: RankItem[];
15
+ max?: number;
16
+ ticks?: number;
17
+ glyphs?: Glyphs;
18
+ palette?: GraphPalette;
19
+ corner?: string;
20
+ class?: string;
21
+ }
22
+ </script>
23
+
24
+ <script lang="ts">
25
+ import Graph from '../frame/Graph.svelte';
26
+ import GraphBody from '../frame/GraphBody.svelte';
27
+ import GraphTrack from '../frame/GraphTrack.svelte';
28
+ import GraphTick from '../frame/GraphTick.svelte';
29
+ import { reveal, stagger } from '../frame/motion';
30
+ import { trackMarks } from '../frame/glyphs';
31
+
32
+ let {
33
+ title,
34
+ items,
35
+ max,
36
+ ticks = 20,
37
+ glyphs,
38
+ palette,
39
+ corner,
40
+ class: className = ''
41
+ }: GraphRankProps = $props();
42
+
43
+ const peak = $derived(max ?? Math.max(...items.map((entry) => entry.value), 1));
44
+ const marks = $derived(trackMarks(glyphs, { empty: '-', rest: '=', fill: '=' }));
45
+ const indexes = $derived(Array.from({ length: ticks }, (_, i) => i));
46
+
47
+ function filledCount(value: number): number {
48
+ return Math.min(ticks, Math.round((Math.max(value, 0) / peak) * ticks));
49
+ }
50
+
51
+ function formatValue(item: RankItem): string {
52
+ if (item.display) {
53
+ return item.display;
54
+ }
55
+
56
+ return item.value.toLocaleString('en-US', {
57
+ maximumFractionDigits: Number.isInteger(item.value) ? 0 : 1
58
+ });
59
+ }
60
+ </script>
61
+
62
+ <Graph {title} {corner} class={className}>
63
+ <GraphBody>
64
+ <ol class="rank" role="list">
65
+ {#each items as entry, index (entry.label)}
66
+ {@const filled = filledCount(entry.value)}
67
+ {@const shown = formatValue(entry)}
68
+ <li
69
+ class="row"
70
+ aria-label="{entry.label} {shown}"
71
+ use:reveal={{ delay: stagger(index, 50), amount: 0.4 }}
72
+ >
73
+ <span class="label">{entry.label}</span>
74
+ <span class="mid">
75
+ <span class="brace" aria-hidden="true">[</span>
76
+ <GraphTrack>
77
+ {#each indexes as tick (tick)}
78
+ <GraphTick>
79
+ <span class="g" class:fill={tick < filled}>
80
+ {tick < filled ? marks.fill : marks.empty}
81
+ </span>
82
+ </GraphTick>
83
+ {/each}
84
+ </GraphTrack>
85
+ <span class="brace" aria-hidden="true">]</span>
86
+ </span>
87
+ <span class="value">{shown}</span>
88
+ </li>
89
+ {/each}
90
+ </ol>
91
+ </GraphBody>
92
+ </Graph>
93
+
94
+ <style>
95
+ .rank {
96
+ display: flex;
97
+ width: 100%;
98
+ flex-direction: column;
99
+ gap: 0.5rem;
100
+ margin: 0;
101
+ padding: 0;
102
+ list-style: none;
103
+ }
104
+
105
+ .row {
106
+ display: grid;
107
+ grid-template-columns: 7rem minmax(0, 1fr) 7rem;
108
+ align-items: center;
109
+ column-gap: 1rem;
110
+ }
111
+
112
+ .label {
113
+ overflow: hidden;
114
+ text-overflow: ellipsis;
115
+ white-space: nowrap;
116
+ }
117
+
118
+ .mid {
119
+ display: flex;
120
+ min-width: 0;
121
+ align-items: center;
122
+ }
123
+
124
+ .brace {
125
+ user-select: none;
126
+ color: var(--graph-frame, oklch(0.6 0 0 / 0.5));
127
+ }
128
+
129
+ .g {
130
+ color: var(--graph-frame, oklch(0.6 0 0 / 0.5));
131
+ }
132
+
133
+ .g.fill {
134
+ color: var(--graph-accent, oklch(0.78 0.17 155));
135
+ }
136
+
137
+ .value {
138
+ text-align: right;
139
+ color: var(--graph-muted, oklch(0.62 0 0));
140
+ }
141
+ </style>