Re-create website using Jekyll

9851e3d3af4dd9f0192364aec8a98256b7f970c2
It's about time.
Alexis Sellier committed ago 1 parent 4d0a9341
.gitignore +6 -1
1 -
index.html
1 +
_site/
2 +
.jekyll-cache/
3 +
_cache/
4 +
.DS_Store
5 +
node_modules/
6 +
vendor/
Gemfile added +4 -0
1 +
source "https://rubygems.org"
2 +
3 +
gem "jekyll", "~> 4.2"
4 +
gem "webrick"
Gemfile.lock added +71 -0
1 +
GEM
2 +
  remote: https://rubygems.org/
3 +
  specs:
4 +
    addressable (2.8.6)
5 +
      public_suffix (>= 2.0.2, < 6.0)
6 +
    colorator (1.1.0)
7 +
    concurrent-ruby (1.2.3)
8 +
    em-websocket (0.5.3)
9 +
      eventmachine (>= 0.12.9)
10 +
      http_parser.rb (~> 0)
11 +
    eventmachine (1.2.7)
12 +
    ffi (1.16.3)
13 +
    forwardable-extended (2.6.0)
14 +
    google-protobuf (3.25.2-x86_64-linux)
15 +
    http_parser.rb (0.8.0)
16 +
    i18n (1.14.1)
17 +
      concurrent-ruby (~> 1.0)
18 +
    jekyll (4.3.3)
19 +
      addressable (~> 2.4)
20 +
      colorator (~> 1.0)
21 +
      em-websocket (~> 0.5)
22 +
      i18n (~> 1.0)
23 +
      jekyll-sass-converter (>= 2.0, < 4.0)
24 +
      jekyll-watch (~> 2.0)
25 +
      kramdown (~> 2.3, >= 2.3.1)
26 +
      kramdown-parser-gfm (~> 1.0)
27 +
      liquid (~> 4.0)
28 +
      mercenary (>= 0.3.6, < 0.5)
29 +
      pathutil (~> 0.9)
30 +
      rouge (>= 3.0, < 5.0)
31 +
      safe_yaml (~> 1.0)
32 +
      terminal-table (>= 1.8, < 4.0)
33 +
      webrick (~> 1.7)
34 +
    jekyll-sass-converter (3.0.0)
35 +
      sass-embedded (~> 1.54)
36 +
    jekyll-watch (2.2.1)
37 +
      listen (~> 3.0)
38 +
    kramdown (2.4.0)
39 +
      rexml
40 +
    kramdown-parser-gfm (1.1.0)
41 +
      kramdown (~> 2.0)
42 +
    liquid (4.0.4)
43 +
    listen (3.8.0)
44 +
      rb-fsevent (~> 0.10, >= 0.10.3)
45 +
      rb-inotify (~> 0.9, >= 0.9.10)
46 +
    mercenary (0.4.0)
47 +
    pathutil (0.16.2)
48 +
      forwardable-extended (~> 2.6)
49 +
    public_suffix (5.0.4)
50 +
    rb-fsevent (0.11.2)
51 +
    rb-inotify (0.10.1)
52 +
      ffi (~> 1.0)
53 +
    rexml (3.2.6)
54 +
    rouge (4.2.0)
55 +
    safe_yaml (1.0.5)
56 +
    sass-embedded (1.69.5-x86_64-linux-gnu)
57 +
      google-protobuf (~> 3.23)
58 +
    terminal-table (3.0.2)
59 +
      unicode-display_width (>= 1.1.1, < 3)
60 +
    unicode-display_width (2.5.0)
61 +
    webrick (1.8.1)
62 +
63 +
PLATFORMS
64 +
  x86_64-linux-gnu
65 +
66 +
DEPENDENCIES
67 +
  jekyll (~> 4.2)
68 +
  webrick
69 +
70 +
BUNDLED WITH
71 +
   2.5.4
Makefile +8 -8
1 -
PAGES  := popol/ donate/ nakamoto/ whoami/
2 -
STATIC := css/ images/ fonts/ js/ favicon.png avatar.png cloudhead.gpg
1 +
default: _site
2 +
	jekyll build
3 3
4 -
default:
5 -
	./build ~/txt/pub .
4 +
serve:
5 +
	jekyll serve --port 3000
6 6
7 -
publish:
8 -
	./publish $(STATIC) $(PAGES) public/ index.html style.css
7 +
dependencies:
8 +
	gem install jekyll bundler aws-sdk-s3
9 9
10 -
deps:
11 -
	gem install commonmarker aws-sdk-s3 rack
10 +
publish: default
11 +
	./publish _site
12 12
13 13
.PHONY: publish
_config.yml added +9 -0
1 +
url: "https://cloudhead.io"
2 +
baseurl: "/"
3 +
title: "cloudhead.io"
4 +
highlighter: none
5 +
markdown: kramdown
6 +
collections:
7 +
  pages:
8 +
    output: true
9 +
    permalink: /:path/
style.css → _includes/index.css renamed +0 -0
_layouts/default.html added +85 -0
1 +
<!doctype html>
2 +
<html lang="en">
3 +
  <head>
4 +
    <title>
5 +
      {% if page.title %}
6 +
        {{ page.title }} ~ {{ site.title }}
7 +
      {% else %}
8 +
        {{ site.title }}
9 +
      {% endif %}
10 +
    </title>
11 +
    <meta charset="utf-8">
12 +
    <meta name="viewport" content="width=device-width, initial-scale=1">
13 +
    <meta name="twitter:site" content="@cloudhead" />
14 +
    <meta name="twitter:creator" content="@cloudhead" />
15 +
    <meta property="og:title" content="<%= title %>" />
16 +
    <link rel="shortcut icon" href="/favicon.png" />
17 +
    <link rel="stylesheet" type="text/css" href="/css/fonts.css">
18 +
    <link rel="stylesheet" type="text/css" href="/css/base.css">
19 +
  </head>
20 +
  <body>
21 +
    <nav>
22 +
      <ul>
23 +
        <li><a href="/">/</a> <span class="subtle">home</span></li>
24 +
        <li><a href="http://twitter.com/cloudhead">@cloudhead</a> <span class="subtle">tweets</span></li>
25 +
        <li><a href="https://github.com/cloudhead">code</a> <span class="subtle">github.com/cloudhead</span></li>
26 +
        <li><a href="https://github.com/cloudhead/dotfiles">dotfiles</a> <span class="subtle">~</span></li>
27 +
        <li><a href="https://rx.cloudhead.io">rx</a> <span class="subtle">rx.cloudhead.io ๐Ÿ‘พ</span></li>
28 +
        <li><a href="https://github.com/cloudhead/shady.vim">shady</a> <span class="subtle">:colorscheme</span</li>
29 +
        {% for page in site.pages %}
30 +
          {% if page.title %}
31 +
            <li>
32 +
              <a href="{{ page.url | relative_url }}">{{ page.slug }}</a>
33 +
              <span class="subtle">{{ page.teaser }}</span>
34 +
            </li>
35 +
          {% endif %}
36 +
        {% endfor %}
37 +
        <li><a href="https://merveilles.town/@cloudhead">toots</a> <span class="subtle">fediverse</span></li>
38 +
        <li><a href="https://art.cloudhead.io">art</a> <span class="subtle">๐Ÿ–Œ๏ธ</span></li>
39 +
        <li><a href="https://radicle.xyz">radicle</a> <span class="subtle">p2p code-collab</span></li>
40 +
      </ul>
41 +
      <footer>
42 +
        &copy; {{ 'now' | date: "%Y" }} Alexis Sellier
43 +
      </footer>
44 +
    </nav>
45 +
46 +
    <article class="note">
47 +
      <header>
48 +
        <div class="meta">
49 +
          {% if page.type == "note" or page.type == "post" %}
50 +
            <div class="date">{{ page.date | date: "%Y.%m.%d" }}</div>
51 +
          {% else %}
52 +
            <div class="date">{{ page.super }}</div>
53 +
          {% endif %}
54 +
          {% if page.crate %}
55 +
            <div class="badges">
56 +
              <a href="https://crates.io/crates/{{ page.crate }}">
57 +
                <img alt="crates.io" src="https://img.shields.io/crates/v/{{ page.crate }}.svg?label={{ page.crate }}"/>
58 +
              </a>
59 +
              <a href="https://docs.rs/{{ page.crate }}">
60 +
                <img alt="docs.rs" src="https://docs.rs/{{ page.crate }}/badge.svg"/>
61 +
              </a>
62 +
              <a href="https://github.com/cloudhead/{{ page.crate }}">
63 +
                <img alt="github.com" src="https://img.shields.io/github/stars/cloudhead/{{ page.crate }}?style=social"/>
64 +
              </a>
65 +
            </div>
66 +
          {% endif %}
67 +
        </div>
68 +
        {% if page.type == "post" %}
69 +
          <h1><a href="{{ page.permalink }}">{{ page.title }}</a></h1>
70 +
        {% else if page.headline %}
71 +
          <h1><a href="{{ page.permalink }}">{{ page.headline }}</a></h1>
72 +
        {% endif %}
73 +
      </header>
74 +
      <section class="body">
75 +
        {{ content }}
76 +
      </section>
77 +
    </article>
78 +
79 +
    <footer>
80 +
      &copy; {{ 'now' | date: "%Y" }} Alexis Sellier
81 +
    </footer>
82 +
    <script type="text/javascript" src="{{ "/js/hirust.js" | relative_url }}"></script>
83 +
  </body>
84 +
</html>
85 +
_pages/fund.md added +82 -0
1 +
---
2 +
title: Fund my work
3 +
teaser: fund my work ๐Ÿ’ธ
4 +
slug: fund
5 +
layout: default
6 +
---
7 +
8 +
<div class="qr">
9 +
  <img src="qr-bc1qa47tl4vmz8j82wdsdkmxex30r23c9ljs84fxah.png" onclick="copyAddress()" />
10 +
  <input readonly class="address" id="address" onclick="selectAddress()" value="bc1qa47tl4vmz8j82wdsdkmxex30r23c9ljs84fxah"/>
11 +
  <div class="copy-message" id="address-copied">๐Ÿจ Address copied!</div>
12 +
</div>
13 +
14 +
Hi, I'm **cloudhead**. I work on peer-to-peer and decentralized technologies.
15 +
My current focus is on creating a **high-quality privacy preserving Bitcoin
16 +
light-client in Rust**.
17 +
18 +
You can fund my work on [nakamoto][] by donating Bitcoin (โ‚ฟ) to the above
19 +
address. This will allow me to release more [work][] in the open. Donations go
20 +
towards my current area of focus.
21 +
22 +
## Donations received
23 +
24 +
Total: <strong id="total-usd">?</strong> (<span id="total-btc">?</span> BTC)
25 +
26 +
* `06.01.2023` ๐Ÿ™‚  0.0005 BTC
27 +
* `26.03.2023` ๐Ÿ™‚  0.0003 BTC
28 +
* `13.12.2022` ๐Ÿ™‚  0.0006 BTC
29 +
* `08.09.2022` ๐Ÿ™‚  0.0008 BTC
30 +
* `21.04.2022` ๐Ÿ˜ƒ  0.0125 BTC
31 +
* `22.08.2021` ๐Ÿ™‚  0.0009 BTC
32 +
* `20.06.2021` ๐Ÿ˜ƒ  0.0300 BTC
33 +
* `12.03.2021` ๐Ÿ™‚  0.0004 BTC
34 +
* `12.03.2021` ๐Ÿ™‚  0.0003 BTC
35 +
* `24.01.2021` ๐Ÿ™‚  0.0008 BTC
36 +
* `28.12.2020` ๐Ÿ˜ƒ  0,0310 BTC
37 +
* `21.12.2020` ๐Ÿ™‚  0,0005 BTC
38 +
* `15.10.2020` ๐Ÿ™‚  0,0002 BTC
39 +
* `13.10.2020` ๐Ÿ™‚  0,0004 BTC
40 +
* `06.10.2020` ๐Ÿ™‚  0,0030 BTC
41 +
* `21.08.2020` ๐Ÿ™‚  0,0020 BTC
42 +
* `16.08.2020` ๐Ÿ™‚  0,0010 BTC
43 +
* `14.08.2020` ๐Ÿ˜ƒ  0,0100 BTC
44 +
45 +
Beyond helping me finance some of these endeavors, donations are a way for me
46 +
to know that the work I'm doing is appreciated.
47 +
48 +
Thank you ๐Ÿ’š
49 +
50 +
<script>
51 +
  function selectAddress() {
52 +
    var address = document.getElementById("address");
53 +
    address.select();
54 +
  }
55 +
56 +
  function copyAddress() {
57 +
    var address = document.getElementById("address");
58 +
    var msg = document.getElementById("address-copied");
59 +
60 +
    if (navigator && navigator.clipboard) {
61 +
      navigator.clipboard.writeText(address.value).then(function () {
62 +
        msg.style.visibility = "visible";
63 +
      });
64 +
    }
65 +
  }
66 +
67 +
  Promise.all([
68 +
    fetch("https://mempool.space/api/address/bc1qa47tl4vmz8j82wdsdkmxex30r23c9ljs84fxah")
69 +
      .then(res => res.json())
70 +
      .then(data => data.chain_stats.funded_txo_sum),
71 +
    fetch("https://mempool.space/api/v1/prices")
72 +
      .then(res => res.json())
73 +
      .then(data => data.USD),
74 +
  ]).then(([donated, price]) => {
75 +
    document.getElementById("total-usd").innerHTML = "$" + ((donated * price) / 100000000).toFixed(2);
76 +
    document.getElementById("total-btc").innerHTML = (donated / 100000000).toFixed(2);
77 +
  });
78 +
</script>
79 +
80 +
[nakamoto]: https://github.com/cloudhead/nakamoto
81 +
[work]: https://github.com/cloudhead
82 +
[popol]: /popol
donate/qr-bc1qa47tl4vmz8j82wdsdkmxex30r23c9ljs84fxah.png → _pages/fund/qr-bc1qa47tl4vmz8j82wdsdkmxex30r23c9ljs84fxah.png renamed +0 -0
_pages/log.md added +16 -0
1 +
---
2 +
title: Blog
3 +
headline: cloudhead's blog
4 +
super: "Thoughts on software & technology"
5 +
layout: default
6 +
permalink: /log/
7 +
teaser: thoughts on software
8 +
slug: log
9 +
---
10 +
11 +
{% for post in site.posts %}
12 +
  <div>
13 +
    <span class="subtle">{{ post.date | date: "%Y.%m.%d" }}</span>
14 +
    <a href="{{ post.url }}">{{ post.title }}</a>
15 +
  </div>
16 +
{% endfor %}
_pages/nakamoto.md added +1 -0
1 +
../_posts/2021-01-22-nakamoto.md
_pages/popol.md added +1 -0
1 +
../_posts/2020-07-19-popol.md
_pages/whoami.md added +49 -0
1 +
---
2 +
title: Who am I?
3 +
teaser: bio
4 +
slug: whoami
5 +
layout: default
6 +
---
7 +
8 +
๐Ÿ‘‹, I'm **cloudhead**. I develop [free software][] that distributes power and
9 +
control away from central authorities, towards individual users.  Most of my
10 +
time is dedicated to [Radicle][radicle], a peer-to-peer network and sovereign
11 +
code forge; and [Bitcoin][bitcoin], for which I maintain a [light
12 +
client][nakamoto].
13 +
14 +
I've been writing software for the last two decades and have worked on almost
15 +
all areas of the software stack, though my interest lies with decentralized
16 +
systems, peer-to-peer technology and computer graphics. When I have time and need a
17 +
break from software, I also practice [art][].
18 +
19 +
Since the early 2000s, I've published a wide range of free and open source
20 +
software, including:
21 +
22 +
* **nakamoto**: a privacy-preserving Bitcoin light-client in Rust
23 +
* **rx**: a minimalist cross-platform pixel editor
24 +
* **popol**: a non-blocking I/O library for Rust
25 +
* **rgx**: a 2D bitmap graphics library for Rust
26 +
* **nonempty**: a correct-by-construction non-empty list library for Rust
27 +
* **http-console**: a simple and intuitive HTTP REPL
28 +
* **neovim-fuzzy**: a fuzzy file finder for neovim
29 +
* **avl-auth**: an authenticated AVL+ tree implementation in Haskell
30 +
* **node-static**: an rfc 2616 compliant HTTP static file server for node
31 +
* **LESS**: an attempt at a better CSS
32 +
* **hijs**: a simple and fast JavaScript syntax highlighter for the browser
33 +
34 +
You can find most of these on [GitHub][free software].
35 +
36 +
My editor of choice is [neovim][], and my primary OS is [Arch Linux][arch] with
37 +
[spectrwm][] as the window manager. You can find my dotfiles [here][dotfiles]
38 +
and my [GPG key][gpg].
39 +
40 +
[free software]: https://github.com/cloudhead
41 +
[art]: https://art.cloudhead.io
42 +
[Radicle]: https://radicle.xyz
43 +
[bitcoin]: https://en.wikipedia.org/wiki/Bitcoin
44 +
[nakamoto]: /nakamoto/
45 +
[neovim]: https://neovim.io/
46 +
[arch]: https://archlinux.org/
47 +
[dotfiles]: https://github.com/cloudhead/dotfiles
48 +
[spectrwm]: https://github.com/conformal/spectrwm
49 +
[gpg]: /cloudhead.gpg
log/articles/2010-04-24-staying-the-hell-out-of-insert-mode.txt → _posts/2010-04-24-staying-the-hell-out-of-insert-mode.md renamed +28 -25
1 -
Title: Staying the hell out of insert mode
2 -
Date: April 24th 2010
1 +
---
2 +
title: "Staying the hell out of insert mode"
3 +
date: 24.04.2010
4 +
type: post
5 +
layout: default
6 +
---
3 7
4 8
Back in the day, the first thing I learnt about [vi][1] was how to get into
5 9
insert mode. It was really quite essential, because without knowing how, you
6 10
couldn't actually type anything.
7 11
8 12
[1]: http://en.wikipedia.org/wiki/Vi
9 13
10 -
The secret was in the "`i`" command.  The `i` command was what made vi useable,
11 -
it was the alpha and the omega, vi's be-all end-all---and whenever the editor
14 +
The secret was in the `i` command.  The `i` command was what made vi useable,
15 +
it was the alpha and the omega, vi's be-all end-all - and whenever the editor
12 16
would magically exit insert mode, panic would ensue and I'd frantically press
13 -
'`i`' to go back to insert-land.
17 +
`i` to go back to insert-land.
14 18
15 19
I mean, what else would you want to do in a *text editor*, besides entering text?
16 20
It puzzled me for a long time.
17 21
18 22
The `i` key turned this rather archaic and obtuse program into a text-editor
27 31
28 32
## Why stay out?
29 33
30 34
Insert mode is vi's weakest mode. In this mode, it's no better than *any other
31 35
editor*, and you may as well be using *any other editor*. Vi's true power lies
32 -
in its 'Normal mode'. Yes, inserting text is *not* normal in vi-land. The
36 +
in its *Normal mode*. Yes, inserting text is *not* normal in vi-land. The
33 37
more time you spend in Normal mode, the more super-powers you will have.
34 38
Trust me, it doesn't get any *less* normal than Normal mode.
35 39
36 40
> Someone once argued that insert mode was actually vi's most powerful mode,
37 -
because it was the only mode in which you could insert text. He obviously
38 -
wasn't familiar with `:r`.
41 +
> because it was the only mode in which you could insert text. He obviously
42 +
> wasn't familiar with `:r`.
39 43
40 -
## How do you get the frack out?
44 +
## How do you get the hell out?
41 45
42 46
Common knowledge states that pressing the `Esc` key will get you out of insert
43 47
mode.  This is correct. This is also not very useful. If you're going to move
44 48
in and out of insert mode all the time, you're going to want it to be as
45 49
seamless as possible. There are two other ways to get out of insert mode:
48 52
49 53
and
50 54
51 55
    Ctrl-C
52 56
53 -
The other alternative, which I have chosen, is to map a key sequence to `Esc`.
54 -
Vi lets you map arbitrary sequences of keys to anything you like. For instance,
55 -
you could map `jj` to `Esc`. `j` is on the home row, so you don't need to move
56 -
your fingers to exit insert mode. I tried this out for a while, as well as a
57 -
couple of other alternatives. In the end, I settled for `kj`, as it was the
58 -
fastest to type. To create this mapping, add this to your `.vimrc` file:
57 +
The other alternative, which I have chosen, is to map a key sequence to enter
58 +
normal mode. Vi lets you map arbitrary sequences of keys to anything you like.
59 +
For instance, you could map `jj` to normal mode. `j` is on the home row, so you
60 +
don't need to move your fingers to exit insert mode. I tried this out for a
61 +
while, as well as a couple of other alternatives. In the end, I settled on
62 +
`kj`, as it was the fastest to type. To create this mapping, add this to your
63 +
`.vimrc` file:
59 64
60 65
    inoremap kj <Esc>
61 66
62 67
So what if I actually wanted to write "jj" or "kj"? For example if I wanted to
63 68
write a blog post such as this one? Well, I'd just have to wait for the
64 69
first letter in the sequence to be inserted. The length of time vi waits for you
65 70
to complete such a sequence of characters is controlled by the `timeoutlen` setting,
66 -
which defaults to 1000 ms. You can change this as such:
71 +
which defaults to `1000ms`. You can change this as such:
67 72
68 73
    set timeoutlen=200
69 74
70 75
Another really useful trick is switching to normal mode for a single command.
71 -
You can do this with `Ctrl-O`. For instance, say you're typing away and you
72 -
want to quickly save your work, you can type `Ctrl-O :w`, which will write the
73 -
file and put you back in insert mode.
76 +
You can do this with `Ctrl-O`. For instance, say you're typing away in insert
77 +
mode and you want to quickly save your work, you can type `Ctrl-O` and `:w`,
78 +
which will write the file and put you back in insert mode.
74 79
75 80
## Learning, the hard way
76 81
77 -
How do you stop yourself from spending too much time in insert mode?
78 -
Well, you could add these key mappings to your *.vimrc*:
82 +
How do you stop yourself from spending too much time in insert mode? Well, you
83 +
could add these key mappings to your *.vimrc*:
79 84
80 85
    inoremap <Left>  <NOP>
81 86
    inoremap <Right> <NOP>
82 87
    inoremap <Up>    <NOP>
83 88
    inoremap <Down>  <NOP>
86 91
start navigating (*gasp*) in insert mode.
87 92
88 93
## During your stay
89 94
90 95
Even though staying out of insert mode is safest, knowing what you *can* do in
91 -
this mode can be useful for the duration of your stay. I know I criticized it a
92 -
little, but it deserves some recognition for these useful commands:
96 +
this mode can be useful for the duration of your stay.
93 97
94 98
- `Ctrl-Y`: insert the character right above the cursor&mdash;you can see how this can be useful...
95 99
- `Ctrl-U`: delete the current line from the cursor position.
96 100
- `Ctrl-A`: re-insert the text inserted in the previous insert session.
97 101
98 102
I hope I didn't put you off of insert mode too much. After all, as someone once
99 -
said: "it is the only mode in which you can insert".
100 -
103 +
said: *it is the only mode in which you can insert*.
_posts/2020-07-19-popol.md added +222 -0
1 +
---
2 +
title: "Popol: Minimal non-blocking I/O with Rust"
3 +
headline: "Popol: Minimal non-blocking I/O with Rust"
4 +
date: "19.07.2020"
5 +
teaser: "async i/o"
6 +
type: note
7 +
slug: popol
8 +
crate: popol
9 +
layout: default
10 +
---
11 +
12 +
Asynchronous I/O in Rust is still very much in its infancy. *Async/await*,
13 +
Rust's solution to the problem, was recently stabilized and so when came
14 +
the time to implement some peer-to-peer networking code, I reached for the shiny new
15 +
feature. To my dismay, it created more problems than it solved. Indeed, I quickly
16 +
regretted going down that path and searched for alternatives. All I was
17 +
looking for was an easy way to handle between fifty and a hundred TCP connections
18 +
(`net::TcpStream`) efficiently to implement the reactor for [nakamoto][], a
19 +
Bitcoin client I've been working on.
20 +
21 +
The crux of the problem with *async/await* is that it is incompatible with the
22 +
standard library traits, such as `Read` and `Write`. Partly, this is due to the
23 +
design decisions behind Rust's async implementation: the choice of cooperative
24 +
multitasking instead of preemptive multitasking, for instance.
25 +
Languages built on the latter don't suffer from this scission. Good examples
26 +
include Haskell and Erlang, which don't have a language-level concept of "async"
27 +
-- the implementation is transparent to the user.
28 +
29 +
As it stands, the *async/await* system in Rust comes with an incompatible set of
30 +
traits: `AsyncRead` and `AsyncWrite`, which don't play nicely with the standard
31 +
library. Channels, for example the amazing *crossbeam-channel* don't work in
32 +
asynchronous code -- you need an *async* variant. Alternatively, most of the
33 +
runtimes that effectively provide replacement types for channels, files and
34 +
sockets, such as *async-std* and *tokio*, have a large dependency footprint,
35 +
inherent in the complexity of the problem. For example, [smol][], one of the
36 +
"small" runtimes still consists of about 24 crates, including its transitive
37 +
dependencies, as of today.
38 +
39 +
    $ cargo tree -p smol -e no-dev --prefix none --no-dedupe | sort | uniq | wc -l
40 +
    24
41 +
42 +
Async/await may be a "zero-cost abstraction", but it certainly has a cost in
43 +
usability, maintenance and complexity. I think it still has a long way to go
44 +
before the benefits outweigh the costs.
45 +
46 +
So what are the alternatives? Well, for parallelism, we have threads. Threads
47 +
are *great* in Rust.  However, for peer-to-peer networking, threads can get a little
48 +
unwieldy. For my use case, the ability to handle between a dozen and a few
49 +
hundred open connections is all I need.  And I believe this is true for *most*
50 +
people, especially in the peer-to-peer space. This requirement  places us squarely
51 +
within the territory of the venerable `poll(2)` system call, which is available on
52 +
almost all platforms nowadays.[^1]
53 +
54 +
[*Popol*][code] is designed as a minimal ergonomic wrapper around `poll`,
55 +
built for use cases such as peer-to-peer networking, where you typically have
56 +
no more than a few hundred concurrent connections. It's meant to be familiar
57 +
enough for those with experience using [mio][], but a little easier to use, and
58 +
a lot smaller.[^2]
59 +
60 +
```rust
61 +
// Create a registry to hold I/O sources.
62 +
let mut sources = popol::Sources::with_capacity(1);
63 +
// Create an events buffer to hold readiness events.
64 +
let mut events = popol::Events::with_capacity(1);
65 +
66 +
// Register the program's standard input as a source of "read" readiness events.
67 +
// The first parameter is the key we want to associate with the source. Since
68 +
// we only have one source in this example, we just pass in the unit type.
69 +
sources.register((), &io::stdin(), popol::interest::READ);
70 +
71 +
// Wait on our event sources for at most 6 seconds. If an event source is
72 +
// ready before then, process its events. Otherwise, timeout.
73 +
match sources.wait_timeout(&mut events, Duration::from_secs(6)) {
74 +
    Ok(()) => {}
75 +
    Err(err) if err.kind() == io::ErrorKind::TimedOut => process::exit(1),
76 +
    Err(err) => return Err(err),
77 +
}
78 +
79 +
// Iterate over source events. Since we only have one source
80 +
// registered, this will only iterate once.
81 +
for ((), event) in events.iter() {
82 +
    // The standard input has data ready to be read.
83 +
    if event.readable || event.hangup {
84 +
        let mut buf = [0; 1024];
85 +
86 +
        // Read what we can from standard input and echo it.
87 +
        match io::stdin().read(&mut buf[..]) {
88 +
            Ok(n) => io::stdout().write_all(&buf[..n])?,
89 +
            Err(err) => panic!(err),
90 +
        }
91 +
    }
92 +
}
93 +
```
94 +
95 +
By building on `poll`, we have the following benefits:
96 +
97 +
* Much smaller implementation than even the smallest *async/await* runtimes.[^3]
98 +
* All the standard library traits just work (`io::Read`, `io::Write`, etc.)
99 +
* All the standard library sockets work, (`net:::TcpStream`, `net::UdpStream`, etc.)
100 +
* No "runtime" keeps the code much easier to reason about.
101 +
102 +
Some people ask why I didn't build on `epoll`[^4]. There's a couple of reasons:
103 +
104 +
1. `epoll` is more complex than `poll` and thus requires us to write more code.
105 +
2. `epoll` isn't portable; it only works on Linux.
106 +
3. `epoll` requires a system call to add, remove or modify the list of sources
107 +
   to poll. This introduces new failure modes.
108 +
4. Even though `epoll` should be able to handle more connections, `poll` is
109 +
   *plenty enough* for all the use-cases I'm interested in.
110 +
111 +
How about *mio*? It solves the first two problems, one might say. But
112 +
there are a couple of things that irked me about *mio*. In particular:
113 +
114 +
* *mio* comes bundled with its own socket types, which you are supposed to use
115 +
  instead of the ones provided by the standard library.
116 +
* *mio* identifies event sources by a `Token` type, which is basically a wrapper
117 +
  around a `u64`. This is leaked from the underlying `epoll` implementation,
118 +
  and is less than ideal.
119 +
120 +
Compared to *mio*, *popol*:
121 +
122 +
* Is a lot smaller (about 10% of the size).
123 +
* Allows you to use any type implementing `Eq` and `Clone` to identify event
124 +
  sources. It's not limited to `u64`.
125 +
* Supports standard library sockets (and other file types) transparently.
126 +
* Supports multiple *wakers* per wait call.
127 +
* Has only a single API call that can fail, since it's built on `poll`.
128 +
129 +
Some of the above is exemplified by popol's `Sources::register` function below:
130 +
131 +
```rust
132 +
impl<K: Eq + Clone> Sources<K> {
133 +
    /// Register a new source with the given key, and wait for the specified
134 +
    /// events.
135 +
    pub fn register(&mut self, key: K, source: &impl AsRawFd, events: Interest);
136 +
}
137 +
```
138 +
139 +
It goes without saying that *popol* is also a lot less mature than *mio*,
140 +
so use it at your own risk! *Popol* also doesn't currently support Windows,
141 +
though this is planned.
142 +
143 +
---
144 +
145 +
Finally, we can look at an example of a TCP server which accepts incoming
146 +
connections and registers them with *popol*. This example showcases the
147 +
use of more complex types for `K`:
148 +
149 +
```rust
150 +
use std::{io, net};
151 +
use popol;
152 +
153 +
/// The identifier we'll use with `popol` to figure out the source of an event.
154 +
/// The `K` in `Sources<K>`.
155 +
#[derive(Eq, PartialEq, Clone)]
156 +
enum Source {
157 +
    /// An event from a connected peer.
158 +
    Peer(net::SocketAddr),
159 +
    /// An event on the listening socket. Most probably a new peer connection.
160 +
    Listener,
161 +
}
162 +
163 +
let listener = net::TcpListener::bind("0.0.0.0:8888")?;
164 +
let mut sources = popol::Sources::new();
165 +
let mut events = popol::Events::new();
166 +
167 +
// Register the listener socket, using the corresponding identifier.
168 +
sources.register(Source::Listener, &listener, popol::interest::READ);
169 +
170 +
// It's important to set the socket in non-blocking mode. This allows
171 +
// us to know when to stop accepting connections.
172 +
listener.set_nonblocking(true)?;
173 +
174 +
loop {
175 +
    // Wait for something to happen on our sources.
176 +
    sources.wait(&mut events)?;
177 +
178 +
    for (key, event) in events.iter() {
179 +
        match key {
180 +
            Source::Listener => loop {
181 +
                // Accept as many connections as we can.
182 +
                let (conn, addr) = match listener.accept() {
183 +
                    Ok((conn, addr)) => (conn, addr),
184 +
185 +
                    Err(e) if e.kind() == io::ErrorKind::WouldBlock => break,
186 +
                    Err(e) => return Err(e),
187 +
                };
188 +
                // Register the new peer using the `Peer` variant of `Source`.
189 +
                sources.register(
190 +
                    Source::Peer(addr),
191 +
                    &conn,
192 +
                    popol::interest::ALL
193 +
                );
194 +
            }
195 +
            Source::Peer(addr) if event.readable => {
196 +
                println!("{} has data to be read", addr);
197 +
            }
198 +
            Source::Peer(addr) if event.writable => {
199 +
                println!("{} is ready to be written", addr);
200 +
            }
201 +
        }
202 +
    }
203 +
}
204 +
```
205 +
206 +
---
207 +
208 +
*Popol* is available on [crates.io][crate]. The source code is hosted on
209 +
[GitHub][code]. Discuss on [reddit][].
210 +
211 +
[^1]: Even Windows has an implementation of poll, called `WSAPoll`.
212 +
[^2]: The name "popol" comes from [Popol Vuh](https://en.wikipedia.org/wiki/Popol_Vuh).
213 +
[^3]: *Popol* is currently around 400 lines of code, excluding tests. The only dependency is the *libc* crate.
214 +
[^4]: <https://en.wikipedia.org/wiki/Epoll>
215 +
216 +
[nakamoto]: https://github.com/cloudhead/nakamoto
217 +
[smol]: https://crates.io/crates/smol
218 +
[mio]: https://crates.io/crates/mio
219 +
[code]: https://github.com/cloudhead/popol
220 +
[crate]: https://crates.io/crates/popol
221 +
[docs]: https://docs.rs/popol
222 +
[reddit]: https://www.reddit.com/r/rust/comments/htyyf3/popol_minimal_nonblocking_io_with_rust/
_posts/2021-01-22-nakamoto.md added +187 -0
1 +
---
2 +
title: "Nakamoto: a new privacy-preserving bitcoin light-client"
3 +
headline: "Nakamoto: a new privacy-preserving bitcoin light-client"
4 +
teaser: "bitcoin light-client"
5 +
date: "2021.01.22"
6 +
type: note
7 +
slug: nakamoto
8 +
crate: nakamoto
9 +
layout: default
10 +
---
11 +
12 +
With Bitcoin Core [0.21][bitcoin] out the door offering full support for *Client
13 +
Side Filtering*,[^1] it seems like a good time to talk about the project I've
14 +
been spending my weekends on for the last six months.
15 +
16 +
*Nakamoto* is a Bitcoin light-client[^2] implementation in [Rust][rust], with
17 +
a focus on low resource utilization, modularity and privacy.
18 +
19 +
The vision for the project is to build a set of high quality and easily
20 +
embeddable libraries targeting light client wallet functionality, that can
21 +
run on any platform -- be it mobile or desktop -- while preserving user privacy.
22 +
23 +
When we take a look at the greater ecosystem, we see that it isn't uncommon for
24 +
users to end up trusting third parties when transacting. In the majority of
25 +
cases - Ethereum being a good example - it is due to poor light-client support.
26 +
This poses a considerable security and privacy risk that is counter to the
27 +
nature and *raison d'รชtre* of blockchains, which are designed as peer-to-peer
28 +
systems.  Light-clients are necessary for the average user to securely
29 +
interface with the network in a peer-to-peer manner, and hence the availability
30 +
of high quality implementations is paramount.
31 +
32 +
### Requirements
33 +
34 +
One of the key long-term plans for Nakamoto is to offer a C FFI, enabling
35 +
interoperation with other languages and opening up the possibility to run on
36 +
mobile devices. This could have been satisfied by writing Nakamoto in C, though
37 +
one of the project's primary goals is to offer the most secure implementation
38 +
possible.  Rust was chosen over C.
39 +
40 +
Another important consideration for mobile is *power* and *resource
41 +
efficiency*. This rules out implementations in managed languages with heavy
42 +
runtimes such as *bitcoinJ* and nudges us towards simpler designs.
43 +
44 +
Finally, user privacy is becoming increasingly important. Privacy influences
45 +
the choice of client protocols and forces us to think about how the client
46 +
interacts with the nodes on the network, and what information it reveals.
47 +
48 +
### Landscape
49 +
50 +
There are very few options out there that are satisfactory. It's fairly common
51 +
for mobile Bitcoin wallets to have a custom light-client implementation, most
52 +
of which are still based on [BIP 37][bip37].  Another common avenue is to use
53 +
something like [Electrum][electrum], which relies on non-standard Bitcoin
54 +
nodes. [Unfortunately neither of those are privacy-preserving][privacy].
55 +
56 +
Nakamoto talks directly to the Bitcoin P2P network, and thus doesn't require
57 +
special servers to connect to. For simple payment verification (SPV), the new
58 +
BIP 157 client-side block filtering protocol offers improved privacy.
59 +
60 +
Additionally, Nakamoto's client architecture enables it to run on a single
61 +
thread, minimizing CPU and memory footprint. This allows the library to
62 +
be easily embedded in any environment.
63 +
64 +
### Modularity
65 +
66 +
A lot of work has been put into building an I/O-free [protocol core][protocol].
67 +
This is the key to Nakamoto's modularity and also enables some very powerful
68 +
testing strategies that only work when the code being tested is
69 +
[deterministic][], i.e. all inputs can be controlled for.
70 +
71 +
Having a clean separation between the networking code and protocol
72 +
state-machine [reduces the possible failure states][sans-io], and
73 +
allows the networking code to be swapped based on the user's needs. If
74 +
performance is more important than efficiency or code complexity,
75 +
the included *poll*-based network [reactor][], which uses [popol](/popol) under
76 +
the hood can be swapped for one based on a thread-pool or a
77 +
thread-per-connection model.
78 +
79 +
![](/assets/nakamoto/nakamoto-architecture.svg "Architecture Diagram")
80 +
81 +
### On Dependencies
82 +
83 +
When developing software that handles money, the security of the software is
84 +
critical. Targetted attacks on dependencies are perhaps one of the most obvious
85 +
attack vectors. One of the most important steps to prevent that is writing
86 +
software that is easy to audit. Nakamoto follows two simple principles:
87 +
88 +
1. Less code equals less bugs and less to audit
89 +
2. Less dependencies equals less moving parts and potential security risks
90 +
91 +
The second point is particularly important because dependencies are
92 +
much harder to track than code that is internal to the library. Third-party
93 +
code often ends up changing hands, and though one may trust the original
94 +
maintainer, one may not trust [whoever comes next][attacks].
95 +
96 +
These have been guiding principles from the start, and thus I've been very
97 +
economical with Nakamoto's dependency budget. Compared to other projects with a
98 +
similar scope, Nakamoto does away with a fraction of the (transitive)
99 +
dependencies.
100 +
101 +
### Closing thoughts
102 +
103 +
The project is still in its early days, though the core functionality has been
104 +
implemented and the client is able to securely synchronize the blockchain and
105 +
use compact block filters for [wallet functionality][wallet] and payment
106 +
verification.
107 +
108 +
There is a lot of work left to do before Nakamoto is ready to power the next
109 +
generation of client software on the Bitcoin and [Lightning][ln] network, so
110 +
if you're interested in peer-to-peer protocols, Rust or Bitcoin, contributions
111 +
are very welcome. If you are developing wallet software and are interested
112 +
in using Nakamoto, [get in touch][email], I'd love to help.
113 +
114 +
```rust
115 +
//! A trivial example of a client that connects to the Bitcoin
116 +
//! testnet and syncs the chain.
117 +
use std::{net, thread};
118 +
119 +
use nakamoto::client::{Client, Config, Network};
120 +
use nakamoto::client::error::Error;
121 +
use nakamoto::client::handle::Handle as _;
122 +
123 +
/// The network reactor we're going to use. This one ships with Nakamoto,
124 +
/// as part of the `nakamoto-net-poll` crate. It's a single-threaded
125 +
/// reactor using non-blocking sockets and `poll(2)`.
126 +
type Reactor = nakamoto::net::poll::Reactor<net::TcpStream>;
127 +
128 +
/// Run the light-client.
129 +
fn main() -> Result<(), Error> {
130 +
    let cfg = Config {
131 +
        network: Network::Testnet,
132 +
        ..Config::default()
133 +
    };
134 +
    // Create a client using the above network reactor.
135 +
    let client = Client::<Reactor>::new(cfg)?;
136 +
    let handle = client.handle();
137 +
138 +
    // Run the client on a different thread, to not block the main thread.
139 +
    thread::spawn(|| client.run().unwrap());
140 +
141 +
    // Wait for the client to be in sync with the blockchain.
142 +
    handle.wait_for_ready()?;
143 +
144 +
    // The client is in sync. Ask it to shutdown.
145 +
    handle.shutdown()?;
146 +
147 +
    Ok(())
148 +
}
149 +
```
150 +
151 +
---
152 +
153 +
*Nakamoto* is available as a ๐Ÿ“ฆ [crate][crate].
154 +
155 +
* ๐Ÿ“œ Browse the [source code][code].
156 +
* ๐Ÿ“– Read the [documentation][docs].
157 +
* ๐Ÿ‘€ Check out the [example][wallet] wallet implementation.
158 +
159 +
---
160 +
161 +
If Nakamoto's vision is appealing, please consider ๐Ÿ’š [funding](/donate)
162 +
the project or contributing to the [code][].
163 +
164 +
[crate]: https://crates.io/crates/nakamoto
165 +
[docs]: https://docs.rs/nakamoto
166 +
[code]: https://github.com/cloudhead/nakamoto
167 +
[rust]: https://www.rust-lang.org/
168 +
[bitcoin]: https://bitcoin.org/en/release/v0.21.0
169 +
[protocol]: https://github.com/cloudhead/nakamoto/blob/01eaa27c8ef88e16535cf2adb89f47886ca0a9f6/p2p/src/protocol.rs
170 +
[bip37]: https://github.com/bitcoin/bips/blob/master/bip-0037.mediawiki
171 +
[reactor]: https://github.com/cloudhead/nakamoto/blob/01eaa27c8ef88e16535cf2adb89f47886ca0a9f6/net/poll/src/reactor.rs
172 +
[attacks]: https://blog.npmjs.org/post/180565383195/details-about-the-event-stream-incident
173 +
[sans-io]: https://sans-io.readthedocs.io/how-to-sans-io.html
174 +
[deterministic]: https://medium.com/@tylerneely/reliable-systems-series-model-based-property-testing-e89a433b360
175 +
[ln]: https://en.wikipedia.org/wiki/Lightning_Network
176 +
[privacy]: https://en.bitcoin.it/wiki/BIP37_privacy_problems
177 +
[electrum]: https://electrum.org/
178 +
[wallet]: https://github.com/cloudhead/nakamoto/blob/cdf0737e3cda2ae29b0862962a649f9a6d02ebeb/wallet/src/lib.rs
179 +
[email]: mailto:nakamoto@cloudhead.io
180 +
181 +
[^1]: *Client Side Block Filtering*, or BIP 157 is the new light-client protocol:
182 +
    <https://github.com/bitcoin/bips/blob/master/bip-0157.mediawiki>
183 +
184 +
[^2]: A light-client, or SPV node is a client able to operate securely
185 +
    without access to the full blockchain. Typically, light-clients use
186 +
    only block headers to verify the chain, and thus offer a different
187 +
    security guarantee to full nodes.
_posts/2024-01-31-segfault-the-mother-of-invention.md added +130 -0
1 +
---
2 +
title: "Segfault, the mother of invention"
3 +
date: 2024-01-31
4 +
type: post
5 +
layout: default
6 +
---
7 +
8 +
There is no greater joy for a programmer than to build his own tools. A few
9 +
weeks ago, I embarked on a little coding experiment: I wanted to see if it was
10 +
possible to build a 2D/3D software suite with an embedded scripting language
11 +
more or less from scratch.
12 +
13 +
This all started when I wanted to try Blender, a popular open source 3D tool.
14 +
15 +
    $ blender
16 +
    Writing: /tmp/blender.crash.txt
17 +
    zsh: segmentation fault (core dumped)  blender
18 +
19 +
The last time I tried running Blender, which was some five years ago, it worked
20 +
fine. Turns out this is likely due to an LLVM dynamic library incompatibility,
21 +
as stated by someone on the Arch Linux bug tracker:
22 +
23 +
> So, OSL is linking LLVM15, and the driver is linking LLVM16. Something is
24 +
> breaking because two different versions of LLVM are being linked concurrently
25 +
> and calling into each other, presumably because symbol versioning is disabled
26 +
> or broken.
27 +
28 +
If you're wondering why Blender depends on LLVM, one of the most sophisticated
29 +
compiler toolchains ever created, with millions of lines of code, your guess is
30 +
as good as mine. But anyway, if your reaction to the above is not *wtf?*, you
31 +
have already given up on software.
32 +
33 +
Here's a useful principle:
34 +
35 +
> **Principle**: To assess the complexity of a piece of software, its entire
36 +
> dependency tree must be considered.
37 +
38 +
Naturally, dynamic linking makes a software's dependency tree a runtime
39 +
concern, ie. mostly unpredictable. Shucks.
40 +
41 +
I tend to have very limited patience dealing with code I didn't write,
42 +
especially when it doesn't work. To avoid that, I try to keep dependencies to a
43 +
bare minimum, and pick ones that mostly get out of the way. This has worked
44 +
well for me, probably because bugs are typically corelated with code size (yes,
45 +
this includes transitive dependencies). In other words, less code equals less
46 +
things that can go wrong. Every dependency you add has a benefit and a cost,
47 +
and we tend to overestimate the benefit and underestimate the cost. Picking
48 +
dependencies is all about this cost to value ratio.
49 +
50 +
    Ratio = Benefit (time saved) รท Cost (dependency burden)
51 +
52 +
Naturally, when I wasn't able to run Blender, I asked myself what it would take
53 +
to write my own. I've dabbled in graphics programming before and wrote a [pixel
54 +
editor][rx] a few years ago, what would it take to add a third dimension? This
55 +
is the question I wanted to answer, and the direction I wanted to explore. I
56 +
like to constrain myself to working mostly with the standard library of a
57 +
language and a very minimal set of dependencies; mostly for the code I don't
58 +
want to write. Things like C bindings and platform-specific code are usually in
59 +
that category.
60 +
61 +
Hence, when thinking about new projects, the question of dependencies is the
62 +
first one I ask, and after doing a bunch of research, I landed on these three,
63 +
somewhat unsurprisingly, for a 2D/3D graphics application:
64 +
65 +
    [dependencies]
66 +
    glfw = { version = "0.54.0" }
67 +
    glow = { version = "0.13.0" }
68 +
    log = { version = "0.4.20" }
69 +
70 +
These are the things I don't want to have to reinvent.
71 +
72 +
[glfw][glfw] is a rock solid OpenGL context creation and input handling library
73 +
I've been using without issue for over a decade. Compared to SDL, it's a tenth
74 +
of the size, while doing everything I need. It contains lots of
75 +
platform-specific code I'm not willing nor able to write.
76 +
77 +
I chose `glfw` over [winit][winit] because I'm not interested in running
78 +
software on mobile or web, and winit's API is a compromise made to support
79 +
these platforms. For instance, `winit` doesn't give you control over your event
80 +
loop. This is a deal breaker for me. It's common to lose power and flexibility
81 +
when using APIs that try to abstract over vastly different domains.
82 +
83 +
[glow][glow] is a set of GL bindings for Rust, optimized for portability. It's
84 +
a great library that doesn't try to do too much:
85 +
86 +
> GL on Whatever: a set of bindings to run GL anywhere (Open GL, OpenGL ES, and
87 +
> WebGL) and avoid target-specific code.
88 +
89 +
The `log` crate doesn't need much of an introduction. Since I avoid async rust,
90 +
there is no need for anything more advanced in terms of logging.
91 +
92 +
I chose to build directly on OpenGL 3.3, as opposed to using something like
93 +
[wgpu][wgpu] because OpenGL is already the hardware abstraction layer that I
94 +
need to write cross-platform code, and it's not going anywhere. All I would add
95 +
is a thin wrapper around it to make it more palatable.
96 +
97 +
What about the GUI? There are dozens of libraries in Rust to choose from, but
98 +
they either depend on something like GTK, have a massive dependency tree, or
99 +
are coupled with their graphics backend. I'm a big fan of [Raph Levien's][raph]
100 +
work, such as [druid][druid] and [xilem][xilem], but these are large, and still
101 +
unfinished projects that I'd rather not take on as dependencies. However, I
102 +
think a lot of [good ideas][archi] can be borrowed from them.
103 +
104 +
There are many other promising UI library projects for Rust, but none tick
105 +
all of the boxes for me, so I decided to build my own on top of `glow`.
106 +
The goal, as usual, is to write something maintainable and as close to
107 +
dependency-free as possible. If it ends up being useful to others, great,
108 +
but it will be primarily built based on my own requirements for this project.
109 +
110 +
So far, a few weeks into the project, I have a thin, ergonomic wrapper around
111 +
`glow` inspired by [sokol-gfx][sokol], a basic 3D renderer, a scripting
112 +
language, and the beginnings of a small [retained mode][retained] GUI library.
113 +
114 +
I'm discovering a lot of interesting problems along the way and having to make
115 +
all sorts of trade-offs and decisions to keep the code size small and
116 +
manageable for one person. So far, it feels like the goal of having a usable
117 +
2D/3D software stack for my own needs is achievable. I'll be posting updates
118 +
here as I make progress.
119 +
120 +
[raph]: https://raphlinus.github.io/
121 +
[archi]: https://raphlinus.github.io/rust/gui/2022/05/07/ui-architecture.html
122 +
[xilem]: https://xilem.dev/
123 +
[rx]: https://rx.cloudhead.io
124 +
[glfw]: https://www.glfw.org/
125 +
[glow]: https://crates.io/crates/glow/
126 +
[winit]: https://crates.io/crates/winit
127 +
[wgpu]: https://wgpu.rs
128 +
[druid]: https://docs.rs/druid/latest/druid/
129 +
[sokol]: https://floooh.github.io/2017/07/29/sokol-gfx-tour.html
130 +
[retained]: https://en.wikipedia.org/wiki/Retained_mode
nakamoto/nakamoto-architecture.svg → assets/nakamoto/nakamoto-architecture.svg renamed +0 -0
build deleted +0 -103
1 -
#!/usr/bin/env ruby
2 -
3 -
require "pathname"
4 -
require "erb"
5 -
require "commonmarker"
6 -
require "date"
7 -
8 -
PAGE_TEMPLATE = File.read("page.html.erb")
9 -
INDEX_PATH    = "index.html"
10 -
11 -
$articles = []
12 -
13 -
class Page
14 -
  def initialize(attrs)
15 -
    @title = attrs[:title]
16 -
    @name = attrs[:name]
17 -
    @year = Date.today.year
18 -
    @articles = $articles
19 -
  end
20 -
21 -
  def to_html
22 -
    ERB.new(PAGE_TEMPLATE, trim_mode: "<>-").result(binding).gsub(/ +$/, '')
23 -
  end
24 -
25 -
  def styles?
26 -
    File.exists? Pathname::new(@name).join("style.css")
27 -
  end
28 -
29 -
  def write!
30 -
    dir = Pathname::new(@name)
31 -
32 -
    Dir::mkdir(@name) unless File.exists? dir
33 -
    File::new(dir.join("index.html"), "w").write(self.to_html)
34 -
  end
35 -
36 -
  def method_missing(m, *args)
37 -
    self.instance_variable_get("@#{m}")
38 -
  end
39 -
end
40 -
41 -
class Article < Page
42 -
  def initialize(input, output)
43 -
    @output = output
44 -
45 -
    abort "#{input} doesn't exist" unless File::exists? input
46 -
    puts "* #{input}.."
47 -
48 -
    name = File::basename(input, File::extname(input))
49 -
50 -
    @name = name
51 -
    @articles = $articles
52 -
    @year = Date.today.year
53 -
54 -
    header, body = File.read(input).split(/\n\n/, 2)
55 -
    result = header && header.strip.match(/^<!--(.*)-->$/m)
56 -
    meta = result[1] || abort("article #{input} is not well formed")
57 -
58 -
    @body = CommonMarker.render_doc(body, [:FOOTNOTES, :SMART, :UNSAFE]).to_html(:UNSAFE)
59 -
60 -
    attrs = {}
61 -
    meta.strip.each_line do |l|
62 -
      matches = l.match(/^(\w+)\s*:\s*(.*)$/)
63 -
      attrs[matches[1].downcase.to_sym] = matches[2].chomp
64 -
    end
65 -
66 -
    @date = attrs[:date]
67 -
    @title = attrs[:title]
68 -
    @teaser = attrs[:teaser]
69 -
    @headline = attrs[:headline]
70 -
  end
71 -
72 -
  def method_missing(m, *args)
73 -
    self.instance_variable_get("@#{m}")
74 -
  end
75 -
end
76 -
77 -
def build!(base, out)
78 -
  base = Pathname::new(base)
79 -
  out = Pathname::new(out)
80 -
81 -
  Dir[base.join("**/**.md")].each do |path|
82 -
    path = Pathname::new(path)
83 -
84 -
    dir = path.dirname.relative_path_from(base)
85 -
    file = path.basename
86 -
87 -
    out_dir = out.join(dir)
88 -
    out_dir.mkpath()
89 -
    out_file = out_dir.join(file.sub_ext(".html"))
90 -
91 -
    $articles << Article::new(path, out_file)
92 -
  end
93 -
94 -
  $articles.each(&:write!)
95 -
96 -
  # Index page.
97 -
  Page::new(title: "cloudhead's homepage", name: ".").write!
98 -
end
99 -
100 -
base = ARGV.shift
101 -
out = ARGV.shift
102 -
103 -
build!(base, out)
code/empty.css deleted +0 -0
code/index.html deleted +0 -141
1 -
<!doctype html>
2 -
<html lang="en">
3 -
  <head>
4 -
    <link rel="stylesheet" type="text/css" href="/css/base.css">
5 -
    <title>cloudhead.io</title>
6 -
    <style>
7 -
      body {
8 -
        padding: 10px;
9 -
        font-size: 14px;
10 -
      }
11 -
12 -
      h1 {
13 -
        font-size: 16px;
14 -
      }
15 -
16 -
      h2 {
17 -
        font-size: 20px;
18 -
        text-decoration: underline;
19 -
      }
20 -
21 -
      h3 {
22 -
        font-size: 20px;
23 -
      }
24 -
25 -
      section > header {
26 -
        margin-bottom: 10px;
27 -
      }
28 -
29 -
      section > header > h2 {
30 -
        font-size: 20px;
31 -
      }
32 -
33 -
      li {
34 -
        list-style-position: inside;
35 -
        margin: 10px 0;
36 -
      }
37 -
38 -
      section p {
39 -
        margin: 15px 0 30px 0;
40 -
      }
41 -
42 -
      a:hover {
43 -
        text-decoration: underline;
44 -
      }
45 -
    </style>
46 -
  </head>
47 -
  <body>
48 -
    <header>
49 -
      <h1><a href="/">cloudhead.io</a>/code</h1>
50 -
    </header>
51 -
    <section>
52 -
      <br /> <br />
53 -
      <ul>
54 -
        <li>
55 -
          <a target="_blank" href="https://github.com/cloudhead/dotfiles">dotfiles</a>
56 -
          - my dotfiles.
57 -
        </li>
58 -
        <li>
59 -
          <a target="_blank" href="https://github.com/cloudhead/shady.vim">shady.vim</a>
60 -
          - my shady vim colorscheme for late-night hacking.
61 -
        </li>
62 -
        <li>
63 -
          <a target="_blank" href="https://github.com/cloudhead/neovim-ghcid">neovim-ghcid</a>
64 -
          - neovim + haskell = &lt;3.
65 -
        </li>
66 -
        <li>
67 -
          <a target="_blank" href="https://github.com/cloudhead/neovim-fuzzy">neovim-fuzzy</a>
68 -
          - a fuzzy file finder for neovim.
69 -
        </li>
70 -
        <li>
71 -
          <a target="_blank" href="https://github.com/cloudhead/s3web">s3web</a>
72 -
          - scripts I use to publish websites on Amazon S3.
73 -
        </li>
74 -
        <li>
75 -
          <a href="https://github.com/cloudhead/monsv">monsv</a>
76 -
          - a service runner inspired by <a href="http://smarden.org/runit/">runit</a>.
77 -
        </li>
78 -
        <li>
79 -
          <a target="_blank" href="https://github.com/cloudhead/spinsv">spinsv</a>
80 -
          - monsv in Haskell.
81 -
        </li>
82 -
        <li>
83 -
          <a target="_blank" href="https://github.com/cloudhead/http-console">http-console</a>
84 -
          - an intuitive HTTP REPL.
85 -
        </li>
86 -
        <li>
87 -
          <a target="_blank" href="https://github.com/cloudhead/hijs">hijs</a>
88 -
          - a simple &amp; fast javascript syntax highlighter for the browser.
89 -
        </li>
90 -
        <li>
91 -
          <a target="_blank" href="https://github.com/readmill/httputil">httputil</a>
92 -
          - an http utility library for go.
93 -
        </li>
94 -
        <li>
95 -
          <a target="_blank" href="https://github.com/readmill/metrics">metrics</a>
96 -
          - a metrics library for go.
97 -
        </li>
98 -
        <li>
99 -
          <a target="_blank" href="https://github.com/less/less.js">LESS</a>
100 -
          - an attempt at building a better css.
101 -
        </li>
102 -
        <li>
103 -
          <a target="_blank" href="https://github.com/cloudhead/node-static">node-static</a>
104 -
          - rfc 2616 compliant HTTP static file server for node.
105 -
        </li>
106 -
        <li>
107 -
          <a target="_blank" href="https://github.com/cloudhead/gogol">gogol</a>
108 -
          - a bitmap drawing &amp; animation library for go.
109 -
        </li>
110 -
        <li>
111 -
          <a target="_blank" href="https://github.com/cloudhead/pixelog">pixelog</a>
112 -
          - a simple pixel tracking server for measuring web traffic.
113 -
        </li>
114 -
        <li>
115 -
          <a target="_blank" href="https://github.com/cloudhead/arbre">arbre</a>
116 -
          - a dynamic functional language runtime and compiler experiment.
117 -
        </li>
118 -
        <li>
119 -
          <a target="_blank" href="https://github.com/flatiron/vows">vows</a><strong></strong>
120 -
          - a testing library for node.js.
121 -
        </li>
122 -
        <li>
123 -
          <a target="_blank" href="https://github.com/cloudhead/nimbus">nimbus</a>
124 -
          - a document database as a library for node.js, based on an append-only file model.
125 -
        </li>
126 -
        <li>
127 -
          <a target="_blank" href="https://github.com/flatiron/cradle">cradle</a>
128 -
          - a high-level javascript client for <a href="http://couchdb.apache.org/">couchdb</a>.
129 -
        </li>
130 -
        <li>
131 -
          <a target="_blank" href="https://github.com/cloudhead/spell-correct">spell-correct</a>
132 -
          - a spelling corrector in as few lines of code as possible.
133 -
        </li>
134 -
      </ul>
135 -
    </section>
136 -
    <footer>
137 -
      <a href="mailto:hello@cloudhead.io">hello@cloudhead.io</a>
138 -
      <p>&copy; 2010-2013 Alexis Sellier</p>
139 -
    </footer>
140 -
  </body>
141 -
</html>
css/base.css +251 -221
1 1
@charset "utf-8";
2 2
3 3
:root {
4 -
	--highlight-color: aquamarine;
5 -
	--subtle-color: #2c4146;
6 -
	--scrollbar-width: 0.5rem;
4 +
  --foreground-color: #eee;
5 +
  --dim-color: #ccc;
6 +
  --highlight-color: aquamarine;
7 +
  --subtle-color: #2c4146;
8 +
  --scrollbar-width: 0.5rem;
7 9
}
8 10
9 11
* {
10 -
	margin: 0;
11 -
	padding: 0;
12 -
	box-sizing: border-box;
12 +
  margin: 0;
13 +
  padding: 0;
14 +
  box-sizing: border-box;
13 15
}
14 16
15 17
html {
16 -
	font-size: 100%;
18 +
  font-size: 100%;
17 19
}
18 20
19 21
body {
20 -
	color: #eee;
21 -
	background-color: #05050c;
22 -
	max-width: 912px;
23 -
	margin: 0 auto;
24 -
	padding: 120px 0 180px 0;
25 -
	line-height: 1.4em;
26 -
	font-size: 125%;
27 -
	scrollbar-width: thin;
28 -
	scrollbar-height: thin;
29 -
	scrollbar-color: var(--highlight-color) transparent;
30 -
	-webkit-font-smoothing: antialiased;
31 -
	-moz-osx-font-smoothing: greyscale;
22 +
  color: var(--foreground-color);
23 +
  background-color: #05050c;
24 +
  max-width: 912px;
25 +
  margin: 0 auto;
26 +
  padding: 4rem 0 4rem 0;
27 +
  line-height: 1.4em;
28 +
  font-size: 125%;
29 +
  scrollbar-width: thin;
30 +
  scrollbar-height: thin;
31 +
  scrollbar-color: var(--highlight-color) transparent;
32 +
  -webkit-font-smoothing: antialiased;
33 +
  -moz-osx-font-smoothing: greyscale;
32 34
}
33 35
34 -
body, code, pre, p, kbd, h1, h2, h3, h4, h5, input {
35 -
	font-family: "Consolas", "Inconsolata", monospace, "Emoji";
36 +
body, code, pre, p, kbd, h1, h2, h3, h4, h5, input, strong, em {
37 +
  font-family: "Consolas", "Inconsolata", monospace, "Emoji";
36 38
}
37 39
38 40
/* Chrome/Edge/Safari scrollbar */
39 41
*::-webkit-scrollbar {
40 -
	width: var(--scrollbar-width);
41 -
	height: var(--scrollbar-width);
42 +
  width: var(--scrollbar-width);
43 +
  height: var(--scrollbar-width);
42 44
}
43 45
*::-webkit-scrollbar-track {
44 -
	background: transparent;
46 +
  background: transparent;
45 47
}
46 48
*::-webkit-scrollbar-thumb {
47 -
	background: transparent;
48 -
	border-radius: 4px;
49 +
  background: transparent;
50 +
  border-radius: 4px;
49 51
}
50 52
*::-webkit-scrollbar-corner {
51 -
	background: transparent;
53 +
  background: transparent;
52 54
}
53 55
*:hover::-webkit-scrollbar-thumb,
54 56
body::-webkit-scrollbar-thumb {
55 -
	background-color: var(--highlight-color);
57 +
  background-color: var(--highlight-color);
56 58
}
57 59
58 60
::selection {
59 -
	color: black;
60 -
	background: #ffeebb;
61 +
  color: black;
62 +
  background: #ffeebb;
61 63
}
62 64
63 65
:focus {
64 -
	outline: none;
65 -
	text-decoration: underline;
66 +
  outline: none;
67 +
  text-decoration: underline;
66 68
}
67 69
68 70
ul, li {
69 -
	display: inline-block;
71 +
  display: inline-block;
70 72
}
71 73
72 74
li {
73 -
	margin: 5px 0;
74 -
	display: block;
75 +
  margin: 5px 0;
76 +
  display: block;
77 +
}
78 +
79 +
li::marker {
80 +
  color: grey;
75 81
}
76 82
77 83
a {
78 -
	color: #ffeebb;
79 -
	text-decoration: none;
84 +
  color: var(--highlight-color);
85 +
  text-decoration: none;
80 86
}
81 87
a:hover {
82 -
	text-decoration: underline;
88 +
  text-decoration: underline;
83 89
}
84 90
85 91
strong {
86 -
	font-weight: 900;
92 +
  font-weight: 900;
87 93
}
88 94
89 95
video, img {
90 -
	outline: none;
96 +
  color: grey;
97 +
  font-style: italic;
98 +
  outline: none;
91 99
}
92 100
93 101
hr {
94 -
	border: none;
95 -
	border-top: 1px solid var(--subtle-color);
96 -
	margin: 2rem 0;
102 +
  border: none;
103 +
  border-top: 1px solid var(--subtle-color);
104 +
  margin: 2rem 0;
97 105
}
98 106
99 107
section.body {
100 -
	margin-bottom: 36rem;
108 +
  margin-bottom: 36rem;
101 109
}
102 110
103 111
footer {
104 -
	font-size: 1rem;
105 -
	color: var(--subtle-color);
112 +
  font-size: 1rem;
113 +
  color: var(--subtle-color);
106 114
}
107 115
108 116
body > footer {
109 -
	display: none;
110 -
	padding: 1rem 1rem 0 1rem;
117 +
  display: none;
118 +
  padding: 1rem 1rem 0 1rem;
111 119
}
112 120
113 121
blockquote {
114 -
  margin-left: 1rem;
122 +
  border-left: 1px solid #444;
123 +
  padding-left: 1rem;
115 124
  color: grey;
116 125
  font-style: italic;
117 126
}
118 127
119 128
/*
120 129
 * HEADERS
121 130
 */
122 131
123 132
h1, h2, h3, h4 {
124 -
	font-family: "Space Mono", "Consolas", monospace;
125 -
	font-weight: bold;
126 -
	color: var(--highlight-color);
133 +
  font-family: "Space Mono", "Consolas", monospace;
134 +
  font-weight: bold;
135 +
  color: var(--highlight-color);
127 136
}
128 137
h3, h4, h5 {
129 -
	font-weight: normal !important;
138 +
  font-weight: normal !important;
130 139
}
131 140
132 141
h1 {
133 -
	font-size: 1.5rem;
134 -
	line-height: 1.4em;
135 -
	font-weight: bold;
142 +
  font-size: 1.5rem;
143 +
  line-height: 1.4em;
144 +
  font-weight: bold;
145 +
  margin: 2.25rem 0;
136 146
}
137 147
h2 {
138 -
	font-size: 1.3rem;
139 -
	line-height: 1.4em;
140 -
	margin-top: 1.5rem;
141 -
	margin-bottom: 1.25rem;
148 +
  font-size: 1.3rem;
149 +
  line-height: 1.4em;
150 +
  margin-top: 1.5rem;
151 +
  margin-bottom: 1.25rem;
142 152
}
143 153
h3 {
144 -
	font-size: 1.25rem;
145 -
	margin-top: 2.5rem;
146 -
	margin-bottom: 1rem;
154 +
  font-size: 1.25rem;
155 +
  margin-top: 2.5rem;
156 +
  margin-bottom: 1rem;
157 +
}
158 +
159 +
header a:hover {
160 +
  text-decoration: none;
161 +
}
162 +
header .meta {
163 +
  display: flex;
164 +
  justify-content: space-between;
165 +
  align-items: center;
166 +
  flex-wrap: nowrap;
167 +
}
168 +
header .badges {
169 +
  display: flex;
170 +
  gap: 1rem;
171 +
  margin-right: 1rem;
172 +
}
173 +
header .badges a {
174 +
  line-height: 0;
147 175
}
148 176
149 177
/*
150 178
 * CODE
151 179
 */
152 180
153 181
pre {
154 -
	padding: 1rem;
155 -
	background-color: #060f11;
156 -
	border-radius: 4px;
157 -
	overflow-x: scroll !important;
182 +
  padding: 1rem;
183 +
  border-radius: 4px;
184 +
  background-color: #060f11;
185 +
  overflow-x: scroll !important;
158 186
}
159 187
pre code {
160 -
	white-space: pre;
188 +
  white-space: pre;
189 +
}
190 +
p code {
191 +
  padding: 0.125rem 0.25rem;
192 +
  background-color: #060f11;
193 +
  border-radius: 4px;
161 194
}
162 -
163 195
code {
164 -
	color: #a1fde6;
165 -
	white-space: nowrap;
166 -
	font-style: normal;
196 +
  color: #bbb;
197 +
  white-space: nowrap;
198 +
  font-style: normal;
167 199
}
168 200
code.language-rust {
169 -
	color: #ddd;
170 -
	font-style: normal;
201 +
  color: #ddd;
202 +
  font-style: normal;
171 203
}
172 204
code.language-rust .comment {
173 -
	color: #4b6267;
174 -
	font-style: italic;
205 +
  color: #4b6267;
206 +
  font-style: italic;
175 207
}
176 208
code.language-rust .op {
177 -
	color: #8b9297;
209 +
  color: #8b9297;
178 210
}
179 211
code.language-rust .keyword {
180 -
	color: #eee;
181 -
	font-weight: bold;
212 +
  color: #eee;
213 +
  font-weight: bold;
182 214
}
183 215
code.language-rust .trait {
184 -
	color: #8b9297;
185 -
	font-weight: bold;
216 +
  color: #8b9297;
217 +
  font-weight: bold;
186 218
}
187 219
code.language-rust .ref {
188 -
	color: #9bb2b7;
220 +
  color: #9bb2b7;
189 221
}
190 222
code.language-rust .macro {
191 -
	color: #eee;
192 -
	font-weight: bold;
223 +
  color: #eee;
224 +
  font-weight: bold;
193 225
}
194 226
code.language-rust .special {
195 -
	color: #eee;
196 -
	font-weight: bold;
227 +
  color: #eee;
228 +
  font-weight: bold;
197 229
}
198 230
code.language-rust .attr {
199 -
	color: #4b6267;
200 -
	font-weight: bold;
231 +
  color: #4b6267;
232 +
  font-weight: bold;
201 233
}
202 234
code.language-rust .string {
203 -
	color: #aaa;
235 +
  color: #aaa;
204 236
}
205 237
code.language-rust .number {
206 -
	color: #fff897;
207 -
}
208 -
209 -
sup.footnote-ref {
210 -
	vertical-align: baseline;
211 -
	position: relative;
212 -
	top: -0.4em;
213 -
}
214 -
215 -
a.footnote-backref {
216 -
	font-family: Helvetica, Arial, sans-serif;
238 +
  color: #fff897;
217 239
}
218 240
219 241
img[alt="crates.io"], img[alt="docs.rs"], img[alt="github.com"] {
220 -
	height: 1.5rem;
242 +
  height: 1.25rem;
221 243
}
222 244
223 245
p + p img {
224 -
	margin-top: 1.5rem;
225 -
	margin-bottom: 1.5rem;
246 +
  margin-top: 1.5rem;
247 +
  margin-bottom: 1.5rem;
226 248
}
227 249
p img[alt="diagram"] {
228 -
	width: 100%;
250 +
  width: 100%;
229 251
}
230 252
231 253
/*
232 254
 * NOTE
233 255
 */
234 256
235 257
.note p {
236 -
	margin: 1.5rem 0;
258 +
  margin: 1.5rem 0;
237 259
}
238 260
.note p:first-child {
239 -
	margin-top: 0;
261 +
  margin-top: 0;
240 262
}
241 263
.note ul {
242 -
	list-style-type: none;
264 +
  list-style-type: none;
243 265
}
244 266
.note ul, .note ol {
245 -
	margin-left: 3rem;
267 +
  margin-left: 3rem;
246 268
}
247 269
.note li {
248 -
	margin: 0;
270 +
  margin: 0;
249 271
}
250 272
.note ol {
251 -
	list-style-type: decimal;
273 +
  list-style-type: decimal;
252 274
}
253 275
.note ol li {
254 -
	display: list-item;
255 -
}
256 -
.note ol li:marker {
257 -
	color: var(--highlight-color);
276 +
  display: list-item;
258 277
}
259 278
.note ul li:before {
260 -
	content: 'โ€“';
261 -
	color: var(--highlight-color);
262 -
	position: absolute;
263 -
	margin-left: -2rem;
279 +
  content: '*';
280 +
  color: grey;
281 +
  position: absolute;
282 +
  margin-left: -2rem;
264 283
}
265 284
.note footer {
266 -
	text-align: left;
285 +
  text-align: left;
267 286
}
268 287
.note .copy {
269 -
	color: var(--subtle-color);
288 +
  color: var(--subtle-color);
270 289
}
271 290
272 291
/*
273 292
 * NAV
274 293
 */
275 294
276 295
nav {
277 -
	position: fixed;
278 -
	left: 1rem;
279 -
	top: 1rem;
280 -
	z-index: -1;
296 +
  position: fixed;
297 +
  left: 1rem;
298 +
  top: 1rem;
299 +
  z-index: -1;
281 300
}
282 301
nav li {
283 -
	line-height: 1.25em;
284 -
}
285 -
nav a {
286 -
	color: var(--highlight-color);
302 +
  line-height: 1.25em;
287 303
}
288 304
nav footer {
289 -
	position: fixed;
290 -
	bottom: 1rem;
291 -
	left: 1rem;
305 +
  position: fixed;
306 +
  bottom: 1rem;
307 +
  left: 1rem;
292 308
}
293 309
294 310
/*
295 311
 * CLASSES
296 312
 */
297 313
298 314
.date {
299 -
	color: #2c4146;
315 +
  color: #2c4146;
300 316
}
301 317
.subtle {
302 -
	color: var(--subtle-color);
318 +
  color: var(--subtle-color);
303 319
}
304 320
.footnotes {
305 -
	margin-top: 2rem;
306 -
	padding-top: 2rem;
307 -
	border-top: 1px solid var(--subtle-color);
308 -
	font-size: 18px;
321 +
  margin-top: 2rem;
322 +
  padding-top: 2rem;
323 +
  border-top: 1px solid var(--subtle-color);
324 +
  font-size: 18px;
325 +
}
326 +
.footnotes ol {
327 +
  margin-left: 2rem;
309 328
}
310 329
.footnotes p {
311 -
	margin: 0 !important;
330 +
  margin: 0 !important;
312 331
}
313 332
.footnote-ref a, a[rel="footnote"] {
314 -
	margin-left: 0.2rem;
333 +
  font-size: 0.875rem;
334 +
  letter-spacing: -0.125rem;
335 +
}
336 +
a.footnote::before {
337 +
  content: "[";
338 +
}
339 +
a.footnote::after {
340 +
  content: "]";
315 341
}
342 +
a.footnote-backref {
343 +
  font-family: Helvetica, Arial, sans-serif;
344 +
}
345 +
316 346
317 347
.qr {
318 -
	margin-bottom: 2em;
319 -
	text-align: center;
320 -
	width: 100%;
321 -
	cursor: pointer;
348 +
  margin-bottom: 2em;
349 +
  text-align: center;
350 +
  width: 100%;
351 +
  cursor: pointer;
322 352
}
323 353
.qr img {
324 -
	text-align: center;
325 -
	display: block;
326 -
	margin: 0 auto 1rem auto;
327 -
	max-width: 50%;
354 +
  text-align: center;
355 +
  display: block;
356 +
  margin: 0 auto 1rem auto;
357 +
  max-width: 50%;
328 358
}
329 359
330 360
input.address {
331 -
	font-size: 100%;
332 -
	background: none;
333 -
	border: none;
334 -
	margin: none;
335 -
	padding: 0.5rem;
336 -
	color: var(--highlight-color);
337 -
	cursor: pointer;
338 -
	text-decoration: none;
339 -
	width: 100%;
340 -
	text-align: center;
361 +
  font-size: 100%;
362 +
  background: none;
363 +
  border: none;
364 +
  margin: none;
365 +
  padding: 0.5rem;
366 +
  color: var(--highlight-color);
367 +
  cursor: pointer;
368 +
  text-decoration: none;
369 +
  width: 100%;
370 +
  text-align: center;
341 371
}
342 372
343 373
.copy-message {
344 -
	vertical-align: middle;
345 -
	color: var(--highlight-color);
346 -
	visibility: hidden;
347 -
	position: fixed;
348 -
	top: 0;
349 -
	right: 0;
350 -
	padding: 1rem;
374 +
  vertical-align: middle;
375 +
  color: var(--highlight-color);
376 +
  visibility: hidden;
377 +
  position: fixed;
378 +
  top: 0;
379 +
  right: 0;
380 +
  padding: 1rem;
351 381
}
352 382
353 383
/*
354 384
 * MEDIA
355 385
 */
356 386
357 387
@media /* Medium desktop */
358 388
only screen and (max-device-width: 1440px),
359 389
only screen and (max-width: 1440px) {
360 -
	nav .subtle {
361 -
		display: none;
362 -
	}
390 +
  nav .subtle {
391 +
    display: none;
392 +
  }
363 393
}
364 394
365 395
@media /* Small desktop */
366 396
only screen and (max-device-width: 1280px),
367 397
only screen and (max-width: 1280px) {
368 -
	body {
369 -
		max-width: 720px;
370 -
	}
398 +
  body {
399 +
    max-width: 720px;
400 +
  }
371 401
}
372 402
373 403
@media /* Smaller desktop */
374 404
only screen and (max-device-width: 960px),
375 405
only screen and (max-width: 960px) {
376 -
	body {
377 -
		max-width: 100%;
378 -
		padding: 1rem 0rem;
379 -
	}
380 -
	nav {
381 -
		display: block;
382 -
		position: static;
383 -
		margin-bottom: 1.5rem;
384 -
	}
385 -
	nav footer {
386 -
		display: none;
387 -
	}
388 -
	nav ul {
389 -
		column-count: 2;
390 -
		column-gap: 4em;
391 -
392 -
		-webkit-column-count: 2;
393 -
		-webkit-column-gap: 4rem;
394 -
		-moz-column-count: 2;
395 -
		-moz-column-gap: 4rem;
396 -
	}
397 -
	img[alt="crates.io"], img[alt="docs.rs"], img[alt="github.com"] {
398 -
		height: 1.25rem;
399 -
	}
400 -
	h1, h2, h3, h4, h5, h6, p, nav {
401 -
		padding: 0 1rem;
402 -
	}
403 -
	pre {
404 -
		padding: 1rem;
405 -
	}
406 -
	body > footer {
407 -
		display: block;
408 -
	}
409 -
	.footnotes {
410 -
		font-size: 0.75rem;
411 -
	}
406 +
  body {
407 +
    max-width: 100%;
408 +
    padding: 1rem 0rem;
409 +
  }
410 +
  nav {
411 +
    display: block;
412 +
    position: static;
413 +
    margin-bottom: 1.5rem;
414 +
  }
415 +
  nav footer {
416 +
    display: none;
417 +
  }
418 +
  nav ul {
419 +
    column-count: 2;
420 +
    column-gap: 4em;
421 +
422 +
    -webkit-column-count: 2;
423 +
    -webkit-column-gap: 4rem;
424 +
    -moz-column-count: 2;
425 +
    -moz-column-gap: 4rem;
426 +
  }
427 +
  img[alt="crates.io"], img[alt="docs.rs"], img[alt="github.com"] {
428 +
    height: 1.25rem;
429 +
  }
430 +
  article {
431 +
    margin: 0 1rem;
432 +
  }
433 +
  nav {
434 +
    padding: 0 1rem;
435 +
  }
436 +
  pre {
437 +
    padding: 1rem;
438 +
  }
439 +
  body > footer {
440 +
    display: block;
441 +
  }
412 442
}
413 443
414 444
@media /* Mobile */
415 445
only screen and (max-device-width: 720px) {
416 -
	body {
417 -
		font-size: 87.5%;
418 -
	}
419 -
	video {
420 -
		height: auto;
421 -
		width: 100%;
422 -
		border-left: none;
423 -
		border-right: none;
424 -
	}
425 -
	pre {
426 -
		overflow: scroll !important;
427 -
	}
428 -
	pre code {
429 -
		white-space: pre !important;
430 -
	}
446 +
  body {
447 +
    font-size: 87.5%;
448 +
  }
449 +
  video {
450 +
    height: auto;
451 +
    width: 100%;
452 +
    border-left: none;
453 +
    border-right: none;
454 +
  }
455 +
  pre {
456 +
    overflow: scroll !important;
457 +
  }
458 +
  pre code {
459 +
    white-space: pre !important;
460 +
  }
431 461
}
index.md added +6 -0
1 +
---
2 +
layout: default
3 +
---
4 +
<style>
5 +
  {% include index.css %}
6 +
</style>
page.html.erb deleted +0 -59
1 -
<!doctype html>
2 -
<html lang="en">
3 -
  <head>
4 -
    <title><%= title %></title>
5 -
    <meta charset="utf-8">
6 -
    <meta name="viewport" content="width=device-width, initial-scale=1">
7 -
    <meta name="twitter:site" content="@cloudhead" />
8 -
    <meta name="twitter:creator" content="@cloudhead" />
9 -
    <meta property="og:title" content="<%= title %>" />
10 -
    <link rel="shortcut icon" href="favicon.png" />
11 -
    <link rel="stylesheet" type="text/css" href="/css/fonts.css">
12 -
    <link rel="stylesheet" type="text/css" href="/css/base.css">
13 -
    <% if styles? %><link rel="stylesheet" type="text/css" href="style.css"><% end %>
14 -
  </head>
15 -
  <body>
16 -
    <nav>
17 -
      <ul>
18 -
        <li><a href="/">/</a> <span class="subtle">home</span></li>
19 -
        <li><a href="http://twitter.com/cloudhead">@cloudhead</a> <span class="subtle">follow my tweets</span></li>
20 -
        <li><a href="https://github.com/cloudhead">code</a> <span class="subtle">github.com/cloudhead</span></li>
21 -
        <li><a href="https://github.com/cloudhead/dotfiles">dotfiles</a> <span class="subtle">~</span></li>
22 -
        <li><a href="https://rx.cloudhead.io">rx</a> <span class="subtle">rx.cloudhead.io ๐Ÿ‘พ</span></li>
23 -
        <li><a href="https://github.com/cloudhead/shady.vim">shady</a> <span class="subtle">:colorscheme</span</li>
24 -
        <% articles.each do |a| -%><li>
25 -
          <a href="/<%= a.name %>/"><%= a.name %></a>
26 -
          <span class="subtle"><%= a.teaser || a.date %></span>
27 -
        </li><% end %>
28 -
        <li><a href="https://twitter.com/cloudhead">tweets</a></li>
29 -
        <li><a href="https://merveilles.town/@cloudhead">toots</a></li>
30 -
        <li><a href="https://art.cloudhead.io">art</a></li>
31 -
        <li><a href="https://radicle.xyz">radicle</a> <span class="subtle">p2p code-collab</span></li>
32 -
      </ul>
33 -
      <footer>
34 -
        &copy; <%= year %> Alexis Sellier
35 -
      </footer>
36 -
    </nav>
37 -
    <% if body %>
38 -
    <article class="note">
39 -
    <% if headline %>
40 -
      <header>
41 -
        <h1><%= headline %></h1>
42 -
        <% if date -%>
43 -
        <p class="date"><%= date %></p>
44 -
        <% end %>
45 -
      </header>
46 -
    <% end %>
47 -
    <section class="body">
48 -
      <%= body -%>
49 -
    </section>
50 -
    <footer>
51 -
    </footer>
52 -
    </article>
53 -
    <footer>
54 -
      &copy; <%= year %> Alexis Sellier
55 -
    </footer>
56 -
    <script type="text/javascript" src="/js/hirust.js"></script>
57 -
    <% end %>
58 -
  </body>
59 -
</html>
public/BIP158.svg deleted +0 -3
1 -
<?xml version="1.0" encoding="UTF-8"?>
2 -
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
3 -
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="1756px" height="888px" viewBox="-0.5 -0.5 1756 888" style="background-color: rgb(0, 0, 0);"><defs><style type="text/css">@import url(https://fonts.googleapis.com/css?family=Inter);&#xa;</style></defs><g><path d="M 243 512 L 124 512 L 124 590.13" fill="none" stroke="#ffffff" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 124 595.38 L 120.5 588.38 L 124 590.13 L 127.5 588.38 Z" fill="#ffffff" stroke="#ffffff" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 495px; margin-left: 173px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: #FFFFFF; line-height: 1.2; pointer-events: all; white-space: nowrap; ">No</div></div></div></foreignObject><text x="173" y="498" fill="#FFFFFF" font-family="Helvetica" font-size="11px" text-anchor="middle">No</text></switch></g><path d="M 414 512 L 521.63 512" fill="none" stroke="#ffffff" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 526.88 512 L 519.88 515.5 L 521.63 512 L 519.88 508.5 Z" fill="#ffffff" stroke="#ffffff" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 504px; margin-left: 470px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: #FFFFFF; line-height: 1.2; pointer-events: all; white-space: nowrap; ">Yes</div></div></div></foreignObject><text x="470" y="507" fill="#FFFFFF" font-family="Helvetica" font-size="11px" text-anchor="middle">Yes</text></switch></g><path d="M 328.5 431 L 414 512 L 328.5 593 L 243 512 Z" fill="none" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 169px; height: 1px; padding-top: 512px; margin-left: 244px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "><font color="#ffffff"><font face="Inter" data-font-src="https://fonts.googleapis.com/css?family=Inter">Filter checkpoints<br />synced?</font><br /></font></div></div></div></foreignObject><text x="328" y="516" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">Filter checkpoints...</text></switch></g><path d="M 400 293 L 328.57 293 L 328.57 424.7" fill="none" stroke="#ffffff" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 328.57 429.95 L 325.07 422.95 L 328.57 424.7 L 332.07 422.95 Z" fill="#ffffff" stroke="#ffffff" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 280px; margin-left: 366px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: #FFFFFF; line-height: 1.2; pointer-events: all; white-space: nowrap; ">No</div></div></div></foreignObject><text x="366" y="283" fill="#FFFFFF" font-family="Helvetica" font-size="11px" text-anchor="middle">No</text></switch></g><path d="M 571 293 L 710.63 293" fill="none" stroke="#ffffff" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 715.88 293 L 708.88 296.5 L 710.63 293 L 708.88 289.5 Z" fill="#ffffff" stroke="#ffffff" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 281px; margin-left: 642px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: #FFFFFF; line-height: 1.2; pointer-events: all; white-space: nowrap; ">Yes</div></div></div></foreignObject><text x="642" y="284" fill="#FFFFFF" font-family="Helvetica" font-size="11px" text-anchor="middle">Yes</text></switch></g><path d="M 485.5 212 L 571 293 L 485.5 374 L 400 293 Z" fill="none" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 169px; height: 1px; padding-top: 293px; margin-left: 401px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "><font color="#ffffff"><font face="Inter" data-font-src="https://fonts.googleapis.com/css?family=Inter">Filter headers<br />synced?</font><br /></font></div></div></div></foreignObject><text x="486" y="297" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">Filter headers...</text></switch></g><path d="M 195 626.5 L 328.57 626.57 L 328.57 599.3" fill="none" stroke="#ffffff" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 328.57 594.05 L 332.07 601.05 L 328.57 599.3 L 325.07 601.05 Z" fill="#ffffff" stroke="#ffffff" stroke-miterlimit="10" pointer-events="all"/><path d="M 797 263 L 797 157 L 485.57 157 L 485.51 205.63" fill="none" stroke="#ffffff" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 485.5 210.88 L 482.01 203.88 L 485.51 205.63 L 489.01 203.89 Z" fill="#ffffff" stroke="#ffffff" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 147px; margin-left: 639px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: #FFFFFF; line-height: 1.2; pointer-events: all; white-space: nowrap; ">Timeout</div></div></div></foreignObject><text x="639" y="150" fill="#FFFFFF" font-family="Helvetica" font-size="11px" text-anchor="middle">Timeout</text></switch></g><rect x="717" y="263" width="149" height="60" fill="none" stroke="#ffffff" stroke-width="2" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 147px; height: 1px; padding-top: 293px; margin-left: 718px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Inter; color: #FFFFFF; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">Idle</div></div></div></foreignObject><text x="792" y="297" fill="#FFFFFF" font-family="Inter" font-size="12px" text-anchor="middle">Idle</text></switch></g><path d="M 43 656.5 L 63 596.5 L 205 596.5 L 185 656.5 Z" fill="none" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 160px; height: 1px; padding-top: 627px; margin-left: 44px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #FFFFFF; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">Sync checkpoints<br />(getcfcheckpt)</div></div></div></foreignObject><text x="124" y="630" fill="#FFFFFF" font-family="Helvetica" font-size="12px" text-anchor="middle">Sync checkpoints...</text></switch></g><path d="M 984 727 L 984 672.37" fill="none" stroke="#ffffff" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 984 667.12 L 987.5 674.12 L 984 672.37 L 980.5 674.12 Z" fill="#ffffff" stroke="#ffffff" stroke-miterlimit="10" pointer-events="all"/><path d="M 947 751.25 C 917.4 751.25 910 775.5 933.68 780.35 C 910 791.02 936.64 814.3 955.88 804.6 C 969.2 824 1013.6 824 1028.4 804.6 C 1058 804.6 1058 785.2 1039.5 775.5 C 1058 756.1 1028.4 736.7 1002.5 746.4 C 984 731.85 954.4 731.85 947 751.25 Z" fill="none" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 146px; height: 1px; padding-top: 776px; margin-left: 911px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #FFFFFF; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">Peer negotiated</div></div></div></foreignObject><text x="984" y="779" fill="#FFFFFF" font-family="Helvetica" font-size="12px" text-anchor="middle">Peer negotiated</text></switch></g><path d="M 984 606 L 984 512 L 884.37 512" fill="none" stroke="#ffffff" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 879.12 512 L 886.12 508.5 L 884.37 512 L 886.12 515.5 Z" fill="#ffffff" stroke="#ffffff" stroke-miterlimit="10" pointer-events="all"/><path d="M 903 666 L 923 606 L 1065 606 L 1045 666 Z" fill="none" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 160px; height: 1px; padding-top: 636px; margin-left: 904px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #FFFFFF; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">Ask for filter chain tip</div></div></div></foreignObject><text x="984" y="640" fill="#FFFFFF" font-family="Helvetica" font-size="12px" text-anchor="middle">Ask for filter chain tip</text></switch></g><path d="M 807 482 L 807 462 L 807 468 L 807 454.37" fill="none" stroke="#ffffff" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 807 449.12 L 810.5 456.12 L 807 454.37 L 803.5 456.12 Z" fill="#ffffff" stroke="#ffffff" stroke-miterlimit="10" pointer-events="all"/><path d="M 726 542 L 746 482 L 888 482 L 868 542 Z" fill="none" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 160px; height: 1px; padding-top: 512px; margin-left: 727px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #FFFFFF; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">Receive cfheaders</div></div></div></foreignObject><text x="807" y="516" fill="#FFFFFF" font-family="Helvetica" font-size="12px" text-anchor="middle">Receive cfheaders</text></switch></g><path d="M 670 512 L 729.63 512" fill="none" stroke="#ffffff" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 734.88 512 L 727.88 515.5 L 729.63 512 L 727.88 508.5 Z" fill="#ffffff" stroke="#ffffff" stroke-miterlimit="10" pointer-events="all"/><path d="M 518 542 L 538 482 L 680 482 L 660 542 Z" fill="none" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 160px; height: 1px; padding-top: 512px; margin-left: 519px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #FFFFFF; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">Sync headers<br />(getcfheaders)</div></div></div></foreignObject><text x="599" y="516" fill="#FFFFFF" font-family="Helvetica" font-size="12px" text-anchor="middle">Sync headers...</text></switch></g><path d="M 860.5 397.5 L 1132.63 397.5" fill="none" stroke="#ffffff" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 1137.88 397.5 L 1130.88 401 L 1132.63 397.5 L 1130.88 394 Z" fill="#ffffff" stroke="#ffffff" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 384px; margin-left: 929px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: #FFFFFF; line-height: 1.2; pointer-events: all; white-space: nowrap; ">Yes</div></div></div></foreignObject><text x="929" y="387" fill="#FFFFFF" font-family="Helvetica" font-size="11px" text-anchor="middle">Yes</text></switch></g><path d="M 807 347 L 860.5 397.5 L 807 448 L 753.5 397.5 Z" fill="none" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 105px; height: 1px; padding-top: 398px; margin-left: 755px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "><font color="#ffffff"><font face="Inter">Conflict?</font><br /></font></div></div></div></foreignObject><text x="807" y="401" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">Conflict?&#xa;</text></switch></g><path d="M 1281 397.5 L 1311 397.57 L 1293 397.57 L 1316.63 397.52" fill="none" stroke="#ffffff" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 1321.88 397.5 L 1314.89 401.02 L 1316.63 397.52 L 1314.87 394.02 Z" fill="#ffffff" stroke="#ffffff" stroke-miterlimit="10" pointer-events="all"/><path d="M 1129 427.5 L 1149 367.5 L 1291 367.5 L 1271 427.5 Z" fill="none" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 160px; height: 1px; padding-top: 398px; margin-left: 1130px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #FFFFFF; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">Find fork height</div></div></div></foreignObject><text x="1210" y="401" fill="#FFFFFF" font-family="Helvetica" font-size="12px" text-anchor="middle">Find fork height</text></switch></g><path d="M 1394 367.5 L 1394 266.37" fill="none" stroke="#ffffff" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 1394 261.12 L 1397.5 268.12 L 1394 266.37 L 1390.5 268.12 Z" fill="#ffffff" stroke="#ffffff" stroke-miterlimit="10" pointer-events="all"/><path d="M 1313 427.5 L 1333 367.5 L 1475 367.5 L 1455 427.5 Z" fill="none" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 160px; height: 1px; padding-top: 398px; margin-left: 1314px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #FFFFFF; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">Download block</div></div></div></foreignObject><text x="1394" y="401" fill="#FFFFFF" font-family="Helvetica" font-size="12px" text-anchor="middle">Download block</text></switch></g><path d="M 1308.5 179 L 1223.37 179" fill="none" stroke="#ffffff" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 1218.12 179 L 1225.12 175.5 L 1223.37 179 L 1225.12 182.5 Z" fill="#ffffff" stroke="#ffffff" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 156px; margin-left: 1264px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: #FFFFFF; line-height: 1.2; pointer-events: all; white-space: nowrap; ">Yes</div></div></div></foreignObject><text x="1264" y="159" fill="#FFFFFF" font-family="Helvetica" font-size="11px" text-anchor="middle">Yes</text></switch></g><path d="M 1479.5 179 L 1564.63 179" fill="none" stroke="#ffffff" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 1569.88 179 L 1562.88 182.5 L 1564.63 179 L 1562.88 175.5 Z" fill="#ffffff" stroke="#ffffff" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 160px; margin-left: 1524px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: #FFFFFF; line-height: 1.2; pointer-events: all; white-space: nowrap; ">No</div></div></div></foreignObject><text x="1524" y="163" fill="#FFFFFF" font-family="Helvetica" font-size="11px" text-anchor="middle">No</text></switch></g><path d="M 1394 98 L 1479.5 179 L 1394 260 L 1308.5 179 Z" fill="none" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 169px; height: 1px; padding-top: 179px; margin-left: 1309px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "><font face="Inter" color="#ffffff">Are local filters<br />valid?</font></div></div></div></foreignObject><text x="1394" y="183" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">Are local filters...</text></switch></g><path d="M 1075 179 L 965.57 179 L 965.57 293 L 872.37 293" fill="none" stroke="#ffffff" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 867.12 293 L 874.12 289.5 L 872.37 293 L 874.12 296.5 Z" fill="#ffffff" stroke="#ffffff" stroke-miterlimit="10" pointer-events="all"/><path d="M 1065 209 L 1085 149 L 1227 149 L 1207 209 Z" fill="none" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 160px; height: 1px; padding-top: 179px; margin-left: 1066px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #FFFFFF; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">Ban peer</div></div></div></foreignObject><text x="1146" y="183" fill="#FFFFFF" font-family="Helvetica" font-size="12px" text-anchor="middle">Ban peer</text></switch></g><path d="M 1649 209 L 1649 861 L 599 861 L 599 548.37" fill="none" stroke="#ffffff" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 599 543.12 L 602.5 550.12 L 599 548.37 L 595.5 550.12 Z" fill="#ffffff" stroke="#ffffff" stroke-miterlimit="10" pointer-events="all"/><path d="M 1561 209 L 1581 149 L 1737 149 L 1717 209 Z" fill="none" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 174px; height: 1px; padding-top: 179px; margin-left: 1562px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #FFFFFF; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">Rollback to fork height</div></div></div></foreignObject><text x="1649" y="183" fill="#FFFFFF" font-family="Helvetica" font-size="12px" text-anchor="middle">Rollback to fork height</text></switch></g><path d="M 485.5 85 L 485.5 205.63" fill="none" stroke="#ffffff" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 485.5 210.88 L 482 203.88 L 485.5 205.63 L 489 203.88 Z" fill="#ffffff" stroke="#ffffff" stroke-miterlimit="10" pointer-events="all"/><ellipse cx="485.5" cy="64" rx="21" ry="21" fill="none" stroke="#ffffff" stroke-width="2" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 40px; height: 1px; padding-top: 64px; margin-left: 466px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #FFFFFF; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">Init</div></div></div></foreignObject><text x="486" y="68" fill="#FFFFFF" font-family="Helvetica" font-size="12px" text-anchor="middle">Init</text></switch></g><path d="M 566 397.5 L 566 394 L 485.57 394 L 485.52 380.37" fill="none" stroke="#ffffff" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 485.5 375.12 L 489.03 382.11 L 485.52 380.37 L 482.03 382.13 Z" fill="#ffffff" stroke="#ffffff" stroke-miterlimit="10" pointer-events="all"/><path d="M 556 427.5 L 576 367.5 L 682 367.5 L 662 427.5 Z" fill="none" stroke="#ffffff" stroke-width="2" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 124px; height: 1px; padding-top: 398px; margin-left: 557px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #FFFFFF; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">Import</div></div></div></foreignObject><text x="619" y="401" fill="#FFFFFF" font-family="Helvetica" font-size="12px" text-anchor="middle">Import</text></switch></g><path d="M 753.5 397.5 L 678.37 397.5" fill="none" stroke="#ffffff" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 673.12 397.5 L 680.12 394 L 678.37 397.5 L 680.12 401 Z" fill="#ffffff" stroke="#ffffff" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 383px; margin-left: 718px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: #FFFFFF; line-height: 1.2; pointer-events: all; white-space: nowrap; ">No</div></div></div></foreignObject><text x="718" y="386" fill="#FFFFFF" font-family="Helvetica" font-size="11px" text-anchor="middle">No</text></switch></g></g><switch><g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"/><a transform="translate(0,-5)" xlink:href="https://www.diagrams.net/doc/faq/svg-export-text-problems" target="_blank"><text text-anchor="middle" font-size="10px" x="50%" y="100%">Viewer does not support full SVG 1.1</text></a></switch></svg>
rx/index.html deleted +0 -45
1 -
<!doctype html>
2 -
<html lang="en">
3 -
  <head>
4 -
    <title>rx &mdash; a minimalist and extensible pixel editor</title>
5 -
    <meta http-equiv="content-type" content="text/html;charset=utf-8">
6 -
    <meta name="viewport" content="width=device-width, initial-scale=1">
7 -
8 -
    <style>
9 -
      body {
10 -
        background-color: black;
11 -
        color: #ddd;
12 -
        padding: 90px 0 90px 0;
13 -
        font-size: 18px;
14 -
        line-height: 1.4em;
15 -
        margin: 0 auto;
16 -
        text-align: center;
17 -
      }
18 -
19 -
      body, code {
20 -
        font-family: Consolas, Inconsolata, monospace;
21 -
      }
22 -
23 -
      a {
24 -
        color: #FFEEBB;
25 -
      }
26 -
27 -
      header {
28 -
        font-size: 24px;
29 -
      }
30 -
31 -
      header img {
32 -
        text-align: center;
33 -
        margin-bottom: 45px;
34 -
      }
35 -
    </style>
36 -
  </head>
37 -
  <body>
38 -
    <header>
39 -
      <img src="rx.png"/>
40 -
      <p>
41 -
        <em>rx</em> has moved to <a href="https://rx.cloudhead.io">https://rx.cloudhead.io</a>
42 -
      </p>
43 -
    </header>
44 -
  </body>
45 -
</html>
rx/rx.png deleted +0 -0

Binary file changed.

scripts/links deleted +0 -15
1 -
#!/usr/bin/env ruby
2 -
3 -
require 'erb'
4 -
require 'ostruct'
5 -
6 -
$stdout.print ERB.new(
7 -
  File.read('templates/links.html.erb')
8 -
).result(
9 -
  OpenStruct.new(
10 -
    links: ARGF.read.scan(/^([&\w -]+)\n={16,}\n/) {|section|
11 -
      [section, ARGF.read.scan(/\*\s+(.+?)\s+<(.+?)>\s+(.+)\n/)]
12 -
    }
13 -
  ).instance_eval { binding }
14 -
)
15 -