<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title/><link>https://fehac.dev/en/</link><atom:link href="https://fehac.dev/en/index.xml" rel="self" type="application/rss+xml"/><description/><generator>HugoBlox Kit (https://hugoblox.com)</generator><language>en-us</language><lastBuildDate>Sat, 08 Aug 2026 00:00:00 +0000</lastBuildDate><image><url>https://fehac.dev/media/icon.svg</url><title/><link>https://fehac.dev/en/</link></image><item><title>About</title><link>https://fehac.dev/en/about/</link><pubDate>Sat, 08 Aug 2026 00:00:00 +0000</pubDate><guid>https://fehac.dev/en/about/</guid><description>&lt;p&gt;
&lt;figure &gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="Felipe Cardoso"
srcset="https://fehac.dev/media/authors/me_hu_366adc2a9fb5980a.webp 320w, https://fehac.dev/media/authors/me_hu_4906c0d8c548a729.webp 480w, https://fehac.dev/media/authors/me_hu_104d08bb37dec6e0.webp 760w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://fehac.dev/media/authors/me_hu_366adc2a9fb5980a.webp"
width="760"
height="760"
loading="lazy" data-zoomable /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m &lt;strong&gt;Felipe Cardoso&lt;/strong&gt;, a Brazilian software engineer. I live in Tokyo and
have been working on backend, cloud infrastructure and distributed systems
since 2018, along a path that runs through a bank, a consultancy, a global
brewer and, now, an electric mobility startup.&lt;/p&gt;
&lt;p&gt;I like owning a whole system: understanding the problem, deciding the
architecture, writing the code, shipping it and answering for it when it
breaks. Language, database and cloud choices come after the problem, never
before.&lt;/p&gt;
&lt;h2 id="why-this-blog-exists"&gt;Why this blog exists&lt;/h2&gt;
&lt;p&gt;To share what I learn and what I think about software engineering and
technology. The technical posts come out of what I am actually building,
including the parts that went wrong and rarely show up when only the final
version gets told. The rest is opinion: how we build software, what is worth
doing and what is just fashion.&lt;/p&gt;
&lt;p&gt;I write in Portuguese, English and Japanese. Comments are anonymous by
default, because disagreeing with me should not require an account anywhere.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The opinions published here are my own.&lt;/strong&gt; They do not represent or
necessarily reflect the positions of the companies I work for or have worked
for, or those of their customers.&lt;/p&gt;
&lt;h2 id="what-i-am-doing-now"&gt;What I am doing now&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;m a backend and infrastructure engineer at &lt;strong&gt;eMotion Fleet&lt;/strong&gt;, a Tokyo
startup building fleet and energy management software for electric bus
operators in Japan, including Nishitetsu and Nagasaki Bus.&lt;/p&gt;
&lt;p&gt;The heart of the product is a real-time decision problem: how much power to
send to each bus plugged in at the depot, without blowing past the contracted
energy limit, while making sure every vehicle leaves on schedule with enough
charge. In my first month I proposed an AI model for that problem and built
the prototype that became the platform&amp;rsquo;s core decision engine, the same
engine that helped the company pass its first customer acceptance test and
reach its first production go-live in September 2025.&lt;/p&gt;
&lt;p&gt;Beyond the engine, I look after the AWS platform, lead product streams from
design to delivery and handle production incidents.&lt;/p&gt;
&lt;h2 id="where-i-have-been"&gt;Where I have been&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;AB InBev, 2022 to 2024.&lt;/strong&gt; I worked on BEES, the B2B commerce platform with
over 1.5 million users and $49 billion moving through it every year. I built
microservices for the rewards program, which reached 2.3 million members with
an NPS of +64, and the reusable Terraform and Terragrunt infrastructure
behind the company-wide migration from AWS to Azure. On the eve of the South
Africa launch, I fixed the production issues that were threatening the date.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Itaú Unibanco, 2019 to 2022.&lt;/strong&gt; I finished there as tech lead on a pricing
platform, leading three developers: corporate price negotiations that used to
depend on spreadsheets and take hours dropped to minutes, with the
calculations and client-level profitability analysis automated in C# and
ASP.NET Core and the interface built in Angular. Before that, in fraud
prevention, I mined historical patterns with Python, SAS and Power BI and
implemented T-SQL detection rules that helped cut debit-card fraud by about
12%.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;SAS, 2018 to 2019.&lt;/strong&gt; Data engineering consulting: anti-corruption
corporate-card analytics for Vale and nationwide fuel supply and demand
forecasting for Raízen&amp;rsquo;s station network.&lt;/p&gt;
&lt;h2 id="education"&gt;Education&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;PUC Minas&lt;/strong&gt;, Postgraduate Degree in Distributed Software Architecture (2024-2025)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Universidade Federal do ABC&lt;/strong&gt;, BTech in Computer Science (2018-2023)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Universidade Federal do ABC&lt;/strong&gt;, BS in Science and Technology (2018-2023)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="contact"&gt;Contact&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;m open to remote roles with globally distributed teams.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Email:
&lt;/li&gt;
&lt;li&gt;LinkedIn:
&lt;/li&gt;
&lt;li&gt;GitHub:
&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>What this blog can render</title><link>https://fehac.dev/en/blog/capabilities/</link><pubDate>Sat, 08 Aug 2026 00:00:00 +0000</pubDate><guid>https://fehac.dev/en/blog/capabilities/</guid><description>&lt;p&gt;A reference for my future self. Everything below is verified against the
theme&amp;rsquo;s own source, not guessed from documentation. Each section shows the
markup and the thing it produces.&lt;/p&gt;
&lt;h2 id="callouts"&gt;Callouts&lt;/h2&gt;
&lt;p&gt;GitHub / Obsidian alert syntax. Five types: &lt;code&gt;NOTE&lt;/code&gt;, &lt;code&gt;TIP&lt;/code&gt;, &lt;code&gt;IMPORTANT&lt;/code&gt;,
&lt;code&gt;WARNING&lt;/code&gt;, &lt;code&gt;CAUTION&lt;/code&gt;. Append &lt;code&gt;+ Some Title&lt;/code&gt; for a custom heading.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;&amp;gt; &lt;/span&gt;&lt;span class="ge"&gt;[!NOTE]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;&amp;gt; &lt;/span&gt;&lt;span class="ge"&gt;Plain note.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;&amp;gt; &lt;/span&gt;&lt;span class="ge"&gt;[!WARNING] Careful with this one
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;&amp;gt; &lt;/span&gt;&lt;span class="ge"&gt;A warning with a custom title.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class="callout flex px-4 py-3 mb-6 rounded-md border-l-4 bg-blue-100 dark:bg-blue-900 border-blue-500"
data-callout="note"
data-callout-metadata=""&gt;
&lt;span class="callout-icon pr-3 pt-1 text-blue-600 dark:text-blue-300"&gt;
&lt;svg height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"&gt;&lt;path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="m16.862 4.487l1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L6.832 19.82a4.5 4.5 0 0 1-1.897 1.13l-2.685.8l.8-2.685a4.5 4.5 0 0 1 1.13-1.897zm0 0L19.5 7.125"/&gt;&lt;/svg&gt;
&lt;/span&gt;
&lt;div class="callout-content dark:text-neutral-300"&gt;
&lt;div class="callout-title font-semibold mb-1"&gt;Note&lt;/div&gt;
&lt;div class="callout-body"&gt;&lt;p&gt;Plain note.&lt;/p&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="callout flex px-4 py-3 mb-6 rounded-md border-l-4 bg-emerald-100 dark:bg-emerald-900 border-emerald-500"
data-callout="tip"
data-callout-metadata=""&gt;
&lt;span class="callout-icon pr-3 pt-1 text-emerald-600 dark:text-emerald-300"&gt;
&lt;svg height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"&gt;&lt;path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M12 18v-5.25m0 0a6 6 0 0 0 1.5-.189m-1.5.189a6 6 0 0 1-1.5-.189m3.75 7.478a12.1 12.1 0 0 1-4.5 0m3.75 2.383a14.4 14.4 0 0 1-3 0M14.25 18v-.192c0-.983.658-1.823 1.508-2.316a7.5 7.5 0 1 0-7.517 0c.85.493 1.509 1.333 1.509 2.316V18"/&gt;&lt;/svg&gt;
&lt;/span&gt;
&lt;div class="callout-content dark:text-neutral-300"&gt;
&lt;div class="callout-title font-semibold mb-1"&gt;Tip&lt;/div&gt;
&lt;div class="callout-body"&gt;&lt;p&gt;Tips look like this.&lt;/p&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="callout flex px-4 py-3 mb-6 rounded-md border-l-4 bg-orange-100 dark:bg-orange-900 border-orange-500"
data-callout="warning"
data-callout-metadata=""&gt;
&lt;span class="callout-icon pr-3 pt-1 text-orange-600 dark:text-orange-300"&gt;
&lt;svg height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"&gt;&lt;path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0zM12 15.75h.007v.008H12z"/&gt;&lt;/svg&gt;
&lt;/span&gt;
&lt;div class="callout-content dark:text-neutral-300"&gt;
&lt;div class="callout-title font-semibold mb-1"&gt;Careful with this one&lt;/div&gt;
&lt;div class="callout-body"&gt;&lt;p&gt;A warning with a custom title.&lt;/p&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="callout flex px-4 py-3 mb-6 rounded-md border-l-4 bg-red-100 dark:bg-red-900 border-red-500"
data-callout="caution"
data-callout-metadata=""&gt;
&lt;span class="callout-icon pr-3 pt-1 text-red-600 dark:text-red-300"&gt;
&lt;svg height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"&gt;&lt;path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0zM12 15.75h.007v.008H12z"/&gt;&lt;/svg&gt;
&lt;/span&gt;
&lt;div class="callout-content dark:text-neutral-300"&gt;
&lt;div class="callout-title font-semibold mb-1"&gt;Caution&lt;/div&gt;
&lt;div class="callout-body"&gt;&lt;p&gt;The strongest one.&lt;/p&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;There is also a &lt;code&gt;{{&amp;lt; callout &amp;gt;}}&lt;/code&gt; shortcode, but the theme marks it
&lt;strong&gt;deprecated&lt;/strong&gt; in favour of the syntax above. Don&amp;rsquo;t use it.&lt;/p&gt;
&lt;h2 id="mermaid"&gt;Mermaid&lt;/h2&gt;
&lt;p&gt;Fenced block with &lt;code&gt;mermaid&lt;/code&gt; as the language. The script only loads on pages
that actually contain one.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;```mermaid
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sequenceDiagram
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Browser-&amp;gt;&amp;gt;Pages: GET /en/blog/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Pages--&amp;gt;&amp;gt;Browser: static HTML
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Browser-&amp;gt;&amp;gt;Remark42: GET /api/v1/find
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;```&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="mermaid"&gt;sequenceDiagram
autonumber
participant B as Browser
participant P as Cloudflare Pages
participant R as Remark42 (VPS)
B-&gt;&gt;P: GET /en/blog/capabilities/
P--&gt;&gt;B: static HTML + embed.js
B-&gt;&gt;R: GET /api/v1/find?site=blog
R--&gt;&gt;B: comments JSON
Note over B,R: anonymous, no account needed
&lt;/div&gt;
&lt;p&gt;Flowcharts work too:&lt;/p&gt;
&lt;div class="mermaid"&gt;flowchart LR
A[git push] --&gt; B{branch?}
B --&gt;|main| C[production build]
B --&gt;|other| D[preview deploy]
C --&gt; E[fehac.dev]
D --&gt; F[*.pages.dev]
&lt;/div&gt;
&lt;h2 id="markmap"&gt;Markmap&lt;/h2&gt;
&lt;p&gt;Mind maps from a nested list. Takes a &lt;code&gt;height&lt;/code&gt; attribute.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;``&lt;span class="sb"&gt;`markmap {height=&amp;#34;300px&amp;#34;}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sb"&gt;- Root
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sb"&gt; - Branch
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sb"&gt; - Leaf
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sb"&gt;`&lt;/span&gt;``
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="markmap" style="height: 420px;"&gt;
&lt;pre&gt;- Backend
- Services
- APIs
- Event-driven
- Data
- PostgreSQL
- MongoDB
- Infrastructure
- AWS
- CDK
- CloudFormation
- Azure
- Terraform
- Practice
- CI/CD
- Observability
- Incident response&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 id="math"&gt;Math&lt;/h2&gt;
&lt;p&gt;KaTeX. Loads when the page has &lt;code&gt;math: true&lt;/code&gt; in front matter, a &lt;code&gt;math&lt;/code&gt;
shortcode, or the site-wide flag. Inline with &lt;code&gt;$...$&lt;/code&gt;, display with &lt;code&gt;$$...$$&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Inline: $O(n \log n)$
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$$
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;p_{99} = \inf\{x : F(x) \ge 0.99\}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$$
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Inline: $O(n \log n)$ is the bound.&lt;/p&gt;
$$
p_{99} = \inf\{x : F(x) \ge 0.99\}
$$&lt;h2 id="code"&gt;Code&lt;/h2&gt;
&lt;p&gt;Standard fenced blocks, highlighted by Hugo&amp;rsquo;s built-in Chroma. Line numbers are
off by default site-wide but can be turned on per block.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;``&lt;span class="sb"&gt;`python {linenos=true, hl_lines=[2]}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sb"&gt;def charge(rate_kw: float) -&amp;gt; float:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sb"&gt; return min(rate_kw, MAX_KW)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sb"&gt;`&lt;/span&gt;``
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;charge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rate_kw&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rate_kw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MAX_KW&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# clamp to the connector limit&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="steps"&gt;Steps&lt;/h2&gt;
&lt;p&gt;Wrap &lt;code&gt;###&lt;/code&gt; headings. Each becomes a numbered step.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{{&amp;lt; steps &amp;gt;}}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gu"&gt;### First thing
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Do this.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gu"&gt;### Second thing
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Then this.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{{&amp;lt; /steps &amp;gt;}}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="hb-steps"&gt;
### Provision DNS
An `A` record has to resolve before certbot will issue anything.
### Issue the certificate
`certbot certonly --webroot`, then swap in the TLS vhost.
### Reload, don't restart
`nginx -t` first. It refuses a bad config instead of taking the box down.
&lt;/div&gt;
&lt;h2 id="spoilers"&gt;Spoilers&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{{&amp;lt; spoiler text=&amp;#34;Click to expand&amp;#34; &amp;gt;}}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Hidden content.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{{&amp;lt; /spoiler &amp;gt;}}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;details class="spoiler " id="spoiler-1"&gt;
&lt;summary class="cursor-pointer"&gt;Why the comment server is not on Cloudflare&lt;/summary&gt;
&lt;div class="rounded-lg bg-neutral-50 dark:bg-neutral-800 p-2"&gt;
Pages is static hosting. Remark42 is a stateful Go process with an embedded
BoltDB, so it needs a real host. It runs on my VPS behind nginx, bound to
loopback only.
&lt;/div&gt;
&lt;/details&gt;
&lt;h2 id="buttons"&gt;Buttons&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{{&amp;lt; button url=&amp;#34;/en/about/&amp;#34; &amp;gt;}}About me{{&amp;lt; /button &amp;gt;}}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class="text-left"&gt;
&lt;a
id="button-db7ad9c7301f40597206b33fcdb455e1"
href="https://fehac.dev/en/about/"
class="inline-flex items-center gap-2 font-medium no-underline transition-all duration-300 ease-out transform-gpu focus:outline-none focus:ring-4 focus:ring-offset-2 focus:ring-offset-white dark:focus:ring-offset-zinc-900 disabled:opacity-50 disabled:cursor-not-allowed disabled:pointer-events-none bg-gradient-to-br from-primary-500 to-primary-600 hover:from-primary-600 hover:to-primary-700 active:from-primary-700 active:to-primary-800 text-white shadow-lg shadow-primary-500/25 hover:shadow-xl hover:shadow-primary-500/30 hover:-translate-y-0.5 hover:scale-[1.02] active:scale-[0.98] focus:ring-primary-500/50 px-4 py-2 text-base rounded-lg"
role="button"
aria-label="About me"
&gt;
&lt;span&gt;About me&lt;/span&gt;
&lt;/a&gt;
&lt;/div&gt;
&lt;h2 id="charts"&gt;Charts&lt;/h2&gt;
&lt;p&gt;Plotly. Point &lt;code&gt;data&lt;/code&gt; at a JSON file in the page folder, without the extension.
Here &lt;code&gt;demo-chart.json&lt;/code&gt; sits next to this &lt;code&gt;index.md&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{{&amp;lt; chart data=&amp;#34;demo-chart&amp;#34; &amp;gt;}}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div id="chart-817932465" class="chart"&gt;&lt;/div&gt;
&lt;script&gt;
(function() {
async function fetchChartJSON() {
console.debug('Hugo Blox fetching chart JSON...')
const response = await fetch('.\/demo-chart.json');
return await response.json();
}
let a = setInterval( function() {
if ( typeof window.Plotly === 'undefined' ) {
console.debug('Plotly not loaded yet...')
return;
}
clearInterval( a );
fetchChartJSON().then(chart =&gt; {
console.debug('Plotting chart...')
window.Plotly.newPlot('chart-817932465', chart.data, chart.layout, {responsive: true});
});
}, 500 );
})();
&lt;/script&gt;
&lt;h2 id="csv-tables"&gt;CSV tables&lt;/h2&gt;
&lt;p&gt;Same idea: a &lt;code&gt;.csv&lt;/code&gt; in the page folder. Note the parameter is &lt;code&gt;path&lt;/code&gt;,
not &lt;code&gt;src&lt;/code&gt;. The shortcode&amp;rsquo;s own docstring says &lt;code&gt;src&lt;/code&gt;, but the code reads
&lt;code&gt;.Get &amp;quot;path&amp;quot;&lt;/code&gt;. Using &lt;code&gt;src&lt;/code&gt; fails the build with a nil-pointer error.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{{&amp;lt; table path=&amp;#34;demo-table.csv&amp;#34; caption=&amp;#34;Latency by service&amp;#34; &amp;gt;}}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;table class="table-auto w-full"&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td data-table-dtype="text" class="border-b border-slate-100 dark:border-slate-700 p-4 text-slate-500 dark:text-slate-400"&gt;service&lt;/td&gt;
&lt;td data-table-dtype="text" class="border-b border-slate-100 dark:border-slate-700 p-4 text-slate-500 dark:text-slate-400"&gt;language&lt;/td&gt;
&lt;td data-table-dtype="text" class="border-b border-slate-100 dark:border-slate-700 p-4 text-slate-500 dark:text-slate-400"&gt;p99_ms&lt;/td&gt;
&lt;td data-table-dtype="text" class="border-b border-slate-100 dark:border-slate-700 p-4 text-slate-500 dark:text-slate-400"&gt;error_rate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td data-table-dtype="text" class="border-b border-slate-100 dark:border-slate-700 p-4 text-slate-500 dark:text-slate-400"&gt;charging-engine&lt;/td&gt;
&lt;td data-table-dtype="text" class="border-b border-slate-100 dark:border-slate-700 p-4 text-slate-500 dark:text-slate-400"&gt;Python&lt;/td&gt;
&lt;td data-table-dtype="number" class="border-b border-slate-100 dark:border-slate-700 p-4 text-slate-500 dark:text-slate-400"&gt;63&lt;/td&gt;
&lt;td data-table-dtype="text" class="border-b border-slate-100 dark:border-slate-700 p-4 text-slate-500 dark:text-slate-400"&gt;0.02&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td data-table-dtype="text" class="border-b border-slate-100 dark:border-slate-700 p-4 text-slate-500 dark:text-slate-400"&gt;telemetry-ingest&lt;/td&gt;
&lt;td data-table-dtype="text" class="border-b border-slate-100 dark:border-slate-700 p-4 text-slate-500 dark:text-slate-400"&gt;Python&lt;/td&gt;
&lt;td data-table-dtype="number" class="border-b border-slate-100 dark:border-slate-700 p-4 text-slate-500 dark:text-slate-400"&gt;148&lt;/td&gt;
&lt;td data-table-dtype="text" class="border-b border-slate-100 dark:border-slate-700 p-4 text-slate-500 dark:text-slate-400"&gt;0.11&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td data-table-dtype="text" class="border-b border-slate-100 dark:border-slate-700 p-4 text-slate-500 dark:text-slate-400"&gt;fleet-api&lt;/td&gt;
&lt;td data-table-dtype="text" class="border-b border-slate-100 dark:border-slate-700 p-4 text-slate-500 dark:text-slate-400"&gt;TypeScript&lt;/td&gt;
&lt;td data-table-dtype="number" class="border-b border-slate-100 dark:border-slate-700 p-4 text-slate-500 dark:text-slate-400"&gt;41&lt;/td&gt;
&lt;td data-table-dtype="text" class="border-b border-slate-100 dark:border-slate-700 p-4 text-slate-500 dark:text-slate-400"&gt;0.01&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;caption class="table-caption"&gt;Latency by service&lt;/caption&gt;
&lt;/table&gt;
&lt;p&gt;Regular Markdown tables also work:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Supported&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Mermaid&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;lazy-loaded per page&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Markmap&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;&lt;code&gt;height&lt;/code&gt; attribute&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PlantUML&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;no&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;see below&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="images"&gt;Images&lt;/h2&gt;
&lt;p&gt;Plain Markdown. The render hook looks in the page folder first, then in
&lt;code&gt;assets/media/&lt;/code&gt;, and emits a responsive &lt;code&gt;srcset&lt;/code&gt; with generated sizes.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;![&lt;span class="nt"&gt;Alt text&lt;/span&gt;](&lt;span class="na"&gt;authors/me.webp&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="video-and-audio"&gt;Video and audio&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{{&amp;lt; video src=&amp;#34;clip.mp4&amp;#34; controls=&amp;#34;yes&amp;#34; &amp;gt;}}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{{&amp;lt; audio src=&amp;#34;episode.mp3&amp;#34; &amp;gt;}}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Both resolve the page folder first, then &lt;code&gt;assets/media/&lt;/code&gt;, then a remote URL.&lt;/p&gt;
&lt;h2 id="icons"&gt;Icons&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{{&amp;lt; icon name=&amp;#34;brands/github&amp;#34; &amp;gt;}} inline in a sentence.
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;
&lt;span class="inline-block pr-1"&gt;
&lt;svg style="height: 1em; transform: translateY(0.1em);" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"&gt;&lt;path fill="currentColor" d="M12 .297c-6.63 0-12 5.373-12 12c0 5.303 3.438 9.8 8.205 11.385c.6.113.82-.258.82-.577c0-.285-.01-1.04-.015-2.04c-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729c1.205.084 1.838 1.236 1.838 1.236c1.07 1.835 2.809 1.305 3.495.998c.108-.776.417-1.305.76-1.605c-2.665-.3-5.466-1.332-5.466-5.93c0-1.31.465-2.38 1.235-3.22c-.135-.303-.54-1.523.105-3.176c0 0 1.005-.322 3.3 1.23c.96-.267 1.98-.399 3-.405c1.02.006 2.04.138 3 .405c2.28-1.552 3.285-1.23 3.285-1.23c.645 1.653.24 2.873.12 3.176c.765.84 1.23 1.91 1.23 3.22c0 4.61-2.805 5.625-5.475 5.92c.42.36.81 1.096.81 2.22c0 1.606-.015 2.896-.015 3.286c0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/&gt;&lt;/svg&gt;
&lt;/span&gt; inline in a sentence.&lt;/p&gt;
&lt;h2 id="jupyter-notebooks"&gt;Jupyter notebooks&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{{&amp;lt; notebook path=&amp;#34;analysis.ipynb&amp;#34; &amp;gt;}}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Renders an &lt;code&gt;.ipynb&lt;/code&gt; inline, useful for the data-heavy posts.&lt;/p&gt;
&lt;h2 id="includes"&gt;Includes&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{{&amp;lt; include &amp;#34;snippets/shared-warning.md&amp;#34; &amp;gt;}}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Pulls another Markdown file in, path relative to &lt;code&gt;content/&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="what-is-not-supported"&gt;What is &lt;em&gt;not&lt;/em&gt; supported&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;PlantUML.&lt;/strong&gt; There is no PlantUML render hook or script anywhere in this
theme. I checked the layouts and the JS bundle. Options if I ever need it:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Use Mermaid instead. It covers sequence, flowchart, class, state, ER, and
Gantt, which is most of what PlantUML gets used for.&lt;/li&gt;
&lt;li&gt;Render the &lt;code&gt;.puml&lt;/code&gt; to SVG offline and commit the SVG, then embed it as a
normal image. No build-time dependency, no external server.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;A PlantUML render hook would be maybe twenty lines in
&lt;code&gt;layouts/_markup/render-codeblock-plantuml.html&lt;/code&gt;, but it would have to call out
to a rendering server at build time, which is why I&amp;rsquo;d rather not.&lt;/p&gt;
&lt;h2 id="front-matter-that-matters"&gt;Front matter that matters&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nn"&gt;---&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Post title&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Shown in listings and meta description.&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ld"&gt;2026-08-08&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;translationKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;some-key &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# links the pt/en/ja versions together&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;authors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="l"&gt;me]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="l"&gt;meta, markdown]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;math&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# load KaTeX on this page&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;commentable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# inherited from blog/_index.md already&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nn"&gt;---&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;translationKey&lt;/code&gt; is the important one: it is what makes the language switcher
jump to the same article instead of the homepage.&lt;/p&gt;</description></item></channel></rss>