@bezda/rhp-mcp 0.0.0-stage → 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 (64) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +222 -2
  3. package/bin/rhp-mcp.js +129 -0
  4. package/package.json +62 -4
  5. package/skill/SKILL.md +450 -0
  6. package/skill/recipes/area.html +156 -0
  7. package/skill/recipes/bar.html +145 -0
  8. package/skill/recipes/box-plot.html +164 -0
  9. package/skill/recipes/bubble.html +188 -0
  10. package/skill/recipes/bullet.html +138 -0
  11. package/skill/recipes/candlestick.html +179 -0
  12. package/skill/recipes/column.html +138 -0
  13. package/skill/recipes/dial.html +349 -0
  14. package/skill/recipes/diverging-bars.html +150 -0
  15. package/skill/recipes/donut.html +231 -0
  16. package/skill/recipes/dumbbell.html +137 -0
  17. package/skill/recipes/gantt.html +196 -0
  18. package/skill/recipes/grouped-bars.html +142 -0
  19. package/skill/recipes/heatmap.html +183 -0
  20. package/skill/recipes/histogram.html +199 -0
  21. package/skill/recipes/line.html +170 -0
  22. package/skill/recipes/live.html +255 -0
  23. package/skill/recipes/log-line.html +493 -0
  24. package/skill/recipes/lollipop.html +152 -0
  25. package/skill/recipes/multi-line.html +228 -0
  26. package/skill/recipes/pyramid.html +162 -0
  27. package/skill/recipes/race.html +278 -0
  28. package/skill/recipes/radial-bars.html +157 -0
  29. package/skill/recipes/scatter.html +199 -0
  30. package/skill/recipes/silhouettes.html +352 -0
  31. package/skill/recipes/slope.html +199 -0
  32. package/skill/recipes/sparklines.html +175 -0
  33. package/skill/recipes/stacked-100.html +158 -0
  34. package/skill/recipes/stacked-bars.html +150 -0
  35. package/skill/recipes/strip.html +221 -0
  36. package/skill/recipes/unit-stack.html +276 -0
  37. package/skill/recipes/violin.html +169 -0
  38. package/skill/recipes/waffle.html +165 -0
  39. package/skill/recipes/waterfall.html +127 -0
  40. package/skill/references/api.md +1684 -0
  41. package/skill/references/design.md +740 -0
  42. package/skill/references/environments.md +1577 -0
  43. package/skill/references/forms.md +197 -0
  44. package/skill/references/interaction.md +794 -0
  45. package/skill/references/looks.md +267 -0
  46. package/skill/references/pitfalls.md +202 -0
  47. package/src/check/README.md +176 -0
  48. package/src/check/browser.js +92 -0
  49. package/src/check/build.js +439 -0
  50. package/src/check/colors.js +324 -0
  51. package/src/check/findings.js +247 -0
  52. package/src/check/index.js +508 -0
  53. package/src/check/inpage.js +1332 -0
  54. package/src/check/interact.js +266 -0
  55. package/src/check/png.js +98 -0
  56. package/src/check/report.js +106 -0
  57. package/src/check/source.js +387 -0
  58. package/src/check/sourcemap.js +75 -0
  59. package/src/guard/note.js +30 -0
  60. package/src/guard/render.js +17 -0
  61. package/src/guard/rhp.js +612 -0
  62. package/src/guard/rules.js +125 -0
  63. package/src/server.js +266 -0
  64. package/src/skill.js +140 -0
@@ -0,0 +1,493 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>Bitcoin's decade, rung by rung</title>
7
+ <meta name="description" content="Log line: one series over ten years on a log scale drawn as a ladder of doublings, every fall below the last record shaded, the falls of more than half measured, events marked at the foot, and a crosshair readout by pointer, tap, sideways drag or arrow keys. Use it for a price or any quantity that grows by multiples.">
8
+ <!--
9
+ A recipe gives technique, not a design.
10
+ Take: a daily Line on a log2 cross scale with a rung at each doubling, the shape between the record line and the price as one filled Line (out along the record line, back along the price, with `fill` and `base`), crashes found from the data and measured with a plumb line, events marked with a lead up to the line, a crosshair that follows pointer, tap or drag, and invisible month columns (quarters on a phone) for the arrow keys.
11
+ Leave: the design. This poster was made for its own subject (Bitcoin's decade on a warm peach ground, an italic display face, the multiple of the first price as the big number, teal for the falls and orange split coins for the halvings).
12
+ Its look, the place of its parts and the way its marks are drawn say that subject: design yours from your own (design.md, section 2).
13
+ -->
14
+ <link rel="preconnect" href="https://fonts.googleapis.com">
15
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
16
+ <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Ubuntu:ital,wght@0,700;1,700&family=Ubuntu+Sans:wght@400;600&display=swap">
17
+ <script type="importmap">{ "imports": { "@bezda/rhp/standalone": "https://cdn.jsdelivr.net/npm/@bezda/rhp@2/dist/standalone.js" } }</script>
18
+ <style>
19
+ /* Bitcoin's own orange, pale, as the paper; a deep teal from the other side of the wheel for the falls */
20
+ :root {
21
+ --page: #d6bd9f; --paper: #fde3c4; --ink: #2a1a0a; --soft: #705c47; --rule: #dbc3a6;
22
+ --accent: #0b5961; --bite: #9ccdc7; --quiet: #89745e; --coin: #f7931a;
23
+ --display: Ubuntu, "Trebuchet MS", sans-serif;
24
+ --text: "Ubuntu Sans", Ubuntu, "Segoe UI", system-ui, sans-serif;
25
+ }
26
+ * { box-sizing: border-box; }
27
+ body { margin: 0; padding: 48px 16px; background: var(--page, #d6bd9f); }
28
+
29
+ /* The poster: a header of two columns over a wide chart, the readout in the chart's empty upper left */
30
+ .poster { max-width: 1040px; margin: 0 auto; padding: 44px 48px 26px; background: var(--paper); color: var(--ink); font: 16px/1.5 var(--text);
31
+ display: grid; grid-template-columns: minmax(0, 1fr); row-gap: 12px; grid-template-areas: "kicker" "headline" "dek" "stage" "note"; }
32
+ .poster figcaption { display: contents; }
33
+ .poster .kicker { grid-area: kicker; font: 600 13px/1.3 var(--text); letter-spacing: .16em; text-transform: uppercase; color: var(--accent); }
34
+ .poster .headline { grid-area: headline; max-width: 15em; font: italic 700 clamp(31px, 4.8vw, 50px)/1.02 var(--display); letter-spacing: -.015em; text-wrap: balance; }
35
+ .poster .dek { grid-area: dek; max-width: 46ch; font-size: 17px; line-height: 1.5; color: var(--soft); }
36
+ .poster .headline .nobreak { white-space: nowrap; }
37
+ .poster .dek .drop { padding: 0 .3em; background: var(--bite); color: var(--ink); }
38
+ .poster .stage { grid-area: stage; position: relative; min-width: 0; margin-top: 8px; }
39
+ .poster .note { grid-area: note; display: block; margin-top: 8px; padding-top: 12px; border-top: 1px solid var(--rule); font-size: 12px; line-height: 1.5; color: var(--soft); }
40
+ @media (min-width: 900px) {
41
+ .poster { grid-template-columns: minmax(0, 8fr) minmax(0, 5fr); column-gap: 48px;
42
+ grid-template-areas: "kicker kicker" "headline dek" "stage stage" "note note"; }
43
+ .poster .dek { align-self: end; }
44
+ }
45
+
46
+ /* The readout: the day picked (the latest close at rest), its multiple of the 2016 price as the big number */
47
+ .plate { display: grid; grid-template-columns: auto 1fr; align-items: center; column-gap: 12px; height: 56px; margin: 0 0 8px;
48
+ font-variant-numeric: tabular-nums; }
49
+ .plate .times { grid-row: 1 / 3; font: italic 700 44px/1 var(--display); letter-spacing: -.02em; }
50
+ .plate .times small { font-size: .5em; }
51
+ .plate .when { align-self: end; font-size: 13px; font-weight: 600; line-height: 1.35; white-space: nowrap; }
52
+ .plate .what { align-self: start; font-size: 13px; line-height: 1.35; color: var(--soft); white-space: nowrap; }
53
+ .plate .what b { font-weight: 600; color: var(--accent); }
54
+ @media (max-width: 360px) {
55
+ /* the narrowest phones: the price and its fall below the record may take two lines */
56
+ .plate { height: 74px; }
57
+ .plate .what { white-space: normal; }
58
+ }
59
+ @media (min-width: 900px) {
60
+ /* over the chart's empty upper left, letting the pointer through to the chart under it */
61
+ .plate { position: absolute; z-index: 2; top: 0; left: 0; display: block; width: 300px; height: 142px; margin: 0; padding: 14px 20px 0 0; background: var(--paper); pointer-events: none; }
62
+ .plate .times { display: block; margin: 4px 0 6px; font-size: 64px; }
63
+ .plate .when, .plate .what { font-size: 14px; }
64
+ }
65
+
66
+ /* The chart's box: a finger dragging sideways moves through the days, up and down still scrolls */
67
+ .plot { touch-action: pan-y; cursor: crosshair; }
68
+ .plot:has(:focus-visible) { outline: 2px solid var(--ink); outline-offset: 6px; }
69
+ .hint { margin: 6px 0 0; font-size: 13px; color: var(--soft); }
70
+ @media (max-width: 640px) {
71
+ body { padding: 16px 16px 32px; }
72
+ .poster { padding: 26px 18px 18px; row-gap: 10px; }
73
+ .poster .dek { font-size: 15px; }
74
+ }
75
+ </style>
76
+ </head>
77
+ <body>
78
+ <div id="chart"></div>
79
+ <script type="module">
80
+ import { Chart, Plot, Scale, Bar, Line, Dot, Tick, Label, Place, Poster, slat, html, render, createSignal, createMemo } from "@bezda/rhp/standalone";
81
+
82
+ // Data: Bitcoin's closing price in US dollars for every day from 5 October 2016 to 5 October 2026, one line per month
83
+ // (Coin Metrics reference rate at 00:00 UTC, rounded to the dollar)
84
+ const FIRST = Date.UTC(2016, 9, 5); // the first day's date
85
+ const CLOSE = [
86
+ 612, 612, 618, 619, 617, 618, 643, 637, 635, 640, 637, 643, 639, 636, 630, 629, 631, 655, 652, 650, 656, 675, 687, 690, 715, 699, 699, // 2016-10
87
+ 731, 740, 690, 705, 707, 716, 706, 712, 722, 713, 716, 704, 704, 706, 712, 742, 735, 750, 751, 730, 737, 748, 741, 737, 740, 735, 728, 730, 732, 742, // 2016-11
88
+ 754, 772, 766, 766, 753, 759, 765, 768, 772, 775, 770, 779, 779, 777, 776, 782, 790, 791, 790, 800, 828, 859, 916, 891, 896, 903, 926, 980, 973, 961, 969, // 2016-12
89
+ 997, 1017, 1033, 1134, 1000, 892, 903, 912, 904, 906, 788, 807, 827, 820, 825, 831, 906, 881, 902, 895, 924, 922, 919, 888, 896, 917, 920, 923, 916, 920, 968, // 2017-01
90
+ 988, 1008, 1015, 1034, 1015, 1023, 1054, 1052, 983, 997, 1011, 1006, 1000, 1012, 1014, 1037, 1059, 1061, 1059, 1089, 1129, 1130, 1189, 1185, 1152, 1182, 1194, 1188, // 2017-02
91
+ 1228, 1263, 1289, 1269, 1276, 1282, 1234, 1152, 1193, 1099, 1177, 1232, 1239, 1247, 1259, 1178, 1072, 963, 1020, 1038, 1112, 1037, 1028, 935, 969, 963, 1043, 1045, 1040, 1038, 1082, // 2017-03
92
+ 1090, 1106, 1145, 1142, 1136, 1195, 1192, 1184, 1214, 1212, 1230, 1216, 1173, 1174, 1176, 1174, 1192, 1202, 1210, 1236, 1247, 1244, 1250, 1257, 1280, 1296, 1346, 1348, 1358, 1382, // 2017-04
93
+ 1437, 1464, 1519, 1540, 1530, 1573, 1526, 1685, 1719, 1782, 1835, 1705, 1780, 1795, 1737, 1757, 1809, 1894, 1968, 2048, 2045, 2079, 2257, 2415, 2320, 2279, 2044, 2226, 2285, 2167, 2299, // 2017-05
94
+ 2400, 2479, 2550, 2525, 2685, 2871, 2707, 2803, 2805, 2916, 2974, 2665, 2707, 2444, 2410, 2476, 2653, 2508, 2590, 2733, 2639, 2687, 2686, 2562, 2498, 2426, 2530, 2563, 2540, 2453, // 2017-06
95
+ 2412, 2519, 2552, 2600, 2608, 2603, 2496, 2553, 2506, 2319, 2319, 2375, 2344, 2215, 1990, 1911, 2216, 2304, 2257, 2824, 2666, 2830, 2753, 2755, 2559, 2522, 2665, 2794, 2706, 2749, 2863, // 2017-07
96
+ 2727, 2690, 2790, 2860, 3237, 3230, 3393, 3426, 3347, 3437, 3661, 3871, 4064, 4305, 4160, 4365, 4309, 4109, 4160, 4086, 3993, 4074, 4147, 4326, 4353, 4342, 4345, 4380, 4600, 4587, 4740, // 2017-08
97
+ 4918, 4595, 4625, 4375, 4454, 4608, 4635, 4345, 4336, 4262, 4213, 4164, 3887, 3247, 3699, 3711, 3712, 4080, 3926, 3896, 3617, 3619, 3776, 3677, 3919, 3890, 4208, 4188, 4155, 4335, // 2017-09
98
+ 4382, 4387, 4307, 4217, 4319, 4366, 4439, 4601, 4778, 4741, 4815, 5394, 5614, 5786, 5690, 5756, 5590, 5584, 5708, 6001, 6030, 5997, 5888, 5518, 5736, 5900, 5772, 5762, 6175, 6119, 6429, // 2017-10
99
+ 6726, 7044, 7199, 7420, 7371, 6985, 7118, 7451, 7118, 6621, 6347, 5829, 6519, 6592, 7257, 7834, 7729, 7796, 8018, 8270, 8092, 8250, 8072, 8232, 8760, 9320, 9731, 9932, 9800, 9997, // 2017-11
100
+ 10809, 10935, 11214, 11562, 11737, 13992, 17032, 16298, 15050, 15475, 16873, 17434, 16515, 16603, 17702, 19641, 19250, 18982, 17653, 16454, 15634, 14306, 14911, 14026, 14081, 15727, 15389, 14289, 14572, 12949, 13921, // 2017-12
101
+ 13465, 14754, 15010, 15070, 16997, 17104, 16232, 14937, 14379, 14669, 13187, 13789, 14231, 13681, 13592, 11431, 11160, 11246, 11440, 12768, 11422, 10737, 10849, 11226, 11130, 11036, 11321, 11616, 11133, 9960, 10050, // 2018-01
102
+ 9040, 8786, 9132, 8251, 6850, 7739, 7625, 8219, 8660, 8530, 8097, 8908, 8524, 9467, 10107, 10189, 11086, 10452, 11139, 11246, 10459, 9878, 10159, 9687, 9603, 10312, 10654, 10307, // 2018-02
103
+ 10924, 11014, 11426, 11463, 11541, 10677, 9903, 9330, 9278, 8787, 9514, 9148, 9161, 8222, 8269, 8377, 7882, 8189, 8537, 8896, 8880, 8711, 8772, 8611, 8441, 8171, 7837, 7934, 7109, 6832, 6922, // 2018-03
104
+ 6805, 7033, 7421, 6781, 6798, 6610, 6886, 7010, 6737, 6817, 6945, 7904, 7919, 8006, 8342, 8047, 7888, 8163, 8273, 8840, 8864, 8801, 8931, 9652, 8856, 9265, 8981, 9342, 9397, 9229, // 2018-04
105
+ 9072, 9204, 9751, 9697, 9796, 9595, 9340, 9212, 9293, 9038, 8420, 8486, 8696, 8671, 8488, 8328, 8051, 8233, 8229, 8511, 8399, 8008, 7521, 7576, 7425, 7326, 7338, 7111, 7454, 7375, 7479, // 2018-05
106
+ 7520, 7637, 7702, 7487, 7611, 7651, 7666, 7623, 7556, 6745, 6849, 6527, 6307, 6630, 6400, 6495, 6451, 6704, 6733, 6753, 6720, 6060, 6174, 6146, 6244, 6096, 6139, 5859, 6221, 6376, // 2018-06
107
+ 6361, 6608, 6498, 6579, 6535, 6598, 6738, 6705, 6664, 6309, 6378, 6159, 6209, 6255, 6348, 6711, 7321, 7376, 7467, 7327, 7411, 7399, 7716, 8398, 8203, 7914, 8174, 8190, 8230, 8172, 7725, // 2018-07
108
+ 7597, 7543, 7411, 7002, 7032, 6930, 6723, 6267, 6558, 6136, 6314, 6315, 6266, 6185, 6270, 6291, 6563, 6399, 6491, 6273, 6473, 6365, 6516, 6698, 6745, 6690, 6839, 7093, 7033, 6967, 7025, // 2018-08
109
+ 7192, 7287, 7257, 7359, 6791, 6488, 6409, 6188, 6249, 6295, 6278, 6328, 6491, 6495, 6524, 6497, 6259, 6340, 6384, 6499, 6741, 6705, 6703, 6577, 6416, 6463, 6678, 6623, 6584, 6604, // 2018-09
110
+ 6562, 6509, 6464, 6545, 6585, 6550, 6565, 6605, 6590, 6529, 6159, 6192, 6198, 6183, 6443, 6462, 6443, 6396, 6383, 6408, 6415, 6408, 6396, 6414, 6402, 6404, 6409, 6407, 6263, 6272, 6306, // 2018-10
111
+ 6343, 6348, 6332, 6420, 6403, 6441, 6501, 6407, 6331, 6353, 6335, 6321, 6268, 5574, 5539, 5496, 5501, 5555, 4792, 4318, 4549, 4304, 4290, 3806, 3947, 3700, 3777, 4208, 4239, 3973, // 2018-11
112
+ 4148, 4099, 3844, 3893, 3710, 3512, 3373, 3408, 3540, 3414, 3353, 3423, 3260, 3199, 3185, 3195, 3499, 3660, 3698, 4068, 3839, 3965, 3943, 4040, 3762, 3808, 3585, 3881, 3823, 3825, 3687, // 2018-12
113
+ 3808, 3898, 3784, 3828, 3799, 4046, 4001, 3993, 4004, 3628, 3624, 3617, 3509, 3667, 3581, 3607, 3640, 3609, 3687, 3538, 3536, 3577, 3552, 3568, 3561, 3557, 3538, 3435, 3396, 3438, 3410, // 2019-01
114
+ 3445, 3462, 3413, 3411, 3426, 3368, 3359, 3612, 3621, 3642, 3591, 3588, 3576, 3562, 3565, 3585, 3623, 3866, 3895, 3932, 3892, 3948, 4110, 3754, 3822, 3796, 3799, 3793, // 2019-02
115
+ 3811, 3810, 3782, 3698, 3840, 3852, 3856, 3839, 3910, 3904, 3852, 3859, 3852, 3854, 3902, 3990, 3967, 3969, 3996, 4031, 3976, 3984, 3982, 3971, 3908, 3918, 4027, 4011, 4089, 4092, 4094, // 2019-03
116
+ 4138, 4904, 4960, 4911, 5033, 5044, 5189, 5283, 5196, 5312, 5051, 5083, 5075, 5157, 5043, 5206, 5226, 5279, 5290, 5314, 5296, 5384, 5545, 5432, 5128, 5150, 5182, 5149, 5149, 5267, // 2019-04
117
+ 5315, 5393, 5658, 5775, 5728, 5692, 5826, 5932, 6150, 6357, 7325, 6943, 7799, 7956, 8215, 7862, 7327, 7278, 8220, 7981, 7984, 7668, 7897, 7987, 8062, 8732, 8817, 8719, 8655, 8272, 8556, // 2019-05
118
+ 8555, 8744, 8173, 7650, 7799, 7777, 8024, 7940, 7634, 7993, 7922, 8147, 8233, 8698, 8851, 9020, 9341, 9047, 9285, 9547, 10083, 10685, 10794, 11004, 11726, 12863, 11110, 12358, 11944, 10843, // 2019-06
119
+ 10570, 10766, 11952, 11177, 10998, 11208, 11456, 12320, 12579, 12107, 11295, 11813, 11347, 10283, 10926, 9471, 9675, 10680, 10532, 10863, 10590, 10319, 9867, 9793, 9897, 9837, 9447, 9530, 9504, 9589, 10059, // 2019-07
120
+ 10400, 10530, 10810, 10987, 11790, 11423, 11967, 11938, 11858, 11304, 11530, 11385, 10867, 10024, 10310, 10373, 10207, 10331, 10885, 10771, 10097, 10120, 10414, 10147, 10100, 10363, 10168, 9725, 9482, 9578, 9605, // 2019-08
121
+ 9763, 10377, 10619, 10577, 10580, 10331, 10495, 10407, 10332, 10086, 10142, 10403, 10345, 10363, 10306, 10258, 10196, 10161, 10268, 10170, 9986, 10047, 9693, 8614, 8424, 8092, 8187, 8202, 8065, 8282, // 2019-09
122
+ 8315, 8363, 8254, 8166, 8142, 7855, 8226, 8190, 8598, 8578, 8290, 8324, 8291, 8346, 8161, 8004, 8072, 7959, 7948, 8214, 8212, 8029, 7458, 7454, 8657, 9250, 9558, 9319, 9431, 9183, 9155, // 2019-10
123
+ 9251, 9300, 9210, 9418, 9331, 9355, 9211, 8791, 8811, 9043, 8713, 8789, 8767, 8645, 8466, 8478, 8505, 8184, 8117, 8077, 7612, 7278, 7325, 6943, 7139, 7165, 7527, 7436, 7748, 7556, // 2019-11
124
+ 7418, 7318, 7306, 7215, 7404, 7534, 7512, 7529, 7340, 7232, 7200, 7193, 7245, 7071, 7111, 6882, 6609, 7285, 7146, 7184, 7140, 7479, 7322, 7236, 7191, 7195, 7237, 7307, 7392, 7229, 7167, // 2019-12
125
+ 7171, 6947, 7315, 7343, 7346, 7766, 8136, 8060, 7817, 8130, 8032, 8165, 8122, 8830, 8811, 8719, 8913, 8928, 8687, 8636, 8731, 8648, 8366, 8423, 8345, 8574, 8902, 9297, 9312, 9509, 9357, // 2020-01
126
+ 9382, 9339, 9280, 9162, 9628, 9739, 9800, 9911, 10151, 9872, 10272, 10360, 10235, 10362, 9912, 9952, 9683, 10195, 9636, 9614, 9702, 9675, 9973, 9648, 9344, 8799, 8779, 8751, 8582, // 2020-02
127
+ 8542, 8906, 8769, 8756, 9063, 9147, 8898, 8110, 7896, 7910, 7939, 4959, 5628, 5145, 5359, 5013, 5417, 5392, 6204, 6174, 6176, 5831, 6486, 6775, 6690, 6754, 6491, 6232, 5905, 6437, 6432, // 2020-03
128
+ 6643, 6793, 6752, 6860, 6791, 7298, 7188, 7372, 7300, 6860, 6883, 6979, 6864, 6876, 6625, 7128, 7081, 7263, 7150, 6853, 6870, 7124, 7489, 7506, 7538, 7684, 7777, 7770, 8762, 8652, // 2020-04
129
+ 8856, 8977, 8897, 8882, 8986, 9267, 9994, 9859, 9577, 8715, 8592, 8817, 9323, 9801, 9327, 9395, 9683, 9727, 9754, 9516, 9069, 9162, 9184, 8806, 8909, 8835, 9169, 9568, 9432, 9692, 9434, // 2020-05
130
+ 10199, 9512, 9642, 9813, 9642, 9669, 9748, 9769, 9779, 9888, 9277, 9456, 9461, 9343, 9441, 9520, 9449, 9390, 9286, 9357, 9285, 9685, 9612, 9293, 9247, 9159, 9002, 9108, 9184, 9144, // 2020-06
131
+ 9241, 9094, 9067, 9126, 9080, 9339, 9252, 9435, 9232, 9285, 9237, 9289, 9237, 9260, 9197, 9133, 9156, 9176, 9219, 9167, 9389, 9531, 9609, 9548, 9702, 9934, 11047, 10945, 11116, 11135, 11338, // 2020-07
132
+ 11798, 11094, 11236, 11203, 11721, 11769, 11599, 11743, 11681, 11870, 11393, 11576, 11773, 11774, 11867, 11900, 12316, 11993, 11737, 11868, 11525, 11682, 11666, 11771, 11358, 11472, 11315, 11528, 11491, 11699, 11678, // 2020-08
133
+ 11970, 11415, 10261, 10472, 10129, 10253, 10368, 10109, 10223, 10335, 10387, 10437, 10323, 10663, 10779, 10968, 10936, 10920, 11074, 10921, 10455, 10520, 10233, 10739, 10684, 10742, 10759, 10727, 10841, 10772, // 2020-09
134
+ 10607, 10570, 10556, 10666, 10773, 10596, 10667, 10903, 11074, 11303, 11378, 11548, 11435, 11417, 11503, 11339, 11363, 11490, 11746, 11929, 12848, 12997, 12953, 13114, 13042, 13081, 13676, 13290, 13475, 13603, 13807, // 2020-10
135
+ 13737, 13596, 13997, 14129, 15606, 15619, 14866, 15500, 15320, 15319, 15731, 16290, 16330, 16102, 15987, 16743, 17682, 17805, 17819, 18664, 18707, 18483, 18373, 19150, 18748, 17114, 17102, 17745, 18191, 19664, // 2020-11
136
+ 18821, 19215, 19470, 18733, 19115, 19356, 19182, 18341, 18575, 18295, 18062, 18813, 19156, 19287, 19433, 21371, 22789, 23049, 23857, 23529, 22937, 23757, 23307, 23708, 24669, 26478, 26431, 27039, 27231, 28845, 29023, // 2020-12
137
+ 29381, 32023, 33278, 31802, 34013, 36643, 39216, 40775, 40371, 38329, 35270, 33712, 37297, 39046, 36710, 36177, 36070, 36594, 36250, 35515, 31023, 32986, 32036, 32216, 32420, 32641, 30359, 33511, 34166, 34350, 33158, // 2021-01
138
+ 33570, 35596, 37578, 37129, 38045, 39342, 39003, 46122, 46548, 45078, 47893, 47525, 47209, 48830, 48110, 49140, 52225, 51650, 55821, 55862, 57501, 53952, 48560, 49567, 47568, 46232, 45835, 45359, // 2021-02
139
+ 49634, 48305, 50690, 48473, 48801, 48971, 51086, 52088, 54739, 56116, 57847, 57335, 61289, 59906, 56121, 56500, 58683, 57731, 58168, 58248, 57448, 54453, 54564, 52595, 51547, 54818, 56020, 55713, 57571, 58685, 58792, // 2021-03
140
+ 58819, 59032, 57282, 58202, 58756, 58084, 56267, 57963, 58052, 59652, 59933, 59906, 63446, 62869, 63231, 61571, 60281, 56390, 55798, 56474, 54026, 51882, 50955, 50280, 48947, 53943, 55027, 54787, 53567, 57741, // 2021-04
141
+ 57864, 56573, 57243, 53724, 57342, 56527, 57341, 58736, 58288, 55903, 56612, 50972, 49369, 49997, 47048, 46023, 43492, 42790, 37643, 40799, 37174, 37643, 34774, 38630, 38270, 39195, 38511, 35597, 34682, 35652, 37313, // 2021-05
142
+ 36661, 37639, 39160, 36884, 35410, 35721, 33725, 33475, 37373, 36826, 37185, 35669, 38925, 40456, 40255, 38266, 38019, 35714, 35556, 35600, 31715, 32395, 33610, 34659, 31684, 31891, 34506, 34376, 35934, 35065, // 2021-06
143
+ 33505, 33783, 34588, 35356, 33912, 34124, 33932, 32820, 33915, 33611, 34258, 33135, 32642, 32798, 31708, 31456, 31548, 31708, 30883, 29767, 32120, 32307, 33440, 34167, 35120, 37445, 39127, 39939, 40086, 41790, 41793, // 2021-07
144
+ 39969, 39304, 38289, 39774, 40942, 42790, 44485, 43972, 46266, 45489, 45676, 44406, 47717, 47124, 47074, 45985, 44714, 44954, 46647, 49241, 49070, 49363, 49591, 47926, 49002, 47190, 49048, 48852, 48948, 47126, 47219, // 2021-08
145
+ 48689, 49370, 49861, 49920, 51748, 52641, 46854, 46188, 46449, 44765, 45099, 46181, 44992, 47023, 48167, 47797, 47214, 48204, 47218, 42856, 40526, 43578, 44861, 42803, 42735, 43131, 42497, 41224, 41454, 43782, // 2021-09
146
+ 48078, 47792, 48203, 49289, 51558, 55420, 53805, 53934, 55011, 54701, 57289, 56196, 57349, 57384, 61446, 60932, 61462, 61977, 64291, 66062, 62374, 60751, 61250, 60865, 63032, 60394, 58563, 60565, 62231, 61743, 61433, // 2021-10
147
+ 61092, 63025, 62956, 61394, 61006, 61455, 63044, 67542, 67096, 64756, 64963, 64079, 64398, 65032, 63754, 60322, 60154, 56794, 58014, 59749, 59033, 56384, 57642, 57142, 59009, 53801, 54636, 57249, 57921, 57097, // 2021-11
148
+ 57181, 56583, 53672, 49160, 49327, 50535, 50574, 50501, 47938, 47437, 49322, 50137, 46785, 48293, 48831, 47663, 46334, 46905, 46876, 46955, 49082, 48692, 50713, 50784, 50590, 50798, 50796, 47673, 46470, 47102, 46355, // 2021-12
149
+ 47560, 47353, 46453, 45906, 43531, 43145, 41513, 41800, 41886, 41817, 42768, 43961, 42627, 43103, 43207, 43169, 42211, 42431, 41788, 40756, 36492, 34996, 36271, 36660, 36953, 36826, 37022, 37769, 38088, 37984, 38463, // 2022-01
150
+ 38788, 36942, 37058, 41080, 41509, 42358, 43889, 44167, 44405, 43614, 42357, 42173, 42196, 42646, 44512, 44066, 40610, 40044, 40096, 38552, 37098, 38203, 37308, 38284, 39324, 39057, 37665, 43179, // 2022-02
151
+ 44334, 43985, 42493, 39106, 39384, 38399, 38147, 38725, 41989, 39482, 38825, 38951, 37808, 39649, 39357, 41102, 40966, 41826, 42193, 41295, 41064, 42383, 42734, 43975, 44347, 44520, 46777, 47224, 47464, 47123, 45562, // 2022-03
152
+ 46250, 45940, 46441, 46663, 45693, 43305, 43569, 42222, 42681, 42264, 39544, 40096, 41153, 39916, 40527, 40440, 39705, 40819, 41490, 41396, 40457, 39738, 39582, 39512, 40474, 38073, 39219, 39748, 38610, 37714, // 2022-04
153
+ 38459, 38566, 37704, 39642, 36502, 36043, 35509, 34059, 30458, 30991, 28797, 29031, 29286, 30052, 31183, 29890, 30438, 28771, 30258, 29224, 29420, 30332, 29088, 29644, 29577, 29332, 28612, 29046, 29430, 31736, 31831, // 2022-05
154
+ 29826, 30504, 29663, 29792, 29915, 31334, 31175, 30228, 30064, 29070, 28361, 26830, 22316, 22073, 22521, 20310, 20465, 19014, 20492, 20572, 20688, 20003, 21097, 21299, 21468, 21067, 20751, 20263, 20081, 19333, // 2022-06
155
+ 19342, 19232, 19277, 20226, 20193, 20561, 21651, 21814, 21585, 20831, 19971, 19339, 20156, 20553, 20832, 21197, 20828, 22306, 23371, 23281, 23152, 22706, 22484, 22653, 21510, 21182, 22898, 23839, 23951, 23623, 23356, // 2022-07
156
+ 23331, 23031, 22810, 22628, 23249, 22996, 23154, 23804, 23186, 23923, 23934, 24396, 24426, 24314, 24084, 23869, 23336, 23221, 20907, 21147, 21554, 21301, 21535, 21425, 21588, 20246, 20044, 19674, 20271, 19825, 20025, // 2022-08
157
+ 20105, 19949, 19804, 19962, 19798, 18846, 19293, 19319, 21363, 21715, 21733, 22365, 20165, 20246, 19697, 19742, 20121, 19427, 19558, 18873, 18507, 19415, 19327, 18925, 18793, 19201, 19107, 19455, 19541, 19435, // 2022-09
158
+ 19312, 19015, 19633, 20340, 20155, 19951, 19539, 19416, 19428, 19154, 19039, 19157, 19388, 19178, 19070, 19262, 19557, 19341, 19132, 19033, 19175, 19208, 19565, 19341, 20095, 20797, 20280, 20605, 20804, 20617, 20491, // 2022-10
159
+ 20484, 20148, 20198, 21157, 21283, 20947, 20569, 18521, 15758, 17542, 16917, 16771, 16320, 16629, 16856, 16660, 16675, 16662, 16696, 16247, 15778, 16173, 16574, 16584, 16522, 16449, 16436, 16213, 16443, 17177, // 2022-11
160
+ 16961, 17075, 16900, 17122, 16961, 17071, 16848, 17231, 17136, 17124, 17094, 17196, 17775, 17810, 17344, 16606, 16774, 16776, 16424, 16902, 16801, 16814, 16781, 16838, 16825, 16880, 16699, 16536, 16631, 16594, 16524, // 2022-12
161
+ 16607, 16689, 16670, 16848, 16825, 16958, 16943, 17059, 17182, 17434, 17834, 18870, 19869, 21011, 20876, 21180, 21178, 20710, 21072, 22661, 22803, 22716, 22929, 22611, 23109, 23009, 23070, 23009, 23775, 22799, 23130, // 2023-01
162
+ 23727, 23506, 23446, 23356, 22957, 22745, 23266, 22938, 21818, 21621, 21865, 21772, 21799, 22222, 24305, 23742, 24607, 24637, 24367, 24798, 24400, 24164, 23910, 23176, 23160, 23542, 23504, 23146, // 2023-02
163
+ 23611, 23470, 22350, 22334, 22420, 22415, 22175, 21701, 20350, 20240, 20585, 22066, 24154, 24769, 24410, 25043, 27451, 26986, 28185, 27834, 28173, 27342, 28388, 27464, 27489, 28049, 27155, 27284, 28389, 28022, 28515, // 2023-03
164
+ 28495, 28162, 27851, 28114, 28190, 28049, 27937, 27964, 28358, 29688, 30247, 29913, 30371, 30459, 30342, 30313, 29470, 30367, 28797, 28251, 27292, 27828, 27605, 27520, 28303, 28384, 29466, 29351, 29222, 29362, // 2023-04
165
+ 28115, 28691, 29045, 28856, 29553, 28899, 28762, 27727, 27642, 27646, 26986, 26773, 26841, 26918, 27232, 27026, 27401, 26843, 26890, 27101, 26777, 26859, 27227, 26341, 26485, 26720, 26855, 28098, 27765, 27712, 27218, // 2023-05
166
+ 26825, 27255, 27079, 27158, 25786, 27214, 26340, 26522, 26471, 25863, 25907, 25907, 25881, 25098, 25559, 26332, 26514, 26354, 26778, 28305, 30100, 29953, 30638, 30549, 30467, 30256, 30667, 30104, 30461, 30485, // 2023-06
167
+ 30582, 30594, 31135, 30800, 30498, 29980, 30330, 30264, 30163, 30395, 30616, 30386, 31435, 30311, 30291, 30250, 30144, 29836, 29913, 29806, 29920, 29726, 30067, 29178, 29221, 29362, 29202, 29316, 29360, 29263, 29220, // 2023-07
168
+ 29500, 29143, 29192, 29057, 29053, 29048, 29164, 29780, 29583, 29430, 29400, 29416, 29286, 29409, 29168, 28754, 26661, 26037, 26094, 26176, 26129, 25978, 26452, 26127, 26041, 26006, 26086, 26103, 27672, 27299, 25955, // 2023-08
169
+ 25803, 25868, 25963, 25800, 25782, 25755, 26211, 25905, 25895, 25831, 25135, 25860, 26227, 26556, 26673, 26556, 26502, 26765, 27215, 27133, 26572, 26581, 26576, 26287, 26289, 26195, 26336, 27033, 26911, 26978, // 2023-09
170
+ 27950, 27566, 27439, 27797, 27426, 27942, 27973, 27936, 27591, 27420, 26828, 26741, 26842, 26862, 27130, 28507, 28433, 28328, 28690, 29705, 29923, 29984, 32954, 33881, 34489, 34181, 33897, 34092, 34574, 34495, 34636, // 2023-10
171
+ 35432, 34883, 34708, 35091, 35105, 35018, 35418, 35798, 36675, 37367, 36980, 37053, 36543, 35591, 37841, 36160, 36570, 36577, 37443, 37516, 36035, 37403, 37312, 37719, 37800, 37483, 37229, 37828, 37847, 37713, // 2023-11
172
+ 38704, 39462, 39978, 41910, 44067, 43746, 43283, 44205, 43740, 43733, 41210, 41467, 42939, 43033, 41978, 42203, 41429, 42636, 42272, 43605, 43870, 44010, 43769, 43098, 43624, 42491, 43437, 42671, 41999, 42204, 42217, // 2023-12
173
+ 44049, 44941, 42774, 44251, 44146, 43918, 43844, 46981, 46084, 46818, 46381, 42817, 42847, 41888, 42529, 43171, 42689, 41262, 41607, 41669, 41537, 39577, 39737, 40102, 39918, 41861, 42125, 41983, 43218, 42950, 42589, // 2024-01
174
+ 43020, 43155, 42982, 42576, 42619, 43103, 44253, 45332, 47177, 47793, 48201, 49990, 49628, 51844, 51939, 52157, 51675, 52148, 51805, 52308, 51744, 51292, 50789, 51578, 51746, 54552, 57050, 62461, 61395, // 2024-02
175
+ 62524, 62050, 62991, 68092, 63951, 66104, 67070, 68344, 68503, 68882, 72198, 71433, 73082, 71505, 69425, 65434, 68285, 67771, 61969, 67848, 65455, 63486, 64361, 67290, 70042, 70071, 69268, 70827, 69917, 69663, 71227, // 2024-03
176
+ 69792, 65534, 66130, 68438, 67905, 69136, 69393, 71713, 69075, 70579, 70047, 67098, 64552, 65649, 63393, 63785, 61324, 63510, 63763, 64908, 64968, 66945, 66385, 64190, 64515, 63777, 63429, 62979, 63908, 60636, // 2024-04
177
+ 58142, 59170, 62984, 63881, 64068, 63228, 62397, 61079, 62998, 60897, 60844, 61431, 62887, 61556, 66282, 65269, 66956, 66978, 66259, 71202, 70217, 69075, 67759, 68609, 69283, 68445, 69348, 68343, 67584, 68375, 67378, // 2024-05
178
+ 67714, 67805, 68833, 70554, 71087, 70789, 69338, 69310, 69644, 69466, 67368, 68214, 66783, 65982, 66205, 66623, 66484, 65112, 64879, 64898, 64086, 64258, 63345, 60259, 61740, 60794, 61570, 60303, 60889, 62763, // 2024-06
179
+ 62835, 62029, 60216, 57418, 56721, 58259, 56113, 56692, 57981, 57722, 57308, 57859, 59329, 61015, 64735, 65119, 64201, 63984, 66756, 67185, 68057, 67535, 65916, 65353, 65722, 67912, 68077, 68171, 66867, 66187, 64691, // 2024-07
180
+ 65178, 61507, 60688, 58306, 54344, 56047, 55130, 61909, 60721, 60872, 58836, 59394, 60536, 58841, 57644, 58927, 59415, 58793, 59412, 59123, 61133, 60394, 64107, 64023, 64498, 62983, 59548, 59101, 59340, 59143, 58960, // 2024-08
181
+ 57349, 59159, 57638, 58033, 56113, 53839, 54066, 54859, 57136, 57664, 57409, 58125, 60537, 60007, 59130, 58227, 60238, 61306, 62995, 63154, 63368, 63572, 63336, 64343, 63060, 65058, 65768, 65771, 65618, 63260, // 2024-09
182
+ 60858, 60686, 60765, 62053, 62030, 62790, 62417, 62126, 60615, 60221, 62463, 63203, 62759, 66090, 66934, 67663, 67338, 68391, 68358, 69012, 67440, 67426, 66647, 68131, 66262, 67023, 68000, 69863, 72679, 72450, 70367, // 2024-10
183
+ 69484, 69242, 68743, 67779, 69538, 75696, 75972, 76507, 76799, 80409, 88860, 88049, 90369, 87204, 91139, 90568, 89733, 90588, 92254, 94160, 98546, 98938, 97707, 97980, 93343, 91876, 95989, 95738, 97399, 96464, // 2024-11
184
+ 97470, 95726, 96011, 98920, 96950, 99953, 99978, 100799, 97390, 96836, 101310, 100091, 101290, 101367, 104551, 105856, 106116, 100470, 97697, 97617, 97093, 95149, 94801, 98638, 99235, 95628, 94220, 95124, 93458, 92523, 93390, // 2024-12
185
+ 94456, 96851, 98121, 98230, 98415, 102180, 96975, 95039, 92346, 94759, 94607, 94354, 94376, 96569, 100172, 99978, 104297, 104398, 100955, 102589, 105988, 103791, 104185, 104716, 104866, 102833, 101907, 101138, 103919, 104987, 102263, // 2025-01
186
+ 100610, 97425, 101582, 97848, 96559, 96541, 96384, 96603, 96310, 97375, 95806, 97756, 96535, 97414, 97583, 96182, 95791, 95444, 96648, 98392, 96049, 96606, 96092, 91870, 88770, 83988, 84625, 84244, // 2025-02
187
+ 85844, 94328, 86316, 87324, 90619, 90222, 86554, 86171, 80648, 79006, 82747, 83630, 81110, 84106, 84353, 82492, 84014, 82681, 86774, 84125, 84069, 83821, 85761, 87323, 87423, 86837, 87211, 84309, 82497, 82216, 82461, // 2025-03
188
+ 85227, 82556, 82932, 83774, 83302, 77949, 79422, 76351, 82721, 79559, 83360, 85269, 83569, 84589, 83671, 84152, 84941, 84435, 85125, 85102, 87361, 93496, 93696, 93850, 94771, 94695, 93813, 95066, 94103, 94237, // 2025-04
189
+ 96412, 96842, 95944, 94378, 94854, 96678, 97137, 103070, 102941, 104583, 103976, 102920, 104146, 103573, 103716, 103566, 103190, 106016, 105616, 106893, 109705, 111477, 107292, 107917, 108915, 109372, 109066, 107895, 105733, 103981, 104709, // 2025-05
190
+ 105750, 105900, 105484, 104783, 101669, 104421, 105685, 105728, 110198, 110074, 108646, 105999, 106025, 105467, 105513, 107244, 104628, 104813, 104710, 103275, 101548, 100899, 105497, 105964, 107305, 107013, 107091, 107358, 108361, 107153, // 2025-06
191
+ 105566, 108927, 109632, 108081, 108245, 109180, 108249, 108957, 111417, 115874, 117619, 117382, 118900, 119871, 117711, 118730, 119502, 117959, 117909, 117306, 117432, 120068, 118651, 118377, 117520, 117983, 119438, 118023, 117871, 117733, 115848, // 2025-07
192
+ 113282, 112546, 114264, 115200, 114111, 115035, 117445, 116741, 116512, 119189, 118742, 120154, 123443, 118510, 117311, 117462, 117602, 116313, 112886, 114277, 112347, 116801, 115280, 113516, 110090, 111895, 111252, 112481, 108473, 108746, 108316, // 2025-08
193
+ 108992, 111141, 111781, 110901, 110711, 110244, 111308, 112112, 111486, 113931, 115446, 116149, 115966, 115396, 115421, 116799, 116576, 117014, 115612, 115753, 115321, 112728, 112052, 113325, 109166, 109669, 109645, 112170, 114341, 113972, // 2025-09
194
+ 118393, 120559, 122348, 122380, 123524, 124824, 121588, 123390, 121603, 113755, 110940, 115153, 115332, 113327, 110863, 108135, 106626, 107144, 108707, 110640, 108700, 107668, 110061, 111025, 111615, 114557, 114088, 112981, 110206, 108020, 109554, // 2025-10
195
+ 110005, 110389, 106496, 101350, 103916, 101242, 103428, 102335, 104686, 106082, 102974, 101682, 100035, 94503, 95541, 94182, 91911, 92836, 91320, 86911, 84948, 84775, 86872, 88378, 87435, 90449, 91336, 90990, 90842, 90608, // 2025-11
196
+ 86505, 91528, 93609, 92206, 89280, 89170, 90068, 90673, 92809, 92103, 92624, 90332, 90245, 88096, 86353, 87752, 86060, 85434, 88168, 88292, 88533, 88474, 87366, 87626, 87231, 87335, 87695, 87748, 87134, 88428, 87517, // 2025-12
197
+ 88684, 89940, 90598, 91360, 93949, 93574, 91209, 91097, 90540, 90406, 90717, 91141, 95304, 97044, 95546, 95489, 95107, 94261, 92526, 88237, 89599, 89396, 89440, 89185, 86446, 88337, 89260, 89178, 84520, 84017, 78702, // 2026-01
198
+ 76911, 78717, 75685, 73095, 63495, 70648, 69166, 70523, 70244, 68689, 66990, 66222, 68861, 69798, 68630, 68747, 67471, 66381, 66932, 67977, 68021, 67550, 64690, 64124, 68039, 67519, 65864, 66966, // 2026-02
199
+ 65734, 68923, 68431, 72667, 70987, 68227, 67316, 66203, 68529, 69892, 70299, 70538, 70930, 71136, 72712, 74724, 74086, 71254, 69949, 70510, 69707, 68018, 70726, 70610, 71281, 68723, 66214, 66351, 65967, 66651, 68215, // 2026-03
200
+ 68117, 66908, 66892, 67295, 68922, 68743, 72057, 71085, 71789, 72945, 73119, 70685, 74633, 74174, 74762, 75096, 77114, 75792, 73905, 75814, 76107, 78318, 78234, 77444, 77625, 78539, 77256, 76296, 75795, 76300, // 2026-04
201
+ 78133, 78713, 78649, 79826, 80937, 81365, 79989, 80180, 80664, 82257, 81715, 80526, 79292, 81176, 79063, 78164, 77498, 76976, 76807, 77408, 77595, 75568, 76620, 76951, 77245, 75816, 74269, 73501, 73323, 73731, 73600, // 2026-05
202
+ 71329, 66541, 64233, 63639, 60908, 60772, 63158, 63110, 61640, 61435, 63475, 63476, 64424, 65615, 66223, 65638, 64401, 62835, 63307, 64221, 63365, 63924, 62593, 60908, 59863, 59962, 59956, 59517, 60175, 58525, // 2026-06
203
+ 60066, 61438, 62565, 63090, 63620, 64063, 63458, 62214, 63189, 64119, 63893, 63752, 62127, 64934, 64765, 63780, 63916, 64778, 64653, 65182, 66403, 65983, 65094, 64100, 64331, 65329, 63718, 63728, 63905, 64828, 62875, // 2026-07
204
+ 62752, 63446, 63460, 64161, 64610, 64212, 64869, 64907, 64865, 63908, 63547, 63359, 63395, 62925, 63034, 62818, 64434, 64696, 69268, 73071, 78359, 77020, 77593, 78894, 78601, 78955, 80297, 77717, 78244, 77689, 78534, // 2026-08
205
+ 77408, 77189, 81243, 79682, 79817, 80320, 79073, 78446, 78207, 76676, 77177, 77252, 76759, 78279, 75650, 76082, 76387, 80944, 81262, 81195, 86505, 86205, 84434, 84387, 84062, 84386, 84392, 83517, 83675, 83580, // 2026-09
206
+ 84777, 84518, 84756, 86521, 85835, // 2026-10
207
+ ];
208
+ // The two halvings in these ten years: the days the reward for mining a block was cut in half
209
+ const HALVINGS = [
210
+ { date: Date.UTC(2020, 4, 11), block: "630,000", reward: "12.5 to 6.25" },
211
+ { date: Date.UTC(2024, 3, 20), block: "840,000", reward: "6.25 to 3.125" },
212
+ ];
213
+ const NOTE = "Source: Coin Metrics Community Data, Bitcoin reference rate in US dollars at each day's close (00:00 UTC), retrieved 6 October 2026. Halvings at blocks 630,000 (11 May 2020) and 840,000 (20 April 2024, UTC).";
214
+
215
+ // What the chart is drawn from, all worked out from the data
216
+ const LAST = CLOSE.length - 1; // the latest day
217
+ const P0 = CLOSE[0]; // the price the ladder starts from
218
+ const DAY = 864e5;
219
+ const dateOf = (i) => new Date(FIRST + i * DAY);
220
+ const dayOf = (time) => Math.round((time - FIRST) / DAY);
221
+ const rung = (price) => Math.log2(price / P0); // doublings of the first price: the cross scale
222
+ const HIGH = []; // the record close up to each day
223
+ CLOSE.forEach((p, i) => HIGH.push(Math.max(p, i ? HIGH[i - 1] : p)));
224
+ const PRICE_LINE = CLOSE.map((p, i) => [i, rung(p)]);
225
+ const HIGH_LINE = HIGH.map((p, i) => [i, rung(p)]);
226
+ // The falls: the record line out to the last day, then back along the price; the shape between them is every fall
227
+ const FALLS = [...HIGH_LINE, ...PRICE_LINE.toReversed()];
228
+ // The crashes: from each record to the lowest close before the next one, where the price lost more than half
229
+ const CRASHES = [];
230
+ let peak = 0;
231
+ let low = 0;
232
+ const crash = () => CLOSE[low] < CLOSE[peak] / 2 && CRASHES.push({ peak, low, depth: 1 - CLOSE[low] / CLOSE[peak] });
233
+ CLOSE.forEach((p, i) => {
234
+ if (p > CLOSE[peak]) {
235
+ crash();
236
+ peak = low = i;
237
+ } else if (p < CLOSE[low]) low = i;
238
+ });
239
+ crash();
240
+ const X = [0, LAST];
241
+ const Y = [-0.3, Math.ceil(rung(HIGH[LAST]))]; // from just under the first price to the doubling above the record
242
+ const RUNGS = Array.from({ length: Y[1] + 1 }, (_, k) => k);
243
+ const YEARS = []; // the first of January of each year, as days
244
+ for (let y = dateOf(0).getUTCFullYear() + 1; y <= dateOf(LAST).getUTCFullYear(); y++) YEARS.push(dayOf(Date.UTC(y, 0, 1)));
245
+ const HALVING_DAYS = HALVINGS.map((h) => ({ ...h, at: dayOf(h.date) }));
246
+ const MONTH_ENDS = []; // the last day of each month shown: what the arrow keys step through
247
+ CLOSE.forEach((_, i) => (i === LAST || dateOf(i + 1).getUTCDate() === 1) && MONTH_ENDS.push(i));
248
+ // On a phone a column per month is under 3px wide: the arrow keys step through the ends of quarters there
249
+ const QUARTER_ENDS = MONTH_ENDS.filter((i) => i === LAST || dateOf(i).getUTCMonth() % 3 === 2);
250
+
251
+ // Words and numbers
252
+ const dollars = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", maximumFractionDigits: 0 });
253
+ const percent = new Intl.NumberFormat("en-US", { style: "percent", maximumFractionDigits: 0 });
254
+ const day = new Intl.DateTimeFormat("en-US", { month: "short", day: "numeric", year: "numeric", timeZone: "UTC" });
255
+ const month = new Intl.DateTimeFormat("en-US", { month: "short", year: "numeric", timeZone: "UTC" });
256
+ const fullMonth = new Intl.DateTimeFormat("en-US", { month: "long", year: "numeric", timeZone: "UTC" });
257
+ const short = (v) => (v >= 1e4 ? `$${Math.round(v / 1e3)}K` : v >= 1e3 ? `$${(v / 1e3).toFixed(1)}K` : `$${Math.round(v)}`);
258
+ const times = (v) => (v < 10 ? v.toFixed(1) : Math.round(v).toString());
259
+ const minus = (v) => `−${percent.format(v)}`;
260
+ const COUNT = ["no", "once", "twice", "three times", "four times", "five times", "six times"];
261
+ const fold = Math.round(CLOSE[LAST] / P0);
262
+ const yearsShown = Math.round(LAST / 365.25);
263
+ const TEN = ["zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve"][yearsShown] ?? yearsShown;
264
+ // What a day reads: its date, its price, its multiple of the first price and how far it is below the record so far
265
+ const say = (i) => {
266
+ const below = 1 - CLOSE[i] / HIGH[i];
267
+ return `${day.format(dateOf(i))}: ${dollars.format(CLOSE[i])}, ${times(CLOSE[i] / P0)} times the price of ${month.format(dateOf(0))}, `
268
+ + (below === 0 ? "a record close" : `${percent.format(below)} below its record`);
269
+ };
270
+
271
+ // The chart's font and colors, read from the page's tokens
272
+ const THEME = {
273
+ font: "var(--text)", ink: "var(--ink)", muted: "var(--soft)", grid: "var(--rule)", surface: "var(--paper)",
274
+ series: ["var(--accent)", "var(--bite)", "var(--quiet)", "var(--coin)"],
275
+ };
276
+
277
+ // Text only a screen reader reads, in the slat types that need it
278
+ const SAY = `.say { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }`;
279
+
280
+ // Slat types
281
+ // The years under the plot: a short mark and the year at each first of January (every other year on a phone)
282
+ const Year = slat({
283
+ room: { after: 28 },
284
+ css: `
285
+ .mark { top: 100%; height: 6px; translate: -50% 0; --rhp-tick-width: 1px; background: var(--rhp-muted); }
286
+ .year { top: calc(100% + 10px); bottom: auto; translate: -50% 0; --rhp-label-gap: 0px; font-size: 12px; color: var(--rhp-muted); }
287
+ @container (max-width: 520px) { .odd .year { display: none; } }
288
+ `,
289
+ }, (t) => html`
290
+ <div class=${() => (dateOf(t.at).getUTCFullYear() % 2 ? "odd" : "")}>
291
+ <${Tick} at=${() => t.at} class="mark" />
292
+ <${Label} at=${() => t.at} class="year">${() => dateOf(t.at).getUTCFullYear()}<//>
293
+ </div>`);
294
+
295
+ // Where a value of the cross scale sits, from the plot's top (0) to its bottom (1): for marks the slat's CSS places
296
+ const fromTop = (v) => (Y[1] - v) / (Y[1] - Y[0]);
297
+ // A vertical mark from the value hi down to lo, as the style of a Tick that the slat's CSS sets from --top and --length
298
+ const span = (hi, lo) => `--top: ${fromTop(hi)}; --length: ${(hi - lo) / (Y[1] - Y[0])}`;
299
+
300
+ // A rung of the ladder: a hairline at each doubling of the first price (a Bar from the first day to the last, set at
301
+ // its height by the slat's CSS), with its multiple and its price at the end
302
+ const Rung = slat({
303
+ room: { before: 14, end: 54 },
304
+ css: `
305
+ .rung { top: calc(var(--y) * 100%); translate: 0 -50%; --rhp-radius: 0px; }
306
+ .first .rung { background: repeating-linear-gradient(to right, var(--rhp-muted) 0 2px, transparent 2px 6px); }
307
+ .times { --rhp-label-gap: 8px; display: grid; line-height: 1.05; }
308
+ .times b { font: italic 700 15px/1.05 Ubuntu, "Trebuchet MS", sans-serif; }
309
+ .times small { font-size: 11px; color: var(--rhp-muted); }
310
+ `,
311
+ }, (r) => html`
312
+ <div class=${() => (r.k === 0 ? "first" : "")}>
313
+ <${Bar} from=${X[0]} to=${X[1]} thick="1px" color="grid" class="rung" style=${() => `--y: ${fromTop(r.k)}`} />
314
+ <${Label} at=${X[1]} cross=${() => r.k} class="times"><b>×${() => 2 ** r.k}</b><small>${() => short(P0 * 2 ** r.k)}</small><//>
315
+ </div>`);
316
+
317
+ // Every fall below the record, as one shape between the record line and the price
318
+ const Falls = slat({
319
+ room: {},
320
+ css: `
321
+ .falls { stroke: none; }
322
+ .falls .rhp-under { fill: var(--rhp-series-2); }
323
+ `,
324
+ }, (d) => html`<div><${Line} points=${() => d.points} fill=${true} base=${Y[0]} class="falls" /></div>`);
325
+
326
+ // A halving: a coin split in two near the foot, and a dotted lead up to the price that day
327
+ const Halving = slat({
328
+ room: {},
329
+ css: `
330
+ .lead { top: calc(var(--top) * 100%); height: calc(var(--length) * 100%); translate: -50% 0; --rhp-tick-width: 2px;
331
+ background: radial-gradient(circle, var(--rhp-series-3) 1px, transparent 1.4px) 50% 0 / 2px 6px repeat-y; }
332
+ .coin { position: absolute; translate: -50% -50%; display: block; width: 22px; height: 22px; }
333
+ .coin path { fill: var(--rhp-series-4); stroke: var(--rhp-ink); stroke-width: 1.5px; }
334
+ .tag { position: absolute; left: 16px; translate: 0 -50%; display: grid; font-size: 12px; line-height: 1.25; white-space: nowrap; }
335
+ .tag b { font-weight: 600; }
336
+ .tag small { font-size: 11px; color: var(--rhp-muted); }
337
+ @container (max-width: 520px) {
338
+ .tag small { display: none; }
339
+ /* the word goes left of its coin, clear of the rung labels at the right edge */
340
+ .tag { left: auto; right: 16px; }
341
+ }
342
+ ${SAY}
343
+ `,
344
+ }, (h) => html`
345
+ <div>
346
+ <${Tick} at=${() => h.at} class="lead" style=${() => span(rung(CLOSE[h.at]) - 0.2, 1.05)} />
347
+ <${Place} at=${() => h.at} cross=${0.62} aria-hidden="true">
348
+ <svg class="coin" viewBox="0 0 22 22"><path d="M10 2.5 A8.5 8.5 0 0 0 10 19.5 Z" /><path d="M12 2.5 A8.5 8.5 0 0 1 12 19.5 Z" /></svg>
349
+ <span class="tag"><b>Halving</b><small>${() => month.format(dateOf(h.at))}</small></span>
350
+ <//>
351
+ <span class="say">${() => `Halving on ${day.format(dateOf(h.at))}, at block ${h.block}: the reward for a block was cut from ${h.reward} bitcoins.`}</span>
352
+ </div>`);
353
+
354
+ // The record line and the price line
355
+ const Trace = slat({
356
+ room: {},
357
+ css: `
358
+ .high { stroke-width: 1.5px; }
359
+ .price { stroke-width: 1.75px; }
360
+ `,
361
+ }, (d) => html`<div><${Line} points=${() => d.points} color=${() => d.color} class=${() => d.kind} /></div>`);
362
+
363
+ // A crash of more than half: a dashed plumb line from the record down to the low, and the fall written under the low
364
+ const Crash = slat({
365
+ room: {},
366
+ css: `
367
+ .plumb { top: calc(var(--top) * 100%); height: calc(var(--length) * 100%); translate: -50% 0; --rhp-tick-width: 1.5px;
368
+ background: repeating-linear-gradient(var(--rhp-series-1) 0 4px, transparent 4px 7px); }
369
+ .tag { position: absolute; top: 8px; left: 0; translate: -35% 0; display: grid; justify-items: center; white-space: nowrap; } /* a little right of the low, clear of the climb before it */
370
+ .back .tag { left: auto; right: 2px; translate: none; justify-items: end; }
371
+ .tag b { font: italic 700 18px/1 Ubuntu, "Trebuchet MS", sans-serif; color: var(--rhp-series-1); }
372
+ .tag small { margin-top: 2px; font-size: 11px; line-height: 1.2; color: var(--rhp-muted); }
373
+ @container (max-width: 520px) {
374
+ .tag { top: 9px; }
375
+ .tag b { font-size: 12.5px; }
376
+ .tag small { display: none; }
377
+ }
378
+ ${SAY}
379
+ `,
380
+ }, (c) => html`
381
+ <div class=${() => (c.low > LAST * 0.85 ? "back" : "")}>
382
+ <${Tick} at=${() => c.low} class="plumb" style=${() => span(rung(CLOSE[c.peak]), rung(CLOSE[c.low]))} />
383
+ <${Dot} at=${() => c.low} cross=${() => rung(CLOSE[c.low])} size="6px" color="series-1" />
384
+ <${Place} at=${() => c.low} cross=${() => rung(CLOSE[c.low])} aria-hidden="true">
385
+ <span class="tag"><b>${() => minus(c.depth)}</b><small>from ${() => month.format(dateOf(c.peak))}</small></span>
386
+ <//>
387
+ <span class="say">${() => `A fall of ${percent.format(c.depth)}: from a record close of ${dollars.format(CLOSE[c.peak])} on ${day.format(dateOf(c.peak))} to ${dollars.format(CLOSE[c.low])} on ${day.format(dateOf(c.low))}.`}</span>
388
+ </div>`);
389
+
390
+ // The day picked: a hairline and a dot on the price
391
+ const Reading = slat({
392
+ room: {},
393
+ css: `
394
+ .hair { --rhp-tick-width: 1px; background: var(--rhp-muted); }
395
+ .rest .hair { opacity: 0; }
396
+ .dot { box-shadow: 0 0 0 2.5px var(--rhp-surface); }
397
+ `,
398
+ }, (d) => html`
399
+ <div class=${() => (d.rest ? "rest" : "")}>
400
+ <${Tick} at=${() => d.at} thick=${1} class="hair" />
401
+ <${Dot} at=${() => d.at} cross=${() => d.lift} size="10px" color="ink" class="dot" />
402
+ </div>`);
403
+
404
+ // One invisible column per month: what the arrow keys step through, and what a screen reader reads
405
+ const Month = slat({
406
+ room: {},
407
+ css: `
408
+ .month { pointer-events: none; outline: none; }
409
+ ${SAY}
410
+ `,
411
+ }, (m) => html`<div class="month" data-m=${() => m.index}><span class="say">${() => `${fullMonth.format(dateOf(m.at))}. ${say(m.at)}`}</span></div>`);
412
+
413
+ // Chart component: the readout and the hairline follow the day under the pointer, a tap, a drag or the arrow keys,
414
+ // and show the latest close otherwise
415
+ function BitcoinDecade() {
416
+ const wide = matchMedia("(min-width: 640px)");
417
+ const [desktop, setDesktop] = createSignal(wide.matches);
418
+ wide.addEventListener("change", (e) => setDesktop(e.matches));
419
+ const [picked, setPicked] = createSignal(null);
420
+ const [pinned, setPinned] = createSignal(null); // the day a click or a tap picked: the chart comes back to it
421
+ const at = () => picked() ?? LAST;
422
+ const shown = createMemo(() => {
423
+ const i = at();
424
+ return { date: day.format(dateOf(i)), times: times(CLOSE[i] / P0), price: dollars.format(CLOSE[i]), below: 1 - CLOSE[i] / HIGH[i] };
425
+ });
426
+ let plot; // the price Plot's element: its box is the plot area, day 0 at its left and the last day at its right
427
+ // A pointer, a tap or a drag picks the day under it; outside the plot the pick stays
428
+ const point = (e) => {
429
+ const r = plot.getBoundingClientRect();
430
+ if (e.clientX < r.left - 8 || e.clientX > r.right + 8) return;
431
+ setPicked(Math.max(0, Math.min(LAST, Math.round(((e.clientX - r.left) / r.width) * LAST))));
432
+ };
433
+ // The keyboard picks the last day of the month (or quarter, on a phone) it focuses
434
+ const steps = () => (desktop() ? MONTH_ENDS : QUARTER_ENDS);
435
+ const monthOf = (el) => {
436
+ const root = el?.closest?.("[data-m]");
437
+ return root ? steps()[+root.dataset.m] : null;
438
+ };
439
+ const focus = (e) => {
440
+ const i = monthOf(e.target);
441
+ if (i != null) setPicked(i);
442
+ };
443
+ let start = [0, 0]; // where the last finger went down
444
+ // A finger that moves more sideways than up or down scrubs; one that moves up or down scrolls the page
445
+ const sideways = (e) => Math.abs(e.clientX - start[0]) > Math.abs(e.clientY - start[1]);
446
+ // A click or a tap pins the day under it
447
+ const pin = (e) => {
448
+ point(e);
449
+ setPinned(picked());
450
+ };
451
+ // Leaving the chart: back to the month that has focus (the keyboard's pick), else the pinned day, else the latest close
452
+ const leave = (e) => {
453
+ if (e.pointerType === "touch") return; // a finger sends pointerleave as it lifts after a tap
454
+ setPicked(e.currentTarget.contains(document.activeElement) ? monthOf(document.activeElement) : pinned());
455
+ };
456
+ return html`
457
+ <${Poster}
458
+ kicker=${`Bitcoin, ${month.format(dateOf(0))} to ${month.format(dateOf(LAST))}`}
459
+ title=${html`<span>Bitcoin rose <span class="nobreak">${fold}-fold</span> in ${TEN} years, losing over half its value ${COUNT[CRASHES.length] ?? `${CRASHES.length} times`}</span>`}
460
+ dek=${html`<span>Its price at the close of every day, in US dollars, on a ladder where each rung up is a doubling of the ${month.format(dateOf(0))} price and each rung down a halving. <span class="drop">Shaded</span> is how far it stood below its last record.</span>`}
461
+ note=${NOTE}>
462
+ <div class="stage">
463
+ <p class="plate" aria-hidden="true">
464
+ <span class="when">${() => shown().date}</span>
465
+ <span class="times"><small>×</small>${() => shown().times}</span>
466
+ <span class="what">${() => (shown().below === 0 ? html`<span>${shown().price}, <b>a record close</b></span>` : html`<span>${shown().price}, <b>${percent.format(shown().below)} below its record</b></span>`)}</span>
467
+ </p>
468
+ <div class="plot" onPointerDown=${(e) => (start = [e.clientX, e.clientY])}
469
+ onPointerMove=${(e) => (e.pointerType !== "touch" || sideways(e)) && point(e)} onClick=${pin} onFocusIn=${focus}
470
+ onPointerLeave=${leave} onFocusOut=${(e) => !e.currentTarget.contains(e.relatedTarget) && setPicked(pinned())}>
471
+ <${Chart} scale=${X} cross=${Y} crossTicks=${false} height=${() => (desktop() ? 440 : 340)} theme=${THEME}
472
+ label=${`Bitcoin's daily closing price in US dollars, ${day.format(dateOf(0))} to ${day.format(dateOf(LAST))}, on a scale of doublings`}>
473
+ <${Scale} ticks=${YEARS}>${Year}<//>
474
+ <${Plot} overlap=${true} static=${true} slats=${1} points=${[FALLS]}>${Falls}<//>
475
+ <${Plot} overlap=${true} static=${true} k=${RUNGS}>${Rung}<//>
476
+ <${Plot} overlap=${true} static=${true} rows=${HALVING_DAYS}>${Halving}<//>
477
+ <${Plot} overlap=${true} static=${true} points=${[HIGH_LINE, PRICE_LINE]} color=${["series-1", "ink"]} kind=${["high", "price"]}
478
+ ref=${(el) => (plot = el)}>${Trace}<//>
479
+ <${Plot} overlap=${true} static=${true} rows=${CRASHES}>${Crash}<//>
480
+ <${Plot} overlap=${true} slats=${1} at=${at} lift=${() => rung(CLOSE[at()])} rest=${() => picked() == null} animate=${["at", "lift"]}
481
+ style=${{ "pointer-events": "none" }}>${Reading}<//>
482
+ <${Plot} orientation="vertical" keyboard=${true} key="at" at=${steps}>${Month}<//>
483
+ <//>
484
+ </div>
485
+ <p class="hint">Point at the chart, tap it or drag along it; on a keyboard, Tab into it and use the arrow keys.</p>
486
+ </div>
487
+ <//>`;
488
+ }
489
+
490
+ render(() => html`<${BitcoinDecade} />`, document.getElementById("chart"));
491
+ </script>
492
+ </body>
493
+ </html>