@liggi/figkit 0.0.0-stage → 0.1.0

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.
@@ -0,0 +1,14 @@
1
+ <!doctype html><html><head><meta charset="utf-8"><style>
2
+ body { margin: 0; font: 15px/1.4 system-ui, sans-serif; background: #f6f6f4; color: #222; }
3
+ .card { width: 560px; margin: 24px; background: #fff; border: 1px solid #ddd; border-radius: 10px; padding: 22px 26px; }
4
+ h2 { margin: 0 0 4px; font-size: 19px; } .sub { color: #777; margin: 0 0 18px; }
5
+ .r { display: flex; justify-content: space-between; align-items: center; padding: 11px 0; border-top: 1px solid #eee; }
6
+ .r span { color: #777; } .ok { color: #2f8f5b; font-weight: 600; } .warn { color: #c9414a; font-weight: 600; }
7
+ button { margin-top: 16px; background: #222; color: #fff; border: 0; border-radius: 6px; padding: 9px 16px; font-size: 14px; }
8
+ </style></head><body><div class="card">
9
+ <h2>Release check</h2><p class="sub">Version 4.2 against 4.1, last 24 hours</p>
10
+ <div class="r"><b>Checkout errors</b><span class="ok">0.2% &nbsp;pass</span></div>
11
+ <div class="r"><b>Page load time</b><span class="ok">1.4 s &nbsp;pass</span></div>
12
+ <div class="r"><b>Failed payments</b><span class="warn">3.1% &nbsp;fail</span></div>
13
+ <div class="r"><b>Search clicks</b><span class="ok">+2% &nbsp;pass</span></div>
14
+ <button>Roll back</button></div></body></html>
@@ -0,0 +1,25 @@
1
+ <main class="fig">
2
+ <header>
3
+ <h1>The nightly export can't email customers: <em>it never sees their address</em></h1>
4
+ <p class="read"><b class="t-second">Blue</b> knows the email address. <b class="t-accent">Orange</b> is where it is lost.</p>
5
+ </header>
6
+ <div style="display: grid; grid-template-columns: 1fr 1fr; column-gap: 190px; row-gap: 84px">
7
+ <div class="group" style="row-gap: 56px">
8
+ <span class="label">Shop front</span>
9
+ <div class="node second tint" id="pub"><h3>Email address</h3><p>Who the customer is</p></div>
10
+ <div class="node second tint" id="live"><h3>Order page</h3></div>
11
+ </div>
12
+ <div class="group" style="row-gap: 56px">
13
+ <span class="label">Warehouse systems</span>
14
+ <div class="node" id="state"><h3>Customer key</h3><p>No address inside</p></div>
15
+ <div class="node" id="batch"><h3>Nightly export</h3></div>
16
+ </div>
17
+ <div class="node second fill" id="seg" style="grid-column: 1 / -1"><h3>Delivery email</h3><p>Sent to the address</p></div>
18
+ </div>
19
+ <fig-arrow from="pub" to="state" accent weight="4" label="one-way hash"></fig-arrow>
20
+ <fig-arrow from="pub" to="live" second></fig-arrow>
21
+ <fig-arrow from="state" to="batch"></fig-arrow>
22
+ <fig-arrow from="live" to="seg" second label="can send"></fig-arrow>
23
+ <fig-arrow from="batch" to="seg" accent dashed blocked label="no address to send to"></fig-arrow>
24
+ <p class="takeaway">So delivery emails go from the order page.</p>
25
+ </main>
@@ -0,0 +1,8 @@
1
+ { "type": "bars", "title": "The new checkout halves the time to pay",
2
+ "note": "Median time from cart to payment",
3
+ "unit": " s",
4
+ "bars": [
5
+ { "label": "Current checkout", "value": 86 },
6
+ { "label": "New checkout", "value": 41, "tone": "good" }
7
+ ],
8
+ "source": "Invented example data" }
@@ -0,0 +1,16 @@
1
+ {
2
+ "type": "before-after",
3
+ "title": "Late deliveries fell by three quarters after the route change",
4
+ "note": "Share of orders delivered late",
5
+ "unit": "%",
6
+ "before": {
7
+ "label": "Before 3 March",
8
+ "value": 18
9
+ },
10
+ "after": {
11
+ "label": "From 3 March",
12
+ "value": 4.5,
13
+ "tone": "good"
14
+ },
15
+ "source": "Invented example data"
16
+ }
@@ -0,0 +1,9 @@
1
+ { "type": "dumbbell", "title": "Every spring release shipped later than planned",
2
+ "fromLabel": "Planned", "toLabel": "Shipped",
3
+ "rows": [
4
+ { "label": "Offline mode", "from": "2026-03-02", "to": "2026-03-09" },
5
+ { "label": "Shared folders", "from": "2026-03-16", "to": "2026-04-06" },
6
+ { "label": "New billing page", "from": "2026-04-01", "to": "2026-04-08" },
7
+ { "label": "Dark theme", "from": "2026-04-20", "to": "2026-04-23" }
8
+ ],
9
+ "source": "Invented example data" }
@@ -0,0 +1,46 @@
1
+ {"type": "series", "title": "Launch day brought twice the usual sign-ups", "note": "Trial sign-ups per day", "points": [
2
+ {"x": "2026-09-20", "y": 52},
3
+ {"x": "2026-09-21", "y": 81},
4
+ {"x": "2026-09-22", "y": 90},
5
+ {"x": "2026-09-23", "y": 79},
6
+ {"x": "2026-09-24", "y": 81},
7
+ {"x": "2026-09-25", "y": 83},
8
+ {"x": "2026-09-26", "y": 56},
9
+ {"x": "2026-09-27", "y": 47},
10
+ {"x": "2026-09-28", "y": 97},
11
+ {"x": "2026-09-29", "y": 88},
12
+ {"x": "2026-09-30", "y": 84},
13
+ {"x": "2026-10-01", "y": 85},
14
+ {"x": "2026-10-02", "y": 97},
15
+ {"x": "2026-10-03", "y": 62},
16
+ {"x": "2026-10-04", "y": 52},
17
+ {"x": "2026-10-05", "y": 93},
18
+ {"x": "2026-10-06", "y": 88},
19
+ {"x": "2026-10-07", "y": 100},
20
+ {"x": "2026-10-08", "y": 88},
21
+ {"x": "2026-10-09", "y": 91},
22
+ {"x": "2026-10-10", "y": 61},
23
+ {"x": "2026-10-11", "y": 55},
24
+ {"x": "2026-10-12", "y": 102},
25
+ {"x": "2026-10-13", "y": 91},
26
+ {"x": "2026-10-14", "y": 212},
27
+ {"x": "2026-10-15", "y": 131},
28
+ {"x": "2026-10-16", "y": 96},
29
+ {"x": "2026-10-17", "y": 67},
30
+ {"x": "2026-10-18", "y": 71},
31
+ {"x": "2026-10-19", "y": 98},
32
+ {"x": "2026-10-20", "y": 98},
33
+ {"x": "2026-10-21", "y": 104},
34
+ {"x": "2026-10-22", "y": 101},
35
+ {"x": "2026-10-23", "y": 99},
36
+ {"x": "2026-10-24", "y": 68},
37
+ {"x": "2026-10-25", "y": 74},
38
+ {"x": "2026-10-26", "y": 101},
39
+ {"x": "2026-10-27", "y": 101},
40
+ {"x": "2026-10-28", "y": 100},
41
+ {"x": "2026-10-29", "y": 106},
42
+ {"x": "2026-10-30", "y": 116},
43
+ {"x": "2026-10-31", "y": 79},
44
+ {"x": "2026-11-01", "y": 77},
45
+ {"x": "2026-11-02", "y": 116},
46
+ {"x": "2026-11-03", "y": 117}], "mark": {"x": "2026-10-14", "label": "212, launch day"}, "source": "Invented example data"}
@@ -0,0 +1,10 @@
1
+ { "type": "split", "title": "Search brings in most October trial sign-ups",
2
+ "note": "By where the visitor came from",
3
+ "unit": "%",
4
+ "parts": [
5
+ { "label": "Search", "value": 58, "tone": "accent" },
6
+ { "label": "Referral", "value": 27 },
7
+ { "label": "Ads", "value": 11 },
8
+ { "label": "Other", "value": 4 }
9
+ ],
10
+ "source": "Invented example data" }
@@ -0,0 +1,29 @@
1
+ <main class="fig">
2
+ <header>
3
+ <h1>Shop settings are scattered. <em>The goal is one file.</em></h1>
4
+ <p class="read">Same behaviour at first, then changed in one place.</p>
5
+ </header>
6
+ <div class="row wide">
7
+ <div class="col grow" id="today" style="gap: 10px">
8
+ <span class="lane-title">Today: five places</span>
9
+ <div class="node"><h3>Prices</h3><p>Spreadsheet</p></div>
10
+ <div class="node"><h3>Delivery zones</h3><p>Database table</p></div>
11
+ <div class="node"><h3>Opening hours</h3><p>Admin page</p></div>
12
+ <div class="node"><h3>Discount rules</h3><p>Code</p></div>
13
+ <div class="node"><h3>Email wording</h3><p>Templates folder</p></div>
14
+ </div>
15
+ <div class="col grow" style="gap: 10px">
16
+ <span class="lane-title t-accent">Proposed: one file per shop</span>
17
+ <div class="node accent grow" id="agent" style="gap: 14px; padding: 26px 28px">
18
+ <span class="num">shop.yaml</span>
19
+ <h3>What each item costs</h3>
20
+ <h3>Where it delivers</h3>
21
+ <h3>When it is open</h3>
22
+ <h3>Which discounts apply</h3>
23
+ <h3>What the emails say</h3>
24
+ <p style="margin-top: auto">Reviewed like code</p>
25
+ </div>
26
+ </div>
27
+ </div>
28
+ <fig-arrow from="today" to="agent" accent weight="3"></fig-arrow>
29
+ </main>
@@ -0,0 +1,25 @@
1
+ <style>
2
+ .bar { display: flex; gap: 3px; height: 96px; }
3
+ .seg { display: flex; flex-direction: column; justify-content: center; gap: 2px; padding: 0 16px; background: var(--soft); color: var(--on-fill); min-width: 0; }
4
+ .seg b { font-family: var(--serif); font-weight: 400; font-size: 34px; line-height: 1; }
5
+ .seg span { font-size: var(--small); font-weight: 600; white-space: nowrap; }
6
+ .seg:first-child { border-radius: 8px 0 0 8px; } .seg:last-child { border-radius: 0 8px 8px 0; }
7
+ </style>
8
+ <main class="fig">
9
+ <header>
10
+ <h1><em>A third to a half</em> of returns give a reason, not 90%</h1>
11
+ <p class="read">612 returned orders. Did the customer say why on the returns form?</p>
12
+ </header>
13
+ <div style="position: relative; padding-top: 62px">
14
+ <span style="position: absolute; left: 0; top: 0; font-weight: 600">47% if the vague ones count</span>
15
+ <div style="position: absolute; left: 0; top: 34px; width: 47%; height: 16px; border: 2px solid var(--ink); border-bottom: 0; border-radius: 6px 6px 0 0"></div>
16
+ <div style="position: absolute; left: 90%; top: 0; height: 170px; border-left: 2px dashed var(--ink)"></div>
17
+ <span style="position: absolute; right: calc(10% + 12px); top: 0; font-weight: 600">90% target</span>
18
+ <div class="bar">
19
+ <div class="seg" style="flex: 33; background: var(--accent)"><b>33%</b><span>give a reason</span></div>
20
+ <div class="seg" style="flex: 14; background: color-mix(in srgb, var(--accent) 45%, var(--soft))"><b>14%</b><span>vague</span></div>
21
+ <div class="seg" style="flex: 53"><b>53%</b><span>leave it blank</span></div>
22
+ </div>
23
+ </div>
24
+ <footer><p>Invented data, for illustration.</p></footer>
25
+ </main>
@@ -0,0 +1,50 @@
1
+ <main class="fig detail" style="--w: 1560px">
2
+ <header>
3
+ <h1>Checkout prices the order before the checks finish, and <em>a check can restart it</em></h1>
4
+ <p class="read">The pricing and the checks start together. Nothing is charged until the checks answer.</p>
5
+ </header>
6
+
7
+ <div style="display: grid; grid-template-columns: 150px repeat(4, 1fr) 310px; column-gap: 44px; row-gap: 70px; margin-top: 40px">
8
+ <div class="node pill fill" id="msg" style="grid-column: 1; grid-row: 1 / span 2; align-self: center"><h3>Customer pays</h3></div>
9
+
10
+ <div class="node" id="choose" style="grid-column: 2; grid-row: 1"><h3>Pick the price list</h3><p>Fixed order, first match</p></div>
11
+ <div class="node" id="load" style="grid-column: 3; grid-row: 1"><h3>Load the prices</h3><p>One of 120 price lists</p></div>
12
+ <div class="node" id="build" style="grid-column: 4; grid-row: 1"><h3>Build the basket</h3><p>Items plus delivery</p></div>
13
+ <div class="node" id="call" style="grid-column: 5; grid-row: 1"><h3>Ask the bank</h3><p>Named by the price list</p></div>
14
+
15
+ <div class="group flush" id="checks" style="grid-column: 2 / span 3; grid-row: 2">
16
+ <span class="label">Checks, run alongside the pricing</span>
17
+ <div class="grid" style="--cols: 3; column-gap: 44px">
18
+ <div class="node"><h3>Discount rules</h3><p>Can change the total</p></div>
19
+ <div class="node"><h3>Fraud checks</h3><p>Can hold the order</p></div>
20
+ <div class="node"><h3>Stock checks</h3><p>Warehouse, suppliers</p></div>
21
+ </div>
22
+ </div>
23
+
24
+ <div class="node dashed" id="mon" style="grid-column: 5; grid-row: 2; margin: 27px 0 19px"><h3>Analytics</h3><p>Records only, never waited for</p></div>
25
+
26
+ <div class="decision accent" id="changed" style="grid-column: 6; grid-row: 1 / span 2"><span>Did a check change the total or the stock?</span></div>
27
+ <div class="decision" id="safe" style="grid-column: 6; grid-row: 3"><span>Is the order flagged for review?</span></div>
28
+
29
+ <div class="col" style="grid-column: 4; grid-row: 3; gap: 28px">
30
+ <div class="node" id="sent"><h3>Charged at once</h3><p>Reviewed afterwards</p></div>
31
+ <div class="node" id="held"><h3>Held for review</h3><p>Then charged, or cancelled</p></div>
32
+ </div>
33
+ <div class="node" id="done" style="grid-column: 2; grid-row: 3; align-self: center"><h3>On the paid order</h3><p>Analytics, then emails</p></div>
34
+ </div>
35
+
36
+ <fig-arrow from="msg" to="choose" label="pricing starts"></fig-arrow>
37
+ <fig-arrow from="msg" to="checks"></fig-arrow>
38
+ <fig-arrow from="choose" to="load"></fig-arrow>
39
+ <fig-arrow from="load" to="build"></fig-arrow>
40
+ <fig-arrow from="build" to="call"></fig-arrow>
41
+ <fig-arrow from="call" to="changed" label="total"></fig-arrow>
42
+ <fig-arrow from="checks" to="changed"></fig-arrow>
43
+ <fig-arrow from="changed" to="choose" accent weight="3.5" from-side="top" to-side="top" label="Yes: drop the total and price again"></fig-arrow>
44
+ <fig-arrow from="changed" to="safe" label="No: use the total"></fig-arrow>
45
+ <fig-arrow from="safe" to="sent" label="No, usually"></fig-arrow>
46
+ <fig-arrow from="safe" to="held" label="Yes"></fig-arrow>
47
+ <fig-arrow from="sent" to="done"></fig-arrow>
48
+ <fig-arrow from="held" to="done"></fig-arrow>
49
+ <fig-arrow from="done" to="msg" dashed label="The basket carries to the next order"></fig-arrow>
50
+ </main>
@@ -0,0 +1,33 @@
1
+ <style>
2
+ .pair { display: grid; grid-template-columns: 1fr 1fr; column-gap: 64px; row-gap: 18px; align-items: start; }
3
+ .chat { display: flex; flex-direction: column; gap: 10px; }
4
+ .unit { border: 2.5px solid var(--accent); border-radius: 22px; padding: 7px; display: flex; flex-direction: column; gap: 8px; width: 74%; }
5
+ .unit.u { align-self: flex-end; border-color: var(--line); }
6
+ .b { background: var(--panel); border: 1.5px solid var(--hair); border-radius: 15px; padding: 9px 16px; font-size: var(--small); color: var(--muted); }
7
+ .u .b { background: #3a4058; }
8
+ .stat { display: flex; flex-direction: column; gap: 6px; }
9
+ .stat p { color: var(--muted); font-size: var(--small); }
10
+ </style>
11
+ <main class="fig">
12
+ <header>
13
+ <h1>The spam filter now checks <em>once per reply</em>, not once per bubble</h1>
14
+ <p class="read">Each outline is one check. <b class="t-accent">Orange</b> is the support agent's reply.</p>
15
+ </header>
16
+ <div class="pair">
17
+ <span class="lane-title">Until 2 March: four checks</span>
18
+ <span class="lane-title">From 2 March: two checks</span>
19
+ <div class="chat">
20
+ <div class="unit u"><div class="b">Customer message</div></div>
21
+ <div class="unit"><div class="b">Agent bubble</div></div>
22
+ <div class="unit"><div class="b">Agent bubble</div></div>
23
+ <div class="unit"><div class="b">Agent bubble</div></div>
24
+ </div>
25
+ <div class="chat">
26
+ <div class="unit u"><div class="b">Customer message</div></div>
27
+ <div class="unit"><div class="b">Agent bubble</div><div class="b">Agent bubble</div><div class="b">Agent bubble</div></div>
28
+ </div>
29
+ <div class="stat"><span class="num">58%</span><p>of flags repeated within 10 seconds</p></div>
30
+ <div class="stat"><span class="num">under 1%</span><p>of flags repeated within 10 seconds</p></div>
31
+ </div>
32
+ <footer><p>Invented data, for illustration.</p></footer>
33
+ </main>
@@ -0,0 +1,25 @@
1
+ <main class="fig paper">
2
+ <header>
3
+ <h1>The shop takes about 50 orders a day, <em>down from a 131 peak</em></h1>
4
+ <p class="read">Orders per day, 2 February to 15 March 2026.</p>
5
+ </header>
6
+ <div id="chart"></div>
7
+ <script>
8
+ const raw = "2026-02-02|44 2026-02-03|131 2026-02-04|78 2026-02-05|46 2026-02-06|54 2026-02-07|35 2026-02-08|36 2026-02-09|60 2026-02-10|51 2026-02-11|37 2026-02-12|45 2026-02-13|52 2026-02-14|35 2026-02-15|63 2026-02-16|50 2026-02-17|40 2026-02-18|35 2026-02-19|36 2026-02-20|47 2026-02-21|47 2026-02-22|10 2026-02-23|15 2026-02-24|10 2026-02-25|25 2026-02-26|21 2026-02-27|10 2026-02-28|60 2026-03-01|52 2026-03-02|37 2026-03-03|64 2026-03-04|41 2026-03-05|54 2026-03-06|54 2026-03-07|52 2026-03-08|64 2026-03-09|35 2026-03-10|52 2026-03-11|52 2026-03-12|46 2026-03-13|35 2026-03-14|41 2026-03-15|35";
9
+ const days = raw.split(" ").map((s) => { const [d, n] = s.split("|"); return { day: new Date(d), n: +n }; });
10
+ const peak = days.reduce((m, d) => (d.n > m.n ? d : m));
11
+ fig.plot("#chart", ({ Plot, theme }) => ({
12
+ height: 380, marginLeft: 44, marginRight: 30, marginTop: 30,
13
+ x: { label: null, ticks: 6 }, y: { label: null, grid: true, ticks: 4, domain: [0, 150] },
14
+ marks: [
15
+ Plot.rectY([0], { x1: () => new Date("2026-02-21T12:00"), x2: () => new Date("2026-02-27T12:00"), y1: 0, y2: 150, fill: theme.second, fillOpacity: 0.12 }),
16
+ Plot.lineY(days, { x: "day", y: "n", stroke: theme.soft, strokeWidth: 2.5 }),
17
+ Plot.dot([peak], { x: "day", y: "n", fill: theme.accent, r: 6 }),
18
+ Plot.text([peak], { x: "day", y: "n", text: () => "131 on 3 Feb", dx: 12, textAnchor: "start", fill: theme.accent, fontWeight: 600, fontSize: 17 }),
19
+ Plot.text([{ day: new Date("2026-02-24T12:00"), n: 92 }], { x: "day", y: "n", text: () => "10 to 25 a day,\nwhile the site was down", fill: theme.second, fontWeight: 600, fontSize: 17, lineHeight: 1.2 }),
20
+ Plot.ruleY([0], { stroke: theme.line }),
21
+ ],
22
+ }));
23
+ </script>
24
+ <footer><p>Invented data, for illustration.</p></footer>
25
+ </main>
@@ -0,0 +1,16 @@
1
+ <main class="fig">
2
+ <header>
3
+ <h1>The release check puts <em>the verdict beside each measure</em></h1>
4
+ <p class="read">A screenshot, with a note pointing at the part it describes.</p>
5
+ </header>
6
+ <div class="row wide middle">
7
+ <div class="shot grow" style="flex: 2.2">
8
+ <img src="assets/settings-page.png">
9
+ <div class="mark" id="rows" style="left: 3%; top: 30%; width: 94%; height: 46%"></div>
10
+ </div>
11
+ <div class="col grow" style="gap: 40px">
12
+ <div class="node bare" id="n1"><h3>One row per measure</h3><p>Its value, then pass or fail</p></div>
13
+ </div>
14
+ </div>
15
+ <fig-arrow from="n1" to="rows" accent route="curve" from-side="left" to-side="right"></fig-arrow>
16
+ </main>
@@ -0,0 +1,35 @@
1
+ <main class="fig">
2
+ <header>
3
+ <h1>Three catalogue copies today, <em>one shared copy proposed</em></h1>
4
+ </header>
5
+ <div class="grid sides" style="--cols: 2; column-gap: 110px">
6
+ <div class="col" style="gap: 22px">
7
+ <span class="lane-title" style="font-size: var(--read); color: var(--ink)">Now: 12 GB</span>
8
+ <div class="row tight">
9
+ <div class="node grow" id="k1"><h3>Copy</h3><p>4 GB</p></div>
10
+ <div class="node grow" id="k2"><h3>Copy</h3><p>4 GB</p></div>
11
+ <div class="node grow" id="k3"><h3>Copy</h3><p>4 GB</p></div>
12
+ </div>
13
+ <div class="row tight" style="margin-top: 40px">
14
+ <div class="node bare grow tc" id="w1"><h3>Worker 1</h3></div>
15
+ <div class="node bare grow tc" id="w2"><h3>Worker 2</h3></div>
16
+ <div class="node bare grow tc" id="w3"><h3>Worker 3</h3></div>
17
+ </div>
18
+ </div>
19
+ <div class="col" style="gap: 22px">
20
+ <span class="lane-title t-accent" style="font-size: var(--read)">Proposed: 4 GB</span>
21
+ <div class="row"><div class="node accent fill grow" id="sh"><h3>One shared copy</h3><p>4 GB</p></div></div>
22
+ <div class="row tight" style="margin-top: 40px">
23
+ <div class="node bare grow tc" id="v1"><h3>Worker 1</h3></div>
24
+ <div class="node bare grow tc" id="v2"><h3>Worker 2</h3></div>
25
+ <div class="node bare grow tc" id="v3"><h3>Worker 3</h3></div>
26
+ </div>
27
+ </div>
28
+ </div>
29
+ <fig-arrow from="k1" to="w1"></fig-arrow>
30
+ <fig-arrow from="k2" to="w2"></fig-arrow>
31
+ <fig-arrow from="k3" to="w3"></fig-arrow>
32
+ <fig-arrow from="sh" to="v1" accent></fig-arrow>
33
+ <fig-arrow from="sh" to="v2" accent></fig-arrow>
34
+ <fig-arrow from="sh" to="v3" accent></fig-arrow>
35
+ </main>
@@ -0,0 +1,13 @@
1
+ <main class="fig">
2
+ <header>
3
+ <h1><em>Three carriers</em> deliver 94% of parcels</h1>
4
+ <p class="read">Share of 48,200 parcels sent in October, in the order the shop tries each carrier.</p>
5
+ </header>
6
+ <fig-split unit="%">
7
+ <part value="0.8">Collected in store</part>
8
+ <part value="3.1" accent>Same-day courier</part>
9
+ <part value="58.6" accent>National post</part>
10
+ <part value="32.3" accent>Parcel lockers</part>
11
+ <part value="5.2">No carrier recorded</part>
12
+ </fig-split>
13
+ </main>
@@ -0,0 +1,33 @@
1
+ <main class="fig">
2
+ <header>
3
+ <h1><code style="font-family: var(--mono); font-size: .78em">created_at</code> dates orders <em>days before their sale started</em></h1>
4
+ <p class="read">The <b class="t-second">payment stamp</b> lands within hours of each sale's start.</p>
5
+ </header>
6
+ <div id="chart"></div>
7
+ <script>
8
+ const D = (s) => new Date(s + "Z");
9
+ const rows = [
10
+ { name: "Spring sale", launch: D("2026-03-06T09:00"), arrive: D("2026-03-06T09:20"), fuzz: D("2026-03-01T15:00"), gap: "4.8 days early" },
11
+ { name: "Garden week", launch: D("2026-03-09T10:00"), arrive: D("2026-03-09T10:15"), fuzz: D("2026-03-01T12:00"), gap: "7.9 days early" },
12
+ { name: "Clearance", launch: D("2026-03-10T16:00"), arrive: D("2026-03-10T19:00"), fuzz: D("2026-03-03T04:00"), gap: "7.5 days early" },
13
+ ];
14
+ const mid = (r) => new Date((+r.launch + +r.fuzz) / 2);
15
+ fig.plot("#chart", ({ Plot, theme }) => ({
16
+ height: 350, marginLeft: 20, marginRight: 60, marginTop: 10, marginBottom: 44,
17
+ x: { label: null, domain: [D("2026-02-28T12:00"), D("2026-03-11T12:00")], ticks: 6, grid: true },
18
+ y: { domain: rows.map((r) => r.name), axis: null, padding: 0.5 },
19
+ marks: [
20
+ Plot.text(rows, { x: "fuzz", y: "name", text: "name", dy: -34, textAnchor: "start", fill: theme.ink, fontWeight: 600, fontSize: 18 }),
21
+ Plot.link(rows, { x1: "fuzz", x2: "launch", y1: "name", y2: "name", stroke: theme.accent, strokeWidth: 3 }),
22
+ Plot.text(rows, { x: mid, y: "name", text: "gap", dy: -16, fill: theme.accent, fontWeight: 600, fontSize: 17 }),
23
+ Plot.dot(rows, { x: "fuzz", y: "name", r: 7, fill: theme.accent }),
24
+ Plot.tickX(rows, { x: "launch", y: "name", stroke: theme.ink, strokeWidth: 3, insetTop: -4, insetBottom: -4 }),
25
+ Plot.dot(rows, { x: "arrive", y: "name", r: 7, fill: theme.second }),
26
+ Plot.text(rows.slice(0, 1), { x: "fuzz", y: "name", text: () => "earliest created_at", dx: -2, dy: 22, textAnchor: "start", fill: theme.accent, fontSize: 16 }),
27
+ Plot.text(rows.slice(0, 1), { x: "launch", y: "name", text: () => "sale starts", dx: 16, dy: -12, textAnchor: "start", fill: theme.ink, fontSize: 16 }),
28
+ Plot.text(rows.slice(0, 1), { x: "launch", y: "name", text: () => "first payment stamp", dx: 16, dy: 12, textAnchor: "start", fill: theme.second, fontSize: 16 }),
29
+ ],
30
+ }));
31
+ </script>
32
+ <footer><p>Invented data, for illustration.</p></footer>
33
+ </main>
package/package.json CHANGED
@@ -1,6 +1,57 @@
1
1
  {
2
2
  "name": "@liggi/figkit",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "Explanatory figures for agents and people: write a little HTML or a chart spec, get a laid-out PNG and a list of what is wrong with it.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "bin": {
8
+ "figkit": "bin/fig.mjs"
9
+ },
10
+ "files": [
11
+ "bin/",
12
+ "runtime/",
13
+ "viz/",
14
+ "dist/",
15
+ "examples/",
16
+ "GUIDE.md",
17
+ "THIRD_PARTY_NOTICES.md"
18
+ ],
19
+ "scripts": {
20
+ "build": "node viz/build.mjs",
21
+ "prepack": "node viz/build.mjs",
22
+ "examples": "node scripts/render-examples.mjs"
23
+ },
24
+ "engines": {
25
+ "node": ">=20"
26
+ },
27
+ "keywords": [
28
+ "figure",
29
+ "diagram",
30
+ "chart",
31
+ "png",
32
+ "observable-plot",
33
+ "playwright",
34
+ "agents"
35
+ ],
36
+ "dependencies": {
37
+ "@fontsource/geist-mono": "^5.3.0",
38
+ "@fontsource/geist-sans": "^5.3.0",
39
+ "@fontsource/newsreader": "^5.3.0",
40
+ "@observablehq/plot": "^0.6.19",
41
+ "d3": "^7.9.2",
42
+ "playwright-core": "^1.65.0",
43
+ "zod": "^4.6.7"
44
+ },
45
+ "devDependencies": {
46
+ "esbuild": "^0.28.2"
47
+ },
48
+ "author": "Jason Liggi",
49
+ "repository": {
50
+ "type": "git",
51
+ "url": "git+https://github.com/Liggi/figkit.git"
52
+ },
53
+ "homepage": "https://github.com/Liggi/figkit#readme",
54
+ "bugs": {
55
+ "url": "https://github.com/Liggi/figkit/issues"
56
+ }
57
+ }