@bezda/rhp-mcp 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -5
- package/bin/rhp-mcp.js +5 -1
- package/package.json +3 -3
- package/skill/SKILL.md +116 -26
- package/skill/recipes/area.html +6 -0
- package/skill/recipes/bar.html +9 -1
- package/skill/recipes/box-plot.html +9 -1
- package/skill/recipes/bubble.html +6 -0
- package/skill/recipes/bullet.html +6 -0
- package/skill/recipes/candlestick.html +9 -1
- package/skill/recipes/column.html +18 -5
- package/skill/recipes/dial.html +349 -0
- package/skill/recipes/diverging-bars.html +6 -0
- package/skill/recipes/donut.html +9 -1
- package/skill/recipes/dumbbell.html +6 -0
- package/skill/recipes/gantt.html +17 -5
- package/skill/recipes/grouped-bars.html +6 -0
- package/skill/recipes/heatmap.html +9 -1
- package/skill/recipes/histogram.html +9 -1
- package/skill/recipes/line.html +7 -1
- package/skill/recipes/live.html +6 -0
- package/skill/recipes/log-line.html +493 -0
- package/skill/recipes/lollipop.html +6 -0
- package/skill/recipes/multi-line.html +10 -5
- package/skill/recipes/pyramid.html +9 -1
- package/skill/recipes/race.html +6 -0
- package/skill/recipes/radial-bars.html +6 -0
- package/skill/recipes/scatter.html +23 -4
- package/skill/recipes/silhouettes.html +354 -0
- package/skill/recipes/slope.html +23 -4
- package/skill/recipes/sparklines.html +7 -1
- package/skill/recipes/stacked-100.html +6 -0
- package/skill/recipes/stacked-bars.html +6 -0
- package/skill/recipes/strip.html +9 -1
- package/skill/recipes/unit-stack.html +276 -0
- package/skill/recipes/violin.html +6 -0
- package/skill/recipes/waffle.html +6 -0
- package/skill/recipes/waterfall.html +6 -0
- package/skill/references/api.md +150 -22
- package/skill/references/design.md +303 -326
- package/skill/references/environments.md +11 -4
- package/skill/references/forms.md +41 -4
- package/skill/references/interaction.md +7 -2
- package/skill/references/looks.md +267 -0
- package/skill/references/performance.md +143 -0
- package/skill/references/pitfalls.md +52 -7
- package/src/check/README.md +33 -3
- package/src/check/browser.js +11 -5
- package/src/check/findings.js +15 -1
- package/src/check/index.js +182 -31
- package/src/check/inpage.js +82 -18
- package/src/check/interact.js +1 -19
- package/src/check/network.js +168 -0
- package/src/check/png.js +18 -0
- package/src/check/report.js +4 -1
- package/src/check/selection.js +123 -0
- package/src/guard/rhp.js +4 -0
- package/src/guard/rules.js +1 -1
- package/src/server.js +9 -8
package/skill/recipes/line.html
CHANGED
|
@@ -5,6 +5,12 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
6
|
<title>Carbon dioxide is up a third</title>
|
|
7
7
|
<meta name="description" content="Line chart of one series over time on a second axis (years along the scale, values across it), with a callout that follows the pointer, a tap or the arrow keys. Use it for one measure over time.">
|
|
8
|
+
<!--
|
|
9
|
+
A recipe gives technique, not a design.
|
|
10
|
+
Take: a Line on a second axis, a Scale of decades, and a callout that follows the pointer, a tap and the arrow keys.
|
|
11
|
+
Leave: the design. This poster was made for its own subject (carbon dioxide measured on Mauna Loa, on basalt black with the volcano's contour lines in a corner).
|
|
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
|
+
-->
|
|
8
14
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
9
15
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
10
16
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Archivo:wdth,wght@62..125,400..800&family=JetBrains+Mono:wght@400;600&display=swap">
|
|
@@ -60,7 +66,7 @@ const theme = { font: '"JetBrains Mono", ui-monospace, Menlo, monospace', ink: "
|
|
|
60
66
|
// Slat types
|
|
61
67
|
// The curve: one Line through every [year, ppm] point, on the Chart's second axis (cross)
|
|
62
68
|
const Curve = slat({ css: `.curve { stroke-width: 2.5px; }` }, (d) => html`
|
|
63
|
-
|
|
69
|
+
<${Line} points=${() => d.points} class="curve" />`);
|
|
64
70
|
|
|
65
71
|
// The scale under the plot: a short mark and the year at each decade (every other decade on a phone), the first and
|
|
66
72
|
// the last kept inside the plot
|
package/skill/recipes/live.html
CHANGED
|
@@ -5,6 +5,12 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
6
|
<title>Northgate carries a third</title>
|
|
7
7
|
<meta name="description" content="Live board: requests per second for six regions, each slat a line of the last minute that scrolls as readings arrive, the current value beside it, with pause and resume and a readout on hover, focus and tap. The lines move by rhp's JS version (animate) because the feed changes the data without pause. Use it for live or streaming data: metrics, sensors, prices, counts.">
|
|
8
|
+
<!--
|
|
9
|
+
A recipe gives technique, not a design.
|
|
10
|
+
Take: JS motion for a feed (animate), a Line in each slat scrolling from one animated now, pause and resume, and a readout.
|
|
11
|
+
Leave: the design. This poster was made for its own subject (API traffic on a control room's dot-matrix display, graphite glass with unlit dots and amber for the story).
|
|
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
|
+
-->
|
|
8
14
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
9
15
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
10
16
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Doto:wght@900&family=Martian+Mono:wght@400;600&family=Oxanium:wght@400..700&display=swap">
|
|
@@ -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>
|
|
@@ -5,6 +5,12 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
6
|
<title>The price of a Big Mac</title>
|
|
7
7
|
<meta name="description" content="Lollipop chart: ranked values as a thin stem and a dot, with a reference line and a switch between the values and their difference from the reference. Use it for a ranked list of prices, scores or rates, lighter than bars.">
|
|
8
|
+
<!--
|
|
9
|
+
A recipe gives technique, not a design.
|
|
10
|
+
Take: a thin Bar and a Dot per slat, a dashed reference line in an overlay Plot, and a switch between values and differences.
|
|
11
|
+
Leave: the design. This poster was made for its own subject (Big Mac prices as the till receipt a burger comes with: torn edges, dotted leaders, a barcode).
|
|
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
|
+
-->
|
|
8
14
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
9
15
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
10
16
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&display=swap">
|
|
@@ -5,6 +5,12 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
6
|
<title>Thirty more years</title>
|
|
7
7
|
<meta name="description" content="Multi-line chart of several series over time on a second axis, with labels at the line ends kept apart and a crosshair whose readout lists every series for the year under the pointer, a tap or the arrow keys. Use it to compare a few series over time.">
|
|
8
|
+
<!--
|
|
9
|
+
A recipe gives technique, not a design.
|
|
10
|
+
Take: several Lines on a second axis, end labels kept apart, and a crosshair whose readout lists every series.
|
|
11
|
+
Leave: the design. This poster was made for its own subject (life expectancy since 1960 as a report on laid paper, a serif headline and an hourglass by the kicker).
|
|
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
|
+
-->
|
|
8
14
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
9
15
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
10
16
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Newsreader:opsz,wght@6..72,400..700&family=Public+Sans:wght@400..700&display=swap">
|
|
@@ -140,14 +146,13 @@ const CountryCode = country(52, (d) => d.code);
|
|
|
140
146
|
|
|
141
147
|
// The year picked: a hairline across the plot, then a dot on each line
|
|
142
148
|
const Crosshair = slat({ css: `.hair { --rhp-tick-width: 1px; background: #1b1d22; opacity: .45; }` }, (d) => html`
|
|
143
|
-
|
|
149
|
+
<${Tick} at=${() => d.year} thick=${1} class="hair" />`);
|
|
144
150
|
|
|
145
151
|
const Mark = slat({
|
|
146
|
-
css: `.dot { box-shadow: 0 0 0 2px #faf8f3; } .story
|
|
152
|
+
css: `.dot { box-shadow: 0 0 0 2px #faf8f3; } .dot.story { z-index: 1; }`,
|
|
147
153
|
}, (d) => html`
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
</div>`);
|
|
154
|
+
<${Dot} at=${() => d.year} cross=${() => d.values[d.i]} color=${() => d.color} size="9px"
|
|
155
|
+
class=${() => (d.name === STORY ? "dot story" : "dot")} />`);
|
|
151
156
|
|
|
152
157
|
// One invisible column per year: what the pointer, a tap and the keyboard pick, and what a screen reader reads
|
|
153
158
|
const Year = slat({
|