>) -> Waker {
+ Waker::from(Arc::new(TaskWaker {
+ task_id,
+ task_queue,
+ }))
+ }
+}
+```
+
+渡された `task_id` と `task_queue` を使って `TaskWaker` を作成します。次に `TaskWaker` を `Arc` で囲み、`Waker::from` の実装を使用してそれを [`Waker`] に変換します。この `from` メソッドは、`TaskWaker` 型の [`RawWakerVTable`] と [`RawWaker`] インスタンスの構築を行います。このメソッドの詳細について興味のある場合は、[`alloc`クレート内での実装][waker-from-impl]をご覧ください。
+
+[waker-from-impl]: https://github.com/rust-lang/rust/blob/cdb50c6f2507319f29104a25765bfb79ad53395c/src/liballoc/task.rs#L58-L87
+
+#### `run`メソッド
+
+wakerの実装ができたので、いよいよexecutorの`run`メソッドを構築します:
+
+```rust
+// in src/task/executor.rs
+
+impl Executor {
+ pub fn run(&mut self) -> ! {
+ loop {
+ self.run_ready_tasks();
+ }
+ }
+}
+```
+
+このメソッドは `run_ready_tasks` 関数の呼び出しをループするだけです。理論的には、`tasks`マップが空になったときにこの関数からリターンすることもできますが、`keyboard_task`が終了しないのでそれは起こらず、よって単純な`loop`で十分です。この関数は決してリターンしませんので、`!`という戻り値の型を使って、コンパイラにこの関数が[発散する (diverging) ][diverging]ことを示します。
+
+[diverging]: https://doc.rust-lang.org/stable/rust-by-example/fn/diverging.html
+
+これで、`kernel_main`で、`SimpleExecutor`の代わりに新しい`Executor`を使うように変更することができます:
+
+```rust
+// in src/main.rs
+
+use blog_os::task::executor::Executor; // new
+
+fn kernel_main(boot_info: &'static BootInfo) -> ! {
+ // init_heap、test_mainを含む初期化ルーチンを省略
+
+ let mut executor = Executor::new(); // new
+ executor.spawn(Task::new(example_task()));
+ executor.spawn(Task::new(keyboard::print_keypresses()));
+ executor.run();
+}
+```
+
+必要なのは、インポート部(`use`のところ)と型名を変更することだけです。関数 `run` は発散する関数となっているので、コンパイラはこの関数が決してリターンしないことを認識し、そのため`kernel_main` 関数の最後に `hlt_loop` を呼び出す必要はもうありません。
+
+ここで、`cargo run`を使ってカーネルを実行すると、キーボード入力が変わらず正常に動作することがわかります:
+
+
+
+しかし、QEMUのCPU使用量は全く減っていません。その理由は、CPUをずっとbusy状態にしているからです。タスクが再び起こされるまでポーリングすることはなくなりましたが、`task_queue`をチェックし続けるbusy loop(忙しないループの意)に入っているのです。この問題を解決するには、やるべき仕事がなくなったらCPUをスリープさせる必要があります。
+
+#### 何もすることがない (idle) ならスリープする
+
+基本的な考え方は、`task_queue`が空になったときに[`hlt`命令][`hlt` instruction]を実行するというものです。この命令は、次の割り込みが来るまでCPUをスリープ状態にします。割り込みが入るとCPUがすぐに活動を再開するので、割り込みハンドラが`task_queue`にpushされたときにも直接反応できるようになっています。
+
+[`hlt` instruction]: https://en.wikipedia.org/wiki/HLT_(x86_instruction)
+
+これを実現するために、executorに新しい`sleep_if_idle`メソッドを作成し、`run`メソッドから呼び出します:
+
+```rust
+// in src/task/executor.rs
+
+impl Executor {
+ pub fn run(&mut self) -> ! {
+ loop {
+ self.run_ready_tasks();
+ self.sleep_if_idle(); // new
+ }
+ }
+
+ fn sleep_if_idle(&self) {
+ if self.task_queue.is_empty() {
+ x86_64::instructions::hlt();
+ }
+ }
+}
+```
+
+`sleep_if_idle`は、`task_queue`が空になるまでループする`run_ready_tasks`の直後に呼び出されるので、キューを再度チェックする必要はないと思われるかもしれません。しかし、`run_ready_tasks` がリターンしてきた直後にハードウェア割り込みが発生する可能性があるため、`sleep_if_idle` 関数が呼ばれた時点ではキューに新しいタスクがあるかもしれません。キューがまだ空であった場合のみ、[`x86_64`]クレートが提供する[`instructions::hlt`]ラッパー関数を介して`hlt`命令を実行することで、CPUをスリープさせます。
+
+[`instructions::hlt`]: https://docs.rs/x86_64/0.14.2/x86_64/instructions/fn.hlt.html
+[`x86_64`]: https://docs.rs/x86_64/0.14.2/x86_64/index.html
+
+残念ながら、この実装には微妙な競合状態が残っています。割り込みは非同期であり、いつでも発生する可能性があるため、`is_empty` のチェックと `hlt` の呼び出しの間に割り込みが発生する可能性があります:
+
+```rust
+if self.task_queue.is_empty() {
+ /// <--- 割り込みがここで起きる可能性があります
+ x86_64::instructions::hlt();
+}
+```
+
+この割り込みが`task_queue`にpushされた場合、タスクの準備ができているにもかかわらず、CPUをスリープ状態にしてしまいます。最悪の場合、キーボード割り込みの処理が次のkeypressや次のタイマー割り込みまで遅れることになります。では、これを防ぐにはどうしたらよいでしょうか?
+
+その答えは、チェックの前にCPUの割り込みを無効にし、`hlt`命令と一緒にアトミックに再度有効にすることです。この方法では、その間に発生するすべての割り込みが `hlt` 命令の後に遅延されるため、wakeupが失敗することはありません。この方法を実装するには、[`x86_64`]クレートが提供する[`interrupts::enable_and_hlt`][`enable_and_hlt`]関数を使用します。
+
+[`enable_and_hlt`]: https://docs.rs/x86_64/0.14.2/x86_64/instructions/interrupts/fn.enable_and_hlt.html
+
+更新された `sleep_if_idle` 関数の実装は次のようになります:
+
+```rust
+// in src/task/executor.rs
+
+impl Executor {
+ fn sleep_if_idle(&self) {
+ use x86_64::instructions::interrupts::{self, enable_and_hlt};
+
+ interrupts::disable();
+ if self.task_queue.is_empty() {
+ enable_and_hlt();
+ } else {
+ interrupts::enable();
+ }
+ }
+}
+```
+
+競合状態を避けるために、`task_queue` が空であるかどうかを確認する前に、割り込みを無効にします。空いていれば、[`enable_and_hlt`]関数を使用して、単一のアトミック操作として割り込みを有効にしCPUをスリープさせます。キューが空でない場合は、`run_ready_tasks` がリターンしてきた後に、割り込みがタスクを起動したことを意味します。その場合は、再び割り込みを有効にして、`hlt`を実行せずにすぐに実行を継続します。
+
+これで、実行することがないときには、executorが適切にCPUをスリープ状態にするようになりました。再び`cargo run`を使ってカーネルを実行すると、QEMUプロセスのCPU使用率が大幅に低下していることがわかります。
+
+#### 考えられる機能拡張
+
+executorは、効率的な方法でタスクを実行できるようになりました。待機中のタスクのポーリングを避けるためにwaker通知を利用し、現在やるべきことがないときはCPUをスリープさせます。しかし、このexecutorはまだ非常に基本的なものであり、機能を拡張する方法はたくさんあります:
+
+- **スケジューリング**: 現在、我々は[`VecDeque`]型を使用して、先入れ先出し(FIFO)戦略を`task_queue`に実装しています。これはしばしば **ラウンドロビン (round robin)** スケジューリングとも呼ばれます。この戦略は、すべてのワークロードにとって最も効率的であるとは限りません。例えば、レイテンシーが重要なタスクや、I/Oを大量に行うタスクを優先させることは意味があるかもしれません。詳しくは、[_Operating Systems: Three Easy Pieces_]の[スケジューリングの章][scheduling chapter]や、[スケジューリングに関するWikipediaの記事][scheduling-wiki]をご覧ください。
+- **タスクの発生 (spawn)**: 現在、私たちの `Executor::spawn` メソッドは `&mut self` の参照を必要とするため、`run` メソッドを開始した後は利用できません。この問題を解決するには、追加で `Spawner` 型を作成します。この型は、ある種のキューをexecutorと共有し、タスク自身の中からタスクを作成することができます。このキューには、例えば `task_queue` を直接使用することもできますし、executorが実行ループの中でチェックする別のキューを使用することもできます。
+- **スレッドを活用する**: まだスレッドのサポートはしていませんが、次の投稿で追加する予定です。これにより、複数のexecutorのインスタンスを異なるスレッドで起動することが可能になります。このアプローチの利点は、複数のタスクが同時に実行できるため、長時間実行するタスクによって課せられる遅延を減らすことができることです。また、この方法では、複数のCPUコアを利用することもできます。
+- **負荷の分配**: スレッドをサポートするようにした場合、すべてのCPUコアが利用されるように、executor間でどのようにタスクを分配するかが重要になります。このための一般的なテクニックは、[_work stealing_]です。
+
+[scheduling chapter]: http://pages.cs.wisc.edu/~remzi/OSTEP/cpu-sched.pdf
+[_Operating Systems: Three Easy Pieces_]: http://pages.cs.wisc.edu/~remzi/OSTEP/
+[scheduling-wiki]: https://en.wikipedia.org/wiki/Scheduling_(computing)
+[_work stealing_]: https://en.wikipedia.org/wiki/Work_stealing
+
+## まとめ
+
+この記事ではまず、**マルチタスク**について紹介し、実行中のタスクを定期的に強制的に中断させる**非協調的**マルチタスクと、タスクが自発的にCPUの制御を放棄するまで実行させてやる**協調的**マルチタスクの違いを説明しました。
+
+次に、Rustがサポートする**async/await**がどのようにして協調的マルチタスクの言語レベルの実装を提供しているかを調べました。Rustは、非同期タスクを抽象化するポーリングベースの`Future` traitをベースにして実装しています。async/awaitを使うと、通常の同期コードとほぼ同じようにfutureを扱うことができます。違いは、非同期関数が再び `Future` を返すことで、それを実行するためにはどこかの時点でこの`Future`をexecutorに追加する必要があります。
+
+舞台裏では、コンパイラが async/await コードを **ステートマシン** に変換し、各 `.await` オペレーションが可能な待ち状態に対応するようにします。対象のプログラムに関する知識を活用することで、コンパイラは各待ち状態に必要な最小限の状態のみを保存することができ、その結果、タスクあたりのメモリ消費量は非常に小さくなります。一つの課題は、生成されたステートマシンに **自己参照**構造体が含まれている可能性があることです。例えば、非同期関数のローカル変数の一方が他方を参照している場合などです。ポインタの無効化を防ぐために、Rustは`Pin`型を用いて、futureが最初にポーリングされた後は、メモリ内で移動できないようにしています。
+
+私たちの**実装**では、まず、`Waker`型を全く使わずに、busy loopですべてのspawnされたタスクをポーリングする非常に基本的なexecutorを作成しました。次に、非同期のキーボードタスクを実装することで、waker通知の利点を示しました。このタスクは、`crossbeam`クレートが提供する`ArrayQueue`というmutexを使用しない型を使って、静的な`SCANCODE_QUEUE`を定義します。キーボード割り込みハンドラは、キーの入力を直接処理する代わりに、受信したすべてのスキャンコードをキューに入れ、登録されている `Waker` を起こして、新しい入力が利用可能であることを通知します。受信側では、`ScancodeStream`型を作成して、キュー内の次のスキャンコードに変化する`Future`を提供しています。これにより、非同期の `print_keypresses` タスクを作成することができました。このタスクは、キュー内のスキャンコードを解釈して出力するために async/await を使用します。
+
+キーボードタスクのwaker通知を利用するために、新しい `Executor` 型を作成しました。この型は、準備のできたタスクに `Arc` で共有された `task_queue` を使用します。 私たちは`TaskWaker`型を実装し、起こされたタスクのIDを直接この`task_queue`にpushし、それをexecutorが再びポーリングするようにしました。また、実行可能なタスクがないときに電力を節約するために、`hlt`命令を用いてCPUをスリープさせる機能を追加しました。最後に、マルチコアへの対応など、executorの拡張の可能性について述べました。
+
+## 次は?
+
+async/waitを使うことで、カーネルで基本的な協調的マルチタスクをサポートできるようになりました。協調的マルチタスクは非常に効率的ですが、個々のタスクが長く実行しすぎる場合、他のタスクの実行が妨げられ、遅延の問題が発生します。このため、カーネルに非協調的マルチタスクのサポートを追加することは理にかなっています。
+
+次回は、非協調的マルチタスクの最も一般的な形態である **スレッド** を紹介します。スレッドは、長時間実行されるタスクの問題を解決するだけでなく、将来的に複数のCPUコアを利用したり、信頼できないユーザープログラムを実行したりするための準備にもなります。
diff --git a/blog/content/edition-2/posts/12-async-await/index.md b/blog/content/edition-2/posts/12-async-await/index.md
index bc58ec10..fbe7e69f 100644
--- a/blog/content/edition-2/posts/12-async-await/index.md
+++ b/blog/content/edition-2/posts/12-async-await/index.md
@@ -16,6 +16,7 @@ This blog is openly developed on [GitHub]. If you have any problems or questions
[GitHub]: https://github.com/phil-opp/blog_os
[at the bottom]: #comments
+
[post branch]: https://github.com/phil-opp/blog_os/tree/post-12
@@ -91,7 +92,7 @@ However, the strong performance and memory benefits of cooperative multitasking
## Async/Await in Rust
-The Rust language provides first-class support for cooperative multitasking in form of async/await. Before we can explore what async/await is and how it works, we need to understand how _futures_ and asynchronous programming work in Rust.
+The Rust language provides first-class support for cooperative multitasking in the form of async/await. Before we can explore what async/await is and how it works, we need to understand how _futures_ and asynchronous programming work in Rust.
### Futures
@@ -420,7 +421,7 @@ ExampleStateMachine::WaitingOnFooTxt(state) => {
};
*self = ExampleStateMachine::WaitingOnBarTxt(state);
} else {
- *self = ExampleStateMachine::End(EndState));
+ *self = ExampleStateMachine::End(EndState);
return Poll::Ready(content);
}
}
@@ -441,7 +442,7 @@ ExampleStateMachine::WaitingOnBarTxt(state) => {
match state.bar_txt_future.poll(cx) {
Poll::Pending => return Poll::Pending,
Poll::Ready(bar_txt) => {
- *self = ExampleStateMachine::End(EndState));
+ *self = ExampleStateMachine::End(EndState);
// from body of `example`
return Poll::Ready(state.content + &bar_txt);
}
@@ -687,7 +688,7 @@ For further reading, check out the documentation of the [`pin` module] and the [
#### Pinning and Futures
-As we already saw in this post, the [`Future::poll`] method uses pinning in form of a `Pin<&mut Self>` parameter:
+As we already saw in this post, the [`Future::poll`] method uses pinning in the form of a `Pin<&mut Self>` parameter:
[`Future::poll`]: https://doc.rust-lang.org/nightly/core/future/trait.Future.html#tymethod.poll
@@ -1025,7 +1026,7 @@ We already have some kind of asynchronicity in our system that we can use for th
[_Interrupts_]: @/edition-2/posts/07-hardware-interrupts/index.md
-In the following, we will create an asynchronous task based on the keyboard interrupt. The keyboard interrupt is a good candidate for this because it is both non-deterministic and latency-critical. Non-deteministic means that there is no way to predict when the next key press will occur because it is entirely dependent on the user. Latency-critical means that we want to handle the keyboard input in a timely manner, otherwise the user will feel a lag. To support such a task in an efficient way, it will be essential that the executor has proper support for `Waker` notifications.
+In the following, we will create an asynchronous task based on the keyboard interrupt. The keyboard interrupt is a good candidate for this because it is both non-deterministic and latency-critical. Non-deterministic means that there is no way to predict when the next key press will occur because it is entirely dependent on the user. Latency-critical means that we want to handle the keyboard input in a timely manner, otherwise the user will feel a lag. To support such a task in an efficient way, it will be essential that the executor has proper support for `Waker` notifications.
#### Scancode Queue
@@ -1136,7 +1137,7 @@ To call the `add_scancode` function on keyboard interrupts, we update our `keybo
// in src/interrupts.rs
extern "x86-interrupt" fn keyboard_interrupt_handler(
- _stack_frame: &mut InterruptStackFrame
+ _stack_frame: InterruptStackFrame
) {
use x86_64::instructions::port::Port;
@@ -1418,7 +1419,7 @@ The `TaskId` struct is a simple wrapper type around `u64`. We derive a number of
[`BTreeMap`]: https://doc.rust-lang.org/alloc/collections/btree_map/struct.BTreeMap.html
-To create a new unique ID, we create a `TaskID::new` function:
+To create a new unique ID, we create a `TaskId::new` function:
```rust
use core::sync::atomic::{AtomicU64, Ordering};
@@ -1643,7 +1644,7 @@ impl Wake for TaskWaker {
}
```
-The trait is still unstable, so we have to add **`#![feature(wake_trait)]`** to the top of our `lib.rs` to use it. Since wakers are commonly shared between the executor and the asynchronous tasks, the trait methods require that the `Self` instance is wrapped in the [`Arc`] type, which implements reference-counted ownership. This means that we have to move our `TaskWaker` to an `Arc` in order to call them.
+Since wakers are commonly shared between the executor and the asynchronous tasks, the trait methods require that the `Self` instance is wrapped in the [`Arc`] type, which implements reference-counted ownership. This means that we have to move our `TaskWaker` to an `Arc` in order to call them.
The difference between the `wake` and `wake_by_ref` methods is that the latter only requires a reference to the `Arc`, while the former takes ownership of the `Arc` and thus often requires an increase of the reference count. Not all types support waking by reference, so implementing the `wake_by_ref` method is optional, however it can lead to better performance because it avoids unnecessary reference count modifications. In our case, we can simply forward both trait methods to our `wake_task` function, which requires only a shared `&self` reference.
@@ -1744,8 +1745,8 @@ impl Executor {
Since we call `sleep_if_idle` directly after `run_ready_tasks`, which loops until the `task_queue` becomes empty, checking the queue again might seem unnecessary. However, a hardware interrupt might occur directly after `run_ready_tasks` returns, so there might be a new task in the queue at the time the `sleep_if_idle` function is called. Only if the queue is still empty, we put the CPU to sleep by executing the `hlt` instruction through the [`instructions::hlt`] wrapper function provided by the [`x86_64`] crate.
-[`instructions::hlt`]: https://docs.rs/x86_64/0.13.2/x86_64/instructions/fn.hlt.html
-[`x86_64`]: https://docs.rs/x86_64/0.13.2/x86_64/index.html
+[`instructions::hlt`]: https://docs.rs/x86_64/0.14.2/x86_64/instructions/fn.hlt.html
+[`x86_64`]: https://docs.rs/x86_64/0.14.2/x86_64/index.html
Unfortunately, there is still a subtle race condition in this implementation. Since interrupts are asynchronous and can happen at any time, it is possible that an interrupt happens right between the `is_empty` check and the call to `hlt`:
@@ -1758,9 +1759,9 @@ if self.task_queue.is_empty() {
In case this interrupt pushes to the `task_queue`, we put the CPU to sleep even though there is now a ready task. In the worst case, this could delay the handling of a keyboard interrupt until the next keypress or the next timer interrupt. So how do we prevent it?
-The answer is to disable interrupts on the CPU before the check and atomically enable them again together with the `hlt` instruction. This way, all interrupts that happen in between are delayed after the `hlt` instruction so that no wake-ups are missed. To implement this approach, we can use the [`interrupts::enable_and_hlt`][`enable_and_hlt`] function provided by the [`x86_64`] crate. This function is only available since version 0.9.6, so you might need to update your `x86_64` dependency to use it.
+The answer is to disable interrupts on the CPU before the check and atomically enable them again together with the `hlt` instruction. This way, all interrupts that happen in between are delayed after the `hlt` instruction so that no wake-ups are missed. To implement this approach, we can use the [`interrupts::enable_and_hlt`][`enable_and_hlt`] function provided by the [`x86_64`] crate.
-[`enable_and_hlt`]: https://docs.rs/x86_64/0.13.2/x86_64/instructions/interrupts/fn.enable_and_hlt.html
+[`enable_and_hlt`]: https://docs.rs/x86_64/0.14.2/x86_64/instructions/interrupts/fn.enable_and_hlt.html
The updated implementation of our `sleep_if_idle` function looks like this:
@@ -1789,7 +1790,7 @@ Now our executor properly puts the CPU to sleep when there is nothing to do. We
Our executor is now able to run tasks in an efficient way. It utilizes waker notifications to avoid polling waiting tasks and puts the CPU to sleep when there is currently no work to do. However, our executor is still quite basic and there are many possible ways to extend its functionality:
-- **Scheduling:** We currently use the [`VecDeque`] type to implement a _first in first out_ (FIFO) strategy for our `task_queue`, which is often also called _round robin_ scheduling. This strategy might not be the most efficient for all workloads. For example, it might make sense to prioritize latency-critical tasks or tasks that do a lot of I/O. See the [scheduling chapter] of the [_Operating Systems: Three Easy Pieces_] book or the [Wikipedia article on scheduling][scheduling-wiki] for more information.
+- **Scheduling**: We currently use the [`VecDeque`] type to implement a _first in first out_ (FIFO) strategy for our `task_queue`, which is often also called _round robin_ scheduling. This strategy might not be the most efficient for all workloads. For example, it might make sense to prioritize latency-critical tasks or tasks that do a lot of I/O. See the [scheduling chapter] of the [_Operating Systems: Three Easy Pieces_] book or the [Wikipedia article on scheduling][scheduling-wiki] for more information.
- **Task Spawning**: Our `Executor::spawn` method currently requires a `&mut self` reference and is thus no longer available after starting the `run` method. To fix this, we could create an additional `Spawner` type that shares some kind of queue with the executor and allows task creation from within tasks themselves. The queue could be for example the `task_queue` directly or a separate queue that the executor checks in its run loop.
- **Utilizing Threads**: We don't have support for threads yet, but we will add it in the next post. This will make it possible to launch multiple instances of the executor in different threads. The advantage of this approach is that the delay imposed by long running tasks can be reduced because other tasks can run concurrently. This approach also allows it to utilize multiple CPU cores.
- **Load Balancing**: When adding threading support, it becomes important how to distribute the tasks between the executors to ensure that all CPU cores are utilized. A common technique for this is [_work stealing_].
diff --git a/blog/content/edition-2/posts/_index.fr.md b/blog/content/edition-2/posts/_index.fr.md
new file mode 100644
index 00000000..c7079c40
--- /dev/null
+++ b/blog/content/edition-2/posts/_index.fr.md
@@ -0,0 +1,7 @@
++++
+title = "Posts"
+sort_by = "weight"
+insert_anchor_links = "left"
+render = false
+page_template = "edition-2/page.html"
++++
diff --git a/blog/content/edition-2/posts/_index.ru.md b/blog/content/edition-2/posts/_index.ru.md
new file mode 100644
index 00000000..c7079c40
--- /dev/null
+++ b/blog/content/edition-2/posts/_index.ru.md
@@ -0,0 +1,7 @@
++++
+title = "Posts"
+sort_by = "weight"
+insert_anchor_links = "left"
+render = false
+page_template = "edition-2/page.html"
++++
diff --git a/blog/content/edition-2/posts/deprecated/04-unit-testing/index.md b/blog/content/edition-2/posts/deprecated/04-unit-testing/index.md
index 81a80343..5c97cf68 100644
--- a/blog/content/edition-2/posts/deprecated/04-unit-testing/index.md
+++ b/blog/content/edition-2/posts/deprecated/04-unit-testing/index.md
@@ -17,6 +17,7 @@ This blog is openly developed on [GitHub]. If you have any problems or questions
[GitHub]: https://github.com/phil-opp/blog_os
[at the bottom]: #comments
+
[post branch]: https://github.com/phil-opp/blog_os/tree/post-04
diff --git a/blog/content/edition-2/posts/deprecated/05-integration-tests/index.md b/blog/content/edition-2/posts/deprecated/05-integration-tests/index.md
index cf1c8b3c..900ec34d 100644
--- a/blog/content/edition-2/posts/deprecated/05-integration-tests/index.md
+++ b/blog/content/edition-2/posts/deprecated/05-integration-tests/index.md
@@ -17,6 +17,7 @@ This blog is openly developed on [GitHub]. If you have any problems or questions
[GitHub]: https://github.com/phil-opp/blog_os
[at the bottom]: #comments
+
[post branch]: https://github.com/phil-opp/blog_os/tree/post-05
diff --git a/blog/content/edition-2/posts/deprecated/10-advanced-paging/index.md b/blog/content/edition-2/posts/deprecated/10-advanced-paging/index.md
index d2348543..70eac621 100644
--- a/blog/content/edition-2/posts/deprecated/10-advanced-paging/index.md
+++ b/blog/content/edition-2/posts/deprecated/10-advanced-paging/index.md
@@ -17,6 +17,7 @@ This blog is openly developed on [GitHub]. If you have any problems or questions
[GitHub]: https://github.com/phil-opp/blog_os
[at the bottom]: #comments
+
[post branch]: https://github.com/phil-opp/blog_os/tree/5c0fb63f33380fc8596d7166c2ebde03ef3d6726
## Introduction
diff --git a/blog/content/status-update/2019-12-02.md b/blog/content/status-update/2019-12-02.md
index b366c0a9..5d7e4fca 100644
--- a/blog/content/status-update/2019-12-02.md
+++ b/blog/content/status-update/2019-12-02.md
@@ -15,7 +15,7 @@ We also have other news: We plan to add [Experimental Support for Community Tran
## `bootloader`
-- [Change the way the kernel entry point is called to honor alignement ABI](https://github.com/rust-osdev/bootloader/pull/81) by [@GuillaumeDIDIER](https://github.com/GuillaumeDIDIER) (published as version 0.8.2)
+- [Change the way the kernel entry point is called to honor alignment ABI](https://github.com/rust-osdev/bootloader/pull/81) by [@GuillaumeDIDIER](https://github.com/GuillaumeDIDIER) (published as version 0.8.2)
- [Add support for Github Actions](https://github.com/rust-osdev/bootloader/pull/82)
- [Remove unnecessary `extern C` on panic handler to fix not-ffi-safe warning](https://github.com/rust-osdev/bootloader/pull/85) by [@cmsd2](https://github.com/cmsd2) (published as version 0.8.3)
diff --git a/blog/sass/css/edition-2/main.scss b/blog/sass/css/edition-2/main.scss
new file mode 100644
index 00000000..f2ca3806
--- /dev/null
+++ b/blog/sass/css/edition-2/main.scss
@@ -0,0 +1,1061 @@
+/*
+ * CSS file for the second edition of os.phil-opp.com.
+ *
+ * Based on `poole`which was designed, built, and released under MIT license by @mdo. See
+ * https://github.com/poole/poole.
+ */
+
+/*
+ * Contents
+ *
+ * Fonts
+ * Body resets
+ * Dark/Light Mode
+ * Custom type
+ * Messages
+ * Container
+ * Masthead
+ * Posts and pages
+ * Pagination
+ * Reverse layout
+ * Themes
+ */
+
+/* Fonts */
+
+@font-face {
+ font-family: "Iosevka";
+ src: url("/fonts/iosevka-regular.woff2") format("woff2"), url("/fonts/iosevka-regular.woff") format("woff");
+ font-weight: normal;
+ font-style: normal;
+ font-display: swap;
+}
+
+/*
+ * Body resets
+ *
+ * Update the foundational and global aspects of the page.
+ */
+
+* {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+
+html,
+body {
+ margin: 0;
+ padding: 0;
+}
+
+html {
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ line-height: 1.5;
+}
+
+/* Dark/Light Mode */
+
+@mixin set-colors-light {
+ --background-color: #fff;
+ --text-color: #515151;
+ --heading-color: #313131;
+ --heading-code-color: #a0565c;
+ --link-color: #268bd2;
+ --hr-color-top: #eee;
+ --hr-color-bottom: #fff;
+ --code-text-color: #bf616a;
+ --code-background-color: #f9f9f9;
+ --masthead-title-color: #505050;
+ --strong-color: #303030;
+ --masthead-subtitle: #c0c0c0;
+ --post-title-color: #228;
+}
+
+@mixin set-colors-dark {
+ --background-color: #252525;
+ --text-color: #f5f5f5;
+ --heading-color: #eee;
+ --heading-code-color: #eee;
+ --link-color: #c59ff3;
+ --hr-color-top: #333;
+ --hr-color-bottom: #000;
+ --code-text-color: #eeeeee;
+ --code-background-color: #222222;
+ --masthead-title-color: #b6b6b6;
+ --strong-color: #c0c0c0;
+ --masthead-subtitle: #8f8f8f;
+ --post-title-color: #c8c8ff;
+}
+
+body {
+ @include set-colors-light();
+}
+
+[data-theme="dark"] body {
+ @include set-colors-dark();
+}
+
+/* Styles for users who prefer dark mode at the OS level */
+@media (prefers-color-scheme: dark) {
+ /* defaults to dark theme */
+ body {
+ @include set-colors-dark();
+ }
+ /* Override dark mode with light mode styles if the user decides to swap */
+ [data-theme="light"] body {
+ @include set-colors-light();
+ }
+}
+
+body {
+ color: var(--text-color);
+ background-color: var(--background-color);
+ -webkit-text-size-adjust: 100%;
+ -ms-text-size-adjust: 100%;
+}
+
+/* No `:visited` state is required by default (browsers will use `a`) */
+
+a {
+ color: var(--link-color);
+ text-decoration: none;
+}
+
+/* `:focus` is linked to `:hover` for basic accessibility */
+
+a:hover,
+a:focus {
+ text-decoration: underline;
+}
+
+/* Headings */
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+ margin-bottom: 0.5rem;
+ font-weight: bold;
+ line-height: 1.25;
+ color: var(--heading-color);
+ text-rendering: optimizeLegibility;
+}
+h1 {
+ font-size: 2rem;
+}
+h2 {
+ margin-top: 1rem;
+ font-size: 1.5rem;
+}
+h3 {
+ margin-top: 1.5rem;
+ font-size: 1.25rem;
+}
+h4,
+h5,
+h6 {
+ margin-top: 1rem;
+ font-size: 1rem;
+}
+
+/* Body text */
+p {
+ margin-top: 0;
+ margin-bottom: 1rem;
+}
+
+strong {
+ color: var(--strong-color);
+}
+
+/* Lists */
+ul,
+ol,
+dl {
+ margin-top: 0;
+ margin-bottom: 1rem;
+}
+
+/* Nested lists */
+li ul,
+li ol,
+li dl {
+ margin-bottom: 0;
+}
+
+li ul + p,
+li ol + p,
+li dl + p {
+ margin-top: 1rem;
+}
+
+dt {
+ font-weight: bold;
+}
+dd {
+ margin-bottom: 0.5rem;
+}
+
+/* Misc */
+hr {
+ position: relative;
+ margin: 1.5rem 0;
+ border: 0;
+ border-top: 1px solid var(--hr-color-top);
+ border-bottom: 1px solid var(--hr-color-bottom);
+}
+
+abbr {
+ font-size: 90%;
+ font-weight: bold;
+ color: #555;
+ text-transform: uppercase;
+}
+abbr[title] {
+ cursor: help;
+ border-bottom: 1px dotted #e5e5e5;
+}
+
+/* Code */
+code,
+pre {
+ font-family: "Iosevka", monospace;
+}
+code {
+ padding: 0.25em 0.5em;
+ font-size: 85%;
+ color: var(--code-text-color);
+ background-color: var(--code-background-color);
+ border-radius: 3px;
+}
+pre {
+ display: block;
+ margin-top: 0;
+ margin-bottom: 1rem;
+ padding: 0.5rem;
+ font-size: 0.95rem;
+ line-height: 1.4;
+ white-space: pre;
+ overflow: auto;
+ word-wrap: normal;
+ background-color: var(--code-background-color);
+}
+pre code {
+ padding: 0;
+ font-size: 100%;
+ color: inherit;
+ background-color: transparent;
+}
+.highlight {
+ margin-bottom: 1rem;
+ border-radius: 4px;
+}
+.highlight pre {
+ margin-bottom: 0;
+}
+
+/* Quotes */
+blockquote {
+ padding: 0.5rem 1rem;
+ margin: 0.8rem 0;
+ color: #7a7a7a;
+ border-left: 0.25rem solid #e5e5e5;
+}
+blockquote p:last-child {
+ margin-bottom: 0;
+}
+@media (min-width: 30rem) {
+ blockquote {
+ padding-right: 5rem;
+ padding-left: 1.25rem;
+ }
+}
+
+img {
+ display: block;
+ margin: 0 0 1rem;
+ border-radius: 5px;
+ max-width: 100%;
+ color: grey;
+ font-style: italic;
+}
+
+/* Tables */
+table {
+ margin-bottom: 1rem;
+ width: 100%;
+ border: 1px solid #e5e5e5;
+ border-collapse: collapse;
+}
+td,
+th {
+ padding: 0.25rem 0.5rem;
+ border: 1px solid #e5e5e5;
+}
+tbody tr:nth-child(odd) td,
+tbody tr:nth-child(odd) th {
+ background-color: var(--code-background-color);
+}
+
+/*
+ * Custom type
+ *
+ * Extend paragraphs with `.lead` for larger introductory text.
+ */
+
+.lead {
+ font-size: 1.25rem;
+ font-weight: 300;
+}
+
+/*
+ * Messages
+ *
+ * Show alert messages to users. You may add it to single elements like a ``,
+ * or to a parent if there are multiple elements to show.
+ */
+
+.message {
+ margin-bottom: 1rem;
+ padding: 1rem;
+ color: #717171;
+ background-color: var(--code-background-color);
+}
+
+/*
+ * Container
+ *
+ * Center the page content.
+ */
+
+.container {
+ max-width: 45rem;
+ padding-left: 1rem;
+ padding-right: 1rem;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+/*
+ * Masthead
+ *
+ * Super small header above the content for site name and short description.
+ */
+
+.masthead {
+ padding-top: 1rem;
+ padding-bottom: 1rem;
+ margin-bottom: 1rem;
+}
+.masthead-title {
+ margin-top: 0;
+ margin-bottom: 0;
+ color: var(--masthead-title-color);
+}
+.masthead-title a {
+ color: var(--masthead-title-color);
+}
+.masthead small {
+ font-size: 75%;
+ font-weight: 400;
+ color: var(--masthead-subtitle);
+ letter-spacing: 0;
+}
+
+/*
+ * Posts and pages
+ *
+ * Each post is wrapped in `.post` and is used on default and post layouts. Each
+ * page is wrapped in `.page` and is only used on the page layout.
+ */
+
+.page {
+ margin-bottom: 4em;
+}
+
+/* Blog post or page title */
+.page-title,
+.post-title a {
+ color: var(--post-title-color);
+}
+.page-title,
+.post-title {
+ margin-top: 0;
+}
+
+/* Meta data line below post title */
+.post-date {
+ display: block;
+ margin-top: -0.5rem;
+ margin-bottom: 1rem;
+ color: #9a9a9a;
+}
+
+/* Related posts */
+.related {
+ padding-top: 2rem;
+ padding-bottom: 2rem;
+ border-top: 1px solid #eee;
+}
+.related-posts {
+ padding-left: 0;
+ list-style: none;
+}
+.related-posts h3 {
+ margin-top: 0;
+}
+.related-posts li small {
+ font-size: 75%;
+ color: #999;
+}
+.related-posts li a:hover {
+ color: #268bd2;
+ text-decoration: none;
+}
+.related-posts li a:hover small {
+ color: inherit;
+}
+
+/*
+ * Pagination
+ *
+ * Super lightweight (HTML-wise) blog pagination. `span`s are provide for when
+ * there are no more previous or next posts to show.
+ */
+
+.pagination {
+ overflow: hidden; /* clearfix */
+ margin-left: -1rem;
+ margin-right: -1rem;
+ font-family: "PT Sans", Helvetica, Arial, sans-serif;
+ color: #ccc;
+ text-align: center;
+}
+
+/* Pagination items can be `span`s or `a`s */
+.pagination-item {
+ display: block;
+ padding: 1rem;
+ border: 1px solid #eee;
+}
+.pagination-item:first-child {
+ margin-bottom: -1px;
+}
+
+/* Only provide a hover state for linked pagination items */
+a.pagination-item:hover {
+ background-color: #f5f5f5;
+}
+
+@media (min-width: 30rem) {
+ .pagination {
+ margin: 3rem 0;
+ }
+ .pagination-item {
+ float: left;
+ width: 50%;
+ }
+ .pagination-item:first-child {
+ margin-bottom: 0;
+ border-top-left-radius: 4px;
+ border-bottom-left-radius: 4px;
+ }
+ .pagination-item:last-child {
+ margin-left: -1px;
+ border-top-right-radius: 4px;
+ border-bottom-right-radius: 4px;
+ }
+}
+
+h1 code,
+h2 code,
+h3 code,
+h4 code,
+h5 code,
+h6 code {
+ padding: 0;
+ color: var(--heading-code-color);
+ font-size: 90%;
+ background-color: inherit;
+}
+
+.masthead-title {
+ font-size: 1.25rem;
+ display: inline;
+}
+
+.masthead p {
+ font-size: 1.25rem;
+ display: inline;
+ margin: 0;
+ margin-left: 1rem;
+ padding: 0;
+ line-height: 1;
+}
+
+.front-page-introduction {
+ margin-bottom: 2rem;
+}
+
+.navigation {
+ float: right;
+}
+
+.navigation img {
+ height: 1em;
+ vertical-align: baseline;
+ display: inline-block;
+ margin: 0;
+ padding: 0;
+ border-radius: 0;
+}
+
+main img {
+ max-width: 100%;
+ margin: auto;
+}
+
+.post {
+ margin-bottom: 2em;
+}
+
+.post:last-child {
+ margin-bottom: 0em;
+}
+
+.frontpage-section {
+ margin-bottom: 2rem;
+}
+
+.posts {
+ padding: 1.5rem 1rem 0.5rem 1rem;
+ border-radius: 10px;
+ margin-bottom: 2rem;
+ margin-left: -0.5rem;
+ margin-right: -0.5rem;
+}
+
+.posts.neutral {
+ border: 2px solid #999;
+}
+
+.posts.subscribe {
+ border: 2px solid #aaa;
+}
+
+.posts.edition-1 {
+ border: 2px solid #aaa;
+ background-color: #99ff0022;
+}
+
+.posts.bare-bones {
+ border: 2px solid #66f;
+}
+
+.posts.memory-management {
+ border: 2px solid #fc0;
+}
+
+.posts.interrupts {
+ border: 2px solid #f66;
+}
+
+.posts.multitasking {
+ border: 2px solid #556b2f;
+}
+
+.posts hr {
+ margin: 2rem 0;
+}
+
+.post-summary {
+ margin-bottom: 1rem;
+}
+
+.post-summary p {
+ display: inline;
+}
+
+.read-more {
+ margin-left: 5px;
+}
+
+.no-translation {
+ margin-top: 0.3rem;
+ color: #999999;
+}
+
+.post-category {
+ margin-right: 0.5rem;
+ text-transform: uppercase;
+ font-size: 0.8rem;
+ text-align: right;
+}
+
+.post-category.bare-bones {
+ color: #55d;
+}
+
+.post-category.memory-management {
+ color: #990;
+}
+
+.post-category.interrupts {
+ color: #f33;
+}
+
+.post-category.multitasking {
+ color: #556b2f;
+}
+
+.post-footer-support {
+ margin-top: 2rem;
+}
+
+.PageNavigation {
+ font-size: 0.9em;
+ display: table;
+ width: 100%;
+ overflow: hidden;
+}
+
+.PageNavigation a {
+ display: table-cell;
+}
+
+.PageNavigation .previous {
+ text-align: left;
+}
+
+.PageNavigation .next {
+ text-align: right;
+}
+
+footer.footer {
+ margin-top: 1rem;
+ margin-bottom: 1rem;
+
+ .spaced {
+ margin-left: 0.5rem;
+ }
+}
+
+.footnotes {
+ font-size: 85%;
+}
+
+.footnotes li {
+ margin-bottom: 1rem;
+}
+
+sup,
+sub {
+ line-height: 0;
+}
+
+a.anchorjs-link:hover {
+ text-decoration: none;
+}
+
+#toc-aside {
+ display: none;
+}
+
+#toc-inline summary {
+ margin-bottom: 0.2rem;
+}
+
+aside#all-posts-link {
+ font-size: 90%;
+ margin-top: 0.5rem;
+}
+
+@media (min-width: 80rem) {
+ #toc-inline {
+ display: none;
+ }
+
+ #toc-aside {
+ display: block;
+ width: 12rem;
+ position: sticky;
+ float: left;
+ top: 3.5rem;
+ margin-top: -4rem;
+ margin-left: -15rem;
+ font-size: 90%;
+ line-height: 1.2;
+ }
+
+ #toc-aside li > a,
+ #toc-aside h2 {
+ opacity: 0.5;
+ transition: opacity 0.5s;
+ }
+
+ #toc-aside:hover li > a,
+ #toc-aside:hover h2 {
+ opacity: 1;
+ }
+
+ #toc-aside li.active > a {
+ font-weight: bold;
+ }
+
+ #toc-aside h2 {
+ font-size: 110%;
+ margin-bottom: 0.2rem;
+ }
+
+ #toc-aside ol {
+ margin: 0 0 0.2rem 0;
+ padding: 0 0 0 1rem;
+ list-style: none;
+ }
+
+ #toc-aside ol li a:before {
+ content: "";
+ border-color: transparent #008eef;
+ border-style: solid;
+ border-width: 0.35em 0 0.35em 0.45em;
+ display: block;
+ height: 0;
+ width: 0;
+ left: -1em;
+ top: 0.9em;
+ position: relative;
+ }
+
+ #toc-aside.coarse li ol {
+ display: none;
+ }
+
+ aside.page-aside-right {
+ position: absolute;
+ min-width: 11rem;
+ max-width: 17rem;
+ top: 4rem;
+ margin-left: 45rem;
+ margin-right: 2rem;
+ font-size: 90%;
+ }
+
+ aside.page-aside-right .block {
+ margin-bottom: 1.5rem;
+ }
+
+ aside.page-aside-right h2 {
+ font-size: 110%;
+ margin-bottom: 0.2rem;
+ }
+
+ aside.page-aside-right ul {
+ margin: 0 0 0.2rem 0;
+ padding: 0 0 0 1rem;
+ }
+
+ aside.page-aside-right ul li {
+ margin-top: 0.5rem;
+ }
+
+ #language-selector li {
+ margin-top: 0;
+ }
+
+ aside#all-posts-link {
+ position: fixed;
+ top: 1.25rem;
+ margin-top: 0;
+ margin-left: -15rem;
+ }
+}
+
+aside.page-aside-right time {
+ color: #9a9a9a;
+}
+
+a code {
+ color: var(--link-color);
+}
+
+a.zola-anchor {
+ opacity: 0;
+ position: absolute;
+ margin-left: -1.5em;
+ padding-right: 1em;
+ font-size: 0.6em;
+ vertical-align: baseline;
+ line-height: 2em;
+}
+
+:hover > a.zola-anchor {
+ opacity: 1;
+ text-decoration: none;
+}
+
+a.zola-anchor:hover {
+ text-decoration: none;
+}
+
+div.note {
+ padding: 0.7rem 1rem;
+ margin: 1rem 0.2rem;
+ border: 2px solid #6ad46a;
+ border-radius: 5px;
+ background-color: #99ff991f;
+}
+
+div.note p:last-child {
+ margin-bottom: 0;
+}
+
+div.warning {
+ padding: 0.7rem 1rem;
+ margin: 1rem 0.2rem;
+ border: 2px solid orange;
+ border-radius: 5px;
+ background-color: #ffa50022;
+}
+
+div.warning p:last-child {
+ margin-bottom: 0;
+}
+
+div.warning h2 {
+ margin-top: 0rem;
+}
+
+form.subscribe {
+ margin: 1rem;
+}
+
+div.subscribe-fields {
+ display: flex;
+}
+
+form.subscribe input {
+ padding: 0.5rem;
+ border: 1px solid #e5e5e5;
+}
+
+form.subscribe input[type="email"] {
+ flex: 1;
+}
+
+form.subscribe input[type="submit"] {
+ padding: 0.25rem 0.5rem;
+ cursor: pointer;
+}
+
+/* Asides */
+aside.post_aside {
+ font-style: italic;
+ padding: 0rem 1rem 0rem;
+ margin: 0.8rem 0;
+ border-left: 0.1rem solid #e5e5e5;
+ border-right: 0.1rem solid #e5e5e5;
+}
+
+details summary {
+ cursor: pointer;
+}
+
+details summary h3,
+details summary h4,
+details summary h5,
+details summary h6 {
+ display: inline;
+}
+
+.gh-repo-box {
+ border: 1px solid #d1d5da;
+ border-radius: 3px;
+ padding: 16px;
+ margin-top: 0.5rem;
+ color: #586069;
+ font-size: 80%;
+}
+
+.gh-repo-box .repo-link {
+ color: #0366d6;
+ font-weight: 600;
+ font-size: 120%;
+}
+
+.gh-repo-box .subtitle {
+ margin-bottom: 16px;
+}
+
+.gh-repo-box .stars-forks {
+ margin-bottom: 0;
+}
+
+.gh-repo-box .stars-forks a {
+ color: #586069;
+}
+
+.gh-repo-box .stars-forks a:hover {
+ color: #0366d6;
+ text-decoration: none;
+}
+
+.gh-repo-box .stars-forks svg {
+ vertical-align: text-bottom;
+ fill: currentColor;
+}
+
+.gh-repo-box .stars {
+ display: inline-block;
+}
+
+.gh-repo-box .forks {
+ display: inline-block;
+ margin-left: 16px;
+}
+
+.gh-repo-box .sponsor {
+ display: inline-block;
+ margin-left: 16px;
+}
+
+.hidden {
+ display: none;
+}
+
+.toc-comments-link {
+ margin-top: 0.5rem;
+}
+
+h5 {
+ font-style: italic;
+ font-size: 0.9rem;
+}
+.gray {
+ color: gray;
+}
+
+a strong {
+ color: #268bd2;
+}
+
+.right-to-left {
+ direction: rtl;
+ font-family: Vazir;
+}
+
+.left-to-right,
+.right-to-left pre,
+.right-to-left table,
+.right-to-left[id="toc-aside"] {
+ direction: ltr;
+}
+
+.status-update-list li {
+ margin-bottom: 0.5rem;
+}
+
+.giscus {
+ margin-top: 1.5rem;
+}
+
+img {
+ background-color: white;
+}
+
+/* Manual switch between dark and light mode */
+
+.theme-switch {
+ margin-bottom: 1rem;
+
+ @media (min-width: 80rem) {
+ position: fixed;
+ left: 2rem;
+ bottom: 2rem;
+ margin-bottom: 0rem;
+ }
+}
+
+.light-switch {
+ @mixin light-switch-light {
+ // icon: https://icons.getbootstrap.com/icons/moon-fill/ (MIT licensed)
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23004' class='bi bi-moon' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M14.53 10.53a7 7 0 0 1-9.058-9.058A7.003 7.003 0 0 0 8 15a7.002 7.002 0 0 0 6.53-4.47z'/%3E%3C/svg%3E");
+ }
+
+ @mixin light-switch-dark {
+ // icon: https://icons.getbootstrap.com/icons/brightness-high-fill/ (MIT licensed)
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23ff9' class='bi bi-brightness-high-fill' viewBox='0 0 16 16'%3E%3Cpath d='M12 8a4 4 0 1 1-8 0 4 4 0 0 1 8 0zM8 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 0zm0 13a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 13zm8-5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 .5.5zM3 8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2A.5.5 0 0 1 3 8zm10.657-5.657a.5.5 0 0 1 0 .707l-1.414 1.415a.5.5 0 1 1-.707-.708l1.414-1.414a.5.5 0 0 1 .707 0zm-9.193 9.193a.5.5 0 0 1 0 .707L3.05 13.657a.5.5 0 0 1-.707-.707l1.414-1.414a.5.5 0 0 1 .707 0zm9.193 2.121a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .707zM4.464 4.465a.5.5 0 0 1-.707 0L2.343 3.05a.5.5 0 1 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .708z'/%3E%3C/svg%3E");
+ }
+
+ display: inline-block;
+ @include light-switch-light();
+
+ background-repeat: no-repeat;
+ width: 2rem;
+ height: 2rem;
+ cursor: pointer;
+ opacity: 0.6;
+
+ &:hover {
+ transform: scale(1.3);
+ transition: 200ms ease-out;
+ opacity: 1;
+ }
+
+ [data-theme="dark"] & {
+ @include light-switch-dark();
+ }
+
+ @media (prefers-color-scheme: dark) {
+ @include light-switch-dark();
+
+ [data-theme="light"] & {
+ @include light-switch-light();
+ }
+ }
+}
+
+/* Clear theme override and go back to system theme */
+
+.light-switch-reset {
+ @mixin light-switch-reset-light {
+ // icon: https://icons.getbootstrap.com/icons/x-circle-fill/ (MIT licensed)
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='%23666' class='bi bi-x-circle' viewBox='0 0 16 16'%3E%3Cpath d='M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z'/%3E%3Cpath d='M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z'/%3E%3C/svg%3E");
+ }
+
+ @mixin light-switch-reset-dark {
+ // icon: https://icons.getbootstrap.com/icons/x-circle-fill/ (MIT licensed)
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='%23999' class='bi bi-x-circle' viewBox='0 0 16 16'%3E%3Cpath d='M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z'/%3E%3Cpath d='M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z'/%3E%3C/svg%3E");
+ }
+
+ @include light-switch-reset-light();
+ vertical-align: bottom;
+ margin-left: 0.5rem;
+ background-repeat: no-repeat;
+ width: 2rem;
+ height: 2rem;
+ cursor: pointer;
+ opacity: 0.6;
+
+ display: none;
+ [data-theme="light"] & {
+ display: inline-block;
+ }
+ [data-theme="dark"] & {
+ @include light-switch-reset-dark();
+ display: inline-block;
+ }
+
+ @media (min-width: 80rem) {
+ position: fixed;
+ left: 4.5rem;
+ bottom: 2rem;
+ }
+
+ &:hover {
+ transform: scale(1.1);
+ transition: 200ms ease-out;
+ opacity: 1;
+ }
+}
diff --git a/blog/static/css/edition-2/main.css b/blog/static/css/edition-2/main.css
deleted file mode 100644
index 32587de6..00000000
--- a/blog/static/css/edition-2/main.css
+++ /dev/null
@@ -1,464 +0,0 @@
-h1 code, h2 code, h3 code, h4 code, h5 code, h6 code {
- padding: 0;
- color: #a0565c;
- font-size: 95%;
- background-color: inherit;
-}
-
-.masthead-title {
- font-size: 1.25rem;
- display: inline;
-}
-
-.masthead p {
- font-size: 1.25rem;
- display: inline;
- margin: 0;
- margin-left: 1rem;
- padding: 0;
- line-height: 1;
-}
-
-.front-page-introduction {
- margin-bottom: 2rem;
-}
-
-.navigation {
- float: right;
-}
-
-.navigation img {
- height: 1em;
- vertical-align: baseline;
- display: inline-block;
- margin: 0;
- padding: 0;
- border-radius: 0;
-}
-
-main img {
- max-width: 100%;
- margin: auto;
-}
-
-.post {
- margin-bottom: 2em;
-}
-
-.post:last-child {
- margin-bottom: 0em;
-}
-
-.frontpage-section {
- margin-bottom: 2rem;
-}
-
-.posts {
- padding: 1.5rem 1rem 0.5rem 1rem;
- border-radius: 10px;
- margin-bottom: 2rem;
- margin-left: -0.5rem;
- margin-right: -0.5rem;
-}
-
-.posts.neutral {
- border: 2px solid #999;
-}
-
-.posts.subscribe {
- border: 2px solid #aaa;
-}
-
-.posts.edition-1 {
- border: 2px solid #aaa;
- background-color: #99ff0022;
-}
-
-.posts.bare-bones {
- border: 2px solid #66f;
-}
-
-.posts.memory-management {
- border: 2px solid #fc0
-}
-
-.posts.interrupts {
- border: 2px solid #f66;
-}
-
-.posts.multitasking {
- border: 2px solid #556b2f;
-}
-
-.posts hr {
- margin: 2rem 0;
-}
-
-.post-summary {
- margin-bottom: 1rem;
-}
-
-.post-summary p {
- display: inline;
-}
-
-.read-more {
- margin-left: 5px;
-}
-
-.no-translation {
- margin-top: .3rem;
- color: #999999;
-}
-
-.post-category {
- margin-right: 0.5rem;
- text-transform: uppercase;
- font-size: 0.8rem;
- text-align: right;
-}
-
-.post-category.bare-bones {
- color: #55d;
-}
-
-.post-category.memory-management {
- color: #990;
-}
-
-.post-category.interrupts {
- color: #f33;
-}
-
-.post-category.multitasking {
- color: #556b2f;
-}
-
-.post-footer-support {
- margin-top: 2rem;
-}
-
-.PageNavigation {
- font-size: 0.9em;
- display: table;
- width: 100%;
- overflow: hidden;
-}
-
-.PageNavigation a {
- display: table-cell;
-}
-
-.PageNavigation .previous {
- text-align: left;
-}
-
-.PageNavigation .next {
- text-align: right;
-}
-
-footer.footer {
- margin-top: 1rem;
- margin-bottom: 1rem;
-}
-
-.footnotes {
- font-size: 85%;
-}
-
-.footnotes li {
- margin-bottom: 1rem;
-}
-
-sup, sub {
- line-height: 0;
-}
-
-a.anchorjs-link:hover {
- text-decoration: none;
-}
-
-#toc-aside {
- display: none;
-}
-
-#toc-inline summary {
- margin-bottom: .2rem;
-}
-
-aside#all-posts-link {
- font-size: 90%;
- margin-top: 0.5rem;
-}
-
-@media (min-width: 80rem) {
- #toc-inline {
- display: none;
- }
-
- #toc-aside {
- display: block;
- width: 12rem;
- position: sticky;
- float: left;
- top: 3.5rem;
- margin-top: -4rem;
- margin-left: -15rem;
- font-size: 90%;
- line-height: 1.2;
- }
-
- #toc-aside li > a, #toc-aside h2 {
- opacity: .5;
- transition: opacity .5s;
- }
-
- #toc-aside:hover li > a, #toc-aside:hover h2 {
- opacity: 1;
- }
-
- #toc-aside li.active > a {
- font-weight: bold;
- }
-
- #toc-aside h2 {
- font-size: 110%;
- margin-bottom: .2rem;
- }
-
- #toc-aside ol {
- margin: 0 0 .2rem 0;
- padding: 0 0 0 1rem;
- list-style:none;
- }
-
- #toc-aside ol li a:before {
- content: "";
- border-color: transparent #008eef;
- border-style: solid;
- border-width: 0.35em 0 0.35em 0.45em;
- display: block;
- height: 0;
- width: 0;
- left: -1em;
- top: 0.9em;
- position: relative;
- }
-
- #toc-aside.coarse li ol {
- display: none;
- }
-
- aside.page-aside-right {
- position: absolute;
- min-width: 11rem;
- max-width: 17rem;
- top: 4rem;
- margin-left: 45rem;
- margin-right: 2rem;
- font-size: 90%;
- }
-
- aside.page-aside-right .block {
- margin-bottom: 1.5rem;
- }
-
- aside.page-aside-right h2 {
- font-size: 110%;
- margin-bottom: .2rem;
- }
-
- aside.page-aside-right ul {
- margin: 0 0 .2rem 0;
- padding: 0 0 0 1rem;
- }
-
- aside.page-aside-right ul li {
- margin-top: .5rem;
- }
-
- #language-selector li {
- margin-top: 0;
- }
-
- aside#all-posts-link {
- position: fixed;
- top: 1.25rem;
- margin-top: 0;
- margin-left: -15rem;
- }
-}
-
-aside.page-aside-right time {
- color: #9a9a9a;
-}
-
-a code {
- color: #268bd2;
-}
-
-a.zola-anchor {
- opacity: 0;
- position: absolute;
- margin-left: -1.5em;
- padding-right: 1em;
- font-size: 0.6em;
- vertical-align: baseline;
- line-height: 2em;
-}
-
-:hover>a.zola-anchor {
- opacity: 1;
- text-decoration: none;
-}
-
-a.zola-anchor:hover {
- text-decoration: none;
-}
-
-div.note {
- padding: .7rem 1rem;
- margin: 1rem .2rem;
- border: 2px solid #6ad46a;
- border-radius: 5px;
- background-color: #99ff991f;
-}
-
-div.note p:last-child {
- margin-bottom: 0;
-}
-
-div.warning {
- padding: .7rem 1rem;
- margin: 1rem .2rem;
- border: 2px solid orange;
- border-radius: 5px;
- background-color: #ffa50022;
-}
-
-div.warning p:last-child {
- margin-bottom: 0;
-}
-
-form.subscribe {
- margin: 1rem;
-}
-
-div.subscribe-fields {
- display: flex;
-}
-
-form.subscribe input {
- padding: .5rem;
- border: 1px solid #e5e5e5;
-}
-
-form.subscribe input[type=email] {
- flex: 1;
-}
-
-form.subscribe input[type=submit] {
- padding: .25rem .5rem;
- cursor: pointer;
-}
-
-/* Asides */
-aside.post_aside {
- font-style: italic;
- padding: 0rem 1rem 0rem;
- margin: .8rem 0;
- border-left: .1rem solid #e5e5e5;
- border-right: .1rem solid #e5e5e5;
-}
-
-details summary {
- cursor: pointer;
-}
-
-details summary h3, details summary h4, details summary h5, details summary h6 {
- display: inline;
-}
-
-.gh-repo-box {
- border: 1px solid #d1d5da;
- border-radius: 3px;
- padding: 16px;
- margin-top: 0.5rem;
- color: #586069;
- font-size: 80%;
-}
-
-.gh-repo-box .repo-link {
- color: #0366d6;
- font-weight: 600;
- font-size: 120%;
-}
-
-.gh-repo-box .subtitle {
- margin-bottom: 16px;
-}
-
-.gh-repo-box .stars-forks {
- margin-bottom: 0;
-}
-
-.gh-repo-box .stars-forks a {
- color: #586069;
-}
-
-.gh-repo-box .stars-forks a:hover {
- color: #0366d6;
- text-decoration: none;
-}
-
-.gh-repo-box .stars-forks svg {
- vertical-align: text-bottom;
- fill: currentColor;
-}
-
-.gh-repo-box .stars {
- display: inline-block;
-}
-
-.gh-repo-box .forks {
- display: inline-block;
- margin-left: 16px;
-}
-
-.gh-repo-box .sponsor {
- display: inline-block;
- margin-left: 16px;
-}
-
-.hidden {
- display: none;
-}
-
-.toc-comments-link {
- margin-top: .5rem;
-}
-
-h5 {
- font-style: italic;
- font-size: 0.9rem;
-}
-.gray {
- color: gray;
-}
-
-a strong {
- color: #268bd2;
-}
-
-.right-to-left {
- direction: rtl;
- font-family: Vazir;
-}
-
-.left-to-right, .right-to-left pre, .right-to-left table, .right-to-left[id="toc-aside"] {
- direction: ltr;
-}
-
-.status-update-list li {
- margin-bottom: .5rem;
-}
diff --git a/blog/static/css/edition-2/poole.css b/blog/static/css/edition-2/poole.css
deleted file mode 100644
index 0856c099..00000000
--- a/blog/static/css/edition-2/poole.css
+++ /dev/null
@@ -1,406 +0,0 @@
-/*
- * ___
- * /\_ \
- * _____ ___ ___\//\ \ __
- * /\ '__`\ / __`\ / __`\\ \ \ /'__`\
- * \ \ \_\ \/\ \_\ \/\ \_\ \\_\ \_/\ __/
- * \ \ ,__/\ \____/\ \____//\____\ \____\
- * \ \ \/ \/___/ \/___/ \/____/\/____/
- * \ \_\
- * \/_/
- *
- * Designed, built, and released under MIT license by @mdo. Learn more at
- * https://github.com/poole/poole.
- */
-
-
-/*
- * Contents
- *
- * Body resets
- * Custom type
- * Messages
- * Container
- * Masthead
- * Posts and pages
- * Pagination
- * Reverse layout
- * Themes
- */
-
-
-/*
- * Body resets
- *
- * Update the foundational and global aspects of the page.
- */
-
-* {
- -webkit-box-sizing: border-box;
- -moz-box-sizing: border-box;
- box-sizing: border-box;
-}
-
-html,
-body {
- margin: 0;
- padding: 0;
-}
-
-html {
- font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
- line-height: 1.5;
-}
-
-body {
- color: #515151;
- background-color: #fff;
- -webkit-text-size-adjust: 100%;
- -ms-text-size-adjust: 100%;
-}
-
-/* No `:visited` state is required by default (browsers will use `a`) */
-a {
- color: #268bd2;
- text-decoration: none;
-}
-/* `:focus` is linked to `:hover` for basic accessibility */
-a:hover,
-a:focus {
- text-decoration: underline;
-}
-
-/* Headings */
-h1, h2, h3, h4, h5, h6 {
- margin-bottom: .5rem;
- font-weight: bold;
- line-height: 1.25;
- color: #313131;
- text-rendering: optimizeLegibility;
-}
-h1 {
- font-size: 2rem;
-}
-h2 {
- margin-top: 1rem;
- font-size: 1.5rem;
-}
-h3 {
- margin-top: 1.5rem;
- font-size: 1.25rem;
-}
-h4, h5, h6 {
- margin-top: 1rem;
- font-size: 1rem;
-}
-
-/* Body text */
-p {
- margin-top: 0;
- margin-bottom: 1rem;
-}
-
-strong {
- color: #303030;
-}
-
-
-/* Lists */
-ul, ol, dl {
- margin-top: 0;
- margin-bottom: 1rem;
-}
-
-/* Nested lists */
-li ul, li ol, li dl {
- margin-bottom: 0;
-}
-
-li ul + p, li ol + p, li dl + p {
- margin-top: 1rem;
-}
-
-dt {
- font-weight: bold;
-}
-dd {
- margin-bottom: .5rem;
-}
-
-/* Misc */
-hr {
- position: relative;
- margin: 1.5rem 0;
- border: 0;
- border-top: 1px solid #eee;
- border-bottom: 1px solid #fff;
-}
-
-abbr {
- font-size: 85%;
- font-weight: bold;
- color: #555;
- text-transform: uppercase;
-}
-abbr[title] {
- cursor: help;
- border-bottom: 1px dotted #e5e5e5;
-}
-
-/* Code */
-code,
-pre {
- font-family: Menlo, Monaco, Consolas, monospace
-}
-code {
- padding: .25em .5em;
- font-size: 85%;
- color: #bf616a;
- background-color: #f9f9f9;
- border-radius: 3px;
-}
-pre {
- display: block;
- margin-top: 0;
- margin-bottom: 1rem;
- padding: .5rem;
- font-size: .85rem;
- line-height: 1.4;
- white-space: pre;
- overflow: auto;
- word-wrap: normal;
- background-color: #f9f9f9;
-}
-pre code {
- padding: 0;
- font-size: 100%;
- color: inherit;
- background-color: transparent;
-}
-.highlight {
- margin-bottom: 1rem;
- border-radius: 4px;
-}
-.highlight pre {
- margin-bottom: 0;
-}
-
-/* Quotes */
-blockquote {
- padding: .5rem 1rem;
- margin: .8rem 0;
- color: #7a7a7a;
- border-left: .25rem solid #e5e5e5;
-}
-blockquote p:last-child {
- margin-bottom: 0;
-}
-@media (min-width: 30rem) {
- blockquote {
- padding-right: 5rem;
- padding-left: 1.25rem;
- }
-}
-
-img {
- display: block;
- margin: 0 0 1rem;
- border-radius: 5px;
- max-width: 100%;
- color: grey;
- font-style: italic;
-}
-
-/* Tables */
-table {
- margin-bottom: 1rem;
- width: 100%;
- border: 1px solid #e5e5e5;
- border-collapse: collapse;
-}
-td,
-th {
- padding: .25rem .5rem;
- border: 1px solid #e5e5e5;
-}
-tbody tr:nth-child(odd) td,
-tbody tr:nth-child(odd) th {
- background-color: #f9f9f9;
-}
-
-
-/*
- * Custom type
- *
- * Extend paragraphs with `.lead` for larger introductory text.
- */
-
-.lead {
- font-size: 1.25rem;
- font-weight: 300;
-}
-
-
-/*
- * Messages
- *
- * Show alert messages to users. You may add it to single elements like a `
`,
- * or to a parent if there are multiple elements to show.
- */
-
-.message {
- margin-bottom: 1rem;
- padding: 1rem;
- color: #717171;
- background-color: #f9f9f9;
-}
-
-
-/*
- * Container
- *
- * Center the page content.
- */
-
-.container {
- max-width: 45rem;
- padding-left: 1rem;
- padding-right: 1rem;
- margin-left: auto;
- margin-right: auto;
-}
-
-
-/*
- * Masthead
- *
- * Super small header above the content for site name and short description.
- */
-
-.masthead {
- padding-top: 1rem;
- padding-bottom: 1rem;
- margin-bottom: 1rem;
-}
-.masthead-title {
- margin-top: 0;
- margin-bottom: 0;
- color: #505050;
-}
-.masthead-title a {
- color: #505050;
-}
-.masthead small {
- font-size: 75%;
- font-weight: 400;
- color: #c0c0c0;
- letter-spacing: 0;
-}
-
-
-/*
- * Posts and pages
- *
- * Each post is wrapped in `.post` and is used on default and post layouts. Each
- * page is wrapped in `.page` and is only used on the page layout.
- */
-
-.page {
- margin-bottom: 4em;
-}
-
-/* Blog post or page title */
-.page-title,
-.post-title,
-.post-title a {
- color: #303030;
-}
-.page-title,
-.post-title {
- margin-top: 0;
-}
-
-/* Meta data line below post title */
-.post-date {
- display: block;
- margin-top: -.5rem;
- margin-bottom: 1rem;
- color: #9a9a9a;
-}
-
-/* Related posts */
-.related {
- padding-top: 2rem;
- padding-bottom: 2rem;
- border-top: 1px solid #eee;
-}
-.related-posts {
- padding-left: 0;
- list-style: none;
-}
-.related-posts h3 {
- margin-top: 0;
-}
-.related-posts li small {
- font-size: 75%;
- color: #999;
-}
-.related-posts li a:hover {
- color: #268bd2;
- text-decoration: none;
-}
-.related-posts li a:hover small {
- color: inherit;
-}
-
-
-/*
- * Pagination
- *
- * Super lightweight (HTML-wise) blog pagination. `span`s are provide for when
- * there are no more previous or next posts to show.
- */
-
-.pagination {
- overflow: hidden; /* clearfix */
- margin-left: -1rem;
- margin-right: -1rem;
- font-family: "PT Sans", Helvetica, Arial, sans-serif;
- color: #ccc;
- text-align: center;
-}
-
-/* Pagination items can be `span`s or `a`s */
-.pagination-item {
- display: block;
- padding: 1rem;
- border: 1px solid #eee;
-}
-.pagination-item:first-child {
- margin-bottom: -1px;
-}
-
-/* Only provide a hover state for linked pagination items */
-a.pagination-item:hover {
- background-color: #f5f5f5;
-}
-
-@media (min-width: 30rem) {
- .pagination {
- margin: 3rem 0;
- }
- .pagination-item {
- float: left;
- width: 50%;
- }
- .pagination-item:first-child {
- margin-bottom: 0;
- border-top-left-radius: 4px;
- border-bottom-left-radius: 4px;
- }
- .pagination-item:last-child {
- margin-left: -1px;
- border-top-right-radius: 4px;
- border-bottom-right-radius: 4px;
- }
-}
diff --git a/blog/static/js/edition-2/main.js b/blog/static/js/edition-2/main.js
index dfa1dd4d..f4cb57c2 100644
--- a/blog/static/js/edition-2/main.js
+++ b/blog/static/js/edition-2/main.js
@@ -1,17 +1,23 @@
-window.onload = function() {
- var container = document.querySelector('#toc-aside');
-
+window.onload = function () {
+ let container = document.querySelector('#toc-aside');
if (container != null) {
resize_toc(container);
toc_scroll_position(container);
- window.onscroll = function() { toc_scroll_position(container) };
+ window.onscroll = function () { toc_scroll_position(container) };
+ }
+
+ let theme = localStorage.getItem("theme");
+ if (theme != null) {
+ setTimeout(() => {
+ set_giscus_theme(theme)
+ }, 500);
}
}
function resize_toc(container) {
- var containerHeight = container.clientHeight;
+ let containerHeight = container.clientHeight;
- var resize = function() {
+ let resize = function () {
if (containerHeight > document.documentElement.clientHeight - 100) {
container.classList.add('coarse');
} else {
@@ -20,8 +26,8 @@ function resize_toc(container) {
};
resize();
- var resizeId;
- window.onresize = function() {
+ let resizeId;
+ window.onresize = function () {
clearTimeout(resizeId);
resizeId = setTimeout(resize, 300);
};
@@ -32,7 +38,6 @@ function toc_scroll_position(container) {
// skip computation if ToC is not visible
return;
}
- var items = container.querySelectorAll("li")
// remove active class for all items
for (item of container.querySelectorAll("li")) {
@@ -40,15 +45,15 @@ function toc_scroll_position(container) {
}
// look for active item
- var site_offset = document.documentElement.scrollTop;
- var current_toc_item = null;
+ let site_offset = document.documentElement.scrollTop;
+ let current_toc_item = null;
for (item of container.querySelectorAll("li")) {
if (item.offsetParent === null) {
// skip items that are not visible
continue;
}
- var anchor = item.firstElementChild.getAttribute("href");
- var heading = document.querySelector(anchor);
+ let anchor = item.firstElementChild.getAttribute("href");
+ let heading = document.querySelector(anchor);
if (heading.offsetTop <= (site_offset + document.documentElement.clientHeight / 3)) {
current_toc_item = item;
} else {
@@ -61,3 +66,35 @@ function toc_scroll_position(container) {
current_toc_item.classList.add("active");
}
}
+
+function toggle_lights() {
+ if (document.documentElement.getAttribute("data-theme") === "dark") {
+ set_theme("light")
+ } else if (document.documentElement.getAttribute("data-theme") === "light") {
+ set_theme("dark")
+ } else {
+ set_theme(window.matchMedia("(prefers-color-scheme: dark)").matches ? "light" : "dark")
+ }
+}
+
+function set_theme(theme) {
+ document.documentElement.setAttribute("data-theme", theme)
+ set_giscus_theme(theme)
+ localStorage.setItem("theme", theme)
+}
+
+function clear_theme_override() {
+ document.documentElement.removeAttribute("data-theme");
+ set_giscus_theme("preferred_color_scheme")
+ localStorage.removeItem("theme")
+}
+
+function set_giscus_theme(theme) {
+ let comment_form = document.querySelector("iframe.giscus-frame");
+ if (comment_form != null) {
+ comment_form.contentWindow.postMessage({
+ giscus: { setConfig: { theme: theme } }
+ }, "https://giscus.app")
+ }
+}
+
diff --git a/blog/templates/edition-1/base.html b/blog/templates/edition-1/base.html
index f2ee6390..c553b675 100644
--- a/blog/templates/edition-1/base.html
+++ b/blog/templates/edition-1/base.html
@@ -40,21 +40,7 @@
-
-
-
+