Re-create website using Jekyll
9851e3d3af4dd9f0192364aec8a98256b7f970c2
It's about time.
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 | + | © {{ '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 | + | © {{ '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—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 | + |  |
|
| 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 = <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 & 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 & 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>© 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 | - | © <%= 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 | - | © <%= 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);
</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?
</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 — 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 | - |