<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title>Sverre</title>
    <link rel="self" type="application/atom+xml" href="https://sverre.me/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://sverre.me"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2026-07-30T00:00:00+00:00</updated>
    <id>https://sverre.me/atom.xml</id>
    <entry xml:lang="en">
        <title>Code Wallet: My first published android app</title>
        <published>2026-07-30T00:00:00+00:00</published>
        <updated>2026-07-30T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Sverre Johann Bjørke
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sverre.me/projects/codewallet/"/>
        <id>https://sverre.me/projects/codewallet/</id>
        
        <content type="html" xml:base="https://sverre.me/projects/codewallet/">&lt;p&gt;&lt;strong&gt;Short version:&lt;&#x2F;strong&gt; I built a simple and small Android app for storing barcodes and QR codes on my phone.&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;play.google.com&#x2F;store&#x2F;apps&#x2F;details?id=me.sverre.codewallet&quot;&gt;Google Playstore&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;long-version&quot;&gt;Long version&lt;&#x2F;h2&gt;
&lt;p&gt;In January this year I started using a &quot;dumber&quot; Android phone as my main phone. A Duoqin F21 Pro, running DumberOS (A lineage fork based on Android 14). I still intend to write a blog post about it and my experiences, but I have procrastinated that for more than six months and see no reason to stop now. Most apps I used before work surprisingly well, but as the phone does not have a NFC chip, Google Wallet is a no-go. Which is completely fine, as I am very slowly trying to pull my life out of Big G&#x27;s clammy hands. But a genuinely useful feature of Google Wallet is adding all your loyalty cards and similar in the app. I find it very convenient to have everything in one app instead of one per store.&lt;&#x2F;p&gt;
&lt;p&gt;I found myself wanting an alternative, but every app I tried either sucked, tried to do everything, was bloated with ads or just plainly unusable on my new phone. So I ended up making my own. I haven&#x27;t touched Android development for over 13 years, but I have used Kotlin professionally, and after asking Claude to scaffold me an Android app with Gradle and all that faff I loathe, I was off to the races.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;small&quot;&gt;Small&lt;&#x2F;h2&gt;
&lt;p&gt;One of my biggest gripes with the alternatives I surveyed was that they all were in the range of several megabytes in size, some up to 50 megabytes. My first and most important goal with this project was that the app would be less than one megabyte. Keeping the used dependencies to a minimum is an increasingly important factor in software development these days, so this would be a win in several ways. The only dependency is ZXing, for the actual barcode encoding and decoding, since reinventing that particular wheel was a bridge too far (although probably an interesting project in its own right...).&lt;&#x2F;p&gt;
&lt;p&gt;The finished app weighs in at &lt;strong&gt;211 KB&lt;&#x2F;strong&gt;, and this pleases me quite a lot.&lt;&#x2F;p&gt;
&lt;div style=&quot;display:flex; gap:1rem; flex-wrap:wrap; justify-content:center;&quot;&gt;
&lt;img src=&quot;card-list.png&quot; alt=&quot;Code Wallet&#x27;s main view, showing various placeholder cards&quot; style=&quot;width:35%; min-width:220px; max-width:378px; height:auto;&quot;&gt;
&lt;img src=&quot;card-display.png&quot; alt=&quot;A QR code shown fullscreen&quot; style=&quot;width:35%; min-width:220px; max-width:378px; height:auto;&quot;&gt;
&lt;&#x2F;div&gt;
&lt;h2 id=&quot;fast&quot;&gt;Fast&lt;&#x2F;h2&gt;
&lt;p&gt;The other design goal of this project was making the app fast. The F21 Pro is very limited hardware-wise, and standing in the checkout and then having to wait for the blasted codes to load was something I wanted to avoid. Researching techniques to make apps fast was a rabbit hole, but since the feature scope was quite limited, and I also made it lightweight via the size goal, there was not a whole lot I could tune.&lt;&#x2F;p&gt;
&lt;p&gt;The &lt;code&gt;Application&lt;&#x2F;code&gt; class does essentially nothing on startup: the repository (and the SQLite file behind it) is opened lazily, so the database isn&#x27;t touched until the first screen actually queries it, after the first frame is already on screen. &lt;code&gt;onCreate()&lt;&#x2F;code&gt; in the launcher activity just inflates the layout and wires up listeners, the actual data loading happens in &lt;code&gt;onResume()&lt;&#x2F;code&gt;, so nothing blocks the first paint.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;testing-hurdles&quot;&gt;Testing hurdles&lt;&#x2F;h2&gt;
&lt;p&gt;Having used the app myself for a while, and feeling pretty good about it, I figured I might as well publish it on Google Play (and eventually F-Droid, I just have to figure out how.) Turns out that writing the app was the easy part. The song and dance of publishing wants a privacy policy (for an app with no internet permission where everything stays on your device...), screenshots in several shapes and sizes, a content rating questionnaire, and a closed testing period with a minimum of 12 active testers held for a minimum of 14 days before it lets you go public. The play console is a masterclass in poor UX design, and while not as horrible as the GCP console, it is a close second. Google seems to assume that developers do not need good UX.&lt;&#x2F;p&gt;
&lt;p&gt;Google is also incredibly nebulous about the testing phase, as what constitutes an &quot;active&quot; tester is not explained anywhere. Word on the street is that the testers need to test the app for at least 15 seconds each day in order to qualify. And even if you do complete the testing, you still might be required to test the app further. As my app is very small in scope, testing it that much for two weeks feels utterly silly. The whole thing can be explored in two minutes. Add to this that I would be hard pressed to count 12 friends that are Android users AND would be willing to spend time every day to test my app AND are tech savvy enough to opt into the testing process, I ended up looking for alternatives.&lt;&#x2F;p&gt;
&lt;p&gt;There is of course a &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;AndroidClosedTesting&#x2F;&quot;&gt;subreddit&lt;&#x2F;a&gt; for this, but my post advertising for testers had very limited response. I did, however, come across an app called &lt;em&gt;App Hive&lt;&#x2F;em&gt; which aims to tackle this problem by grouping users into &quot;hives&quot; of 17 (there will be some dropouts) users where everyone is required to test the other users&#x27; apps each day for 14 days. While extremely tedious, this worked well enough and I was finally approved for production. Hooray 🎉&lt;&#x2F;p&gt;
&lt;p&gt;I get that some sort of filter is needed with the (probable) influx of low-effort AI generated apps and malware. But I feel the process should be a lot simpler to understand, and that it should be graded based on the size of the app, the permissions it require or other factors. I dread having to go through the hoops again, should another app idea pop up.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Rust (and Slint) on a jailbroken Kindle.</title>
        <published>2026-05-26T00:00:00+00:00</published>
        <updated>2026-05-26T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Sverre Johann Bjørke
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sverre.me/blog/rust-on-kindle/"/>
        <id>https://sverre.me/blog/rust-on-kindle/</id>
        
        <content type="html" xml:base="https://sverre.me/blog/rust-on-kindle/">&lt;p&gt;I recently jailbroke my 7th generation Kindle Paperwhite. While my motivation probably should have been &quot;breaking free from Amazon&#x27;s clammy and tightening grip&quot;, the truth is I wanted a way to use it as a clock on my nightstand. I found &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;byronknoll&#x2F;kindle-clock&quot;&gt;this project&lt;&#x2F;a&gt; and figured I could just make some adjustments to the code. And that worked &lt;em&gt;fine&lt;&#x2F;em&gt;. But as I now had opened the door, I started thinking about if I could get Rust to work on the Kindle as well. Maybe I could do more useful stuff with it? As I have recently started to tinker with Home Assistant and smart devices again, the idea of a dashboard for some of the features could be a fun project. And while there are probably many &lt;em&gt;perfectly fine&lt;&#x2F;em&gt; projects out there, &lt;em&gt;I&lt;&#x2F;em&gt; haven&#x27;t made any of those.&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Telling a programmer there&#x27;s already a library to do X is like telling a songwriter there&#x27;s already a song about love.&lt;br &#x2F;&gt;
-Pete Cordell&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h2 id=&quot;cross-compiling-rust-for-the-kindle&quot;&gt;Cross compiling Rust for the Kindle&lt;&#x2F;h2&gt;
&lt;p&gt;After some research I found out that I needed to target ARMv7 and musl libc. I have &lt;a href=&quot;..&#x2F;..&#x2F;projects&#x2F;infoscreen&#x2F;&quot;&gt;dabbled with Rust on ARM machines&lt;&#x2F;a&gt; before, and know from painful experience that getting the Rust compilation toolchain to work on such low-powered devices is a non-starter. Luckily there are great tools for cross compilation. My go-to for cross compiling Rust is, rather ironically, &lt;code&gt;cargo-zigbuild&lt;&#x2F;code&gt;. The Zig compiler ships with musl libc sources and headers built in, for all supported architectures. It also has its own linker, so zig cc can act as a complete cross-compile toolchain for any musl target, on any host. Compiling for the Kindle becomes as easy as:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #F8F8F2; background-color: #272822;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;* Installing Zig &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;* Installing cargo-zigbuild&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;* cargo zigbuild --release --target armv7-unknown-linux-musleabihf&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h2 id=&quot;getting-shell-access-on-the-kindle&quot;&gt;Getting shell access on the Kindle&lt;&#x2F;h2&gt;
&lt;p&gt;With my hello-world-app ready and built, I needed a way to get it on the kindle and run it. While I probably could have used KUAL which I installed during the jailbreaking process, I wanted to also be able to see stdout to verify my application actually works. After some digging I found the USBNetwork tool that allows for setting up SSH access to your device either via USB or Wifi. For convenience I added an entry in my sshconfig and copied over my public key. Note: ssh-copy-id did not work for me, I had to add my .pub file to &lt;code&gt;&#x2F;mnt&#x2F;us&#x2F;usbnet&#x2F;etc&#x2F;authorized_keys&lt;&#x2F;code&gt; on the Kindle.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;hello-world-now-what&quot;&gt;Hello, World! Now what?&lt;&#x2F;h2&gt;
&lt;p&gt;With shell access in order I was able to confirm that my cross compilation toolchain did indeed work, &quot;Hello, World!&quot; showed up as expected. But a program that prints to stdout readable over SSH is not much help on a Kindle.&lt;&#x2F;p&gt;
&lt;p&gt;As Rust has matured, &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;areweguiyet.com&#x2F;#ecosystem&quot;&gt;quite a few&lt;&#x2F;a&gt; GUI libraries have sprung up. Personally, I only have experience with Slint, so that is what I reached for. Could I get it to work on the Kindle? From my experience with getting Slint to run on a Raspberry Pi I knew the ARMv7 platform was supported out of the box. The missing links would be output to the e-ink screen and input from the touch panel.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;we-have-visual&quot;&gt;We have visual!&lt;&#x2F;h2&gt;
&lt;p&gt;Slint supports various renderers and backends, including a handy and lightweight software renderer that works on basically anything. By supplying a &lt;code&gt;LineBufferProvider&lt;&#x2F;code&gt; that implements &lt;code&gt;process_line()&lt;&#x2F;code&gt; we are able to take one by one line of rasterized visual output, convert it to grayscale and write it to the framebuffer, that on my Kindle is just a file at &lt;code&gt;&#x2F;dev&#x2F;fb0&lt;&#x2F;code&gt; that we have memory mapped. I love the linux philosophy of &quot;everything is a file&quot; sometimes. Now the only thing left to do is to notify the driver to refresh the display, which is how e-ink works. This is done via the libc crate with &lt;code&gt;ioctl()&lt;&#x2F;code&gt; (input&#x2F;output control). We pass in the dirty region to be refreshed, handily provided by Slint internals.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;touch-me-here-touch-me-there&quot;&gt;Touch me here, touch me there&lt;&#x2F;h2&gt;
&lt;p&gt;With pixels on the screen, the other half of the puzzle is getting the touch panel to talk to Slint. And again the &quot;everything is a file&quot; mantra comes to the rescue: the touch controller shows up as &lt;code&gt;&#x2F;dev&#x2F;input&#x2F;event1&lt;&#x2F;code&gt;, and we can just &lt;code&gt;read()&lt;&#x2F;code&gt; from it. Each read gives us back a struct that the kernel has written directly into our buffer: a timestamp, an event type, a code, and a value. No parsing, no protocol, just a memory layout we have to match.&lt;&#x2F;p&gt;
&lt;p&gt;The Kindle uses the Linux kernel&#x27;s &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.kernel.org&#x2F;doc&#x2F;html&#x2F;latest&#x2F;input&#x2F;multi-touch-protocol.html&quot;&gt;multi-touch protocol type B&lt;&#x2F;a&gt;, which means events arrive as a stream of &quot;the X coordinate is now this&quot;, &quot;the Y coordinate is now that&quot;, &quot;the tracking ID is now this&quot; and then a SYNC_REPORT event that says &quot;okay, that batch is done, you can act on it now&quot;. So we accumulate the latest X, Y and tracking ID as events come in, and on each SYNC_REPORT we figure out what to dispatch to Slint. A tracking ID of &lt;code&gt;-1&lt;&#x2F;code&gt; means the finger lifted, which becomes a &lt;code&gt;PointerReleased&lt;&#x2F;code&gt;. Otherwise, the first sync after a touch-down becomes a &lt;code&gt;PointerPressed&lt;&#x2F;code&gt;, and any subsequent ones become &lt;code&gt;PointerMoved&lt;&#x2F;code&gt;. Slint handles the rest.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;it-actually-works&quot;&gt;It actually works!&lt;&#x2F;h2&gt;
&lt;p&gt;After a lot of debugging of no visible output, screen not refreshing, double refresh flashes, touch input not registering, touch input registering twice and lots of more bugs I had a counter and a increment button.&lt;&#x2F;p&gt;
&lt;img src=&quot;&#x2F;images&#x2F;kindle_demo.webp&quot; alt=&quot;Kindle paperwhite running a Slint app&quot; width=&quot;100%&quot; loading=&quot;lazy&quot; &#x2F;&gt;
&lt;p&gt;With an seemingly working (at least for my specific device, it will probably need adjustments for other Kindle versions) kindle-backend for Slint, I extracted the relevant code into a separate crate and published it on &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;crates.io&#x2F;crates&#x2F;slint-backend-kindle&quot;&gt;crates.io&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;With that in order, I just need to draw the rest of the owl (dashboard). That will be another time.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Docxide-template: Type safe docx templates with code generation in Rust</title>
        <published>2026-02-26T00:00:00+00:00</published>
        <updated>2026-02-26T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Sverre Johann Bjørke
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sverre.me/projects/docxide-template/"/>
        <id>https://sverre.me/projects/docxide-template/</id>
        
        <content type="html" xml:base="https://sverre.me/projects/docxide-template/">&lt;p&gt;&lt;strong&gt;Short version:&lt;&#x2F;strong&gt; I built a Rust proc-macro library that generates type-safe structs from Word .docx templates, turning placeholders into compile-time checked fields.&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;sverrejb&#x2F;docxide-template&quot;&gt;Github&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;crates.io&#x2F;crates&#x2F;docxide-template&quot;&gt;Crates.io&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;long-version&quot;&gt;Long version&lt;&#x2F;h2&gt;
&lt;p&gt;My previous job was as a software consultant, and at one of the places I was deployed they handled lots of legal documents. These documents would be generated by an in-house case management software written in Java. The templates for these documents were MS Word .docx files and were pulled in from a separate repo at build time. The separation here was for organizational purposes, but the legal people who managed the templates were trained in using git so they could deal with all template-related things. But the software that essentially filled out these templates needed to know about where to insert things like names, addresses and legal details. This was done via some XML-files containing information about the placeholders in the corresponding .docx, and whatever Java-details needed to link this together. This was tedious to work with, and error prone. An update to either file and not the other could result in errors, like runtime faults at best, or placeholders finding their way into a finished document at worst. It was also essentially &quot;stringly typed&quot;, no type system guarantee that things would work at all.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;a-case-for-rust&quot;&gt;A case for Rust&lt;&#x2F;h2&gt;
&lt;p&gt;I enjoy Rust for many reasons, and the strong type system is one of them. The notion that &quot;bugs shouldn&#x27;t compile&quot; resonates with me. And while the chances I could convert a Java shop in the public sector to Rust were slim, I figured that making a type-safe .docx templating library would be a fun project that someone out there might find useful. The ecosystem of a language is an important factor when choosing a technology stack. If I can move the needle a little bit on the &quot;corporate friendly&quot; scale that would be a small win for Rust adoption.&lt;&#x2F;p&gt;
&lt;p&gt;My design goals were pretty straightforward:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;The library should be super easy to use. &lt;em&gt;No manual wiring or glue code.&lt;&#x2F;em&gt;&lt;&#x2F;li&gt;
&lt;li&gt;The .docx-files should essentially be a part of the source code.&lt;&#x2F;li&gt;
&lt;li&gt;Any addition or removal of the template placeholders would incur a compilation error.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;macros&quot;&gt;Macros&lt;&#x2F;h2&gt;
&lt;p&gt;One of Rust&#x27;s super powers is metaprogramming with macros. From the official book:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Procedural macros allow you to run code at compile time that operates over Rust syntax, both consuming and producing Rust syntax.&lt;br &#x2F;&gt;
 &lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;Not having worked with macros before, it felt a bit daunting, but I figured the best way to learn to swim is to jump in. After a few cold starts, some research into what other templating libraries do, a complete rewrite, and a long hiatus, I released version 1.0.0 of Docxide-template.&lt;&#x2F;p&gt;
&lt;p&gt;So how does it work? The consumer places .docx template files in a folder, and points the macro to it. The macro is executed at compile time, and traverses the files. It generates structs where template placeholders become struct fields. The file name becomes the PascalCased struct name, and the placeholders become snake-cased field names. Like so, for a HelloWorld.docx template:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #F8F8F2; background-color: #272822;&quot;&gt;&lt;code data-lang=&quot;java&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;use docxide_template&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span&gt;generate_templates;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;generate_templates&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;!&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;quot;path&#x2F;to&#x2F;templates&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;main&lt;&#x2F;span&gt;&lt;span&gt;() {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    let doc &lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; HelloWorld {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        first_name&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &amp;quot;Alice&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;into&lt;&#x2F;span&gt;&lt;span&gt;(),&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        company&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &amp;quot;Acme Corp&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;into&lt;&#x2F;span&gt;&lt;span&gt;(),&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    };&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    doc.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;save&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;quot;output&#x2F;greeting&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;).&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;();&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88846F;&quot;&gt;    &#x2F;&#x2F; alternatively, output the filled template as bytes:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    doc.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;to_bytes&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The generated structs expose two methods, &lt;code&gt;save()&lt;&#x2F;code&gt; that saves the filled out template as a .docx file, and &lt;code&gt;to_bytes()&lt;&#x2F;code&gt; which outputs the raw bytes if the document is to be further processed before saving. Like converted to a PDF, or streamed to another service.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;baked-in-bytes&quot;&gt;Baked in bytes&lt;&#x2F;h2&gt;
&lt;p&gt;One way things can break with this setup is that the template files can be changed after compilation, like on the server or in whichever production environment it is deployed. Depending on how the build and distribution pipeline of a theoretical consumer of this library is set up, this may or may not be a problem. But the template files will have to be distributed with the compiled binary in order to work.&lt;&#x2F;p&gt;
&lt;p&gt;My second goal was for the .docx-files to essentially be a part of the source code. So as an alternative feature that can be toggled, the processed templates can also be baked directly into the binary. This ensures that the templates can&#x27;t be changed accidentally (or maliciously), and makes the built application more portable. The only reason I have this as opt-in rather than opt-out is that someone might have thousands of template files, and that can balloon the binary size quite fast.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Firefox Extension: Recently Used Tabs Indicator</title>
        <published>2026-01-17T00:00:00+00:00</published>
        <updated>2026-01-17T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Sverre Johann Bjørke
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sverre.me/projects/recently-used-tabs/"/>
        <id>https://sverre.me/projects/recently-used-tabs/</id>
        
        <content type="html" xml:base="https://sverre.me/projects/recently-used-tabs/">&lt;p&gt;I made a Firefox extension to make my life a tiny bit easier. I have dabbled in this before, but I have never published one.&lt;&#x2F;p&gt;
&lt;p&gt;I have a problem with tabs. I often end up with too many open, and often several from the same site. So when I switch to a different tab to check something, and I want to go back, I am stuck scanning through a sea of tiny favicons trying to remember which one I just left, as the title becomes truncated.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;projects&#x2F;recently-used-tabs&#x2F;tabs.png&quot; alt=&quot;Screenshot showing the extension in action.&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;So I made a Firefox extension that tracks the 5 most recently used tabs and adds visual indicators to the start of the titles. The most recent tab gets a red circle 🔴, the next one an orange circle 🟠, and so on. This makes them stand out and easy to spot.&lt;&#x2F;p&gt;
&lt;p&gt;The extension is available on the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;addons.mozilla.org&#x2F;en-US&#x2F;firefox&#x2F;addon&#x2F;recently-used-tabs-indicator&#x2F;&quot;&gt;Firefox Add-ons store&lt;&#x2F;a&gt;, and the code is on &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;sverrejb&#x2F;fruti&quot;&gt;GitHub&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;And yes, I know that Firefox has an option to have &lt;code&gt;ctrl&lt;&#x2F;code&gt;+&lt;code&gt;tab&lt;&#x2F;code&gt; cycle through recently open tabs, but I have never gotten used to using that for some reason. That is probably the best solution, but it is nice to have options.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;how-it-works&quot;&gt;How it works&lt;&#x2F;h2&gt;
&lt;p&gt;A background script tracks tab activation events per window and maintains a recency list. When you switch tabs, it sends messages to content scripts that modify the document titles by adding prefix indicators. Each browser window maintains its own independent tracking.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;projects&#x2F;recently-used-tabs&#x2F;options.png&quot; alt=&quot;Screenshot showing the options of the extension.&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;There are several indicator styles to choose from: simple numbers, colored circles, colored squares, moon phases, or define your own custom indicators.&lt;&#x2F;p&gt;
&lt;p&gt;Ideally I wanted to rather color the tabs with different hues, or add some other kind of styling, but this is currently not possible, and &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;bugzilla.mozilla.org&#x2F;show_bug.cgi?id=1320585&quot;&gt;the issue&lt;&#x2F;a&gt; on this possible feature is nine years old...&lt;&#x2F;p&gt;
&lt;h2 id=&quot;permissions&quot;&gt;Permissions&lt;&#x2F;h2&gt;
&lt;p&gt;The extension requires access to all websites to modify tab titles, which looks scary in the permissions prompt. The code only reads &lt;code&gt;document.title&lt;&#x2F;code&gt; and adds a prefix, nothing invasive. But browsers do not have a more granular permission for just modifying titles.&lt;&#x2F;p&gt;
&lt;p&gt;Tabs that are already open when you install or reload the extension will not get indicators until you navigate somewhere. This is because content scripts are automatically injected only when pages load.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Auox: A Terminal banking client</title>
        <published>2025-12-27T00:00:00+00:00</published>
        <updated>2025-12-27T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Sverre Johann Bjørke
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sverre.me/projects/auox/"/>
        <id>https://sverre.me/projects/auox/</id>
        
        <content type="html" xml:base="https://sverre.me/projects/auox/">&lt;p&gt;Short version: I made a terminal banking client for my personal banking needs with Rust, Ratatui and the open and customer facing APIs of my bank. I call it Auox, short for Aurum Oxidatum, a pseudo-latin name for &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Gold(III)_oxide&quot;&gt;gold oxide&lt;&#x2F;a&gt;, or &quot;gold rust&quot;.
The code is on &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;sverrejb&#x2F;auox&quot;&gt;GitHub&lt;&#x2F;a&gt; if you want to take a look.&lt;&#x2F;p&gt;
&lt;p&gt;The application looks like this:&lt;&#x2F;p&gt;
&lt;div id=&quot;demo&quot;&gt;&lt;noscript&gt;
    &lt;p&gt;This terminal recording requires JavaScript to function properly.&lt;&#x2F;p&gt;
&lt;&#x2F;noscript&gt;&lt;&#x2F;div&gt;
&lt;script&gt;
    AsciinemaPlayer.create(&#x27;&#x2F;casts&#x2F;auox.cast&#x27;, document.getElementById(&#x27;demo&#x27;), {autoPlay: true, loop: true, theme: &#x27;dracula&#x27;});
&lt;&#x2F;script&gt;
&lt;p&gt;(Some personal data has been replaced with placeholders for this demo.)&lt;&#x2F;p&gt;
&lt;h2 id=&quot;longer-version&quot;&gt;Longer version&lt;&#x2F;h2&gt;
&lt;p&gt;When I started out learning Rust, one of the first projects I tried to build was a CLI app for interfacing with my bank. This was a few years after the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Payment_Services_Directive&quot;&gt;PSD2-law&lt;&#x2F;a&gt; went into effect, mandating banks to allow third party applications to access your banking data. Some banks took it even further and allowed customers to access APIs themselves. The API&#x27;s at that time were not well documented, and I was a very green developer learning a brand new language with a steep learning curve, so progress was slow. Before I had anything actully working, I ended up switching banks as well, and the project was abandoned.&lt;&#x2F;p&gt;
&lt;p&gt;I have since kept up with both the Rust language and ecosystem, and seen it grow and become a viable (and in many cases, the right) choice for a wide range of applications. Personally, I often put the carriage before the horse and choose Rust just because I like working with it and want to become better at using it, even if it is not the most pragmatic choice for a given project. This fall I have been hunting &quot;the one that got away&quot;, and actually made a useful banking application.&lt;&#x2F;p&gt;
&lt;p&gt;As all software, it is a work in progress, but it currently does what I most often need from a banking app: show my accounts, their respective balance and  transactions, and lets me transfer money between accounts. All while not leaving the comfort of the terminal!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;under-the-hood&quot;&gt;Under the hood&lt;&#x2F;h2&gt;
&lt;p&gt;The app is made with &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;ratatui&#x2F;ratatui&quot;&gt;ratatui&lt;&#x2F;a&gt; for the TUI (Text-based User Interface, a bit more interactive than a CLI). Ratatui is a very active project, well documented and feature rich. The app is first set up, and then everything happens in a main loop where a frame is drawn, user input is registred and state is updated. The different application views and the movement between them I have attempted to model sort of like a state machine, with the current view applied to the top of a stack that can be popped to go up&#x2F;back.&lt;&#x2F;p&gt;
&lt;p&gt;The OAuth flow for the banking API is pretty standard. It tries to use a stored access token first, then attempts to refresh it if expired, and only initiates a full OAuth flow if that fails. The full flow spawns a tiny HTTP server to catch the callback, then opens the browser for authentication. Most of the time I don&#x27;t need to touch the browser at all, ideally only once a year.&lt;&#x2F;p&gt;
&lt;p&gt;The SpareBank 1 API itself is straightforward, and pretty &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;developer.sparebank1.no&#x2F;#&#x2F;apis&quot;&gt;well documented&lt;&#x2F;a&gt;. The API for personal use is quite limited in scope, and allows for listing account details and transactions, as well as transfering money between accounts you own yourself.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;terminal-but-make-it-sparkles-fancysparkles&quot;&gt;Terminal... but make it ✨&lt;em&gt;fancy&lt;&#x2F;em&gt;✨&lt;&#x2F;h2&gt;
&lt;p&gt;I recently came across &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;junkdog&#x2F;tachyonfx&quot;&gt;tachyonfx&lt;&#x2F;a&gt;, a Ratatui library for making advanced animations possible in your TUI application, and I &lt;em&gt;had&lt;&#x2F;em&gt; to try it out. After playing around with it a bunch, I settled for an effect where the app sort of &quot;pixelates&quot; into view on start, and out again on terminating. This is done with the coalesce-effect. You can see it in the demo recording on the top. An online showcase of some of the effects can be seen in &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;junkdog.github.io&#x2F;tachyonfx-ftl&#x2F;&quot;&gt;this sandbox&lt;&#x2F;a&gt;,&lt;&#x2F;p&gt;
&lt;h2 id=&quot;further-work&quot;&gt;Further work:&lt;&#x2F;h2&gt;
&lt;p&gt;Currently, these are some of the things I am planning to improve:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;The error handling is pretty rough, with some &lt;code&gt;panic!&lt;&#x2F;code&gt; and &lt;code&gt;expect()&lt;&#x2F;code&gt; calls that should be handled better.&lt;&#x2F;li&gt;
&lt;li&gt;Logging is haphazardly applied, and should ideally write to a file I think.&lt;&#x2F;li&gt;
&lt;li&gt;Search (and&#x2F;or filtering) in transactions list.&lt;&#x2F;li&gt;
&lt;li&gt;Support for more banks, ideally making it configurable.&lt;&#x2F;li&gt;
&lt;li&gt;Async API requests. Currently every call to the API is blocking. This is fine enough, as there is not much to do in the app without the data from a request, but it would be nice with some placeholder skeleton text or something while waiting. Not sure if it is worth it.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Claude Code integration in Helix using Tmux</title>
        <published>2025-11-28T00:00:00+00:00</published>
        <updated>2025-11-28T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Sverre Johann Bjørke
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sverre.me/blog/helix-claude-integration/"/>
        <id>https://sverre.me/blog/helix-claude-integration/</id>
        
        <content type="html" xml:base="https://sverre.me/blog/helix-claude-integration/">&lt;p&gt;I am still trying out Helix as my primary editor on a daily basis. I have also started to try out Claude Code&#x27;s CLI. I wanted a quick way to use Claude from within Helix, and to be able to point it to the currently open file or selected code. So I made a small script leveraging tmux that lets me do exactly that with &lt;code&gt;Space + i&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-setup&quot;&gt;The setup&lt;&#x2F;h2&gt;
&lt;p&gt;A bash script at &lt;code&gt;~&#x2F;.local&#x2F;bin&#x2F;helix-claude-tmux.sh&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #F8F8F2; background-color: #272822;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88846F;&quot;&gt;#!&#x2F;bin&#x2F;bash&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;FILEPATH&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FD971F;font-style: italic;&quot;&gt;$1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;SELECTION_START&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FD971F;font-style: italic;&quot;&gt;$2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;SELECTION_END&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FD971F;font-style: italic;&quot;&gt;$3&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88846F;&quot;&gt;# Try to find git root, otherwise use current directory&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;WORKDIR&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt;$(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;git&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; rev-parse&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; --show-toplevel&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; 2&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&#x2F;dev&#x2F;null&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; ||&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt; pwd&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;if&lt;&#x2F;span&gt;&lt;span&gt; [[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; -n&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;$SELECTION_START&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; &amp;amp;&amp;amp; -n&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;$SELECTION_END&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; &amp;amp;&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;$SELECTION_START&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; !=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;$SELECTION_END&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt; ]];&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; then&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;    tmux&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; command-prompt&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; -p&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &amp;quot;Claude prompt:&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;        &amp;quot;split-pane -h -c &amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;$WORKDIR&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;#39; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;\&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;claude &amp;#39;%1 @&lt;&#x2F;span&gt;&lt;span&gt;$FILEPATH&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; on lines &lt;&#x2F;span&gt;&lt;span&gt;$SELECTION_START&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; to &lt;&#x2F;span&gt;&lt;span&gt;$SELECTION_END&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;\&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;else&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;    tmux&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; command-prompt&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; -p&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &amp;quot;Claude prompt:&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;        &amp;quot;split-pane -h -c &amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;$WORKDIR&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;#39; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;\&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;claude &amp;#39;%1 @&lt;&#x2F;span&gt;&lt;span&gt;$FILEPATH&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;\&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;fi&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And two identical keybindings in &lt;code&gt;~&#x2F;.config&#x2F;helix&#x2F;config.toml&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #F8F8F2; background-color: #272822;&quot;&gt;&lt;code data-lang=&quot;toml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[keys.normal.space]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;i =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &amp;quot;:sh helix-claude-tmux.sh %{buffer_name} %{selection_line_start} %{selection_line_end}&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[keys.select.space]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;i =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &amp;quot;:sh helix-claude-tmux.sh %{buffer_name} %{selection_line_start} %{selection_line_end}&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Pretty straightforward. Hitting &lt;code&gt;Space + i&lt;&#x2F;code&gt; launches a Tmux command prompt where I can input my initial query to Claude. If any text is selected in the editor, the line range is passed to Claude, else just the prompt and the filepath is used. The script opens Claude in a horizontal tmux split pane with a prompt and the context. The pane is closed when I exit Claude.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;not-perfect&quot;&gt;Not perfect&lt;&#x2F;h2&gt;
&lt;p&gt;While this is a pretty workable setup, it is still a litle less user friendly than the Code agent plugins in VS Code. As Claude edits the files on disk, the change is not reflected in Helix automatically, and requires a reload. Since Helix keeps the opened file in a buffer, I can end up with two sources of truth, and need to be a bit careful here. Usually this is not a problem, as I mostly use Claude for ideas and hints, and instruct it to not change the code, but rather give suggestions.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Pretty printed &quot;git blame&quot; in Helix</title>
        <published>2025-11-19T00:00:00+00:00</published>
        <updated>2025-11-19T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Sverre Johann Bjørke
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sverre.me/blog/git-blame-in-helix/"/>
        <id>https://sverre.me/blog/git-blame-in-helix/</id>
        
        <content type="html" xml:base="https://sverre.me/blog/git-blame-in-helix/">&lt;p&gt;So I have recently started to try out &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;helix-editor.com&#x2F;&quot;&gt;Helix&lt;&#x2F;a&gt;. I have a used Vim (and Neovim) a little bit, but never actually tried to use it in any meaningful capacity. Helix is a modern alternative that has more sane defaults and should pretty much work out of the box, not requiring a bunch of config and plugins. So far I have been happy with what it offers, but there was one thing I missed from VSCode.&lt;&#x2F;p&gt;
&lt;p&gt;Sometimes it is useful to see who last commited on a line, and more importantly, the commit message. This is currently not supported out of the box in Helix. Taking inspiration from the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;docs.helix-editor.com&#x2F;command-line.html#expansions&quot;&gt;documentation&lt;&#x2F;a&gt;, I banged my head against the wall until i ended up with this beauty:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #F8F8F2; background-color: #272822;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[keys.normal.space]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;B&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; = &amp;quot;:echo %sh{commit=$(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;git&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; blame&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; -L&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; %{cursor_line},%{cursor_line}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; --porcelain&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; %{buffer_name}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; |&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt; sed&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; -n&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &amp;#39;1s&#x2F; .*&#x2F;&#x2F;p&amp;#39;); info=$(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;git&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; show&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; -s --format=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;#39;%%an|%%s|%%ad&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; --date=format:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;#39;%%d. %%b %%Y&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; \&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;$commit&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;\&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;); IFS=&amp;#39;|&amp;#39; read -r name msg date &amp;lt;&amp;lt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;\&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;$info&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;\&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;; printf &amp;#39;%%s - %%s - %%s&amp;#39; &lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;\&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;$name&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;\&amp;quot; \&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;$msg&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;\&amp;quot; \&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;$date&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;\&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;}&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;See it in action here: (notice the status line change to a commit message at the end).&lt;&#x2F;p&gt;
&lt;div id=&quot;demo&quot;&gt;&lt;noscript&gt;
    &lt;p&gt;This terminal recording requires JavaScript to function properly.&lt;&#x2F;p&gt;
&lt;&#x2F;noscript&gt;&lt;&#x2F;div&gt;
&lt;script&gt;
    AsciinemaPlayer.create(&#x27;&#x2F;casts&#x2F;gitblame.cast&#x27;, document.getElementById(&#x27;demo&#x27;), {startAt:2, theme: &#x27;dracula&#x27;});
&lt;&#x2F;script&gt;</content>
        
    </entry>
    <entry xml:lang="en">
        <title>The website of Trondheim Bike Kitchen</title>
        <published>2025-10-24T00:00:00+00:00</published>
        <updated>2025-10-24T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Sverre Johann Bjørke
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sverre.me/projects/sykkelkjokken-website/"/>
        <id>https://sverre.me/projects/sykkelkjokken-website/</id>
        
        <content type="html" xml:base="https://sverre.me/projects/sykkelkjokken-website/">&lt;p&gt;This is the website of Trondheim Bike Kitchen. It is built with Sveltekit, hosted on Netlify and has a few features:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;An admin-interface with editable pages and settings&lt;&#x2F;strong&gt; -  using Decap CMS, a git-based CMS that keeps all the content in the repo.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Event page that also syncs to Facebook&lt;&#x2F;strong&gt; - Using Eventbrite as the backend for events saves us from creating events twice, as their API can be used to list out all events on our website.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Membership registration&lt;&#x2F;strong&gt; - With storage in a Google spreadsheet and payment via Vipps or credit card. (Yes, I know spreadsheets aren&#x27;t data bases...)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Internationalization&lt;&#x2F;strong&gt; - We even have Nynorsk!&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;




&lt;a hx-boost=&quot;false&quot; class=&quot;lightbox-image-link&quot; id=&quot;bk.png&quot; href=&quot;#lightbox-bk.png&quot;&gt;
    &lt;img width=&quot;100%&quot; loading=&quot;lazy&quot; alt=&quot;Screenshot of website&quot; src=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;processed_images&#x2F;bk.234836b3122c478e.webp&quot; &#x2F;&gt;
&lt;&#x2F;a&gt;

&lt;a hx-boost=&quot;false&quot; class=&quot;lightbox-image-link mobile&quot; id=&quot;bk.png&quot; href=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;projects&#x2F;sykkelkjokken-website&#x2F;bk.png&quot;&gt;
    &lt;img width=&quot;100%&quot; loading=&quot;lazy&quot; alt=&quot;Screenshot of website&quot; src=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;processed_images&#x2F;bk.234836b3122c478e.webp&quot; &#x2F;&gt;
&lt;&#x2F;a&gt;



&lt;dialog id=&quot;lightbox-bk.png&quot; class=&quot;lightbox&quot;&gt;
    &lt;a class=&quot;close-background&quot; href=&quot;#bk.png&quot; title=&quot;Close image&quot; role=&quot;button&quot;&gt;&lt;&#x2F;a&gt;
    &lt;img src=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;projects&#x2F;sykkelkjokken-website&#x2F;bk.png&quot; loading=&quot;lazy&quot; alt=&quot;Screenshot of website&quot;&gt;
    &lt;a class=&quot;close-button&quot; href=&quot;#bk.png&quot; title=&quot;Close image&quot; role=&quot;button&quot;&gt;×&lt;&#x2F;a&gt;
&lt;&#x2F;dialog&gt;&lt;h2 id=&quot;how-it-came-to-be&quot;&gt;How it came to be&lt;&#x2F;h2&gt;
&lt;p&gt;During the Covid-19 pandemic I started tinkering with bikes. I did this mostly on our patio, and bolts and nuts often fell between the decking. My lovely wife happened to read about a new organization that had popped up in Trondheim: a bike kitchen. I ended up going there, and after a while, I became a board member.&lt;&#x2F;p&gt;
&lt;p&gt;Fast forward a bit, our firstborn had arrived and I was on parental leave from work. Being blessed with a baby that actually slept a bit during the day gave me some time to tinker with Svelte. Before long I had started on what would become the website of &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;sykkelkjokken.no&quot;&gt;Trondheim Bike Kitchen&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;It started out very basic. All pages had hard coded content. I used Svelte&#x27;s static adapter to build the site. This generated html, css and js files. I rewrote some of the pages to use Decap CMS in order to allow the other board members to edit them.&lt;&#x2F;p&gt;
&lt;p&gt;As the site grew and feature requests came in I changed it to use server side rendering for some of the pages, while keeping others static. Membership registration was added. Vipps and credit card payment was added in an effort to make the treasurer&#x27;s job a bit easier.&lt;&#x2F;p&gt;
&lt;p&gt;Having worked mostly with React for frontend at that point, Svelte(kit) was nothing short of a revelation. Things made sense. The builds were small and loaded fast. The code was a joy to write, and not horrible to read after a few weeks. I still enjoy it, although I now prefer non-javascript&#x2F;typescript when I can.&lt;&#x2F;p&gt;
&lt;p&gt;There are still many things I want to do with the site, but time is the most limited resource in my life in these days. Luckily others have joined in a bit in the development.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Disabling bluetooth on a sleeping Mac, or: &quot;Why the fuck is this not a setting, Apple? - Part 73&quot;</title>
        <published>2025-08-20T00:00:00+00:00</published>
        <updated>2025-08-20T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Sverre Johann Bjørke
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sverre.me/blog/mac-sleep-bluetooth/"/>
        <id>https://sverre.me/blog/mac-sleep-bluetooth/</id>
        
        <content type="html" xml:base="https://sverre.me/blog/mac-sleep-bluetooth/">&lt;p&gt;Here&#x27;s another addition to my ever-growing collection of &quot;things that should be simple but require scripting because Apple knows better than you.&quot; Bluetooth staying on when my Mac goes to sleep.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-problem&quot;&gt;The Problem&lt;&#x2F;h2&gt;
&lt;p&gt;Often I use my wireless headphones around the house while doing chores, and when walking the dog around the block. And it is &lt;em&gt;super annoying&lt;&#x2F;em&gt; when I go in and out of the bluetooth range of my macbook and the earphones announce &quot;Disconnected!&quot; and &quot;Connected!&quot; reapeatedly. When my macbook is in sleep mode I obviously do not want to connect my earphones to it!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-should-exist-but-doesn-t&quot;&gt;What Should Exist But Doesn&#x27;t&lt;&#x2F;h2&gt;
&lt;p&gt;You&#x27;d think there would be a checkbox somewhere in System Preferences (sorry, &quot;System Settings&quot; now, because we needed another UI overhaul) that says &quot;Turn off Bluetooth when computer sleeps.&quot; Makes sense, right?&lt;&#x2F;p&gt;
&lt;p&gt;But no, Apple decided this isn&#x27;t something users should control. Because obviously Apple&#x27;s engineers know better than you whether you want your peripheral devices staying connected to a sleeping computer. The hubris is honestly impressive. I googled around, and found a &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;superuser.com&#x2F;questions&#x2F;1819753&#x2F;macos-how-to-disable-bluetooth-while-the-computer-is-asleep-lid-is-closed&quot;&gt;working solution&lt;&#x2F;a&gt;, but I feel I should not have had to.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-solution&quot;&gt;The Solution&lt;&#x2F;h2&gt;
&lt;p&gt;Time to script our way around their design decisions. Enter &lt;code&gt;sleepwatcher&lt;&#x2F;code&gt; and &lt;code&gt;blueutil&lt;&#x2F;code&gt; - two third-party tools that do what macOS should do natively.&lt;&#x2F;p&gt;
&lt;p&gt;First, install the tools:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #F8F8F2; background-color: #272822;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;brew&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; install sleepwatcher blueutil&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Then create two simple scripts. One to disable Bluetooth when the Mac goes to sleep:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #F8F8F2; background-color: #272822;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88846F;&quot;&gt;#!&#x2F;bin&#x2F;bash&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;&#x2F;opt&#x2F;homebrew&#x2F;bin&#x2F;blueutil&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; --power&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; off&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt;echo&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &amp;quot;$(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;date&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; -Iseconds&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;) -- Sleep event detected, bluetooth disabled. Bluetooth status: $(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;&#x2F;opt&#x2F;homebrew&#x2F;bin&#x2F;blueutil&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; --power&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;)&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; &amp;gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; ~&#x2F;.sleepwatcher.log&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And another to re-enable it when waking up:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #F8F8F2; background-color: #272822;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88846F;&quot;&gt;#!&#x2F;bin&#x2F;bash&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;&#x2F;opt&#x2F;homebrew&#x2F;bin&#x2F;blueutil&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; --power&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; on&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt;echo&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &amp;quot;$(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;date&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; -Iseconds&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;) -- Wake event detected, bluetooth enabled. Bluetooth status: $(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;&#x2F;opt&#x2F;homebrew&#x2F;bin&#x2F;blueutil&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; --power&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;)&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; &amp;gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; ~&#x2F;.sleepwatcher.log&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Save these as &lt;code&gt;~&#x2F;.sleep&lt;&#x2F;code&gt; and &lt;code&gt;~&#x2F;.wakeup&lt;&#x2F;code&gt; respectively, make them executable with &lt;code&gt;chmod 700&lt;&#x2F;code&gt;, and configure sleepwatcher to run them automatically.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;brew services start sleepwatcher&lt;&#x2F;code&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;apple-bruh&quot;&gt;Apple, bruh&lt;&#x2F;h2&gt;
&lt;p&gt;This is just another example of Apple&#x27;s philosophy that they know what&#x27;s best for you. Sometimes they are right, and their default settings often work well for most people. But when they&#x27;re wrong, they really don&#x27;t want you to be able to fix it.&lt;&#x2F;p&gt;
&lt;p&gt;The irony is that macOS is supposedly the &quot;pro&quot; operating system, built for people who know what they&#x27;re doing. Yet basic power management options require third-party tools and shell scripting.&lt;&#x2F;p&gt;
&lt;p&gt;If you&#x27;re dealing with the same Bluetooth annoyance, the scripts above should sort you out. And if you happen to work at Apple: please, just add the checkbox. We promise we won&#x27;t hurt ourselves with it.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>My restomodded acoustic bike</title>
        <published>2025-08-20T00:00:00+00:00</published>
        <updated>2025-08-20T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Sverre Johann Bjørke
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sverre.me/projects/bike/"/>
        <id>https://sverre.me/projects/bike/</id>
        
        <content type="html" xml:base="https://sverre.me/projects/bike/">



&lt;a hx-boost=&quot;false&quot; class=&quot;lightbox-image-link&quot; id=&quot;1.jpg&quot; href=&quot;#lightbox-1.jpg&quot;&gt;
    &lt;img width=&quot;100%&quot; loading=&quot;lazy&quot; alt=&quot;Hero shot of the bike&quot; src=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;processed_images&#x2F;1.532e1ed2511fc0f7.webp&quot; &#x2F;&gt;
&lt;&#x2F;a&gt;

&lt;a hx-boost=&quot;false&quot; class=&quot;lightbox-image-link mobile&quot; id=&quot;1.jpg&quot; href=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;projects&#x2F;bike&#x2F;1.jpg&quot;&gt;
    &lt;img width=&quot;100%&quot; loading=&quot;lazy&quot; alt=&quot;Hero shot of the bike&quot; src=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;processed_images&#x2F;1.532e1ed2511fc0f7.webp&quot; &#x2F;&gt;
&lt;&#x2F;a&gt;



&lt;dialog id=&quot;lightbox-1.jpg&quot; class=&quot;lightbox&quot;&gt;
    &lt;a class=&quot;close-background&quot; href=&quot;#1.jpg&quot; title=&quot;Close image&quot; role=&quot;button&quot;&gt;&lt;&#x2F;a&gt;
    &lt;img src=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;projects&#x2F;bike&#x2F;1.jpg&quot; loading=&quot;lazy&quot; alt=&quot;Hero shot of the bike&quot;&gt;
    &lt;a class=&quot;close-button&quot; href=&quot;#1.jpg&quot; title=&quot;Close image&quot; role=&quot;button&quot;&gt;×&lt;&#x2F;a&gt;
&lt;&#x2F;dialog&gt;
&lt;p&gt;Building up my &lt;a href=&quot;..&#x2F;ebike&#x2F;&quot;&gt;e-bike&lt;&#x2F;a&gt; lit a bit of a fire in me, and I wanted another project to work on as well. I decided to try to make it somewhat of a budget build, as the e-bike became more and more expensive as time went on.&lt;&#x2F;p&gt;
&lt;p&gt;I found a listing for a derelict DBS Winner, an entry-level racing bike from the 70&#x27;s, where basically only the frame and drive train were in a somewhat OK condition. The handlebars were bent, the leather saddle was cracking and horrible to sit on, the wheels trashed and the tires decayed. The reason I jumped on this was that the frame size was 61 cm. That is kind of rare, and as a somewhat tall person I wanted a large bike.&lt;&#x2F;p&gt;




&lt;a hx-boost=&quot;false&quot; class=&quot;lightbox-image-link&quot; id=&quot;7.jpg&quot; href=&quot;#lightbox-7.jpg&quot;&gt;
    &lt;img width=&quot;100%&quot; loading=&quot;lazy&quot; alt=&quot;The bike as I bought it&quot; src=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;processed_images&#x2F;7.95d402fd58f430f6.webp&quot; &#x2F;&gt;
&lt;&#x2F;a&gt;

&lt;a hx-boost=&quot;false&quot; class=&quot;lightbox-image-link mobile&quot; id=&quot;7.jpg&quot; href=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;projects&#x2F;bike&#x2F;7.jpg&quot;&gt;
    &lt;img width=&quot;100%&quot; loading=&quot;lazy&quot; alt=&quot;The bike as I bought it&quot; src=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;processed_images&#x2F;7.95d402fd58f430f6.webp&quot; &#x2F;&gt;
&lt;&#x2F;a&gt;



&lt;dialog id=&quot;lightbox-7.jpg&quot; class=&quot;lightbox&quot;&gt;
    &lt;a class=&quot;close-background&quot; href=&quot;#7.jpg&quot; title=&quot;Close image&quot; role=&quot;button&quot;&gt;&lt;&#x2F;a&gt;
    &lt;img src=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;projects&#x2F;bike&#x2F;7.jpg&quot; loading=&quot;lazy&quot; alt=&quot;The bike as I bought it&quot;&gt;
    &lt;a class=&quot;close-button&quot; href=&quot;#7.jpg&quot; title=&quot;Close image&quot; role=&quot;button&quot;&gt;×&lt;&#x2F;a&gt;
&lt;&#x2F;dialog&gt;&lt;h1 id=&quot;internal-gear-hubs&quot;&gt;Internal gear hubs&lt;&#x2F;h1&gt;
&lt;p&gt;I initially had no plans for it, but as the e-bike was using a derailleur for gearing, I thought it would be fun to get some experience with internally geared hubs on this one. IGHs are very cool pieces of technology, and offer a lot of benefits, and some drawbacks. Essentially a gearbox inside the wheel hub, the closed shell makes the maintenance requirement very low, as the gears are protected from the elements, dirt and impact. They also allow you to change gears while standing still, which is super neat when stopping for a red light or similar. The design is very discrete, and allow for a look almost as minimalistic as the single speed or fixie bikes. On the other hand, they typically have a lower gear range than regular gear setups, they are less efficient and quite a bit more heavy. And when&#x2F;if they require maintenance, the job is a lot more complicated. There are high-end models, even &lt;em&gt;very&lt;&#x2F;em&gt; high-end, offering wider gear ranges and higher efficiency. I, however, was on a budget, and that meant the good old Nexus 7 from Shimano.&lt;&#x2F;p&gt;
&lt;p&gt;I started looking for someone selling parts, ideally a rear wheel and the gear shifter, but the only listings I found were unreasonably priced. Most complete bikes with the same parts on were going for less. While it kind of pained me to butcher a working bike for parts, I bought a well used ladies bike and harvested the parts I wanted from it. Both wheels were transplanted, as well as the shifter. I realized that the donor bike had come with a coaster brake as well. This I had not considered, but it turned out great. I love the clean setup of the cockpit, and skidding while backpedalling triggers some childhood nostalgia.&lt;&#x2F;p&gt;




&lt;a hx-boost=&quot;false&quot; class=&quot;lightbox-image-link&quot; id=&quot;2.jpg&quot; href=&quot;#lightbox-2.jpg&quot;&gt;
    &lt;img width=&quot;100%&quot; loading=&quot;lazy&quot; alt=&quot;The bike as it looks now&quot; src=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;processed_images&#x2F;2.9898c467385f50dc.webp&quot; &#x2F;&gt;
&lt;&#x2F;a&gt;

&lt;a hx-boost=&quot;false&quot; class=&quot;lightbox-image-link mobile&quot; id=&quot;2.jpg&quot; href=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;projects&#x2F;bike&#x2F;2.jpg&quot;&gt;
    &lt;img width=&quot;100%&quot; loading=&quot;lazy&quot; alt=&quot;The bike as it looks now&quot; src=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;processed_images&#x2F;2.9898c467385f50dc.webp&quot; &#x2F;&gt;
&lt;&#x2F;a&gt;



&lt;dialog id=&quot;lightbox-2.jpg&quot; class=&quot;lightbox&quot;&gt;
    &lt;a class=&quot;close-background&quot; href=&quot;#2.jpg&quot; title=&quot;Close image&quot; role=&quot;button&quot;&gt;&lt;&#x2F;a&gt;
    &lt;img src=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;projects&#x2F;bike&#x2F;2.jpg&quot; loading=&quot;lazy&quot; alt=&quot;The bike as it looks now&quot;&gt;
    &lt;a class=&quot;close-button&quot; href=&quot;#2.jpg&quot; title=&quot;Close image&quot; role=&quot;button&quot;&gt;×&lt;&#x2F;a&gt;
&lt;&#x2F;dialog&gt;&lt;h1 id=&quot;parts&quot;&gt;Parts&lt;&#x2F;h1&gt;
&lt;p&gt;The rest of the parts were picked to match some sort of green and brown color scheme.&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;The handle bars are from Biltema.&lt;&#x2F;li&gt;
&lt;li&gt;The grips are cheap &quot;vintage&quot;-looking in faux leather.&lt;&#x2F;li&gt;
&lt;li&gt;The fenders are from listings on finn.no.&lt;&#x2F;li&gt;
&lt;li&gt;The tires are brown Delta Cruiser Plus from Schwalbe.&lt;&#x2F;li&gt;
&lt;li&gt;The front brake is a no-name brand from Aliexpress. Not great, but a lot better than the shitty one the bike came with.&lt;&#x2F;li&gt;
&lt;li&gt;The saddle is a Velo Orange Microfiber Touring saddle.&lt;&#x2F;li&gt;
&lt;li&gt;The brake lever is from Velo Orange as well.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;



&lt;a hx-boost=&quot;false&quot; class=&quot;lightbox-image-link&quot; id=&quot;3.jpg&quot; href=&quot;#lightbox-3.jpg&quot;&gt;
    &lt;img width=&quot;100%&quot; loading=&quot;lazy&quot; alt=&quot;The cockpit of the bike&quot; src=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;processed_images&#x2F;3.db1775b9a5b0ad89.webp&quot; &#x2F;&gt;
&lt;&#x2F;a&gt;

&lt;a hx-boost=&quot;false&quot; class=&quot;lightbox-image-link mobile&quot; id=&quot;3.jpg&quot; href=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;projects&#x2F;bike&#x2F;3.jpg&quot;&gt;
    &lt;img width=&quot;100%&quot; loading=&quot;lazy&quot; alt=&quot;The cockpit of the bike&quot; src=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;processed_images&#x2F;3.db1775b9a5b0ad89.webp&quot; &#x2F;&gt;
&lt;&#x2F;a&gt;



&lt;dialog id=&quot;lightbox-3.jpg&quot; class=&quot;lightbox&quot;&gt;
    &lt;a class=&quot;close-background&quot; href=&quot;#3.jpg&quot; title=&quot;Close image&quot; role=&quot;button&quot;&gt;&lt;&#x2F;a&gt;
    &lt;img src=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;projects&#x2F;bike&#x2F;3.jpg&quot; loading=&quot;lazy&quot; alt=&quot;The cockpit of the bike&quot;&gt;
    &lt;a class=&quot;close-button&quot; href=&quot;#3.jpg&quot; title=&quot;Close image&quot; role=&quot;button&quot;&gt;×&lt;&#x2F;a&gt;
&lt;&#x2F;dialog&gt;




&lt;a hx-boost=&quot;false&quot; class=&quot;lightbox-image-link&quot; id=&quot;6.jpg&quot; href=&quot;#lightbox-6.jpg&quot;&gt;
    &lt;img width=&quot;100%&quot; loading=&quot;lazy&quot; alt=&quot;The front brake&quot; src=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;processed_images&#x2F;6.954995294cc2232f.webp&quot; &#x2F;&gt;
&lt;&#x2F;a&gt;

&lt;a hx-boost=&quot;false&quot; class=&quot;lightbox-image-link mobile&quot; id=&quot;6.jpg&quot; href=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;projects&#x2F;bike&#x2F;6.jpg&quot;&gt;
    &lt;img width=&quot;100%&quot; loading=&quot;lazy&quot; alt=&quot;The front brake&quot; src=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;processed_images&#x2F;6.954995294cc2232f.webp&quot; &#x2F;&gt;
&lt;&#x2F;a&gt;



&lt;dialog id=&quot;lightbox-6.jpg&quot; class=&quot;lightbox&quot;&gt;
    &lt;a class=&quot;close-background&quot; href=&quot;#6.jpg&quot; title=&quot;Close image&quot; role=&quot;button&quot;&gt;&lt;&#x2F;a&gt;
    &lt;img src=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;projects&#x2F;bike&#x2F;6.jpg&quot; loading=&quot;lazy&quot; alt=&quot;The front brake&quot;&gt;
    &lt;a class=&quot;close-button&quot; href=&quot;#6.jpg&quot; title=&quot;Close image&quot; role=&quot;button&quot;&gt;×&lt;&#x2F;a&gt;
&lt;&#x2F;dialog&gt;&lt;&#x2F;p&gt;
&lt;h1 id=&quot;stronger-legs-no-bigger-cog&quot;&gt;Stronger legs? No, bigger cog!&lt;&#x2F;h1&gt;
&lt;p&gt;Riding the bike to and from work I quickly ran into the limitation of the gear range. It is a little bit of a climb to get home, and I can&#x27;t claim to be especially fit. After a few trips ending in burning legs I started to look for a larger rear cog. The front chainring was the 52 teeth one that came with the bike. This is quite a bit larger than what is usually shipped with Nexus 7&#x27;s, and finding a new smaller one proved to be hard. The bike has the old single piece crank style, often called &quot;Fauberkrank&quot; in Scandinavia, so no current chain rings will work. Luckily, the Nexus gear uses a common enough standard, so I was able to find a 24 teeth cog that helped quite a bit. Anything larger would push the gear ratio outside the limits of what the Nexus hub can handle, potentially damaging the gears.&lt;&#x2F;p&gt;




&lt;a hx-boost=&quot;false&quot; class=&quot;lightbox-image-link&quot; id=&quot;5.jpg&quot; href=&quot;#lightbox-5.jpg&quot;&gt;
    &lt;img width=&quot;100%&quot; loading=&quot;lazy&quot; alt=&quot;The bike as I bought it&quot; src=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;processed_images&#x2F;5.05c6ff70f0c5003d.webp&quot; &#x2F;&gt;
&lt;&#x2F;a&gt;

&lt;a hx-boost=&quot;false&quot; class=&quot;lightbox-image-link mobile&quot; id=&quot;5.jpg&quot; href=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;projects&#x2F;bike&#x2F;5.jpg&quot;&gt;
    &lt;img width=&quot;100%&quot; loading=&quot;lazy&quot; alt=&quot;The bike as I bought it&quot; src=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;processed_images&#x2F;5.05c6ff70f0c5003d.webp&quot; &#x2F;&gt;
&lt;&#x2F;a&gt;



&lt;dialog id=&quot;lightbox-5.jpg&quot; class=&quot;lightbox&quot;&gt;
    &lt;a class=&quot;close-background&quot; href=&quot;#5.jpg&quot; title=&quot;Close image&quot; role=&quot;button&quot;&gt;&lt;&#x2F;a&gt;
    &lt;img src=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;projects&#x2F;bike&#x2F;5.jpg&quot; loading=&quot;lazy&quot; alt=&quot;The bike as I bought it&quot;&gt;
    &lt;a class=&quot;close-button&quot; href=&quot;#5.jpg&quot; title=&quot;Close image&quot; role=&quot;button&quot;&gt;×&lt;&#x2F;a&gt;
&lt;&#x2F;dialog&gt;&lt;h1 id=&quot;further-work&quot;&gt;Further work&lt;&#x2F;h1&gt;
&lt;p&gt;In the future I want to get:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Matching fenders - the rear one isn&#x27;t shiny like the front!&lt;&#x2F;li&gt;
&lt;li&gt;More gears - The Nexus 7 is a workhorse, but the Nexus 8 offers quite a larger range, and also a gear that is direct drive, meaning there is no efficiency loss from moving parts. The Alfine hubs are also tempting me, especially the 11 gear one.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;del&gt;Proper lights. See and be seen!&lt;&#x2F;del&gt;&lt;&#x2F;li&gt;
&lt;li&gt;A more retro looking shifter. The current twist shifter is very functional, but I think the look clashes a bit with the rest of the bike.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;del&gt;Frame lock. I want a frame lock with an optional wire for locking the bike to stuff as well. Ideally one that can be keyed with a key I can use on more than one lock, so I don&#x27;t have to carry around multiple keys.&lt;&#x2F;del&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;and last, but not least:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Belt drive.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;I am playing with the idea of trying to get a belt drive setup to work with this bike. It would either require some modifications to a seat stay to allow for opening and closing the frame, or getting the &quot;Veer Split Belt&quot; that allows for joining the belt together after threading it through the frame like a regular bike chain. This is probably a stupid idea, so I really want to do it.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>A lightbox feature for my website without using JavaScript</title>
        <published>2025-08-14T00:00:00+00:00</published>
        <updated>2025-08-14T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Sverre Johann Bjørke
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sverre.me/blog/lightbox-no-js/"/>
        <id>https://sverre.me/blog/lightbox-no-js/</id>
        
        <content type="html" xml:base="https://sverre.me/blog/lightbox-no-js/">&lt;p&gt;While writing up the &lt;a href=&quot;..&#x2F;..&#x2F;projects&#x2F;ebike&#x2F;&quot;&gt;e-bike&lt;&#x2F;a&gt; project page, I realized I wanted a way to pop out images in cases where the reader might want to take a closer look at things.&lt;&#x2F;p&gt;
&lt;p&gt;I take some pride in having websites I make work as well as possible without using JavaScript. I also like the challenge of it; sometimes it feels like a puzzle. Modern CSS makes a lot of things possible! So I thought maybe I could try to make a &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;lokeshdhakar.com&#x2F;projects&#x2F;lightbox2&#x2F;&quot;&gt;lightbox&lt;&#x2F;a&gt;-like feature without resorting to JavaScript.&lt;&#x2F;p&gt;
&lt;p&gt;Zola, the static site generator I use, has borrowed the &quot;shortcode&quot; concept from WordPress. Templates that can be used directly from Markdown. I ended up with a shortcode that does a few things:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Renders a clickable resized .webp-version of an image on the page.&lt;&#x2F;li&gt;
&lt;li&gt;Optionally renders a caption.&lt;&#x2F;li&gt;
&lt;li&gt;Clicking it opens the original image in a semi-fullscreen &amp;lt;dialog&amp;gt;-element&lt;&#x2F;li&gt;
&lt;li&gt;Clicking anywhere on the large image or around it closes it&lt;&#x2F;li&gt;
&lt;li&gt;In a mobile-sized view, it instead links directly to the original image, as images take up the full width of the page anyway.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;You can see an example here:&lt;&#x2F;p&gt;




&lt;a hx-boost=&quot;false&quot; class=&quot;lightbox-image-link&quot; id=&quot;luna.jpg&quot; href=&quot;#lightbox-luna.jpg&quot;&gt;
    &lt;img width=&quot;100%&quot; loading=&quot;lazy&quot; alt=&quot;Our dog, Luna&quot; src=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;processed_images&#x2F;luna.7bfedc49bcbd8648.webp&quot; &#x2F;&gt;
&lt;&#x2F;a&gt;

&lt;a hx-boost=&quot;false&quot; class=&quot;lightbox-image-link mobile&quot; id=&quot;luna.jpg&quot; href=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;images&#x2F;luna.jpg&quot;&gt;
    &lt;img width=&quot;100%&quot; loading=&quot;lazy&quot; alt=&quot;Our dog, Luna&quot; src=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;processed_images&#x2F;luna.7bfedc49bcbd8648.webp&quot; &#x2F;&gt;
&lt;&#x2F;a&gt;



&lt;dialog id=&quot;lightbox-luna.jpg&quot; class=&quot;lightbox&quot;&gt;
    &lt;a class=&quot;close-background&quot; href=&quot;#luna.jpg&quot; title=&quot;Close image&quot; role=&quot;button&quot;&gt;&lt;&#x2F;a&gt;
    &lt;img src=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;images&#x2F;luna.jpg&quot; loading=&quot;lazy&quot; alt=&quot;Our dog, Luna&quot;&gt;
    &lt;a class=&quot;close-button&quot; href=&quot;#luna.jpg&quot; title=&quot;Close image&quot; role=&quot;button&quot;&gt;×&lt;&#x2F;a&gt;
&lt;&#x2F;dialog&gt;
&lt;p&gt;The shortcode looks like this:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #F8F8F2; background-color: #272822;&quot;&gt;&lt;code data-lang=&quot;html&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88846F;&quot;&gt;&amp;lt;!-- Use the image preprocessing of Zola  --&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;{% set resized = resize_image(format=&amp;quot;webp&amp;quot;, path=page.colocated_path ~ img_name, width=800, op=&amp;quot;fit_width&amp;quot;, quality=100) %}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88846F;&quot;&gt;&amp;lt;!-- Render the desktop version --&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;a&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt; class&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;quot;lightbox-image-link&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt; id&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;quot;{{ img_name }}&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt; href&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;quot;#lightbox-{{ img_name }}&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;img&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt; width&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;quot;100%&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt; loading&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;quot;lazy&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt; alt&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;quot;{{ alt }}&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt; src&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;quot;{{ resized.url | safe }}&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt; &#x2F;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;a&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88846F;&quot;&gt;&amp;lt;!-- Render the mobile version --&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;a&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt; class&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;quot;lightbox-image-link mobile&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt; id&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;quot;{{ img_name }}&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;    href&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;quot;&#x2F;{{ page.colocated_path ~ img_name | safe }}&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;img&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt; width&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;quot;100%&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt; loading&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;quot;lazy&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt; alt&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;quot;{{ alt }}&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt; src&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;quot;{{ resized.url | safe }}&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt; &#x2F;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;a&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88846F;&quot;&gt;&amp;lt;!-- Conditionally render the caption, if present --&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;{% if caption %}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;div&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt; class&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;quot;caption&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;{{caption}}&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;div&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;{% endif %}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88846F;&quot;&gt;&amp;lt;!-- Render the lightbox-dialog, using &amp;lt;a&amp;gt;-tags for closing the box. --&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;dialog&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt; id&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;quot;lightbox-{{img_name}}&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt; class&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;quot;lightbox&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;a&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt; class&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;quot;close-background&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt; href&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;quot;#{{ img_name }}&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt; title&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;quot;Close image&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt; role&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;quot;button&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;a&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;img&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt; src&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;quot;&#x2F;{{ page.colocated_path ~ img_name | safe }}&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt; loading&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;quot;lazy&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt; alt&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;quot;{{ alt }}&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;a&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt; class&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;quot;close-button&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt; href&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;quot;#{{ img_name }}&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt; title&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;quot;Close image&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt; role&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;quot;button&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;×&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;a&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;dialog&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This not really that interesting, all the magic happens in the CSS:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #F8F8F2; background-color: #272822;&quot;&gt;&lt;code data-lang=&quot;css&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;body&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;:has&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;dialog&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;:target&lt;&#x2F;span&gt;&lt;span&gt;) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  overflow&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt; hidden&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;a&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;href&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;^=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;#39;#lightbox-&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;] {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  all&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt; unset&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  cursor&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt; zoom-in&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;.lightbox-image-link&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  margin-top&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;rem&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  width&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 100&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;%&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  display&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt; block&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  border-radius&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 15&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;px&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  overflow&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt; hidden&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;.lightbox-image-link&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; img&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  width&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 100&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;% !important&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  height&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt; auto&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  display&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt; block&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  max-width&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt; unset&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  transition&lt;&#x2F;span&gt;&lt;span&gt;: transform &lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;0.3&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;s&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt; ease&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;.lightbox-image-link:hover&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; img&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  transform&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt; scale&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;1.04&lt;&#x2F;span&gt;&lt;span&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;.lightbox&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  all&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt; unset&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  display&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt; none&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  position&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt; fixed&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  top&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  left&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  width&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 100&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;%&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  height&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 100&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;%&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  z-index&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 2&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  background-color&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt; rgba&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;51&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 51&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 51&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 0.75&lt;&#x2F;span&gt;&lt;span&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;.lightbox:target&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  display&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt; flex&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  justify-content&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt; center&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  align-items&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt; center&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;.lightbox .close-background&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  position&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt; absolute&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  top&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  left&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  width&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 100&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;%&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  height&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 100&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;%&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  z-index&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  cursor&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt; default&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;.lightbox&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; img&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  all&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt; unset&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  object-fit&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt; contain&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  max-height&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 95&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;%&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  max-width&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 95&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;%&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  z-index&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 2&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  position&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt; relative&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  pointer-events&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt; none&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  box-shadow&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;    0 25&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;px&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 50&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;px&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; -12&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;px&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt; rgba&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 0.8&lt;&#x2F;span&gt;&lt;span&gt;),&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;    0 10&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;px&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 25&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;px&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; -5&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;px&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt; rgba&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 0.4&lt;&#x2F;span&gt;&lt;span&gt;),&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;    0 0 0 1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;px&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt; rgba&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;255&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 255&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 255&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 0.1&lt;&#x2F;span&gt;&lt;span&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  border-radius&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 8&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;px&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;.lightbox .close-button&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  position&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt; absolute&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  top&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 20&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;px&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  right&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 30&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;px&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  font-size&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 40&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;px&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  color&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt; white&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  text-decoration&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt; none&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  z-index&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 3&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  cursor&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt; pointer&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  transition&lt;&#x2F;span&gt;&lt;span&gt;: transform &lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;0.3&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;s&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt; ease&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  background-color&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt; rgba&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 0.5&lt;&#x2F;span&gt;&lt;span&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  border-radius&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 50&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;%&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  width&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 40&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;px&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  height&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 40&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;px&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  display&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt; flex&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  align-items&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt; center&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  justify-content&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt; center&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  line-height&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  font-family&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt; monospace&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;.lightbox .close-button:hover&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  transform&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt; scale&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;1.3&lt;&#x2F;span&gt;&lt;span&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;.lightbox&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; a&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  background-color&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt; unset&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88846F;&quot;&gt;&#x2F;* Screen reader only text *&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;.sr-only&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  position&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt; absolute&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  width&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;px&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  height&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;px&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  padding&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  margin&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; -1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;px&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  overflow&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt; hidden&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  clip&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt; rect&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  white-space&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt; nowrap&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  border&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;.lightbox-image-link.mobile&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  display&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt; none&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;@media&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;max-width&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 768&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;px&lt;&#x2F;span&gt;&lt;span&gt;) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;  .lightbox-image-link&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;    display&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt; none&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; !important&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;  .lightbox-image-link.mobile&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;    display&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt; block&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; !important&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Yup, that sure is a lot of css. And I will in no way claim I write clean, readable or well structured CSS. The various &lt;code&gt;unsets&lt;&#x2F;code&gt; are because of the suCSS-stylesheet I use as a part of the theme for this site.&lt;&#x2F;p&gt;
&lt;p&gt;So how does this work?&lt;&#x2F;p&gt;
&lt;p&gt;The main functionallity comes from using the &lt;code&gt;:target&lt;&#x2F;code&gt; pseudo-class in CSS, combined with semantic &lt;code&gt;&amp;lt;dialog&amp;gt;&lt;&#x2F;code&gt; element. When you click the image, the URL hash changes to &lt;code&gt;#lightbox-&amp;lt;imagename&amp;gt;&lt;&#x2F;code&gt;, which targets the dialog element. The &lt;code&gt;:target&lt;&#x2F;code&gt; selector then makes the dialog visible and positions it as a fullscreen overlay.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #F8F8F2; background-color: #272822;&quot;&gt;&lt;code data-lang=&quot;css&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;.lightbox&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  display&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt; none&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #88846F;&quot;&gt; &#x2F;* Hidden by default *&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt;.lightbox:target&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  display&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt; flex&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #88846F;&quot;&gt; &#x2F;* Show when targeted *&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  justify-content&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt; center&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;  align-items&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt; center&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;To close the lightbox, both the background and the × button link back to &lt;code&gt;#imagename&lt;&#x2F;code&gt;, which removes the &lt;code&gt;:target&lt;&#x2F;code&gt; state and hides the dialog again.&lt;&#x2F;p&gt;
&lt;p&gt;I also added &lt;code&gt;body:has(dialog:target)&lt;&#x2F;code&gt; to prevent some annoying scrolling when the lightbox is open, and some nice touches like a hover effect on the thumbnail images.&lt;&#x2F;p&gt;
&lt;p&gt;For mobile devices, I decided the lightbox would be pointless since images already fill most of the screen width anyway. So I render two versions of each image link and use media queries to show the appropriate one. On mobile, it simply links directly to the original image file.&lt;&#x2F;p&gt;
&lt;p&gt;The whole approach feels very much in the spirit of the web. Using the URL hash for state, using semantic HTML elements, and providing meaningful fallbacks. No JavaScript dependencies, no frameworks. Just HTML and CSS doing what they do best.&lt;&#x2F;p&gt;
&lt;p&gt;Sure, it&#x27;s not as fancy as some JavaScript lightbox libraries with their smooth transitions and keyboard navigation. But it&#x27;s reliable, fast, and works mostly everywhere. Plus, there&#x27;s something satisfying about solving a problem using only the fundamental building blocks of the web.&lt;&#x2F;p&gt;
&lt;p&gt;Now, if this is actuially a &lt;em&gt;good&lt;&#x2F;em&gt; idea in regards to things like accessibility etc. I do not know, and there are probably better ways to do this, but having fun building the blog is a big part of having a blog as a developer.&lt;&#x2F;p&gt;
&lt;p&gt;The approach could be extended with some progessive enhancements: adding keyboard navigation with a small amount of JavaScript, implementing image galleries with next&#x2F;previous buttons, or even animating the transitions. But for my use case, this simple solution hits the sweet spot between functionality and simplicity.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>My dumb e-bike</title>
        <published>2025-08-11T00:00:00+00:00</published>
        <updated>2025-08-11T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Sverre Johann Bjørke
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sverre.me/projects/ebike/"/>
        <id>https://sverre.me/projects/ebike/</id>
        
        <content type="html" xml:base="https://sverre.me/projects/ebike/">&lt;p&gt;I use my DIY e-bike for getting around. The current iteration looks like this:&lt;&#x2F;p&gt;




&lt;a hx-boost=&quot;false&quot; class=&quot;lightbox-image-link&quot; id=&quot;1.jpg&quot; href=&quot;#lightbox-1.jpg&quot;&gt;
    &lt;img width=&quot;100%&quot; loading=&quot;lazy&quot; alt=&quot;Picture of my home made e-bike&quot; src=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;processed_images&#x2F;1.ecaa9545fa4f9f4e.webp&quot; &#x2F;&gt;
&lt;&#x2F;a&gt;

&lt;a hx-boost=&quot;false&quot; class=&quot;lightbox-image-link mobile&quot; id=&quot;1.jpg&quot; href=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;projects&#x2F;ebike&#x2F;1.jpg&quot;&gt;
    &lt;img width=&quot;100%&quot; loading=&quot;lazy&quot; alt=&quot;Picture of my home made e-bike&quot; src=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;processed_images&#x2F;1.ecaa9545fa4f9f4e.webp&quot; &#x2F;&gt;
&lt;&#x2F;a&gt;



&lt;dialog id=&quot;lightbox-1.jpg&quot; class=&quot;lightbox&quot;&gt;
    &lt;a class=&quot;close-background&quot; href=&quot;#1.jpg&quot; title=&quot;Close image&quot; role=&quot;button&quot;&gt;&lt;&#x2F;a&gt;
    &lt;img src=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;projects&#x2F;ebike&#x2F;1.jpg&quot; loading=&quot;lazy&quot; alt=&quot;Picture of my home made e-bike&quot;&gt;
    &lt;a class=&quot;close-button&quot; href=&quot;#1.jpg&quot; title=&quot;Close image&quot; role=&quot;button&quot;&gt;×&lt;&#x2F;a&gt;
&lt;&#x2F;dialog&gt;
&lt;p&gt;I call this my &quot;Dumb&quot; e-bike because it is both a result of a lot of dumb decisions and mistakes (and lessons learned!), but also because it is the opposite of smart. There is no cloud service, required app or vendor lock-in. A lot of modern e-bikes become e-waste the second the manufacturer goes bankrupt. Others require proprietary parts or authorized dealers to repair. Not mine!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;how-it-began&quot;&gt;How it began&lt;&#x2F;h2&gt;
&lt;p&gt;During the early days of the Covid-19 lockdowns I started the project of building my own e-bike. I don&#x27;t really remember why or how that got started, but pretty quickly the idea of a vintage&#x2F;retro bike converted to a stealth e-bike took root. I wanted it to not really look like an e-bike. Having done very little bike mechanics myself up to that point, I knew next to nothing about a lot of things. Since then I have learnt a lot, both about DIY e-bikes and bike mechanics in general.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-first-attempt&quot;&gt;The first attempt&lt;&#x2F;h2&gt;
&lt;p&gt;I scoured finn.no and happened on a conversion kit with everything needed, sans battery. Most importantly, it did not have a display, which would help with the stealth aspect. Jumping the gun without any planning, I bought the kit. While waiting for it to arrive, I spent my time browsing for a suitable bike to convert, as well as looking at similar projects on reddit and youtube. One in particular stood out, and I wanted to create something similar. &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;ebikes&#x2F;comments&#x2F;mrk9hy&#x2F;three_speeds_and_looks_like_it_just_escaped_from&#x2F;&quot;&gt;Reddit user PetyaLB&#x27;s &quot;Thaxted&quot;:&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;




&lt;a hx-boost=&quot;false&quot; class=&quot;lightbox-image-link&quot; id=&quot;thaxted.jpg&quot; href=&quot;#lightbox-thaxted.jpg&quot;&gt;
    &lt;img width=&quot;100%&quot; loading=&quot;lazy&quot; alt=&quot;Vintage bike converted to an e-bike&quot; src=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;processed_images&#x2F;thaxted.ca521b4d15fdbac0.webp&quot; &#x2F;&gt;
&lt;&#x2F;a&gt;

&lt;a hx-boost=&quot;false&quot; class=&quot;lightbox-image-link mobile&quot; id=&quot;thaxted.jpg&quot; href=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;projects&#x2F;ebike&#x2F;thaxted.jpg&quot;&gt;
    &lt;img width=&quot;100%&quot; loading=&quot;lazy&quot; alt=&quot;Vintage bike converted to an e-bike&quot; src=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;processed_images&#x2F;thaxted.ca521b4d15fdbac0.webp&quot; &#x2F;&gt;
&lt;&#x2F;a&gt;



&lt;dialog id=&quot;lightbox-thaxted.jpg&quot; class=&quot;lightbox&quot;&gt;
    &lt;a class=&quot;close-background&quot; href=&quot;#thaxted.jpg&quot; title=&quot;Close image&quot; role=&quot;button&quot;&gt;&lt;&#x2F;a&gt;
    &lt;img src=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;projects&#x2F;ebike&#x2F;thaxted.jpg&quot; loading=&quot;lazy&quot; alt=&quot;Vintage bike converted to an e-bike&quot;&gt;
    &lt;a class=&quot;close-button&quot; href=&quot;#thaxted.jpg&quot; title=&quot;Close image&quot; role=&quot;button&quot;&gt;×&lt;&#x2F;a&gt;
&lt;&#x2F;dialog&gt;
&lt;p&gt;But after a while I realized I had made a slightly stupid mistake, and the first of many! The kit I bought had a 26&quot; wheel. And while 26&quot; wheels are pretty common, they are first and foremost used on a lot of &quot;offroad&quot; and &quot;hybrid&quot; bikes. So that limited my options quite a bit. After a while I found a listing of a DBS &lt;em&gt;Golden Flash&lt;&#x2F;em&gt;. A bike that was very popular in the 70&#x27;s. The listing said it had 26&quot; wheels (foreshadowing...). While not very similar to the Thaxted-bike, I liked the orange metallic color and thought it would work well. So I bought it, and had it shipped to Trondheim. I didn&#x27;t take any good pictures of it, so here is a random one I found:&lt;&#x2F;p&gt;




&lt;a hx-boost=&quot;false&quot; class=&quot;lightbox-image-link&quot; id=&quot;flash.jpg&quot; href=&quot;#lightbox-flash.jpg&quot;&gt;
    &lt;img width=&quot;100%&quot; loading=&quot;lazy&quot; alt=&quot;&amp;#x27;Borrowed&amp;#x27; archival photo of a DBS Golden Flash&quot; src=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;processed_images&#x2F;flash.4673fe96045afee1.webp&quot; &#x2F;&gt;
&lt;&#x2F;a&gt;

&lt;a hx-boost=&quot;false&quot; class=&quot;lightbox-image-link mobile&quot; id=&quot;flash.jpg&quot; href=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;projects&#x2F;ebike&#x2F;flash.jpg&quot;&gt;
    &lt;img width=&quot;100%&quot; loading=&quot;lazy&quot; alt=&quot;&amp;#x27;Borrowed&amp;#x27; archival photo of a DBS Golden Flash&quot; src=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;processed_images&#x2F;flash.4673fe96045afee1.webp&quot; &#x2F;&gt;
&lt;&#x2F;a&gt;



&lt;dialog id=&quot;lightbox-flash.jpg&quot; class=&quot;lightbox&quot;&gt;
    &lt;a class=&quot;close-background&quot; href=&quot;#flash.jpg&quot; title=&quot;Close image&quot; role=&quot;button&quot;&gt;&lt;&#x2F;a&gt;
    &lt;img src=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;projects&#x2F;ebike&#x2F;flash.jpg&quot; loading=&quot;lazy&quot; alt=&quot;&amp;#x27;Borrowed&amp;#x27; archival photo of a DBS Golden Flash&quot;&gt;
    &lt;a class=&quot;close-button&quot; href=&quot;#flash.jpg&quot; title=&quot;Close image&quot; role=&quot;button&quot;&gt;×&lt;&#x2F;a&gt;
&lt;&#x2F;dialog&gt;
&lt;p&gt;After the bike arrived I quickly realized I had made my second mistake. Turns out bike wheel sizes are ... &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.sheldonbrown.com&#x2F;tire-sizing.html&quot;&gt;complicated&lt;&#x2F;a&gt;, and 26&quot; can refer to a lot of things. So while the motorized wheel I now had could technically fit, the rim diameter would be slightly smaller than the original wheels, meaning the brake calipers would not be able to reach the braking surface of the rim. After googling and reading a lot, I found out that there exists adapters that can effectively &quot;move&quot; the brake posts (the parts of the frame where typical bike brakes are attached). Unfortunately for me, this bike had a caliper brake mounted on a brake bridge between the seat stays. So my only option was a brake caliper with a longer reach. But one of those would have had less braking force, not a good option for an ebike! So after a lot of back and forth, I ended up looking for another used bike to convert.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-first-iteration&quot;&gt;The first iteration&lt;&#x2F;h2&gt;
&lt;p&gt;I still wanted a &quot;classical&quot; looking bike, meaning no suspension, a frame with a horizontal top tube and slim, round tubes of steel, no bulky aluminum! I found a bike going for 200 NOK. An old 80&#x27;s mountain bike from Merida. While more modern than the DBS, the shape of the frame looked about right, and it had 26&quot; wheels. It was very well used, had a lot of problems with the gears and scratched paint, but seemed robust and suitable. I planned to have the frame sanded and powder coated at some time anyway, maybe in some retro-looking british racing green.&lt;&#x2F;p&gt;




&lt;a hx-boost=&quot;false&quot; class=&quot;lightbox-image-link&quot; id=&quot;merida.jpg&quot; href=&quot;#lightbox-merida.jpg&quot;&gt;
    &lt;img width=&quot;100%&quot; loading=&quot;lazy&quot; alt=&quot;The Merida bike before conversion&quot; src=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;processed_images&#x2F;merida.a71c2b7c4ed72e4c.webp&quot; &#x2F;&gt;
&lt;&#x2F;a&gt;

&lt;a hx-boost=&quot;false&quot; class=&quot;lightbox-image-link mobile&quot; id=&quot;merida.jpg&quot; href=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;projects&#x2F;ebike&#x2F;merida.jpg&quot;&gt;
    &lt;img width=&quot;100%&quot; loading=&quot;lazy&quot; alt=&quot;The Merida bike before conversion&quot; src=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;processed_images&#x2F;merida.a71c2b7c4ed72e4c.webp&quot; &#x2F;&gt;
&lt;&#x2F;a&gt;



&lt;dialog id=&quot;lightbox-merida.jpg&quot; class=&quot;lightbox&quot;&gt;
    &lt;a class=&quot;close-background&quot; href=&quot;#merida.jpg&quot; title=&quot;Close image&quot; role=&quot;button&quot;&gt;&lt;&#x2F;a&gt;
    &lt;img src=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;projects&#x2F;ebike&#x2F;merida.jpg&quot; loading=&quot;lazy&quot; alt=&quot;The Merida bike before conversion&quot;&gt;
    &lt;a class=&quot;close-button&quot; href=&quot;#merida.jpg&quot; title=&quot;Close image&quot; role=&quot;button&quot;&gt;×&lt;&#x2F;a&gt;
&lt;&#x2F;dialog&gt;
&lt;p&gt;Converting the bike was easy enough, swap the wheel, attach the pedal assist sensor, run some wires. The biggest challenge was placing the battery. I ended up putting it and the motor controller in a big junction box I found. The water resistance and robust construction of the box would certainly be useful, as the battery itself is a shrink wrapped &quot;softpack&quot;, and not the typical hard shell variant you see on store bought e-bikes. I added a power cutoff key so I can disable the bike as potential theft deterrent. I found a bike bag that fit the box &lt;em&gt;perfectly&lt;&#x2F;em&gt; and mounted it on the luggage rack. While not &lt;em&gt;invisible&lt;&#x2F;em&gt; by any stretch, it is not a dead giveaway of an e-bike.&lt;&#x2F;p&gt;




&lt;a hx-boost=&quot;false&quot; class=&quot;lightbox-image-link&quot; id=&quot;2.jpg&quot; href=&quot;#lightbox-2.jpg&quot;&gt;
    &lt;img width=&quot;100%&quot; loading=&quot;lazy&quot; alt=&quot;The battery pack&quot; src=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;processed_images&#x2F;2.575e541a4ab06eff.webp&quot; &#x2F;&gt;
&lt;&#x2F;a&gt;

&lt;a hx-boost=&quot;false&quot; class=&quot;lightbox-image-link mobile&quot; id=&quot;2.jpg&quot; href=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;projects&#x2F;ebike&#x2F;2.jpg&quot;&gt;
    &lt;img width=&quot;100%&quot; loading=&quot;lazy&quot; alt=&quot;The battery pack&quot; src=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;processed_images&#x2F;2.575e541a4ab06eff.webp&quot; &#x2F;&gt;
&lt;&#x2F;a&gt;



&lt;dialog id=&quot;lightbox-2.jpg&quot; class=&quot;lightbox&quot;&gt;
    &lt;a class=&quot;close-background&quot; href=&quot;#2.jpg&quot; title=&quot;Close image&quot; role=&quot;button&quot;&gt;&lt;&#x2F;a&gt;
    &lt;img src=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;projects&#x2F;ebike&#x2F;2.jpg&quot; loading=&quot;lazy&quot; alt=&quot;The battery pack&quot;&gt;
    &lt;a class=&quot;close-button&quot; href=&quot;#2.jpg&quot; title=&quot;Close image&quot; role=&quot;button&quot;&gt;×&lt;&#x2F;a&gt;
&lt;&#x2F;dialog&gt;
&lt;p&gt;The bike existed in this form for a year or so, before disaster struck. Something broke in the hub motor. I tried all I could to open the damn thing to see if it was repairable, but I was not able to. The outer casing was made from a cheap and soft alloy, so the tool-marks for where to apply a keyed tool became deformed. I ended up recycling (heh... ) it as metal scrap.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-second-iteration&quot;&gt;The second iteration&lt;&#x2F;h2&gt;
&lt;p&gt;While the hub motor was the more stealthy option, it came with some caveats. Not utilizing the mechanical leverage of a gear made it really bad for climbing hills, and not having a display with controllers to adjust power settings made it a very binary experience. Forgoing some of the stealth vision, I ended up ordering a Bafang conversion kit. Bafang is one of the most popular choices for DIY e-bikes, and they have also supplied the motors for many store bought e-bikes. Keeping it road-legal in Norway, I chose the Bafang BBS01B model since it is limited to 250 watts of output. After a little bit more involved installation than the hub motor, I got it working, and it has been running fine since. The kit came with the Eggrider V2 display, the sleekest option I have seen on the market.&lt;&#x2F;p&gt;




&lt;a hx-boost=&quot;false&quot; class=&quot;lightbox-image-link&quot; id=&quot;4.jpg&quot; href=&quot;#lightbox-4.jpg&quot;&gt;
    &lt;img width=&quot;100%&quot; loading=&quot;lazy&quot; alt=&quot;The Eggrider display mounted on my handlebars&quot; src=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;processed_images&#x2F;4.7f6e0f7d48d70b9c.webp&quot; &#x2F;&gt;
&lt;&#x2F;a&gt;

&lt;a hx-boost=&quot;false&quot; class=&quot;lightbox-image-link mobile&quot; id=&quot;4.jpg&quot; href=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;projects&#x2F;ebike&#x2F;4.jpg&quot;&gt;
    &lt;img width=&quot;100%&quot; loading=&quot;lazy&quot; alt=&quot;The Eggrider display mounted on my handlebars&quot; src=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;processed_images&#x2F;4.7f6e0f7d48d70b9c.webp&quot; &#x2F;&gt;
&lt;&#x2F;a&gt;



&lt;dialog id=&quot;lightbox-4.jpg&quot; class=&quot;lightbox&quot;&gt;
    &lt;a class=&quot;close-background&quot; href=&quot;#4.jpg&quot; title=&quot;Close image&quot; role=&quot;button&quot;&gt;&lt;&#x2F;a&gt;
    &lt;img src=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;projects&#x2F;ebike&#x2F;4.jpg&quot; loading=&quot;lazy&quot; alt=&quot;The Eggrider display mounted on my handlebars&quot;&gt;
    &lt;a class=&quot;close-button&quot; href=&quot;#4.jpg&quot; title=&quot;Close image&quot; role=&quot;button&quot;&gt;×&lt;&#x2F;a&gt;
&lt;&#x2F;dialog&gt;&lt;h2 id=&quot;some-details&quot;&gt;Some details&lt;&#x2F;h2&gt;
&lt;p&gt;Some of the parts on the bike I have swapped out or added for mostly aesthetic reasons:&lt;&#x2F;p&gt;
&lt;h3 id=&quot;the-cockpit&quot;&gt;The cockpit&lt;&#x2F;h3&gt;
&lt;p&gt;I swapped the straight mountain bike bars for some curved and raised bars in shiny silver for a more upright sitting position and a more vintage look. They were cheap from Biltema. I found a minimal old-school stem from &quot;Humpert Ergotech&quot; as well. The grips are Brooks&#x27; &lt;em&gt;Slender Leather Grips&lt;&#x2F;em&gt; in brown. The bell is &lt;em&gt;Brass Striker Bell&lt;&#x2F;em&gt; from Velo Orange.




&lt;a hx-boost=&quot;false&quot; class=&quot;lightbox-image-link&quot; id=&quot;3.jpg&quot; href=&quot;#lightbox-3.jpg&quot;&gt;
    &lt;img width=&quot;100%&quot; loading=&quot;lazy&quot; alt=&quot;Picture the cockpit&quot; src=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;processed_images&#x2F;3.8b4c5aa52213e84a.webp&quot; &#x2F;&gt;
&lt;&#x2F;a&gt;

&lt;a hx-boost=&quot;false&quot; class=&quot;lightbox-image-link mobile&quot; id=&quot;3.jpg&quot; href=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;projects&#x2F;ebike&#x2F;3.jpg&quot;&gt;
    &lt;img width=&quot;100%&quot; loading=&quot;lazy&quot; alt=&quot;Picture the cockpit&quot; src=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;processed_images&#x2F;3.8b4c5aa52213e84a.webp&quot; &#x2F;&gt;
&lt;&#x2F;a&gt;


&lt;div class=&quot;caption&quot;&gt;Yes, I know the cabling is a mess...&lt;&#x2F;div&gt;


&lt;dialog id=&quot;lightbox-3.jpg&quot; class=&quot;lightbox&quot;&gt;
    &lt;a class=&quot;close-background&quot; href=&quot;#3.jpg&quot; title=&quot;Close image&quot; role=&quot;button&quot;&gt;&lt;&#x2F;a&gt;
    &lt;img src=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;projects&#x2F;ebike&#x2F;3.jpg&quot; loading=&quot;lazy&quot; alt=&quot;Picture the cockpit&quot;&gt;
    &lt;a class=&quot;close-button&quot; href=&quot;#3.jpg&quot; title=&quot;Close image&quot; role=&quot;button&quot;&gt;×&lt;&#x2F;a&gt;
&lt;&#x2F;dialog&gt;&lt;&#x2F;p&gt;
&lt;h3 id=&quot;brakes-and-levers&quot;&gt;Brakes and levers&lt;&#x2F;h3&gt;
&lt;p&gt;The rear brake is a quirk of the 80&#x27;s: a U-brake mounted under the chain stays. Not very easy to maintain or adjust, but pretty good stopping-wise. It is also not very visible. The front brake was originally a pair of cantilever-brakes in a matte silver finish. While they have some advantages, a pair of more recent V-brakes is more adjustable and less fiddly to maintain. I also wanted some brakes in shiny metal to match the rest of the parts on the handlebars. I ended up getting &lt;em&gt;Tektro M 530 Silver&lt;&#x2F;em&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;I had to have some matching brake levers, and this turned out to be a bit more of a challenge than the rest. Bike brakes come in short and long pull variants. V-brakes have a greater mechanical advantage and pull more cable. U-brakes have a smaller mechanical advantage and require brake levers that pull less cable. After some research, I found out that some brake levers have a selectable pull-ratio by moving where the brake cable is attached to the lever. But I also wanted levers that matched the shiny metal finish of the rest of my setup. This narrowed the options down a lot. I almost gave up and was prepared to live with two different levers when I found the &lt;em&gt;Shimano BL-R780&lt;&#x2F;em&gt;&#x27;s that worked out both mechanically and aesthetically.&lt;&#x2F;p&gt;




&lt;a hx-boost=&quot;false&quot; class=&quot;lightbox-image-link&quot; id=&quot;5.jpg&quot; href=&quot;#lightbox-5.jpg&quot;&gt;
    &lt;img width=&quot;100%&quot; loading=&quot;lazy&quot; alt=&quot;The cockpit of my ebike, showing the handlebars, bell, brake levers and grips&quot; src=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;processed_images&#x2F;5.84dbb00628e90686.webp&quot; &#x2F;&gt;
&lt;&#x2F;a&gt;

&lt;a hx-boost=&quot;false&quot; class=&quot;lightbox-image-link mobile&quot; id=&quot;5.jpg&quot; href=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;projects&#x2F;ebike&#x2F;5.jpg&quot;&gt;
    &lt;img width=&quot;100%&quot; loading=&quot;lazy&quot; alt=&quot;The cockpit of my ebike, showing the handlebars, bell, brake levers and grips&quot; src=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;processed_images&#x2F;5.84dbb00628e90686.webp&quot; &#x2F;&gt;
&lt;&#x2F;a&gt;



&lt;dialog id=&quot;lightbox-5.jpg&quot; class=&quot;lightbox&quot;&gt;
    &lt;a class=&quot;close-background&quot; href=&quot;#5.jpg&quot; title=&quot;Close image&quot; role=&quot;button&quot;&gt;&lt;&#x2F;a&gt;
    &lt;img src=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;projects&#x2F;ebike&#x2F;5.jpg&quot; loading=&quot;lazy&quot; alt=&quot;The cockpit of my ebike, showing the handlebars, bell, brake levers and grips&quot;&gt;
    &lt;a class=&quot;close-button&quot; href=&quot;#5.jpg&quot; title=&quot;Close image&quot; role=&quot;button&quot;&gt;×&lt;&#x2F;a&gt;
&lt;&#x2F;dialog&gt;&lt;h3 id=&quot;tires-mudguards&quot;&gt;Tires, Mudguards&lt;&#x2F;h3&gt;
&lt;p&gt;Tires change a lot of a bike&#x27;s appearance. They effectively convey the &lt;em&gt;intention&lt;&#x2F;em&gt; of the bike. Like how you can easily tell if a person is going on a mountain hike or to a nice dinner based on their shoes. The bike came with typical mountain bike tires, with a rough thread, made for gripping the surface. As this was going to be more of a city-bike riding on roads, I wanted something more sleek with less rolling resistance. As 26&quot; is a more typical mountain bike size, the alternatives weren&#x27;t many, but I found a pair of &lt;em&gt;Panaracer Hi-road 26x1.50.&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;A classical looking bike needs mudguards, or fenders. Again, me being quick to get the original conversion kit with the 26&quot; wheel ended up causing problems down the line. I was still stuck with 26&quot; wheels now, even though the original motor was long gone. (I feel there is parallel applicable to software development here...) I found out that most classical looking mudguards in metal are made for 28&quot; wheels. Mudguards for 26&quot; wheels are mostly plastic and made for mountain bikes. Not the sleek curved type that wrap around the wheel, but short and stubby kind. Luckily, Velo Orange has a wide selection, including the 26&quot; Snakeskin Fenders.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;further-plans&quot;&gt;Further plans&lt;&#x2F;h1&gt;
&lt;p&gt;While I like the bike as it is now, it is still a far bit from how I envisioned it originally. The colour scheme is not really what I want, and it turns out the geometry of a mountain bike does not really translate well to a more upright position. The 26&quot; wheels also look kinda small on a bike trying to look like a classical vintage bike. In the future I might look for a different frame to build on. I am also playing around with the idea of trying to a build with a belt drive instead of a chain. This will also require a hub geared bike instead of a derailleur. In that case I might go for a CVT gear like one from Enviolo (formerly NuVinci). This will require a lot of work, and that will mean that the e-bike will be out of commission for a long time, so maybe I will start from scratch so I can work on one bike while having one in a rideable condition.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Lint bin for the clothes dryer</title>
        <published>2025-03-06T00:00:00+00:00</published>
        <updated>2025-03-06T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Sverre Johann Bjørke
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sverre.me/projects/lint-bin/"/>
        <id>https://sverre.me/projects/lint-bin/</id>
        
        <content type="html" xml:base="https://sverre.me/projects/lint-bin/">&lt;p&gt;I have been around and used 3D printers since they became more or less mainstream — first in university, then in hackerspaces, and now at work. While I have printed a few doodads, I haven&#x27;t really modeled a lot myself. Recently, a colleague held a 3D modeling course&#x2F;workshop, and I was able to make something useful — a litter bin for dryer lint. I embossed &quot;lo&quot; (lint) on it to indicate what it is for and to give it some character. It is stuck to the wall using command strips with velcro.&lt;&#x2F;p&gt;
&lt;div &gt;
    &lt;img src=&quot;01.jpg&quot;  alt=&quot;Picture of a white lint bin hanging on the wall next to a clothes dryer.&quot;  &#x2F;&gt;
    
&lt;&#x2F;div&gt;</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Couch gaming with WoL, Steamlink, gamepad and LG TV API</title>
        <published>2025-01-16T00:00:00+00:00</published>
        <updated>2025-01-16T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Sverre Johann Bjørke
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sverre.me/blog/couch-gaming/"/>
        <id>https://sverre.me/blog/couch-gaming/</id>
        
        <content type="html" xml:base="https://sverre.me/blog/couch-gaming/">&lt;p&gt;I recently purchased a mini PC to use as a media center and home server. One of the things I wanted to explore was streaming games from my desktop PC to the TV. This works well enough, but it is kind of a hassle to get to the gaming:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;If off, turn on the TV&lt;&#x2F;li&gt;
&lt;li&gt;Select correct HDMI-input (no CEC on the mini PC)&lt;&#x2F;li&gt;
&lt;li&gt;Launch SteamLink, which means I have to fish out the keyboard&#x2F;trackpad-combo from under the table (Logitech K400)&lt;&#x2F;li&gt;
&lt;li&gt;If it is not running, I also have to power on &#x2F; wake the Desktop PC.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;&lt;em&gt;Hardly a console-like experience!&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;So instead of using my limited time to enjoy some gaming, I ended up scripting the whole thing. I wanted to be able to just pick up the gamepad and jump into a game. Using Python and &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;Tenzer&#x2F;alga&quot;&gt;Alga&lt;&#x2F;a&gt;, I was able to achieve this quite nicely.&lt;&#x2F;p&gt;
&lt;p&gt;The script waits for a connected gamepad, and if present, listens for a specific button combo held for a few seconds. This triggers a set of steps that:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Rumbles the controller to indicate that the script is started&lt;&#x2F;li&gt;
&lt;li&gt;Turns on the TV, using Wake On Lan&lt;&#x2F;li&gt;
&lt;li&gt;Turns on the desktop PC, also using Wake On Lan&lt;&#x2F;li&gt;
&lt;li&gt;Sets the TV to the correct HDMI-input, using Alga&lt;&#x2F;li&gt;
&lt;li&gt;Jiggles the mouse cursor, in cases where the TV is on and set to the right input, but the screensaver &#x2F; screen blanking is active&lt;&#x2F;li&gt;
&lt;li&gt;Launches the SteamLink-app&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The script is configured to run as a service using systemd. The repository is &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;sverrejb&#x2F;Couchgaming&#x2F;tree&#x2F;main&quot;&gt;here&lt;&#x2F;a&gt;. It is kludged together and can probably benefit from some TLC, but it works!&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #F8F8F2; background-color: #272822;&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88846F;&quot;&gt;#! &#x2F;usr&#x2F;bin&#x2F;python3&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;from&lt;&#x2F;span&gt;&lt;span&gt; evdev&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; import&lt;&#x2F;span&gt;&lt;span&gt; InputDevice, ecodes, list_devices, ff, categorize&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;import&lt;&#x2F;span&gt;&lt;span&gt; time&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;import&lt;&#x2F;span&gt;&lt;span&gt; subprocess&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;import&lt;&#x2F;span&gt;&lt;span&gt; random&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;import&lt;&#x2F;span&gt;&lt;span&gt; socket&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;MAC_ADDRESS_DESKTOP&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &amp;#39;d8:80:83:9c:5b:2f&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;MAC_ADDRESS_TV&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &amp;#39;48:8d:36:bc:e8:ec&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;DEVICE_PATH&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &amp;#39;&#x2F;dev&#x2F;input&#x2F;event14&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;IP_ADDRESS_TV&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &amp;#39;192.168.1.43&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;RETRY_INTERVAL&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 3&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;BTN_A&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; ecodes.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;BTN_SOUTH&lt;&#x2F;span&gt;&lt;span&gt; &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;BTN_B&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; ecodes.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;BTN_EAST&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;BTN_COMBO&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt; set&lt;&#x2F;span&gt;&lt;span&gt;([&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;BTN_A&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; BTN_B&lt;&#x2F;span&gt;&lt;span&gt;])&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;COMBO_HOLD_TIME&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 1.5&lt;&#x2F;span&gt;&lt;span style=&quot;color: #88846F;&quot;&gt; # Seconds&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;HDMI_INPUT&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &amp;#39;HDMI_2&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt; main&lt;&#x2F;span&gt;&lt;span&gt;():&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    device&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; find_controller_device()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    pressed_buttons&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt; set&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    combo_pressed_time&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; None&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;    while&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; True&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;        try&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;            for&lt;&#x2F;span&gt;&lt;span&gt; event&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; in&lt;&#x2F;span&gt;&lt;span&gt; device.read_loop():&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;                if&lt;&#x2F;span&gt;&lt;span&gt; event.type&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; !=&lt;&#x2F;span&gt;&lt;span&gt; ecodes.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;EV_KEY&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;                    continue&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                key_event&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; categorize(event)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;                if&lt;&#x2F;span&gt;&lt;span&gt; key_event.keystate&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; ==&lt;&#x2F;span&gt;&lt;span&gt; key_event.key_down:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                    pressed_buttons.add(key_event.scancode)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;                    if&lt;&#x2F;span&gt;&lt;span&gt; pressed_buttons&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; ==&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; BTN_COMBO&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                        combo_pressed_time&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; time.time()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;                    else&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                        combo_pressed_time&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; None&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;                elif&lt;&#x2F;span&gt;&lt;span&gt; key_event.keystate&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; ==&lt;&#x2F;span&gt;&lt;span&gt; key_event.key_up:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;                    if&lt;&#x2F;span&gt;&lt;span&gt; combo_pressed_time&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; and&lt;&#x2F;span&gt;&lt;span&gt; (time.time()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; -&lt;&#x2F;span&gt;&lt;span&gt; combo_pressed_time&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; &amp;gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; COMBO_HOLD_TIME&lt;&#x2F;span&gt;&lt;span&gt;):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                        execute_commands(device)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                        combo_pressed_time&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; None&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                    pressed_buttons.discard(key_event.scancode)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                    combo_pressed_time&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; None&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;        except&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt; OSError&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; as&lt;&#x2F;span&gt;&lt;span&gt; e:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;            if&lt;&#x2F;span&gt;&lt;span&gt; e.errno&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; ==&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 19&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #88846F;&quot;&gt;  # 19 = No such device (disconnected)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt;                print&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;f&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;#39;Device disconnected. Reconnecting...&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                device&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; find_controller_device()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;            else&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt;                print&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;f&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;#39;Unexpected error: &lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;{&lt;&#x2F;span&gt;&lt;span&gt;e&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                time.sleep(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;RETRY_INTERVAL&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt; find_controller_device&lt;&#x2F;span&gt;&lt;span&gt;():&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;    while&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; True&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;        try&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            devices&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; [InputDevice(path)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; for&lt;&#x2F;span&gt;&lt;span&gt; path&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; in&lt;&#x2F;span&gt;&lt;span&gt; list_devices()]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;            for&lt;&#x2F;span&gt;&lt;span&gt; dev&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; in&lt;&#x2F;span&gt;&lt;span&gt; devices:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;                if&lt;&#x2F;span&gt;&lt;span&gt; dev.path&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; ==&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; DEVICE_PATH&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt;                    print&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;f&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;#39;Device found: &lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;{&lt;&#x2F;span&gt;&lt;span&gt;dev.path&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;                    return&lt;&#x2F;span&gt;&lt;span&gt; InputDevice(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;DEVICE_PATH&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt;            print&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;f&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;#39;Device not found. Retrying in &lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;{RETRY_INTERVAL}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; seconds...&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            time.sleep(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;RETRY_INTERVAL&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;        except&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt; Exception&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; as&lt;&#x2F;span&gt;&lt;span&gt; e:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt;            print&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;f&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;#39;Error checking devices: &lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;{&lt;&#x2F;span&gt;&lt;span&gt;e&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            time.sleep(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;RETRY_INTERVAL&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt; execute_commands&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FD971F;font-style: italic;&quot;&gt;device&lt;&#x2F;span&gt;&lt;span&gt;):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt;    print&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;#39;Button combo detected! Running script...&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    rumble_controller(device)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt;    print&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;#39;Waking TV....&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    send_wol_packet(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;MAC_ADDRESS_TV&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt;    print&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;#39;Waking Desktop....&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    send_wol_packet(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;MAC_ADDRESS_DESKTOP&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    wake_screen()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    start_steamlink()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt;    print&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;#39;Setting TV input&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    set_tv_input()        &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt;    print&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;#39;Commands completed.&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt; rumble_controller&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FD971F;font-style: italic;&quot;&gt;device&lt;&#x2F;span&gt;&lt;span&gt;):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    rumble&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; ff.Rumble(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FD971F;font-style: italic;&quot;&gt;strong_magnitude&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;0x&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;c000&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FD971F;font-style: italic;&quot;&gt; weak_magnitude&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;0x&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;c000&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    duration_ms&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 500&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    effect&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; ff.Effect(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        ecodes.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;FF_RUMBLE&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;        -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;        0&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        ff.Trigger(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span&gt;),&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        ff.Replay(duration_ms,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span&gt;),&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        ff.EffectType(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FD971F;font-style: italic;&quot;&gt;ff_rumble_effect&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt;rumble)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    )&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    effect_id&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; device.upload_effect(effect)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    repeat_count&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    device.write(ecodes.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;EV_FF&lt;&#x2F;span&gt;&lt;span&gt;, effect_id, repeat_count)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    time.sleep(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;0.5&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    device.erase_effect(effect_id) &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt; send_wol_packet&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FD971F;font-style: italic;&quot;&gt;mac_address&lt;&#x2F;span&gt;&lt;span&gt;):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    mac_bytes&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt; bytes&lt;&#x2F;span&gt;&lt;span&gt;.fromhex(mac_address.replace(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;#39;:&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &amp;#39;&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    magic_packet&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt; b&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;\xff&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; *&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 6&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; +&lt;&#x2F;span&gt;&lt;span&gt; mac_bytes&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; *&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 16&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88846F;&quot;&gt;    # TV will sometimes not respond, so we spam WoL a bit.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;    for&lt;&#x2F;span&gt;&lt;span&gt; _&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; in&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt; range&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;10&lt;&#x2F;span&gt;&lt;span&gt;):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;        with&lt;&#x2F;span&gt;&lt;span&gt; socket.socket(socket.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;AF_INET&lt;&#x2F;span&gt;&lt;span&gt;, socket.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;SOCK_DGRAM&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; as&lt;&#x2F;span&gt;&lt;span&gt; sock:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            sock.setsockopt(socket.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;SOL_SOCKET&lt;&#x2F;span&gt;&lt;span&gt;, socket.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;SO_BROADCAST&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            sock.sendto(magic_packet, (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;#39;&amp;lt;broadcast&amp;gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 9&lt;&#x2F;span&gt;&lt;span&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt; wake_screen&lt;&#x2F;span&gt;&lt;span&gt;():&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #88846F;&quot;&gt;    # If TV is ON, but media-PC has blanked the screen&#x2F;screensaver, move the mouse a bit.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    x, y&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; random.randint(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 10&lt;&#x2F;span&gt;&lt;span&gt;), random.randint(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 10&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    subprocess.run([&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;#39;ydotool&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &amp;#39;mousemove&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt; str&lt;&#x2F;span&gt;&lt;span&gt;(x),&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt; str&lt;&#x2F;span&gt;&lt;span&gt;(y)],&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FD971F;font-style: italic;&quot;&gt; stdout&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt;subprocess.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;DEVNULL&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FD971F;font-style: italic;&quot;&gt; stderr&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt;subprocess.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;DEVNULL&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt; start_steamlink&lt;&#x2F;span&gt;&lt;span&gt;():&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;    try&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        result&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; subprocess.run(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;#39;pgrep&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &amp;#39;-f&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &amp;#39;com.valvesoftware.SteamLink&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;], &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FD971F;font-style: italic;&quot;&gt;            stdout&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt;subprocess.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;PIPE&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FD971F;font-style: italic;&quot;&gt;            stderr&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt;subprocess.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;PIPE&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        )&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;        if&lt;&#x2F;span&gt;&lt;span&gt; result.stdout:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt;            print&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;#39;SteamLink is already running, skipping launch.&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;        else&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt;            print&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;#39;Starting SteamLink...&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;            with&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt; open&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;#39;&#x2F;home&#x2F;sverrejb&#x2F;workspace&#x2F;gamestart&#x2F;steamlink.log&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &amp;#39;a&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; as&lt;&#x2F;span&gt;&lt;span&gt; log_file:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                subprocess.Popen(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;                    &amp;#39;QT_QPA_PLATFORM=wayland WAYLAND_DISPLAY=wayland-0 flatpak run com.valvesoftware.SteamLink&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FD971F;font-style: italic;&quot;&gt;                    shell&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;True&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FD971F;font-style: italic;&quot;&gt;                    stdout&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt;log_file,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FD971F;font-style: italic;&quot;&gt;                    stderr&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt;subprocess.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;STDOUT&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FD971F;font-style: italic;&quot;&gt;                    start_new_session&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;True&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #FD971F;font-style: italic;&quot;&gt;                    env&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;                            &amp;#39;QT_QPA_PLATFORM&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &amp;#39;wayland&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;                            &amp;#39;WAYLAND_DISPLAY&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &amp;#39;wayland-0&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;                            &amp;#39;XDG_RUNTIME_DIR&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &amp;#39;&#x2F;run&#x2F;user&#x2F;1000&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                        }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                )&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;    except&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt; Exception&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; as&lt;&#x2F;span&gt;&lt;span&gt; e:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt;        print&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;f&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;#39;Error checking for SteamLink process: &lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;{&lt;&#x2F;span&gt;&lt;span&gt;e&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;def&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E22E;&quot;&gt; set_tv_input&lt;&#x2F;span&gt;&lt;span&gt;():&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;    try&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        subprocess.run([&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;#39;&#x2F;home&#x2F;sverrejb&#x2F;.local&#x2F;bin&#x2F;alga&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &amp;#39;input&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &amp;#39;set&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; HDMI_INPUT&lt;&#x2F;span&gt;&lt;span&gt;],&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FD971F;font-style: italic;&quot;&gt; check&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;True&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;    except&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt;        print&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;#39;Initial HDMI-switch failed. Trying harder.&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;        while&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; True&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            response&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; subprocess.run([&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;#39;ping&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &amp;#39;-c&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &amp;#39;1&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &amp;#39;-W&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &amp;#39;1&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; IP_ADDRESS_TV&lt;&#x2F;span&gt;&lt;span&gt;],&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FD971F;font-style: italic;&quot;&gt; stdout&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt;subprocess.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;PIPE&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;            if&lt;&#x2F;span&gt;&lt;span&gt; response.returncode&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; ==&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt;                print&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;#39;TV is responding to ping.&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;                break&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;            else&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt;                print&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;#39;Waiting for TV to respond to ping...&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                time.sleep(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        time.sleep(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;10&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;        for&lt;&#x2F;span&gt;&lt;span&gt; i&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; in&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt; range&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;5&lt;&#x2F;span&gt;&lt;span&gt;):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt;            print&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt;f&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;#39;Setting HDMI-input &lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;{&lt;&#x2F;span&gt;&lt;span&gt;i&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;1}&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&#x2F;5&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            subprocess.run([&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;#39;&#x2F;home&#x2F;sverrejb&#x2F;.local&#x2F;bin&#x2F;alga&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &amp;#39;input&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &amp;#39;set&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt; HDMI_INPUT&lt;&#x2F;span&gt;&lt;span&gt;])&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            time.sleep(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #AE81FF;&quot;&gt;0.2&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;if&lt;&#x2F;span&gt;&lt;span&gt; __name__&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F92672;&quot;&gt; ==&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt; &amp;#39;__main__&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;    try&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        main()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #F92672;&quot;&gt;    except&lt;&#x2F;span&gt;&lt;span style=&quot;color: #66D9EF;font-style: italic;&quot;&gt; KeyboardInterrupt&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #66D9EF;&quot;&gt;        print&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E6DB74;&quot;&gt;&amp;#39;Exiting...&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Water temperature mixing calculator</title>
        <published>2024-10-26T00:00:00+00:00</published>
        <updated>2024-10-26T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Sverre Johann Bjørke
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sverre.me/projects/water-mix/"/>
        <id>https://sverre.me/projects/water-mix/</id>
        
        <content type="html" xml:base="https://sverre.me/projects/water-mix/">&lt;p&gt;Short version: A friend and I made a simple tool for figuring out how much water of given temperatures to mix in order to reach a desired temperature:&lt;&#x2F;p&gt;




&lt;a hx-boost=&quot;false&quot; class=&quot;lightbox-image-link&quot; id=&quot;0.png&quot; href=&quot;#lightbox-0.png&quot;&gt;
    &lt;img width=&quot;100%&quot; loading=&quot;lazy&quot; alt=&quot;Screenshot of calculator&quot; src=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;processed_images&#x2F;0.6028fdad5f3643e3.webp&quot; &#x2F;&gt;
&lt;&#x2F;a&gt;

&lt;a hx-boost=&quot;false&quot; class=&quot;lightbox-image-link mobile&quot; id=&quot;0.png&quot; href=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;projects&#x2F;water-mix&#x2F;0.png&quot;&gt;
    &lt;img width=&quot;100%&quot; loading=&quot;lazy&quot; alt=&quot;Screenshot of calculator&quot; src=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;processed_images&#x2F;0.6028fdad5f3643e3.webp&quot; &#x2F;&gt;
&lt;&#x2F;a&gt;



&lt;dialog id=&quot;lightbox-0.png&quot; class=&quot;lightbox&quot;&gt;
    &lt;a class=&quot;close-background&quot; href=&quot;#0.png&quot; title=&quot;Close image&quot; role=&quot;button&quot;&gt;&lt;&#x2F;a&gt;
    &lt;img src=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;projects&#x2F;water-mix&#x2F;0.png&quot; loading=&quot;lazy&quot; alt=&quot;Screenshot of calculator&quot;&gt;
    &lt;a class=&quot;close-button&quot; href=&quot;#0.png&quot; title=&quot;Close image&quot; role=&quot;button&quot;&gt;×&lt;&#x2F;a&gt;
&lt;&#x2F;dialog&gt;
&lt;p&gt;It can be visited here: &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;water-mix-temp.fly.dev&quot;&gt;https:&#x2F;&#x2F;water-mix-temp.fly.dev&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Technologies used:&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Python&lt;&#x2F;li&gt;
&lt;li&gt;Uv&lt;&#x2F;li&gt;
&lt;li&gt;HTMX&lt;&#x2F;li&gt;
&lt;li&gt;Fly.io&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;long-version&quot;&gt;Long version:&lt;&#x2F;h2&gt;
&lt;p&gt;I recently became a dad for the second time, and we are relying on baby formula to feed the baby. When mixing formula with water, you need to mix it with sterile&#x2F;boiled water. Ideally, you want the formula to end up around body temperature. A trick is to mix boiling water with previously boiled, room temperature water from a container. But the ratio needs to be right.&lt;&#x2F;p&gt;
&lt;p&gt;When we had our first child, I asked a good friend, who is a chemist, if he could help me figure out a way to calculate the mixing ratio, given the temperature of the available water amounts. He cooked up a Python script, and I used this a little bit before I ended up with a pretty good gut-feeling for the whole thing.&lt;&#x2F;p&gt;
&lt;p&gt;During some of the naps of the new baby, I coded up a quick web-app to have the calculator available on my phone at all times. Not really necessary, but something to do while the baby slept. This allowed me to try a few new things that had been on my to-do list for a while. &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;astral.sh&#x2F;blog&#x2F;uv&quot;&gt;Uv&lt;&#x2F;a&gt;, an extremely fast Python package installer and resolver, written in Rust, and &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;fly.io&#x2F;&quot;&gt;Fly.io&lt;&#x2F;a&gt;, a cloud service for hosting apps leveraging microVMs. While only dipping my toes, both of these seem very promising, and have been added to my toolbox for future use.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Progressive enhancement of static sites with HTMX</title>
        <published>2024-07-25T00:00:00+00:00</published>
        <updated>2024-07-25T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Sverre Johann Bjørke
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sverre.me/blog/progressive-enhancement/"/>
        <id>https://sverre.me/blog/progressive-enhancement/</id>
        
        <content type="html" xml:base="https://sverre.me/blog/progressive-enhancement/">&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.htmx.org&quot;&gt;HTMX&lt;&#x2F;a&gt; is pretty neat and has quickly become one of my top contenders for frontend technologies for future projects. While it primarily is meant for more complex and dynamic sites and apps than this blog, even a website built with a static site generator like Zola can benefit. While Static site generators are great, and offer several &lt;a href=&quot;..&#x2F;..&#x2F;projects&#x2F;this-site&#x2F;&quot;&gt;upsides&lt;&#x2F;a&gt;, a downside is the user experience compared to more complex frontend applications. A collection of linked documents is how the web works, but navigating between them forces the browser to clear the current view and load the new page. On a fast connection, this is quite seamless, but on a slower connection or device, it can be a bit jarring. HTMX solves this with the &lt;code&gt;hx-boost&lt;&#x2F;code&gt;-property, which automatically enhances all anchor tags and forms (the hypermedia controllers of HTML), overriding the default operation with an AJAX-call that replaces only the content of the page, and not the page itself. The result is smoother navigation, no flash of unstyled content and no clearing of the whole page for each click on a link. And in cases where the client has deactivated JavaScript, the fallback is of course the default behavior, meaning the site works just as it should.&lt;&#x2F;p&gt;
&lt;p&gt;For this site, I put &lt;code&gt;hx-boost=&quot;true&quot;&lt;&#x2F;code&gt; on the &lt;code&gt;body&lt;&#x2F;code&gt; tag, as I wanted the whole site boosted. This initially broke the light&#x2F;dark-mode toggle, as the content is reloaded, but not the javascript, resulting in a missing event-listner. The good people behind HTMX had luckily thought of such things, as the &lt;code&gt;hx-preserve&lt;&#x2F;code&gt;-attribute prevents single elements from being replaced. This is done via the &lt;code&gt;id-attribute&lt;&#x2F;code&gt;, so I had to set a unique and unchanging &lt;code&gt;id&lt;&#x2F;code&gt; on the toggle.&lt;&#x2F;p&gt;
&lt;p&gt;Seeing how HTMX bridges the gap between traditional hypermedia and modern user experiences gives me hope for future projects. Leveraging the strengths of HTML, HTTP and going with the grain of the web, while not compromising on the usability of the product feels very powerful.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Info screen for the office</title>
        <published>2024-04-29T00:00:00+00:00</published>
        <updated>2024-04-29T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Sverre Johann Bjørke
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sverre.me/projects/infoscreen/"/>
        <id>https://sverre.me/projects/infoscreen/</id>
        
        <content type="html" xml:base="https://sverre.me/projects/infoscreen/">&lt;p&gt;Short version: I made an info screen for the office. It currently looks like this:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;github.com&#x2F;Knowit-Objectnet&#x2F;infoskjerm-trondheim&#x2F;raw&#x2F;main&#x2F;screenshot.png&quot; alt=&quot;&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The code can be found &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;Knowit-Objectnet&#x2F;infoskjerm-trondheim&quot;&gt;here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;I used:&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Rust, with:
&lt;ul&gt;
&lt;li&gt;Slint&lt;&#x2F;li&gt;
&lt;li&gt;Serde&lt;&#x2F;li&gt;
&lt;li&gt;Reqwest&lt;&#x2F;li&gt;
&lt;li&gt;Tokio&lt;&#x2F;li&gt;
&lt;li&gt;Tide&lt;&#x2F;li&gt;
&lt;li&gt;Cargo-zigbuild&lt;&#x2F;li&gt;
&lt;li&gt;... and more&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;Github Actions&lt;&#x2F;li&gt;
&lt;li&gt;Raspberry Pi&lt;&#x2F;li&gt;
&lt;li&gt;Python and bash for some scripting&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;long-version&quot;&gt;Long version:&lt;&#x2F;h2&gt;
&lt;p&gt;When I was a student, we had an info screen showing bus times, the lunch menu, upcoming events, meeting room bookings and so on. There were several iterations of this, but the one in use during my time was a web application. It looked like this:&lt;&#x2F;p&gt;

&lt;a hx-boost=&quot;false&quot; class=&quot;project-image-link&quot; href=&quot;1.jpg&quot;&gt;
    &lt;img width=&quot;100%&quot; loading=&quot;lazy&quot; alt=&quot;Picture of info screen from my student days&quot; src=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;processed_images&#x2F;1.6c79ab100ba9ea0c.webp&quot; &#x2F;&gt;
&lt;&#x2F;a&gt;
&lt;p&gt;What we don&#x27;t see in this picture is the chunky computer on the floor that ran this thing. At the time, the first generation Raspberry Pi was out, but as the info screen featured some simple CSS animations, even a relatively simple web app was too much to handle. Later, when the Raspberry Pi 2 became available, I set up the screen to run on one. It worked well enough, but running an application (the info screen) within another application (the browser) seemed unnecessarily complex.&lt;&#x2F;p&gt;
&lt;p&gt;Some years later I started to learn Rust, and one of the first things I wanted to make was an info screen for our apartment. But these were the early days of the Rust ecosystem, and while I was able to render a simple &quot;Hello World&quot; text string with the Conrod GUI library on a Raspberry Pi, the whole thing was very bare bones, and I had barely scratched the surface of Rust. And while I continued learning Rust, the info screen idea was relegated to the back burner.&lt;&#x2F;p&gt;
&lt;p&gt;Rust has since become my favorite language that I rarely find a fitting use case for. Most of my projects are silly web things. But I wanted to do a &quot;proper&quot; project in Rust now. Putting the cart before the horse, I decided to write an info screen for our office in Rust. Providentially, an episode of &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;rustacean-station.org&#x2F;episode&#x2F;tobias-hunger-slint-1.0&#x2F;&quot;&gt;Rustacean Station&lt;&#x2F;a&gt; about &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;slint.dev&#x2F;&quot;&gt;Slint&lt;&#x2F;a&gt; that had just reached v1.0 came out around the same time.&lt;&#x2F;p&gt;
&lt;p&gt;The first version was released with a single useful feature: telling the time. Notice the clock in the lower right corner.

&lt;a hx-boost=&quot;false&quot; class=&quot;project-image-link&quot; href=&quot;2.jpg&quot;&gt;
    &lt;img width=&quot;100%&quot; loading=&quot;lazy&quot; alt=&quot;Picture of info screen on wall&quot; src=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;processed_images&#x2F;2.347939c7f477344b.webp&quot; &#x2F;&gt;
&lt;&#x2F;a&gt;
&lt;&#x2F;p&gt;
&lt;p&gt;The application is written in Slint, which so far has been great to work with. It runs on a Raspberry Pi 3, stuck to the back of the screen.&lt;&#x2F;p&gt;
&lt;p&gt;After a while, I added weather forecast and the newest XKCD strip. The individual components were pulled out into separate modules, and are updated by dedicated worker threads.&lt;&#x2F;p&gt;

&lt;a hx-boost=&quot;false&quot; class=&quot;project-image-link&quot; href=&quot;3.png&quot;&gt;
    &lt;img width=&quot;100%&quot; loading=&quot;lazy&quot; alt=&quot;Picture of info screen on wall&quot; src=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;processed_images&#x2F;3.bd7fa6ce6ec27488.webp&quot; &#x2F;&gt;
&lt;&#x2F;a&gt;
&lt;p&gt;Eventually, some of my colleagues took an interest, and we made the info screen the focus of a hackathon where a new design was cooked up, a calendar widget was prototyped and bus times were worked on.&lt;&#x2F;p&gt;

&lt;a hx-boost=&quot;false&quot; class=&quot;project-image-link&quot; href=&quot;4.png&quot;&gt;
    &lt;img width=&quot;100%&quot; loading=&quot;lazy&quot; alt=&quot;Picture of info screen on wall&quot; src=&quot;https:&#x2F;&#x2F;sverre.me&#x2F;processed_images&#x2F;4.2a80dd6e86a4a921.webp&quot; &#x2F;&gt;
&lt;&#x2F;a&gt;
&lt;p&gt;It is still far from finished, but it is starting to take shape. I also added a feature to track our food orders on the screen, showing the remaining time and production status.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Hello, world!</title>
        <published>2024-04-10T00:00:00+00:00</published>
        <updated>2024-04-10T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Sverre Johann Bjørke
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sverre.me/blog/hello-world/"/>
        <id>https://sverre.me/blog/hello-world/</id>
        
        <content type="html" xml:base="https://sverre.me/blog/hello-world/">&lt;p&gt;Hello, world! This is a post in my new blog. I didn&#x27;t end up using the last one much, I expect this to meet the same fate. Oh well.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>This very website</title>
        <published>2024-04-10T00:00:00+00:00</published>
        <updated>2024-04-10T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Sverre Johann Bjørke
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sverre.me/projects/this-site/"/>
        <id>https://sverre.me/projects/this-site/</id>
        
        <content type="html" xml:base="https://sverre.me/projects/this-site/">&lt;p&gt;This is my little corner of the web. This site is built with Zola, a static site generator written in Rust. It is deployed to Netlify, which is by far the most user-friendly platform I have used. I have been a fan of static site generators and the first wave of &quot;JAM-stack&quot; architecture for a long time. Serving only static files makes sense in many cases, and much more often than it is done, I suspect. Unless your website contains live content or is updated multiple times per hour, static sites have great benefits:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;No backend -&amp;gt; Reduced attack vectors&lt;&#x2F;li&gt;
&lt;li&gt;SEO with less effort&lt;&#x2F;li&gt;
&lt;li&gt;Performance&lt;&#x2F;li&gt;
&lt;li&gt;Cheap hosting, free in many cases&lt;&#x2F;li&gt;
&lt;li&gt;Content can be version-controlled in git&lt;&#x2F;li&gt;
&lt;li&gt;The site is portable and can easily be hosted anywhere&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Personally, I think a lot of the SPA&#x27;s and full-stack websites out there would benefit greatly if they were made with a static site generator, a CMS and an automated CICD-pipeline.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>My old blog</title>
        <published>2019-03-18T00:00:00+00:00</published>
        <updated>2019-03-18T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Sverre Johann Bjørke
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sverre.me/projects/my-old-blog/"/>
        <id>https://sverre.me/projects/my-old-blog/</id>
        
        <summary type="html">&lt;div &gt;
    &lt;img src=&quot;blog.png&quot;  alt=&quot;Screenshot of old blog&quot;  &#x2F;&gt;
    
&lt;&#x2F;div&gt;
&lt;p&gt;My &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;blog.sverre.me&quot;&gt;previous blog&lt;&#x2F;a&gt; was made mostly as an exercise to get some experience with Elm.</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>Mirroring your Android display to Mac or Linux</title>
        <published>2018-11-08T00:00:00+00:00</published>
        <updated>2018-11-08T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Sverre Johann Bjørke
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sverre.me/blog/mirror-android/"/>
        <id>https://sverre.me/blog/mirror-android/</id>
        
        <content type="html" xml:base="https://sverre.me/blog/mirror-android/">&lt;p&gt;(This is an old post originally posted on our company Medium blog, which is now gone. I transplanted it here for posterity.)&lt;&#x2F;p&gt;
&lt;p&gt;For a presentation, I needed to show the audience what was going on on my Android phone. I expected this to be easy, but I ended up spending more than enough time figuring it out.&lt;&#x2F;p&gt;
&lt;p&gt;My first idea was to connect my phone to the projector using a USB-C to HDMI adapter. After testing with two different adapters, trawling the phone settings, and finally finding that &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;List_of_devices_with_video_output_over_USB-C#Smartphones&quot;&gt;this (now deleted) Wikipedia Article &lt;&#x2F;a&gt; did not list my phone, I realized that direct video output was not an option.&lt;&#x2F;p&gt;
&lt;p&gt;My next attempt was to find an app that would let me accomplish my goal. Several alternatives exist, such as &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.vysor.io&#x2F;&quot;&gt;Vysor&lt;&#x2F;a&gt;, &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.airdroid.com&#x2F;&quot;&gt;AirDroid&lt;&#x2F;a&gt; and &lt;a rel=&quot;external&quot; href=&quot;http:&#x2F;&#x2F;droid-at-screen.org&#x2F;&quot;&gt;Droid@Screen&lt;&#x2F;a&gt; among others. Vysor is capped at a low bandwidth unless you pay for the premium features, resulting in a low-resolution image. It also required installing an app on both my phone and computer. AirDroid was cumbersome to use, required me to register an account and also delivered sub-par resolution. Droid@Screen I could not even get to work on my Mac. Back to the googling board, then.&lt;&#x2F;p&gt;
&lt;p&gt;Hidden in &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;android.stackexchange.com&#x2F;a&#x2F;154328&#x2F;278658&quot;&gt;an answer on the Android Stackexchange&lt;&#x2F;a&gt;, with ten measly upvotes, I finally found my solution. It came in the most pleasing of forms, a simple combination of command line tools that can be run on your Mac or Linux laptop. It relies on Android Debug Bridge (adb), a tool that lets you interact with your phone using the terminal.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;adb shell screenrecord --output-format=h264 — | ffplay -framerate 60 -probesize 32 -sync video -\&lt;&#x2F;code&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;cdn-images-1.medium.com&#x2F;max&#x2F;800&#x2F;1*nNpym802m5b1SOIlDob-uQ.gif&quot; alt=&quot;&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;It’s working!&lt;&#x2F;p&gt;
&lt;p&gt;This solved my problem in a simple and developer-friendly way. It also did not force me to install any additional software, as I had installed both Android Debug Bridge and ffmpeg previously. Awesome!&lt;&#x2F;p&gt;
&lt;p&gt;If adb is not installed, it can be done with &lt;code&gt;brew cask install android-platform-tools&lt;&#x2F;code&gt; on Mac or with your package manager of choice on Linux.&lt;&#x2F;p&gt;
&lt;p&gt;So what does this command do? Let’s break it down a bit:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;adb shell screenrecord&lt;&#x2F;code&gt; starts the screenrecord application on the phone. Screenrecord has been available on Android since 4.4, and does, as the name implies, record whatever is on the screen. &lt;code&gt;--output-format=h264&lt;&#x2F;code&gt; spcecifies the output format, and the final hyphen sets the output of screenrecord to stdout.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;| ffplay&lt;&#x2F;code&gt; pipes the output from screenrecord to ffplay on the computer. ffplay is a simple media player that comes with ffmpeg. &lt;code&gt;-framerate 60 -probesize 32&lt;&#x2F;code&gt; specifies the frame rate and probesize. 32 is the lowest setting for probe size, and minimizes the delay in the playback.&lt;code&gt;-sync video&lt;&#x2F;code&gt; drops frames instead of fast-forwarding them, and the last hyphen sets the video input to stdin.&lt;&#x2F;p&gt;
&lt;p&gt;This solution does come with some caveats:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;You need to connect to your phone with a USB-cable (adb does support connecting over wifi, but I have not tested it for video transfer)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;screenrecord&lt;&#x2F;code&gt; has a time limit of three minutes, unless you want to &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;stackoverflow.com&#x2F;questions&#x2F;21938948&#x2F;how-to-increase-time-limit-of-adb-screen-record-of-android-kitkat&#x2F;25834761#25834761&quot;&gt;recompile from source&lt;&#x2F;a&gt;. Alternatively, you can put screenrecord in a while loop, like so: &lt;code&gt;adb shell &quot;while true; do screenrecord --output-format=h264 -; done&quot; | ffplay -framerate 60 -probesize 32 -sync video -&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</content>
        
    </entry>
</feed>
