Add 'website/' from commit 'ac10505d276c16fc192346417545bc6a2b255ee8'

git-subtree-dir: website
git-subtree-mainline: 1c7aa7812c
git-subtree-split: ac10505d27
wch-ch32v003
Matt Knight 7 months ago
commit 09542f5e98

@ -0,0 +1,2 @@
# use_nix
use_flake

@ -0,0 +1 @@
*.zig text=auto eol=lf

@ -0,0 +1,38 @@
name: Render PR Preview
on: [pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
submodules: 'recursive'
- name: Setup Zig
uses: goto-bus-stop/setup-zig@v2
with:
version: master
- name: Render website
run: |
zig build
- name: Deploy
uses: easingthemes/ssh-deploy@main
with:
SSH_PRIVATE_KEY: ${{ secrets.DEPLOY_PRIVATE_KEY }}
ARGS: "-vzrli"
SOURCE: "zig-out/"
REMOTE_HOST: ${{ secrets.DEPLOY_HOST }}
REMOTE_USER: ${{ secrets.DEPLOY_USER }}
REMOTE_PORT: ${{ secrets.DEPLOY_PORT }}
TARGET: "./staging/pulls/${{ github.event.number }}"
- uses: mshick/add-pr-comment@v1
with:
message: |
Heya!
You can check out a preview of your PR at [staging.microzig.tech/pulls/${{ github.event.number }}](https://staging.microzig.tech/pulls/${{ github.event.number }}/)!
repo-token: ${{ secrets.GITHUB_TOKEN }}
repo-token-user-login: 'github-actions[bot]' # The user.login for temporary GitHub tokens
allow-repeats: false # This is the default

@ -0,0 +1,36 @@
name: Render Website
on:
push:
branches:
- 'main'
- 'master'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
submodules: 'recursive'
- name: Setup Zig
uses: goto-bus-stop/setup-zig@v2
with:
version: master
- name: Render website
run: |
zig build
- name: Deploy
uses: easingthemes/ssh-deploy@main
with:
SSH_PRIVATE_KEY: ${{ secrets.DEPLOY_PRIVATE_KEY }}
ARGS: "-vzrli"
SOURCE: "zig-out/"
REMOTE_HOST: ${{ secrets.DEPLOY_HOST }}
REMOTE_USER: ${{ secrets.DEPLOY_USER }}
REMOTE_PORT: ${{ secrets.DEPLOY_PORT }}
TARGET: "./live"

@ -0,0 +1,4 @@
zig-cache/
render/
zig-out/
.direnv/

@ -0,0 +1,19 @@
Copyright (c) 2021 Felix "xq" Queißner
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
OR OTHER DEALINGS IN THE SOFTWARE.

@ -0,0 +1,34 @@
# Zig Embedded Group - Website and Articles
This project both contains the contents and the generation of the ZEG website.
## Folder Structure
```
.
├── build.zig
├── deps Contains submodule dependencies
│ └── …
├── LICENCE
├── README.md
├── render Not included in the repo, will contain the rendered website
│ └── …
├── src Source of the website generator and other tools
│ └── main.zig
└── website Contains the raw input data for the website
├── articles Contains all articles in the format `YYYY-MM-dd - ${TITLE}.md`
│ └── …
├── img Contains the images used on the website.
│ └── …
├── index.md Index page of the website
└── tutorials Contains the raw tutorial files
└── …
```
## Markdown
The website uses basic markdown that allows GFM style tables and also supports *some* placeholders:
- `<!-- TOC -->` will insert a table of contents if alone in a single line. The ToC will be rendered in the same depth as the next heading, so everything higher in the hierarchy will be ignored.
- `<!-- ARTICLES -->` renders a list of all available articles
- `<!-- ARTICLES10 -->` renders a list of the 10 latest articles

@ -0,0 +1,42 @@
# Planned/TODO articles
## Work in progress
These have either incomplete content on the website or a branch where they are being written.
- Tutorials
- 01-embedded-basics
- 02-embedded-programming
- Articles
- NONE
## TODO
Things that should be written eventually.
- Tutorials
- Getting started with:
- Arduino/AVR
- LPC1768
- NRF52
- Hardware: nRF52840 Dongle
- https://www.nordicsemi.com/Software-and-tools/Development-Kits/nRF52840-Dongle/GetStarted
- https://www.nordicsemi.com/Software-and-Tools/Development-Tools/nRF-Connect-for-desktop
- https://infocenter.nordicsemi.com/index.jsp?topic=%2Fug_nc_programmer%2FUG%2Fnrf_connect_programmer%2Fncp_programming_dongle.html
- Raspberry Pi Pico
- STM32
- What device to chose?
- Introduction to HAL 9001
- Articles
- Creating your own JTAG debugger
- Black Magic Probe
- https://paramaggarwal.medium.com/converting-an-stm32f103-board-to-a-black-magic-probe-c013cf2cc38c
- zCOM, a network stack for embedded devices
## Ideas
Ideas for things to write that would be great to do eventually or projects to write up about.
- Tutorials
- Articles
- Make your own keyboard with zig (and replace qmk)

@ -0,0 +1,15 @@
const std = @import("std");
const zine = @import("zine");
pub fn build(b: *std.Build) !void {
// zine.scriptyReferenceDocs(b, "content/documentation/scripty/index.md");
try zine.addWebsite(b, .{
.layouts_dir_path = "layouts",
.content_dir_path = "content",
.static_dir_path = "static",
.site = .{
.base_url = "https://microzig.tech",
.title = "Zig Embedded Group",
},
});
}

@ -0,0 +1,11 @@
.{
.name = "microzig.tech",
.version = "0.2.0",
.paths = .{"."},
.dependencies = .{
.zine = .{
.url = "https://github.com/kristoff-it/zine/archive/03f80646b83cadb2e693ff5d97445d3e16c8e222.tar.gz",
.hash = "1220e3e4938edf652776349c45b3bb58774d540a050034488ff8dab7dbe410cc2977",
},
},
}

@ -0,0 +1,11 @@
---
{
"title": "Home",
"date": "2020-07-06T00:00:00",
"author": "Felix Queißner",
"draft": false,
"layout": "getting-started.html",
"tags": []
}
---
Dummy, full text is implemented in the HTML file for now.

@ -0,0 +1,11 @@
---
{
"title": "Home",
"date": "2020-07-06T00:00:00",
"author": "Felix Queißner",
"draft": false,
"layout": "index.html",
"tags": []
}
---
Dummy, full text is implemented in the HTML file for now.

@ -0,0 +1,46 @@
---
{
"title": "Embedded Basics",
"date": "2020-07-06T00:00:00",
"author": "Felix Queißner",
"draft": false,
"layout": "tutorial.html",
"tags": []
}
---
# Embedded Basics
In this tutorial, you'll learn the absolute basics of the embedded world. If
you have already experience with embedded systems and/or electronics, this
chapter probably doesn't provide anything new to you.
## Prerequisites
None! This is your entry point into the embedded world!
## Contents
<!-- TOC -->
## What are embedded systems?
Wikipedia does a good job defining embedded systems with this opener:
> An embedded system is a computer system—a combination of a computer processor, computer memory, and input/output peripheral devices—that has a dedicated function within a larger mechanical or electrical system.
So at the end of the day, if you are adding any sort of computation to some object who's main purpose is not being a computer, it's an embedded system.
Some examples of Embedded systems:
- cars
- industrial control systems
- mars rovers
- digital thermometer
### Real time
An important characteristic that's often required for an embedded system is "real time".
This is simply the ability for the system to respond to an input within a hard deadline, Eg. automatic breaks for a car.
A general operating system like Linux is not suitable for these applications because it uses time sharing when scheduling tasks/programs, and unreliably responds to important signals.
## MORE COMING SOON

@ -0,0 +1,151 @@
---
{
"title": "Embedded Basics",
"date": "2020-07-06T00:00:00",
"author": "Felix Queißner",
"draft": false,
"layout": "tutorial.html",
"tags": []
}
---
# Embedded Programming
In this tutorial, you'll learn the ways of the embedded programmer and how to master your MCU.
## Prerequisites
- [Embedded Basics](01-embedded-basics.htm)
## Contents
<!-- TOC -->
## Differences to desktop programming
The embedded world is quite different compared to the convenient environment of desktop computers. You are not
protected by an operating system, you don't have convenient APIs for file access or even an allocator. You are as
close to the metal as it can be.
Most of your programs don't even have a real entry point, as an embedded system is usually "started" by triggering
the *RESET* interrupt (which is some kind of callback). Desktop programs also have a protected memory area, where the
address `0x00…00` is usually invalid and cannot be accessed. On embedded systems though, this address is either where
some relevant data is or even more important, your entry point. You usually also have very little RAM available, sometimes
even less than 2048 byte. This means that thinking about memory usage is very important.
## Inventory of an embedded programmer
Every embedded programmer requires some materials to get their work done efficiently.
First of all, the *SOC datasheet*. It contains all relevant information about the SOC/chip you are using, which functions each pin of the package has, where your RAM and flash is located in the memory map and so on. You *will* learn to navigate this document very quickly, as it's the main reference for everything you do.
Second, you need the schematics of the device you want to program. You usually can obtain them from the manufacturer of your development board (assuming you are using one), by the vendor of the device you're hacking (if you are lucky enough) or by reverse engineering the device you have at hands (consider this the *hard mode* of embedded development). Reading a schematic is crucial to get your device do what you want, and you can learn a bit about this in the [Embedded Basics](01-embedded-basics.htm) tutorial.
These two documents are the ones you *definitly* need and it's near-impossible to work without them. But usually you need more documents than this:
Datasheets for all the peripherial devices like displays, display controllers, motor controllers, expander chips and so on. Another document that helps a lot is the CPU datasheet for the core of your SOC. This document contains a precise description of the startup procedure of your system, what instructions are available, how the interrupts work in detail and similar topics.
And last, but not least: You need a [text editor of your choice](https://en.wikipedia.org/wiki/List_of_text_editors), a toolchain which consists of a [compiler](https://ziglang.org/) and [binutils](https://www.gnu.org/software/binutils/), and a programmer/flashing tool for your SOC, so you can load your program.
## The startup procedure
So to get an embedded program up and running, we first need to check out the *memory map* in the datasheet. These usually look like this:
![Memory Map of LPC1768](memory-map.png)
Here you can see that the memory contains continuous flash memory (*On-chip [non-volatile memory](https://en.wikipedia.org/wiki/Non-volatile_memory)*), two sections of SRAM (*On-chip [SRAM](https://en.wikipedia.org/wiki/Static_random-access_memory)*), some *Boot ROM*, and peripherials.
This memory map tells us how to design the [linker script](https://ftp.gnu.org/old-gnu/Manuals/ld-2.9.1/html_chapter/ld_3.html#SEC6) and how to lay out our sections (`.text`, `.data`, …). As sections are quite complex topic for themselves, they [will be explained later](#text-data-and-other-curious-sections). For now, we only need to know that `.text` is all of our code (this is where our functions live), `.rodata` is pre-initialized immutable data, `.data` is the pre-initialized mutable data and `.bss` is zero-initialized mutable data.
Note the difference between `.rodata` and `.data`? We can safely put `.rodata` into a flash section in memory, as we can access flash memory the same as we can access RAM. `.data` must be in RAM though, as we need to be able to change it. But there is one huge problem:
The RAM will contain garbage after startup. Our variables aren't initialized and thus also contain garbage. So the first thing to do in a microcontroller software is somehow restoring the data in RAM and initialize both known and zero-initialized data.
To do this, we first have to tell the compiler to store our pre-initialized data somewhere in flash memory, so we can copy it into RAM at startup. This is done via the linker script:
```ld
/* This section declares two memory regions:
* flash: 512k of non-writeable memory at position 0,
* ram: 32k of writeable memory at position 256M
*/
MEMORY
{
flash (rx!w) : ORIGIN = 0x00000000, LENGTH = 512k
ram (rwx) : ORIGIN = 0x10000000, LENGTH = 32k
}
/* This section declares rules where to put different
* symbols (functions and variables) in memory
*/
SECTIONS
{
/* this is the output section ".text" which will be located in flash */
.text :
{
/* include all things that are functions or have linksection(".text") */
*(.text)
} >flash /* this means that this section is layed out in "flash" MEMORY */
/* assign the current location to a symbol called code_end */
code_end = .;
/* this is the output section ".data" which will be located in RAM.
* AT(X) means that this section is *loaded* at position X in memory, in this
* case: after our code in flash.
*/
.data : AT (code_end)
{
/* create a symbol called data_begin at the start of the data section */
data_begin = .;
/* include all things that are variables or have linksection(".data") */
*(.data)
/* same as above, but now at the end of the section */
data_end = .;
} >ram /* this means that this section is layed out in "ram" MEMORY */
}
```
This script includes all symbols from `.text` and `.data` in the final executable and assigns addresses from everything in `.text` to the flash memory and from everything in `.data` into the RAM. The section `.data` will have a different *load address* though: It is located directly behind `.text` and is not located at `0x10000000`.
As most people have never heard of the *load address*, here's a short excourse: When linking a program, two things happen:
1. Objects get assigned a address in memory (*link address*)
2. Objects get a position where they are loaded (*load address*)
On a normal desktop program, these two are the same. In our embedded world, we need to "load" the RAM contents into flash though, as RAM cannot store the data. This means that when we link our program, the linker will treat everything in `.data` as it would be stored in RAM (and puts the symbol *address* there), but will actually store the data into the flash (and will store the bits there).
With this linker script, we now know two things: Everything in `.data` must be at the address of `data_begin` (which is in RAM), but is still located at `code_end`. Thus, we have to copy the memory from flash to RAM:
```zig
const std = @import("std");
// We can access symbols by declaring them as extern c_void
// and taking their address
extern var code_end: c_void;
extern var data_begin: c_void;
extern var data_end: c_void;
extern fn _start() callconv(.Naked) noreturn {
// first, gather both source and destination addresses:
const src_ptr = @ptrCast([*]const u8, &code_end);
const dst_ptr = @ptrCast([*]u8, &data_begin);
// then, compute the length of the .data section by
// just subtracting two pointers
const length = @ptrToInt(&data_end) - @ptrToInt(&data_start);
// and finally, initialize .data:
std.mem.copy(u8, dst_ptr[0..length], src_ptr[0..length]);
// call your program enty point here:
// …
}
```
There's two sections i left out:
`.rodata`, which is just made the same way as `.text` and will reside in flash and `.bss` which is similar to `.data`, but doesn't have initial content and can just be set to zero with `std.mem.set(u8, bss_ptr[0..bss_length], 0)`.
As you might have noticed, we have a function called `_start`. This is our programs entry point and *must never* return, otherwise **bad things** will happen (and arbitrary code will be executed). Make sure to always include some endless loop that disables interrupt for safety here!
But you might wonder: How is this entry point called? This is very SOC-dependent and is explained in the respective tutorials for each SOC. The same is true for setting up the [stack pointer](https://en.wikipedia.org/wiki/Call_stack) which is required for calling functions and storing temporary variables.
## MORE COMING SOON

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

@ -0,0 +1,146 @@
{
"nodes": {
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1696426674,
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"flake-compat_2": {
"flake": false,
"locked": {
"lastModified": 1673956053,
"narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1705309234,
"narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_2": {
"locked": {
"lastModified": 1659877975,
"narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1708682153,
"narHash": "sha256-5sMDOig3rOe5/2yrhiVjQZnVranorjKHVkzQGmZNNLY=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "33a498b7b1e3af01cb9f99bed64c96c1b4acaa70",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "release-23.11",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1702350026,
"narHash": "sha256-A+GNZFZdfl4JdDphYKBJ5Ef1HOiFsP18vQe9mqjmUis=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "9463103069725474698139ab10f17a9d125da859",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-23.05",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-compat": "flake-compat",
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs",
"zig": "zig"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"zig": {
"inputs": {
"flake-compat": "flake-compat_2",
"flake-utils": "flake-utils_2",
"nixpkgs": "nixpkgs_2"
},
"locked": {
"lastModified": 1708647717,
"narHash": "sha256-iA+MJG6isCog6KIq9uyyTmBMacJCwIuecBbkZol8XiE=",
"owner": "mitchellh",
"repo": "zig-overlay",
"rev": "433ed3117af772faad68a50dcf0199bf273f8721",
"type": "github"
},
"original": {
"owner": "mitchellh",
"repo": "zig-overlay",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

@ -0,0 +1,52 @@
{
description = "microzig website environment";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/release-23.11";
flake-utils.url = "github:numtide/flake-utils";
# required for latest zig
zig.url = "github:mitchellh/zig-overlay";
# Used for shell.nix
flake-compat = {
url = github:edolstra/flake-compat;
flake = false;
};
};
outputs =
{ self
, nixpkgs
, flake-utils
, ...
} @ inputs:
let
overlays = [
(final: prev: { zigpkgs = inputs.zig.packages.${prev.system}; })
];
# Our supported systems are the same supported systems as the Zig binaries
systems = builtins.attrNames inputs.zig.packages;
in
flake-utils.lib.eachSystem systems (
system:
let
pkgs = import nixpkgs { inherit overlays system; };
in
rec {
devShells.default = pkgs.mkShell {
nativeBuildInputs = [ pkgs.zigpkgs.master ];
buildInputs = [ pkgs.bashInteractive ];
shellHook = ''
export SHELL=${pkgs.bashInteractive}/bin/bash
'';
};
# For compatibility with older versions of the `nix` binary
devShell = self.devShells.${system}.default;
}
);
}

@ -0,0 +1,56 @@
<extend template="base.html"/>
<title id="title" var="$page.title"></title>
<div id="content">
<nav id="intro-nav">
<h1><a href="/">zig embedded group</a> -&nbsp;learn</h1>
<img src="img/ember.svg" alt="vectorized Ember, the awesome zeg mascot!">
</nav>
<div id="intro-grid">
<div>
<h2>microzig examples</h2>
<ul class="bars">
<li><a href="https://github.com/ZigEmbeddedGroup/microzig/blob/main/test/programs/minimal.zig">Minimal File</a>
</li>
<li><a href="https://github.com/ZigEmbeddedGroup/microzig/blob/main/test/programs/blinky.zig">Blinky</a></li>
<li><a href="https://github.com/ZigEmbeddedGroup/microzig/blob/main/test/programs/uart-sync.zig">Synchronous
UART Example</a></li>
<li><a href="https://github.com/ZigEmbeddedGroup/microzig/blob/main/test/programs/interrupt.zig">Interrupts</a>
</li>
</ul>
</div>
<div>
<h2>generic zig learning material</h2>
<ul class="bars">
<li><a href="https://ziglang.org/documentation/master/">Language reference</a></li>
<li><a href="https://ziglearn.org/">Ziglearn</a></li>
<li><a href="https://github.com/ratfactor/ziglings">Ziglings</a></li>
<!-- <li><a href=""></a></li> -->
</ul>
</div>
<div>
<h2>external tutorials</h2>
<ul class="bars">
<li><a
href="https://www.digikey.be/en/maker/projects/raspberry-pi-pico-and-rp2040-cc-part-2-debugging-with-vs-code/470abc7efb07432b82c95f6f67f184c0">Raspberry
Pi Pico and RP2040: Debugging with VS Code</a></li>
<li><a
href="https://paramaggarwal.medium.com/converting-an-stm32f103-board-to-a-black-magic-probe-c013cf2cc38c">Converting
an STM32F103 board to a Black Magic Probe</a></li>
</ul>
</div>
</div>
<footer>
<img src="img/ember.svg" alt="vectorized Ember, the awesome zeg mascot!">
</footer>
</div>

@ -0,0 +1,96 @@
<extend template="base.html"/>
<title id="title" var="$page.title"></title>
<div id="content">
<nav id="intro-nav">
<h1>zig embedded group</h1>
<img src="img/ember.svg" alt="vectorized Ember, the awesome zeg mascot!">
</nav>
<div id="intro-grid">
<div>
<h2><svg viewBox="0 0 24 24">
<path
d="M14.4,6H20V16H13L12.6,14H7V21H5V4H14L14.4,6M14,14H16V12H18V10H16V8H14V10L13,8V6H11V8H9V6H7V8H9V10H7V12H9V10H11V12H13V10L14,12V14M11,10V8H13V10H11M14,10H16V12H14V10Z" />
</svg>goals</h2>
<ul class="bars">
<li>provide documents on how to get started with embedded programming (for absolute newbies)</li>
<li>provide example snippets for common operations on certain architectures (LPC, STM32, AVR, ...)</li>
<li>provide example worked through embedded mini-projects</li>
<li>create register definition libraries</li>
<li>create a common interface/HAL over several architectures</li>
<li>create a performant common set of drivers for external platforms</li>
</ul>
</div>
<div>
<h2><svg viewBox="0 0 24 24">
<path
d="M10.59,13.41C11,13.8 11,14.44 10.59,14.83C10.2,15.22 9.56,15.22 9.17,14.83C7.22,12.88 7.22,9.71 9.17,7.76V7.76L12.71,4.22C14.66,2.27 17.83,2.27 19.78,4.22C21.73,6.17 21.73,9.34 19.78,11.29L18.29,12.78C18.3,11.96 18.17,11.14 17.89,10.36L18.36,9.88C19.54,8.71 19.54,6.81 18.36,5.64C17.19,4.46 15.29,4.46 14.12,5.64L10.59,9.17C9.41,10.34 9.41,12.24 10.59,13.41M13.41,9.17C13.8,8.78 14.44,8.78 14.83,9.17C16.78,11.12 16.78,14.29 14.83,16.24V16.24L11.29,19.78C9.34,21.73 6.17,21.73 4.22,19.78C2.27,17.83 2.27,14.66 4.22,12.71L5.71,11.22C5.7,12.04 5.83,12.86 6.11,13.65L5.64,14.12C4.46,15.29 4.46,17.19 5.64,18.36C6.81,19.54 8.71,19.54 9.88,18.36L13.41,14.83C14.59,13.66 14.59,11.76 13.41,10.59C13,10.2 13,9.56 13.41,9.17Z" />
</svg>important links</h2>
<ul class="bars">
<li><strong><a href="getting-started.htm">documentation / getting started</a></strong></li>
<li><a href="https://github.com/ZigEmbeddedGroup/">code</a></li>
<li><a href="https://ziglang.org/">Zig programming language</a></li>
</ul>
</div>
<div>
<h2><svg viewBox="0 0 24 24">
<path
d="M12,5.5A3.5,3.5 0 0,1 15.5,9A3.5,3.5 0 0,1 12,12.5A3.5,3.5 0 0,1 8.5,9A3.5,3.5 0 0,1 12,5.5M5,8C5.56,8 6.08,8.15 6.53,8.42C6.38,9.85 6.8,11.27 7.66,12.38C7.16,13.34 6.16,14 5,14A3,3 0 0,1 2,11A3,3 0 0,1 5,8M19,8A3,3 0 0,1 22,11A3,3 0 0,1 19,14C17.84,14 16.84,13.34 16.34,12.38C17.2,11.27 17.62,9.85 17.47,8.42C17.92,8.15 18.44,8 19,8M5.5,18.25C5.5,16.18 8.41,14.5 12,14.5C15.59,14.5 18.5,16.18 18.5,18.25V20H5.5V18.25M0,20V18.5C0,17.11 1.89,15.94 4.45,15.6C3.86,16.28 3.5,17.22 3.5,18.25V20H0M24,20H20.5V18.25C20.5,17.22 20.14,16.28 19.55,15.6C22.11,15.94 24,17.11 24,18.5V20Z" />
</svg>community</h2>
<ul class="bars">
<li><img class="inline" src="img/chat-discord.svg">&nbsp;<a href="https://discord.gg/zqa3fgv6Ma">zig
embedded group
discord</a></li>
<li><img class="inline" src="img/chat-irc.svg">&nbsp;<a href="irc://irc.libera.chat/microzig">microzig
irc channel</a> (<a target="_blank"
href="https://kiwiirc.com/nextclient/irc.libera.chat/#microzig">webchat</a>)
</li>
<li><img class="inline" src="img/chat-discord.svg">&nbsp;<a href="https://discord.gg/TyzJXjser6">zig
language
discord</a>
</li>
</ul>
</div>
<div>
<h2><svg viewBox="0 0 24 24">
<path
d="M19,20H4C2.89,20 2,19.1 2,18V6C2,4.89 2.89,4 4,4H10L12,6H19A2,2 0 0,1 21,8H21L4,8V18L6.14,10H23.21L20.93,18.5C20.7,19.37 19.92,20 19,20Z" />
</svg>projects</h2>
<ul class="bars">
<li><a href="https://github.com/ZigEmbeddedGroup/microzig/">microzig - Cross-Device Embedded
Framework</a></li>
<li><a href="https://github.com/ZigEmbeddedGroup/regz/">regz - SVD and ATDF Code Generator</a></li>
<li><a href="https://github.com/ZigEmbeddedGroup/uf2">uf2 - Generate UF2 files in your build</a></li>
<li><a href="https://github.com/orgs/ZigEmbeddedGroup/repositories">read more…</a></li>
</ul>
</div>
<div>
<h2><svg viewBox="0 0 24 24">
<path
d="M21.33,12.91C21.42,14.46 20.71,15.95 19.44,16.86L20.21,18.35C20.44,18.8 20.47,19.33 20.27,19.8C20.08,20.27 19.69,20.64 19.21,20.8L18.42,21.05C18.25,21.11 18.06,21.14 17.88,21.14C17.37,21.14 16.89,20.91 16.56,20.5L14.44,18C13.55,17.85 12.71,17.47 12,16.9C11.5,17.05 11,17.13 10.5,17.13C9.62,17.13 8.74,16.86 8,16.34C7.47,16.5 6.93,16.57 6.38,16.56C5.59,16.57 4.81,16.41 4.08,16.11C2.65,15.47 1.7,14.07 1.65,12.5C1.57,11.78 1.69,11.05 2,10.39C1.71,9.64 1.68,8.82 1.93,8.06C2.3,7.11 3,6.32 3.87,5.82C4.45,4.13 6.08,3 7.87,3.12C9.47,1.62 11.92,1.46 13.7,2.75C14.12,2.64 14.56,2.58 15,2.58C16.36,2.55 17.65,3.15 18.5,4.22C20.54,4.75 22,6.57 22.08,8.69C22.13,9.8 21.83,10.89 21.22,11.82C21.29,12.18 21.33,12.54 21.33,12.91M16.33,11.5C16.9,11.57 17.35,12 17.35,12.57A1,1 0 0,1 16.35,13.57H15.72C15.4,14.47 14.84,15.26 14.1,15.86C14.35,15.95 14.61,16 14.87,16.07C20,16 19.4,12.87 19.4,12.82C19.34,11.39 18.14,10.27 16.71,10.33A1,1 0 0,1 15.71,9.33A1,1 0 0,1 16.71,8.33C17.94,8.36 19.12,8.82 20.04,9.63C20.09,9.34 20.12,9.04 20.12,8.74C20.06,7.5 19.5,6.42 17.25,6.21C16,3.25 12.85,4.89 12.85,5.81V5.81C12.82,6.04 13.06,6.53 13.1,6.56A1,1 0 0,1 14.1,7.56C14.1,8.11 13.65,8.56 13.1,8.56V8.56C12.57,8.54 12.07,8.34 11.67,8C11.19,8.31 10.64,8.5 10.07,8.56V8.56C9.5,8.61 9.03,8.21 9,7.66C8.92,7.1 9.33,6.61 9.88,6.56C10.04,6.54 10.82,6.42 10.82,5.79V5.79C10.82,5.13 11.07,4.5 11.5,4C10.58,3.75 9.59,4.08 8.59,5.29C6.75,5 6,5.25 5.45,7.2C4.5,7.67 4,8 3.78,9C4.86,8.78 5.97,8.87 7,9.25C7.5,9.44 7.78,10 7.59,10.54C7.4,11.06 6.82,11.32 6.3,11.13C5.57,10.81 4.75,10.79 4,11.07C3.68,11.34 3.68,11.9 3.68,12.34C3.68,13.08 4.05,13.77 4.68,14.17C5.21,14.44 5.8,14.58 6.39,14.57C6.24,14.31 6.11,14.04 6,13.76C5.81,13.22 6.1,12.63 6.64,12.44C7.18,12.25 7.77,12.54 7.96,13.08C8.36,14.22 9.38,15 10.58,15.13C11.95,15.06 13.17,14.25 13.77,13C14,11.62 15.11,11.5 16.33,11.5M18.33,18.97L17.71,17.67L17,17.83L18,19.08L18.33,18.97M13.68,10.36C13.7,9.83 13.3,9.38 12.77,9.33C12.06,9.29 11.37,9.53 10.84,10C10.27,10.58 9.97,11.38 10,12.19A1,1 0 0,0 11,13.19C11.57,13.19 12,12.74 12,12.19C12,11.92 12.07,11.65 12.23,11.43C12.35,11.33 12.5,11.28 12.66,11.28C13.21,11.31 13.68,10.9 13.68,10.36Z" />
</svg>core members</h2>
<ul class="bars">
<li><a href="https://github.com/MasterQ32/">Felix &quot;xq&quot; Queißner</a></li>
<li><a href="https://github.com/mattnite/">Matthew &quot;mattnite&quot; Knight</a></li>
<li><a href="https://github.com/vesim987/">Vesim</a></li>
<li><a href="https://github.com/FireFox317">Timon &quot;FireFox317&quot; Kruiper</a></li>
<li><a href="https://github.com/SpexGuy">Martin &quot;SpexGuy&quot; Wickham</a></li>
</ul>
</div>
</div>
<footer>
<img src="img/ember.svg" alt="vectorized Ember, the awesome zeg mascot!">
</footer>
</div>

@ -0,0 +1,28 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title id="title"><super/> - Zig Embedded Group</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="Zig Embedded Group: Bringing joy to the world of embedded development">
<meta name="twitter:card" content="summary">
<!--
<meta name="twitter:site" content="@croloris">
<meta name="twitter:author" content="@croloris">
-->
<meta name="twitter:description" content="Zig Embedded Group: Bringing joy to the world of embedded development">
<meta name="twitter:title" content="$page.title.suffix(' - Zig Embedded Group')">
<meta property="og:title" content="$page.title">
<meta property="og:type" content="website">
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="content"><super/></div>
</body>
</html>

@ -0,0 +1,17 @@
<extend template="base.html"/>
<title id="title" var="$page.title"></title>
<div id="content">
<nav id="intro-nav">
<h1>zig embedded group</h1>
<img src="img/ember.svg" alt="vectorized Ember, the awesome zeg mascot!">
</nav>
<div id="page" var="$page.content"></div>
<footer>
<img src="img/ember.svg" alt="vectorized Ember, the awesome zeg mascot!">
</footer>
</div>

@ -0,0 +1,684 @@
const std = @import("std");
const koino = @import("koino");
const markdown_options = koino.Options{
.extensions = .{
.table = true,
.autolink = true,
.strikethrough = true,
},
.render = .{
.header_anchors = true,
.anchor_icon = "§ ",
},
};
/// verifies and parses a file name in the format
/// "YYYY-MM-DD - " [.*] ".md"
fn isValidArticleFileName(path: []const u8) ?Date {
if (path.len < 16)
return null;
if (!std.mem.endsWith(u8, path, ".md"))
return null;
if (path[4] != '-' or path[7] != '-' or !std.mem.eql(u8, path[10..13], " - "))
return null;
return Date{
.year = std.fmt.parseInt(u16, path[0..4], 10) catch return null,
.month = std.fmt.parseInt(u8, path[5..7], 10) catch return null,
.day = std.fmt.parseInt(u8, path[8..10], 10) catch return null,
};
}
pub fn main() anyerror!void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
var website = Website{
.allocator = allocator,
.arena = std.heap.ArenaAllocator.init(allocator),
.articles = std.ArrayList(Article).init(allocator),
.tutorials = std.ArrayList(Tutorial).init(allocator),
.images = std.ArrayList([]const u8).init(allocator),
};
defer website.deinit();
// gather step
{
var root_dir = try std.fs.cwd().openDir("website", .{});
defer root_dir.close();
// Tutorials are maintained manually right now
try website.addTutorial(Tutorial{
.src_file = "website/tutorials/01-embedded-basics.md",
});
try website.addTutorial(Tutorial{
.src_file = "website/tutorials/02-embedded-programming.md",
});
// try website.addTutorial(Tutorial{
// .src_file = "website/tutorials/03-lpc1768.md",
// });
// try website.addTutorial(Tutorial{
// .src_file = "website/tutorials/03-nrf52.md",
// });
// try website.addTutorial(Tutorial{
// .src_file = "website/tutorials/03-avr.md",
// });
// try website.addTutorial(Tutorial{
// .src_file = "website/tutorials/03-pi-pico.md",
// });
// try website.addTutorial(Tutorial{
// .src_file = "website/tutorials/03-stm32.md",
// });
// try website.addTutorial(Tutorial{
// .src_file = "website/tutorials/04-chose-device.md",
// });
// try website.addTutorial(Tutorial{
// .src_file = "website/tutorials/05-hal.md",
// });
// img articles
{
var dir = try root_dir.openIterableDir("img", .{});
defer dir.close();
var iter = dir.iterate();
while (try iter.next()) |entry| {
if (entry.kind != .File) {
std.log.err("Illegal folder in directory website/img: {s}", .{entry.name});
continue;
}
const path = try std.fs.path.join(website.arena.allocator(), &[_][]const u8{
"website",
"img",
entry.name,
});
try website.addImage(path);
}
}
// gather articles
{
var dir = try root_dir.openIterableDir("articles", .{});
defer dir.close();
var iter = dir.iterate();
while (try iter.next()) |entry| {
if (entry.kind != .File) {
std.log.err("Illegal folder in directory website/articles: {s}", .{entry.name});
continue;
}
const date = isValidArticleFileName(entry.name) orelse {
if (!std.mem.eql(u8, entry.name, ".keep"))
std.log.err("Illegal file name in directory website/articles: {s}", .{entry.name});
continue;
};
var article = Article{
.title = "Not yet generated",
.src_file = undefined,
.date = date,
};
article.src_file = try std.fs.path.join(website.arena.allocator(), &[_][]const u8{
"website",
"articles",
entry.name,
});
try website.addArticle(article);
}
}
}
try website.prepareRendering();
// final rendering
{
var root_dir = try std.fs.cwd().makeOpenPath("render", .{});
defer root_dir.close();
try std.fs.Dir.copyFile(
std.fs.cwd(),
"src/style.css",
root_dir,
"style.css",
.{},
);
try std.fs.Dir.copyFile(
std.fs.cwd(),
"website/favicon.ico",
root_dir,
"favicon.ico",
.{},
);
try website.renderHtmlFile("website/index.htm", root_dir, "index.htm");
try website.renderHtmlFile("website/getting-started.htm", root_dir, "getting-started.htm");
try website.renderArticleIndex(root_dir, "articles.htm");
var art_dir = try root_dir.makeOpenPath("articles", .{});
defer art_dir.close();
var tut_dir = try root_dir.makeOpenPath("tutorials", .{});
defer tut_dir.close();
var img_dir = try root_dir.makeOpenPath("img", .{});
defer img_dir.close();
try website.renderArticles(art_dir);
try website.renderTutorials(tut_dir);
try website.renderAtomFeed(root_dir, "feed.atom");
try website.renderImages(img_dir);
}
}
const Date = struct {
const Self = @This();
day: u8,
month: u8,
year: u16,
fn toInteger(self: Self) u32 {
return @as(u32, self.day) + 33 * @as(u32, self.month) + (33 * 13) * @as(u32, self.year);
}
pub fn lessThan(lhs: Self, rhs: Self) bool {
return lhs.toInteger() < rhs.toInteger();
}
pub fn eql(a: Self, b: Self) bool {
return std.meta.eql(a, b);
}
pub fn format(self: Self, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
_ = fmt;
_ = options;
try writer.print("{d:0>4}-{d:0>2}-{d:0>2}", .{
self.year, self.month, self.day,
});
}
};
const Article = struct {
date: Date,
src_file: []const u8,
title: []const u8 = "<undetermined>",
};
const Tutorial = struct {
src_file: []const u8,
title: []const u8 = "<undetermined>",
};
const Website = struct {
const Self = @This();
is_prepared: bool = false,
allocator: std.mem.Allocator,
arena: std.heap.ArenaAllocator,
articles: std.ArrayList(Article),
tutorials: std.ArrayList(Tutorial),
images: std.ArrayList([]const u8),
fn deinit(self: *Self) void {
self.tutorials.deinit();
self.articles.deinit();
self.images.deinit();
self.arena.deinit();
self.* = undefined;
}
fn addArticle(self: *Self, article: Article) !void {
self.is_prepared = false;
try self.articles.append(Article{
.date = article.date,
.src_file = try self.arena.allocator().dupe(u8, article.src_file),
.title = try self.arena.allocator().dupe(u8, article.title),
});
}
fn addTutorial(self: *Self, tutorial: Tutorial) !void {
self.is_prepared = false;
try self.tutorials.append(Tutorial{
.src_file = try self.arena.allocator().dupe(u8, tutorial.src_file),
.title = try self.arena.allocator().dupe(u8, tutorial.title),
});
}
fn addImage(self: *Self, path: []const u8) !void {
self.is_prepared = false;
try self.images.append(try self.arena.allocator().dupe(u8, path));
}
fn findTitle(self: *Self, file: []const u8) !?[]const u8 {
var doc = blk: {
var p = try koino.parser.Parser.init(self.allocator, markdown_options);
defer p.deinit();
const markdown = try std.fs.cwd().readFileAlloc(self.allocator, file, 10_000_000);
defer self.allocator.free(markdown);
try p.feed(markdown);
break :blk try p.finish();
};
defer doc.deinit();
std.debug.assert(doc.data.value == .Document);
var iter = doc.first_child;
var heading_or_null: ?*koino.nodes.AstNode = while (iter) |item| : (iter = item.next) {
if (item.data.value == .Heading) {
if (item.data.value.Heading.level == 1) {
break item;
}
}
} else null;
if (heading_or_null) |heading| {
var list = std.ArrayList(u8).init(self.arena.allocator());
defer list.deinit();
var options = markdown_options;
options.render.header_anchors = false;
try koino.html.print(list.writer(), self.arena.allocator(), options, heading);
const string = list.toOwnedSlice();
std.debug.assert(std.mem.startsWith(u8, string, "<h1>"));
std.debug.assert(std.mem.endsWith(u8, string, "</h1>\n"));
return string[4 .. string.len - 6];
} else {
return null;
}
}
fn prepareRendering(self: *Self) !void {
std.sort.sort(Article, self.articles.items, self.*, sortArticlesDesc);
for (self.articles.items) |*article| {
if (try self.findTitle(article.src_file)) |title| {
article.title = title;
}
}
for (self.tutorials.items) |*tutorial| {
std.debug.print("{s}\n", .{tutorial.src_file});
if (try self.findTitle(tutorial.src_file)) |title| {
tutorial.title = title;
}
}
self.is_prepared = true;
}
fn sortArticlesDesc(self: Self, lhs: Article, rhs: Article) bool {
_ = self;
if (lhs.date.lessThan(rhs.date))
return false;
if (rhs.date.lessThan(lhs.date))
return true;
return (std.mem.order(u8, lhs.title, rhs.title) == .gt);
}
fn removeExtension(src_name: []const u8) []const u8 {
const ext = std.fs.path.extension(src_name);
return src_name[0 .. src_name.len - ext.len];
}
fn changeExtension(self: *Self, src_name: []const u8, new_ext: []const u8) ![]const u8 {
return std.mem.join(self.arena.allocator(), "", &[_][]const u8{
removeExtension(src_name),
new_ext,
});
}
fn urlEscape(self: *Self, text: []const u8) ![]u8 {
const legal_character = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
var len: usize = 0;
for (text) |c| {
len += if (std.mem.indexOfScalar(u8, legal_character, c) == null)
@as(usize, 3)
else
@as(usize, 1);
}
const buf = try self.arena.allocator().alloc(u8, len);
var offset: usize = 0;
for (text) |c| {
if (std.mem.indexOfScalar(u8, legal_character, c) == null) {
const hexdigits = "0123456789ABCDEF";
buf[offset + 0] = '%';
buf[offset + 1] = hexdigits[(c >> 4) & 0xF];
buf[offset + 2] = hexdigits[(c >> 0) & 0xF];
offset += 3;
} else {
buf[offset] = c;
offset += 1;
}
}
return buf;
}
fn renderArticles(self: *Self, dst_dir: std.fs.Dir) !void {
std.debug.assert(self.is_prepared);
for (self.articles.items) |art| {
try self.renderMarkdownFile(
art.src_file,
dst_dir,
try self.changeExtension(std.fs.path.basename(art.src_file), ".htm"),
);
}
}
fn renderTutorials(self: *Self, dst_dir: std.fs.Dir) !void {
std.debug.assert(self.is_prepared);
for (self.tutorials.items) |tut| {
try self.renderMarkdownFile(
tut.src_file,
dst_dir,
try self.changeExtension(std.fs.path.basename(tut.src_file), ".htm"),
);
}
}
/// Renders a list of all possible articles
fn renderArticleIndex(self: *Self, dst_dir: std.fs.Dir, file_name: []const u8) !void {
std.debug.assert(self.is_prepared);
try self.renderMarkdown(
\\# Articles
\\
\\<!-- ARTICLES -->
, dst_dir, file_name);
}
/// Render a given markdown file into `dst_path`.
fn renderMarkdownFile(self: *Self, src_path: []const u8, dst_dir: std.fs.Dir, dst_path: []const u8) !void {
std.debug.assert(self.is_prepared);
var markdown_input = try std.fs.cwd().readFileAlloc(self.allocator, src_path, 10_000_000);
defer self.allocator.free(markdown_input);
try self.renderMarkdown(markdown_input, dst_dir, dst_path);
}
/// Render the given markdown source into `dst_path`.
/// supported features here are:
/// - `<!-- TOC -->` (renders a table of contents with all items that come *after* said TOC
/// - `<!-- ARTICLES10 -->` Renders the 10 latest articles
/// - `<!-- ARTICLES -->` Renders all articles
fn renderMarkdown(self: *Self, source: []const u8, dst_dir: std.fs.Dir, dst_path: []const u8) !void {
std.debug.assert(self.is_prepared);
var doc: *koino.nodes.AstNode = blk: {
var p = try koino.parser.Parser.init(self.arena.allocator(), markdown_options);
try p.feed(source);
defer p.deinit();
break :blk try p.finish();
};
defer doc.deinit();
std.debug.assert(doc.data.value == .Document);
var output_file = try dst_dir.createFile(dst_path, .{});
defer output_file.close();
var writer = output_file.writer();
try self.renderHeader(writer);
{
var renderer = koino.html.makeHtmlFormatter(writer, self.arena.allocator(), markdown_options);
defer renderer.deinit();
var iter = doc.first_child;
while (iter) |item| : (iter = item.next) {
if (item.data.value == .HtmlBlock) {
const raw_string = item.data.value.HtmlBlock.literal.items;
const string = std.mem.trim(u8, raw_string, " \t\r\n");
if (std.mem.eql(u8, string, "<!-- TOC -->")) {
var min_heading_level: ?u8 = null;
var current_heading_level: u8 = undefined;
var heading_options = markdown_options;
heading_options.render.header_anchors = false;
try writer.writeAll("<ul>");
var it = item.next;
while (it) |child| : (it = child.next) {
if (child.data.value == .Heading) {
var heading = child.data.value.Heading;
if (min_heading_level == null) {
min_heading_level = heading.level;
current_heading_level = heading.level;
}
if (heading.level < min_heading_level.?)
continue;
while (current_heading_level > heading.level) {
try writer.writeAll("</ul>");
current_heading_level -= 1;
}
while (current_heading_level < heading.level) {
try writer.writeAll("<ul>");
current_heading_level += 1;
}
try writer.writeAll("<li><a href=\"#");
try writer.writeAll(try renderer.getNodeAnchor(child));
try writer.writeAll("\">");
{
var i = child.first_child;
while (i) |c| : (i = c.next) {
try koino.html.print(
writer,
self.arena.allocator(),
heading_options,
c,
);
}
}
try writer.writeAll("</a>");
while (current_heading_level > heading.level) {
try writer.writeAll("</ul>");
current_heading_level -= 1;
}
try writer.writeAll("</li>");
}
}
if (min_heading_level) |mhl| {
while (current_heading_level > mhl) {
try writer.writeAll("</ul>");
current_heading_level -= 1;
}
}
try writer.writeAll("</ul>");
} else if (std.mem.eql(u8, string, "<!-- ARTICLES -->")) {
for (self.articles.items[0..std.math.min(self.articles.items.len, 10)]) |art| {
try writer.print(
\\<li><a href="articles/{s}.htm">{} - {s}</a></li>
\\
, .{
try self.urlEscape(removeExtension(std.fs.path.basename(art.src_file))),
art.date,
art.title,
});
}
} else if (std.mem.eql(u8, string, "<!-- ARTICLES -->")) {
try writer.writeAll("<ul>\n");
for (self.articles.items[0..std.math.min(self.articles.items.len, 10)]) |art| {
try writer.print(
\\<li><a href="articles/{s}.htm">{} - {s}</a></li>
\\
, .{
try self.urlEscape(removeExtension(std.fs.path.basename(art.src_file))),
art.date,
art.title,
});
}
try writer.writeAll("</ul>\n");
} else {
std.log.err("Unhandled HTML inline: {s}", .{string});
}
} else {
try renderer.format(item, false);
}
}
}
try self.renderFooter(writer);
}
/// Render a given markdown file into `dst_path`.
fn renderHtmlFile(self: *Self, src_path: []const u8, dst_dir: std.fs.Dir, dst_path: []const u8) !void {
std.debug.assert(self.is_prepared);
var html_input = try std.fs.cwd().readFileAlloc(self.allocator, src_path, 10_000_000);
defer self.allocator.free(html_input);
try self.renderHtml(html_input, dst_dir, dst_path);
}
/// Render the html body into `dst_path`.
fn renderHtml(self: Self, source: []const u8, dst_dir: std.fs.Dir, dst_path: []const u8) !void {
std.debug.assert(self.is_prepared);
var output_file = try dst_dir.createFile(dst_path, .{});
defer output_file.close();
var writer = output_file.writer();
try self.renderHeader(writer);
try writer.writeAll(source);
try self.renderFooter(writer);
}
fn renderHeader(self: Self, writer: anytype) !void {
std.debug.assert(self.is_prepared);
try writer.writeAll(
\\<!DOCTYPE html>
\\<html lang="en">
\\
\\<head>
\\ <meta charset="utf-8">
\\ <meta name="viewport" content="width=device-width, initial-scale=1">
\\ <title>ZEG</title>
\\ <link rel="stylesheet" href="style.css">
\\</head>
\\<body>
);
}
fn renderFooter(self: Self, writer: anytype) !void {
std.debug.assert(self.is_prepared);
try writer.writeAll(
\\</body>
\\</html>
\\
);
}
fn renderAtomFeed(self: *Self, dir: std.fs.Dir, file_name: []const u8) !void {
var feed_file = try dir.createFile(file_name, .{});
defer feed_file.close();
var feed_writer = feed_file.writer();
try feed_writer.writeAll(
\\<?xml version="1.0" encoding="utf-8"?>
\\<feed xmlns="http://www.w3.org/2005/Atom">
\\ <author>
\\ <name>Zig Embedded Group</name>
\\ </author>
\\ <title>Zig Embedded Group</title>
\\ <id>https://zeg.random-projects.net/</id>
\\
);
var last_update = Date{ .year = 0, .month = 0, .day = 0 };
var article_count: usize = 0;
for (self.articles.items) |article| {
if (last_update.lessThan(article.date)) {
last_update = article.date;
article_count = 0;
} else {
article_count += 1;
}
}
try feed_writer.print(" <updated>{d:0>4}-{d:0>2}-{d:0>2}T{d:0>2}:00:00Z</updated>\n", .{
last_update.year,
last_update.month,
last_update.day,
article_count, // this is fake, but is just here for creating a incremental version for multiple articles a day
});
for (self.articles.items) |article| {
const uri_name = try self.urlEscape(removeExtension(article.src_file));
try feed_writer.print(
\\ <entry>
\\ <title>{s}</title>
\\ <link href="https://zeg.random-projects.net/articles/{s}.htm" />
\\ <id>zeg.random-projects.net/articles/{s}.htm</id>
\\ <updated>{d:0>4}-{d:0>2}-{d:0>2}T00:00:00Z</updated>
\\ </entry>
\\
, .{
article.title,
uri_name,
uri_name,
article.date.year,
article.date.month,
article.date.day,
});
}
try feed_writer.writeAll("</feed>");
}
fn renderImages(self: Self, target_dir: std.fs.Dir) !void {
for (self.images.items) |img| {
try std.fs.Dir.copyFile(
std.fs.cwd(),
img,
target_dir,
std.fs.path.basename(img),
.{},
);
}
}
// fn renderArticle(self: *Website, article: Article, dst_dir: std.fs.Dir, dst_name: []const u8) !void {
// var formatter = HtmlFormatter.init(allocator, options);
// defer formatter.deinit();
// try formatter.format(root, false);
// return formatter.buffer.toOwnedSlice();
// }
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

@ -0,0 +1,20 @@
<svg id="emoji" viewBox="0 0 72 72" xmlns="http://www.w3.org/2000/svg">
<g id="color">
<path fill="#d0cfce" d="M18.7308,35.288,51.0536,15.1054S63.2017,17.4511,60.7223,29.09l-10.005,8.8024L28.5734,54.9045s3.265-6.7108-.0966-10.6423A87.228,87.228,0,0,0,18.7308,35.288Z"/>
<path fill="#9b9b9a" d="M23.2318,39.0817l-2.924,2.4575s5.1675,1.8864,5.5582,4.8693.112,5.1147-.5208,6.3865-1.5339,3.01-7.7116,3.52c0,0,3.5921,3.6172,5.7058,2.54s6.1977-3.2394,6.3307-4.793.61-8.3143-.6677-9.4794S23.2318,39.0817,23.2318,39.0817Z"/>
<path fill="#d0cfce" d="M16.3468,56.4944s-3.09.5421-3.54-9.2745c0,0,2.204-5.6592,5.4531-4.9507,0,0,6.3952.8884,4.3931,6.403,0,0-2.7457-4.7324-4.8049-1.9766,0,0-2.4963,5.449,1.3831,7.4591,0,0,3.8718.7912,4.6268,0S22.653,58.8549,16.3468,56.4944Z"/>
<path fill="#9b9b9a" d="M21.4581,43.3374a6.4586,6.4586,0,0,1,.4513,5.5146s-.206,4.5555-3.65,3.057c0,0,.5034,2.3376,3.7639,2.0979,0,0,5.3319-.4781,4.2206-6.787C26.2444,47.22,24.153,43.3974,21.4581,43.3374Z"/>
<path fill="#d0cfce" d="M21.4581,48.1926s-.8292-2.5175-3.049-1.6184c0,0-1.9053,2.5775-.7755,4.7953a2.27,2.27,0,0,0,1.0972,1.7982s3.1907-.6855,3.07-3.52Z"/>
<path fill="#3f3f3f" d="M46.6436,40.8149a12.54,12.54,0,0,0-5.1888-14.2023L29.868,33.8018s9.4553,5.7543,6.8227,15.3448Z"/>
</g>
<g id="line">
<path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M48.0744,16.79c1.5539-.9135,3.2775-2.0476,5.1366-1.6845A11.6435,11.6435,0,0,1,62,24.3155c.5589,2.9164-1.3411,5.6107-3.4232,7.2949"/>
<path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M28.666,44.5825s-2.967-5.306-10.4061-8.9976L48.0744,16.79"/>
<line x1="26.9184" x2="58.577" y1="56.914" y2="31.6104" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/>
<path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21.9094,48.852c.0858-3.1169-2.2309-2.9971-2.2309-2.9971-1.6223-.103-2.3394,1.3973-2.6559,2.6974a5.2371,5.2371,0,0,0,.3447,3.504c2.09,4.31,6.5755,1.9634,6.5755,1.9634a5.8246,5.8246,0,0,0,2.5659-5.6731,3.9532,3.9532,0,0,0-.3332-1.3079,7.6974,7.6974,0,0,0-5.41-4.7587,6.4607,6.4607,0,0,0-7.1889,3.8446,11.6134,11.6134,0,0,0-.5913,1.6671,4.9354,4.9354,0,0,0-.1586.9214,9.557,9.557,0,0,0,4.0008,8.6531c7.2447,4.4384,11.7463-2.4617,11.7463-2.4617a9.2057,9.2057,0,0,0,.8758-8.6C27.179,39.86,18.26,35.5849,18.26,35.5849"/>
<path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16.3468,43.0114c.3429-.1894,6.3062-3.93,6.3062-3.93"/>
<path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M53.34,35.5849c.1579-9.66-7.0056-12.9869-7.0056-12.9869"/>
<path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M58.577,31.61A11.3593,11.3593,0,0,0,53.1562,20.86"/>
<path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M46.6436,40.8149a12.54,12.54,0,0,0-5.1888-14.2023L29.868,33.8018s9.4553,5.7543,6.8227,15.3448Z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

@ -0,0 +1,21 @@
<?xml version="1.0"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="1em" height="1em" id="RSSicon" viewBox="0 0 256 256">
<defs>
<linearGradient x1="0.085" y1="0.085" x2="0.915" y2="0.915" id="RSSg">
<stop offset="0.0" stop-color="#E3702D" />
<stop offset="0.1071" stop-color="#EA7D31" />
<stop offset="0.3503" stop-color="#F69537" />
<stop offset="0.5" stop-color="#FB9E3A" />
<stop offset="0.7016" stop-color="#EA7C31" />
<stop offset="0.8866" stop-color="#DE642B" />
<stop offset="1.0" stop-color="#D95B29" />
</linearGradient>
</defs>
<rect width="256" height="256" rx="55" ry="55" x="0" y="0" fill="#CC5D15" />
<rect width="246" height="246" rx="50" ry="50" x="5" y="5" fill="#F49C52" />
<rect width="236" height="236" rx="47" ry="47" x="10" y="10" fill="url(#RSSg)" />
<circle cx="68" cy="189" r="24" fill="#FFF" />
<path d="M160 213h-34a82 82 0 0 0 -82 -82v-34a116 116 0 0 1 116 116z" fill="#FFF" />
<path d="M184 213A140 140 0 0 0 44 73 V 38a175 175 0 0 1 175 175z" fill="#FFF" />
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@ -0,0 +1,14 @@
<svg id="emoji" viewBox="0 0 72 72" xmlns="http://www.w3.org/2000/svg">
<g id="color">
<path fill="#d0cfce" stroke="none" d="M59.971,42.7985c0-0.5523,0.4477-1,1-1h4.143v-9.183h-4.143c-0.5523,0-1-0.4477-1-1 M59.971,31.6155v-7.159 H6.662v25.013h53.309v-6.671"/>
<path fill="#d0cfce" stroke="none" d="M61.083,32.3485c-0.5523,0-1-0.4477-1-1v-7.159H36.967H6.773v25.013h11.736h41.577v-6.671 c0-0.5523,0.4477-1,1-1h4.143v-9.183H61.083z"/>
<path fill="#b1cc33" stroke="none" d="M12.002,45.3665h32.239v-17.007H12.002V45.3665z"/>
</g>
<g id="hair"/>
<g id="skin"/>
<g id="skin-shadow"/>
<g id="line">
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M61.081,31.3475v-7.159 c0-0.5523-0.4477-1-1-1H6.772c-0.5523,0-1,0.4477-1,1v25.016c0,0.5523,0.4477,1,1,1h53.313c0.5523,0,1-0.4477,1-1v-6.674h5.143 v-11.183H61.081z"/>
<rect x="11" y="27.3595" width="34.239" height="19.007" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

@ -0,0 +1,10 @@
<svg width="71" height="55" viewBox="0 0 71 55" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0)">
<path d="M60.1045 4.8978C55.5792 2.8214 50.7265 1.2916 45.6527 0.41542C45.5603 0.39851 45.468 0.440769 45.4204 0.525289C44.7963 1.6353 44.105 3.0834 43.6209 4.2216C38.1637 3.4046 32.7345 3.4046 27.3892 4.2216C26.905 3.0581 26.1886 1.6353 25.5617 0.525289C25.5141 0.443589 25.4218 0.40133 25.3294 0.41542C20.2584 1.2888 15.4057 2.8186 10.8776 4.8978C10.8384 4.9147 10.8048 4.9429 10.7825 4.9795C1.57795 18.7309 -0.943561 32.1443 0.293408 45.3914C0.299005 45.4562 0.335386 45.5182 0.385761 45.5576C6.45866 50.0174 12.3413 52.7249 18.1147 54.5195C18.2071 54.5477 18.305 54.5139 18.3638 54.4378C19.7295 52.5728 20.9469 50.6063 21.9907 48.5383C22.0523 48.4172 21.9935 48.2735 21.8676 48.2256C19.9366 47.4931 18.0979 46.6 16.3292 45.5858C16.1893 45.5041 16.1781 45.304 16.3068 45.2082C16.679 44.9293 17.0513 44.6391 17.4067 44.3461C17.471 44.2926 17.5606 44.2813 17.6362 44.3151C29.2558 49.6202 41.8354 49.6202 53.3179 44.3151C53.3935 44.2785 53.4831 44.2898 53.5502 44.3433C53.9057 44.6363 54.2779 44.9293 54.6529 45.2082C54.7816 45.304 54.7732 45.5041 54.6333 45.5858C52.8646 46.6197 51.0259 47.4931 49.0921 48.2228C48.9662 48.2707 48.9102 48.4172 48.9718 48.5383C50.038 50.6034 51.2554 52.5699 52.5959 54.435C52.6519 54.5139 52.7526 54.5477 52.845 54.5195C58.6464 52.7249 64.529 50.0174 70.6019 45.5576C70.6551 45.5182 70.6887 45.459 70.6943 45.3942C72.1747 30.0791 68.2147 16.7757 60.1968 4.9823C60.1772 4.9429 60.1437 4.9147 60.1045 4.8978ZM23.7259 37.3253C20.2276 37.3253 17.3451 34.1136 17.3451 30.1693C17.3451 26.225 20.1717 23.0133 23.7259 23.0133C27.308 23.0133 30.1626 26.2532 30.1066 30.1693C30.1066 34.1136 27.28 37.3253 23.7259 37.3253ZM47.3178 37.3253C43.8196 37.3253 40.9371 34.1136 40.9371 30.1693C40.9371 26.225 43.7636 23.0133 47.3178 23.0133C50.9 23.0133 53.7545 26.2532 53.6986 30.1693C53.6986 34.1136 50.9 37.3253 47.3178 37.3253Z" fill="#5865F2"/>
</g>
<defs>
<clipPath id="clip0">
<rect width="71" height="55" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg viewBox="0 0 60 60" xmlns="http://www.w3.org/2000/svg">
<path transform="matrix(2.6872 -1.389e-16 1.389e-16 2.6872 8.7902 -72.349)" d="m11.93414,39.67663c0-.62109.13281-1.19336.39844-1.7168 .26563-.52734.63672-.94141 1.11328-1.24219s1.02734-.45117 1.64063-.45117c.32422,0 .6582.03906 1.00195.11719 .34375.07422.63281.20313.86719.38672s.35156.43164.35156.74414c0,.25-.07422.45898-.22266.62695-.14453.16797-.32617.25195-.54492.25195-.10547,0-.20898-.01758-.31055-.05273l-.29883-.11133c-.09766-.04297-.20313-.07813-.31641-.10547s-.24805-.04102-.41602-.04102c-.30469,0-.5625.07227-.77344.2168s-.36328.33203-.46875.57422c-.10156.23828-.15234.50586-.15234.80273 0,.28906.05078.55859.15234.80859s.24805.44141.43945.58594c.19531.14453.42578.2168.69141.2168 .30078,0 .60352-.05664.9082-.16992s.48633-.16992.54492-.16992c.14063,0 .26953.04102.38672.12305s.20898.18945.27539.32227 .10547.26172.10547.39844c0,.28906-.12695.5332-.38086.73242s-.54883.34375-.89648.43359-.66211.14063-.94336.14063c-.46484,0-.89063-.08594-1.27734-.25781-.38281-.17188-.7168-.41602-1.00195-.73242s-.50195-.6875-.65039-1.10156c-.14844-.41797-.22266-.86133-.22266-1.33008zm-3.97266,2.31445v-4.62891c0-.27734.08594-.50781.25781-.69141 .17578-.1875.40039-.28125.67383-.28125 .22656,0 .42188.06055.58594.18164 .16797.11719.28516.25586.35156.41602 .40234-.48047.83398-.7207 1.29492-.7207 .25391,0 .47656.0918.66797.27539 .19531.17969.29297.41211.29297.69727s-.0918.50586-.27539.63867-.47852.25977-.89649.38086c-.42578.12109-.71289.27344-.86133.45703s-.22266.50391-.22266.97266v2.30273c0,.27734-.08789.51172-.26367.70313-.17188.1875-.39648.28125-.67383.28125-.27344,0-.49805-.09375-.67383-.28125-.17188-.19141-.25781-.42578-.25781-.70313zm-2.70117,0v-4.62891c0-.27734.08594-.50781.25781-.69141 .17578-.1875.40039-.28125.67383-.28125 .27734,0 .50195.09375.67383.28125 .17578.18359.26367.41406.26367.69141v4.62891c0,.27734-.08789.51172-.26367.70313-.17188.1875-.39648.28125-.67383.28125-.27344,0-.49805-.09375-.67383-.28125-.17188-.19141-.25781-.42578-.25781-.70313zm-.18164-7.29492c0-.30469.10938-.56836.32813-.79102s.48047-.33398.78516-.33398c.30859,0 .57227.10938.79102.32813s.32813.48438.32813.79688c0,.32422-.10938.59375-.32813.80859s-.48242.32227-.79102.32227c-.30469,0-.56641-.10938-.78516-.32813s-.32813-.48633-.32813-.80273zm-6.03516,7.35938 .21094-1.57617h-.45117c-.19922,0-.3457-.05664-.43945-.16992-.09375-.11719-.14063-.25586-.14063-.41602s.04688-.29492.14063-.4043 .24023-.16992.43945-.16992h.60938l.19336-1.44141h-.45703c-.19531,0-.33984-.05664-.43359-.16992-.09375-.11719-.14063-.25586-.14063-.41602s.04688-.30078.14063-.41016 .23828-.16992.43359-.16992h.63281l.25781-1.88086c.02734-.19922.08984-.35352.1875-.46289s.26172-.16406.48047-.16406c.15625,0 .28516.04492.38672.13477s.15234.20508.15234.35742c0,.06641-.01563.21094-.04688.43359l-.2168,1.58203h1.08398l.25195-1.88086c.02734-.19922.08984-.35352.1875-.46289s.26172-.16406.48047-.16406c.15625,0 .28516.04492.38672.13477s.15234.20898.15234.35742c0,.06641-.01563.21094-.04688.43359l-.2168,1.58203h.45703c.20313,0 .34961.05664.43945.16992s.14063.24609.14063.41016-.04688.29883-.14063.41602c-.09375.11328-.24023.16992-.43945.16992h-.60938l-.19336,1.44141h.45703c.20313,0 .34961.05469.43945.16406s.13477.24609.13477.41016-.04688.30273-.14063.41602-.23438.16992-.43359.16992h-.63867l-.25195,1.875c-.02734.19531-.08984.34766-.1875.45703s-.25781.16406-.48047.16406c-.15625,0-.28711-.04297-.39258-.12891-.10156-.08984-.15234-.21094-.15234-.36328 0-.07813.01758-.2207.05273-.42773l.2168-1.57617h-1.08398l-.25195,1.875c-.02734.19922-.0918.35352-.19336.46289s-.25781.1582-.48047.1582c-.15234,0-.28125-.04297-.38672-.12891-.10156-.08984-.15234-.21094-.15234-.36328 0-.07813.01758-.2207.05273-.42773zm2.67188-2.73633 .19336-1.44141h-1.08984l-.19336,1.44141h1.08984z" fill="#333"/>
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

@ -0,0 +1,17 @@
<svg id="emoji" viewBox="0 0 72 72" xmlns="http://www.w3.org/2000/svg">
<g id="color">
<rect x="11" y="16.0833" width="50" height="39.8333" fill="#d0cfce" stroke="none"/>
</g>
<g id="hair"/>
<g id="skin"/>
<g id="skin-shadow"/>
<g id="line">
<rect x="11" y="16.0009" width="50" height="39.9982" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2"/>
<polyline fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" points="16.3287,16.4792 16.3287,20.8542 11,20.8542 61,20.8542"/>
<line x1="28.8333" x2="21.9062" y1="30.3947" y2="37.3218" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2"/>
<line x1="28.8333" x2="21.9062" y1="44.3166" y2="37.3895" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2"/>
<line x1="38.1836" x2="32.8086" y1="28.1523" y2="46.25" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2"/>
<line x1="42.1588" x2="49.0859" y1="44.2515" y2="37.3244" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2"/>
<line x1="42.1588" x2="49.0859" y1="30.3296" y2="37.2567" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

@ -0,0 +1,18 @@
<svg id="emoji" viewBox="0 0 72 72" xmlns="http://www.w3.org/2000/svg">
<g id="color">
<path fill="#fff" d="m17.09 50.75c-3.184-4.073-5.088-9.191-5.088-14.75 0-13.23 10.77-24 24-24s24 10.77 24 24-10.77 24-24 24c-1.955 0-3.855-0.2406-5.676-0.6839-5.919-1.481-10.33-4.341-13.24-8.567z"/>
<circle cx="23.48" cy="54.97" r="5" fill="#fff"/>
<circle cx="-15.8" cy="62.94" r="2.5" transform="scale(-1,1)" fill="#fff"/>
</g>
<g id="hair"/>
<g id="skin"/>
<g id="skin-shadow"/>
<g id="line">
<path fill="none" stroke="#000" stroke-linecap="round" stroke-miterlimit="10" stroke-width="2" d="m31.54 58.56c1.442 0.2858 2.93 0.4391 4.455 0.4391 12.68 0 23-10.32 23-23s-10.32-23-23-23-23 10.32-23 23c0 4.913 1.552 9.467 4.187 13.21"/>
<circle cx="26.79" cy="36" r="2"/>
<circle cx="36" cy="36" r="2"/>
<circle cx="45.21" cy="36" r="2"/>
<circle cx="-4.026" cy="64.73" r="2.5" transform="matrix(-.9831 .1829 .1829 .9831 0 0)" fill="none" stroke="#000" stroke-miterlimit="10" stroke-width="2"/>
<circle cx="-13.58" cy="57.88" r="4.981" transform="matrix(-.9831 .1829 .1829 .9831 0 0)" fill="none" stroke="#000" stroke-miterlimit="10" stroke-width="2"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 19 KiB

@ -0,0 +1,35 @@
<svg id="emoji" viewBox="0 0 72 72" xmlns="http://www.w3.org/2000/svg">
<g id="color">
<rect x="22.3576" y="17.2677" width="6.0019" height="6.0019" fill="#FFFFFF" stroke="none"/>
<polygon fill="#FFFFFF" stroke="none" points="28.3595,33.8934 22.3576,35.2734 22.3576,29.2715 28.3595,29.2715"/>
<rect x="28.3595" y="23.2696" width="6.0019" height="6.0019" fill="#FFFFFF" stroke="none"/>
<rect x="34.3615" y="17.2677" width="6.0019" height="6.0019" fill="#FFFFFF" stroke="none"/>
<rect x="28.3595" y="14.9868" width="6.0019" height="2.2809" fill="#FFFFFF" stroke="none"/>
<polygon fill="#FFFFFF" stroke="none" points="40.3634,35.2734 34.3615,33.822 34.3615,29.2715 40.3634,29.2715"/>
<rect x="40.3634" y="23.2696" width="6.0019" height="6.0019" fill="#FFFFFF" stroke="none"/>
<rect x="46.3653" y="20.2686" width="6.0019" height="3.001" fill="#FFFFFF" stroke="none"/>
<rect x="46.3653" y="29.2715" width="6.0019" height="6.0019" fill="#FFFFFF" stroke="none"/>
<rect x="52.3672" y="23.2696" width="1.7444" height="6.0019" fill="#FFFFFF" stroke="none"/>
<rect x="40.3634" y="35.2734" width="6.0019" height="1.9308" fill="#FFFFFF" stroke="none"/>
<rect x="52.3672" y="35.2734" width="2.1134" height="2.4038" fill="#FFFFFF" stroke="none"/>
</g>
<g id="hair"/>
<g id="skin"/>
<g id="skin-shadow"/>
<g id="line">
<rect x="22.3576" y="23.2696" width="6.0019" height="6.0019" fill="#000000" stroke="none"/>
<rect x="28.3595" y="17.2677" width="6.0019" height="6.0019" fill="#000000" stroke="none"/>
<rect x="28.3595" y="29.2715" width="6.0019" height="5.1396" fill="#000000" stroke="none"/>
<polygon fill="#000000" stroke="none" points="28.3595,17.2677 22.3576,17.2677 23.0418,16.6469 28.3595,16.0262"/>
<polygon fill="#000000" stroke="none" points="40.3634,17.2677 34.3615,17.2677 34.3615,15.6486 40.3634,16.621"/>
<rect x="34.3615" y="23.2696" width="6.0019" height="6.0019" fill="#000000" stroke="none"/>
<polygon fill="#000000" stroke="none" points="46.3653,23.2696 40.3634,23.2696 40.3634,18.6459 46.3653,19.9674"/>
<rect x="40.3634" y="29.2715" width="6.0019" height="6.0019" fill="#000000" stroke="none"/>
<rect x="46.3653" y="23.2696" width="6.0019" height="6.0019" fill="#000000" stroke="none"/>
<rect x="52.3672" y="20.3094" width="1.7444" height="2.9602" fill="#000000" stroke="none"/>
<rect x="52.3672" y="29.2715" width="1.9094" height="6.0019" fill="#000000" stroke="none"/>
<rect x="46.3653" y="35.2734" width="6.0019" height="3.4735" fill="#000000" stroke="none"/>
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M23.0844,17.488C25.9694,15.6897,31.5062,13.2554,37.5,16c3.689,1.6892,5.6344,4.6976,15.0405,4.1142 C53.3291,20.0653,54,20.7109,54,21.5248c0,3.3025,0,11.1393,0,15.1253c0,0.6226-0.4235,1.0771-0.9648,1.354 C51.0882,39,45.7637,38.939,37.5,35c-3.3653-1.6041-8.5703-2.5683-14.4582,1.4425"/>
<line x1="19.3271" x2="19.3271" y1="12.875" y2="59.8348" fill="none" stroke="#000000" stroke-linecap="round" stroke-miterlimit="10" stroke-width="2"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

@ -0,0 +1,15 @@
<svg id="emoji" viewBox="0 0 72 72" xmlns="http://www.w3.org/2000/svg">
<g id="color">
<path fill="#D0CFCE" stroke="none" d="M63,61c0,0,0-4,0-8c0-7-4-10-8-10c-5,0-5,0-12,0c-2.7842,0-5.5645,0.5059-7,3.75 c-1.125-3.3125-4.2158-4.7031-7-4.7031C24,42.0469,24,43,17,43c-4,0-8,3-8,10c0,1,0,8,0,8"/>
<path fill="#D0CFCE" stroke="none" d="M15,26c0,4,0.876,6.3145,2,8c1.4316,2.1484,3.7061,3,6,3c2.3809,0,4.5664-0.8516,6-3c1.123-1.6855,2-4,2-8 c0-2.1484-1.0771-9-8-9S15,22.4189,15,26z"/>
<path fill="#D0CFCE" stroke="none" d="M41,26c0,4,0.876,6.3145,2,8c1.4316,2.1484,3.7061,3,6,3c2.3809,0,4.5664-0.8516,6-3c1.123-1.6855,2-4,2-8 c0-2.1484-1.0771-9-8-9C42.0771,17,41,22.4189,41,26z"/>
</g>
<g id="hair"/>
<g id="skin"/>
<g id="skin-shadow"/>
<g id="line">
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M35,60v-7c0-7,4-10,8-10 c7,0,7,0,12,0c4,0,8,3,8,10c0,4,0,7,0,7 M34,45.0977 M9,60c0,0,0-6,0-7c0-7,4-10,8-10c7,0,7,0,12,0 c1.5313,0,3.0605,0.5313,4.3672,1.5488"/>
<path fill="none" stroke="#000000" stroke-linejoin="round" stroke-width="2" d="M15,26c0,4,0.876,6.3145,2,8 c1.4316,2.1484,3.7061,3,6,3c2.3809,0,4.5664-0.8516,6-3c1.123-1.6855,2-4,2-8c0-2.1484-1.0771-9-8-9S15,22.4189,15,26z"/>
<path fill="none" stroke="#000000" stroke-linejoin="round" stroke-width="2" d="M41,26c0,4,0.876,6.3145,2,8 c1.4316,2.1484,3.7061,3,6,3c2.3809,0,4.5664-0.8516,6-3c1.123-1.6855,2-4,2-8c0-2.1484-1.0771-9-8-9C42.0771,17,41,22.4189,41,26z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

@ -0,0 +1,21 @@
<svg id="emoji" viewBox="0 0 72 72" xmlns="http://www.w3.org/2000/svg">
<g id="color">
<rect x="11" y="22" width="50" height="28" fill="#d0cfce"/>
<polygon fill="#9b9b9a" points="61 50 50.685 50 36.512 22 61 22 61 50"/>
<rect x="16" y="27" width="33" height="11.2" fill="#b1cc33"/>
<polygon fill="#5c9e31" points="49 38.2 41.512 38.2 35.843 27 49 27 49 38.2"/>
</g>
<g id="line">
<line x1="11" x2="11" y1="50" y2="22" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/>
<line x1="61" x2="11" y1="50" y2="50" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/>
<line x1="61" x2="61" y1="22" y2="50" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/>
<line x1="11" x2="61" y1="22" y2="22" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/>
<line x1="16" x2="16" y1="38.2" y2="27" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8"/>
<line x1="49" x2="16" y1="38.2" y2="38.2" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8"/>
<line x1="49" x2="49" y1="27" y2="38.2" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8"/>
<line x1="16" x2="49" y1="27" y2="27" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8"/>
<circle cx="20.6383" cy="44.1" r="2"/>
<circle cx="28.6383" cy="44.1" r="2"/>
<circle cx="36.6383" cy="44.1" r="2"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

@ -0,0 +1,11 @@
<svg id="emoji" viewBox="0 0 72 72" xmlns="http://www.w3.org/2000/svg">
<g id="color">
<polygon id="_x2197__xFE0F__1_" fill="#3F3F3F" stroke="none" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" points="35.134,14.0387 59.3629,13.1416 58.4658,37.3705 52.9078,37.1647 53.4324,23.0048 17.5399,58.8973 13.6073,54.9646 49.4998,19.0721 35.3399,19.5967"/>
</g>
<g id="hair"/>
<g id="skin"/>
<g id="skin-shadow"/>
<g id="line">
<polygon id="_x2197__xFE0F__1_" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" points="35.134,14.0387 59.3629,13.1416 58.4658,37.3705 52.9078,37.1647 53.4324,23.0048 17.5399,58.8973 13.6073,54.9646 49.4998,19.0721 35.3399,19.5967"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 796 B

@ -0,0 +1,32 @@
<svg id="emoji" viewBox="0 0 72 72" version="1.1" xmlns="http://www.w3.org/2000/svg">
<g id="color">
<polyline fill="#5C9E31" points="13.8857,52.0966 10.8304,52.0966 10.8304,16.9138 19.8222,16.9138 36.3277,16.9138 52.0393,16.9138 60.7657,16.9138 60.7657,52.0966 56.6808,52.0966"/>
<path fill="#6A462F" d="M53.8795,58.9993c0,0,2.7-14.1375-9.3-14.1375c-3.1919,2.1192-5.9264,3.5837-9,3.5775h0.125 c-3.0736,0.0062-5.8082-1.4583-9-3.5775c-12,0-10,14.1375-10,14.1375"/>
<polygon fill="#3F3F3F" points="44.5804,45.3645 47.1504,54.4045 41.5304,54.1645 41.6204,58.9545 35.7404,57.3545 35.8304,57.3245 35.7404,57.1945"/>
<path fill="#D0CFCE" d="M44.4804,45.2145l0.1,0.15l-8.84,11.83l-8.85-11.83l0.16-0.24c3.08,2.05,5.75,3.46,8.72,3.49 c0.01,0,0.02,0,0.03,0h0.09C38.8204,48.5845,41.4504,47.2145,44.4804,45.2145z"/>
<polygon fill="#3F3F3F" points="35.7404,57.1945 35.6404,57.3245 35.7404,57.3545 29.8504,58.9545 29.9404,54.1645 24.3204,54.4045 26.8904,45.3645"/>
</g>
<g id="skin-shadow"/>
<g id="hair">
<path fill="#A57939" d="M26,36.5808c-4,0-4-6-4-13s4-14,14-14s14,7,14,14s0,13-4,13"/>
</g>
<g id="skin">
<path fill="#FCEA2B" d="M24.9365,28.5808c0,9,4.9365,14,11,14c5.9365,0,11.0635-5,11.0635-14c0.0245-1.7187-0.3164-3.4229-1-5 c-3-3-7-8-7-8c-4,3-7,6-13,7.0005C26,22.5808,24.9365,23.58,24.9365,28.5808z"/>
</g>
<g id="line">
<path d="M22.9654,17.9098H11.83v32.29h2.06c0.55,0,1,0.44,1,1c0,0.55-0.45,1-1,1h-3.06c-0.55,0-1-0.45-1-1v-34.29 c0-0.55,0.45-1,1-1l13.0581,0L22.9654,17.9098z"/>
<path d="M48.15,15.9098h12.62c0.55,0,1,0.4499,1,1v34.29c0,0.55-0.45,1-1,1h-2.59c-0.55,0-1-0.45-1-1c0-0.56,0.45-1,1-1h1.59 v-32.29H49.0303L48.15,15.9098z"/>
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M27.4414,25.171"/>
<path d="M47.15,27.2997h-1.96v1.24c0,2.11-1.71,3.82-3.81,3.82h-2.01c-1.55,0-2.88-0.93-3.48-2.25c-0.6,1.32-1.94,2.25-3.48,2.25 H30.4c-2.11,0-3.82-1.71-3.82-3.82v-1.24h-0.69c-0.45,0-0.83-0.3-0.95-0.71c0.23-0.38,0.44-0.8,0.62-1.23 c0.1-0.04,0.21-0.06,0.33-0.06h1.69c0.05,0,0.1,0,0.15,0.01c0.49,0.08,0.85,0.49,0.85,0.99v2.24c0,1,0.82,1.82,1.82,1.82h2.01 c1,0,1.81-0.82,1.81-1.82v-1.1c0-0.55,0.45-1,1-1h1.33c0.56,0,1,0.45,1,1v1.1c0,1,0.82,1.82,1.82,1.82h2.01 c1,0,1.81-0.82,1.81-1.82v-2.24c0-0.55,0.45-1,1-1h2.406L47.15,27.2997z"/>
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M16.8904,58.0245c0,0-2-13,10-13c0.05,0.04,0.11,0.07,0.16,0.1c3.08,2.05,5.75,3.46,8.72,3.49c0.01,0,0.02,0,0.03,0h0.09 c2.93-0.03,5.56-1.4,8.59-3.4c0.1-0.06,0.19-0.12,0.29-0.19c3.99,0,6.43,1.44,7.92,3.36l0.78,1.26"/>
<polygon fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" points="35.7404,57.1945 35.8304,57.3245 35.7404,57.3545 29.8504,58.9545 29.9404,54.1645 24.3204,54.4045 26.8904,45.3645"/>
<polygon fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" points="35.7404,57.1945 35.6404,57.3245 35.7404,57.3545 41.6204,58.9545 41.5304,54.1645 47.1504,54.4045 44.5804,45.3645"/>
<path d="M53.0441,58.7525c-0.5096,0-0.9023-0.4491-0.8345-0.9541l3.3718-26.5033L54.1676,57.967 C54.1379,58.4091,53.4872,58.7525,53.0441,58.7525L53.0441,58.7525z"/>
<path d="M53.0439,59.5029c-0.4599,0-0.8964-0.1982-1.1982-0.5439c-0.3027-0.3467-0.4404-0.8057-0.3789-1.2608l3.3701-26.498 c0.0508-0.3994,0.3975-0.6895,0.8115-0.6523c0.4014,0.0361,0.7032,0.3837,0.6817,0.7871L54.917,58.0068 C54.8506,58.9746,53.7676,59.5029,53.0439,59.5029z M53.7539,51.6025l-0.7998,6.2911l0.0898,0.1093 c0.1153,0,0.3028-0.0859,0.379-0.1572L53.7539,51.6025z"/>
<path d="M41.9529,27.0468c0,1.1046-0.8954,2-2,2s-2-0.8954-2-2s0.8954-2,2-2C41.0573,25.0472,41.9525,25.9424,41.9529,27.0468"/>
<path d="M33.9529,27.0468c0,1.1046-0.8954,2-2,2c-1.1046,0-2-0.8954-2-2s0.8954-2,2-2 C33.0573,25.0472,33.9525,25.9424,33.9529,27.0468"/>
<path d="M35.9527,37.0492c-1.2005-0.0116-2.3813-0.3055-3.4472-0.8579c-0.494-0.247-0.6943-0.8477-0.4473-1.3418 s0.8477-0.6943,1.3418-0.4473c1.5934,0.8593,3.5121,0.8593,5.1055,0c0.494-0.247,1.0947-0.0468,1.3418,0.4473 s0.0468,1.0947-0.4473,1.3418C38.3341,36.7437,37.1532,37.0376,35.9527,37.0492z"/>
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M26,36.5808c-4,0-4-6-4-13 s4-14,14-14s14,7,14,14s0,13-4,13"/>
<path fill="none" stroke="#000000" stroke-linejoin="round" stroke-width="2" d="M24.9365,28.5808c0,9,4.9365,14,11,14 c5.9365,0,11.0635-5,11.0635-14c0.0245-1.7187-0.3164-3.4229-1-5c-3-3-7-8-7-8c-4,3-7,6-13,7.0005 C26,22.5808,24.9365,23.58,24.9365,28.5808z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.7 KiB

@ -0,0 +1,213 @@
/* Limit the text width of the body to roughly 40 characters
body {
max-width: 40em;
margin-left: auto;
margin-right: auto;
font-family: sans;
}
@media screen and (max-width: 600px) {
body {
padding: 2em;
}
}
// Align top-level headings
h1 {
text-align: center;
}
// Make images in headings and links exactly 1 character high.
h1 img, h2 img, h3 img, h3 img, h4 img, h5 img, h6 img, a img {
width: 1em;
height: 1em;
vertical-align: middle;
}
// center images in a paragraph and display them as a block
p > img {
display: block;
max-width: 100%;
margin-left: auto;
margin-right: auto;
}
// Make nice top-level codeblocks
body > pre {
background-color: #EEE;
padding: 0.5em;
}
// Make nice top-level blockquotes
body > blockquote {
border-left: 3pt solid cornflowerblue;
padding-left: 0.5em;
margin-left: 0.5em;
}
// Make links in headings invisible
h1 a, h2 a, h3 a, h4 a, h5 a, h6 a {
text-decoration: none;
font-weight: lighter;
color: unset;
opacity: 10%;
margin-left: -1.5em;
padding-left: 0.5em;
}
h1:hover a, h2:hover a, h3:hover a, h4:hover a, h5:hover a, h6:hover a {
opacity: 50%;
}*/
@import "https://rsms.me/inter/inter.css";
@import "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css";
html {
font-family: "Inter", "Arial", sans-serif
}
@supports(font-variation-settings: normal) {
html {
font-family: "Inter var", "Arial", sans-serif
}
}
* {
box-sizing: border-box
}
html,
body {
margin: 0
}
h1 {
margin: 0;
font-size: 24pt;
font-weight: 700;
letter-spacing: -2px
}
h2 {
font-size: 18pt;
font-weight: 600
}
ul.bars {
margin: 0;
padding: 0;
list-style: none
}
ul.bars li {
border-bottom: 1px solid gray;
margin-bottom: 15px;
padding-bottom: 15px;
font-size: 12pt
}
a {
color: #5c8ebf
}
strong,
strong a {
color: #f7a41d
}
#content {
padding: 80px
}
#intro-nav {
display: flex;
margin-bottom: 20px;
justify-content: space-between
}
#intro-grid {
display: grid;
gap: 40px;
grid-auto-rows: 1fr;
grid-template-columns: 1fr 1fr 1fr
}
@media only screen and (max-width: 1200px) {
#intro {
padding: 50px;
}
#intro-grid {
grid-template-columns: 1fr 1fr
}
}
@media only screen and (max-width: 700px) {
#intro {
padding: 20px;
}
#intro-grid {
display: block
}
#intro-grid>* {
display: block;
margin-bottom: 40px
}
}
#intro-grid a {
text-decoration: none
}
#docs {
display: flex
}
#docs-nav {
border-right: 1px solid gray;
padding: 40px 0px;
width: 20%
}
#docs-nav-header {
display: flex;
align-items: center;
flex-direction: column
}
#docs-body {
width: 80%;
padding: 40px
}
a[href^="http"]::after,
a[href^="https://"]::after {
content: "";
width: 11px;
height: 11px;
margin-left: 4px;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' stroke='%235c8ebf' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M8.636 3.5a.5.5 0 0 0-.5-.5H1.5A1.5 1.5 0 0 0 0 4.5v10A1.5 1.5 0 0 0 1.5 16h10a1.5 1.5 0 0 0 1.5-1.5V7.864a.5.5 0 0 0-1 0V14.5a.5.5 0 0 1-.5.5h-10a.5.5 0 0 1-.5-.5v-10a.5.5 0 0 1 .5-.5h6.636a.5.5 0 0 0 .5-.5z'/%3E%3Cpath fill-rule='evenodd' d='M16 .5a.5.5 0 0 0-.5-.5h-5a.5.5 0 0 0 0 1h3.793L6.146 9.146a.5.5 0 1 0 .708.708L15 1.707V5.5a.5.5 0 0 0 1 0v-5z'/%3E%3C/svg%3E");
background-position: center;
background-repeat: no-repeat;
background-size: contain;
display: inline-block;
}
img.inline {
display: inline;
max-height: 1.2em;
max-width: 1.2em;
vertical-align: middle;
}
h2 svg {
display: inline;
max-height: 1.1em;
vertical-align: middle;
max-width: 1.1em;
margin-right: 0.25em;
}
#intro-nav a {
text-decoration: none;
}
Loading…
Cancel
Save