the ugly ocaml monstrosity powering my site oppi.li
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

rss

Signed-off-by: oppiliappan <me@oppi.li>

+4680 -9
+1 -1
_site/index.html
··· 37 37 --bg:#fff; 38 38 } 39 39 } 40 - </style><div class="flex flex-col items-center md:flex-row p-2 mt-12 gap-12"><svg style="position:absolute;width:0;height:0;overflow:hidden" aria-hidden="true"><defs><filter id="dither" color-interpolation-filters="sRGB" x="0" y="0" width="100%" height="100%"><feImage width="8" height="8" result="pat" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAA+UlEQVR42gXBERTCUABA0X/OYDAYDAZBEAyCIBgMgiAIgiAYBINgEAwGgyAIBsFgMAiCIAiCIAgGQTAYDAaDIAiCwWDwulcIIXg8HgwGA36/H4qi8Hq9sCyLtm0Rm82G0WjE5XJhvV4ThiHT6ZT7/U4QBIhut0tVVaiqSpZl9Pt9vt8vnU6HsiwRh8OB5XLJfr9nNptxPp9xXZckSbBtGyHLMs/nE9M0aZoGSZJI05ThcEhd14jdbsdkMuF2u+H7PtvtlvF4zPV6xfM8hGEYfD4fdF2nKAp6vR7v9xtN08jzHHE6nVitVsRxzGKx4Hg84jgOURQxn8/5A7oKnYRU4EpfAAAAAElFTkSuQmCC"/><feTile in="pat" result="tiled"/><feComposite operator="arithmetic" k1="0" k2="1" k3="1" k4="-0.5" in="SourceGraphic" in2="tiled"/><feComponentTransfer><feFuncR type="discrete" tableValues="0 1"/><feFuncG type="discrete" tableValues="0 1"/><feFuncB type="discrete" tableValues="0 1"/></feComponentTransfer></filter></defs></svg><div class="aspect-1/1 w-48 flex items-center justify-center"><div class="sphere-clip"><div class="sphere"></div></div></div><div class="text-justify"><p>I&#x27;m Akshay, programmer, pixel-artist &amp; programming-language enthusiast.</p><p>I am currently building <a href="https://tangled.org">Tangled</a>, a new social-enabled code-collaboration platform.</p><p>I am available on libera.chat as oppili.</p></div></div><div><div class="space-y-4"><div class="flex items-center justify-between"><h2>posts</h2><a class="mt-10" href="/posts">view all</a></div><div><div class="flex"><a href="posts/mounting_the_atmosphere">mounting the atmosphere</a><span class="grow text-right tabular-nums">31.07.2025</span></div><div class="flex"><a href="posts/configuring_jujutsu">configuring jujutsu</a><span class="grow text-right tabular-nums">24.05.2025</span></div><div class="flex"><a href="posts/tales_from_mainframe_modernization">tales from mainframe modernization</a><span class="grow text-right tabular-nums">21.05.2025</span></div></div></div><div><div class="flex items-center justify-between"><h2>weeklies</h2><a class="mt-10" href="/weeklies">view all</a></div><div><div class="flex flex-row items-start"><div class="flex flex-col mt-4 items-center"><a class="text-lg" href="weeklies/2026-16">16</a><span class="text-xs">2026</span></div><ul><li>FP Launchpad at IIT Madras</li><li>Renewed interest in OCaml</li><li>Building a web-of-trust</li></ul></div></div></div></div></div></main></div></body></html> 40 + </style><div class="flex flex-col items-center md:flex-row p-2 mt-12 gap-12"><svg style="position:absolute;width:0;height:0;overflow:hidden" aria-hidden="true"><defs><filter id="dither" color-interpolation-filters="sRGB" x="0" y="0" width="100%" height="100%"><feImage width="8" height="8" result="pat" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAA+UlEQVR42gXBERTCUABA0X/OYDAYDAZBEAyCIBgMgiAIgiAYBINgEAwGgyAIBsFgMAiCIAiCIAgGQTAYDAaDIAiCwWDwulcIIXg8HgwGA36/H4qi8Hq9sCyLtm0Rm82G0WjE5XJhvV4ThiHT6ZT7/U4QBIhut0tVVaiqSpZl9Pt9vt8vnU6HsiwRh8OB5XLJfr9nNptxPp9xXZckSbBtGyHLMs/nE9M0aZoGSZJI05ThcEhd14jdbsdkMuF2u+H7PtvtlvF4zPV6xfM8hGEYfD4fdF2nKAp6vR7v9xtN08jzHHE6nVitVsRxzGKx4Hg84jgOURQxn8/5A7oKnYRU4EpfAAAAAElFTkSuQmCC"/><feTile in="pat" result="tiled"/><feComposite operator="arithmetic" k1="0" k2="1" k3="1" k4="-0.5" in="SourceGraphic" in2="tiled"/><feComponentTransfer><feFuncR type="discrete" tableValues="0 1"/><feFuncG type="discrete" tableValues="0 1"/><feFuncB type="discrete" tableValues="0 1"/></feComponentTransfer></filter></defs></svg><div class="aspect-1/1 w-48 flex items-center justify-center"><div class="sphere-clip"><div class="sphere"></div></div></div><div class="text-justify"><p>I&#x27;m Akshay, programmer, pixel-artist &amp; programming-language enthusiast.</p><p>I am currently building <a href="https://tangled.org">Tangled</a>, a new social-enabled code-collaboration platform.</p><p>I am available on libera.chat as oppili.</p></div></div><div><div class="space-y-4"><div class="flex items-center justify-between"><h2>posts</h2><div class="flex items-center mt-10" style="gap: 1ch"><a href="/posts">view all</a><a href="/posts/index.rss">rss</a></div></div><div><div class="flex"><a href="posts/mounting_the_atmosphere">mounting the atmosphere</a><span class="grow text-right tabular-nums">31.07.2025</span></div><div class="flex"><a href="posts/configuring_jujutsu">configuring jujutsu</a><span class="grow text-right tabular-nums">24.05.2025</span></div><div class="flex"><a href="posts/tales_from_mainframe_modernization">tales from mainframe modernization</a><span class="grow text-right tabular-nums">21.05.2025</span></div></div></div><div><div class="flex items-center justify-between"><h2>weeklies</h2><div class="flex items-center mt-10" style="gap: 1ch"><a href="/weeklies">view all</a><a href="/weeklies/index.rss">rss</a></div></div><div><div class="flex flex-row items-start"><div class="flex flex-col mt-4 items-center"><a class="text-lg" href="weeklies/2026-16">16</a><span class="text-xs">2026</span></div><ul><li>FP Launchpad at IIT Madras</li><li>Renewed interest in OCaml</li><li>Building a web-of-trust</li></ul></div></div></div></div></div></main></div></body></html>
+1 -1
_site/posts/index.html
··· 1 1 <!DOCTYPE html> 2 - <html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><meta name="description" content="oppiliappan&#x27;s μsings" /><meta name="author" content="Akshay Oppiliappan" /><meta name="color-scheme" content="light dark" /><meta name="twitter:card" content="summary" /><title>posts</title><link rel="stylesheet" href="/style.css?v=c98bdd9f" /><style>html { font-size-adjust: ex-height 0.53; }</style><meta property="og:type" content="website" /><meta property="og:site_name" content="oppi.li" /><meta property="og:title" content="posts" /><meta property="og:description" content="oppiliappan&#x27;s μsings" /></head><body><div><nav class="max-w-xl mx-auto"><a href="/">home</a><span> :: <a href="#">posts</a></span></nav><main class="px-2"><div class="max-w-xl mx-auto"><h1 class="text-center">posts</h1><div class="text-center space-x-2"><span>33 entries</span><span>·</span><span>6 years</span></div><div class="mt-4"><div class="flex"><a href="mounting_the_atmosphere">mounting the atmosphere</a><span class="grow text-right tabular-nums">31.07.2025</span></div><div class="flex"><a href="configuring_jujutsu">configuring jujutsu</a><span class="grow text-right tabular-nums">24.05.2025</span></div><div class="flex"><a href="tales_from_mainframe_modernization">tales from mainframe modernization</a><span class="grow text-right tabular-nums">21.05.2025</span></div><div class="flex"><a href="OSC-52">OSC-52</a><span class="grow text-right tabular-nums">27.11.2024</span></div><div class="flex"><a href="introducing_tablespoon">introducing tablespoon</a><span class="grow text-right tabular-nums">01.08.2024</span></div><div class="flex"><a href="snip_snap">snip snap</a><span class="grow text-right tabular-nums">29.05.2024</span></div><div class="flex"><a href="plain_text_journaling">plain text journaling</a><span class="grow text-right tabular-nums">18.06.2023</span></div><div class="flex"><a href="curing_a_case_of_git-UX">curing a case of git-UX</a><span class="grow text-right tabular-nums">03.09.2022</span></div><div class="flex"><a href="programming_on_34_keys">programming on 34 keys</a><span class="grow text-right tabular-nums">28.08.2022</span></div><div class="flex"><a href="a_reference_counted_afterlife">a reference counted afterlife</a><span class="grow text-right tabular-nums">02.08.2022</span></div><div class="flex"><a href="lotus58">lotus58</a><span class="grow text-right tabular-nums">13.06.2022</span></div><div class="flex"><a href="lightweight_linting">lightweight linting</a><span class="grow text-right tabular-nums">26.01.2022</span></div><div class="flex"><a href="novice_nix:_flake_templates">novice nix: flake templates</a><span class="grow text-right tabular-nums">05.10.2021</span></div><div class="flex"><a href="SDL2_devlog">SDL2 devlog</a><span class="grow text-right tabular-nums">11.04.2021</span></div><div class="flex"><a href="self-hosting_git">self-hosting git</a><span class="grow text-right tabular-nums">17.10.2020</span></div><div class="flex"><a href="nixOS">nixOS</a><span class="grow text-right tabular-nums">01.09.2020</span></div><div class="flex"><a href="gripes_with_go">gripes with go</a><span class="grow text-right tabular-nums">01.08.2020</span></div><div class="flex"><a href="turing_complete_type_systems">turing complete type systems</a><span class="grow text-right tabular-nums">17.06.2020</span></div><div class="flex"><a href="auto-currying_rust_functions">auto-currying rust functions</a><span class="grow text-right tabular-nums">08.05.2020</span></div><div class="flex"><a href="pixel_art_in_GIMP">pixel art in GIMP</a><span class="grow text-right tabular-nums">08.04.2020</span></div><div class="flex"><a href="rapid_refactoring_with_vim">rapid refactoring with vim</a><span class="grow text-right tabular-nums">31.03.2020</span></div><div class="flex"><a href="font_size_fallacies">font size fallacies</a><span class="grow text-right tabular-nums">16.03.2020</span></div><div class="flex"><a href="termux_tandem">termux tandem</a><span class="grow text-right tabular-nums">07.03.2020</span></div><div class="flex"><a href="call_to_ARMs">call to ARMs</a><span class="grow text-right tabular-nums">07.02.2020</span></div><div class="flex"><a href="color_conundrum">color conundrum</a><span class="grow text-right tabular-nums">30.12.2019</span></div><div class="flex"><a href="static_sites_with_bash">static sites with bash</a><span class="grow text-right tabular-nums">22.11.2019</span></div><div class="flex"><a href="my_setup">my setup</a><span class="grow text-right tabular-nums">06.11.2019</span></div><div class="flex"><a href="WPA_woes">WPA woes</a><span class="grow text-right tabular-nums">12.10.2019</span></div><div class="flex"><a href="bye_bye_BDFs">bye bye BDFs</a><span class="grow text-right tabular-nums">07.08.2019</span></div><div class="flex"><a href="onivim_sucks">onivim sucks</a><span class="grow text-right tabular-nums">02.08.2019</span></div><div class="flex"><a href="bash_harder_with_vim">bash harder with vim</a><span class="grow text-right tabular-nums">30.07.2019</span></div><div class="flex"><a href="hold_position!">hold position!</a><span class="grow text-right tabular-nums">30.07.2019</span></div><div class="flex"><a href="get_better_at_yanking_and_putting_in_vim">get better at yanking and putting in vim</a><span class="grow text-right tabular-nums">30.07.2019</span></div></div></div></main></div></body></html> 2 + <html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><meta name="description" content="oppiliappan&#x27;s μsings" /><meta name="author" content="Akshay Oppiliappan" /><meta name="color-scheme" content="light dark" /><meta name="twitter:card" content="summary" /><title>posts</title><link rel="stylesheet" href="/style.css?v=c98bdd9f" /><style>html { font-size-adjust: ex-height 0.53; }</style><meta property="og:type" content="website" /><meta property="og:site_name" content="oppi.li" /><meta property="og:title" content="posts" /><meta property="og:description" content="oppiliappan&#x27;s μsings" /></head><body><div><nav class="max-w-xl mx-auto"><a href="/">home</a><span> :: <a href="#">posts</a></span></nav><main class="px-2"><div class="max-w-xl mx-auto"><h1 class="text-center">posts</h1><div class="text-center space-x-2"><span>33 entries</span><span>·</span><span>6 years</span><span>·</span><a href="/posts/index.rss">rss</a></div><div class="mt-4"><div class="flex"><a href="mounting_the_atmosphere">mounting the atmosphere</a><span class="grow text-right tabular-nums">31.07.2025</span></div><div class="flex"><a href="configuring_jujutsu">configuring jujutsu</a><span class="grow text-right tabular-nums">24.05.2025</span></div><div class="flex"><a href="tales_from_mainframe_modernization">tales from mainframe modernization</a><span class="grow text-right tabular-nums">21.05.2025</span></div><div class="flex"><a href="OSC-52">OSC-52</a><span class="grow text-right tabular-nums">27.11.2024</span></div><div class="flex"><a href="introducing_tablespoon">introducing tablespoon</a><span class="grow text-right tabular-nums">01.08.2024</span></div><div class="flex"><a href="snip_snap">snip snap</a><span class="grow text-right tabular-nums">29.05.2024</span></div><div class="flex"><a href="plain_text_journaling">plain text journaling</a><span class="grow text-right tabular-nums">18.06.2023</span></div><div class="flex"><a href="curing_a_case_of_git-UX">curing a case of git-UX</a><span class="grow text-right tabular-nums">03.09.2022</span></div><div class="flex"><a href="programming_on_34_keys">programming on 34 keys</a><span class="grow text-right tabular-nums">28.08.2022</span></div><div class="flex"><a href="a_reference_counted_afterlife">a reference counted afterlife</a><span class="grow text-right tabular-nums">02.08.2022</span></div><div class="flex"><a href="lotus58">lotus58</a><span class="grow text-right tabular-nums">13.06.2022</span></div><div class="flex"><a href="lightweight_linting">lightweight linting</a><span class="grow text-right tabular-nums">26.01.2022</span></div><div class="flex"><a href="novice_nix:_flake_templates">novice nix: flake templates</a><span class="grow text-right tabular-nums">05.10.2021</span></div><div class="flex"><a href="SDL2_devlog">SDL2 devlog</a><span class="grow text-right tabular-nums">11.04.2021</span></div><div class="flex"><a href="self-hosting_git">self-hosting git</a><span class="grow text-right tabular-nums">17.10.2020</span></div><div class="flex"><a href="nixOS">nixOS</a><span class="grow text-right tabular-nums">01.09.2020</span></div><div class="flex"><a href="gripes_with_go">gripes with go</a><span class="grow text-right tabular-nums">01.08.2020</span></div><div class="flex"><a href="turing_complete_type_systems">turing complete type systems</a><span class="grow text-right tabular-nums">17.06.2020</span></div><div class="flex"><a href="auto-currying_rust_functions">auto-currying rust functions</a><span class="grow text-right tabular-nums">08.05.2020</span></div><div class="flex"><a href="pixel_art_in_GIMP">pixel art in GIMP</a><span class="grow text-right tabular-nums">08.04.2020</span></div><div class="flex"><a href="rapid_refactoring_with_vim">rapid refactoring with vim</a><span class="grow text-right tabular-nums">31.03.2020</span></div><div class="flex"><a href="font_size_fallacies">font size fallacies</a><span class="grow text-right tabular-nums">16.03.2020</span></div><div class="flex"><a href="termux_tandem">termux tandem</a><span class="grow text-right tabular-nums">07.03.2020</span></div><div class="flex"><a href="call_to_ARMs">call to ARMs</a><span class="grow text-right tabular-nums">07.02.2020</span></div><div class="flex"><a href="color_conundrum">color conundrum</a><span class="grow text-right tabular-nums">30.12.2019</span></div><div class="flex"><a href="static_sites_with_bash">static sites with bash</a><span class="grow text-right tabular-nums">22.11.2019</span></div><div class="flex"><a href="my_setup">my setup</a><span class="grow text-right tabular-nums">06.11.2019</span></div><div class="flex"><a href="WPA_woes">WPA woes</a><span class="grow text-right tabular-nums">12.10.2019</span></div><div class="flex"><a href="bye_bye_BDFs">bye bye BDFs</a><span class="grow text-right tabular-nums">07.08.2019</span></div><div class="flex"><a href="onivim_sucks">onivim sucks</a><span class="grow text-right tabular-nums">02.08.2019</span></div><div class="flex"><a href="bash_harder_with_vim">bash harder with vim</a><span class="grow text-right tabular-nums">30.07.2019</span></div><div class="flex"><a href="hold_position!">hold position!</a><span class="grow text-right tabular-nums">30.07.2019</span></div><div class="flex"><a href="get_better_at_yanking_and_putting_in_vim">get better at yanking and putting in vim</a><span class="grow text-right tabular-nums">30.07.2019</span></div></div></div></main></div></body></html>
+4485
_site/posts/index.rss
··· 1 + <?xml version="1.0" encoding="UTF-8"?> 2 + <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"> 3 + <channel> 4 + <title>oppiliappan's μsings</title> 5 + <link>https://oppi.li</link> 6 + <description>programming, design, software</description> 7 + <atom:link href="https://oppi.li/posts/index.rss" rel="self" type="application/rss+xml" /> 8 + <language>en-us</language> 9 + <copyright>Creative Commons BY-NC-SA 4.0</copyright> 10 + <item> 11 + <title>mounting the atmosphere</title> 12 + <description>&lt;p&gt;&lt;a href="https://tangled.sh/@oppi.li/pdsfs"&gt;pdsfs&lt;/a&gt; is a tool that 13 + mounts &lt;a href="https://atproto.com"&gt;atproto&lt;/a&gt; PDS repositories as a 14 + &lt;a href="https://en.wikipedia.org/wiki/Filesystem_in_Userspace"&gt;FUSE&lt;/a&gt; 15 + filesystem.&lt;/p&gt; 16 + &lt;p&gt;A &lt;a href="https://en.wikipedia.org/wiki/Filesystem_in_Userspace"&gt;PDS 17 + repository&lt;/a&gt; 18 + contains all data published by a user to the atmosphere. It 19 + is exportable as a CAR (content-addressable archive) file. 20 + pdsfs is a tool that mounts this CAR file as a readonly-FUSE 21 + filesystem, allowing quick and easy exploration.&lt;/p&gt; 22 + &lt;p&gt;To motivate the need for such a program, we could begin by 23 + mounting a repository:&lt;/p&gt; 24 + &lt;pre&gt;&lt;code&gt;λ pdsfs oppi.li 25 + mounted at &amp;quot;mnt&amp;quot; 26 + hit enter to unmount and exit... 27 + &lt;/code&gt;&lt;/pre&gt; 28 + &lt;p&gt;oppi.li is my handle in the atmosphere. The tool does some 29 + hardwork to determine the location of my PDS repository 30 + given my handle, but that is not important. Let's have a 31 + look around:&lt;/p&gt; 32 + &lt;pre&gt;&lt;code&gt;λ ls mnt/ 33 + did:plc:qfpnj4og54vl56wngdriaxug/ 34 + &lt;/code&gt;&lt;/pre&gt; 35 + &lt;p&gt;The &lt;code&gt;did:plc:stuff&lt;/code&gt; is my 36 + &lt;a href="https://en.wikipedia.org/wiki/Filesystem_in_Userspace"&gt;DID&lt;/a&gt;. 37 + Digging deeper:&lt;/p&gt; 38 + &lt;pre&gt;&lt;code&gt;λ ls mnt/did\:plc\:qfpnj4og54vl56wngdriaxug/ 39 + app.bsky.actor.profile/ place.stream.chat.message/ 40 + app.bsky.actor.status/ place.stream.chat.profile/ 41 + app.bsky.feed.generator/ place.stream.key/ 42 + app.bsky.feed.like/ place.stream.livestream/ 43 + app.bsky.feed.post/ sh.tangled.actor.profile/ 44 + app.bsky.feed.repost/ sh.tangled.feed.reaction/ 45 + app.bsky.graph.block/ sh.tangled.feed.star/ 46 + app.bsky.graph.follow/ sh.tangled.graph.follow/ 47 + app.rocksky.album/ sh.tangled.knot/ 48 + app.rocksky.artist/ sh.tangled.knot.member/ 49 + . 50 + . 51 + . 52 + &lt;/code&gt;&lt;/pre&gt; 53 + &lt;p&gt;We have some data from the repository now. These are 54 + &amp;quot;collections&amp;quot;. If I want to publish a post to Bluesky, I 55 + would write content to the &lt;code&gt;app.bsky.feed.post&lt;/code&gt; collection 56 + in my PDS. This will then be indexed by a Bluesky appview 57 + (such as &lt;a href="https://bsky.app"&gt;bsky.app&lt;/a&gt; or 58 + &lt;a href="https://zeppelin.social"&gt;zeppelin.social&lt;/a&gt;) and show up 59 + under my profile there.&lt;/p&gt; 60 + &lt;p&gt;pdsfs is kind enough to deserialize the in the PDS 61 + repository (stored as CBOR normally) to JSON on the 62 + filesystem:&lt;/p&gt; 63 + &lt;pre&gt;&lt;code&gt;λ cat sh.tangled.repo/3ljidbevrjh22 | jq 64 + { 65 + &amp;quot;$type&amp;quot;: &amp;quot;sh.tangled.repo&amp;quot;, 66 + &amp;quot;addedAt&amp;quot;: &amp;quot;2025-03-03T16:04:13Z&amp;quot;, 67 + &amp;quot;knot&amp;quot;: &amp;quot;knot1.tangled.sh&amp;quot;, 68 + &amp;quot;name&amp;quot;: &amp;quot;hello-world&amp;quot;, 69 + &amp;quot;owner&amp;quot;: &amp;quot;did:plc:3danwc67lo7obz2fmdg6jxcr&amp;quot; 70 + } 71 + &lt;/code&gt;&lt;/pre&gt; 72 + &lt;p&gt;Thanks pdsfs!&lt;/p&gt; 73 + &lt;p&gt;I publish my music listening habits to my PDS to the 74 + &lt;code&gt;app.rocksky.scrobble&lt;/code&gt; collection, because 75 + &lt;a href="https://rocksky.app"&gt;Rocksky&lt;/a&gt; recognizes and indexes this 76 + collection. I have wired up my personal navidrome instance 77 + to write data of this form into my PDS everytime I listen to 78 + a track. Here are my top artists in order:&lt;/p&gt; 79 + &lt;pre&gt;&lt;code&gt;λ jq -r '.artist' app.rocksky.scrobble/* | sort | uniq -c | sort -nr 80 + 117 Thank You Scientist 81 + 45 FKJ 82 + 34 Covet 83 + 33 VOLA 84 + 23 Sam Cooke 85 + 22 Dark Tranquillity 86 + 21 Piero Piccioni 87 + 12 Bloodywood 88 + 11 Frank Sinatra 89 + 10 Dream Theater 90 + &lt;/code&gt;&lt;/pre&gt; 91 + &lt;p&gt;It is true, I love Sam Cooke.&lt;/p&gt; 92 + &lt;p&gt;pdsfs allows mounting multiple repositories at a time. Allow 93 + me to introduce my friends:&lt;/p&gt; 94 + &lt;pre&gt;&lt;code&gt;λ pdsfs icyphox.sh anil.recoil.org steveklabnik.com tangled.sh 95 + using cached CAR file for...did:plc:hwevmowznbiukdf6uk5dwrrq 96 + using cached CAR file for...did:plc:nhyitepp3u4u6fcfboegzcjw 97 + download complete for...did:plc:3danwc67lo7obz2fmdg6jxcr 98 + download complete for...did:plc:wshs7t2adsemcrrd4snkeqli 99 + mounted at &amp;quot;mnt&amp;quot; 100 + hit enter to unmount and exit... 101 + 102 + # -- in a separate shell -- 103 + 104 + λ cat ./mnt/*/app.bsky.actor.profile/* \ 105 + | jq -r '&amp;quot;\(.displayName)\n\(.description)\n---&amp;quot;' \ 106 + | sed '/^$/d' 107 + Steve Klabnik 108 + #rustlang, #jj-vcs, atproto, shitposts, urbanism. I 109 + contain multitudes. Working on #ruelang but just for 110 + fun. Currently in Austin, TX, but from Pittsburgh. 111 + Previously in Bushwick, the Mission, LA. 112 + --- 113 + Anirudh Oppiliappan 114 + building @tangled.sh — code collaboration platform built 115 + on atproto helsinki, finland · https://anirudh.fi · 116 + (somewhat) effective altruist 117 + --- 118 + Anil Madhavapeddy 119 + Professor of Planetary Computing at the University of 120 + Cambridge @cst.cam.ac.uk, where I co-lead the 121 + @eeg.cl.cam.ac.uk, and am also to found at 122 + @conservation.cam.ac.uk. Homepage at 123 + https://anil.recoil.org 124 + --- 125 + Tangled 126 + https://tangled.sh is a git collaboration platform built 127 + on atproto. Social coding, but for real this time! 128 + Discord: chat.tangled.sh IRC: #tangled @ libera.chat 129 + Built by @oppi.li &amp;amp; @icyphox.sh 130 + --- 131 + &lt;/code&gt;&lt;/pre&gt; 132 + &lt;p&gt;All my friends use &lt;a href="https://tangled.sh"&gt;tangled.sh&lt;/a&gt;, which 133 + requires them to publish their ssh public key to their PDii 134 + (PDSes?). Perhaps I would like to add their keys to my 135 + allowed_signers file to verify their commit signatures:&lt;/p&gt; 136 + &lt;pre&gt;&lt;code&gt;λ for dir in ./*/sh.tangled.publicKey; 137 + do cat $dir/$(ls -r $dir | head -n1) | jq -r '.key'; 138 + done | tee allowed_signers 139 + ssh-rsa AAAAB3NzaC1yc2EAAA...dHPqc= steveklabnik@DESKTOP-VV370NK 140 + ssh-ed25519 AAAAC3NzaC1lZD...g9bAdk icy@wyndle 141 + ssh-ed25519 AAAAC3NzaC1lZD...BqlM1u anil@recoil.org 142 + &lt;/code&gt;&lt;/pre&gt; 143 + &lt;p&gt;FUSE is quite liberating in that it allows you to represent 144 + anything as a filesystem. When applications like &lt;code&gt;ls&lt;/code&gt; and 145 + &lt;code&gt;cat&lt;/code&gt; are executed, the syscalls to &lt;code&gt;open&lt;/code&gt; and &lt;code&gt;read&lt;/code&gt; are 146 + rerouted to your custom fs implementation (pdsfs in this 147 + case). The custom fs implementation is free to as it 148 + pleases, in fact the first iteration of pdsfs accessed the 149 + network for each open/read call to fetch live data from 150 + PDii.&lt;/p&gt; 151 + </description> 152 + <link>https://oppi.li/posts/mounting_the_atmosphere/</link> 153 + <pubDate>Thu, 31 Jul 2025 00:00:00 +0000</pubDate> 154 + <guid>https://oppi.li/posts/mounting_the_atmosphere/</guid> 155 + </item> 156 + <item> 157 + <title>configuring jujutsu</title> 158 + <description>&lt;p&gt;There are a lot of reasons to use 159 + &lt;a href="https://github.com/jj-vcs/jj"&gt;jujutsu&lt;/a&gt;, but this post is 160 + not about that. I have this terrible habit of turning every 161 + knob on a tool before I even fully absorb how it works; and 162 + boy does &lt;code&gt;jj&lt;/code&gt; have a lot of knobs.&lt;/p&gt; 163 + &lt;h3 id="templates"&gt;Templates&lt;/h3&gt; 164 + &lt;p&gt;&lt;code&gt;jj&lt;/code&gt; let you tweak nearly every single character of its 165 + output. In fact, &lt;code&gt;jj&lt;/code&gt; includes a full-blown templating 166 + language to do so. Lets start from the basics:&lt;/p&gt; 167 + &lt;p&gt;Format all timestamps in relative fashion, like &amp;quot;5 hours 168 + ago&amp;quot;:&lt;/p&gt; 169 + &lt;pre&gt;&lt;code class="language-toml"&gt;[template-aliases] 170 + &amp;quot;format_timestamp(timestamp)&amp;quot; = &amp;quot;timestamp.ago()&amp;quot;; 171 + &lt;/code&gt;&lt;/pre&gt; 172 + &lt;p&gt;The default nodes in the log graph are a bit large for my 173 + taste, we can modify the &lt;code&gt;log_node&lt;/code&gt; template to fix that:&lt;/p&gt; 174 + &lt;pre&gt;&lt;code class="language-toml"&gt;[templates] 175 + log_node = ''' 176 + label(&amp;quot;node&amp;quot;, 177 + coalesce( 178 + if(!self, label(&amp;quot;elided&amp;quot;, &amp;quot;~&amp;quot;)), 179 + if(current_working_copy, label(&amp;quot;working_copy&amp;quot;, &amp;quot;@&amp;quot;)), 180 + if(conflict, label(&amp;quot;conflict&amp;quot;, &amp;quot;×&amp;quot;)), 181 + if(immutable, label(&amp;quot;immutable&amp;quot;, &amp;quot;*&amp;quot;)), 182 + label(&amp;quot;normal&amp;quot;, &amp;quot;·&amp;quot;) 183 + ) 184 + ) 185 + ''' 186 + &lt;/code&gt;&lt;/pre&gt; 187 + &lt;p&gt;This uses smaller iconography in &lt;code&gt;jj log&lt;/code&gt; (these icons are 188 + also more widely available in fonts, in my experience):&lt;/p&gt; 189 + &lt;pre&gt;&lt;code&gt;@ wuuownsw me@oppi.li 21 minutes ago 30d1bd12 190 + │ (no description set) 191 + · plmznxvy me@oppi.li 5 hours ago push-plmznxvyqrqw git_head() 051c142e 192 + │ appview: pulls: bump sourceRev for stacks without causing resubmits 193 + │ * towvwqxk x@icyphox.sh 4 hours ago master a588f625 194 + │ │ appview: pages/markup: don't double camo in post process 195 + │ * ryzqnyvs me@oppi.li 4 hours ago ea4b520a 196 + │ │ appview: fix stack merging 197 + │ * kqvutzxr me@oppi.li 4 hours ago ff73ca23 198 + ├─╯ appview: rework RepoLanguages 199 + * vwpqwmms jeynesbrook@gmail.com 8 hours ago d759587b 200 + │ appview: repo: inject language percentage into repo index 201 + ~ 202 + &lt;/code&gt;&lt;/pre&gt; 203 + &lt;p&gt;Much nicer! And speaking of &lt;code&gt;log&lt;/code&gt;, I find it hard to parse 204 + the output when every change occupies two lines instead of 205 + just one line, we can remedy that quickly; in fact, &lt;code&gt;jj&lt;/code&gt; has 206 + a builtin template for single-line log outputs:&lt;/p&gt; 207 + &lt;pre&gt;&lt;code&gt;λ jj log -T builtin_log_oneline 208 + @ wuuownsw me@oppi.li 22 minutes ago 30d1bd12 (no description set) 209 + · plmznxvy me@oppi.li 5 hours ago push-plmznxvyqrqw git_head() 051c142e appview: pulls: bump sourceRev for stacks without causing resubmits 210 + │ * towvwqxk x@icyphox.sh 4 hours ago master a588f625 appview: pages/markup: don't double camo in post process 211 + │ * ryzqnyvs me@oppi.li 4 hours ago ea4b520a appview: fix stack merging 212 + │ * kqvutzxr me@oppi.li 4 hours ago ff73ca23 appview: rework RepoLanguages 213 + ├─╯ 214 + * vwpqwmms jeynesbrook 8 hours ago d759587b appview: repo: inject language percentage into repo index 215 + 216 + ~ 217 + &lt;/code&gt;&lt;/pre&gt; 218 + &lt;p&gt;Bit too long! I personally do not always need author and 219 + time information when quickly scrolling through the log, so 220 + I created my own template based on &lt;code&gt;builtin_log_oneline&lt;/code&gt;:&lt;/p&gt; 221 + &lt;pre&gt;&lt;code class="language-toml"&gt;[templates] 222 + log = ''' 223 + if(root, 224 + format_root_commit(self), 225 + label(if(current_working_copy, &amp;quot;working_copy&amp;quot;), 226 + concat( 227 + separate(&amp;quot; &amp;quot;, 228 + format_short_change_id_with_hidden_and_divergent_info(self), 229 + if(empty, label(&amp;quot;empty&amp;quot;, &amp;quot;(empty)&amp;quot;)), 230 + if(description, 231 + description.first_line(), 232 + label(if(empty, &amp;quot;empty&amp;quot;), description_placeholder), 233 + ), 234 + bookmarks, 235 + tags, 236 + working_copies, 237 + if(git_head, label(&amp;quot;git_head&amp;quot;, &amp;quot;HEAD&amp;quot;)), 238 + if(conflict, label(&amp;quot;conflict&amp;quot;, &amp;quot;conflict&amp;quot;)), 239 + if(config(&amp;quot;ui.show-cryptographic-signatures&amp;quot;).as_boolean(), 240 + format_short_cryptographic_signature(signature)), 241 + ) ++ &amp;quot;\n&amp;quot;, 242 + ), 243 + ) 244 + ) 245 + ''' 246 + &lt;/code&gt;&lt;/pre&gt; 247 + &lt;p&gt;Which produces:&lt;/p&gt; 248 + &lt;pre&gt;&lt;code&gt;@ wuuownsw (no description set) 249 + · plmznxvy appview: pulls: bump sourceRev for stacks without causing resubmits push-plmznxvyqrqw HEAD 250 + │ * towvwqxk appview: pages/markup: don't double camo in post process master 251 + │ * ryzqnyvs appview: fix stack merging 252 + │ * kqvutzxr appview: rework RepoLanguages 253 + ├─╯ 254 + * vwpqwmms appview: repo: inject language percentage into repo index 255 + 256 + ~ 257 + &lt;/code&gt;&lt;/pre&gt; 258 + &lt;p&gt;Sweet! To get a more detailed log, you can always use a 259 + different template for the output: &lt;code&gt;jj log -T builtin_log_detailed&lt;/code&gt;.&lt;/p&gt; 260 + &lt;p&gt;With git, I set &lt;code&gt;commit.verbose&lt;/code&gt; to true, this lets me view 261 + the diff when composing a commit messaege in my &lt;code&gt;$EDITOR&lt;/code&gt;:&lt;/p&gt; 262 + &lt;pre&gt;&lt;code class="language-bash"&gt;λ git commit 263 + # no message passed, opens $EDITOR to compose message 264 + 265 + # -- inside vim -- 266 + 267 + # Please enter the commit message for your changes. Lines starting 268 + # with '#' will be ignored, and an empty message aborts the commit. 269 + # 270 + # On branch trunk 271 + # Changes to be committed: 272 + # new file: scripts/handle.js 273 + # 274 + # ------------------------ &amp;gt;8 ------------------------ 275 + # Do not modify or remove the line above. 276 + # Everything below it will be ignored. 277 + diff --git a/scripts/handle.js b/scripts/handle.js 278 + new file mode 100644 279 + index 0000000..d8a07f3 280 + --- /dev/null 281 + +++ b/scripts/handle.js 282 + @@ -0,0 +1,104 @@ 283 + +// Run using node handle.js 284 + +// Install axios using npm install axios 285 + +// nodejs v18+ 286 + +const axios = require(&amp;quot;axios&amp;quot;); 287 + . 288 + . 289 + . 290 + &lt;/code&gt;&lt;/pre&gt; 291 + &lt;p&gt;The equivalent in &lt;code&gt;jj&lt;/code&gt; requires modifying the 292 + &lt;code&gt;draft_commit_description&lt;/code&gt; template:&lt;/p&gt; 293 + &lt;pre&gt;&lt;code class="language-toml"&gt;[templates] 294 + draft_commit_description =''' 295 + concat( 296 + coalesce(description, default_commit_description, &amp;quot;\n&amp;quot;), 297 + surround( 298 + &amp;quot;\nJJ: This commit contains the following changes:\n&amp;quot;, &amp;quot;&amp;quot;, 299 + indent(&amp;quot;JJ: &amp;quot;, diff.stat(72)), 300 + ), 301 + &amp;quot;\nJJ: ignore-rest\n&amp;quot;, 302 + diff.git(), 303 + ) 304 + ''' 305 + &lt;/code&gt;&lt;/pre&gt; 306 + &lt;h3 id="revsets"&gt;Revsets&lt;/h3&gt; 307 + &lt;p&gt;As a not-so-power-user, I presently only use revsets to 308 + improve my &lt;code&gt;jj log&lt;/code&gt; experience. In the context of &lt;code&gt;jj log&lt;/code&gt;, 309 + giving it a revset means &amp;quot;here is a bag of revisions, graph 310 + them for me neatly&amp;quot;.&lt;/p&gt; 311 + &lt;p&gt;I find myself using the &amp;quot;rebase&amp;quot; flow quite often:&lt;/p&gt; 312 + &lt;ul&gt; 313 + &lt;li&gt;I hack on a stack of changes 314 + &lt;/li&gt; 315 + &lt;li&gt;&lt;code&gt;trunk&lt;/code&gt; is updated by my fellow collaborators 316 + &lt;/li&gt; 317 + &lt;li&gt;I rebase my stack using &lt;code&gt;jj rebase -s &amp;lt;mine&amp;gt; -d &amp;lt;trunk&amp;gt;&lt;/code&gt; 318 + &lt;/li&gt; 319 + &lt;/ul&gt; 320 + &lt;p&gt;And I scrub through the log output to roughly figure out how 321 + much work it would be to rebase, what I need for this is:&lt;/p&gt; 322 + &lt;ul&gt; 323 + &lt;li&gt;the changes added to &lt;code&gt;trunk&lt;/code&gt; since I last diverged from it 324 + &lt;/li&gt; 325 + &lt;li&gt;the changes add to my stack since I last diverged from 326 + &lt;code&gt;trunk&lt;/code&gt; 327 + &lt;/li&gt; 328 + &lt;/ul&gt; 329 + &lt;pre&gt;&lt;code&gt; X--Y--Z my stack 330 + / 331 + F--A--B--C--D trunk 332 + &lt;/code&gt;&lt;/pre&gt; 333 + &lt;p&gt;If you want &lt;code&gt;jj log&lt;/code&gt; to print this (and by &amp;quot;this&amp;quot;, I mean, 334 + the graph above, exactly as presented), you have to supply 335 + it a &lt;code&gt;revset&lt;/code&gt; argument that grabs X, Y, Z, A, B, C, D and F. 336 + Some examples of revsets are:&lt;/p&gt; 337 + &lt;pre&gt;&lt;code&gt;@ # the rev marking the working copy 338 + x # the rev identified by shorthand `x` 339 + x | y # the set of two revs, [x, y] 340 + @ | trunk() # the set of two revs, [@, trunk()] 341 + .. # everything in this repository 342 + all() # also everything in this repository 343 + fork_point(@ | trunk()) # the rev where @ and trunk() forked off 344 + &lt;/code&gt;&lt;/pre&gt; 345 + &lt;p&gt;And the revset that captures &amp;quot;ahead-behind&amp;quot; style output is:&lt;/p&gt; 346 + &lt;pre&gt;&lt;code&gt;trunk()..@ | @..trunk() | trunk() | @:: | fork_point(trunk() | @) 347 + &lt;/code&gt;&lt;/pre&gt; 348 + &lt;p&gt;Which includes:&lt;/p&gt; 349 + &lt;ul&gt; 350 + &lt;li&gt;&lt;code&gt;trunk()..@&lt;/code&gt;: the new changes that my collaborators have added 351 + &lt;/li&gt; 352 + &lt;li&gt;&lt;code&gt;@..trunk()&lt;/code&gt;: the new changes that I have added 353 + &lt;/li&gt; 354 + &lt;li&gt;&lt;code&gt;trunk()&lt;/code&gt;: the trunk rev itself 355 + &lt;/li&gt; 356 + &lt;li&gt;&lt;code&gt;@::&lt;/code&gt;: all descendants of &lt;code&gt;@&lt;/code&gt; 357 + &lt;/li&gt; 358 + &lt;li&gt;&lt;code&gt;fork_point(trunk() | @)&lt;/code&gt;: the rev from which the two 359 + streams of work diverged 360 + &lt;/li&gt; 361 + &lt;/ul&gt; 362 + &lt;p&gt;Rougly, when working on a stack, this is what I can see by 363 + supplying the above revset expression (simplified output):&lt;/p&gt; 364 + &lt;pre&gt;&lt;code class="language-bash"&gt;λ jj log -r 'trunk()..@ | @..trunk() | trunk() | @:: | fork_point(trunk() | @)' 365 + @ xdihgmke &amp;lt;-- me 366 + · vurstull 367 + · plmznxvy 368 + · ihefghyy 369 + │ * towvwqxk trunk &amp;lt;-- trunk 370 + │ * ryzqnyvs 371 + │ * kqvutzxr 372 + ├─╯ 373 + * vwpqwmms &amp;lt;-- fork-point 374 + &lt;/code&gt;&lt;/pre&gt; 375 + &lt;p&gt;And sometimes, when I am editing older parts of my stack 376 + (&lt;code&gt;@::&lt;/code&gt; helps us grab &lt;code&gt;xdihgmke&lt;/code&gt; and &lt;code&gt;vurstull&lt;/code&gt;):&lt;/p&gt; 377 + &lt;pre&gt;&lt;code class="language-bash"&gt;λ jj log -r 'trunk()..@ | @..trunk() | trunk() | @:: | fork_point(trunk() | @)' 378 + · xdihgmke 379 + · vurstull 380 + @ plmznxvy &amp;lt;-- me 381 + · ihefghyy 382 + │ * towvwqxk trunk &amp;lt;-- trunk 383 + │ * ryzqnyvs 384 + │ * kqvutzxr 385 + ├─╯ 386 + * vwpqwmms &amp;lt;-- fork-point 387 + &lt;/code&gt;&lt;/pre&gt; 388 + &lt;p&gt;To rebase, I would run:&lt;/p&gt; 389 + &lt;pre&gt;&lt;code&gt;jj rebase -s ihefghyy -d towvwqxk 390 + &lt;/code&gt;&lt;/pre&gt; 391 + &lt;h3 id="aliases"&gt;Aliases&lt;/h3&gt; 392 + &lt;p&gt;I imagine that most git power users are already familiar 393 + with aliases. The only alias I see myself using often is:&lt;/p&gt; 394 + &lt;pre&gt;&lt;code class="language-toml"&gt;[aliases] 395 + tug = [&amp;quot;bookmark&amp;quot;, &amp;quot;move&amp;quot;, &amp;quot;--from&amp;quot;, &amp;quot;heads(::@- &amp;amp; bookmarks())&amp;quot;, &amp;quot;--to&amp;quot;, &amp;quot;@-&amp;quot;]; 396 + &lt;/code&gt;&lt;/pre&gt; 397 + &lt;p&gt;In action:&lt;/p&gt; 398 + &lt;pre&gt;&lt;code class="language-bash"&gt;# ugh my bookmark is way behind 399 + λ jj log 400 + @ xdihgmke 401 + · vurstull 402 + · cyzmakil 403 + · plmznxvy 404 + · ihefghyy some-bookmark 405 + 406 + ~ 407 + 408 + λ jj tug 409 + 410 + # ready to push! 411 + λ jj log 412 + @ xdihgmke 413 + · vurstull some-bookmark* 414 + · cyzmakil 415 + · plmznxvy 416 + · ihefghyy 417 + 418 + ~ 419 + &lt;/code&gt;&lt;/pre&gt; 420 + &lt;p&gt;This alias was stolen from this &lt;a href="https://gist.github.com/thoughtpolice/8f2fd36ae17cd11b8e7bd93a70e31ad6#file-jjconfig-toml"&gt;wonderful 421 + gist&lt;/a&gt; 422 + by &lt;a href="https://github.com/jj-vcs/jj/blob/main/docs/testimonials.md?plain=1#L120"&gt;Austin 423 + Seipp&lt;/a&gt;.&lt;/p&gt; 424 + &lt;h3 id="experimental-features"&gt;Experimental features&lt;/h3&gt; 425 + &lt;p&gt;I use one experimental feature, that is only available on 426 + some of the newer versions of jujutsu:&lt;/p&gt; 427 + &lt;pre&gt;&lt;code class="language-bash"&gt;# built from master 428 + λ jj version 429 + jj 0.29.0-8c7ca30074767257d75e3842581b61e764d022cf 430 + &lt;/code&gt;&lt;/pre&gt; 431 + &lt;pre&gt;&lt;code class="language-toml"&gt;[git] 432 + write-change-id-header = true 433 + &lt;/code&gt;&lt;/pre&gt; 434 + &lt;p&gt;This writes an extra commit-header (not to be confused with 435 + commit-trailer, which goes directly in the commit body) with 436 + the jujutsu change-id, as seen by inspecting the commit 437 + object:&lt;/p&gt; 438 + &lt;pre&gt;&lt;code class="language-bash"&gt;λ git cat-file commit 4edebe96159bf81c3be662d0a2b4c7b08a062968 439 + tree a9b7e9e3eed8a22c35829bf586d7560ec8396124 440 + parent edc0d2750d4848bc05cfd3255ee1ca916bea9156 441 + author oppiliappan &amp;lt;me@oppi.li&amp;gt; 1747919102 +0100 442 + committer oppiliappan &amp;lt;me@oppi.li&amp;gt; 1747919102 +0100 443 + change-id skrrxvvxlpzrqzpxlxksvryrykpxkvon &amp;lt;-- this 444 + &lt;/code&gt;&lt;/pre&gt; 445 + &lt;p&gt;This allows code forges to extract change-ids from commits, 446 + and most crucially: &lt;strong&gt;allows tracking changes across force 447 + pushes&lt;/strong&gt;. This enables the &lt;a href="https://gist.github.com/thoughtpolice/9c45287550a56b2047c6311fbadebed2"&gt;interdiff 448 + format&lt;/a&gt; 449 + of code-review. Stacking, commit-wise-review, and interdiff 450 + are all supported on &lt;a href="https://tangled.sh"&gt;tangled&lt;/a&gt; (you can 451 + read more 452 + &lt;a href="https://bsky.app/profile/tangled.sh/post/3lptwcb47kc2u"&gt;here&lt;/a&gt;), 453 + if you submit a branch with this experimental feature 454 + enabled.&lt;/p&gt; 455 + &lt;h3 id="misc"&gt;Misc&lt;/h3&gt; 456 + &lt;p&gt;Couple of other quality of life additions:&lt;/p&gt; 457 + &lt;pre&gt;&lt;code class="language-toml"&gt;[ui] 458 + default-command = &amp;quot;status&amp;quot; 459 + pager = &amp;quot;delta&amp;quot; 460 + &lt;/code&gt;&lt;/pre&gt; 461 + &lt;p&gt;I also use nix and home-manager to configure jj, you can 462 + find my configuration &lt;a href="https://plonk.li/r/YQ"&gt;here&lt;/a&gt;.&lt;/p&gt; 463 + </description> 464 + <link>https://oppi.li/posts/configuring_jujutsu/</link> 465 + <pubDate>Sat, 24 May 2025 00:00:00 +0000</pubDate> 466 + <guid>https://oppi.li/posts/configuring_jujutsu/</guid> 467 + </item> 468 + <item> 469 + <title>tales from mainframe modernization</title> 470 + <description>&lt;p&gt;At my last workplace, I wrote transpilers (or just 471 + &lt;a href="https://people.csail.mit.edu/rachit/post/transpiler/"&gt;compilers&lt;/a&gt; 472 + if you prefer) from mainframe languages (COBOL, JCL, BASIC etc.) to 473 + Java (in Rust!).&lt;/p&gt; 474 + &lt;p&gt;Legacy code is full of surprises. In the roughly 200k lines 475 + of COBOL that I had the (dis)pleasure of working with, I saw 476 + some wonderful hacks to get around the limitations of the 477 + system. Mainframes are also chock full of history.&lt;/p&gt; 478 + &lt;h3 id="base-10-numerics"&gt;Base-10 numerics&lt;/h3&gt; 479 + &lt;p&gt;This is the first thing that stood out to me when I looked 480 + at COBOL code, a data-definition (the phrase for &amp;quot;variable&amp;quot;) 481 + in COBOL is declared like so:&lt;/p&gt; 482 + &lt;pre&gt;&lt;code&gt; ,-- name 483 + | ,- type 484 + __|___ __|_ 485 + 01 HEIGHT PIC 9(3). 486 + -- --- 487 + | | 488 + | `- picture clause (keyword) 489 + `- level number 490 + &lt;/code&gt;&lt;/pre&gt; 491 + &lt;p&gt;That statement declares a variable called &lt;code&gt;HEIGHT&lt;/code&gt; with type 492 + &lt;code&gt;9(3)&lt;/code&gt;, which is shorthand for &lt;code&gt;999&lt;/code&gt;, which indicates 493 + &amp;quot;3-digit number&amp;quot;. The possible values for this variable are 494 + &lt;code&gt;0&lt;/code&gt; to &lt;code&gt;999&lt;/code&gt;!&lt;/p&gt; 495 + &lt;h3 id="internationalisation"&gt;Internationalisation&lt;/h3&gt; 496 + &lt;p&gt;Below is another data-definition in COBOL, declaring 3 497 + variables:&lt;/p&gt; 498 + &lt;pre&gt;&lt;code class="language-cobol"&gt;01 FOO-PERSON. 499 + 05 FOO-NAME PIC X(5). 500 + 05 FOO-HEIGHT PIC 9(3). 501 + &lt;/code&gt;&lt;/pre&gt; 502 + &lt;p&gt;What that means is:&lt;/p&gt; 503 + &lt;ul&gt; 504 + &lt;li&gt;&lt;code&gt;FOO-PERSON&lt;/code&gt;: a &amp;quot;group&amp;quot; variable consisting of two other 505 + variables 506 + &lt;/li&gt; 507 + &lt;li&gt;&lt;code&gt;FOO-NAME&lt;/code&gt;: an alphanumeric type with 5 characters 508 + &lt;/li&gt; 509 + &lt;li&gt;&lt;code&gt;FOO-HEIGHT&lt;/code&gt;: a numeric type with 3 digits (remember, base 10 510 + and not base 2) 511 + &lt;/li&gt; 512 + &lt;/ul&gt; 513 + &lt;p&gt;COBOL has an interesting construct called &amp;quot;REDEFINES&amp;quot;:&lt;/p&gt; 514 + &lt;pre&gt;&lt;code class="language-cobol"&gt;01 FOO-PERSON. 515 + 05 FOO-NAME PIC X(5). 516 + 05 FOO-HEIGHT PIC 9(3). 517 + 518 + 01 FOO-PERSONNE REDEFINES FOO-PERSON. 519 + 05 FOO-NOM PIC X(5). 520 + 05 FOO-TAILLE PIC 9(3). 521 + &lt;/code&gt;&lt;/pre&gt; 522 + &lt;p&gt;&lt;code&gt;FOO-PERSON&lt;/code&gt; and &lt;code&gt;FOO-PERSONNE&lt;/code&gt; refer to the same region of 523 + memory.&lt;/p&gt; 524 + &lt;p&gt;I helped modernise a codebase that had clearly been worked 525 + on by a Spanish consultancy at some point, and they had 526 + decided to redefine all data definitions in Spanish.&lt;/p&gt; 527 + &lt;h3 id="string-parsing"&gt;String parsing&lt;/h3&gt; 528 + &lt;p&gt;Here's another fun one:&lt;/p&gt; 529 + &lt;pre&gt;&lt;code class="language-cobol"&gt; 01 FOO-PERSON. 530 + 05 FOO-NAME PIC X(5). 531 + 05 FOO-HEIGHT PIC 9(3). 532 + . 533 + . 534 + . 535 + 536 + MOVE &amp;quot;PETER&amp;quot; TO FOO-NAME. 537 + MOVE 175 TO FOO-HEIGHT. 538 + 539 + *&amp;gt; display the entire memory region 540 + DISPLAY FOO-PERSON. 541 + *&amp;gt; PETER175 542 + 543 + *&amp;gt; subscripting the first 7 bytes... 544 + DISPLAY FOO-PERSON (1:7) 545 + *&amp;gt; PETER17 546 + &lt;/code&gt;&lt;/pre&gt; 547 + &lt;p&gt;So data-definitions simply describe names for regions. Which 548 + enables a clever way to parse strings:&lt;/p&gt; 549 + &lt;pre&gt;&lt;code class="language-cobol"&gt; 01 DATE. 550 + 05 DD PIC 9(2). 551 + 05 FILLER PIC X. 552 + 05 MMM PIC A(3). 553 + 05 FILLER PIC X. 554 + 05 YYYY PIC 9(4). 555 + 556 + . 557 + . 558 + . 559 + 560 + MOVE &amp;quot;03 MAR 2025&amp;quot; TO DATE. 561 + DISPLAY &amp;quot;DAY: &amp;quot; DD. *&amp;gt; DAY: 03 562 + DISPLAY &amp;quot;MONTH: &amp;quot; MMM. *&amp;gt; MONTH: MAR 563 + DISPLAY &amp;quot;YEAR: &amp;quot; YYYY. *&amp;gt; YEAR: 2025 564 + 565 + *&amp;gt; also works: 566 + MOVE &amp;quot;03-MAR-2025&amp;quot; TO DATE. 567 + &lt;/code&gt;&lt;/pre&gt; 568 + &lt;h3 id="early-exit"&gt;Early exit&lt;/h3&gt; 569 + &lt;p&gt;I'd see this peppered around in a few places; which I later 570 + realized was a way to trigger an abnormal end to a batch job 571 + (possibly triggering an error handling routine in the outer 572 + job control system):&lt;/p&gt; 573 + &lt;pre&gt;&lt;code class="language-cobol"&gt; 01 CONSTANT-ZERO S9(9)V9 VALUE 0. 574 + 01 ABEND S9(9)V9. 575 + 576 + . 577 + . 578 + . 579 + 580 + COMPUTE ABEND = CONSTANT-ZERO / CONSTANT-ZERO. 581 + &lt;/code&gt;&lt;/pre&gt; 582 + &lt;h3 id="all-the-numbers"&gt;All the numbers&lt;/h3&gt; 583 + &lt;p&gt;I have yet to find an explanation for this one, but I once 584 + found a file with just the first 800 natural numbers defined 585 + as string constants:&lt;/p&gt; 586 + &lt;pre&gt;&lt;code class="language-cobol"&gt; 01 TC0001 X(5) &amp;quot;00001&amp;quot;. 587 + 01 TC0002 X(5) &amp;quot;00002&amp;quot;. 588 + 01 TC0003 X(5) &amp;quot;00003&amp;quot;. 589 + . 590 + . 591 + *&amp;gt; .... 800 lines later .... 592 + . 593 + . 594 + 01 TC0800 X(5) &amp;quot;00800&amp;quot;. 595 + &lt;/code&gt;&lt;/pre&gt; 596 + &lt;p&gt;The file was definitely not generated, and I can't imagine 597 + text editors on the mainframe were all that advanced either.&lt;/p&gt; 598 + &lt;h3 id="dd---disk-destroyer"&gt;&lt;code&gt;dd&lt;/code&gt; - disk destroyer&lt;/h3&gt; 599 + &lt;p&gt;The &lt;code&gt;DD&lt;/code&gt; statement in the JCL subsystem stands for &amp;quot;data 600 + definition&amp;quot;, which is largely used to describe files and IO 601 + streams used by a batch job. The &lt;code&gt;dd&lt;/code&gt; command [^dd] on UNIX is 602 + named after this statement!&lt;/p&gt; 603 + &lt;p&gt;[^dd]: &lt;a href="https://en.wikipedia.org/wiki/Dd_%28Unix%29#History"&gt;Wikipedia - dd (Unix)&lt;/a&gt;&lt;/p&gt; 604 + </description> 605 + <link>https://oppi.li/posts/tales_from_mainframe_modernization/</link> 606 + <pubDate>Wed, 21 May 2025 00:00:00 +0000</pubDate> 607 + <guid>https://oppi.li/posts/tales_from_mainframe_modernization/</guid> 608 + </item> 609 + <item> 610 + <title>OSC-52</title> 611 + <description>&lt;p&gt;I use &lt;code&gt;ssh&lt;/code&gt; a lot. Copying text from the remote machine to 612 + the host machine always sucked. But OSC-52 makes that easy.&lt;/p&gt; 613 + &lt;p&gt;OSC-52 is an ANSI escape sequence to write text to the 614 + terminal emulator. The terminal emulator, if it understands 615 + what is going on, will in turn write this text to the system 616 + clipboard.&lt;/p&gt; 617 + &lt;p&gt;What this means is some &lt;code&gt;printf&lt;/code&gt; magic can send text to your 618 + clipboard. I store this one-liner in a script called 619 + &lt;code&gt;oclip&lt;/code&gt;:&lt;/p&gt; 620 + &lt;pre&gt;&lt;code class="language-bash"&gt;printf &amp;quot;\033]52;c;%s\007&amp;quot; &amp;quot;$(base64 &amp;lt;&amp;amp;0)&amp;quot; 621 + &lt;/code&gt;&lt;/pre&gt; 622 + &lt;p&gt;and I run it with:&lt;/p&gt; 623 + &lt;pre&gt;&lt;code class="language-bash"&gt;remote $ cat some_file.txt | oclip 624 + 625 + # some_file.txt's contents are now the host's clipboard 626 + &lt;/code&gt;&lt;/pre&gt; 627 + &lt;h3 id="the-catch"&gt;The catch&lt;/h3&gt; 628 + &lt;p&gt;Your terminal emulator must support OSC-52, &lt;code&gt;alacritty&lt;/code&gt; and 629 + &lt;code&gt;termux&lt;/code&gt; seem to support this out of the box. In &lt;code&gt;st&lt;/code&gt;, 630 + OSC-52 works with this change to &lt;code&gt;config.h&lt;/code&gt;:&lt;/p&gt; 631 + &lt;pre&gt;&lt;code&gt;int allowwindowops = 1; 632 + &lt;/code&gt;&lt;/pre&gt; 633 + &lt;p&gt;If you are using &lt;code&gt;tmux&lt;/code&gt;, you need to flip this switch on:&lt;/p&gt; 634 + &lt;pre&gt;&lt;code&gt;set -s set-clipboard on 635 + &lt;/code&gt;&lt;/pre&gt; 636 + &lt;p&gt;If you are inside &lt;code&gt;nvim&lt;/code&gt;, it may work as expected as long as 637 + &lt;code&gt;$SSH_TTY&lt;/code&gt; is set. I sometimes physically start a session, 638 + and &lt;code&gt;ssh&lt;/code&gt; into the same session later from another machine, 639 + and &lt;code&gt;$SSH_TTY&lt;/code&gt; remains unset, so I force OSC-52 in &lt;code&gt;nvim&lt;/code&gt; at 640 + all times (see 641 + &lt;a href="https://neovim.io/doc/user/provider.html#clipboard-osc52"&gt;nvimdoc&lt;/a&gt;):&lt;/p&gt; 642 + &lt;pre&gt;&lt;code class="language-lua"&gt;vim.g.clipboard = { 643 + name = 'OSC 52', 644 + copy = { 645 + ['+'] = require('vim.ui.clipboard.osc52').copy('+'), 646 + ['*'] = require('vim.ui.clipboard.osc52').copy('*'), 647 + }, 648 + paste = { 649 + ['+'] = require('vim.ui.clipboard.osc52').paste('+'), 650 + ['*'] = require('vim.ui.clipboard.osc52').paste('*'), 651 + }, 652 + } 653 + &lt;/code&gt;&lt;/pre&gt; 654 + &lt;p&gt;If you are inside &lt;code&gt;nvim&lt;/code&gt; inside &lt;code&gt;tmux&lt;/code&gt; inside an &lt;code&gt;ssh&lt;/code&gt; 655 + session inside &lt;code&gt;st&lt;/code&gt;, you neeed all of the above tweaks. 656 + &lt;code&gt;nvim&lt;/code&gt; will pass the contents around to &lt;code&gt;tmux&lt;/code&gt;, which in 657 + turn will pass the contents to &lt;code&gt;st&lt;/code&gt;, which should pass it to 658 + your system clipboard.&lt;/p&gt; 659 + </description> 660 + <link>https://oppi.li/posts/OSC-52/</link> 661 + <pubDate>Wed, 27 Nov 2024 00:00:00 +0000</pubDate> 662 + <guid>https://oppi.li/posts/OSC-52/</guid> 663 + </item> 664 + <item> 665 + <title>introducing tablespoon</title> 666 + <description>&lt;p&gt;&lt;a href="https://git.peppe.rs/languages/tbsp"&gt;tbsp&lt;/a&gt; (tree-based 667 + source-processing language) is an awk-like language that 668 + operates on tree-sitter syntax trees. To motivate the need 669 + for such a program, we could begin by writing a 670 + markdown-to-html converter using &lt;code&gt;tbsp&lt;/code&gt; and 671 + &lt;a href="https://github.com/tree-sitter-grammars/tree-sitter-markdown"&gt;tree-sitter-md&lt;/a&gt;. 672 + We need some markdown to begin with:&lt;/p&gt; 673 + &lt;pre&gt;&lt;code&gt;# 1 heading 674 + 675 + content of first paragraph 676 + 677 + ## 1.1 heading 678 + 679 + content of nested paragraph 680 + &lt;/code&gt;&lt;/pre&gt; 681 + &lt;p&gt;For future reference, this markdown is parsed like so by 682 + tree-sitter-md (visualization generated by 683 + &lt;a href="https://git.peppe.rs/cli/tree-viz"&gt;tree-viz&lt;/a&gt;):&lt;/p&gt; 684 + &lt;pre&gt;&lt;code&gt;document 685 + | section 686 + | | atx_heading 687 + | | | atx_h1_marker &amp;quot;#&amp;quot; 688 + | | | heading_content inline &amp;quot;1 heading&amp;quot; 689 + | | paragraph 690 + | | | inline &amp;quot;content of first paragraph&amp;quot; 691 + | | section 692 + | | | atx_heading 693 + | | | | atx_h2_marker &amp;quot;##&amp;quot; 694 + | | | | heading_content inline &amp;quot;1.1 heading&amp;quot; 695 + | | | paragraph 696 + | | | | inline &amp;quot;content of nested paragraph&amp;quot; 697 + &lt;/code&gt;&lt;/pre&gt; 698 + &lt;p&gt;Onto the converter itself. Every &lt;code&gt;tbsp&lt;/code&gt; program is written as 699 + a collection of stanzas. Typically, we start with a stanza 700 + like so:&lt;/p&gt; 701 + &lt;pre&gt;&lt;code&gt;BEGIN { 702 + int depth = 0; 703 + 704 + print(&amp;quot;&amp;lt;html&amp;gt;\n&amp;quot;); 705 + print(&amp;quot;&amp;lt;body&amp;gt;\n&amp;quot;); 706 + } 707 + &lt;/code&gt;&lt;/pre&gt; 708 + &lt;p&gt;The stanza begins with a &amp;quot;pattern&amp;quot;, in this case, &lt;code&gt;BEGIN&lt;/code&gt;, 709 + and is followed a block of code. This block specifically, is 710 + executed right at the beginning, before traversing the parse 711 + tree. In this stanza, we set a &amp;quot;depth&amp;quot; variable to keep 712 + track of nesting of markdown headers, and begin our html 713 + document by printing the &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; tags.&lt;/p&gt; 714 + &lt;p&gt;We can follow this stanza with an &lt;code&gt;END&lt;/code&gt; stanza, that is 715 + executed after the traversal:&lt;/p&gt; 716 + &lt;pre&gt;&lt;code&gt;END { 717 + print(&amp;quot;&amp;lt;/body&amp;gt;\n&amp;quot;); 718 + print(&amp;quot;&amp;lt;/html&amp;gt;\n&amp;quot;); 719 + } 720 + &lt;/code&gt;&lt;/pre&gt; 721 + &lt;p&gt;In this stanza, we close off the tags we opened at the start 722 + of the document. We can move onto the interesting bits of 723 + the conversion now:&lt;/p&gt; 724 + &lt;pre&gt;&lt;code&gt;enter section { 725 + depth += 1; 726 + } 727 + leave section { 728 + depth -= 1; 729 + } 730 + &lt;/code&gt;&lt;/pre&gt; 731 + &lt;p&gt;The above stanzas begin with &lt;code&gt;enter&lt;/code&gt; and &lt;code&gt;leave&lt;/code&gt; clauses, 732 + followed by the name of a tree-sitter node kind: &lt;code&gt;section&lt;/code&gt;. 733 + The &lt;code&gt;section&lt;/code&gt; identifier is visible in the 734 + tree-visualization above, it encompasses a markdown-section, 735 + and is created for every markdown header. To understand how 736 + &lt;code&gt;tbsp&lt;/code&gt; executes above stanzas:&lt;/p&gt; 737 + &lt;pre&gt;&lt;code&gt;document ... depth = 0 738 + | section &amp;lt;-------- enter section (1) ... depth = 1 739 + | | atx_heading 740 + | | | inline 741 + | | paragraph 742 + | | | inline 743 + | | section &amp;lt;----- enter section (2) ... depth = 2 744 + | | | atx_heading 745 + | | | | inline 746 + | | | paragraph 747 + | | | | inline 748 + | | | &amp;lt;----------- leave section (2) ... depth = 1 749 + | | &amp;lt;-------------- leave section (1) ... depth = 0 750 + &lt;/code&gt;&lt;/pre&gt; 751 + &lt;p&gt;The following stanzas should be self-explanatory now:&lt;/p&gt; 752 + &lt;pre&gt;&lt;code&gt;enter atx_heading { 753 + print(&amp;quot;&amp;lt;h&amp;quot;); 754 + print(depth); 755 + print(&amp;quot;&amp;gt;&amp;quot;); 756 + } 757 + leave atx_heading { 758 + print(&amp;quot;&amp;lt;/h&amp;quot;); 759 + print(depth); 760 + print(&amp;quot;&amp;gt;\n&amp;quot;); 761 + } 762 + 763 + enter inline { 764 + print(text(node)); 765 + } 766 + &lt;/code&gt;&lt;/pre&gt; 767 + &lt;p&gt;But an explanation is included nonetheless:&lt;/p&gt; 768 + &lt;pre&gt;&lt;code&gt;document ... depth = 0 769 + | section &amp;lt;-------- enter section (1) ... depth = 1 770 + | | atx_heading &amp;lt;- enter atx_heading ... print &amp;quot;&amp;lt;h1&amp;gt;&amp;quot; 771 + | | | inline &amp;lt;--- enter inline ... print .. 772 + | | | &amp;lt;----------- leave atx_heading ... print &amp;quot;&amp;lt;/h1&amp;gt;&amp;quot; 773 + | | paragraph 774 + | | | inline &amp;lt;--- enter inline ... print .. 775 + | | section &amp;lt;----- enter section (2) ... depth = 2 776 + | | | atx_heading enter atx_heading ... print &amp;quot;&amp;lt;h2&amp;gt;&amp;quot; 777 + | | | | inline &amp;lt;- enter inline ... print .. 778 + | | | | &amp;lt;-------- leave atx_heading ... print &amp;quot;&amp;lt;/h2&amp;gt;&amp;quot; 779 + | | | paragraph 780 + | | | | inline &amp;lt;- enter inline ... print .. 781 + | | | &amp;lt;----------- leave section (2) ... depth = 1 782 + | | &amp;lt;-------------- leave section (1) ... depth = 0 783 + &lt;/code&gt;&lt;/pre&gt; 784 + &lt;p&gt;The 785 + &lt;a href="https://git.peppe.rs/languages/tbsp/tree/examples"&gt;examples&lt;/a&gt; 786 + directory contains a complete markdown-to-html converter, 787 + along with a few other motivating examples.&lt;/p&gt; 788 + &lt;h3 id="usage"&gt;Usage&lt;/h3&gt; 789 + &lt;p&gt;The &lt;code&gt;tbsp&lt;/code&gt; evaluator is written in rust, use cargo to build 790 + and run:&lt;/p&gt; 791 + &lt;pre&gt;&lt;code&gt;cargo build --release 792 + ./target/release/tbsp --help 793 + &lt;/code&gt;&lt;/pre&gt; 794 + &lt;p&gt;&lt;code&gt;tbsp&lt;/code&gt; requires three inputs:&lt;/p&gt; 795 + &lt;ul&gt; 796 + &lt;li&gt;a &lt;code&gt;tbsp&lt;/code&gt; program, referred to as &amp;quot;program file&amp;quot; 797 + &lt;/li&gt; 798 + &lt;li&gt;a language 799 + &lt;/li&gt; 800 + &lt;li&gt;an input file or some input text at stdin 801 + &lt;/li&gt; 802 + &lt;/ul&gt; 803 + &lt;p&gt;You can run the interpreter like so (this program prints an 804 + overview of a rust file):&lt;/p&gt; 805 + &lt;pre&gt;&lt;code&gt;$ ./target/release/tbsp \ 806 + -f./examples/code-overview/overview.tbsp \ 807 + -l rust \ 808 + src/main.rs 809 + module 810 + └╴struct Cli 811 + └╴trait Cli 812 + └╴fn program 813 + └╴fn language 814 + └╴fn file 815 + └╴fn try_consume_stdin 816 + └╴fn main 817 + &lt;/code&gt;&lt;/pre&gt; 818 + </description> 819 + <link>https://oppi.li/posts/introducing_tablespoon/</link> 820 + <pubDate>Thu, 01 Aug 2024 00:00:00 +0000</pubDate> 821 + <guid>https://oppi.li/posts/introducing_tablespoon/</guid> 822 + </item> 823 + <item> 824 + <title>snip snap</title> 825 + <description>&lt;p&gt;I regularly switch between exactly two things while working, 826 + a &amp;quot;current&amp;quot; and an &amp;quot;alternate&amp;quot; item; a lot of tools I use 827 + seem to support this flow.&lt;/p&gt; 828 + &lt;h4 id="git"&gt;git&lt;/h4&gt; 829 + &lt;p&gt;Pass &lt;code&gt;-&lt;/code&gt; to &lt;code&gt;git-checkout&lt;/code&gt; to switch to the previously 830 + active branch:&lt;/p&gt; 831 + &lt;pre&gt;&lt;code class="language-bash"&gt;$ git branch 832 + * foo 833 + bar 834 + 835 + $ git checkout bar 836 + $ git branch 837 + foo 838 + * bar 839 + 840 + $ git checkout - 841 + $ git branch 842 + * foo 843 + bar 844 + &lt;/code&gt;&lt;/pre&gt; 845 + &lt;h4 id="bash---cd"&gt;bash - cd&lt;/h4&gt; 846 + &lt;p&gt;This may not be exclusive to &lt;code&gt;bash&lt;/code&gt;:&lt;/p&gt; 847 + &lt;pre&gt;&lt;code class="language-bash"&gt;~/foo $ cd ~/bar 848 + ~/bar $ cd - 849 + ~/foo $ 850 + &lt;/code&gt;&lt;/pre&gt; 851 + &lt;p&gt;This is especially handy in combination with my &lt;a href="../curing_a_case_of_git-UX/"&gt;git-worktree 852 + flow&lt;/a&gt;:&lt;/p&gt; 853 + &lt;pre&gt;&lt;code class="language-bash"&gt;~/main-branch $ gwj feature 854 + ~/feat-branch $ cd - 855 + ~/main-branch $ 856 + &lt;/code&gt;&lt;/pre&gt; 857 + &lt;h4 id="bash---jobs"&gt;bash - jobs&lt;/h4&gt; 858 + &lt;p&gt;I often suspend multiple &lt;code&gt;vim&lt;/code&gt; sessions with &lt;code&gt;Ctrl-Z&lt;/code&gt;:&lt;/p&gt; 859 + &lt;pre&gt;&lt;code class="language-bash"&gt;$ jobs 860 + [1]+ Stopped vim transpiler/src/transform.rs 861 + [2]- Stopped git commit --verbose 862 + &lt;/code&gt;&lt;/pre&gt; 863 + &lt;p&gt;In the above example: I suspended &lt;code&gt;vim&lt;/code&gt; when working on 864 + &lt;code&gt;transform.rs&lt;/code&gt;, and then began working on a commit by 865 + running &lt;code&gt;git commit&lt;/code&gt; without a message flag (lets you craft 866 + a message in &lt;code&gt;$EDITOR&lt;/code&gt;). To bring the current job to the 867 + foreground, you can use &lt;code&gt;fg&lt;/code&gt;:&lt;/p&gt; 868 + &lt;pre&gt;&lt;code class="language-bash"&gt;$ fg 869 + &lt;/code&gt;&lt;/pre&gt; 870 + &lt;p&gt;With a job identifier:&lt;/p&gt; 871 + &lt;pre&gt;&lt;code class="language-bash"&gt;$ fg %2 # resumes interactive git commit 872 + &lt;/code&gt;&lt;/pre&gt; 873 + &lt;p&gt;Or switch to &amp;quot;last&amp;quot; job, or the second-most-recently-resumed 874 + job:&lt;/p&gt; 875 + &lt;pre&gt;&lt;code class="language-bash"&gt;$ fg %- 876 + $ %- # shorthand 877 + 878 + &lt;/code&gt;&lt;/pre&gt; 879 + &lt;h4 id="vim"&gt;vim&lt;/h4&gt; 880 + &lt;p&gt;Switch to the last active buffer with &lt;code&gt;Ctrl+^&lt;/code&gt;. In 881 + command-mode, &lt;code&gt;#&lt;/code&gt; refers to the last active buffer, you can 882 + use this as an argument to a few commands:&lt;/p&gt; 883 + &lt;pre&gt;&lt;code class="language-vimscript"&gt;:b# &amp;quot; switch to alternate buffer (same as Ctrl+^) 884 + :vsp# &amp;quot; create a vertical split with the alternate buffer 885 + :read# &amp;quot; read contents of alternate buffer into current buffer 886 + :!wc # &amp;quot; pass file name of alternate buffer to the command `wc` 887 + &lt;/code&gt;&lt;/pre&gt; 888 + &lt;p&gt;See &lt;code&gt;:help c_#&lt;/code&gt; for more.&lt;/p&gt; 889 + &lt;h4 id="tmux"&gt;tmux&lt;/h4&gt; 890 + &lt;p&gt;Switch to the last active tmux session with 891 + &lt;code&gt;&amp;lt;prefix&amp;gt;+shift+L&lt;/code&gt;.&lt;/p&gt; 892 + &lt;h4 id="qutebrowser"&gt;qutebrowser&lt;/h4&gt; 893 + &lt;p&gt;Switch to the last active tab with &lt;code&gt;g$&lt;/code&gt;.&lt;/p&gt; 894 + </description> 895 + <link>https://oppi.li/posts/snip_snap/</link> 896 + <pubDate>Wed, 29 May 2024 00:00:00 +0000</pubDate> 897 + <guid>https://oppi.li/posts/snip_snap/</guid> 898 + </item> 899 + <item> 900 + <title>plain text journaling</title> 901 + <description>&lt;p&gt;I cobbled together a journaling system with {neo,}vim, 902 + coreutils and &lt;a href="http://www.fresse.org/dateutils"&gt;dateutils&lt;/a&gt;. 903 + This system is loosely based on &lt;a href="https://www.rydercarroll.com/"&gt;Ryder 904 + Caroll's&lt;/a&gt; Bullet Journal 905 + method.&lt;/p&gt; 906 + &lt;p&gt;&lt;a href="https://cdn.oppi.li/SpF.png"&gt;&lt;img src="https://cdn.oppi.li/SpF.png" alt="" /&gt;&lt;/a&gt;&lt;/p&gt; 907 + &lt;h3 id="the-format"&gt;The format&lt;/h3&gt; 908 + &lt;p&gt;The journal for a given year is a directory:&lt;/p&gt; 909 + &lt;pre&gt;&lt;code class="language-bash"&gt;λ ls journal/ 910 + 2022/ 2023/ 911 + &lt;/code&gt;&lt;/pre&gt; 912 + &lt;p&gt;In each directory are 12 files, one for each month of the 913 + year, numbered like so:&lt;/p&gt; 914 + &lt;pre&gt;&lt;code class="language-bash"&gt;λ ls journal/2023/ 915 + 01 02 03 04 05 06 07 08 09 10 11 12 916 + &lt;/code&gt;&lt;/pre&gt; 917 + &lt;p&gt;We can now begin writing stuff down:&lt;/p&gt; 918 + &lt;pre&gt;&lt;code class="language-bash"&gt;λ vim journal/2023/1 919 + &lt;/code&gt;&lt;/pre&gt; 920 + &lt;p&gt;Every month must start with a calendar of course, fill that 921 + in with:&lt;/p&gt; 922 + &lt;pre&gt;&lt;code class="language-vim"&gt;:read !cal -m 923 + &lt;/code&gt;&lt;/pre&gt; 924 + &lt;p&gt;Your entry for January might look like this:&lt;/p&gt; 925 + &lt;pre&gt;&lt;code class="language-bash"&gt;λ cat journal/2023/01 926 + January 2023 927 + Mo Tu We Th Fr Sa Su 928 + 1 929 + 2 3 4 5 6 7 8 930 + 9 10 11 12 13 14 15 931 + 16 17 18 19 20 21 22 932 + 23 24 25 26 27 28 29 933 + 30 31 934 + &lt;/code&gt;&lt;/pre&gt; 935 + &lt;p&gt;I prefer planning week by week, as opposed to creating a 936 + task-list every day, here's what I have for the first couple 937 + of weeks:&lt;/p&gt; 938 + &lt;pre&gt;&lt;code&gt; January 2023 939 + Mo Tu We Th Fr Sa Su 940 + 1 941 + 2 3 4 5 6 7 8 942 + 9 10 11 12 13 14 15 943 + 16 17 18 19 20 21 22 944 + 23 24 25 26 27 28 29 945 + 30 31 946 + 947 + 948 + week 1 949 + 950 + done apply leaves 951 + done dload boarding pass 952 + moved reply to dan 953 + 954 + 955 + week 2 956 + 957 + todo reply to dan 958 + todo pack bags 959 + done travel insurance 960 + todo weigh luggage 961 + &lt;/code&gt;&lt;/pre&gt; 962 + &lt;p&gt;I start the week by writing a header and each item that week 963 + is placed on its own line. The items are prefixed with a 964 + &lt;code&gt;todo&lt;/code&gt; or a &lt;code&gt;done&lt;/code&gt; signifier.&lt;/p&gt; 965 + &lt;h3 id="form-over-function"&gt;Form over function&lt;/h3&gt; 966 + &lt;p&gt;Right off the bat, the signifiers look very noisy, Even more 967 + so once we start introducing variety (I use &amp;quot;event&amp;quot;, &amp;quot;note&amp;quot; 968 + and &amp;quot;moved&amp;quot;):&lt;/p&gt; 969 + &lt;pre&gt;&lt;code&gt;week 1 970 + 971 + todo apply leaves 972 + done dload boarding pass 973 + todo reply to dan 974 + event fr trip 975 + note weight 68.6 976 + &lt;/code&gt;&lt;/pre&gt; 977 + &lt;p&gt;We can clean this up with &amp;quot;abbreviations&amp;quot; (&lt;code&gt;:h abbreviations&lt;/code&gt;):&lt;/p&gt; 978 + &lt;pre&gt;&lt;code class="language-vim"&gt;:iabbrev todo · 979 + :iabbrev done × 980 + &lt;/code&gt;&lt;/pre&gt; 981 + &lt;p&gt;Now, typing this:&lt;/p&gt; 982 + &lt;pre&gt;&lt;code&gt;todo apply leaves 983 + &lt;/code&gt;&lt;/pre&gt; 984 + &lt;p&gt;Automatically inserts:&lt;/p&gt; 985 + &lt;pre&gt;&lt;code&gt;· apply leaves 986 + &lt;/code&gt;&lt;/pre&gt; 987 + &lt;p&gt;You can use &lt;code&gt;x&lt;/code&gt; and &lt;code&gt;o&lt;/code&gt; as well, but &lt;code&gt;×&lt;/code&gt; (U+00D7, 988 + MULTIPLICATION SIGN) and &lt;code&gt;·&lt;/code&gt; (U+00B7, MIDDLE DOT) are more 989 + ... &lt;em&gt;gourmet&lt;/em&gt;.&lt;/p&gt; 990 + &lt;p&gt;The other signifiers I use are:&lt;/p&gt; 991 + &lt;ul&gt; 992 + &lt;li&gt;&lt;code&gt;-&lt;/code&gt; for note 993 + &lt;/li&gt; 994 + &lt;li&gt;&lt;code&gt;o&lt;/code&gt; for event 995 + &lt;/li&gt; 996 + &lt;li&gt;&lt;code&gt;&amp;gt;&lt;/code&gt; for moved. 997 + &lt;/li&gt; 998 + &lt;/ul&gt; 999 + &lt;p&gt;Nit #2 is the lack of order. We can employ vim to introduce 1000 + grouping and sorting. Select the list of entries for this 1001 + week:&lt;/p&gt; 1002 + &lt;pre&gt;&lt;code class="language-vim"&gt;vip &amp;quot; line-wise select inner paragraph 1003 + :'&amp;lt;,'&amp;gt;sort &amp;quot; the markers '&amp;lt; and '&amp;gt; are automatically inserted, 1004 + &amp;quot; they mark the start and end of the selection 1005 + &lt;/code&gt;&lt;/pre&gt; 1006 + &lt;p&gt;We end up with:&lt;/p&gt; 1007 + &lt;pre&gt;&lt;code&gt;week 1 1008 + 1009 + · apply leaves 1010 + · reply to dan 1011 + × dload boarding pass 1012 + &lt;/code&gt;&lt;/pre&gt; 1013 + &lt;p&gt;The lines are grouped by their signifiers, segregating todo 1014 + items from completed items. Luckily, MIDDLE DOT is lesser 1015 + than MULTIPLICATION SIGN, so todo items are placed at the 1016 + top. The same goes for &lt;code&gt;o&lt;/code&gt; and &lt;code&gt;x&lt;/code&gt; symbols, either set of 1017 + signifiers will result in the same sorting order.&lt;/p&gt; 1018 + &lt;p&gt;We can shorten this select-paragraph-invoke-sort dance by 1019 + setting the &lt;code&gt;formatprg&lt;/code&gt; variable:&lt;/p&gt; 1020 + &lt;pre&gt;&lt;code class="language-vim"&gt;:set formatprg=sort\ -V 1021 + &lt;/code&gt;&lt;/pre&gt; 1022 + &lt;p&gt;Now, hitting &lt;code&gt;gqip&lt;/code&gt; should automatically group and sort the 1023 + items for the week under the cursor, moving todo items to 1024 + the top. Finding signifier glyphs that suit your sorting 1025 + preference is a fun exercise.&lt;/p&gt; 1026 + &lt;h3 id="syntax-highlighting"&gt;Syntax highlighting&lt;/h3&gt; 1027 + &lt;p&gt;Adding color to items introduces another layer of visual 1028 + distinction. In truth, I like to deck it out just because.&lt;/p&gt; 1029 + &lt;p&gt;First, create a few syntax groups:&lt;/p&gt; 1030 + &lt;pre&gt;&lt;code class="language-vim"&gt;:syntax match JournalAll /.*/ &amp;quot; captures the entire buffer 1031 + :syntax match JournalDone /^×.*/ &amp;quot; lines containing 'done' items: × 1032 + :syntax match JournalTodo /^·.*/ &amp;quot; lines containing 'todo' items: · 1033 + :syntax match JournalEvent /^o.*/ &amp;quot; lines containing 'event' items: o 1034 + :syntax match JournalNote /^- .*/ &amp;quot; lines containing 'note' items: - 1035 + :syntax match JournalMoved /^&amp;gt;.*/ &amp;quot; lines containing 'moved' items: &amp;gt; 1036 + &lt;/code&gt;&lt;/pre&gt; 1037 + &lt;p&gt;Add highlights to each group:&lt;/p&gt; 1038 + &lt;pre&gt;&lt;code class="language-vim"&gt;:highlight JournalAll ctermfg=12 &amp;quot; bright black 1039 + :highlight JournalDone ctermfg=12 &amp;quot; bright black 1040 + :highlight JournalEvent ctermfg=6 &amp;quot; cyan 1041 + :highlight JournalMoved ctermfg=5 &amp;quot; magenta 1042 + :highlight JournalNote ctermfg=3 &amp;quot; yellow 1043 + &lt;/code&gt;&lt;/pre&gt; 1044 + &lt;p&gt;In my terminal, this is rendered like so:&lt;/p&gt; 1045 + &lt;p&gt;&lt;a href="https://u.peppe.rs/Du6.png"&gt;&lt;img src="https://u.peppe.rs/Du6.png" alt="" /&gt;&lt;/a&gt;&lt;/p&gt; 1046 + &lt;h3 id="habit-tracking"&gt;Habit tracking&lt;/h3&gt; 1047 + &lt;p&gt;While this is not a part of my journaling system anymore, a 1048 + few headers and an awk script is all it takes to track 1049 + habits. My weekly entries would include a couple of habit 1050 + headers like so:&lt;/p&gt; 1051 + &lt;pre&gt;&lt;code&gt;week 1 -------------- 1052 + 1053 + × wake up on time 1054 + × water the plants 1055 + 1056 + spend 7.5 7 10 1057 + --------------------- 1058 + 1059 + 1060 + week 2 -------------- 1061 + 1062 + · make the bed 1063 + · go to bed 1064 + 1065 + spend 30 2.75 6 1066 + --------------------- 1067 + &lt;/code&gt;&lt;/pre&gt; 1068 + &lt;p&gt;Here, under the &lt;code&gt;spend&lt;/code&gt; header in week 1, are a list of 1069 + expenditures accumulated over the week. The monthly spend is 1070 + calculated with this awk script:&lt;/p&gt; 1071 + &lt;pre&gt;&lt;code class="language-awk"&gt;BEGIN {spend=0;} 1072 + /spend/ {for(i=1;i&amp;lt;=$NF;i++) spend+=$i;} 1073 + END { printf spend &amp;quot;eur&amp;quot;} 1074 + &lt;/code&gt;&lt;/pre&gt; 1075 + &lt;p&gt;And invoked like so:&lt;/p&gt; 1076 + &lt;pre&gt;&lt;code&gt;λ awk -f spend.awk journal/2023/01 1077 + 63.25eur 1078 + &lt;/code&gt;&lt;/pre&gt; 1079 + &lt;h3 id="reflection"&gt;Reflection&lt;/h3&gt; 1080 + &lt;p&gt;Journaling is not just about planning what is to come, but 1081 + also reflecting on what has passed. It would make sense to 1082 + simultaneously look at the past few weeks' entries while 1083 + making your current one. To open multiple months of entries 1084 + at the same time:&lt;/p&gt; 1085 + &lt;pre&gt;&lt;code&gt;λ vim -O journal/2023/0{1,2,3} 1086 + &lt;/code&gt;&lt;/pre&gt; 1087 + &lt;p&gt;Opens 3 months, side-by-side, in vertical splits:&lt;/p&gt; 1088 + &lt;pre&gt;&lt;code&gt;JANUARY ------------ │ FEBRUARY ----------- │ MARCH -------------- 1089 + │ │ 1090 + Mo Tu We Th Fr Sa Su │ Mo Tu We Th Fr Sa Su │ Mo Tu We Th Fr Sa Su 1091 + 1 │ 1 2 3 4 5 │ 1 2 3 4 5 1092 + 2 3 4 5 6 7 8 │ 6 7 8 9 10 11 12 │ 6 7 8 9 10 11 12 1093 + 9 10 11 12 13 14 15 │ 13 14 15 16 17 18 19 │ 13 14 15 16 17 18 19 1094 + 16 17 18 19 20 21 22 │ 20 21 22 23 24 25 26 │ 20 21 22 23 24 25 26 1095 + 23 24 25 26 27 28 29 │ 27 28 │ 27 28 29 30 31 1096 + 30 31 │ │ 1097 + │ │ 1098 + │ │ 1099 + WEEK 1 ------------- │ WEEK 1 ------------- │ WEEK 1 ------------- 1100 + │ │ 1101 + &amp;gt; latex setup │ &amp;gt; forex │ - weight: 64 1102 + × make the bed │ × clean shoes │ &amp;gt; close sg-pr 1103 + × 03: dentist │ × buy clothes │ × facewash 1104 + × integrate tsg │ × draw │ × groceries 1105 + │ │ 1106 + │ │ 1107 + WEEK 2 ------------- │ WEEK 2 ------------- │ WEEK 2 ------------- 1108 + │ │ 1109 + × latex setup │ - viral fever │ &amp;gt; close sg-pr 1110 + × send invoice │ × forex │ × plan meet 1111 + × stack-graph pr │ × activate sim │ × sg storage 1112 + │ × bitlbee │ 1113 + &lt;/code&gt;&lt;/pre&gt; 1114 + &lt;h3 id="reducing-friction"&gt;Reducing friction&lt;/h3&gt; 1115 + &lt;p&gt;Journaling already requires a solid amount of discipline and 1116 + consistency. The added friction of typing &lt;code&gt;vim journal/$CURRENT_YEAR/$CURRENT_MONTH&lt;/code&gt; each time is doing no 1117 + favors.&lt;/p&gt; 1118 + &lt;p&gt;To open the current month based on system time:&lt;/p&gt; 1119 + &lt;pre&gt;&lt;code class="language-bash"&gt;λ vim $(date +&amp;quot;%Y/%m&amp;quot;) 1120 + &lt;/code&gt;&lt;/pre&gt; 1121 + &lt;p&gt;To open all the months within a 2 month window of today, is 1122 + a little trickier. The command we wish to generate is (if 1123 + today is 2023/12):&lt;/p&gt; 1124 + &lt;pre&gt;&lt;code class="language-bash"&gt;λ vim -O 2023/10 2023/11 2023/12 2024/01 2024/02 1125 + &lt;/code&gt;&lt;/pre&gt; 1126 + &lt;p&gt;And that is where &lt;code&gt;dateseq&lt;/code&gt; from 1127 + &lt;a href="http://www.fresse.org/dateutils"&gt;dateutils&lt;/a&gt; comes in handy, 1128 + for example:&lt;/p&gt; 1129 + &lt;pre&gt;&lt;code class="language-bash"&gt;λ dateseq 2012-02-01 2012-03-01 1130 + 2012-02-01 1131 + 2012-02-02 1132 + 2012-02-03 1133 + ... 1134 + 2012-02-28 1135 + 2012-02-29 1136 + 2012-03-01 1137 + &lt;/code&gt;&lt;/pre&gt; 1138 + &lt;p&gt;This script opens all months within a 2 month window of 1139 + today:&lt;/p&gt; 1140 + &lt;pre&gt;&lt;code class="language-bash"&gt;λ vim -O $( 1141 + dateseq \ 1142 + &amp;quot;$(date --date &amp;quot;2 months ago&amp;quot; +%Y/%m)&amp;quot; \ 1143 + &amp;quot;$(date --date &amp;quot;2 months&amp;quot; +%Y/%m)&amp;quot; \ 1144 + -i %Y/%m \ 1145 + -f %Y/%m 1146 + ) 1147 + &lt;/code&gt;&lt;/pre&gt; 1148 + &lt;h3 id="fin"&gt;Fin&lt;/h3&gt; 1149 + &lt;p&gt;You can find a sample vimrc file here: 1150 + &lt;a href="https://git.peppe.rs/cli/journal/tree"&gt;cli/journal&lt;/a&gt;, along 1151 + with a nix flake file to kick things off.&lt;/p&gt; 1152 + &lt;p&gt;Plain text journaling can be just as much fun as a pen and 1153 + paper. Throw in some ASCII art for each month, use swankier 1154 + signifiers, or louder syntax highlighting. Don't expect 1155 + forgiveness from org-mode users though.&lt;/p&gt; 1156 + &lt;p&gt;&lt;a href="https://cdn.oppi.li/ZCK.png"&gt;&lt;img src="https://cdn.oppi.li/ZCK.png" alt="" /&gt;&lt;/a&gt;&lt;/p&gt; 1157 + </description> 1158 + <link>https://oppi.li/posts/plain_text_journaling/</link> 1159 + <pubDate>Sun, 18 Jun 2023 00:00:00 +0000</pubDate> 1160 + <guid>https://oppi.li/posts/plain_text_journaling/</guid> 1161 + </item> 1162 + <item> 1163 + <title>curing a case of git-UX</title> 1164 + <description>&lt;p&gt;Git worktrees are great, but they fall behind the venerable 1165 + &lt;code&gt;git checkout&lt;/code&gt; sometimes. I attempted to fix that with 1166 + &lt;a href="https://github.com/junegunn/fzf"&gt;fzf&lt;/a&gt; and 1167 + a bit of bash.&lt;/p&gt; 1168 + &lt;p&gt;&lt;a href="https://asciinema.org/a/D297ztKRzpE4gAHbPTPmkqYps"&gt;&lt;img src="https://asciinema.org/a/D297ztKRzpE4gAHbPTPmkqYps.svg" alt="" /&gt;&lt;/a&gt;&lt;/p&gt; 1169 + &lt;p&gt;Fear not if you haven't heard of &amp;quot;worktrees&amp;quot;, I have 1170 + included a primer here.&lt;br /&gt; 1171 + &lt;a href="#what-makes-them-clunky"&gt;Skip the primer 1172 + -&amp;gt;&lt;/a&gt;.&lt;/p&gt; 1173 + &lt;h3 id="why-worktrees"&gt;Why Worktrees?&lt;/h3&gt; 1174 + &lt;p&gt;Picture this. You are whacking away on a feature branch. 1175 + Halfway there, in fact. Your friend asks you fix something 1176 + urgently. You proceed to do one of three things:&lt;/p&gt; 1177 + &lt;ul&gt; 1178 + &lt;li&gt;create a temporary branch, make a WIP commit, begin 1179 + working on the fix 1180 + &lt;/li&gt; 1181 + &lt;li&gt;stash away your changes, begin working on the fix 1182 + &lt;/li&gt; 1183 + &lt;li&gt;unfriend said friend for disturbing your flow 1184 + &lt;/li&gt; 1185 + &lt;/ul&gt; 1186 + &lt;p&gt;All of these options are ... subpar. With the temporary 1187 + branch, you are forced to create a partial, non-working 1188 + commit, and then reset said commit once done with the fix. 1189 + With the stash approach, you are required to now keep a 1190 + mental model of the stash, be aware of untracked files that 1191 + don't get stashed by default, etc. Why won't git just let 1192 + you work on two things at the same time without &lt;em&gt;thinking&lt;/em&gt; 1193 + so much?&lt;/p&gt; 1194 + &lt;p&gt;That is exactly what worktrees let you do. Worktrees let you 1195 + have more than one checkout at a time, each checkout in a 1196 + separate directory. Like creating a new clone, but safer (it 1197 + disallows checking out the same branch twice) and a lot more 1198 + space efficient (the new working tree is &amp;quot;linked&amp;quot; to the 1199 + &amp;quot;main&amp;quot; worktree, and a good amount of stuff is shared). When 1200 + your friend asks you to make the fix, you proceed like so:&lt;/p&gt; 1201 + &lt;ol&gt; 1202 + &lt;li&gt;Create a new working tree with: 1203 + &lt;/li&gt; 1204 + &lt;/ol&gt; 1205 + &lt;pre&gt;&lt;code class="language-bash"&gt;# git worktree add -b &amp;lt;branch-name&amp;gt; &amp;lt;path&amp;gt; &amp;lt;from&amp;gt; 1206 + git worktree add -b fix-stuff /path/to/tree master 1207 + &lt;/code&gt;&lt;/pre&gt; 1208 + &lt;ol start="2"&gt; 1209 + &lt;li&gt;&lt;code&gt;cd&lt;/code&gt; into &lt;code&gt;/path/to/tree&lt;/code&gt; 1210 + &lt;/li&gt; 1211 + &lt;li&gt;Fix, test, commit, push, party 1212 + &lt;/li&gt; 1213 + &lt;li&gt;Go back to your work, &lt;code&gt;cd -&lt;/code&gt; 1214 + &lt;/li&gt; 1215 + &lt;/ol&gt; 1216 + &lt;p&gt;Easy as cake. You didn't have to settle for a partially 1217 + working commit, you didn't to deal with this &amp;quot;stash&amp;quot; thing, 1218 + &lt;em&gt;and&lt;/em&gt; you didn't have to unfriend your friend. Treating each 1219 + branch as a directory just &lt;em&gt;feels&lt;/em&gt; more intuitive, more 1220 + UNIX-y.&lt;/p&gt; 1221 + &lt;p&gt;A few weeks later, you find yourself singing in praise of 1222 + worktrees, working on several things simultaneously. And at 1223 + the same time, cursing them for being a little ... clunky.&lt;/p&gt; 1224 + &lt;h3 id="what-makes-them-clunky"&gt;What makes them clunky?&lt;/h3&gt; 1225 + &lt;p&gt;Worktrees are great at what they claim to do. They stay out 1226 + of the way when you need a checkout posthaste. However, as 1227 + you start using them regularly, you realize they are not as 1228 + flexible as &lt;code&gt;git checkout&lt;/code&gt; or &lt;code&gt;git switch&lt;/code&gt;.&lt;/p&gt; 1229 + &lt;h4 id="branch-hopping"&gt;Branch-hopping&lt;/h4&gt; 1230 + &lt;p&gt;You can &lt;code&gt;git checkout &amp;lt;branch&amp;gt;&lt;/code&gt; from anywhere within a git 1231 + repository. You can't &amp;quot;jump&amp;quot; to a worktree in the same 1232 + fashion. The closest you can get, is to run &lt;code&gt;git worktree list&lt;/code&gt;, copy the path corresponding to your branch, and &lt;code&gt;cd&lt;/code&gt; 1233 + into it.&lt;/p&gt; 1234 + &lt;p&gt;Branch-hopping with the good ol' git-checkout:&lt;/p&gt; 1235 + &lt;pre&gt;&lt;code class="language-bash"&gt;# anywhere, anytime 1236 + λ git checkout feature/is-ascii-octdigit 1237 + &lt;/code&gt;&lt;/pre&gt; 1238 + &lt;p&gt;Meanwhile, in worktree world:&lt;/p&gt; 1239 + &lt;pre&gt;&lt;code class="language-bash"&gt;# keeping these paths in your head is hard 1240 + λ git worktree list 1241 + ~/worktrees/rustc/master eac6c33bc63 [master] 1242 + ~/worktrees/rustc/improve-std-char-docs 94cba88553e [improve-std-char-docs] 1243 + ~/worktrees/rustc/is-ascii-octdigit bc57be3af7a [feature/is-ascii-octdigit] 1244 + ~/my/other/path/oh/god op57or3ns7n [fix/some-error] 1245 + 1246 + λ cd ~/worktrees/rustc/is-ascii-octdigit 1247 + &lt;/code&gt;&lt;/pre&gt; 1248 + &lt;h4 id="branch-previewing"&gt;Branch-previewing&lt;/h4&gt; 1249 + &lt;p&gt;You can &amp;quot;preview&amp;quot; branches with &lt;code&gt;git branch -v&lt;/code&gt;. However, to 1250 + get an idea of what &amp;quot;recent activity&amp;quot; on a worktree looks 1251 + like, you might need some juggling. You can't glean much 1252 + info about a worktree in a jiffy.&lt;/p&gt; 1253 + &lt;p&gt;Branch-previewing with the good ol' git-branch:&lt;/p&gt; 1254 + &lt;pre&gt;&lt;code class="language-bash"&gt;λ git branch -v 1255 + + feature/is-ascii-octdigit bc57be3af7a introduce {char, u8}::is_ ... 1256 + + improve-std-char-docs 94cba88553e add whitespace in assert ... 1257 + * master eac6c33bc63 Auto merge of #100869 - n ... 1258 + &lt;/code&gt;&lt;/pre&gt; 1259 + &lt;p&gt;Meanwhile in worktree wonderland:&lt;/p&gt; 1260 + &lt;pre&gt;&lt;code&gt;λ git worktree list 1261 + ~/worktrees/rustc/master eac6c33bc63 [master] 1262 + ~/worktrees/rustc/improve-std-char-docs 94cba88553e [improve-std-char-docs] 1263 + ~/worktrees/rustc/is-ascii-octdigit bc57be3af7a [feature/is-ascii-octdigit] 1264 + 1265 + # aha, so ../is-ascii-octdigit corresponds to `feature/is-ascii-octdigit` 1266 + λ git log feature/is-ascii-octdigit 1267 + bc57be3af7a introduce {char, u8}::is_ascii_octdigit 1268 + eac6c33bc63 Auto merge of #100869 - nnethercote:repl ... 1269 + b32223fec10 Auto merge of #100707 - dzvon:fix-typo, ... 1270 + aa857eb953e Auto merge of #100537 - petrochenkov:pic ... 1271 + 1272 + # extra work to make the branch &amp;lt;-&amp;gt; worktree correspondence 1273 + &lt;/code&gt;&lt;/pre&gt; 1274 + &lt;h4 id="shell-completions"&gt;Shell completions&lt;/h4&gt; 1275 + &lt;p&gt;Lastly, you can bank on shell completions to fill in your 1276 + branch whilst using &lt;code&gt;git checkout&lt;/code&gt;. Worktrees have no such 1277 + conveniences.&lt;/p&gt; 1278 + &lt;p&gt;We can mend these minor faults with fzf.&lt;/p&gt; 1279 + &lt;h3 id="unclunkifying-worktrees"&gt;Unclunkifying worktrees&lt;/h3&gt; 1280 + &lt;p&gt;I'd suggest looking up 1281 + &lt;a href="https://github.com/junegunn/fzf"&gt;fzf&lt;/a&gt; (or 1282 + &lt;a href="https://github.com/lotabout/skim"&gt;skim&lt;/a&gt; or 1283 + &lt;a href="https://github.com/jhawthorn/fzy"&gt;fzy&lt;/a&gt;). These things make 1284 + it cake-easy to add interactivity to your shell. Onto fixing 1285 + the first minor fault, the inability to &amp;quot;jump&amp;quot; to a worktree 1286 + from anywhere within a git repository.&lt;/p&gt; 1287 + &lt;p&gt;I have a little function called &lt;code&gt;gwj&lt;/code&gt; which stands for &amp;quot;git 1288 + worktree jump&amp;quot;. The idea is to list all the worktrees, 1289 + select one with fzf, and &lt;code&gt;cd&lt;/code&gt; to it upon selection:&lt;/p&gt; 1290 + &lt;pre&gt;&lt;code class="language-bash"&gt;gwj () { 1291 + local out 1292 + out=$(git worktree list | fzf | awk '{print $1}') 1293 + cd $out 1294 + } 1295 + &lt;/code&gt;&lt;/pre&gt; 1296 + &lt;p&gt;That is all of it really. Head into a git repository:&lt;/p&gt; 1297 + &lt;pre&gt;&lt;code class="language-bash"&gt;# here, &amp;quot;master&amp;quot; is a directory, which contains my main 1298 + # worktree: a checkout of the master branch on rust-lang/rust 1299 + λ cd ~/worktrees/rustc/master/library/core/src 1300 + λ # hack away 1301 + &lt;/code&gt;&lt;/pre&gt; 1302 + &lt;p&gt;Preferably one with a few worktrees:&lt;/p&gt; 1303 + &lt;pre&gt;&lt;code class="language-bash"&gt;λ git worktree list 1304 + ~/worktrees/rustc/master eac6c33bc63 [master] 1305 + ~/worktrees/rustc/improve-std-char-docs 94cba88553e [improve-std-char-docs] 1306 + ~/worktrees/rustc/is-ascii-octdigit bc57be3af7a [feature/is-ascii-octdigit] 1307 + &lt;/code&gt;&lt;/pre&gt; 1308 + &lt;p&gt;And hit &lt;code&gt;gwj&lt;/code&gt; (pretend that the pipe, |, is your cursor):&lt;/p&gt; 1309 + &lt;pre&gt;&lt;code class="language-bash"&gt;λ gwj 1310 + &amp;gt; | 1311 + 4/4 1312 + &amp;gt; ~/worktrees/rustc/master eac6c33bc63 [master] 1313 + ~/worktrees/rustc/improve-std-char-docs 94cba88553e [improve-std-char-docs] 1314 + ~/worktrees/rustc/is-ascii-octdigit bc57be3af7a [feature/is-ascii-octdigit] 1315 + &lt;/code&gt;&lt;/pre&gt; 1316 + &lt;p&gt;Approximately type in your branch of choice:&lt;/p&gt; 1317 + &lt;pre&gt;&lt;code class="language-bash"&gt;λ gwj 1318 + &amp;gt; docs| 1319 + 4/4 1320 + &amp;gt; ~/worktrees/rustc/improve-std-char-docs 94cba88553e [improve-std-char-docs] 1321 + &lt;/code&gt;&lt;/pre&gt; 1322 + &lt;p&gt;And hit enter. You should find yourself in the selected 1323 + worktree.&lt;/p&gt; 1324 + &lt;p&gt;Onward, to the next fault, lack of preview-bility. We can 1325 + utilize fzf's aptly named &lt;code&gt;--preview&lt;/code&gt; flag, to, well, 1326 + preview our worktree before performing a selection:&lt;/p&gt; 1327 + &lt;pre&gt;&lt;code class="language-bash"&gt;gwj () { 1328 + local out 1329 + out=$( 1330 + git worktree list | 1331 + fzf --preview='git log --oneline -n10 {2}' | 1332 + awk '{print $1}' 1333 + ) 1334 + cd $out 1335 + } 1336 + &lt;/code&gt;&lt;/pre&gt; 1337 + &lt;p&gt;Once again, hit &lt;code&gt;gwj&lt;/code&gt; inside a git repository with linked worktrees:&lt;/p&gt; 1338 + &lt;pre&gt;&lt;code class="language-bash"&gt;λ gwj 1339 + ╭─────────────────────────────────────────────────────────╮ 1340 + │ eac6c33bc63 Auto merge of 100869 nnethercote:replace... │ 1341 + │ b32223fec10 Auto merge of 100707 dzvon:fix-typo, r=d... │ 1342 + │ aa857eb953e Auto merge of 100537 petrochenkov:picche... │ 1343 + │ 3892b7074da Auto merge of 100210 mystor:proc_macro_d... │ 1344 + │ db00199d999 Auto merge of 101249 matthiaskrgr:rollup... │ 1345 + │ 14d216d33ba Rollup merge of 101240 JohnTitor:JohnTit... │ 1346 + │ 3da66f03531 Rollup merge of 101236 thomcc:winfs-noze... │ 1347 + │ 0620f6e90af Rollup merge of 101230 davidtwco:transla... │ 1348 + │ c30c42ee299 Rollup merge of 101229 mgeisler:link-try... │ 1349 + │ e5356712b9e Rollup merge of 101165 ldm0:drain_to_ite... │ 1350 + ╰─────────────────────────────────────────────────────────╯ 1351 + &amp;gt; 1352 + 4/4 1353 + &amp;gt; /home/np/worktrees/compiler/master eac6c... 1354 + /home/np/worktrees/compiler/improve-std-char-docs 94cba... 1355 + /home/np/worktrees/compiler/is-ascii-octdigit bc57b... 1356 + &lt;/code&gt;&lt;/pre&gt; 1357 + &lt;p&gt;A fancy preview of the last 10 commits on the branch that 1358 + the selected worktree corresponds to. In other words, sight 1359 + for sore eyes. Our little script is already shaping up to be 1360 + useful, you hit &lt;code&gt;gwj&lt;/code&gt;, browse through your worktrees, 1361 + preview each one and automatically &lt;code&gt;cd&lt;/code&gt; to your selection. 1362 + But we are not done yet.&lt;/p&gt; 1363 + &lt;p&gt;The last fault was lack shell completions. A quick review of 1364 + what a shell completion really does:&lt;/p&gt; 1365 + &lt;pre&gt;&lt;code class="language-bash"&gt;λ git checkout f&amp;lt;tab&amp;gt; 1366 + feature/is-ascii-octdigit 1367 + fix/some-error 1368 + format-doc-tests 1369 + 1370 + λ git checkout feat&amp;lt;tab&amp;gt; 1371 + 1372 + λ git checkout feature/is-ascii-octdigit 1373 + &lt;/code&gt;&lt;/pre&gt; 1374 + &lt;p&gt;Each time you hit &amp;quot;tab&amp;quot;, the shell produces a few 1375 + &amp;quot;completion candidates&amp;quot;, and once you have just a single 1376 + candidate left, the shell inserts that for you directly into 1377 + your edit line. Of course, this process varies from shell to 1378 + shell.&lt;/p&gt; 1379 + &lt;p&gt;fzf narrows down your options as you type into the prompt, 1380 + but you still have to:&lt;/p&gt; 1381 + &lt;ol&gt; 1382 + &lt;li&gt;Type &lt;code&gt;gwj&lt;/code&gt; 1383 + &lt;/li&gt; 1384 + &lt;li&gt;Hit enter 1385 + &lt;/li&gt; 1386 + &lt;li&gt;Type out a query and narrow down your search 1387 + &lt;/li&gt; 1388 + &lt;li&gt;Hit enter 1389 + &lt;/li&gt; 1390 + &lt;/ol&gt; 1391 + &lt;p&gt;We can speed that up a bit, have fzf narrow down the 1392 + candidates on startup, just like our shell does:&lt;/p&gt; 1393 + &lt;pre&gt;&lt;code class="language-bash"&gt;gwj () { 1394 + local out query 1395 + query=&amp;quot;${1:- }&amp;quot; 1396 + out=$( 1397 + git worktree list | 1398 + fzf --preview='git log --oneline -n10 {2}' --query &amp;quot;$query&amp;quot; -1 | 1399 + awk '{print $1}' 1400 + ) 1401 + cd $out 1402 + } 1403 + &lt;/code&gt;&lt;/pre&gt; 1404 + &lt;p&gt;The change is extremely tiny, blink-and-you'll-miss-it kinda 1405 + tiny. We added a little &lt;code&gt;--query&lt;/code&gt; flag, that allows you to 1406 + prefill the prompt, and the &lt;code&gt;-1&lt;/code&gt; flag, that avoids the 1407 + interactive finder if only one match exists on startup:&lt;/p&gt; 1408 + &lt;pre&gt;&lt;code class="language-bash"&gt;# skip through the fzf prompt: 1409 + λ gwj master 1410 + # cd -- ~/worktrees/rustc/master 1411 + 1412 + # more than one option, we end up in the interactive finder 1413 + λ gwj improve 1414 + ╭─────────────────────────────────────────────────────────╮ 1415 + │ eac6c33bc63 Auto merge of 100869 nnethercote:replace... │ 1416 + │ b32223fec10 Auto merge of 100707 dzvon:fix-typo, r=d... │ 1417 + │ aa857eb953e Auto merge of 100537 petrochenkov:picche... │ 1418 + ╰─────────────────────────────────────────────────────────╯ 1419 + &amp;gt; improve 1420 + 2/2 1421 + &amp;gt; /home/np/worktrees/compiler/improve-const-perf eac6c... 1422 + /home/np/worktrees/compiler/improve-std-char-docs 94cba... 1423 + &lt;/code&gt;&lt;/pre&gt; 1424 + &lt;p&gt;Throw some error handling in there, hook up a similar script 1425 + to improve the UX of &lt;code&gt;git worktree remove&lt;/code&gt;, go wild. A few 1426 + more helpers I've got:&lt;/p&gt; 1427 + &lt;pre&gt;&lt;code class="language-bash"&gt;# gwa /path/to/branch-name 1428 + # creates a new branch and &amp;quot;switches&amp;quot; to it 1429 + function gwa () { 1430 + git worktree add &amp;quot;$1&amp;quot; &amp;amp;&amp;amp; cd &amp;quot;$1&amp;quot; 1431 + } 1432 + 1433 + alias gwls=&amp;quot;git worktree list&amp;quot; 1434 + &lt;/code&gt;&lt;/pre&gt; 1435 + </description> 1436 + <link>https://oppi.li/posts/curing_a_case_of_git-UX/</link> 1437 + <pubDate>Sat, 03 Sep 2022 00:00:00 +0000</pubDate> 1438 + <guid>https://oppi.li/posts/curing_a_case_of_git-UX/</guid> 1439 + </item> 1440 + <item> 1441 + <title>programming on 34 keys</title> 1442 + <description>&lt;p&gt;Minimizing your keyboard layout is a slippery slope. A few 1443 + months ago, I built the 1444 + &lt;a href="https://github.com/icyphox/ferricy"&gt;Ferricy&lt;/a&gt;, a 1445 + 34-key-split-ortho-ergo keyboard. The Ferricy is a fork of 1446 + the &lt;a href="https://github.com/davidphilipbarr/Sweep/tree/main/Sweep%20Bling%20MX"&gt;Ferris Sweep MX 1447 + Bling&lt;/a&gt;.&lt;/p&gt; 1448 + &lt;p&gt;&lt;img src="https://cdn.oppi.li/otz.jpg" alt="The Ferricy, designed by icyphox" /&gt;&lt;/p&gt; 1449 + &lt;p&gt;My daily use consists of a bit of prose and a lot of 1450 + program, my layout has evolved accordingly.&lt;/p&gt; 1451 + &lt;h1 id="base-layer"&gt;Base Layer&lt;/h1&gt; 1452 + &lt;p&gt;&lt;img src="https://cdn.oppi.li/base.png" alt="Colemak with no mods" /&gt;&lt;/p&gt; 1453 + &lt;p&gt;The base layer contains alphabets, four symbols and four 1454 + whitespace keys:&lt;/p&gt; 1455 + &lt;ul&gt; 1456 + &lt;li&gt;Alphas: Stock Colemak, with no modifications whatsoever 1457 + &lt;/li&gt; 1458 + &lt;li&gt;Symbols: &lt;code&gt;. , / ;&lt;/code&gt; 1459 + &lt;/li&gt; 1460 + &lt;li&gt;Whitespace: tab, space, enter, backspace (from left to 1461 + right) 1462 + &lt;/li&gt; 1463 + &lt;/ul&gt; 1464 + &lt;h1 id="layers"&gt;Layers&lt;/h1&gt; 1465 + &lt;p&gt;Keyboard input is complex and it is impossible to skirt 1466 + around it. You can either use a keyboard with enough keys to 1467 + supply all possible inputs (a mechanical burden), or you can 1468 + use firmware to supply all possible inputs (a cognitive 1469 + burden). Layers are a cognitive burden.&lt;/p&gt; 1470 + &lt;p&gt;I use 3 layers, heavily inspired by 1471 + &lt;a href="https://github.com/manna-harbour/miryoku"&gt;Miryoku&lt;/a&gt;, but 1472 + tuned for programming. Excluding the base Colemak layer:&lt;/p&gt; 1473 + &lt;ul&gt; 1474 + &lt;li&gt;&lt;code&gt;NAV&lt;/code&gt;: activated on holding &lt;code&gt;space&lt;/code&gt; (left thumb) 1475 + &lt;/li&gt; 1476 + &lt;li&gt;&lt;code&gt;NUM&lt;/code&gt;: activated on holding &lt;code&gt;tab&lt;/code&gt; (left thumb) 1477 + &lt;/li&gt; 1478 + &lt;li&gt;&lt;code&gt;SYM&lt;/code&gt;: activated on holding &lt;code&gt;enter&lt;/code&gt; (right thumb) 1479 + &lt;/li&gt; 1480 + &lt;/ul&gt; 1481 + &lt;h2 id="the-nav-layer"&gt;The &lt;code&gt;NAV&lt;/code&gt; Layer&lt;/h2&gt; 1482 + &lt;p&gt;As the name suggests, this layer is focused on navigation. 1483 + Arrow keys and the likes.&lt;/p&gt; 1484 + &lt;p&gt;&lt;img src="https://cdn.oppi.li/nav.png" alt="NAV, on holding space" /&gt;&lt;/p&gt; 1485 + &lt;p&gt;Using Vim and Colemak means you lose out on HJKL navigation. 1486 + However, on activating the &lt;code&gt;NAV&lt;/code&gt; layer, the right home-row is 1487 + converted into arrow keys. In essence, by holding space, I 1488 + can navigate Vim with the home-row, or Firefox, or my PDF 1489 + reader. I no longer need to look for software that allows 1490 + Vim navigation keys, because it is baked into the firmware!&lt;/p&gt; 1491 + &lt;p&gt;My Vim motions are not limited to HJKL. In fact, my Vim 1492 + motions are rarely HJKL. I tend to use &lt;code&gt;}&lt;/code&gt; (next paragraph) 1493 + and &lt;code&gt;)&lt;/code&gt; (next sentence) more often. As a result, these have 1494 + found their way into my &lt;code&gt;NAV&lt;/code&gt; layer, over the likes of 1495 + &lt;code&gt;PgDown&lt;/code&gt; and &lt;code&gt;End&lt;/code&gt;. Having brackets at my index and middle 1496 + fingers is nice for programming too.&lt;/p&gt; 1497 + &lt;h2 id="the-sym-layer"&gt;The &lt;code&gt;SYM&lt;/code&gt; Layer&lt;/h2&gt; 1498 + &lt;p&gt;&lt;img src="https://cdn.oppi.li/sym.png" alt="SYM, on holding enter" /&gt;&lt;/p&gt; 1499 + &lt;p&gt;This layer contains all the symbols that you would find by 1500 + hitting &lt;code&gt;Shift&lt;/code&gt; and a key on the number row. Probably 1501 + noteworthy to Vim users: the symbols are arranged in the 1502 + form of a mirrored numpad for exactly one reason: to move 1503 + &lt;code&gt;$&lt;/code&gt; to the left of &lt;code&gt;^&lt;/code&gt;. It has always annoyed me that &lt;code&gt;$&lt;/code&gt; 1504 + moves the cursor to the end of the line and &lt;code&gt;^&lt;/code&gt; moves it to 1505 + the beginning, but their position on a typical number row 1506 + are reversed, 4 comes before 6.&lt;/p&gt; 1507 + &lt;h2 id="the-num-layer"&gt;The &lt;code&gt;NUM&lt;/code&gt; layer&lt;/h2&gt; 1508 + &lt;p&gt;&lt;img src="https://cdn.oppi.li/num.png" alt="NUM, on holding tab" /&gt;&lt;/p&gt; 1509 + &lt;p&gt;Another deviation from Miryoku, the numpad just feels &lt;em&gt;right&lt;/em&gt; 1510 + on my &lt;em&gt;right&lt;/em&gt; hand.&lt;/p&gt; 1511 + &lt;h1 id="zmk-combos"&gt;ZMK Combos&lt;/h1&gt; 1512 + &lt;p&gt;If you have been paying close attention, you might have 1513 + noticed that &lt;code&gt;escape&lt;/code&gt; didn't make it to any layer. &lt;code&gt;escape&lt;/code&gt; 1514 + is too crucial to put on a non-base layer, but at the same 1515 + time, not as important to deserve a place on the base layer. 1516 + That is where ZMK's combos come in. Combos let you tap any 1517 + number of keys, and combine them to form a single key. I 1518 + have combos set up for underscore, minus, escape and 1519 + caps-word (more on caps-word later):&lt;/p&gt; 1520 + &lt;p&gt;&lt;img src="https://cdn.oppi.li/combos.png" alt="Combos are almost piano-like" /&gt;&lt;/p&gt; 1521 + &lt;h1 id="home-row-mods"&gt;Home-row Mods&lt;/h1&gt; 1522 + &lt;p&gt;Inherited from Miryoku, I have home-row mods for activating 1523 + &lt;code&gt;Super&lt;/code&gt;, &lt;code&gt;Alt&lt;/code&gt;, &lt;code&gt;Shift&lt;/code&gt;, &lt;code&gt;Ctrl&lt;/code&gt; and &lt;code&gt;Hyper&lt;/code&gt; (&lt;code&gt;Ctrl + Shift + Alt + Super&lt;/code&gt;). The idea is to send &lt;code&gt;T&lt;/code&gt; on tap and &lt;code&gt;Ctrl&lt;/code&gt; on 1524 + hold. Home-row mods are fairly popular, so I'll not go into 1525 + the details.&lt;/p&gt; 1526 + &lt;p&gt;&lt;img src="https://cdn.oppi.li/homerow.png" alt="Super, Alt, Shift, Ctrl, Hyper; on the left half, and mirrored on the right half" /&gt;&lt;/p&gt; 1527 + &lt;p&gt;&lt;code&gt;Hyper&lt;/code&gt; bridges the gap between firmware and software. You 1528 + can never configure key combination that, opens Firefox, for 1529 + example, through firmware alone. However, with the &lt;code&gt;Hyper&lt;/code&gt; 1530 + key, and some &lt;code&gt;sxhkd&lt;/code&gt; magic, you can emulate that. Pressing 1531 + &lt;code&gt;Hyper + F&lt;/code&gt; on a keyboard is just two keys, but the key 1532 + codes sent are &lt;code&gt;Ctrl + Shift + Alt + Super + F&lt;/code&gt;. That key 1533 + combination is not intercepted by any application as a 1534 + shortcut, except for the following &lt;code&gt;sxhkd&lt;/code&gt; stanza:&lt;/p&gt; 1535 + &lt;pre&gt;&lt;code class="language-bash"&gt;super + alt + shift + ctrl + f 1536 + xdotool search &amp;quot;Mozilla Firefox&amp;quot; windowactivate 1537 + &lt;/code&gt;&lt;/pre&gt; 1538 + &lt;p&gt;Alternatively, you can intercept unused &lt;code&gt;F&lt;/code&gt; keys: &lt;code&gt;F13&lt;/code&gt; 1539 + through &lt;code&gt;F24&lt;/code&gt;.&lt;/p&gt; 1540 + &lt;p&gt;Home-row mods are mirrored on each half because it would be 1541 + impossible to hit &lt;code&gt;Ctrl + T&lt;/code&gt; if not; they lie on the same 1542 + key.&lt;/p&gt; 1543 + &lt;h1 id="caps-word"&gt;Caps-word&lt;/h1&gt; 1544 + &lt;p&gt;Caps-word is a clever caps-lock, built into ZMK. Typing out 1545 + constants such as &lt;code&gt;PORT&lt;/code&gt; with home-row mods would look like 1546 + this:&lt;/p&gt; 1547 + &lt;ul&gt; 1548 + &lt;li&gt;hold &lt;code&gt;e&lt;/code&gt; (shift) on left hand, and tap &lt;code&gt;p&lt;/code&gt; on right hand 1549 + &lt;/li&gt; 1550 + &lt;li&gt;hold &lt;code&gt;e&lt;/code&gt; (shift) on left hand, and tap &lt;code&gt;o&lt;/code&gt; on right hand 1551 + &lt;/li&gt; 1552 + &lt;li&gt;hold &lt;code&gt;s&lt;/code&gt; (shift) on right hand, and tap &lt;code&gt;r&lt;/code&gt; on left hand 1553 + &lt;/li&gt; 1554 + &lt;li&gt;hold &lt;code&gt;s&lt;/code&gt; (shift) on right hand, and tap &lt;code&gt;t&lt;/code&gt; on left hand 1555 + &lt;/li&gt; 1556 + &lt;/ul&gt; 1557 + &lt;p&gt;This hold-alternate-hold dance gets tiring quickly. With 1558 + caps-word, however:&lt;/p&gt; 1559 + &lt;ul&gt; 1560 + &lt;li&gt;toggle &lt;code&gt;caps_word&lt;/code&gt; 1561 + &lt;/li&gt; 1562 + &lt;li&gt;type out &lt;code&gt;p&lt;/code&gt;, &lt;code&gt;o&lt;/code&gt;, &lt;code&gt;r&lt;/code&gt;, &lt;code&gt;t&lt;/code&gt; 1563 + &lt;/li&gt; 1564 + &lt;li&gt;hit a &lt;em&gt;break&lt;/em&gt; character (space, enter will do) 1565 + &lt;/li&gt; 1566 + &lt;li&gt;continue 1567 + &lt;/li&gt; 1568 + &lt;/ul&gt; 1569 + &lt;p&gt;Caps-word automatically disables capitalization upon 1570 + encountering a breaking character, (which are space, enter 1571 + or any modifier, by default) right in the firmware!&lt;/p&gt; 1572 + &lt;h1 id="findings"&gt;Findings&lt;/h1&gt; 1573 + &lt;p&gt;34-keys has been reasonably comfortable to use, for both 1574 + prose and program. My palms do not move across the desk at 1575 + all, as I reach for keys. I mostly write Rust and Bash, and 1576 + my layout has evolved to accomodate special characters from 1577 + their grammars (angled brackets and hyphens, specifically). 1578 + If you are on a similar journey, I would suggest focusing on 1579 + accuracy and comfort over speed. Speed comes with time.&lt;/p&gt; 1580 + </description> 1581 + <link>https://oppi.li/posts/programming_on_34_keys/</link> 1582 + <pubDate>Sun, 28 Aug 2022 00:00:00 +0000</pubDate> 1583 + <guid>https://oppi.li/posts/programming_on_34_keys/</guid> 1584 + </item> 1585 + <item> 1586 + <title>a reference counted afterlife</title> 1587 + <description>&lt;p&gt;I took interest in the Egyptian rendition of the afterlife 1588 + recently.&lt;/p&gt; 1589 + &lt;h3 id="parts-of-the-soul"&gt;Parts of the Soul&lt;/h3&gt; 1590 + &lt;p&gt;Ancient Egyptians believed that the soul comprised of 1591 + several components:&lt;/p&gt; 1592 + &lt;ul&gt; 1593 + &lt;li&gt;&lt;em&gt;ren&lt;/em&gt; 1594 + &lt;/li&gt; 1595 + &lt;li&gt;&lt;em&gt;ka&lt;/em&gt; 1596 + &lt;/li&gt; 1597 + &lt;li&gt;&lt;em&gt;ib&lt;/em&gt; 1598 + &lt;/li&gt; 1599 + &lt;li&gt;&lt;em&gt;ba&lt;/em&gt; 1600 + &lt;/li&gt; 1601 + &lt;li&gt;&lt;em&gt;sheut&lt;/em&gt; 1602 + &lt;/li&gt; 1603 + &lt;/ul&gt; 1604 + &lt;p&gt;Egyptians emphasized on preserving the different parts of 1605 + the soul. Mummification for example, served to preserve the 1606 + physical part of the soul. The other components have their 1607 + respective preservation strategies.&lt;/p&gt; 1608 + &lt;p&gt;Of all of these bits, I find &lt;em&gt;ren&lt;/em&gt;, which simply means 1609 + &lt;em&gt;name&lt;/em&gt;, to be the most interesting. &lt;em&gt;Ba&lt;/em&gt;, the human-headed 1610 + chicken that represents &lt;em&gt;personality&lt;/em&gt;, is a close favourite.&lt;/p&gt; 1611 + &lt;p&gt;&lt;em&gt;Ren&lt;/em&gt; is the name given to a person at birth. Egyptians 1612 + believed that this portion of the soul would continue to 1613 + live on for as long as it was spoken. If you were someone 1614 + worthy of continued existence, your name would be inscribed 1615 + all over the place. If you were the type to snatch away 1616 + bread from children, your name would be condemned from 1617 + memory, forgotten.&lt;/p&gt; 1618 + &lt;h3 id="garbage-collection"&gt;Garbage-collection&lt;/h3&gt; 1619 + &lt;p&gt;The concept of &lt;em&gt;ren&lt;/em&gt; seems to be perfectly analogous to 1620 + reference counted garbage-collection.&lt;/p&gt; 1621 + &lt;ul&gt; 1622 + &lt;li&gt;A name (&lt;em&gt;ren&lt;/em&gt;) is assigned to an object (person) on 1623 + initialization (at birth) 1624 + &lt;/li&gt; 1625 + &lt;li&gt;Names are used to refer to objects 1626 + &lt;/li&gt; 1627 + &lt;li&gt;Objects go out of existence when there are no more 1628 + references to them 1629 + &lt;/li&gt; 1630 + &lt;/ul&gt; 1631 + &lt;p&gt;The concept of &lt;em&gt;ren&lt;/em&gt; seems to model human-memory. The 1632 + similarity with garbage-collection is now easily explained, 1633 + because garbage-collection models a program's memory.&lt;/p&gt; 1634 + &lt;p&gt;Perhaps some cheeky Egyptian has attained immortality by 1635 + creating a &lt;em&gt;ren&lt;/em&gt;-cycle.&lt;/p&gt; 1636 + </description> 1637 + <link>https://oppi.li/posts/a_reference_counted_afterlife/</link> 1638 + <pubDate>Tue, 02 Aug 2022 00:00:00 +0000</pubDate> 1639 + <guid>https://oppi.li/posts/a_reference_counted_afterlife/</guid> 1640 + </item> 1641 + <item> 1642 + <title>lotus58</title> 1643 + <description>&lt;p&gt;Earlier this month, I decided that I would laugh at Indian 1644 + customs in the face by building a split-ergo mechanical 1645 + keyboard from scratch rather than purchasing a Moonlander.&lt;/p&gt; 1646 + &lt;p&gt;&lt;img src="https://cdn.oppi.li/i8k.jpg" alt="The finished product" /&gt;&lt;/p&gt; 1647 + &lt;h2 id="sourcing-the-parts"&gt;Sourcing the parts&lt;/h2&gt; 1648 + &lt;p&gt;If you, like me, live in India, you might find this section 1649 + useful. My approach to finding parts:&lt;/p&gt; 1650 + &lt;ul&gt; 1651 + &lt;li&gt;Check reputed, local online stores 1652 + &lt;/li&gt; 1653 + &lt;li&gt;Check physical hardware stores 1654 + &lt;/li&gt; 1655 + &lt;li&gt;Import the part 1656 + &lt;/li&gt; 1657 + &lt;/ul&gt; 1658 + &lt;h3 id="pcbs"&gt;PCBs&lt;/h3&gt; 1659 + &lt;p&gt;This was by far the hardest component to procure. 1660 + Fabrication services have certain &lt;em&gt;capabilities&lt;/em&gt;. 1661 + Capabilities are the limitations of a fabrication service. 1662 + For example, a service may be capable of drilling holes no 1663 + smaller than 0.3mm in diameter. Most sites have a 1664 + verification process to check if their capabilities meet 1665 + your design's requirements. I tried a few local PCB 1666 + fabrication services:&lt;/p&gt; 1667 + &lt;ul&gt; 1668 + &lt;li&gt;Lion PCB: Capabilities did not meet my requirements 1669 + &lt;/li&gt; 1670 + &lt;li&gt;PCBPower: Capabilities did not meet my requirements 1671 + &lt;/li&gt; 1672 + &lt;li&gt;Circuitwala: Capabilities did not meet my requirements 1673 + &lt;/li&gt; 1674 + &lt;/ul&gt; 1675 + &lt;p&gt;I settled for JLCPCB, a Chinese service. PCBs themselves 1676 + were 16 USD, shipping was another 35 USD, and customs was 1677 + another 3.5K INR (ouch).&lt;/p&gt; 1678 + &lt;h3 id="case-material"&gt;Case material&lt;/h3&gt; 1679 + &lt;p&gt;I don't really have a case for the Lotus58, it is more of a 1680 + &amp;quot;plastic sandwich&amp;quot;. I purchased acrylic plates from Robu. 1681 + Cheap, fast, solid, and customizable. I cannot recommend 1682 + Robu enough. A full set of plates (2 top plates and 2 bottom 1683 + plates) cost me about 500 INR. I also bought a pair of 1684 + laptop height raisers on Amazon to create a budget tenting 1685 + setup.&lt;/p&gt; 1686 + &lt;h3 id="electronics"&gt;Electronics&lt;/h3&gt; 1687 + &lt;p&gt;You'll need a few rather specific electronic components such 1688 + as hotswap sockets and TRRS mounts, the rest are commonly 1689 + available:&lt;/p&gt; 1690 + &lt;ul&gt; 1691 + &lt;li&gt;Hotswap sockets: StacksKB 1692 + &lt;/li&gt; 1693 + &lt;li&gt;TRRS mounts (PJ 320A): StacksKB 1694 + &lt;/li&gt; 1695 + &lt;li&gt;Diodes (1N4841): StacksKB 1696 + &lt;/li&gt; 1697 + &lt;li&gt;M2 screws: StacksKB 1698 + &lt;/li&gt; 1699 + &lt;li&gt;M2 spacers: ThinkRobotics 1700 + &lt;/li&gt; 1701 + &lt;li&gt;Arduino Pro Micro: Robu 1702 + &lt;/li&gt; 1703 + &lt;/ul&gt; 1704 + &lt;p&gt;I skimped out on optional components such as OLEDs and 1705 + rotary encoders.&lt;/p&gt; 1706 + &lt;h3 id="switches-and-keycaps"&gt;Switches and Keycaps&lt;/h3&gt; 1707 + &lt;p&gt;Arguably the most fun part of the build:&lt;/p&gt; 1708 + &lt;ul&gt; 1709 + &lt;li&gt;Gateron Oil King switches: Rectangles 1710 + &lt;/li&gt; 1711 + &lt;li&gt;DSA blanks: Meckeys 1712 + &lt;/li&gt; 1713 + &lt;/ul&gt; 1714 + &lt;h2 id="building-the-keyboard"&gt;Building the keyboard&lt;/h2&gt; 1715 + &lt;p&gt;The the build is extremely straightforward. Through hole 1716 + components are easy to solder. Be wary of component 1717 + placement and orientation. Check thrice, solder once. Few 1718 + debugging tips:&lt;/p&gt; 1719 + &lt;ul&gt; 1720 + &lt;li&gt;if a single key does not actuate, check the hotswap for 1721 + poor soldering (reflow the joint), and the switch pins for 1722 + deformation during installation 1723 + &lt;/li&gt; 1724 + &lt;li&gt;if an entire column or row activates on a single 1725 + key-press, a connection has been shorted 1726 + &lt;/li&gt; 1727 + &lt;li&gt;if only some of the keys on a given row actuate, a diode 1728 + has been soldered on the wrong way 1729 + &lt;/li&gt; 1730 + &lt;/ul&gt; 1731 + &lt;h2 id="the-typing-experience"&gt;The typing experience&lt;/h2&gt; 1732 + &lt;p&gt;I decidede to give QWERTY the boot and learn Colemak along 1733 + with the new keyboard. The first few weeks were terrible 1734 + because I could neither type QWERTY nor Colemak, but I got 1735 + the hang of it pretty quickly. Typing websites do help, but 1736 + it is best to simply use it in your daily workflow. No site 1737 + can help you get accustomed to the various things you use 1738 + your keyboard for such as switching windows or navigating 1739 + vim/tmux.&lt;/p&gt; 1740 + &lt;h3 id="colemak"&gt;Colemak&lt;/h3&gt; 1741 + &lt;p&gt;Alt layouts such as Colemak are definitely worth it. I find 1742 + that Colemak reduces finger movement a lot, a good portion 1743 + of the keys on the left hand are the same as QWERTY, it 1744 + is fairly easy to pick up as well.&lt;/p&gt; 1745 + &lt;h3 id="vim"&gt;Vim&lt;/h3&gt; 1746 + &lt;p&gt;Using an alt layout means most programs with keyboard 1747 + shortcuts are not going to work as expected, &lt;code&gt;HJKL&lt;/code&gt; on vim for 1748 + movements being one of them. I took the short route out by 1749 + creating a new layer with arrow keys on the home row:&lt;/p&gt; 1750 + &lt;pre&gt;&lt;code&gt;default homerow: 1751 + H N E I 1752 + 1753 + &amp;quot;nav&amp;quot; layer: 1754 + &amp;lt; v ^ &amp;gt; 1755 + &lt;/code&gt;&lt;/pre&gt; 1756 + &lt;p&gt;The remaining commands in vim are largely mnemonics. 1757 + Navigating with home-row arrow keys also means that I can 1758 + use &amp;quot;HJKL&amp;quot; globally, to scroll a website, for example.&lt;/p&gt; 1759 + &lt;h3 id="cutting-down-to-34-keys"&gt;Cutting down to 34 keys&lt;/h3&gt; 1760 + &lt;p&gt;A couple months into my ergo journey, I realized that I 1761 + could get away by moving my fingers even lesser. I moved 1762 + modifiers such as &lt;code&gt;Super&lt;/code&gt;, &lt;code&gt;Ctrl&lt;/code&gt;, &lt;code&gt;Alt&lt;/code&gt; and &lt;code&gt;Shift&lt;/code&gt; to the 1763 + home-row as QMK Mod Taps. The rest of the keys are cleverly 1764 + placed in layers, not too much unlike the Miryoku layout. 1765 + Even for someone that writes Rust (a symbol-heavy grammar), 1766 + I find 34-keys to be sufficient.&lt;/p&gt; 1767 + &lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt; 1768 + &lt;p&gt;I have been bitten by the ergomech bug.&lt;/p&gt; 1769 + &lt;p&gt;&lt;img src="https://cdn.oppi.li/XM3.jpg" alt="The Lotus58 in action" /&gt;&lt;/p&gt; 1770 + </description> 1771 + <link>https://oppi.li/posts/lotus58/</link> 1772 + <pubDate>Mon, 13 Jun 2022 00:00:00 +0000</pubDate> 1773 + <guid>https://oppi.li/posts/lotus58/</guid> 1774 + </item> 1775 + <item> 1776 + <title>lightweight linting</title> 1777 + <description>&lt;p&gt;&lt;a href="https://tree-sitter.github.io/tree-sitter/using-parsers#pattern-matching-with-queries"&gt;Tree-sitter&lt;/a&gt; 1778 + queries allow you to search for patterns in syntax trees, 1779 + much like a regex would, in text. Combine that with some Rust 1780 + glue to write simple, custom linters.&lt;/p&gt; 1781 + &lt;h3 id="tree-sitter-syntax-trees"&gt;Tree-sitter syntax trees&lt;/h3&gt; 1782 + &lt;p&gt;Here is a quick crash course on syntax trees generated by 1783 + tree-sitter. Syntax trees produced by tree-sitter are 1784 + represented by S-expressions. The generated S-expression for 1785 + the following Rust code,&lt;/p&gt; 1786 + &lt;pre&gt;&lt;code class="language-rust"&gt;fn main() { 1787 + let x = 2; 1788 + } 1789 + &lt;/code&gt;&lt;/pre&gt; 1790 + &lt;p&gt;would be:&lt;/p&gt; 1791 + &lt;pre&gt;&lt;code class="language-scheme"&gt;(source_file 1792 + (function_item 1793 + name: (identifier) 1794 + parameters: (parameters) 1795 + body: 1796 + (block 1797 + (let_declaration 1798 + pattern: (identifier) 1799 + value: (integer_literal))))) 1800 + &lt;/code&gt;&lt;/pre&gt; 1801 + &lt;p&gt;Syntax trees generated by tree-sitter have a couple of other 1802 + cool properties: they are &lt;em&gt;lossless&lt;/em&gt; syntax trees. Given a 1803 + lossless syntax tree, you can regenerate the original source 1804 + code in its entirety. Consider the following addition to our 1805 + example:&lt;/p&gt; 1806 + &lt;pre&gt;&lt;code class="language-rust"&gt; fn main() { 1807 + + // a comment goes here 1808 + let x = 2; 1809 + } 1810 + &lt;/code&gt;&lt;/pre&gt; 1811 + &lt;p&gt;The tree-sitter syntax tree preserves the comment, while the 1812 + typical abstract syntax tree wouldn't:&lt;/p&gt; 1813 + &lt;pre&gt;&lt;code class="language-scheme"&gt; (source_file 1814 + (function_item 1815 + name: (identifier) 1816 + parameters: (parameters) 1817 + body: 1818 + (block 1819 + + (line_comment) 1820 + (let_declaration 1821 + pattern: (identifier) 1822 + value: (integer_literal))))) 1823 + &lt;/code&gt;&lt;/pre&gt; 1824 + &lt;h3 id="tree-sitter-queries"&gt;Tree-sitter queries&lt;/h3&gt; 1825 + &lt;p&gt;Tree-sitter provides a DSL to match over CSTs. These queries 1826 + resemble our S-expression syntax trees, here is a query to 1827 + match all line comments in a Rust CST:&lt;/p&gt; 1828 + &lt;pre&gt;&lt;code class="language-scheme"&gt;(line_comment) 1829 + 1830 + ; matches the following rust code 1831 + ; // a comment goes here 1832 + &lt;/code&gt;&lt;/pre&gt; 1833 + &lt;p&gt;Neat, eh? But don't take my word for it, give it a go on the 1834 + &lt;a href="https://tree-sitter.github.io/tree-sitter/playground"&gt;tree-sitter 1835 + playground&lt;/a&gt;. 1836 + Type in a query like so:&lt;/p&gt; 1837 + &lt;pre&gt;&lt;code class="language-scheme"&gt;; the web playground requires you to specify a &amp;quot;capture&amp;quot; 1838 + ; you will notice the capture and the nodes it captured 1839 + ; turn blue 1840 + (line_comment) @capture 1841 + &lt;/code&gt;&lt;/pre&gt; 1842 + &lt;p&gt;Here's another to match &lt;code&gt;let&lt;/code&gt; expressions that 1843 + bind an integer to an identifier:&lt;/p&gt; 1844 + &lt;pre&gt;&lt;code class="language-scheme"&gt;(let_declaration 1845 + pattern: (identifier) 1846 + value: (integer_literal)) 1847 + 1848 + ; matches: 1849 + ; let foo = 2; 1850 + &lt;/code&gt;&lt;/pre&gt; 1851 + &lt;p&gt;We can &lt;em&gt;capture&lt;/em&gt; nodes into variables:&lt;/p&gt; 1852 + &lt;pre&gt;&lt;code class="language-scheme"&gt;(let_declaration 1853 + pattern: (identifier) @my-capture 1854 + value: (integer_literal)) 1855 + 1856 + ; matches: 1857 + ; let foo = 2; 1858 + 1859 + ; captures: 1860 + ; foo 1861 + &lt;/code&gt;&lt;/pre&gt; 1862 + &lt;p&gt;And apply certain &lt;em&gt;predicates&lt;/em&gt; to captures:&lt;/p&gt; 1863 + &lt;pre&gt;&lt;code class="language-scheme"&gt;((let_declaration 1864 + pattern: (identifier) @my-capture 1865 + value: (integer_literal)) 1866 + (#eq? @my-capture &amp;quot;foo&amp;quot;)) 1867 + 1868 + ; matches: 1869 + ; let foo = 2; 1870 + 1871 + ; and not: 1872 + ; let bar = 2; 1873 + &lt;/code&gt;&lt;/pre&gt; 1874 + &lt;p&gt;The &lt;code&gt;#match?&lt;/code&gt; predicate checks if a capture matches a regex:&lt;/p&gt; 1875 + &lt;pre&gt;&lt;code class="language-scheme"&gt;((let_declaration 1876 + pattern: (identifier) @my-capture 1877 + value: (integer_literal)) 1878 + (#match? @my-capture &amp;quot;foo|bar&amp;quot;)) 1879 + 1880 + ; matches both `foo` and `bar`: 1881 + ; let foo = 2; 1882 + ; let bar = 2; 1883 + &lt;/code&gt;&lt;/pre&gt; 1884 + &lt;p&gt;Exhibit indifference, as a stoic programmer would, with the 1885 + &lt;em&gt;wildcard&lt;/em&gt; pattern:&lt;/p&gt; 1886 + &lt;pre&gt;&lt;code class="language-scheme"&gt;(let_declaration 1887 + pattern: (identifier) 1888 + value: (_)) 1889 + 1890 + ; matches: 1891 + ; let foo = &amp;quot;foo&amp;quot;; 1892 + ; let foo = 42; 1893 + ; let foo = bar; 1894 + &lt;/code&gt;&lt;/pre&gt; 1895 + &lt;p&gt;&lt;a href="https://tree-sitter.github.io/tree-sitter/using-parsers#pattern-matching-with-queries"&gt;The 1896 + documentation&lt;/a&gt; 1897 + does the tree-sitter query DSL more justice, but we now know 1898 + enough to write our first lint.&lt;/p&gt; 1899 + &lt;h3 id="write-you-a-tree-sitter-lint"&gt;Write you a tree-sitter lint&lt;/h3&gt; 1900 + &lt;p&gt;Strings in &lt;code&gt;std::env&lt;/code&gt; functions are error prone:&lt;/p&gt; 1901 + &lt;pre&gt;&lt;code class="language-rust"&gt;std::env::remove_var(&amp;quot;RUST_BACKTACE&amp;quot;); 1902 + // ^^^^ &amp;quot;TACE&amp;quot; instead of &amp;quot;TRACE&amp;quot; 1903 + &lt;/code&gt;&lt;/pre&gt; 1904 + &lt;p&gt;I prefer this instead:&lt;/p&gt; 1905 + &lt;pre&gt;&lt;code class="language-rust"&gt;// somewhere in a module that is well spellchecked 1906 + static BACKTRACE: &amp;amp;str = &amp;quot;RUST_BACKTRACE&amp;quot;; 1907 + 1908 + // rest of the codebase 1909 + std::env::remove_var(BACKTRACE); 1910 + &lt;/code&gt;&lt;/pre&gt; 1911 + &lt;p&gt;Let's write a lint to find &lt;code&gt;std::env&lt;/code&gt; functions that use 1912 + strings. Put aside the effectiveness of this lint for the 1913 + moment, and take a stab at writing a tree-sitter query. For 1914 + reference, a function call like so:&lt;/p&gt; 1915 + &lt;pre&gt;&lt;code class="language-rust"&gt;remove_var(&amp;quot;RUST_BACKTRACE&amp;quot;) 1916 + &lt;/code&gt;&lt;/pre&gt; 1917 + &lt;p&gt;Produces the following S-expression:&lt;/p&gt; 1918 + &lt;pre&gt;&lt;code class="language-scheme"&gt;(call_expression 1919 + function: (identifier) 1920 + arguments: (arguments (string_literal))) 1921 + &lt;/code&gt;&lt;/pre&gt; 1922 + &lt;p&gt;We are definitely looking for a &lt;code&gt;call_expression&lt;/code&gt;:&lt;/p&gt; 1923 + &lt;pre&gt;&lt;code class="language-scheme"&gt;(call_expression) @raise 1924 + &lt;/code&gt;&lt;/pre&gt; 1925 + &lt;p&gt;Whose function name matches &lt;code&gt;std::env::var&lt;/code&gt; or 1926 + &lt;code&gt;std::env::remove_var&lt;/code&gt; at the very least (I know, I know, 1927 + this isn't the most optimal regex):&lt;/p&gt; 1928 + &lt;pre&gt;&lt;code class="language-scheme"&gt;((call_expression 1929 + function: (_) @fn-name) @raise 1930 + (#match? @fn-name &amp;quot;std::env::(var|remove_var)&amp;quot;)) 1931 + &lt;/code&gt;&lt;/pre&gt; 1932 + &lt;p&gt;Let's turn that &lt;code&gt;std::&lt;/code&gt; prefix optional:&lt;/p&gt; 1933 + &lt;pre&gt;&lt;code class="language-scheme"&gt;((call_expression 1934 + function: (_) @fn-name) @raise 1935 + (#match? @fn-name &amp;quot;(std::|)env::(var|remove_var)&amp;quot;)) 1936 + &lt;/code&gt;&lt;/pre&gt; 1937 + &lt;p&gt;And ensure that &lt;code&gt;arguments&lt;/code&gt; is a string:&lt;/p&gt; 1938 + &lt;pre&gt;&lt;code class="language-scheme"&gt;((call_expression 1939 + function: (_) @fn-name 1940 + arguments: (arguments (string_literal))) 1941 + (#match? @fn-name &amp;quot;(std::|)env::(var|remove_var)&amp;quot;)) 1942 + &lt;/code&gt;&lt;/pre&gt; 1943 + &lt;h3 id="running-our-linter"&gt;Running our linter&lt;/h3&gt; 1944 + &lt;p&gt;We could always plug our query into the web playground, but 1945 + let's go a step further:&lt;/p&gt; 1946 + &lt;pre&gt;&lt;code class="language-bash"&gt;cargo new --bin toy-lint 1947 + &lt;/code&gt;&lt;/pre&gt; 1948 + &lt;p&gt;Add &lt;code&gt;tree-sitter&lt;/code&gt; and &lt;code&gt;tree-sitter-rust&lt;/code&gt; to your 1949 + dependencies:&lt;/p&gt; 1950 + &lt;pre&gt;&lt;code class="language-toml"&gt;# within Cargo.toml 1951 + [dependencies] 1952 + tree-sitter = &amp;quot;0.20&amp;quot; 1953 + 1954 + [dependencies.tree-sitter-rust] 1955 + git = &amp;quot;https://github.com/tree-sitter/tree-sitter-rust&amp;quot; 1956 + &lt;/code&gt;&lt;/pre&gt; 1957 + &lt;p&gt;Let's load in some Rust code to work with. As &lt;a href="https://en.wikipedia.org/wiki/Self-reference"&gt;an ode to 1958 + Gödel&lt;/a&gt; 1959 + (G&lt;code&gt;ode&lt;/code&gt;l?), why not load in our linter itself:&lt;/p&gt; 1960 + &lt;pre&gt;&lt;code class="language-rust"&gt;fn main() { 1961 + let src = include_str!(&amp;quot;main.rs&amp;quot;); 1962 + } 1963 + &lt;/code&gt;&lt;/pre&gt; 1964 + &lt;p&gt;Most tree-sitter APIs require a reference to a &lt;code&gt;Language&lt;/code&gt; 1965 + struct, we will be working with Rust if you haven't 1966 + already guessed:&lt;/p&gt; 1967 + &lt;pre&gt;&lt;code class="language-rust"&gt;use tree_sitter::Language; 1968 + 1969 + let rust_lang: Language = tree_sitter_rust::language(); 1970 + &lt;/code&gt;&lt;/pre&gt; 1971 + &lt;p&gt;Enough scaffolding, let's parse some Rust:&lt;/p&gt; 1972 + &lt;pre&gt;&lt;code class="language-rust"&gt;use tree_sitter::Parser; 1973 + 1974 + let mut parser = Parser::new(); 1975 + parser.set_language(rust_lang).unwrap(); 1976 + 1977 + let parse_tree = parser.parse(&amp;amp;src, None).unwrap(); 1978 + &lt;/code&gt;&lt;/pre&gt; 1979 + &lt;p&gt;The second argument to &lt;code&gt;Parser::parse&lt;/code&gt; may be of interest. 1980 + Tree-sitter has this cool feature that allows for quick 1981 + reparsing of existing parse trees if they contain edits. If 1982 + you do happen to want to reparse a source file, you can pass 1983 + in the old tree:&lt;/p&gt; 1984 + &lt;pre&gt;&lt;code class="language-rust"&gt;// if you wish to reparse instead of parse 1985 + old_tree.edit(/* redacted */); 1986 + 1987 + // generate shiny new reparsed tree 1988 + let new_tree = parser.parse(&amp;amp;src, Some(old_tree)).unwrap() 1989 + &lt;/code&gt;&lt;/pre&gt; 1990 + &lt;p&gt;Anyhow (&lt;a href="http://github.com/dtolnay/anyhow"&gt;hah!&lt;/a&gt;), now that we have a parse tree, we can inspect it:&lt;/p&gt; 1991 + &lt;pre&gt;&lt;code class="language-rust"&gt;println!(&amp;quot;{}&amp;quot;, parse_tree.root_node().to_sexp()); 1992 + &lt;/code&gt;&lt;/pre&gt; 1993 + &lt;p&gt;Or better yet, run a query on it:&lt;/p&gt; 1994 + &lt;pre&gt;&lt;code class="language-rust"&gt;use tree_sitter::Query; 1995 + 1996 + let query = Query::new( 1997 + rust_lang, 1998 + r#&amp;quot; 1999 + ((call_expression 2000 + function: (_) @fn-name 2001 + arguments: (arguments (string_literal))) @raise 2002 + (#match? @fn-name &amp;quot;(std::|)env::(var|remove_var)&amp;quot;)) 2003 + &amp;quot;# 2004 + ) 2005 + .unwrap(); 2006 + &lt;/code&gt;&lt;/pre&gt; 2007 + &lt;p&gt;A &lt;code&gt;QueryCursor&lt;/code&gt; is tree-sitter's way of maintaining state as 2008 + we iterate through the matches or captures produced by 2009 + running a query on the parse tree. Observe:&lt;/p&gt; 2010 + &lt;pre&gt;&lt;code class="language-rust"&gt;use tree_sitter::QueryCursor; 2011 + 2012 + let mut query_cursor = QueryCursor::new(); 2013 + let all_matches = query_cursor.matches( 2014 + &amp;amp;query, 2015 + parse_tree.root_node(), 2016 + src.as_bytes(), 2017 + ); 2018 + &lt;/code&gt;&lt;/pre&gt; 2019 + &lt;p&gt;We begin by passing our query to the cursor, followed by the 2020 + &amp;quot;root node&amp;quot;, which is another way of saying, &amp;quot;start from the 2021 + top&amp;quot;, and lastly, the source itself. If you have already 2022 + taken a look at the C API, you will notice that the last 2023 + argument, the source (known as the &lt;code&gt;TextProvider&lt;/code&gt;), is not 2024 + required. The Rust bindings seem to require this argument to 2025 + provide predicate functionality such as &lt;code&gt;#match?&lt;/code&gt; and 2026 + &lt;code&gt;#eq?&lt;/code&gt;.&lt;/p&gt; 2027 + &lt;p&gt;Do something with the matches:&lt;/p&gt; 2028 + &lt;pre&gt;&lt;code class="language-rust"&gt;// get the index of the capture named &amp;quot;raise&amp;quot; 2029 + let raise_idx = query.capture_index_for_name(&amp;quot;raise&amp;quot;).unwrap(); 2030 + 2031 + for each_match in all_matches { 2032 + // iterate over all captures called &amp;quot;raise&amp;quot; 2033 + // ignore captures such as &amp;quot;fn-name&amp;quot; 2034 + for capture in each_match 2035 + .captures 2036 + .iter() 2037 + .filter(|c| c.idx == raise_idx) 2038 + { 2039 + let range = capture.node.range(); 2040 + let text = &amp;amp;src[range.start_byte..range.end_byte]; 2041 + let line = range.start_point.row; 2042 + let col = range.start_point.column; 2043 + println!( 2044 + &amp;quot;[Line: {}, Col: {}] Offending source code: `{}`&amp;quot;, 2045 + line, col, text 2046 + ); 2047 + } 2048 + } 2049 + &lt;/code&gt;&lt;/pre&gt; 2050 + &lt;p&gt;Lastly, add the following line to your source code, to get 2051 + the linter to catch something:&lt;/p&gt; 2052 + &lt;pre&gt;&lt;code class="language-rust"&gt;env::remove_var(&amp;quot;RUST_BACKTRACE&amp;quot;); 2053 + &lt;/code&gt;&lt;/pre&gt; 2054 + &lt;p&gt;And &lt;code&gt;cargo run&lt;/code&gt;:&lt;/p&gt; 2055 + &lt;pre&gt;&lt;code class="language-shell"&gt;λ cargo run 2056 + Compiling toy-lint v0.1.0 (/redacted/path/to/toy-lint) 2057 + Finished dev [unoptimized + debuginfo] target(s) in 0.74s 2058 + Running `target/debug/toy-lint` 2059 + [Line: 40, Col: 4] Offending source code: `env::remove_var(&amp;quot;RUST_BACKTRACE&amp;quot;)` 2060 + &lt;/code&gt;&lt;/pre&gt; 2061 + &lt;p&gt;Thank you tree-sitter!&lt;/p&gt; 2062 + &lt;h3 id="bonus"&gt;Bonus&lt;/h3&gt; 2063 + &lt;p&gt;Keen readers will notice that I avoided &lt;code&gt;std::env::set_var&lt;/code&gt;. 2064 + Because &lt;code&gt;set_var&lt;/code&gt; is called with two arguments, a &amp;quot;key&amp;quot; and 2065 + a &amp;quot;value&amp;quot;, unlike &lt;code&gt;env::var&lt;/code&gt; and &lt;code&gt;env::remove_var&lt;/code&gt;. As a 2066 + result, it requires more juggling:&lt;/p&gt; 2067 + &lt;pre&gt;&lt;code class="language-scheme"&gt;((call_expression 2068 + function: (_) @fn-name 2069 + arguments: (arguments . (string_literal)? . (string_literal) .)) @raise 2070 + (#match? @fn-name &amp;quot;(std::|)env::(var|remove_var|set_var)&amp;quot;)) 2071 + &lt;/code&gt;&lt;/pre&gt; 2072 + &lt;p&gt;The interesting part of this query is the humble &lt;code&gt;.&lt;/code&gt;, the 2073 + &lt;em&gt;anchor&lt;/em&gt; operator. Anchors help constrain child nodes in 2074 + certain ways. In this case, it ensures that we match exactly 2075 + two &lt;code&gt;string_literal&lt;/code&gt;s who are siblings or exactly one 2076 + &lt;code&gt;string_literal&lt;/code&gt; with no siblings. Unfortunately, this query 2077 + also matches the following invalid Rust code:&lt;/p&gt; 2078 + &lt;pre&gt;&lt;code class="language-rust"&gt;// remove_var accepts only 1 arg! 2079 + std::env::remove_var(&amp;quot;RUST_BACKTRACE&amp;quot;, &amp;quot;1&amp;quot;); 2080 + &lt;/code&gt;&lt;/pre&gt; 2081 + &lt;h3 id="notes"&gt;Notes&lt;/h3&gt; 2082 + &lt;p&gt;All-in-all, the query DSL does a great job in lowering the 2083 + bar to writing language tools. The knowledge gained from 2084 + mastering the query DSL can be applied to other languages 2085 + that have tree-sitter grammars too. This query 2086 + detects &lt;code&gt;to_json&lt;/code&gt; methods that do not accept additional 2087 + arguments, in Ruby:&lt;/p&gt; 2088 + &lt;pre&gt;&lt;code class="language-scheme"&gt;((method 2089 + name: (identifier) @fn 2090 + !parameters) 2091 + (#is? @fn &amp;quot;to_json&amp;quot;)) 2092 + &lt;/code&gt;&lt;/pre&gt; 2093 + </description> 2094 + <link>https://oppi.li/posts/lightweight_linting/</link> 2095 + <pubDate>Wed, 26 Jan 2022 00:00:00 +0000</pubDate> 2096 + <guid>https://oppi.li/posts/lightweight_linting/</guid> 2097 + </item> 2098 + <item> 2099 + <title>novice nix: flake templates</title> 2100 + <description>&lt;p&gt;Flakes are very handy to setup entirely pure, 2101 + project-specific dependencies (not just dependencies, but 2102 + build steps, shell environments and more) in a declarative 2103 + way. Writing Flake expressions can get repetitive though, 2104 + oftentimes, you'd much rather start off with a skeleton. 2105 + Luckily, &lt;code&gt;nix&lt;/code&gt; already supports templates!&lt;/p&gt; 2106 + &lt;p&gt;You might already be familiar with &lt;code&gt;nix flake init&lt;/code&gt;, that 2107 + drops a &amp;quot;default&amp;quot; flake expression into your current working 2108 + directory. If you head over to the manpage:&lt;/p&gt; 2109 + &lt;pre&gt;&lt;code class="language-bash"&gt;nix flake init --help 2110 + &lt;/code&gt;&lt;/pre&gt; 2111 + &lt;p&gt;You will read that &lt;code&gt;nix flake init&lt;/code&gt; creates a flake using 2112 + the &amp;quot;default template&amp;quot;. Additionally, you can create a flake 2113 + from a specific template by passing the &lt;code&gt;-t&lt;/code&gt; flag. Where 2114 + does this default originate from?&lt;/p&gt; 2115 + &lt;h2 id="flake-registries"&gt;Flake Registries&lt;/h2&gt; 2116 + &lt;p&gt;Quick detour into registries! Registries are a way to alias 2117 + popular flakes using identifiers:&lt;/p&gt; 2118 + &lt;pre&gt;&lt;code class="language-bash"&gt;# list a few predefined registries 2119 + $ nix registry list 2120 + . . . 2121 + global flake:nixpkgs github:NixOS/nixpkgs 2122 + global flake:patchelf github:NixOS/patchelf 2123 + global flake:nix-serve github:edolstra/nix-serve 2124 + global flake:templates github:NixOS/templates 2125 + global flake:nickel github:tweag/nickel 2126 + . . . 2127 + 2128 + # you can do 2129 + $ nix flake show nickel 2130 + 2131 + # instead of 2132 + $ nix flake show github:tweag/nickel 2133 + 2134 + # which is short for 2135 + $ nix flake show git+https://github.com/tweag/nickel 2136 + &lt;/code&gt;&lt;/pre&gt; 2137 + &lt;p&gt;You might notice a registry called &lt;code&gt;templates&lt;/code&gt; aliased to 2138 + &lt;code&gt;github:NixOS/templates&lt;/code&gt;. Take a peek with &lt;code&gt;nix flake show&lt;/code&gt;:&lt;/p&gt; 2139 + &lt;pre&gt;&lt;code class="language-bash"&gt;$ nix flake show templates 2140 + github:NixOS/templates/79f48a7b822f35c068c5e235da2e9fbd154cecee 2141 + ├───defaultTemplate: template: A very basic flake 2142 + └───templates 2143 + ├───bash-hello: template: An over-engineered Hello World in bash 2144 + ├───c-hello: template: An over-engineered Hello World in C 2145 + ├───rust-web-server: template: A Rust web server including a NixOS module 2146 + ├───simpleContainer: template: A NixOS container running apache-httpd 2147 + └───trivial: template: A very basic flake 2148 + &lt;/code&gt;&lt;/pre&gt; 2149 + &lt;p&gt;Aha! There is a flake output called &lt;code&gt;defaultTemplate&lt;/code&gt;. This 2150 + is the template being sourced when you run &lt;code&gt;nix flake init&lt;/code&gt;. 2151 + Astute readers may conclude the following:&lt;/p&gt; 2152 + &lt;pre&gt;&lt;code class="language-bash"&gt;$ nix flake init 2153 + 2154 + # is equivalent to 2155 + $ nix flake init -t templates#defaultTemplate 2156 + 2157 + # is equivalent to 2158 + $ nix flake init -t github:NixOS/templates#defaultTemplate 2159 + 2160 + # which is short for 2161 + $ nix flake init -t git+https://github.com/NixOS/templates#defaultTemplate 2162 + &lt;/code&gt;&lt;/pre&gt; 2163 + &lt;p&gt;Similarly, the other templates can be accessed via:&lt;/p&gt; 2164 + &lt;pre&gt;&lt;code class="language-bash"&gt;$ nix flake init -t templates#c-hello 2165 + $ nix flake init -t templates#simpleContainer 2166 + # I think you get the drift ... 2167 + &lt;/code&gt;&lt;/pre&gt; 2168 + &lt;h2 id="rolling-your-own-templates"&gt;Rolling your own templates&lt;/h2&gt; 2169 + &lt;p&gt;Alright, so all we need to do is:&lt;/p&gt; 2170 + &lt;ul&gt; 2171 + &lt;li&gt;create a flake with a &lt;code&gt;templates&lt;/code&gt; output 2172 + &lt;/li&gt; 2173 + &lt;li&gt;populate our template directories with content 2174 + &lt;/li&gt; 2175 + &lt;li&gt;(&lt;strong&gt;optionally&lt;/strong&gt;) alias our custom templates flake to an 2176 + identifier using registries, for easier access 2177 + &lt;/li&gt; 2178 + &lt;/ul&gt; 2179 + &lt;p&gt;Start off by creating a directory to store your templates in 2180 + (we will be converting this to a registry later):&lt;/p&gt; 2181 + &lt;pre&gt;&lt;code class="language-bash"&gt;$ mkdir ~/mytemplates 2182 + &lt;/code&gt;&lt;/pre&gt; 2183 + &lt;p&gt;A flake that exposes a &amp;quot;template&amp;quot; as its output looks 2184 + something like this:&lt;/p&gt; 2185 + &lt;pre&gt;&lt;code class="language-nix"&gt;# inside ~/mytemplates/flake.nix 2186 + 2187 + { 2188 + description = &amp;quot;Pepper's flake templates&amp;quot;; 2189 + 2190 + outputs = { self, ... }: { 2191 + templates = { 2192 + latex-report = { 2193 + path = ./latex-report-template; 2194 + description = &amp;quot;A latex whitepaper project&amp;quot;; 2195 + }; 2196 + rust-hello = { 2197 + path = ./rust-hello-template; 2198 + description = &amp;quot;Simple Hello World in Rust&amp;quot;; 2199 + }; 2200 + }; 2201 + }; 2202 + } 2203 + &lt;/code&gt;&lt;/pre&gt; 2204 + &lt;p&gt;The &lt;code&gt;path&lt;/code&gt; attribute to each template is what gets copied 2205 + over when you initialize a flake. Running &lt;code&gt;nix flake init -t .#latex-report&lt;/code&gt; will initialize the current directory with 2206 + the contents of &lt;code&gt;./latex-report-template&lt;/code&gt; (we are yet to 2207 + populate these directories).&lt;/p&gt; 2208 + &lt;p&gt;The output of &lt;code&gt;nix flake show&lt;/code&gt; should be something like:&lt;/p&gt; 2209 + &lt;pre&gt;&lt;code class="language-bash"&gt;$ nix flake show 2210 + path:/home/np/code/nix-stuff/template-tests?narHash=sha256-{...} 2211 + └───templates 2212 + ├───latex-report: template: A latex whitepaper project 2213 + └───rust-hello: template: Simple Hello World in Rust 2214 + &lt;/code&gt;&lt;/pre&gt; 2215 + &lt;p&gt;Populate your template directories with content, here are my 2216 + template directories for example:&lt;/p&gt; 2217 + &lt;pre&gt;&lt;code class="language-bash"&gt;$ tree mytemplates 2218 + mytemplates/ 2219 + ├── flake.nix 2220 + ├── latex-report-template 2221 + │   ├── flake.nix 2222 + │   ├── makefile 2223 + │   └── src 2224 + │   ├── meta.sty 2225 + │   └── report.tex 2226 + └── rust-hello-template 2227 + ├── Cargo.toml 2228 + ├── flake.nix 2229 + └── src 2230 + └── main.rs 2231 + &lt;/code&gt;&lt;/pre&gt; 2232 + &lt;p&gt;And that's it! Start using your templates with:&lt;/p&gt; 2233 + &lt;pre&gt;&lt;code class="language-bash"&gt;$ nix flake init -t ~/mytemplates#rust-hello 2234 + $ tree . 2235 + . 2236 + ├── Cargo.toml 2237 + ├── flake.nix 2238 + └── src 2239 + └── main.rs 2240 + &lt;/code&gt;&lt;/pre&gt; 2241 + &lt;p&gt;To avoid writing &lt;code&gt;~/mytemplates&lt;/code&gt; each time, simply alias it 2242 + to a registry:&lt;/p&gt; 2243 + &lt;pre&gt;&lt;code class="language-bash"&gt;# alias it to `biscuits` 2244 + $ nix registry add biscuits ~/mytemplates 2245 + 2246 + # you will see it listed under `user` registries 2247 + $ nix registry list 2248 + . . . 2249 + user flake:biscuits path:/home/np/template-tests 2250 + . . . 2251 + 2252 + $ nix flake init -t biscuits#latex-report 2253 + &lt;/code&gt;&lt;/pre&gt; 2254 + &lt;h2 id="extending-the-official-templates"&gt;Extending the official templates&lt;/h2&gt; 2255 + &lt;p&gt;I personally, would like the &lt;code&gt;biscuits&lt;/code&gt; registry to include 2256 + not just my homemade templates, but also the templates from 2257 + &lt;code&gt;NixOS/templates&lt;/code&gt; (and maybe a couple of other repositories 2258 + in the wild):&lt;/p&gt; 2259 + &lt;pre&gt;&lt;code class="language-nix"&gt; { 2260 + description = &amp;quot;Pepper's flake templates&amp;quot;; 2261 + 2262 + + inputs = { 2263 + + official-templates.url = github:NixOS/templates; 2264 + + other-templates.url = github:some-other/templates; 2265 + + }; 2266 + 2267 + outputs = { self, official-templates, other-templates ... }: { 2268 + 2269 + templates = { 2270 + latex-report = { 2271 + path = ./latex-report-template; 2272 + description = &amp;quot;A latex whitepaper project&amp;quot;; 2273 + }; 2274 + rust-hello = { 2275 + path = ./rust-hello-template; 2276 + description = &amp;quot;Simple Hello World in Rust, with overloaded Rust toolchain&amp;quot;; 2277 + }; 2278 + } 2279 + + // official-templates.templates 2280 + + // other-templates.templates; 2281 + 2282 + }; 2283 + } 2284 + &lt;/code&gt;&lt;/pre&gt; 2285 + &lt;p&gt;Running &lt;code&gt;nix flake show biscuits&lt;/code&gt; will now list templates 2286 + from the &lt;code&gt;biscuits&lt;/code&gt; registry as well as the ones from 2287 + &lt;code&gt;NixOS/templates&lt;/code&gt;. Ensure that the names don't collide 2288 + though.&lt;/p&gt; 2289 + </description> 2290 + <link>https://oppi.li/posts/novice_nix:_flake_templates/</link> 2291 + <pubDate>Tue, 05 Oct 2021 00:00:00 +0000</pubDate> 2292 + <guid>https://oppi.li/posts/novice_nix:_flake_templates/</guid> 2293 + </item> 2294 + <item> 2295 + <title>SDL2 devlog</title> 2296 + <description>&lt;p&gt;I have been working on an editor for the &lt;a href="https://git.peppe.rs/graphics/obi/about"&gt;One Bit 2297 + Image&lt;/a&gt; file format 2298 + in Rust and SDL2. This entry in my blog follows my progress 2299 + on the editor. The days are listed in reverse chronological 2300 + order, begin from the bottom, if this is your first time on 2301 + this page.&lt;/p&gt; 2302 + &lt;h3 id="day-20"&gt;Day 20&lt;/h3&gt; 2303 + &lt;p&gt;More &lt;code&gt;lisp&lt;/code&gt; stuff! I added a new brush, for rectangular 2304 + selections. While selection doesn't do much on its own, the 2305 + selected area can be passed onto a &lt;code&gt;lisp&lt;/code&gt; procedure, for 2306 + example, a procedure to draw horizontal black and white 2307 + lines:&lt;/p&gt; 2308 + &lt;p&gt;&lt;img src="https://cdn.oppi.li/frU.mp4" alt="Day 20" /&gt;&lt;/p&gt; 2309 + &lt;h3 id="day-19"&gt;Day 19&lt;/h3&gt; 2310 + &lt;p&gt;Attempted &lt;a href="https://peppe.rs/art/conduit.png"&gt;some isometric 2311 + art&lt;/a&gt; within the editor. 2312 + The angles displayed alongside the line brush are handly, 2313 + however, having only a rectangular grid did not help. I 2314 + implemented an isometric grid today. Isometric grids in 2315 + pixel art differ in that the tangent of the isometric angle 2316 + is exactly 0.5! For every pixel down, you go exactly two 2317 + pixels sideways. The math works out really well in the 2318 + drawing procedures too, dealing with floating points is a 2319 + pain.&lt;/p&gt; 2320 + &lt;p&gt;&lt;img src="https://cdn.oppi.li/1Kb.png" alt="Day 19" /&gt;&lt;/p&gt; 2321 + &lt;h3 id="day-18"&gt;Day 18&lt;/h3&gt; 2322 + &lt;p&gt;I added basic support for guides, they can be added and 2323 + activated from the &lt;code&gt;lisp&lt;/code&gt; REPL. Another long standing 2324 + improvement I wanted to make was reworking the pixmap 2325 + drawing procedure. The old procedure draws a square for each 2326 + pixel in the pixmap, coloured according to its value in the 2327 + pixmap. Naturally, this means, for an &lt;strong&gt;NxN&lt;/strong&gt; pixmap, there 2328 + are &lt;strong&gt;N²&lt;/strong&gt; calls to SDL! I reworked this procedure to 2329 + compress each line of the pixmap using RLE (run length 2330 + encoding), and call out to SDL for each run in the line. 2331 + This drastically improved drawing speeds on larger grids. 2332 + The following is a comparison between the two procedures, 2333 + the leftmost picture is the rendered image, the middle 2334 + picture is the optimized drawing procedure (draws each run 2335 + instead of pixel), and the right most picture is the 2336 + primitive drawing procedure (draws each pixel):&lt;/p&gt; 2337 + &lt;p&gt;&lt;img src="https://cdn.oppi.li/U4B.png" alt="Day 18" /&gt;&lt;/p&gt; 2338 + &lt;h3 id="day-17"&gt;Day 17&lt;/h3&gt; 2339 + &lt;p&gt;I decided to give the text-only statusline a touch up, by 2340 + adding a active color and dither level preview. Aligning the 2341 + &amp;quot;widget&amp;quot; to the right of statusline involved a lot more than 2342 + I thought, so I created a ghetto CSS-like rectangle 2343 + placement system to position containers inside containers:&lt;/p&gt; 2344 + &lt;pre&gt;&lt;code class="language-rust"&gt;// roughly something like this 2345 + let statusline = 2346 + Container::new(Offset::Left(0), Offset::Bottom(40)) 2347 + .width(Size::Max) 2348 + .height(Size::Absolute(20)); 2349 + 2350 + let mut primary = Container::uninit() 2351 + .width(Size::Absolute(16)) 2352 + .height(Size::Absolute(16)); 2353 + 2354 + container.place( 2355 + &amp;amp;mut padding_box, 2356 + HorAlign::Right, 2357 + VertAlign::Center 2358 + ); 2359 + &lt;/code&gt;&lt;/pre&gt; 2360 + &lt;p&gt;The result (brush preview on the bottom right):&lt;/p&gt; 2361 + &lt;p&gt;&lt;img src="https://cdn.oppi.li/OtU.mp4" alt="Day 17" /&gt;&lt;/p&gt; 2362 + &lt;h3 id="day-16"&gt;Day 16&lt;/h3&gt; 2363 + &lt;p&gt;The embedded lisp is coming along nicely, users can load 2364 + a custom &lt;code&gt;rc.lisp&lt;/code&gt;, which is evaluated on startup. To 2365 + disable to grid on start, for example:&lt;/p&gt; 2366 + &lt;pre&gt;&lt;code class="language-scheme"&gt;;;; rc.lisp 2367 + (toggle-grid) 2368 + &lt;/code&gt;&lt;/pre&gt; 2369 + &lt;p&gt;Some aliases to switch between brushes:&lt;/p&gt; 2370 + &lt;pre&gt;&lt;code class="language-scheme"&gt;;;; rc.lisp 2371 + (define (brush kind) 2372 + (cond 2373 + ((eq? kind 'f) (brush-fill)) 2374 + ((eq? kind 'c) (brush-circle)) 2375 + ((eq? kind 'l) (brush-line)) 2376 + ((eq? kind 'l+) (brush-line-extend)) 2377 + (else (brush-circle)))) 2378 + &lt;/code&gt;&lt;/pre&gt; 2379 + &lt;p&gt;The following script draws a straight line along a given 2380 + axis, at a given distance from the canvas boundary:&lt;/p&gt; 2381 + &lt;p&gt;&lt;img src="https://cdn.oppi.li/b3i.mp4" alt="Day 16" /&gt;&lt;/p&gt; 2382 + &lt;h3 id="day-15"&gt;Day 15&lt;/h3&gt; 2383 + &lt;p&gt;I began writing a standard library for the lisp, in lisp. It 2384 + includes basic list operations: &lt;code&gt;car&lt;/code&gt;, &lt;code&gt;cdr&lt;/code&gt;, &lt;code&gt;null?&lt;/code&gt;, 2385 + &lt;code&gt;list&lt;/code&gt;, higher order functions: &lt;code&gt;map&lt;/code&gt;, &lt;code&gt;filter&lt;/code&gt;, &lt;code&gt;fold&lt;/code&gt;:&lt;/p&gt; 2386 + &lt;pre&gt;&lt;code class="language-lisp"&gt;(define (member? item ls) 2387 + (fold #f 2388 + (lambda (acc x) (or acc (eq? item x))) 2389 + ls)) 2390 + &lt;/code&gt;&lt;/pre&gt; 2391 + &lt;h3 id="day-14"&gt;Day 14&lt;/h3&gt; 2392 + &lt;p&gt;I attempted a &lt;a href="https://peppe.rs/art/ramen_noodles.png"&gt;small art 2393 + piece&lt;/a&gt; using the 2394 + editor, while it was largely usable, I felt a certain lack 2395 + of feedback. The brushes just didn't relay as much info as 2396 + I'd have liked, for example, the approximate points of the 2397 + line or the angle made by the line against the x-axis. 2398 + Unfortunately, the existing infrastructure around brushes 2399 + and line drawing didn't easily allow for this either. I went 2400 + ahead and reimplemented brushes, and added a new flood fill 2401 + brush too:&lt;/p&gt; 2402 + &lt;p&gt;&lt;img src="https://cdn.oppi.li/8q.mp4" alt="Day 14" /&gt;&lt;/p&gt; 2403 + &lt;h3 id="day-13"&gt;Day 13&lt;/h3&gt; 2404 + &lt;p&gt;I added a few more forms to the &lt;code&gt;lisp&lt;/code&gt; evaluator. It handles 2405 + recursion, definitions, variable mutation and more. The 2406 + prelude contains 20 subroutines so far, including 2407 + comparision and logic operators. The REPL interface on the 2408 + SDL side requires some UX tweaks; environment based 2409 + completion, readline motions sound doable.&lt;/p&gt; 2410 + &lt;p&gt;&lt;img src="https://cdn.oppi.li/u3.mp4" alt="Day 13" /&gt;&lt;/p&gt; 2411 + &lt;h3 id="day-12"&gt;Day 12&lt;/h3&gt; 2412 + &lt;p&gt;I lifted most of 2413 + &lt;a href="https://github.com/murarth/ketos"&gt;murarth/ketos&lt;/a&gt; into the 2414 + editor. &lt;code&gt;ketos&lt;/code&gt;'s implementation of &lt;code&gt;lisp&lt;/code&gt; is too vast for 2415 + my use case. For example, the editor does not need data 2416 + types to handle raw strings or byte strings. I have got a 2417 + basic evaluator running inside the SDL2 context (notice the 2418 + &lt;code&gt;lisp&lt;/code&gt; REPL at the bottom of the window). Over the 2419 + following days, I intend to create a set of prelude 2420 + functions to manipulate the pixmap. Users can implement 2421 + their own brushes, dithering patterns, keybinds and more 2422 + (hopefully).&lt;/p&gt; 2423 + &lt;p&gt;&lt;img src="https://cdn.oppi.li/y0.mp4" alt="Day 12" /&gt;&lt;/p&gt; 2424 + &lt;h3 id="day-11"&gt;Day 11&lt;/h3&gt; 2425 + &lt;p&gt;I intend to supplement the editor with scripting language 2426 + and an inbuilt REPL for the same. I began by implementing a 2427 + text box widget from scratch, with history and readline like 2428 + editing:&lt;/p&gt; 2429 + &lt;p&gt;&lt;img src="https://cdn.oppi.li/Mh.mp4" alt="Day 11" /&gt;&lt;/p&gt; 2430 + &lt;h3 id="day-10"&gt;Day 10&lt;/h3&gt; 2431 + &lt;p&gt;I started reading up on dithering methods and half-toning, I 2432 + wanted to create a dithering brush that would automatically 2433 + produce popular dithering patterns. The method that caught 2434 + my eye (and also the one used most often in pixel art), was 2435 + Bayer's ordered dithering. When applied to a black and white 2436 + image, each pixel, based on its intensity, is mapped to a 2437 + 4x4 grid of pixels. A completely empty (completely black) 2438 + 4x4 grid represents zero intensity, and a filled 4x4 grid 2439 + represents full intensity. Bayer's ordered dithering can 2440 + produce 15 steps of intensity between zero and full (by 2441 + switching on exactly 1 pixel more at each level), thus, 2442 + being able to draw 17 &amp;quot;shades&amp;quot; from white to black. Creating 2443 + a dithering brush from here was fairly trivial. Our pixmap 2444 + is supposed to represent the final dithered image, it must 2445 + be divided into 4x4 grids. Each grid is colored based on the 2446 + intensity of the brush passing over it:&lt;/p&gt; 2447 + &lt;p&gt;&lt;img src="https://cdn.oppi.li/Mn.png" alt="Day 10" /&gt;&lt;/p&gt; 2448 + &lt;h3 id="day-9"&gt;Day 9&lt;/h3&gt; 2449 + &lt;p&gt;I started working towards an interface. I like the idea of a 2450 + largely read-only HUD, i. e., an interface that simply 2451 + describes the state of the application. Changes to this 2452 + state are initiated via keybinds or text commands. I am 2453 + proud of the symmetry indicator; &lt;code&gt;-&lt;/code&gt; for horizontal 2454 + symmetry, &lt;code&gt;|&lt;/code&gt; for vertical symmetry, &lt;code&gt;+&lt;/code&gt; for radial 2455 + symmetry.&lt;/p&gt; 2456 + &lt;p&gt;&lt;img src="https://cdn.oppi.li/hx.png" alt="Day 9" /&gt;&lt;/p&gt; 2457 + &lt;h3 id="day-8"&gt;Day 8&lt;/h3&gt; 2458 + &lt;p&gt;One of my favourite features of GIMP was symmetric editing. 2459 + I added some coordinate geometry primitives to my pixmap 2460 + abstraction, allowing for mirroring and reflecting figures 2461 + about lines or points. The result was an ergonomic function 2462 + that applies symmetry to any painting operation, (undo/redo 2463 + works as expected):&lt;/p&gt; 2464 + &lt;pre&gt;&lt;code class="language-rust"&gt;let line = self.pixmap.get_line(start, end); 2465 + let sym_line = self.symmetry.apply(&amp;amp;line); 2466 + for point on line.extend(sym_line) { 2467 + // draw to window 2468 + } 2469 + &lt;/code&gt;&lt;/pre&gt; 2470 + &lt;p&gt;&lt;img src="https://cdn.oppi.li/B1.mp4" alt="Day 8" /&gt;&lt;/p&gt; 2471 + &lt;h3 id="day-7"&gt;Day 7&lt;/h3&gt; 2472 + &lt;p&gt;Bresenham saves the day again! This time, I implemented his 2473 + line drawing algorithm, to, well, draw lines. Each point on 2474 + the line is then &amp;quot;buffed&amp;quot; based on the active brush size. 2475 + Today's changes fit in very well with the undo system and 2476 + the brush size feature. Creating the right abstractions, one 2477 + at a time :)&lt;/p&gt; 2478 + &lt;p&gt;&lt;img src="https://cdn.oppi.li/xt.mp4" alt="Day 7" /&gt;&lt;/p&gt; 2479 + &lt;h3 id="day-6"&gt;Day 6&lt;/h3&gt; 2480 + &lt;p&gt;I extended Bresenham's algorithm to draw not just circle 2481 + outlines, but also generate their fills. Unlike Bresenham's 2482 + algorithm, this variant generates points for two quadrants 2483 + at once, these points are mirrored over the dividing axis to 2484 + generate the other two quadrants.&lt;/p&gt; 2485 + &lt;p&gt;&lt;img src="https://cdn.oppi.li/f3.png" alt="Day 6" /&gt;&lt;/p&gt; 2486 + &lt;h3 id="day-5"&gt;Day 5&lt;/h3&gt; 2487 + &lt;p&gt;I discovered and implemented Bresenham's algorithm for 2488 + efficient circle drawing. The algorithm allowed for sized 2489 + circular brushes, something I really liked from GIMP. Very 2490 + convenient that the Wikipedia page for Bresenham's algorithm 2491 + also includes a section about optimizing for integer based 2492 + arithmetic. I managed to abstract out another giant 2493 + component of the application, the pixmap. Any image is just 2494 + a grid of pixels (a pixmap), where the pixel's value is 2495 + decided by the application (1-bit in my case). I could 2496 + potentially extend the application to a 24-bit image editor!&lt;/p&gt; 2497 + &lt;p&gt;&lt;img src="https://cdn.oppi.li/Kh.mp4" alt="Day 5" /&gt;&lt;/p&gt; 2498 + &lt;h3 id="day-4"&gt;Day 4&lt;/h3&gt; 2499 + &lt;p&gt;I created a generic &amp;quot;undo stack&amp;quot; data structure that allows 2500 + for infinite &amp;quot;undos&amp;quot; and &amp;quot;redos&amp;quot;. Every modification 2501 + operation to the grid is persisted to the application state. 2502 + A couple of keybinds allow the user to revert and re-apply 2503 + these operations! I expect abstracting this component will 2504 + come in handy down the line.&lt;/p&gt; 2505 + &lt;p&gt;&lt;img src="https://cdn.oppi.li/w5.mp4" alt="Day 4" /&gt;&lt;/p&gt; 2506 + &lt;h3 id="day-3"&gt;Day 3&lt;/h3&gt; 2507 + &lt;p&gt;I implemented the bare minimum required to call the program 2508 + an &amp;quot;editor&amp;quot;. The application displays a grid, tracks mouse 2509 + events, paints white to the canvas on left click, and black 2510 + to the canvas on right click. I created a make-shift MVC 2511 + architecture à la Elm in Rust.&lt;/p&gt; 2512 + &lt;p&gt;&lt;img src="https://cdn.oppi.li/GF.mp4" alt="Day 3" /&gt;&lt;/p&gt; 2513 + &lt;h3 id="day-2"&gt;Day 2&lt;/h3&gt; 2514 + &lt;p&gt;I started figuring out event handling today. Implemented a 2515 + couple of keybinds to zoom in/out of the drawing area. 2516 + Conversions of SDL2 coordinates (measured in signed 32 bit 2517 + integers) to my internal &amp;quot;drawing area&amp;quot; coordinates 2518 + (measured in unsigned 32 bit integers) is very annoying. 2519 + Hopefully the unchecked conversions won't haunt me later.&lt;/p&gt; 2520 + &lt;p&gt;&lt;img src="https://cdn.oppi.li/L4.mp4" alt="Day 2" /&gt;&lt;/p&gt; 2521 + &lt;h3 id="day-1"&gt;Day 1&lt;/h3&gt; 2522 + &lt;p&gt;Getting started with Rust and SDL2 is very straightforward. 2523 + The &lt;code&gt;rust-sdl2&lt;/code&gt; library contains some detailed examples that 2524 + allowed me to get all the way to drawing a grid from a 2525 + &lt;code&gt;Vec&amp;lt;bool&amp;gt;&lt;/code&gt;:&lt;/p&gt; 2526 + &lt;p&gt;&lt;img src="https://cdn.oppi.li/Ma.png" alt="Day 1" /&gt;&lt;/p&gt; 2527 + </description> 2528 + <link>https://oppi.li/posts/SDL2_devlog/</link> 2529 + <pubDate>Sun, 11 Apr 2021 00:00:00 +0000</pubDate> 2530 + <guid>https://oppi.li/posts/SDL2_devlog/</guid> 2531 + </item> 2532 + <item> 2533 + <title>self-hosting git</title> 2534 + <description>&lt;p&gt;Earlier this week, I began migrating my repositories from 2535 + Github to &lt;a href="https://git.zx2c4.com/cgit/about/"&gt;cgit&lt;/a&gt;. If you care at 2536 + all about big corporates turning open-source into a T-shirt 2537 + farming service, this is the way to go.&lt;/p&gt; 2538 + &lt;h3 id="offerings"&gt;Offerings&lt;/h3&gt; 2539 + &lt;p&gt;cgit is &lt;em&gt;very&lt;/em&gt; bare bones. It is 2540 + &lt;a href="https://tools.ietf.org/html/rfc3875"&gt;cgi-based&lt;/a&gt; web 2541 + interface to git, and nothing more. You may browse 2542 + repositories, view diffs, commit logs and even clone via 2543 + http. If you are looking to replace Github with cgit, keep 2544 + in mind that cgit does not handle issues or pull/merge 2545 + requests. If people wish to contribute to your work, they 2546 + would have to send you a patch via email.&lt;/p&gt; 2547 + &lt;h3 id="setup"&gt;Setup&lt;/h3&gt; 2548 + &lt;p&gt;Installing cgit is fairly straightforward, if you would 2549 + like to compile it from source:&lt;/p&gt; 2550 + &lt;pre&gt;&lt;code class="language-sh"&gt;# fetch 2551 + git clone https://git.zx2c4.com &amp;amp;&amp;amp; cd cgit 2552 + git submodule init 2553 + git submodule update 2554 + 2555 + # install 2556 + make NO_LUA=1 2557 + sudo make install 2558 + &lt;/code&gt;&lt;/pre&gt; 2559 + &lt;p&gt;This would drop the cgit cgi script (and the default css) 2560 + into &lt;code&gt;/var/www/htdocs/cgit&lt;/code&gt;. You may configure cgit by 2561 + editing &lt;code&gt;/etc/cgitrc&lt;/code&gt;. I specify the &lt;code&gt;NO_LUA&lt;/code&gt; flag to 2562 + compile without lua support, exclude that flag if you would 2563 + like to extend cgit via lua scripts.&lt;/p&gt; 2564 + &lt;h3 id="going-live"&gt;Going live&lt;/h3&gt; 2565 + &lt;p&gt;You might want to use, 2566 + &lt;a href="https://github.com/gnosek/fcgiwrap"&gt;fcgiwrap&lt;/a&gt;, a 2567 + &lt;a href="http://www.nongnu.org/fastcgi"&gt;fastcgi&lt;/a&gt; wrapper for &lt;code&gt;cgi&lt;/code&gt; 2568 + scripts,&lt;/p&gt; 2569 + &lt;pre&gt;&lt;code class="language-sh"&gt;sudo apt install fcgiwrap 2570 + sudo systemctl start fcgiwrap.socket 2571 + &lt;/code&gt;&lt;/pre&gt; 2572 + &lt;p&gt;Expose the cgit cgi script to the web via &lt;code&gt;nginx&lt;/code&gt;:&lt;/p&gt; 2573 + &lt;pre&gt;&lt;code&gt;# nginx.conf 2574 + server { 2575 + listen 80; 2576 + server_name git.example.com; 2577 + 2578 + # serve static files 2579 + location ~* ^.+\.(css|png|ico)$ { 2580 + root /var/www/htdocs/cgit; 2581 + } 2582 + 2583 + location / { 2584 + fastcgi_pass unix:/run/fcgiwrap.socket; 2585 + fastcgi_param SCRIPT_FILENAME /var/www/htdocs/cgit/cgit.cgi; # the default location of the cgit cgi script 2586 + fastcgi_param PATH_INFO $uri; 2587 + fastcgi_param QUERY_STRING $args; 2588 + } 2589 + } 2590 + &lt;/code&gt;&lt;/pre&gt; 2591 + &lt;p&gt;Point cgit to your git repositories:&lt;/p&gt; 2592 + &lt;pre&gt;&lt;code&gt;# /etc/cgitrc 2593 + scan-path=/path/to/git/repos 2594 + &lt;/code&gt;&lt;/pre&gt; 2595 + &lt;p&gt;&lt;em&gt;&lt;strong&gt;Note&lt;/strong&gt;&lt;/em&gt;: &lt;em&gt;&lt;code&gt;scan-path&lt;/code&gt; works best if you stick it at the end of your 2596 + &lt;code&gt;cgitrc&lt;/code&gt;&lt;/em&gt;.&lt;/p&gt; 2597 + &lt;p&gt;You may now create remote repositories at 2598 + &lt;code&gt;/path/to/git/repos&lt;/code&gt;, via:&lt;/p&gt; 2599 + &lt;pre&gt;&lt;code&gt;git init --bare 2600 + &lt;/code&gt;&lt;/pre&gt; 2601 + &lt;p&gt;Add the remote to your local repository:&lt;/p&gt; 2602 + &lt;pre&gt;&lt;code&gt;git remote set-url origin user@remote:/above/path 2603 + git push origin master 2604 + &lt;/code&gt;&lt;/pre&gt; 2605 + &lt;h3 id="configuration"&gt;Configuration&lt;/h3&gt; 2606 + &lt;p&gt;cgit is fairly easy to configure, all configuration 2607 + options can be found &lt;a href="https://git.zx2c4.com/cgit/tree/cgitrc.5.txt"&gt;in the 2608 + manual&lt;/a&gt;, here 2609 + are a couple of cool ones though:&lt;/p&gt; 2610 + &lt;p&gt;&lt;strong&gt;enable-commit-graph&lt;/strong&gt;: Generates a text based graphical 2611 + representation of the commit history, similar to &lt;code&gt;git log --graph --oneline&lt;/code&gt;.&lt;/p&gt; 2612 + &lt;pre&gt;&lt;code&gt;| * | Add support for configuration file 2613 + * | | simplify command parsing logic 2614 + * | | Refactor parsers 2615 + * | | Add basic tests 2616 + * | | Merge remote-tracking branch 'origin/master' in... 2617 + |\| | 2618 + | * | add installation instructions for nix 2619 + | * | switch to pancurses backendv0.2.2 2620 + | * | bump to v0.2.2 2621 + * | | Merge branch 'master' into feature/larger-names... 2622 + |\| | 2623 + | * | enable feature based compilation to support win... 2624 + | * | remove dependency on rustc v1.45, bump to v0.2.... 2625 + | * | Merge branch 'feature/windows' of https://git... 2626 + | |\ \ 2627 + | | * | add windows to github actions 2628 + | | * | switch to crossterm backend 2629 + | | * | Merge branch 'fix/duplicate-habits' 2630 + | | |\ \ 2631 + | | | * | move duplicate check to command parsing blo... 2632 + &lt;/code&gt;&lt;/pre&gt; 2633 + &lt;p&gt;&lt;strong&gt;section-from-path&lt;/strong&gt;: This option paired with &lt;code&gt;scan-path&lt;/code&gt; 2634 + will automatically generate sections in your cgit index 2635 + page, from the path to each repo. For example, the directory 2636 + structure used to generate sections on &lt;a href="https://git.peppe.rs"&gt;my cgit 2637 + instance&lt;/a&gt; looks like this:&lt;/p&gt; 2638 + &lt;pre&gt;&lt;code&gt;├── cli 2639 + │ ├── dijo 2640 + │ ├── eva 2641 + │ ├── pista 2642 + │ ├── taizen 2643 + │ └── xcursorlocate 2644 + ├── config 2645 + │ ├── dotfiles 2646 + │ └── nixos 2647 + ├── fonts 2648 + │ ├── curie 2649 + │ └── scientifica 2650 + ├── languages 2651 + │ └── lisk 2652 + ├── libs 2653 + │ ├── cutlass 2654 + │ └── fondant 2655 + ├── terminfo 2656 + ├── university 2657 + │ └── furby 2658 + └── web 2659 + └── isostatic 2660 + &lt;/code&gt;&lt;/pre&gt; 2661 + &lt;h3 id="ease-of-use"&gt;Ease of use&lt;/h3&gt; 2662 + &lt;p&gt;As I mentioned before, &lt;code&gt;cgit&lt;/code&gt; is simply a view into your git 2663 + repositories, you will have to manually create new 2664 + repositories by entering your remote and using &lt;code&gt;git init --bare&lt;/code&gt;. Here are a couple of scripts I wrote to perform 2665 + actions on remotes, think of it as a smaller version of 2666 + Github's &lt;code&gt;gh&lt;/code&gt; program.&lt;/p&gt; 2667 + &lt;p&gt;You may save these scripts as &lt;code&gt;git-script-name&lt;/code&gt; and drop 2668 + them in your &lt;code&gt;$PATH&lt;/code&gt;, and git will automatically add an 2669 + alias called &lt;code&gt;script-name&lt;/code&gt;, callable via:&lt;/p&gt; 2670 + &lt;pre&gt;&lt;code&gt;git script-name 2671 + &lt;/code&gt;&lt;/pre&gt; 2672 + &lt;h4 id="git-new-repo"&gt;git-new-repo&lt;/h4&gt; 2673 + &lt;p&gt;Creates a new repository on your remote, 2674 + the first arg may be a path (section/repo-name) or just the 2675 + repo name:&lt;/p&gt; 2676 + &lt;pre&gt;&lt;code&gt;#! /usr/bin/env bash 2677 + # 2678 + # usage: 2679 + # git new-repo section/repo-name 2680 + # 2681 + # example: 2682 + # git new-repo fonts/scientifica 2683 + # creates: user@remote:fonts/scientifica 2684 + 2685 + if [ $# -eq 0 ]; then 2686 + echo &amp;quot;requires an arg&amp;quot; 2687 + exit 1 2688 + fi 2689 + 2690 + ssh user@remote git init --bare &amp;quot;$1&amp;quot;; 2691 + &lt;/code&gt;&lt;/pre&gt; 2692 + &lt;h4 id="git-set-desc"&gt;git-set-desc&lt;/h4&gt; 2693 + &lt;p&gt;To set a one line repository 2694 + description. It simply copies the local &lt;code&gt;.git/description&lt;/code&gt;, 2695 + into &lt;code&gt;remote/description&lt;/code&gt;. &lt;code&gt;cgit&lt;/code&gt; displays the contents of 2696 + this file on the index page:&lt;/p&gt; 2697 + &lt;pre&gt;&lt;code&gt;#! /usr/bin/env bash 2698 + # 2699 + # usage: 2700 + # enter repo description into .git/description and run: 2701 + # git set-desc 2702 + 2703 + remote=$(git remote get-url --push origin) 2704 + scp .git/description &amp;quot;$remote/description&amp;quot; 2705 + &lt;/code&gt;&lt;/pre&gt; 2706 + </description> 2707 + <link>https://oppi.li/posts/self-hosting_git/</link> 2708 + <pubDate>Sat, 17 Oct 2020 00:00:00 +0000</pubDate> 2709 + <guid>https://oppi.li/posts/self-hosting_git/</guid> 2710 + </item> 2711 + <item> 2712 + <title>nixOS</title> 2713 + <description>&lt;p&gt;I have been eyeing operating systems with functional package 2714 + managers for a while now, aka, NixOS or Guix. Reproducible 2715 + builds, declarative and rollback-able system configuration, 2716 + system consistency, all sound pretty cool. I have been using 2717 + NixOS for about a month now.&lt;/p&gt; 2718 + &lt;h3 id="installation"&gt;Installation&lt;/h3&gt; 2719 + &lt;p&gt;I went with their minimal installation ISO. The installation 2720 + was pretty smooth from start to end, no hitches there. The 2721 + entire &lt;a href="https://nixos.org/manual/nixos/stable/"&gt;manual&lt;/a&gt; is 2722 + available offline, and is accessible during the 2723 + installation. Very handy.&lt;/p&gt; 2724 + &lt;h3 id="setup"&gt;Setup&lt;/h3&gt; 2725 + &lt;p&gt;The entire system is configured via 2726 + &lt;code&gt;/etc/nixos/configuration.nix&lt;/code&gt;. Wifi, &lt;code&gt;libinput&lt;/code&gt; gestures, 2727 + audio, locale settings, there are options for literally 2728 + everything. You can declaratively write down the packages 2729 + you want installed too. With fresh installs of most distros, 2730 + I usually fumble with getting things like screen backlight 2731 + and media keys to work. If I do manage to fix it, I can't 2732 + carry it forward to future installations trivially. Getting 2733 + all my hardware to work on NixOS is as easy as:&lt;/p&gt; 2734 + &lt;pre&gt;&lt;code&gt;{ 2735 + server.xserver.libinput.enable = true; # touchpad 2736 + programs.light.enable = true; # backlight 2737 + hardware.pulseaudio.enable = true; # audio 2738 + networking.wireless.enable = true; # wifi 2739 + } 2740 + &lt;/code&gt;&lt;/pre&gt; 2741 + &lt;h3 id="developing-with-nix"&gt;Developing with Nix&lt;/h3&gt; 2742 + &lt;p&gt;Nix makes it easy to enter environments that aren't affected 2743 + by your system configuration using &lt;code&gt;nix-shell&lt;/code&gt;.&lt;/p&gt; 2744 + &lt;p&gt;Builds may be generated by specifying a &lt;code&gt;default.nix&lt;/code&gt; file, 2745 + and running &lt;code&gt;nix-build&lt;/code&gt;. Conventional package managers 2746 + require you to specify a dependency list, but there is no 2747 + guarantee that this list is complete. The package will build 2748 + on your machine even if you forget a dependency. However, 2749 + with Nix, packages are installed to &lt;code&gt;/nix/store&lt;/code&gt;, and not 2750 + global paths such as &lt;code&gt;/usr/bin/...&lt;/code&gt;, if your project builds, 2751 + it means you have included every last one.&lt;/p&gt; 2752 + &lt;p&gt;Issues on most my projects have been &amp;quot;unable to build 2753 + because &lt;code&gt;libxcb&lt;/code&gt; is missing&amp;quot;, or &amp;quot;this version of &lt;code&gt;openssl&lt;/code&gt; 2754 + is too old&amp;quot;. Tools like &lt;code&gt;cargo&lt;/code&gt; and &lt;code&gt;pip&lt;/code&gt; are poor package 2755 + managers. While they &lt;em&gt;can&lt;/em&gt; guarantee that Rust or Python 2756 + dependencies are met, they make assumptions about the 2757 + target system.&lt;/p&gt; 2758 + &lt;p&gt;For example, &lt;a href="https://github.com/nerdypepper/site"&gt;this 2759 + website&lt;/a&gt; is now built 2760 + using Nix, anyone using Nix may simply, clone the repository 2761 + and run &lt;code&gt;./generate.sh&lt;/code&gt;, and it would &lt;em&gt;just work&lt;/em&gt;, while 2762 + keeping your global namespace clean™:&lt;/p&gt; 2763 + &lt;pre&gt;&lt;code class="language-bash"&gt;#! /usr/bin/env nix-shell 2764 + #! nix-shell -i bash -p eva pandoc esh 2765 + 2766 + # some bash magic ;) 2767 + &lt;/code&gt;&lt;/pre&gt; 2768 + &lt;p&gt;Dependencies are included with the &lt;code&gt;-p&lt;/code&gt; flag, the shell 2769 + script is executed with an interpreter, specified with the 2770 + &lt;code&gt;-i&lt;/code&gt; flag.&lt;/p&gt; 2771 + &lt;h3 id="impressions"&gt;Impressions&lt;/h3&gt; 2772 + &lt;p&gt;NixOS is by no means, simple. As a newcomer, using Nix was 2773 + not easy, heck, I had to learn a purely functional, lazy 2774 + language to just build programs. There is a lot to be 2775 + desired on the tooling front as well. A well fleshed out LSP 2776 + plugin would be nice (&lt;a href="https://github.com/nix-community/rnix-lsp"&gt;rnix-lsp looks 2777 + promising&lt;/a&gt;).&lt;/p&gt; 2778 + &lt;p&gt;Being able to rollback changes at a system level is cool. 2779 + Package broke something? Just &lt;code&gt;nixos-rebuild switch --rollback&lt;/code&gt;! Deleted &lt;code&gt;nix&lt;/code&gt; by mistake? Find the binary in 2780 + &lt;code&gt;/nix/store&lt;/code&gt; and rollback! You aren't punished for not 2781 + thinking twice.&lt;/p&gt; 2782 + &lt;p&gt;I don't see myself switching to anything else in the near 2783 + future, NixOS does a lot of things right. If I ever need to 2784 + reinstall NixOS, I can generate an &lt;a href="https://github.com/nix-community/nixos-generators"&gt;image of my current 2785 + system&lt;/a&gt;.&lt;/p&gt; 2786 + &lt;p&gt;&lt;a href="https://cdn.oppi.li/6m.png"&gt;&lt;img src="https://cdn.oppi.li/6m.png" alt="" /&gt;&lt;/a&gt;&lt;/p&gt; 2787 + </description> 2788 + <link>https://oppi.li/posts/nixOS/</link> 2789 + <pubDate>Tue, 01 Sep 2020 00:00:00 +0000</pubDate> 2790 + <guid>https://oppi.li/posts/nixOS/</guid> 2791 + </item> 2792 + <item> 2793 + <title>gripes with go</title> 2794 + <description>&lt;p&gt;You've read a lot of posts about the shortcomings of the Go 2795 + programming language, so what's one more.&lt;/p&gt; 2796 + &lt;ol&gt; 2797 + &lt;li&gt;&lt;a href="#lack-of-sum-types"&gt;Lack of sum types&lt;/a&gt; 2798 + &lt;/li&gt; 2799 + &lt;li&gt;&lt;a href="#type-assertions"&gt;Type assertions&lt;/a&gt; 2800 + &lt;/li&gt; 2801 + &lt;li&gt;&lt;a href="#date-and-time"&gt;Date and Time&lt;/a&gt; 2802 + &lt;/li&gt; 2803 + &lt;li&gt;&lt;a href="#statements-over-expressions"&gt;Statements over Expressions&lt;/a&gt; 2804 + &lt;/li&gt; 2805 + &lt;li&gt;&lt;a href="#erroring-out-on-unused-variables"&gt;Erroring out on unused variables&lt;/a&gt; 2806 + &lt;/li&gt; 2807 + &lt;li&gt;&lt;a href="#error-handling"&gt;Error handling&lt;/a&gt; 2808 + &lt;/li&gt; 2809 + &lt;/ol&gt; 2810 + &lt;h3 id="lack-of-sum-types"&gt;Lack of Sum types&lt;/h3&gt; 2811 + &lt;p&gt;A &amp;quot;Sum&amp;quot; type is a data type that can hold one of many states 2812 + at a given time, similar to how a boolean can hold a true or 2813 + a false, not too different from an &lt;code&gt;enum&lt;/code&gt; type in C. Go 2814 + lacks &lt;code&gt;enum&lt;/code&gt; types unfortunately, and you are forced to 2815 + resort to crafting your own substitute.&lt;/p&gt; 2816 + &lt;p&gt;A type to represent gender for example:&lt;/p&gt; 2817 + &lt;pre&gt;&lt;code class="language-go"&gt;type Gender int 2818 + 2819 + const ( 2820 + Male Gender = iota // assigns Male to 0 2821 + Female // assigns Female to 1 2822 + Other // assigns Other to 2 2823 + ) 2824 + 2825 + fmt.Println(&amp;quot;My gender is &amp;quot;, Male) 2826 + // My gender is 0 2827 + // Oops! We have to implement String() for Gender ... 2828 + 2829 + func (g Gender) String() string { 2830 + switch (g) { 2831 + case 0: return &amp;quot;Male&amp;quot; 2832 + case 1: return &amp;quot;Female&amp;quot; 2833 + default: return &amp;quot;Other&amp;quot; 2834 + } 2835 + } 2836 + 2837 + // You can accidentally do stupid stuff like: 2838 + gender := Male + 1 2839 + &lt;/code&gt;&lt;/pre&gt; 2840 + &lt;p&gt;The Haskell equivalent of the same:&lt;/p&gt; 2841 + &lt;pre&gt;&lt;code class="language-haskell"&gt;data Gender = Male 2842 + | Female 2843 + | Other 2844 + deriving (Show) 2845 + 2846 + print $ &amp;quot;My gender is &amp;quot; ++ (show Male) 2847 + &lt;/code&gt;&lt;/pre&gt; 2848 + &lt;h3 id="type-assertions"&gt;Type Assertions&lt;/h3&gt; 2849 + &lt;p&gt;A downcast with an optional error check? What could go 2850 + wrong?&lt;/p&gt; 2851 + &lt;p&gt;Type assertions in Go allow you to do:&lt;/p&gt; 2852 + &lt;pre&gt;&lt;code class="language-go"&gt;var x interface{} = 7 2853 + y, goodToGo := x.(int) 2854 + if goodToGo { 2855 + fmt.Println(y) 2856 + } 2857 + &lt;/code&gt;&lt;/pre&gt; 2858 + &lt;p&gt;The error check however is optional:&lt;/p&gt; 2859 + &lt;pre&gt;&lt;code class="language-go"&gt;var x interface{} = 7 2860 + var y := x.(float64) 2861 + fmt.Println(y) 2862 + // results in a runtime error: 2863 + // panic: interface conversion: interface {} is int, not float64 2864 + &lt;/code&gt;&lt;/pre&gt; 2865 + &lt;h3 id="date-and-time"&gt;Date and Time&lt;/h3&gt; 2866 + &lt;p&gt;Anyone that has written Go previously, will probably already 2867 + know what I am getting at here. For the uninitiated, parsing 2868 + and formatting dates in Go requires a &amp;quot;layout&amp;quot;. This 2869 + &amp;quot;layout&amp;quot; is based on magical reference date:&lt;/p&gt; 2870 + &lt;pre&gt;&lt;code&gt;Mon Jan 2 15:04:05 MST 2006 2871 + &lt;/code&gt;&lt;/pre&gt; 2872 + &lt;p&gt;Which is the date produced when you write the first seven 2873 + natural numbers like so:&lt;/p&gt; 2874 + &lt;pre&gt;&lt;code&gt;01/02 03:04:05 '06 -0700 2875 + &lt;/code&gt;&lt;/pre&gt; 2876 + &lt;p&gt;Parsing a string in &lt;code&gt;YYYY-MM-DD&lt;/code&gt; format would look something 2877 + like:&lt;/p&gt; 2878 + &lt;pre&gt;&lt;code class="language-go"&gt;const layout = &amp;quot;2006-01-02&amp;quot; 2879 + time.Parse(layout, &amp;quot;2020-08-01&amp;quot;) 2880 + &lt;/code&gt;&lt;/pre&gt; 2881 + &lt;p&gt;This so-called &amp;quot;intuitive&amp;quot; method of formatting dates 2882 + doesn't allow you to print &lt;code&gt;0000 hrs&lt;/code&gt; as &lt;code&gt;2400 hrs&lt;/code&gt;, it 2883 + doesn't allow you to omit the leading zero in 24 hour 2884 + formats. It is rife with inconveniences, if only there were 2885 + a &lt;a href="https://man7.org/linux/man-pages/man3/strftime.3.html"&gt;tried and 2886 + tested&lt;/a&gt; 2887 + date formatting convention ...&lt;/p&gt; 2888 + &lt;h3 id="statements-over-expressions"&gt;Statements over Expressions&lt;/h3&gt; 2889 + &lt;p&gt;Statements have side effects, expressions return values. 2890 + More often than not, expressions are easier to understand at 2891 + a glance: evaluate the LHS and assign the same to the RHS.&lt;/p&gt; 2892 + &lt;p&gt;Rust allows you to create local namespaces, and treats 2893 + blocks (&lt;code&gt;{}&lt;/code&gt;) as expressions:&lt;/p&gt; 2894 + &lt;pre&gt;&lt;code class="language-rust"&gt;let twenty_seven = { 2895 + let three = 1 + 2; 2896 + let nine = three * three; 2897 + nine * three 2898 + }; 2899 + &lt;/code&gt;&lt;/pre&gt; 2900 + &lt;p&gt;The Go equivalent of the same:&lt;/p&gt; 2901 + &lt;pre&gt;&lt;code class="language-go"&gt;twenty_seven := nil 2902 + 2903 + three := 1 + 2 2904 + nine := three * three 2905 + twenty_seven = nine * three 2906 + &lt;/code&gt;&lt;/pre&gt; 2907 + &lt;h3 id="erroring-out-on-unused-variables"&gt;Erroring out on unused variables&lt;/h3&gt; 2908 + &lt;p&gt;Want to quickly prototype something? Go says no! In all 2909 + seriousness, a warning would suffice, I don't want to have 2910 + to go back and comment each unused import out, only to come 2911 + back and uncomment them a few seconds later.&lt;/p&gt; 2912 + &lt;h3 id="error-handling"&gt;Error handling&lt;/h3&gt; 2913 + &lt;pre&gt;&lt;code class="language-go"&gt;if err != nil { ... } 2914 + &lt;/code&gt;&lt;/pre&gt; 2915 + &lt;p&gt;Need I say more? I will, for good measure:&lt;/p&gt; 2916 + &lt;ol&gt; 2917 + &lt;li&gt;Error handling is optional 2918 + &lt;/li&gt; 2919 + &lt;li&gt;Errors are propagated via a clunky &lt;code&gt;if&lt;/code&gt; + &lt;code&gt;return&lt;/code&gt; statement 2920 + &lt;/li&gt; 2921 + &lt;/ol&gt; 2922 + &lt;p&gt;I prefer Haskell's &amp;quot;Monadic&amp;quot; error handling, which is 2923 + employed by Rust as well:&lt;/p&gt; 2924 + &lt;pre&gt;&lt;code class="language-rust"&gt;// 1. error handling is compulsory 2925 + // 2. errors are propagated with the `?` operator 2926 + fn foo() -&amp;gt; Result&amp;lt;String, io::Error&amp;gt; { 2927 + let mut f = File::open(&amp;quot;foo.txt&amp;quot;)?; // return if error 2928 + let mut s = String::new(); 2929 + 2930 + f.read_to_string(&amp;amp;mut s)?; // return if error 2931 + 2932 + Ok(s) // all good, return a string inside a `Result` context 2933 + } 2934 + 2935 + fn main() { 2936 + // `contents` is an enum known as Result: 2937 + let contents = foo(); 2938 + match contents { 2939 + Ok(c) =&amp;gt; println!(c), 2940 + Err(e) =&amp;gt; eprintln!(e) 2941 + } 2942 + } 2943 + &lt;/code&gt;&lt;/pre&gt; 2944 + &lt;h3 id="conclusion"&gt;Conclusion&lt;/h3&gt; 2945 + &lt;p&gt;I did not want to conclude without talking about stylistic 2946 + choices, lack of metaprogramming, bizzare export rules, but, 2947 + I am too busy converting my &lt;code&gt;interface{}&lt;/code&gt; types into actual 2948 + generic code for Go v2.&lt;/p&gt; 2949 + </description> 2950 + <link>https://oppi.li/posts/gripes_with_go/</link> 2951 + <pubDate>Sat, 01 Aug 2020 00:00:00 +0000</pubDate> 2952 + <guid>https://oppi.li/posts/gripes_with_go/</guid> 2953 + </item> 2954 + <item> 2955 + <title>turing complete type systems</title> 2956 + <description>&lt;p&gt;Rust's type system is Turing complete:&lt;/p&gt; 2957 + &lt;ul&gt; 2958 + &lt;li&gt;&lt;a href="https://github.com/doctorn/trait-eval/"&gt;FizzBuzz with Rust Traits&lt;/a&gt; 2959 + &lt;/li&gt; 2960 + &lt;li&gt;&lt;a href="https://github.com/Ashymad/fortraith"&gt;A Forth implementation with Rust Traits&lt;/a&gt; 2961 + &lt;/li&gt; 2962 + &lt;/ul&gt; 2963 + &lt;p&gt;It is impossible to determine if a program written in a 2964 + generally Turing complete system will ever stop. That is, it 2965 + is impossible to write a program &lt;code&gt;f&lt;/code&gt; that determines if a 2966 + program &lt;code&gt;g&lt;/code&gt;, where &lt;code&gt;g&lt;/code&gt; is written in a Turing complete 2967 + programming language, will ever halt. The &lt;a href="https://en.wikipedia.org/wiki/Halting_problem"&gt;Halting 2968 + Problem&lt;/a&gt; is 2969 + in fact, an &lt;a href="https://en.wikipedia.org/wiki/Undecidable_problem"&gt;undecidable 2970 + problem&lt;/a&gt;.&lt;/p&gt; 2971 + &lt;p&gt;&lt;em&gt;How is any of this relevant?&lt;/em&gt;&lt;/p&gt; 2972 + &lt;p&gt;Rust performs compile-time type inference. The type checker, 2973 + in turn, compiles and infers types, I would describe it as a 2974 + compiler inside a compiler. It is possible that &lt;code&gt;rustc&lt;/code&gt; may 2975 + never finish compiling your Rust program! I lied, &lt;code&gt;rustc&lt;/code&gt; 2976 + stops after a while, after hitting the recursion limit.&lt;/p&gt; 2977 + &lt;p&gt;I understand that this post lacks content.&lt;/p&gt; 2978 + </description> 2979 + <link>https://oppi.li/posts/turing_complete_type_systems/</link> 2980 + <pubDate>Wed, 17 Jun 2020 00:00:00 +0000</pubDate> 2981 + <guid>https://oppi.li/posts/turing_complete_type_systems/</guid> 2982 + </item> 2983 + <item> 2984 + <title>auto-currying rust functions</title> 2985 + <description>&lt;p&gt;This post contains a gentle introduction to procedural 2986 + macros in Rust and a guide to writing a procedural macro to 2987 + curry Rust functions. The source code for the entire library 2988 + can be found &lt;a href="https://github.com/nerdypepper/cutlass"&gt;here&lt;/a&gt;. 2989 + It is also available on &lt;a href="https://crates.io/crates/cutlass"&gt;crates.io&lt;/a&gt;.&lt;/p&gt; 2990 + &lt;p&gt;The following links might prove to be useful before getting 2991 + started:&lt;/p&gt; 2992 + &lt;ul&gt; 2993 + &lt;li&gt;&lt;a href="https://doc.rust-lang.org/reference/procedural-macros.html"&gt;Procedural Macros&lt;/a&gt; 2994 + &lt;/li&gt; 2995 + &lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Currying"&gt;Currying&lt;/a&gt; 2996 + &lt;/li&gt; 2997 + &lt;/ul&gt; 2998 + &lt;p&gt;Or you can pretend you read them, because I have included 2999 + a primer here :)&lt;/p&gt; 3000 + &lt;h3 id="contents"&gt;Contents&lt;/h3&gt; 3001 + &lt;ol&gt; 3002 + &lt;li&gt;&lt;a href="#currying"&gt;Currying&lt;/a&gt; 3003 + &lt;/li&gt; 3004 + &lt;li&gt;&lt;a href="#procedural-macros"&gt;Procedural Macros&lt;/a&gt; 3005 + &lt;/li&gt; 3006 + &lt;li&gt;&lt;a href="#definitions"&gt;Definitions&lt;/a&gt; 3007 + &lt;/li&gt; 3008 + &lt;li&gt;&lt;a href="#refinement"&gt;Refinement&lt;/a&gt; 3009 + &lt;/li&gt; 3010 + &lt;li&gt;&lt;a href="#the-in-betweens"&gt;The In-betweens&lt;/a&gt;&lt;br /&gt; 3011 +      5.1 &lt;a href="#dependencies"&gt;Dependencies&lt;/a&gt;&lt;br /&gt; 3012 +      5.2 &lt;a href="#the-attribute-macro"&gt;The attribute macro&lt;/a&gt;&lt;br /&gt; 3013 +      5.3 &lt;a href="#function-body"&gt;Function Body&lt;/a&gt;&lt;br /&gt; 3014 +      5.4 &lt;a href="#function-signature"&gt;Function Signature&lt;/a&gt;&lt;br /&gt; 3015 +      5.5 &lt;a href="#getting-it-together"&gt;Getting it together&lt;/a&gt; 3016 + &lt;/li&gt; 3017 + &lt;li&gt;&lt;a href="#debugging-and-testing"&gt;Debugging and Testing&lt;/a&gt; 3018 + &lt;/li&gt; 3019 + &lt;li&gt;&lt;a href="#notes"&gt;Notes&lt;/a&gt; 3020 + &lt;/li&gt; 3021 + &lt;li&gt;&lt;a href="#conclusion"&gt;Conclusion&lt;/a&gt; 3022 + &lt;/li&gt; 3023 + &lt;/ol&gt; 3024 + &lt;h3 id="currying"&gt;Currying&lt;/h3&gt; 3025 + &lt;p&gt;Currying is the process of transformation of a function call 3026 + like &lt;code&gt;f(a, b, c)&lt;/code&gt; to &lt;code&gt;f(a)(b)(c)&lt;/code&gt;. A curried function 3027 + returns a concrete value only when it receives all its 3028 + arguments! If it does recieve an insufficient amount of 3029 + arguments, say 1 of 3, it returns a &lt;em&gt;curried function&lt;/em&gt;, that 3030 + returns after receiving 2 arguments.&lt;/p&gt; 3031 + &lt;pre&gt;&lt;code&gt;curry(f(a, b, c)) = h(a)(b)(c) 3032 + 3033 + h(x) = g &amp;lt;- curried function that takes upto 2 args (g) 3034 + g(y) = k &amp;lt;- curried function that takes upto 1 arg (k) 3035 + k(z) = v &amp;lt;- a value (v) 3036 + 3037 + Keen readers will conclude the following, 3038 + h(x)(y)(z) = g(y)(z) = k(z) = v 3039 + &lt;/code&gt;&lt;/pre&gt; 3040 + &lt;p&gt;Mathematically, if &lt;code&gt;f&lt;/code&gt; is a function that takes two 3041 + arguments &lt;code&gt;x&lt;/code&gt; and &lt;code&gt;y&lt;/code&gt;, such that &lt;code&gt;x ϵ X&lt;/code&gt;, and &lt;code&gt;y ϵ Y&lt;/code&gt; , we 3042 + write it as:&lt;/p&gt; 3043 + &lt;pre&gt;&lt;code&gt;f: (X × Y) -&amp;gt; Z 3044 + &lt;/code&gt;&lt;/pre&gt; 3045 + &lt;p&gt;where &lt;code&gt;×&lt;/code&gt; denotes the Cartesian product of set &lt;code&gt;X&lt;/code&gt; and &lt;code&gt;Y&lt;/code&gt;, 3046 + and curried &lt;code&gt;f&lt;/code&gt; (denoted by &lt;code&gt;h&lt;/code&gt; here) is written as:&lt;/p&gt; 3047 + &lt;pre&gt;&lt;code&gt;h: X -&amp;gt; (Y -&amp;gt; Z) 3048 + &lt;/code&gt;&lt;/pre&gt; 3049 + &lt;h3 id="procedural-macros"&gt;Procedural Macros&lt;/h3&gt; 3050 + &lt;p&gt;These are functions that take code as input and spit out 3051 + modified code as output. Powerful stuff. Rust has three 3052 + kinds of proc-macros:&lt;/p&gt; 3053 + &lt;ul&gt; 3054 + &lt;li&gt;Function like macros 3055 + &lt;/li&gt; 3056 + &lt;li&gt;Derive macros: &lt;code&gt;#[derive(...)]&lt;/code&gt;, used to automatically 3057 + implement traits for structs/enums 3058 + &lt;/li&gt; 3059 + &lt;li&gt;and Attribute macros: &lt;code&gt;#[test]&lt;/code&gt;, usually slapped onto 3060 + functions 3061 + &lt;/li&gt; 3062 + &lt;/ul&gt; 3063 + &lt;p&gt;We will be using Attribute macros to convert a Rust function 3064 + into a curried Rust function, which we should be able to 3065 + call via: &lt;code&gt;function(arg1)(arg2)&lt;/code&gt;.&lt;/p&gt; 3066 + &lt;h3 id="definitions"&gt;Definitions&lt;/h3&gt; 3067 + &lt;p&gt;Being respectable programmers, we define the input to and 3068 + the output from our proc-macro. Here's a good non-trivial 3069 + function to start out with:&lt;/p&gt; 3070 + &lt;pre&gt;&lt;code class="language-rust"&gt;fn add(x: u32, y: u32, z: u32) -&amp;gt; u32 { 3071 + return x + y + z; 3072 + } 3073 + &lt;/code&gt;&lt;/pre&gt; 3074 + &lt;p&gt;Hmm, what would our output look like? What should our 3075 + proc-macro generate ideally? Well, if we understood currying 3076 + correctly, we should accept an argument and return a 3077 + function that accepts an argument and returns ... you get 3078 + the point. Something like this should do:&lt;/p&gt; 3079 + &lt;pre&gt;&lt;code class="language-rust"&gt;fn add_curried1(x: u32) -&amp;gt; ? { 3080 + return fn add_curried2 (y: u32) -&amp;gt; ? { 3081 + return fn add_curried3 (z: u32) -&amp;gt; u32 { 3082 + return x + y + z; 3083 + } 3084 + } 3085 + } 3086 + &lt;/code&gt;&lt;/pre&gt; 3087 + &lt;p&gt;A couple of things to note:&lt;/p&gt; 3088 + &lt;p&gt;&lt;strong&gt;Return types&lt;/strong&gt;&lt;br /&gt; 3089 + We have placed &lt;code&gt;?&lt;/code&gt;s in place of return 3090 + types. Let's try to fix that. &lt;code&gt;add_curried3&lt;/code&gt; returns the 3091 + 'value', so &lt;code&gt;u32&lt;/code&gt; is accurate. &lt;code&gt;add_curried2&lt;/code&gt; returns 3092 + &lt;code&gt;add_curried3&lt;/code&gt;. What is the type of &lt;code&gt;add_curried3&lt;/code&gt;? It is a 3093 + function that takes in a &lt;code&gt;u32&lt;/code&gt; and returns a &lt;code&gt;u32&lt;/code&gt;. So a 3094 + &lt;code&gt;fn(u32) -&amp;gt; u32&lt;/code&gt; will do right? No, I'll explain why in the 3095 + next point, but for now, we will make use of the &lt;code&gt;Fn&lt;/code&gt; trait, 3096 + our return type is &lt;code&gt;impl Fn(u32) -&amp;gt; u32&lt;/code&gt;. This basically 3097 + tells the compiler that we will be returning something 3098 + function-like, a.k.a, behaves like a &lt;code&gt;Fn&lt;/code&gt;. Cool!&lt;/p&gt; 3099 + &lt;p&gt;If you have been following along, you should be able to tell 3100 + that the return type of &lt;code&gt;add_curried1&lt;/code&gt; is:&lt;/p&gt; 3101 + &lt;pre&gt;&lt;code&gt;impl Fn(u32) -&amp;gt; (impl Fn(u32) -&amp;gt; u32) 3102 + &lt;/code&gt;&lt;/pre&gt; 3103 + &lt;p&gt;We can drop the parentheses because &lt;code&gt;-&amp;gt;&lt;/code&gt; is right associative:&lt;/p&gt; 3104 + &lt;pre&gt;&lt;code&gt;impl Fn(u32) -&amp;gt; impl Fn(u32) -&amp;gt; u32 3105 + 3106 + &lt;/code&gt;&lt;/pre&gt; 3107 + &lt;p&gt;&lt;strong&gt;Accessing environment&lt;/strong&gt;&lt;br /&gt; 3108 + A function cannot access it's environment. Our solution 3109 + will not work. &lt;code&gt;add_curried3&lt;/code&gt; attempts to access &lt;code&gt;x&lt;/code&gt;, which 3110 + is not allowed! A closure&lt;a href="%5Bhttps://doc.rust-lang.org/book/ch13-01-closures.html%5D(https://doc.rust-lang.org/book/ch13-01-closures.html)"&gt;^closure&lt;/a&gt; however, can. If we are 3111 + returning a closure, our return type must be &lt;code&gt;impl Fn&lt;/code&gt;, and 3112 + not &lt;code&gt;fn&lt;/code&gt;. The difference between the &lt;code&gt;Fn&lt;/code&gt; trait and 3113 + function pointers is beyond the scope of this post.&lt;/p&gt; 3114 + &lt;h3 id="refinement"&gt;Refinement&lt;/h3&gt; 3115 + &lt;p&gt;Armed with knowledge, we refine our expected output, this 3116 + time, employing closures:&lt;/p&gt; 3117 + &lt;pre&gt;&lt;code class="language-rust"&gt;fn add(x: u32) -&amp;gt; impl Fn(u32) -&amp;gt; impl Fn(u32) -&amp;gt; u32 { 3118 + return move |y| move |z| x + y + z; 3119 + } 3120 + &lt;/code&gt;&lt;/pre&gt; 3121 + &lt;p&gt;Alas, that does not compile either! It errors out with the 3122 + following message:&lt;/p&gt; 3123 + &lt;pre&gt;&lt;code&gt;error[E0562]: `impl Trait` not allowed outside of function 3124 + and inherent method return types 3125 + --&amp;gt; src/main.rs:17:37 3126 + | 3127 + | fn add(x: u32) -&amp;gt; impl Fn(u32) -&amp;gt; impl Fn(u32) -&amp;gt; u32 3128 + | ^^^^^^^^^^^^^^^^^^^ 3129 + 3130 + &lt;/code&gt;&lt;/pre&gt; 3131 + &lt;p&gt;You are allowed to return an &lt;code&gt;impl Fn&lt;/code&gt; only inside a 3132 + function. We are currently returning it from another return! 3133 + Or at least, that was the most I could make out of the error 3134 + message.&lt;/p&gt; 3135 + &lt;p&gt;We are going to have to cheat a bit to fix this issue; with 3136 + type aliases and a convenient nightly feature [^features]:&lt;/p&gt; 3137 + &lt;p&gt;[^features]: &lt;a href="https://caniuse.rs"&gt;caniuse.rs&lt;/a&gt; contains an 3138 + indexed list of features and their status.&lt;/p&gt; 3139 + &lt;pre&gt;&lt;code class="language-rust"&gt;#![feature(type_alias_impl_trait)] // allows us to use `impl Fn` in type aliases! 3140 + 3141 + type T0 = u32; // the return value when zero args are to be applied 3142 + type T1 = impl Fn(u32) -&amp;gt; T0; // the return value when one arg is to be applied 3143 + type T2 = impl Fn(u32) -&amp;gt; T1; // the return value when two args are to be applied 3144 + 3145 + fn add(x: u32) -&amp;gt; T2 { 3146 + return move |y| move |z| x + y + z; 3147 + } 3148 + &lt;/code&gt;&lt;/pre&gt; 3149 + &lt;p&gt;Drop that into a cargo project, call &lt;code&gt;add(4)(5)(6)&lt;/code&gt;, cross 3150 + your fingers, and run &lt;code&gt;cargo +nightly run&lt;/code&gt;. You should see a 3151 + 15 unless you forgot to print it!&lt;/p&gt; 3152 + &lt;h3 id="the-in-betweens"&gt;The In-Betweens&lt;/h3&gt; 3153 + &lt;p&gt;Let us write the magical bits that take us from function to 3154 + curried function.&lt;/p&gt; 3155 + &lt;p&gt;Initialize your workspace with &lt;code&gt;cargo new --lib currying&lt;/code&gt;. 3156 + Proc-macro crates are libraries with exactly one export, the 3157 + macro itself. Add a &lt;code&gt;tests&lt;/code&gt; directory to your crate root. 3158 + Your directory should look something like this:&lt;/p&gt; 3159 + &lt;pre&gt;&lt;code&gt;. 3160 + ├── Cargo.toml 3161 + ├── src 3162 + │   └── lib.rs 3163 + └── tests 3164 + └── smoke.rs 3165 + &lt;/code&gt;&lt;/pre&gt; 3166 + &lt;h4 id="dependencies"&gt;Dependencies&lt;/h4&gt; 3167 + &lt;p&gt;We will be using a total of 3 external crates:&lt;/p&gt; 3168 + &lt;ul&gt; 3169 + &lt;li&gt;&lt;a href="https://docs.rs/proc-macro2/1.0.12/proc_macro2/"&gt;proc_macro2&lt;/a&gt; 3170 + &lt;/li&gt; 3171 + &lt;li&gt;&lt;a href="https://docs.rs/syn/1.0.18/syn/index.html"&gt;syn&lt;/a&gt; 3172 + &lt;/li&gt; 3173 + &lt;li&gt;&lt;a href="https://docs.rs/quote/1.0.4/quote/index.html"&gt;quote&lt;/a&gt; 3174 + &lt;/li&gt; 3175 + &lt;/ul&gt; 3176 + &lt;p&gt;Here's a sample &lt;code&gt;Cargo.toml&lt;/code&gt;:&lt;/p&gt; 3177 + &lt;pre&gt;&lt;code&gt;# Cargo.toml 3178 + 3179 + [dependencies] 3180 + proc-macro2 = &amp;quot;1.0.9&amp;quot; 3181 + quote = &amp;quot;1.0&amp;quot; 3182 + 3183 + [dependencies.syn] 3184 + version = &amp;quot;1.0&amp;quot; 3185 + features = [&amp;quot;full&amp;quot;] 3186 + 3187 + [lib] 3188 + proc-macro = true # this is important! 3189 + &lt;/code&gt;&lt;/pre&gt; 3190 + &lt;p&gt;We will be using an external &lt;code&gt;proc-macro2&lt;/code&gt; crate as well as 3191 + an internal &lt;code&gt;proc-macro&lt;/code&gt; crate. Not confusing at all!&lt;/p&gt; 3192 + &lt;h4 id="the-attribute-macro"&gt;The attribute macro&lt;/h4&gt; 3193 + &lt;p&gt;Drop this into &lt;code&gt;src/lib.rs&lt;/code&gt;, to get the ball rolling.&lt;/p&gt; 3194 + &lt;pre&gt;&lt;code class="language-rust"&gt;// src/lib.rs 3195 + 3196 + use proc_macro::TokenStream; // 1 3197 + use quote::quote; 3198 + use syn::{parse_macro_input, ItemFn}; 3199 + 3200 + #[proc_macro_attribute] // 2 3201 + pub fn curry(_attr: TokenStream, item: TokenStream) -&amp;gt; TokenStream { 3202 + let parsed = parse_macro_input!(item as ItemFn); // 3 3203 + generate_curry(parsed).into() // 4 3204 + } 3205 + 3206 + fn generate_curry(parsed: ItemFn) -&amp;gt; proc_macro2::TokenStream {} 3207 + &lt;/code&gt;&lt;/pre&gt; 3208 + &lt;p&gt;&lt;strong&gt;1. Imports&lt;/strong&gt;&lt;/p&gt; 3209 + &lt;p&gt;A &lt;code&gt;Tokenstream&lt;/code&gt; holds (hopefully valid) Rust code, this 3210 + is the type of our input and output. Note that we are 3211 + importing this type from &lt;code&gt;proc_macro&lt;/code&gt; and not &lt;code&gt;proc_macro2&lt;/code&gt;.&lt;/p&gt; 3212 + &lt;p&gt;&lt;code&gt;quote!&lt;/code&gt; from the &lt;code&gt;quote&lt;/code&gt; crate is a macro that allows us to 3213 + quickly produce &lt;code&gt;TokenStream&lt;/code&gt;s. Much like the LISP &lt;code&gt;quote&lt;/code&gt; 3214 + procedure, you can use the &lt;code&gt;quote!&lt;/code&gt; macro for symbolic 3215 + transformations.&lt;/p&gt; 3216 + &lt;p&gt;&lt;code&gt;ItemFn&lt;/code&gt; from the &lt;code&gt;syn&lt;/code&gt; crate holds the parsed &lt;code&gt;TokenStream&lt;/code&gt; 3217 + of a Rust function. &lt;code&gt;parse_macro_input!&lt;/code&gt; is a helper macro 3218 + provided by &lt;code&gt;syn&lt;/code&gt;.&lt;/p&gt; 3219 + &lt;p&gt;&lt;strong&gt;2. The lone export&lt;/strong&gt;&lt;/p&gt; 3220 + &lt;p&gt;Annotate the only &lt;code&gt;pub&lt;/code&gt; of our crate with 3221 + &lt;code&gt;#[proc_macro_attribute]&lt;/code&gt;. This tells rustc that &lt;code&gt;curry&lt;/code&gt; is 3222 + a procedural macro, and allows us to use it as 3223 + &lt;code&gt;#[crate_name::curry]&lt;/code&gt; in other crates. Note the signature 3224 + of the &lt;code&gt;curry&lt;/code&gt; function. &lt;code&gt;_attr&lt;/code&gt; is the &lt;code&gt;TokenStream&lt;/code&gt; 3225 + representing the attribute itself, &lt;code&gt;item&lt;/code&gt; refers to the 3226 + thing we slapped our macro into, in this case a function 3227 + (like &lt;code&gt;add&lt;/code&gt;). The return value is a modified &lt;code&gt;TokenStream&lt;/code&gt;, 3228 + this will contain our curried version of &lt;code&gt;add&lt;/code&gt;.&lt;/p&gt; 3229 + &lt;p&gt;&lt;strong&gt;3. The helper macro&lt;/strong&gt;&lt;/p&gt; 3230 + &lt;p&gt;A &lt;code&gt;TokenStream&lt;/code&gt; is a little hard to work with, which is why 3231 + we have the &lt;code&gt;syn&lt;/code&gt; crate, which provides types to represent 3232 + Rust tokens. An &lt;code&gt;RArrow&lt;/code&gt; struct to represent the return 3233 + arrow on a function and so on. One of those types is 3234 + &lt;code&gt;ItemFn&lt;/code&gt;, that represents an entire Rust function. The 3235 + &lt;code&gt;parse_macro_input!&lt;/code&gt; automatically puts the input to our 3236 + macro into an &lt;code&gt;ItemFn&lt;/code&gt;. What a gentleman!&lt;/p&gt; 3237 + &lt;p&gt;**4. Returning &lt;code&gt;TokenStream&lt;/code&gt;s **&lt;/p&gt; 3238 + &lt;p&gt;We haven't filled in &lt;code&gt;generate_curry&lt;/code&gt; yet, but we can see 3239 + that it returns a &lt;code&gt;proc_macro2::TokenStream&lt;/code&gt; and not a 3240 + &lt;code&gt;proc_macro::TokenStream&lt;/code&gt;, so drop a &lt;code&gt;.into()&lt;/code&gt; to convert 3241 + it.&lt;/p&gt; 3242 + &lt;p&gt;Lets move on, and fill in &lt;code&gt;generate_curry&lt;/code&gt;, I would suggest 3243 + keeping the documentation for 3244 + &lt;a href="https://docs.rs/syn/1.0.19/syn/struct.ItemFn.html"&gt;&lt;code&gt;syn::ItemFn&lt;/code&gt;&lt;/a&gt; 3245 + and 3246 + &lt;a href="https://docs.rs/syn/1.0.19/syn/struct.Signature.html"&gt;&lt;code&gt;syn::Signature&lt;/code&gt;&lt;/a&gt; 3247 + open.&lt;/p&gt; 3248 + &lt;pre&gt;&lt;code class="language-rust"&gt;// src/lib.rs 3249 + 3250 + fn generate_curry(parsed: ItemFn) -&amp;gt; proc_macro2::TokenStream { 3251 + let fn_body = parsed.block; // function body 3252 + let sig = parsed.sig; // function signature 3253 + let vis = parsed.vis; // visibility, pub or not 3254 + let fn_name = sig.ident; // function name/identifier 3255 + let fn_args = sig.inputs; // comma separated args 3256 + let fn_return_type = sig.output; // return type 3257 + } 3258 + &lt;/code&gt;&lt;/pre&gt; 3259 + &lt;p&gt;We are simply extracting the bits of the function, we will 3260 + be reusing the original function's visibility and name. Take 3261 + a look at what &lt;code&gt;syn::Signature&lt;/code&gt; can tell us about a 3262 + function:&lt;/p&gt; 3263 + &lt;pre&gt;&lt;code&gt; .-- syn::Ident (ident) 3264 + / 3265 + fn add(x: u32, y: u32) -&amp;gt; u32 3266 + (fn_token) / ~~~~~~~,~~~~~~ ~~~~~~ 3267 + syn::token::Fn --' / \ (output) 3268 + ' `- syn::ReturnType 3269 + Punctuated&amp;lt;FnArg, Comma&amp;gt; (inputs) 3270 + &lt;/code&gt;&lt;/pre&gt; 3271 + &lt;p&gt;Enough analysis, lets produce our first bit of Rust code.&lt;/p&gt; 3272 + &lt;h4 id="function-body"&gt;Function Body&lt;/h4&gt; 3273 + &lt;p&gt;Recall that the body of a curried &lt;code&gt;add&lt;/code&gt; should look like 3274 + this:&lt;/p&gt; 3275 + &lt;pre&gt;&lt;code class="language-rust"&gt;return move |y| move |z| x + y + z; 3276 + &lt;/code&gt;&lt;/pre&gt; 3277 + &lt;p&gt;And in general:&lt;/p&gt; 3278 + &lt;pre&gt;&lt;code class="language-rust"&gt;return move |arg2| move |arg3| ... |argN| &amp;lt;function body here&amp;gt; 3279 + &lt;/code&gt;&lt;/pre&gt; 3280 + &lt;p&gt;We already have the function's body, provided by &lt;code&gt;fn_body&lt;/code&gt;, 3281 + in our &lt;code&gt;generate_curry&lt;/code&gt; function. All that's left to add is 3282 + the &lt;code&gt;move |arg2| move |arg3| ...&lt;/code&gt; stuff, for which we need 3283 + to extract the argument identifiers 3284 + (doc: 3285 + &lt;a href="https://docs.rs/syn/1.0.18/syn/punctuated/struct.Punctuated.html"&gt;Punctuated&lt;/a&gt;, 3286 + &lt;a href="https://docs.rs/syn/1.0.18/syn/enum.FnArg.html"&gt;FnArg&lt;/a&gt;, 3287 + &lt;a href="https://docs.rs/syn/1.0.18/syn/struct.PatType.html"&gt;PatType&lt;/a&gt;):&lt;/p&gt; 3288 + &lt;pre&gt;&lt;code class="language-rust"&gt;// src/lib.rs 3289 + use syn::punctuated::Punctuated; 3290 + use syn::{parse_macro_input, FnArg, Pat, ItemFn, Block}; 3291 + 3292 + fn extract_arg_idents(fn_args: Punctuated&amp;lt;FnArg, syn::token::Comma&amp;gt;) -&amp;gt; Vec&amp;lt;Box&amp;lt;Pat&amp;gt;&amp;gt; { 3293 + return fn_args.into_iter().map(extract_arg_pat).collect::&amp;lt;Vec&amp;lt;_&amp;gt;&amp;gt;(); 3294 + } 3295 + &lt;/code&gt;&lt;/pre&gt; 3296 + &lt;p&gt;Alright, so we are iterating over function args 3297 + (&lt;code&gt;Punctuated&lt;/code&gt; is a collection that you can iterate over) and 3298 + mapping an &lt;code&gt;extract_arg_pat&lt;/code&gt; to every item. What's 3299 + &lt;code&gt;extract_arg_pat&lt;/code&gt;?&lt;/p&gt; 3300 + &lt;pre&gt;&lt;code class="language-rust"&gt;// src/lib.rs 3301 + 3302 + fn extract_arg_pat(a: FnArg) -&amp;gt; Box&amp;lt;Pat&amp;gt; { 3303 + match a { 3304 + FnArg::Typed(p) =&amp;gt; p.pat, 3305 + _ =&amp;gt; panic!(&amp;quot;Not supported on types with `self`!&amp;quot;), 3306 + } 3307 + } 3308 + &lt;/code&gt;&lt;/pre&gt; 3309 + &lt;p&gt;&lt;code&gt;FnArg&lt;/code&gt; is an enum type as you might have guessed. The 3310 + &lt;code&gt;Typed&lt;/code&gt; variant encompasses args that are written as &lt;code&gt;name: type&lt;/code&gt; and the other variant, &lt;code&gt;Reciever&lt;/code&gt; refers to &lt;code&gt;self&lt;/code&gt; 3311 + types. Ignore those for now, keep it simple.&lt;/p&gt; 3312 + &lt;p&gt;Every &lt;code&gt;FnArg::Typed&lt;/code&gt; value contains a &lt;code&gt;pat&lt;/code&gt;, which is in 3313 + essence, the name of the argument. The type of the arg is 3314 + accessible via &lt;code&gt;p.ty&lt;/code&gt; (we will be using this later).&lt;/p&gt; 3315 + &lt;p&gt;With that done, we should be able to write the codegen for 3316 + the function body:&lt;/p&gt; 3317 + &lt;pre&gt;&lt;code class="language-rust"&gt;// src/lib.rs 3318 + 3319 + fn generate_body(fn_args: &amp;amp;[Box&amp;lt;Pat&amp;gt;], body: Box&amp;lt;Block&amp;gt;) -&amp;gt; proc_macro2::TokenStream { 3320 + quote! { 3321 + return #( move |#fn_args| )* #body 3322 + } 3323 + } 3324 + &lt;/code&gt;&lt;/pre&gt; 3325 + &lt;p&gt;That is some scary looking syntax! Allow me to explain. The 3326 + &lt;code&gt;quote!{ ... }&lt;/code&gt; returns a &lt;code&gt;proc_macro2::TokenStream&lt;/code&gt;, if we 3327 + wrote &lt;code&gt;quote!{ let x = 1 + 2; }&lt;/code&gt;, it wouldn't create a new 3328 + variable &lt;code&gt;x&lt;/code&gt; with value 3, it would literally produce a 3329 + stream of tokens with that expression.&lt;/p&gt; 3330 + &lt;p&gt;The &lt;code&gt;#&lt;/code&gt; enables variable interpolation. &lt;code&gt;#body&lt;/code&gt; will look 3331 + for &lt;code&gt;body&lt;/code&gt; in the current scope, take its value, and insert 3332 + it in the returned &lt;code&gt;TokenStream&lt;/code&gt;. Kinda like quasi quoting 3333 + in LISPs, you have written one.&lt;/p&gt; 3334 + &lt;p&gt;What about &lt;code&gt;#( move |#fn_args| )*&lt;/code&gt;? That is repetition. 3335 + &lt;code&gt;quote&lt;/code&gt; iterates through &lt;code&gt;fn_args&lt;/code&gt;, and drops a &lt;code&gt;move&lt;/code&gt; behind 3336 + each one, it then places pipes (&lt;code&gt;|&lt;/code&gt;), around it.&lt;/p&gt; 3337 + &lt;p&gt;Let us test our first bit of codegen! Modify &lt;code&gt;generate_curry&lt;/code&gt; like so:&lt;/p&gt; 3338 + &lt;pre&gt;&lt;code class="language-rust"&gt;// src/lib.rs 3339 + 3340 + fn generate_curry(parsed: ItemFn) -&amp;gt; TokenStream { 3341 + let fn_body = parsed.block; 3342 + let sig = parsed.sig; 3343 + let vis = parsed.vis; 3344 + let fn_name = sig.ident; 3345 + let fn_args = sig.inputs; 3346 + let fn_return_type = sig.output; 3347 + 3348 + + let arg_idents = extract_arg_idents(fn_args.clone()); 3349 + + let first_ident = &amp;amp;arg_idents.first().unwrap(); 3350 + 3351 + + // remember, our curried body starts with the second argument! 3352 + + let curried_body = generate_body(&amp;amp;arg_idents[1..], fn_body.clone()); 3353 + + println!(&amp;quot;{}&amp;quot;, curried_body); 3354 + 3355 + return TokenStream::new(); 3356 + } 3357 + &lt;/code&gt;&lt;/pre&gt; 3358 + &lt;p&gt;Add a little test to &lt;code&gt;tests/&lt;/code&gt;:&lt;/p&gt; 3359 + &lt;pre&gt;&lt;code class="language-rust"&gt;// tests/smoke.rs 3360 + 3361 + #[currying::curry] 3362 + fn add(x: u32, y: u32, z: u32) -&amp;gt; u32 { 3363 + x + y + z 3364 + } 3365 + 3366 + #[test] 3367 + fn works() { 3368 + assert!(true); 3369 + } 3370 + &lt;/code&gt;&lt;/pre&gt; 3371 + &lt;p&gt;You should find something like this in the output of &lt;code&gt;cargo test&lt;/code&gt;:&lt;/p&gt; 3372 + &lt;pre&gt;&lt;code&gt;return move | y | move | z | { x + y + z } 3373 + &lt;/code&gt;&lt;/pre&gt; 3374 + &lt;p&gt;Glorious &lt;code&gt;println!&lt;/code&gt; debugging!&lt;/p&gt; 3375 + &lt;h4 id="function-signature"&gt;Function signature&lt;/h4&gt; 3376 + &lt;p&gt;This section gets into the more complicated bits of the 3377 + macro, generating type aliases and the function signature. 3378 + By the end of this section, we should have a full working 3379 + auto-currying macro!&lt;/p&gt; 3380 + &lt;p&gt;Recall what our generated type aliases should look like, for 3381 + our &lt;code&gt;add&lt;/code&gt; function:&lt;/p&gt; 3382 + &lt;pre&gt;&lt;code class="language-rust"&gt;type T0 = u32; 3383 + type T1 = impl Fn(u32) -&amp;gt; T0; 3384 + type T2 = impl Fn(u32) -&amp;gt; T1; 3385 + &lt;/code&gt;&lt;/pre&gt; 3386 + &lt;p&gt;In general:&lt;/p&gt; 3387 + &lt;pre&gt;&lt;code class="language-rust"&gt;type T0 = &amp;lt;return type&amp;gt;; 3388 + type T1 = impl Fn(&amp;lt;type of arg N&amp;gt;) -&amp;gt; T0; 3389 + type T2 = impl Fn(&amp;lt;type of arg N - 1&amp;gt;) -&amp;gt; T1; 3390 + . 3391 + . 3392 + . 3393 + type T(N-1) = impl Fn(&amp;lt;type of arg 2&amp;gt;) -&amp;gt; T(N-2); 3394 + &lt;/code&gt;&lt;/pre&gt; 3395 + &lt;p&gt;To codegen that, we need the types of:&lt;/p&gt; 3396 + &lt;ul&gt; 3397 + &lt;li&gt;all our inputs (arguments) 3398 + &lt;/li&gt; 3399 + &lt;li&gt;the output (the return type) 3400 + &lt;/li&gt; 3401 + &lt;/ul&gt; 3402 + &lt;p&gt;To fetch the types of all our inputs, we can simply reuse 3403 + the bits we wrote to fetch the names of all our inputs! 3404 + (doc: &lt;a href="https://docs.rs/syn/1.0.18/syn/enum.Type.html"&gt;Type&lt;/a&gt;)&lt;/p&gt; 3405 + &lt;pre&gt;&lt;code class="language-rust"&gt;// src/lib.rs 3406 + 3407 + use syn::{parse_macro_input, Block, FnArg, ItemFn, Pat, ReturnType, Type}; 3408 + 3409 + fn extract_type(a: FnArg) -&amp;gt; Box&amp;lt;Type&amp;gt; { 3410 + match a { 3411 + FnArg::Typed(p) =&amp;gt; p.ty, // notice `ty` instead of `pat` 3412 + _ =&amp;gt; panic!(&amp;quot;Not supported on types with `self`!&amp;quot;), 3413 + } 3414 + } 3415 + 3416 + fn extract_arg_types(fn_args: Punctuated&amp;lt;FnArg, syn::token::Comma&amp;gt;) -&amp;gt; Vec&amp;lt;Box&amp;lt;Type&amp;gt;&amp;gt; { 3417 + return fn_args.into_iter().map(extract_type).collect::&amp;lt;Vec&amp;lt;_&amp;gt;&amp;gt;(); 3418 + 3419 + } 3420 + &lt;/code&gt;&lt;/pre&gt; 3421 + &lt;p&gt;A good reader would have looked at the docs for output 3422 + member of the &lt;code&gt;syn::Signature&lt;/code&gt; struct. It has the type 3423 + &lt;code&gt;syn::ReturnType&lt;/code&gt;. So there is no extraction to do here 3424 + right? There are actually a couple of things we have to 3425 + ensure here:&lt;/p&gt; 3426 + &lt;ol&gt; 3427 + &lt;li&gt; 3428 + &lt;p&gt;We need to ensure that the function returns! A function 3429 + that does not return is pointless in this case, and I 3430 + will tell you why, in the &lt;a href="#notes"&gt;Notes&lt;/a&gt; section.&lt;/p&gt; 3431 + &lt;/li&gt; 3432 + &lt;li&gt; 3433 + &lt;p&gt;A &lt;code&gt;ReturnType&lt;/code&gt; encloses the arrow of the return as well, 3434 + we need to get rid of that. Recall:&lt;/p&gt; 3435 + &lt;pre&gt;&lt;code class="language-rust"&gt;&lt;/code&gt;&lt;/pre&gt; 3436 + &lt;/li&gt; 3437 + &lt;/ol&gt; 3438 + &lt;p&gt;type T0 = u32 3439 + // and not 3440 + type T0 = -&amp;gt; u32&lt;/p&gt; 3441 + &lt;pre&gt;&lt;code&gt; 3442 + Here is the snippet that handles extraction of the 3443 + return type (doc: [syn::ReturnType](https://docs.rs/syn/1.0.19/syn/enum.ReturnType.html)): 3444 + 3445 + ```rust 3446 + // src/lib.rs 3447 + 3448 + fn extract_return_type(a: ReturnType) -&amp;gt; Box&amp;lt;Type&amp;gt; { 3449 + match a { 3450 + ReturnType::Type(_, p) =&amp;gt; p, 3451 + _ =&amp;gt; panic!(&amp;quot;Not supported on functions without return types!&amp;quot;), 3452 + } 3453 + } 3454 + &lt;/code&gt;&lt;/pre&gt; 3455 + &lt;p&gt;You might notice that we are making extensive use of the 3456 + &lt;code&gt;panic!&lt;/code&gt; macro. Well, that is because it is a good idea to 3457 + quit on receiving an unsatisfactory &lt;code&gt;TokenStream&lt;/code&gt;.&lt;/p&gt; 3458 + &lt;p&gt;With all our types ready, we can get on with generating type 3459 + aliases:&lt;/p&gt; 3460 + &lt;pre&gt;&lt;code class="language-rust"&gt;// src/lib.rs 3461 + 3462 + use quote::{quote, format_ident}; 3463 + 3464 + fn generate_type_aliases( 3465 + fn_arg_types: &amp;amp;[Box&amp;lt;Type&amp;gt;], 3466 + fn_return_type: Box&amp;lt;Type&amp;gt;, 3467 + fn_name: &amp;amp;syn::Ident, 3468 + ) -&amp;gt; Vec&amp;lt;proc_macro2::TokenStream&amp;gt; { // 1 3469 + 3470 + let type_t0 = format_ident!(&amp;quot;_{}_T0&amp;quot;, fn_name); // 2 3471 + let mut type_aliases = vec![quote! { type #type_t0 = #fn_return_type }]; 3472 + 3473 + // 3 3474 + for (i, t) in (1..).zip(fn_arg_types.into_iter().rev()) { 3475 + let p = format_ident!(&amp;quot;_{}_{}&amp;quot;, fn_name, format!(&amp;quot;T{}&amp;quot;, i - 1)); 3476 + let n = format_ident!(&amp;quot;_{}_{}&amp;quot;, fn_name, format!(&amp;quot;T{}&amp;quot;, i)); 3477 + 3478 + type_aliases.push(quote! { 3479 + type #n = impl Fn(#t) -&amp;gt; #p 3480 + }); 3481 + } 3482 + 3483 + return type_aliases; 3484 + } 3485 + 3486 + &lt;/code&gt;&lt;/pre&gt; 3487 + &lt;p&gt;&lt;strong&gt;1. The return value&lt;/strong&gt;&lt;br /&gt; 3488 + We are returning a &lt;code&gt;Vec&amp;lt;proc_macro2::TokenStream&amp;gt;&lt;/code&gt;, i. e., a 3489 + list of &lt;code&gt;TokenStream&lt;/code&gt;s, where each item is a type alias.&lt;/p&gt; 3490 + &lt;p&gt;&lt;strong&gt;2. Format identifier?&lt;/strong&gt;&lt;br /&gt; 3491 + I've got some explanation to do on this line. Clearly, we 3492 + are trying to write the first type alias, and initialize our 3493 + &lt;code&gt;TokenStream&lt;/code&gt; vector with &lt;code&gt;T0&lt;/code&gt;, because it is different from 3494 + the others:&lt;/p&gt; 3495 + &lt;pre&gt;&lt;code class="language-rust"&gt;type T0 = something 3496 + // the others are of the form 3497 + type Tr = impl Fn(something) -&amp;gt; something 3498 + &lt;/code&gt;&lt;/pre&gt; 3499 + &lt;p&gt;&lt;code&gt;format_ident!&lt;/code&gt; is similar to &lt;code&gt;format!&lt;/code&gt;. Instead of 3500 + returning a formatted string, it returns a &lt;code&gt;syn::Ident&lt;/code&gt;. 3501 + Therefore, &lt;code&gt;type_t0&lt;/code&gt; is actually an identifier for, in the 3502 + case of our &lt;code&gt;add&lt;/code&gt; function, &lt;code&gt;_add_T0&lt;/code&gt;. Why is this 3503 + formatting important? Namespacing.&lt;/p&gt; 3504 + &lt;p&gt;Picture this, we have two functions, &lt;code&gt;add&lt;/code&gt; and &lt;code&gt;subtract&lt;/code&gt;, 3505 + that we wish to curry with our macro:&lt;/p&gt; 3506 + &lt;pre&gt;&lt;code class="language-rust"&gt;#[curry] 3507 + fn add(...) -&amp;gt; u32 { ... } 3508 + 3509 + #[curry] 3510 + fn sub(...) -&amp;gt; u32 { ... } 3511 + &lt;/code&gt;&lt;/pre&gt; 3512 + &lt;p&gt;Here is the same but with macros expanded:&lt;/p&gt; 3513 + &lt;pre&gt;&lt;code class="language-rust"&gt;type T0 = u32; 3514 + type T1 = impl Fn(u32) -&amp;gt; T0; 3515 + fn add( ... ) -&amp;gt; T1 { ... } 3516 + 3517 + type T0 = u32; 3518 + type T1 = impl Fn(u32) -&amp;gt; T0; 3519 + fn sub( ... ) -&amp;gt; T1 { ... } 3520 + &lt;/code&gt;&lt;/pre&gt; 3521 + &lt;p&gt;We end up with two definitions of &lt;code&gt;T0&lt;/code&gt;! Now, if we do the 3522 + little &lt;code&gt;format_ident!&lt;/code&gt; dance we did up there:&lt;/p&gt; 3523 + &lt;pre&gt;&lt;code class="language-rust"&gt;type _add_T0 = u32; 3524 + type _add_T1 = impl Fn(u32) -&amp;gt; _add_T0; 3525 + fn add( ... ) -&amp;gt; _add_T1 { ... } 3526 + 3527 + type _sub_T0 = u32; 3528 + type _sub_T1 = impl Fn(u32) -&amp;gt; _sub_T0; 3529 + fn sub( ... ) -&amp;gt; _sub_T1 { ... } 3530 + &lt;/code&gt;&lt;/pre&gt; 3531 + &lt;p&gt;Voilà! The type aliases don't tread on each other. Remember 3532 + to import &lt;code&gt;format_ident&lt;/code&gt; from the &lt;code&gt;quote&lt;/code&gt; crate.&lt;/p&gt; 3533 + &lt;p&gt;&lt;strong&gt;3. The TokenStream Vector&lt;/strong&gt;&lt;/p&gt; 3534 + &lt;p&gt;We iterate over our types in reverse order (&lt;code&gt;T0&lt;/code&gt; is the 3535 + last return, &lt;code&gt;T1&lt;/code&gt; is the second last, so on), assign a 3536 + number to each iteration with &lt;code&gt;zip&lt;/code&gt;, generate type names 3537 + with &lt;code&gt;format_ident&lt;/code&gt;, push a &lt;code&gt;TokenStream&lt;/code&gt; with the help of 3538 + &lt;code&gt;quote&lt;/code&gt; and variable interpolation.&lt;/p&gt; 3539 + &lt;p&gt;If you are wondering why we used &lt;code&gt;(1..).zip()&lt;/code&gt; instead of 3540 + &lt;code&gt;.enumerate()&lt;/code&gt;, it's because we wanted to start counting 3541 + from 1 instead of 0 (we are already done with &lt;code&gt;T0&lt;/code&gt;!).&lt;/p&gt; 3542 + &lt;h4 id="getting-it-together"&gt;Getting it together&lt;/h4&gt; 3543 + &lt;p&gt;I promised we'd have a fully working macro by the end of 3544 + last section. I lied, we have to tie everything together in 3545 + our &lt;code&gt;generate_curry&lt;/code&gt; function:&lt;/p&gt; 3546 + &lt;pre&gt;&lt;code class="language-rust"&gt;// src/lib.rs 3547 + 3548 + fn generate_curry(parsed: ItemFn) -&amp;gt; proc_macro2::TokenStream { 3549 + let fn_body = parsed.block; 3550 + let sig = parsed.sig; 3551 + let vis = parsed.vis; 3552 + let fn_name = sig.ident; 3553 + let fn_args = sig.inputs; 3554 + let fn_return_type = sig.output; 3555 + 3556 + let arg_idents = extract_arg_idents(fn_args.clone()); 3557 + let first_ident = &amp;amp;arg_idents.first().unwrap(); 3558 + let curried_body = generate_body(&amp;amp;arg_idents[1..], fn_body.clone()); 3559 + 3560 + + let arg_types = extract_arg_types(fn_args.clone()); 3561 + + let first_type = &amp;amp;arg_types.first().unwrap(); 3562 + + let type_aliases = generate_type_aliases( 3563 + + &amp;amp;arg_types[1..], 3564 + + extract_return_type(fn_return_type), 3565 + + &amp;amp;fn_name, 3566 + + ); 3567 + 3568 + + let return_type = format_ident!(&amp;quot;_{}_{}&amp;quot;, &amp;amp;fn_name, format!(&amp;quot;T{}&amp;quot;, type_aliases.len() - 1)); 3569 + 3570 + + return quote! { 3571 + + #(#type_aliases);* ; 3572 + + #vis fn #fn_name (#first_ident: #first_type) -&amp;gt; #return_type { 3573 + + #curried_body ; 3574 + + } 3575 + + }; 3576 + } 3577 + &lt;/code&gt;&lt;/pre&gt; 3578 + &lt;p&gt;Most of the additions are self explanatory, I'll go through 3579 + the return statement with you. We are returning a &lt;code&gt;quote!{ ... }&lt;/code&gt;, so a &lt;code&gt;proc_macro2::TokenStream&lt;/code&gt;. We are iterating 3580 + through the &lt;code&gt;type_aliases&lt;/code&gt; variable, which you might recall, 3581 + is a &lt;code&gt;Vec&amp;lt;TokenStream&amp;gt;&lt;/code&gt;. You might notice the sneaky 3582 + semicolon before the &lt;code&gt;*&lt;/code&gt;. This basically tells &lt;code&gt;quote&lt;/code&gt;, to 3583 + insert an item, then a semicolon, and then the next one, 3584 + another semicolon, and so on. The semicolon is a separator. 3585 + We need to manually insert another semicolon at the end of 3586 + it all, &lt;code&gt;quote&lt;/code&gt; doesn't insert a separator at the end of the 3587 + iteration.&lt;/p&gt; 3588 + &lt;p&gt;We retain the visibility and name of our original function. 3589 + Our curried function takes as args, just the first argument 3590 + of our original function. The return type of our curried 3591 + function is actually, the last type alias we create. If you 3592 + think back to our manually curried &lt;code&gt;add&lt;/code&gt; function, we 3593 + returned &lt;code&gt;T2&lt;/code&gt;, which was in fact, the last type alias we 3594 + created.&lt;/p&gt; 3595 + &lt;p&gt;I am sure, at this point, you are itching to test this out, 3596 + but before that, let me introduce you to some good methods 3597 + of debugging proc-macro code.&lt;/p&gt; 3598 + &lt;h3 id="debugging-and-testing"&gt;Debugging and Testing&lt;/h3&gt; 3599 + &lt;p&gt;Install &lt;code&gt;cargo-expand&lt;/code&gt; via:&lt;/p&gt; 3600 + &lt;pre&gt;&lt;code&gt;cargo install cargo-expand 3601 + &lt;/code&gt;&lt;/pre&gt; 3602 + &lt;p&gt;&lt;code&gt;cargo-expand&lt;/code&gt; is a neat little tool that expands your macro 3603 + in places where it is used, and lets you view the generated 3604 + code! For example:&lt;/p&gt; 3605 + &lt;pre&gt;&lt;code class="language-shell"&gt;# create a bin package hello 3606 + $ cargo new hello 3607 + 3608 + # view the expansion of the println! macro 3609 + $ cargo expand 3610 + 3611 + #![feature(prelude_import)] 3612 + #[prelude_import] 3613 + use std::prelude::v1::*; 3614 + #[macro_use] 3615 + extern crate std; 3616 + fn main() { 3617 + { 3618 + ::std::io::_print(::core::fmt::Arguments::new_v1( 3619 + &amp;amp;[&amp;quot;Hello, world!\n&amp;quot;], 3620 + &amp;amp;match () { 3621 + () =&amp;gt; [], 3622 + }, 3623 + )); 3624 + }; 3625 + } 3626 + &lt;/code&gt;&lt;/pre&gt; 3627 + &lt;p&gt;Writing proc-macros without &lt;code&gt;cargo-expand&lt;/code&gt; is tantamount to 3628 + driving a vehicle without rear view mirrors! Keep an eye on 3629 + what is going on behind your back.&lt;/p&gt; 3630 + &lt;p&gt;Now, your macro won't always compile, you might just recieve 3631 + the bee movie script as an error. &lt;code&gt;cargo-expand&lt;/code&gt; will not 3632 + work in such cases. I would suggest printing out your 3633 + variables to inspect them. &lt;code&gt;TokenStream&lt;/code&gt; implements 3634 + &lt;code&gt;Display&lt;/code&gt; as well as &lt;code&gt;Debug&lt;/code&gt;. We don't always have to be 3635 + respectable programmers. Just print it.&lt;/p&gt; 3636 + &lt;p&gt;Enough of that, lets get testing:&lt;/p&gt; 3637 + &lt;pre&gt;&lt;code class="language-rust"&gt;// tests/smoke.rs 3638 + 3639 + #![feature(type_alias_impl_trait)] 3640 + 3641 + #[crate_name::curry] 3642 + fn add(x: u32, y: u32, z: u32) -&amp;gt; u32 { 3643 + x + y + z 3644 + } 3645 + 3646 + #[test] 3647 + fn works() { 3648 + assert_eq!(15, add(4)(5)(6)); 3649 + } 3650 + &lt;/code&gt;&lt;/pre&gt; 3651 + &lt;p&gt;Run &lt;code&gt;cargo +nightly test&lt;/code&gt;. You should see a pleasing 3652 + message:&lt;/p&gt; 3653 + &lt;pre&gt;&lt;code&gt;running 1 test 3654 + test tests::works ... ok 3655 + &lt;/code&gt;&lt;/pre&gt; 3656 + &lt;p&gt;Take a look at the expansion for our curry macro, via 3657 + &lt;code&gt;cargo +nightly expand --tests smoke&lt;/code&gt;:&lt;/p&gt; 3658 + &lt;pre&gt;&lt;code class="language-rust"&gt;type _add_T0 = u32; 3659 + type _add_T1 = impl Fn(u32) -&amp;gt; _add_T0; 3660 + type _add_T2 = impl Fn(u32) -&amp;gt; _add_T1; 3661 + fn add(x: u32) -&amp;gt; _add_T2 { 3662 + return (move |y| { 3663 + move |z| { 3664 + return x + y + z; 3665 + } 3666 + }); 3667 + } 3668 + 3669 + // a bunch of other stuff generated by #[test] and assert_eq! 3670 + &lt;/code&gt;&lt;/pre&gt; 3671 + &lt;p&gt;A sight for sore eyes.&lt;/p&gt; 3672 + &lt;p&gt;Here is a more complex example that generates ten multiples 3673 + of the first ten natural numbers:&lt;/p&gt; 3674 + &lt;pre&gt;&lt;code class="language-rust"&gt;#[curry] 3675 + fn product(x: u32, y: u32) -&amp;gt; u32 { 3676 + x * y 3677 + } 3678 + 3679 + fn multiples() -&amp;gt; Vec&amp;lt;Vec&amp;lt;u32&amp;gt;&amp;gt;{ 3680 + let v = (1..=10).map(product); 3681 + return (1..=10) 3682 + .map(|x| v.clone().map(|f| f(x)).collect()) 3683 + .collect(); 3684 + } 3685 + &lt;/code&gt;&lt;/pre&gt; 3686 + &lt;h3 id="notes"&gt;Notes&lt;/h3&gt; 3687 + &lt;p&gt;I didn't quite explain why we use &lt;code&gt;move |arg|&lt;/code&gt; in our 3688 + closure. This is because we want to take ownership of the 3689 + variable supplied to us. Take a look at this example:&lt;/p&gt; 3690 + &lt;pre&gt;&lt;code class="language-rust"&gt;let v = add(5); 3691 + let g; 3692 + { 3693 + let x = 5; 3694 + g = v(x); 3695 + } 3696 + println!(&amp;quot;{}&amp;quot;, g(2)); 3697 + &lt;/code&gt;&lt;/pre&gt; 3698 + &lt;p&gt;Variable &lt;code&gt;x&lt;/code&gt; goes out of scope before &lt;code&gt;g&lt;/code&gt; can return a 3699 + concrete value. If we take ownership of &lt;code&gt;x&lt;/code&gt; by &lt;code&gt;move&lt;/code&gt;ing it 3700 + into our closure, we can expect this to work reliably. In 3701 + fact, rustc understands this, and forces you to use &lt;code&gt;move&lt;/code&gt;.&lt;/p&gt; 3702 + &lt;p&gt;This usage of &lt;code&gt;move&lt;/code&gt; is exactly why &lt;strong&gt;a curried function 3703 + without a return is useless&lt;/strong&gt;. Every variable we pass to our 3704 + curried function gets moved into its local scope. Playing 3705 + with these variables cannot cause a change outside this 3706 + scope. Returning is our only method of interaction with 3707 + anything beyond this function.&lt;/p&gt; 3708 + &lt;h3 id="conclusion"&gt;Conclusion&lt;/h3&gt; 3709 + &lt;p&gt;Currying may not seem to be all that useful. Curried 3710 + functions are unwieldy in Rust because the standard library 3711 + is not built around currying. If you enjoy the possibilities 3712 + posed by currying, consider taking a look at Haskell or 3713 + Scheme.&lt;/p&gt; 3714 + &lt;p&gt;My original intention with &lt;a href="https://peppe.rs"&gt;peppe.rs&lt;/a&gt; was 3715 + to post condensed articles, a micro blog, but this one 3716 + turned out extra long.&lt;/p&gt; 3717 + &lt;p&gt;Perhaps I should call it a 'macro' blog :)&lt;/p&gt; 3718 + </description> 3719 + <link>https://oppi.li/posts/auto-currying_rust_functions/</link> 3720 + <pubDate>Fri, 08 May 2020 00:00:00 +0000</pubDate> 3721 + <guid>https://oppi.li/posts/auto-currying_rust_functions/</guid> 3722 + </item> 3723 + <item> 3724 + <title>pixel art in GIMP</title> 3725 + <description>&lt;p&gt;I've always been an admirer of pixel art, because of it's 3726 + simplicity and it's resemblance to bitmap font design. 3727 + Recently, I decided to take the dive and make some art of my 3728 + own.&lt;/p&gt; 3729 + &lt;p&gt;I used GIMP because I am fairly familiar with it. Aseprite 3730 + seems to be the editor of choice for animated pixel art 3731 + though.&lt;/p&gt; 3732 + &lt;h3 id="setting-up-the-canvas"&gt;Setting up the canvas&lt;/h3&gt; 3733 + &lt;p&gt;Picking a canvas size is daunting. Too small, and you won't 3734 + be able to fit in enough detail to make a legible piece. Too 3735 + big and you've got too many pixels to work with!&lt;/p&gt; 3736 + &lt;p&gt;I would suggest starting out with anywhere between 100x100 3737 + and 200x200. &lt;a href="https://cdn.oppi.li/u9.png"&gt;Here's&lt;/a&gt; a sample 3738 + configuration.&lt;/p&gt; 3739 + &lt;p&gt;Sometimes I use a 10x10 grid, &lt;code&gt;View &amp;gt; Show Grid&lt;/code&gt; and &lt;code&gt;Edit &amp;gt; Preferences &amp;gt; Default Grid &amp;gt; Spacing&lt;/code&gt;, but that can get 3740 + jarring, so I throw down a couple of guides, drag right or 3741 + down from the left or top gutters for vertical and 3742 + horizontal guides respectively.&lt;/p&gt; 3743 + &lt;h3 id="choosing-a-brush"&gt;Choosing a Brush&lt;/h3&gt; 3744 + &lt;p&gt;The most important part of our setup is the brush. Use the 3745 + Pencil Tool (&lt;code&gt;n&lt;/code&gt; on the keyboard) for hard edge drawings. 3746 + Here's a small comparison if you don't know the difference 3747 + between a hard edge and a soft edge:&lt;/p&gt; 3748 + &lt;p&gt;&lt;img src="https://cdn.oppi.li/kz.png" alt="Hard edge vs Soft Edge" /&gt;&lt;/p&gt; 3749 + &lt;p&gt;I turn the size down all the way to 1 (&lt;code&gt;[&lt;/code&gt; on the keyboard). 3750 + Set &lt;code&gt;Dynamics&lt;/code&gt; off. &lt;a href="https://cdn.oppi.li/Fs.png"&gt;Here's&lt;/a&gt; a 3751 + sample brush configuration.&lt;/p&gt; 3752 + &lt;h3 id="laying-down-the-pixels"&gt;Laying down the pixels!&lt;/h3&gt; 3753 + &lt;p&gt;With the boring stuff out of the way, we can start with our 3754 + piece. I usually follow a three step process:&lt;/p&gt; 3755 + &lt;ul&gt; 3756 + &lt;li&gt;draw a rough outline 3757 + &lt;/li&gt; 3758 + &lt;li&gt;fill in the shadows 3759 + &lt;/li&gt; 3760 + &lt;li&gt;add highlights 3761 + &lt;/li&gt; 3762 + &lt;/ul&gt; 3763 + &lt;p&gt;But this process is better explained with an example: an 3764 + onigiri. Let us start off with a 100x100 canvas.&lt;/p&gt; 3765 + &lt;h4 id="drawing-the-outline"&gt;Drawing the outline&lt;/h4&gt; 3766 + &lt;p&gt;For the most part, our figure will be symmetric. If you are 3767 + on GIMP 2.10+, you can take advantage of the Symmetry 3768 + Painting feature. Go ahead and enable vertical symmetry, 3769 + &lt;code&gt;Window &amp;gt; Dockable Dialogs &amp;gt; Symmetry Painting&lt;/code&gt; and 3770 + &lt;code&gt;Symmetry Painting &amp;gt; Symmetry &amp;gt; Mirror &amp;gt; Vertical&lt;/code&gt;.&lt;/p&gt; 3771 + &lt;p&gt;If you are running an older version of GIMP, draw in the 3772 + left side, duplicate the layer, flip it horizontally, and 3773 + merge it with the original.&lt;/p&gt; 3774 + &lt;p&gt;Your outline might look something like this:&lt;/p&gt; 3775 + &lt;p&gt;&lt;img src="https://cdn.oppi.li/mn.png" alt="" /&gt;&lt;/p&gt; 3776 + &lt;p&gt;Go ahead and fill it in with the fill tool (&lt;code&gt;Shift + b&lt;/code&gt; on 3777 + the keyboard), add in some seaweed as well, preferably on a 3778 + different layer. You can toggle symmetry on and off to save 3779 + yourself some time.&lt;/p&gt; 3780 + &lt;p&gt;&lt;img src="https://cdn.oppi.li/xu.png" alt="" /&gt;&lt;/p&gt; 3781 + &lt;h4 id="shadows"&gt;Shadows&lt;/h4&gt; 3782 + &lt;p&gt;For now, let us focus on the shadows on the object itself, 3783 + we'll come back to the shadows cast by the object on the 3784 + surface later.&lt;/p&gt; 3785 + &lt;p&gt;Shadows on any surface always follow the shape of the 3786 + surface. A spherical onigiri would have a circular shadow:&lt;/p&gt; 3787 + &lt;p&gt;&lt;img src="https://cdn.oppi.li/FU.png" alt="" /&gt;&lt;/p&gt; 3788 + &lt;p&gt;A couple of noticeable changes:&lt;/p&gt; 3789 + &lt;p&gt;&lt;strong&gt;Layers&lt;/strong&gt;: The layer containing the seaweed has been hidden.&lt;br /&gt; 3790 + &lt;strong&gt;Color&lt;/strong&gt;: The color of the shadow is just a slightly 3791 + lighter version of the original object (reduce the Value on 3792 + the HSV scale).&lt;br /&gt; 3793 + &lt;strong&gt;Area&lt;/strong&gt;: The shadow does not go all the way (notice the bottom 3794 + edges).&lt;/p&gt; 3795 + &lt;p&gt;The shadow does not go all the way because we will be 3796 + filling in that area with another, darker shadow! An image 3797 + might explain better:&lt;/p&gt; 3798 + &lt;p&gt;&lt;img src="https://cdn.oppi.li/Br.png" alt="" /&gt;&lt;/p&gt; 3799 + &lt;p&gt;To emulate soft lights, reduce the value by 2 to 3 points 3800 + every iteration. Notice how area &lt;code&gt;1&lt;/code&gt; is much larger than 3801 + area &lt;code&gt;4&lt;/code&gt;. This is because an onigiri resembles a bottom 3802 + heavy oblate spheroid, a sphere that is slightly fatter 3803 + around the lower bottom, and areas &lt;code&gt;1&lt;/code&gt; and &lt;code&gt;2&lt;/code&gt; catch more 3804 + light than areas &lt;code&gt;3&lt;/code&gt; and &lt;code&gt;4&lt;/code&gt;.&lt;/p&gt; 3805 + &lt;p&gt;Do the same with the seaweed. The seaweed, being a smaller, 3806 + flatter object, doesn't cast much of a shadow, so stop with 3807 + 1 or 2 iterations of the gradient:&lt;/p&gt; 3808 + &lt;p&gt;&lt;img src="https://cdn.oppi.li/T3.png" alt="" /&gt;&lt;/p&gt; 3809 + &lt;p&gt;We're getting there!&lt;/p&gt; 3810 + &lt;h4 id="highlights"&gt;Highlights&lt;/h4&gt; 3811 + &lt;p&gt;This step handles the details on the strongly illuminated 3812 + portions of the object. Seaweed is a bit glossy, lighten the 3813 + edges to make it seem shiny. The rice is not as shiny, but 3814 + it does form an uneven surface. Add in some shadows to 3815 + promote the idea of rice grains. Here is the finished 3816 + result:&lt;/p&gt; 3817 + &lt;p&gt;&lt;img src="https://cdn.oppi.li/VE.png" alt="" /&gt;&lt;/p&gt; 3818 + &lt;h3 id="finishing-touches"&gt;Finishing Touches&lt;/h3&gt; 3819 + &lt;p&gt;Some color correction and &lt;code&gt;a e s t h e t i c&lt;/code&gt; Japanese text 3820 + later, our piece is complete!&lt;/p&gt; 3821 + &lt;p&gt;&lt;img src="https://cdn.oppi.li/cn.png" alt="" /&gt;&lt;/p&gt; 3822 + &lt;p&gt;Hold on, why is it so tiny? Well, that's because our canvas 3823 + was 100x100, head over to &lt;code&gt;Image &amp;gt; Scale Image&lt;/code&gt;, set 3824 + &lt;code&gt;Quality &amp;gt; Interpolation&lt;/code&gt; to &lt;code&gt;None&lt;/code&gt; and scale it up to 3825 + 700x700, et voilà!&lt;/p&gt; 3826 + &lt;p&gt;&lt;img src="https://cdn.oppi.li/CH.png" alt="" /&gt;&lt;/p&gt; 3827 + </description> 3828 + <link>https://oppi.li/posts/pixel_art_in_GIMP/</link> 3829 + <pubDate>Wed, 08 Apr 2020 00:00:00 +0000</pubDate> 3830 + <guid>https://oppi.li/posts/pixel_art_in_GIMP/</guid> 3831 + </item> 3832 + <item> 3833 + <title>rapid refactoring with vim</title> 3834 + <description>&lt;p&gt;Last weekend, I was tasked with refactoring the 96 unit 3835 + tests on 3836 + &lt;a href="https://github.com/ruma/ruma-events/pull/70"&gt;ruma-events&lt;/a&gt; 3837 + to use strictly typed json objects using &lt;code&gt;serde_json::json!&lt;/code&gt; 3838 + instead of raw strings. It was rather painless thanks to 3839 + vim :)&lt;/p&gt; 3840 + &lt;p&gt;Here's a small sample of what had to be done (note the lines 3841 + prefixed with the arrow):&lt;/p&gt; 3842 + &lt;pre&gt;&lt;code class="language-rust"&gt;→ use serde_json::{from_str}; 3843 + 3844 + #[test] 3845 + fn deserialize() { 3846 + assert_eq!( 3847 + → from_str::&amp;lt;Action&amp;gt;(r#&amp;quot;{&amp;quot;set_tweak&amp;quot;: &amp;quot;highlight&amp;quot;}&amp;quot;#), 3848 + Action::SetTweak(Tweak::Highlight { value: true }) 3849 + ); 3850 + } 3851 + &lt;/code&gt;&lt;/pre&gt; 3852 + &lt;p&gt;had to be converted to:&lt;/p&gt; 3853 + &lt;pre&gt;&lt;code class="language-rust"&gt;→ use serde_json::{from_value}; 3854 + 3855 + #[test] 3856 + fn deserialize() { 3857 + assert_eq!( 3858 + → from_value::&amp;lt;Action&amp;gt;(json!({&amp;quot;set_tweak&amp;quot;: &amp;quot;highlight&amp;quot;})), 3859 + Action::SetTweak(Tweak::Highlight { value: true }) 3860 + ); 3861 + } 3862 + &lt;/code&gt;&lt;/pre&gt; 3863 + &lt;h3 id="the-arglist"&gt;The arglist&lt;/h3&gt; 3864 + &lt;p&gt;For the initial pass, I decided to handle imports, this was 3865 + a simple find and replace operation, done to all the files 3866 + containing tests. Luckily, modules (and therefore files) 3867 + containing tests in Rust are annotated with the 3868 + &lt;code&gt;#[cfg(test)]&lt;/code&gt; attribute. I opened all such files:&lt;/p&gt; 3869 + &lt;pre&gt;&lt;code class="language-bash"&gt;# `grep -l pattern files` lists all the files 3870 + # matching the pattern 3871 + 3872 + vim $(grep -l 'cfg\(test\)' ./**/*.rs) 3873 + 3874 + # expands to something like: 3875 + vim push_rules.rs room/member.rs key/verification/lib.rs 3876 + &lt;/code&gt;&lt;/pre&gt; 3877 + &lt;p&gt;Starting vim with more than one file at the shell prompt 3878 + populates the arglist. Hit &lt;code&gt;:args&lt;/code&gt; to see the list of 3879 + files currently ready to edit. The square [brackets] 3880 + indicate the current file. Navigate through the arglist 3881 + with &lt;code&gt;:next&lt;/code&gt; and &lt;code&gt;:prev&lt;/code&gt;. I use tpope's vim-unimpaired 3882 + &lt;a href="https://github.com/tpope/vim-unimpaired"&gt;^un&lt;/a&gt;, which adds &lt;code&gt;]a&lt;/code&gt; and &lt;code&gt;[a&lt;/code&gt;, mapped to &lt;code&gt;:next&lt;/code&gt; and 3883 + &lt;code&gt;:prev&lt;/code&gt;.&lt;/p&gt; 3884 + &lt;p&gt;It also handles various other mappings, &lt;code&gt;]q&lt;/code&gt; and &lt;code&gt;[q&lt;/code&gt; to 3885 + navigate the quickfix list for example&lt;/p&gt; 3886 + &lt;p&gt;All that's left to do is the find and replace, for which we 3887 + will be using vim's &lt;code&gt;argdo&lt;/code&gt;, applying a substitution to 3888 + every file in the arglist:&lt;/p&gt; 3889 + &lt;pre&gt;&lt;code&gt;:argdo s/from_str/from_value/g 3890 + &lt;/code&gt;&lt;/pre&gt; 3891 + &lt;h3 id="the-quickfix-list"&gt;The quickfix list&lt;/h3&gt; 3892 + &lt;p&gt;Next up, replacing &lt;code&gt;r#&amp;quot; ... &amp;quot;#&lt;/code&gt; with &lt;code&gt;json!( ... )&lt;/code&gt;. I 3893 + couldn't search and replace that trivially, so I went with a 3894 + macro call [^macro] instead, starting with the cursor on 3895 + 'r', represented by the caret, in my attempt to breakdown 3896 + the process:&lt;/p&gt; 3897 + &lt;p&gt;[^macro]: &lt;code&gt;:help recording&lt;/code&gt;&lt;/p&gt; 3898 + &lt;pre&gt;&lt;code&gt;BUFFER: r#&amp;quot; ... &amp;quot;#; 3899 + ^ 3900 + 3901 + ACTION: vllsjson!( 3902 + 3903 + BUFFER json!( ... &amp;quot;#; 3904 + ^ 3905 + 3906 + ACTION: &amp;lt;esc&amp;gt;$F# 3907 + 3908 + BUFFER: json!( ... &amp;quot;#; 3909 + ^ 3910 + 3911 + ACTION: vhs)&amp;lt;esc&amp;gt; 3912 + 3913 + BUFFER: json!( ... ); 3914 + &lt;/code&gt;&lt;/pre&gt; 3915 + &lt;p&gt;Here's the recorded [^rec] macro in all its glory: 3916 + &lt;code&gt;vllsjson!(&amp;lt;esc&amp;gt;$F#vhs)&amp;lt;esc&amp;gt;&lt;/code&gt;.&lt;/p&gt; 3917 + &lt;p&gt;[^rec]: When I'm recording a macro, I prefer starting out by 3918 + storing it in register &lt;code&gt;q&lt;/code&gt;, and then copying it over to 3919 + another register if it works as intended. I think of &lt;code&gt;qq&lt;/code&gt; as 3920 + 'quick record'.&lt;/p&gt; 3921 + &lt;p&gt;Great! So now we just go ahead, find every occurrence of 3922 + &lt;code&gt;r#&lt;/code&gt; and apply the macro right? Unfortunately, there were 3923 + more than a few occurrences of raw strings that had to stay 3924 + raw strings. Enter, the quickfix list.&lt;/p&gt; 3925 + &lt;p&gt;The idea behind the quickfix list is to jump from one 3926 + position in a file to another (maybe in a different file), 3927 + much like how the arglist lets you jump from one file to 3928 + another.&lt;/p&gt; 3929 + &lt;p&gt;One of the easiest ways to populate this list with a bunch 3930 + of positions is to use &lt;code&gt;vimgrep&lt;/code&gt;:&lt;/p&gt; 3931 + &lt;pre&gt;&lt;code&gt;# basic usage 3932 + :vimgrep pattern files 3933 + 3934 + # search for raw strings 3935 + :vimgrep 'r#' ./**/*.rs 3936 + &lt;/code&gt;&lt;/pre&gt; 3937 + &lt;p&gt;Like &lt;code&gt;:next&lt;/code&gt; and &lt;code&gt;:prev&lt;/code&gt;, you can navigate the quickfix list 3938 + with &lt;code&gt;:cnext&lt;/code&gt; and &lt;code&gt;:cprev&lt;/code&gt;. Every time you move up or down 3939 + the list, vim indicates your index:&lt;/p&gt; 3940 + &lt;pre&gt;&lt;code&gt;(1 of 131): r#&amp;quot;{&amp;quot;set_tweak&amp;quot;: &amp;quot;highlight&amp;quot;}&amp;quot;#; 3941 + &lt;/code&gt;&lt;/pre&gt; 3942 + &lt;p&gt;And just like &lt;code&gt;argdo&lt;/code&gt;, you can &lt;code&gt;cdo&lt;/code&gt; to apply commands to 3943 + &lt;em&gt;every&lt;/em&gt; match in the quickfix list:&lt;/p&gt; 3944 + &lt;pre&gt;&lt;code&gt;:cdo norm! @q 3945 + &lt;/code&gt;&lt;/pre&gt; 3946 + &lt;p&gt;But, I had to manually pick out matches, and it involved 3947 + some button mashing.&lt;/p&gt; 3948 + &lt;h3 id="external-filtering"&gt;External Filtering&lt;/h3&gt; 3949 + &lt;p&gt;Some code reviews later, I was asked to format all the json 3950 + inside the &lt;code&gt;json!&lt;/code&gt; macro. All you have to do is pass a 3951 + visual selection through a pretty json printer. Select the 3952 + range to be formatted in visual mode, and hit &lt;code&gt;:&lt;/code&gt;, you will 3953 + notice the command line displaying what seems to be 3954 + gibberish:&lt;/p&gt; 3955 + &lt;pre&gt;&lt;code&gt;:'&amp;lt;,'&amp;gt; 3956 + &lt;/code&gt;&lt;/pre&gt; 3957 + &lt;p&gt;&lt;code&gt;'&amp;lt;&lt;/code&gt; and &lt;code&gt;'&amp;gt;&lt;/code&gt; are &lt;em&gt;marks&lt;/em&gt; [^mark-motions]. More 3958 + specifically, they are marks that vim sets automatically 3959 + every time you make a visual selection, denoting the start 3960 + and end of the selection.&lt;/p&gt; 3961 + &lt;p&gt;[^mark-motions]: &lt;code&gt;:help mark-motions&lt;/code&gt;&lt;/p&gt; 3962 + &lt;p&gt;A range is one or more line specifiers separated by a &lt;code&gt;,&lt;/code&gt;:&lt;/p&gt; 3963 + &lt;pre&gt;&lt;code&gt;:1,7 lines 1 through 7 3964 + :32 just line 32 3965 + :. the current line 3966 + :.,$ the current line to the last line 3967 + :'a,'b mark 'a' to mark 'b' 3968 + &lt;/code&gt;&lt;/pre&gt; 3969 + &lt;p&gt;Most &lt;code&gt;:&lt;/code&gt; commands can be prefixed by ranges. &lt;code&gt;:help usr_10.txt&lt;/code&gt; for more on that.&lt;/p&gt; 3970 + &lt;p&gt;Alright, lets pass json through &lt;code&gt;python -m json.tool&lt;/code&gt;, a 3971 + json formatter that accepts &lt;code&gt;stdin&lt;/code&gt; (note the use of &lt;code&gt;!&lt;/code&gt; to 3972 + make use of an external program):&lt;/p&gt; 3973 + &lt;pre&gt;&lt;code&gt;:'&amp;lt;,'&amp;gt;!python -m json.tool 3974 + &lt;/code&gt;&lt;/pre&gt; 3975 + &lt;p&gt;Unfortunately that didn't quite work for me because the 3976 + range included some non-json text as well, a mix of regex 3977 + and macros helped fix that. I think you get the drift.&lt;/p&gt; 3978 + &lt;p&gt;Another fun filter I use from time to time is &lt;code&gt;:!sort&lt;/code&gt;, to 3979 + sort css attributes, or &lt;code&gt;:!uniq&lt;/code&gt; to remove repeated imports.&lt;/p&gt; 3980 + </description> 3981 + <link>https://oppi.li/posts/rapid_refactoring_with_vim/</link> 3982 + <pubDate>Tue, 31 Mar 2020 00:00:00 +0000</pubDate> 3983 + <guid>https://oppi.li/posts/rapid_refactoring_with_vim/</guid> 3984 + </item> 3985 + <item> 3986 + <title>font size fallacies</title> 3987 + <description>&lt;p&gt;I am not an expert with fonts, but I do have some 3988 + experience &lt;a href="https://github.com/nerdypepper/scientifica"&gt;^exp&lt;/a&gt;, and common sense. This post aims to debunk some 3989 + misconceptions about font sizes!&lt;/p&gt; 3990 + &lt;p&gt;11 px on your display is &lt;em&gt;probably not&lt;/em&gt; 11 px on my display. 3991 + Let's do some quick math. I have two displays, 1366x768 @ 3992 + 21&amp;quot; and another with 1920x1080 @ 13&amp;quot;, call them &lt;code&gt;A&lt;/code&gt; and 3993 + &lt;code&gt;B&lt;/code&gt; for now.&lt;/p&gt; 3994 + &lt;p&gt;Display &lt;code&gt;A&lt;/code&gt; has 1,049,088 pixels. A pixel is a square, of 3995 + side say, &lt;code&gt;s&lt;/code&gt; cm. The total area covered by my 21&amp;quot; display 3996 + is about 1,066 cm^2 (41x26). Thus,&lt;/p&gt; 3997 + &lt;pre&gt;&lt;code&gt;Display A 3998 + Dimensions: 1366x768 @ 21&amp;quot; (41x26 sq. cm) 3999 + 1,049,088 s^2 = 1066 4000 + s = 0.0318 cm (side of a pixel on Display A) 4001 + &lt;/code&gt;&lt;/pre&gt; 4002 + &lt;p&gt;Bear with me, as I repeat the number crunching for Display 4003 + &lt;code&gt;B&lt;/code&gt;:&lt;/p&gt; 4004 + &lt;pre&gt;&lt;code&gt;Display B 4005 + Dimensions: 1920x1080 @ 13&amp;quot; (29.5x16.5 sq. cm) 4006 + 2,073,600 s^2 = 486.75 4007 + s = 0.0153 cm (side of a pixel on Display B) 4008 + &lt;/code&gt;&lt;/pre&gt; 4009 + &lt;p&gt;The width of a pixel on Display &lt;code&gt;A&lt;/code&gt; is &lt;em&gt;double&lt;/em&gt; the width of a 4010 + pixel on Display &lt;code&gt;B&lt;/code&gt;. The area occupied by a pixel on Display 4011 + &lt;code&gt;A&lt;/code&gt; is &lt;em&gt;4 times&lt;/em&gt; the area occupied by a pixel on Display &lt;code&gt;B&lt;/code&gt;.&lt;/p&gt; 4012 + &lt;p&gt;&lt;em&gt;The size of a pixel varies from display to display!&lt;/em&gt;&lt;/p&gt; 4013 + &lt;p&gt;A 5x11 bitmap font on Display &lt;code&gt;A&lt;/code&gt; would be around 4 mm tall 4014 + whereas the same bitmap font on Display &lt;code&gt;B&lt;/code&gt; would be around 4015 + 1.9 mm tall. A 11 px tall character on &lt;code&gt;B&lt;/code&gt; is visually 4016 + equivalent to a 5 px character on &lt;code&gt;A&lt;/code&gt;. When you view a 4017 + screenshot of Display &lt;code&gt;A&lt;/code&gt; on Display &lt;code&gt;B&lt;/code&gt;, the contents are 4018 + shrunk down by a factor of 2!&lt;/p&gt; 4019 + &lt;p&gt;So screen resolution is not enough, how else do we measure 4020 + size? Pixel Density! Keen readers will realize that the 5^th 4021 + grade math problem we solved up there showcases pixel 4022 + density, or, pixels per cm (PPCM). Usually we deal with 4023 + pixels per inch (PPI).&lt;/p&gt; 4024 + &lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; PPI is not to be confused with DPI &lt;a href="https://en.wikipedia.org/wiki/Dots_per_inch"&gt;^dpi&lt;/a&gt; (dots 4025 + per inch). DPI is defined for printers.&lt;/p&gt; 4026 + &lt;p&gt;In our example, &lt;code&gt;A&lt;/code&gt; is a 75 ppi display and &lt;code&gt;B&lt;/code&gt; is around 4027 + 165 ppi &lt;a href="https://www.sven.de/dpi/"&gt;^ppi&lt;/a&gt;. A low ppi display appears to be 4028 + 'pixelated', because the pixels are more prominent, much 4029 + like Display &lt;code&gt;A&lt;/code&gt;. A higher ppi usually means you can view 4030 + larger images and render crispier fonts. The average desktop 4031 + display can stuff 100-200 pixels per inch. Smart phones 4032 + usually fall into the 400-600 ppi (XXXHDPI) category. The 4033 + human eye fails to differentiate detail past 300 ppi.&lt;/p&gt; 4034 + &lt;p&gt;&lt;em&gt;So ... streaming an 8K video on a 60&amp;quot; TV provides the same 4035 + clarity as a HD video on a smart phone?&lt;/em&gt;&lt;/p&gt; 4036 + &lt;p&gt;Absolutely. Well, clarity is subjective, but the amount of 4037 + detail you can discern on mobile displays has always been 4038 + limited. Salty consumers of the Xperia 1 &lt;a href="https://en.wikipedia.org/wiki/Sony_Xperia_1"&gt;^sony&lt;/a&gt; will say 4039 + otherwise.&lt;/p&gt; 4040 + &lt;p&gt;Maybe I will talk about font rendering in another post, but 4041 + thats all for now. Don't judge a font size by its 4042 + screenshot.&lt;/p&gt; 4043 + </description> 4044 + <link>https://oppi.li/posts/font_size_fallacies/</link> 4045 + <pubDate>Mon, 16 Mar 2020 00:00:00 +0000</pubDate> 4046 + <guid>https://oppi.li/posts/font_size_fallacies/</guid> 4047 + </item> 4048 + <item> 4049 + <title>termux tandem</title> 4050 + <description>&lt;p&gt;I learnt about &lt;code&gt;termux&lt;/code&gt; from a friend on IRC recently. 4051 + It looked super gimmicky to me at first, but it eventually 4052 + proved to be useful. Here's what I use it for:&lt;/p&gt; 4053 + &lt;h3 id="rsync"&gt;rsync&lt;/h3&gt; 4054 + &lt;p&gt;Ever since I degoogled my android device, syncing files 4055 + between my phone and my PC has always been a pain. I'm 4056 + looking at you MTP. But, with &lt;code&gt;termux&lt;/code&gt; and &lt;code&gt;sshd&lt;/code&gt; all set up, 4057 + it's as simple as:&lt;/p&gt; 4058 + &lt;pre&gt;&lt;code&gt;$ arp 4059 + Address HWtype HWad ... 4060 + 192.168.43.187 ether d0:0 ... 4061 + 4062 + $ rsync -avz 192.168.43.187:~/frogs ~/pics/frogs 4063 + &lt;/code&gt;&lt;/pre&gt; 4064 + &lt;h3 id="ssh-tmux"&gt;ssh &amp;amp; tmux&lt;/h3&gt; 4065 + &lt;p&gt;My phone doubles as a secondary view into my main machine 4066 + with &lt;code&gt;ssh&lt;/code&gt; and &lt;code&gt;tmux&lt;/code&gt;. When I am away from my PC (read: 4067 + sitting across the room), I check build status and IRC 4068 + messages by &lt;code&gt;ssh&lt;/code&gt;ing into a tmux session running the said 4069 + build or weechat.&lt;/p&gt; 4070 + &lt;h3 id="file-uploads"&gt;file uploads&lt;/h3&gt; 4071 + &lt;p&gt;Not being able to access my (ssh-only) file host was 4072 + crippling. With a &lt;code&gt;bash&lt;/code&gt; instance on my phone, I just copied 4073 + over my ssh keys, and popped in a file upload script (a 4074 + glorified &lt;code&gt;scp&lt;/code&gt;). Now I just have to figure out a way to 4075 + clean up these file names ...&lt;/p&gt; 4076 + &lt;pre&gt;&lt;code&gt;~/storage/pictures/ $ ls 4077 + 02muf5g7b2i41.jpg 7alt3cwg77841.jpg cl4bsrge7id11.png 4078 + mtZabXG.jpg p8d5c584f2841.jpg vjUxGjq.jpg 4079 + &lt;/code&gt;&lt;/pre&gt; 4080 + &lt;h3 id="cmus"&gt;cmus&lt;/h3&gt; 4081 + &lt;p&gt;Alright, I don't really listen to music via &lt;code&gt;cmus&lt;/code&gt;, but I 4082 + did use it a couple times when my default music player was 4083 + acting up. &lt;code&gt;cmus&lt;/code&gt; is a viable option:&lt;/p&gt; 4084 + &lt;p&gt;&lt;a href="https://cdn.oppi.li/CP.jpg"&gt;&lt;img src="https://cdn.oppi.li/CP.jpg" alt="" /&gt;&lt;/a&gt;&lt;/p&gt; 4085 + </description> 4086 + <link>https://oppi.li/posts/termux_tandem/</link> 4087 + <pubDate>Sat, 07 Mar 2020 00:00:00 +0000</pubDate> 4088 + <guid>https://oppi.li/posts/termux_tandem/</guid> 4089 + </item> 4090 + <item> 4091 + <title>call to ARMs</title> 4092 + <description>&lt;p&gt;My 4th semester involves ARM programming. And proprietary 4093 + tooling (Keil C). But we don't do that here.&lt;/p&gt; 4094 + &lt;h3 id="building"&gt;Building&lt;/h3&gt; 4095 + &lt;p&gt;Assembling and linking ARM binaries on non-ARM architecture 4096 + devices is fairly trivial. I went along with the GNU cross 4097 + bare metal toolchain binutils, which provides &lt;code&gt;arm-as&lt;/code&gt; and 4098 + &lt;code&gt;arm-ld&lt;/code&gt; (among a bunch of other utils that I don't care 4099 + about for now).&lt;/p&gt; 4100 + &lt;p&gt;Assemble &lt;code&gt;.s&lt;/code&gt; files with:&lt;/p&gt; 4101 + &lt;pre&gt;&lt;code class="language-shell"&gt;arm-none-eabi-as main.s -g -march=armv8.1-a -o main.out 4102 + &lt;/code&gt;&lt;/pre&gt; 4103 + &lt;p&gt;The &lt;code&gt;-g&lt;/code&gt; flag generates extra debugging information that 4104 + &lt;code&gt;gdb&lt;/code&gt; picks up. The &lt;code&gt;-march&lt;/code&gt; option establishes target 4105 + architecture.&lt;/p&gt; 4106 + &lt;p&gt;Link &lt;code&gt;.o&lt;/code&gt; files with:&lt;/p&gt; 4107 + &lt;pre&gt;&lt;code class="language-shell"&gt;arm-none-eabi-ld main.out -o main 4108 + &lt;/code&gt;&lt;/pre&gt; 4109 + &lt;h3 id="running-and-debugging"&gt;Running (and Debugging)&lt;/h3&gt; 4110 + &lt;p&gt;Things get interesting here. &lt;code&gt;gdb&lt;/code&gt; on your x86 machine 4111 + cannot read nor execute binaries compiled for ARM. So, we 4112 + simulate an ARM processor using &lt;code&gt;qemu&lt;/code&gt;. Now qemu allows you 4113 + to run &lt;code&gt;gdbserver&lt;/code&gt; on startup. Connecting our local &lt;code&gt;gdb&lt;/code&gt; 4114 + instance to &lt;code&gt;gdbserver&lt;/code&gt; gives us a view into the program's 4115 + execution. Easy!&lt;/p&gt; 4116 + &lt;p&gt;Run &lt;code&gt;qemu&lt;/code&gt;, with &lt;code&gt;gdbserver&lt;/code&gt; on port &lt;code&gt;1234&lt;/code&gt;, with our ARM 4117 + binary, &lt;code&gt;main&lt;/code&gt;:&lt;/p&gt; 4118 + &lt;pre&gt;&lt;code class="language-shell"&gt;qemu-arm -singlestep -g 1234 main 4119 + &lt;/code&gt;&lt;/pre&gt; 4120 + &lt;p&gt;Start up &lt;code&gt;gdb&lt;/code&gt; on your machine, and connect to &lt;code&gt;qemu&lt;/code&gt;'s 4121 + &lt;code&gt;gdbserver&lt;/code&gt;:&lt;/p&gt; 4122 + &lt;pre&gt;&lt;code&gt;(gdb) set architecture armv8-a 4123 + (gdb) target remote localhost:1234 4124 + (gdb) file main 4125 + Reading symbols from main... # yay! 4126 + &lt;/code&gt;&lt;/pre&gt; 4127 + &lt;h3 id="gdb-enhanced"&gt;GDB Enhanced&lt;/h3&gt; 4128 + &lt;p&gt;&lt;code&gt;gdb&lt;/code&gt; is cool, but it's not nearly as comfortable as well 4129 + fleshed out emulators/IDEs like Keil. Watching registers, 4130 + CPSR and memory chunks update &lt;em&gt;is&lt;/em&gt; pretty fun.&lt;/p&gt; 4131 + &lt;p&gt;I came across &lt;code&gt;gdb&lt;/code&gt;'s TUI mode (hit &lt;code&gt;C-x C-a&lt;/code&gt; or type &lt;code&gt;tui enable&lt;/code&gt; at the prompt). TUI mode is a godsend. It highlights 4132 + the current line of execution, shows you disassembly 4133 + outputs, updated registers, active breakpoints and more.&lt;/p&gt; 4134 + &lt;p&gt;&lt;em&gt;But&lt;/em&gt;, it is an absolute eyesore.&lt;/p&gt; 4135 + &lt;p&gt;Say hello to &lt;a href="https://github.com/hugsy/gef"&gt;GEF&lt;/a&gt;! &amp;quot;GDB 4136 + Enhanced Features&amp;quot; teaches our old dog some cool new tricks. 4137 + Here are some additions that made my ARM debugging 4138 + experience loads better:&lt;/p&gt; 4139 + &lt;ul&gt; 4140 + &lt;li&gt;Memory watches 4141 + &lt;/li&gt; 4142 + &lt;li&gt;Register watches, with up to 7 levels of deref (overkill, 4143 + I agree) 4144 + &lt;/li&gt; 4145 + &lt;li&gt;Stack tracing 4146 + &lt;/li&gt; 4147 + &lt;/ul&gt; 4148 + &lt;p&gt;And it's pretty! See for yourself:&lt;/p&gt; 4149 + &lt;p&gt;&lt;a href="https://cdn.oppi.li/wq.png"&gt;&lt;img src="https://cdn.oppi.li/wq.png" alt="" /&gt;&lt;/a&gt;&lt;/p&gt; 4150 + &lt;h3 id="editing"&gt;Editing&lt;/h3&gt; 4151 + &lt;p&gt;Vim, with &lt;code&gt;syntax off&lt;/code&gt; because it 4152 + dosen't handle GNU ARM syntax too well.&lt;/p&gt; 4153 + </description> 4154 + <link>https://oppi.li/posts/call_to_ARMs/</link> 4155 + <pubDate>Fri, 07 Feb 2020 00:00:00 +0000</pubDate> 4156 + <guid>https://oppi.li/posts/call_to_ARMs/</guid> 4157 + </item> 4158 + <item> 4159 + <title>color conundrum</title> 4160 + <description>&lt;p&gt;This piece aims to highlight (pun intended) some of the 4161 + reasons behind my &lt;a href="https://cdn.oppi.li/bF.png"&gt;color 4162 + free&lt;/a&gt; editor setup.&lt;/p&gt; 4163 + &lt;p&gt;Imagine highlighting an entire book because &lt;em&gt;all&lt;/em&gt; of it is 4164 + important. That is exactly what (most) syntax highlighting 4165 + does. It is difficult for the human eye to filter out noise 4166 + in rainbow barf. Use color to draw attention, not diverge 4167 + it.&lt;/p&gt; 4168 + &lt;p&gt;At the same time, a book devoid of color is &lt;em&gt;boring!&lt;/em&gt; What 4169 + is the takeaway from this 10 line paragraph? What are the 4170 + technical terms used?&lt;/p&gt; 4171 + &lt;p&gt;Prose and code are certainly different, but the fickle 4172 + minded human eye is the same. The eye constantly looks for a 4173 + frame of reference, a focal point. It grows tired when it 4174 + can't find one.&lt;/p&gt; 4175 + &lt;p&gt;The following comparison does a better job of explaining 4176 + (none, ample and over-the-top highlighting, from left to 4177 + right):&lt;/p&gt; 4178 + &lt;p&gt;&lt;a href="https://cdn.oppi.li/lt.png"&gt;&lt;img src="https://cdn.oppi.li/lt.png" alt="" /&gt;&lt;/a&gt;&lt;/p&gt; 4179 + &lt;p&gt;Without highlighting (far left), it is hard to differentiate 4180 + between comments and code! The florid color scheme (far 4181 + right) is no good either, it contains too many attention 4182 + grabbers. The center sample is a healthy balance of both. 4183 + Function calls and constants stand out, and repetitive 4184 + keywords and other noise (&lt;code&gt;let&lt;/code&gt;, &lt;code&gt;as&lt;/code&gt;) are mildly dimmed 4185 + out. Comments and non-code text (sign column, status text) 4186 + are dimmed further.&lt;/p&gt; 4187 + &lt;p&gt;I'll stop myself before I rant about color contrast and 4188 + combinations.&lt;/p&gt; 4189 + </description> 4190 + <link>https://oppi.li/posts/color_conundrum/</link> 4191 + <pubDate>Mon, 30 Dec 2019 00:00:00 +0000</pubDate> 4192 + <guid>https://oppi.li/posts/color_conundrum/</guid> 4193 + </item> 4194 + <item> 4195 + <title>static sites with bash</title> 4196 + <description>&lt;p&gt;After going through a bunch of static site generators 4197 + (&lt;a href="https://blog.getpelican.com/"&gt;pelican&lt;/a&gt;, 4198 + &lt;a href="https://gohugo.io"&gt;hugo&lt;/a&gt;, 4199 + &lt;a href="https://github.com/icyphox/vite"&gt;vite&lt;/a&gt;), I decided to roll 4200 + my own. If you are more of the 'show me the code' kinda guy, 4201 + &lt;a href="https://github.com/nerdypepper/site"&gt;here&lt;/a&gt; you go.&lt;/p&gt; 4202 + &lt;h3 id="text-formatting"&gt;Text formatting&lt;/h3&gt; 4203 + &lt;p&gt;I chose to write in markdown, and convert 4204 + to html with &lt;a href="https://kristaps.bsd.lv/lowdown/"&gt;lowdown&lt;/a&gt;.&lt;/p&gt; 4205 + &lt;h3 id="directory-structure"&gt;Directory structure&lt;/h3&gt; 4206 + &lt;p&gt;I host my site on GitHub pages, so 4207 + &lt;code&gt;docs/&lt;/code&gt; has to be the entry point. Markdown formatted posts 4208 + go into &lt;code&gt;posts/&lt;/code&gt;, get converted into html, and end up in 4209 + &lt;code&gt;docs/index.html&lt;/code&gt;, something like this:&lt;/p&gt; 4210 + &lt;pre&gt;&lt;code class="language-bash"&gt;posts=$(ls -t ./posts) # chronological order! 4211 + for f in $posts; do 4212 + file=&amp;quot;./posts/&amp;quot;$f # `ls` mangled our file paths 4213 + echo &amp;quot;generating post $file&amp;quot; 4214 + 4215 + html=$(lowdown &amp;quot;$file&amp;quot;) 4216 + echo -e &amp;quot;html&amp;quot; &amp;gt;&amp;gt; docs/index.html 4217 + done 4218 + &lt;/code&gt;&lt;/pre&gt; 4219 + &lt;h3 id="assets"&gt;Assets&lt;/h3&gt; 4220 + &lt;p&gt;Most static site generators recommend dropping image 4221 + assets into the site source itself. That does have it's 4222 + merits, but I prefer hosting images separately:&lt;/p&gt; 4223 + &lt;pre&gt;&lt;code class="language-bash"&gt;# strip file extension 4224 + ext=&amp;quot;${1##*.}&amp;quot; 4225 + 4226 + # generate a random file name 4227 + id=$( cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 2 | head -n 1 ) 4228 + id=&amp;quot;$id.$ext&amp;quot; 4229 + 4230 + # copy to my file host 4231 + scp -P 443 &amp;quot;$1&amp;quot; emerald:files/&amp;quot;$id&amp;quot; 4232 + echo &amp;quot;https://cdn.oppi.li/$id&amp;quot; 4233 + &lt;/code&gt;&lt;/pre&gt; 4234 + &lt;h3 id="templating"&gt;Templating&lt;/h3&gt; 4235 + &lt;p&gt;&lt;a href="https://github.com/NerdyPepper/site/blob/master/generate.sh"&gt;&lt;code&gt;generate.sh&lt;/code&gt;&lt;/a&gt; 4236 + brings the above bits and pieces together (with some extra 4237 + cruft to avoid javascript). It uses &lt;code&gt;sed&lt;/code&gt; to produce nice 4238 + titles from the file names (removes underscores, 4239 + title-case), and &lt;code&gt;date(1)&lt;/code&gt; to add the date to each post 4240 + listing!&lt;/p&gt; 4241 + </description> 4242 + <link>https://oppi.li/posts/static_sites_with_bash/</link> 4243 + <pubDate>Fri, 22 Nov 2019 00:00:00 +0000</pubDate> 4244 + <guid>https://oppi.li/posts/static_sites_with_bash/</guid> 4245 + </item> 4246 + <item> 4247 + <title>my setup</title> 4248 + <description>&lt;p&gt;Decided to do one of these because everyone does one of 4249 + these.&lt;/p&gt; 4250 + &lt;p&gt;&lt;img src="https://cdn.oppi.li/Hb.png" alt="" /&gt;&lt;/p&gt; 4251 + &lt;p&gt;My entire setup is managed with GNU &lt;code&gt;stow&lt;/code&gt;, making it easier 4252 + to replicate on fresh installations. You can find my 4253 + configuration files on &lt;a href="https://github.com/nerdypepper"&gt;GitHub&lt;/a&gt;.&lt;/p&gt; 4254 + &lt;p&gt;I run Void Linux (glibc) on my 4255 + &lt;a href="https://store.hp.com/us/en/mdp/laptops/envy-13"&gt;HP Envy 13&amp;quot; (2018)&lt;/a&gt;. 4256 + To keep things simple, I run a raw X session with &lt;code&gt;2bwm&lt;/code&gt; as my 4257 + window manager, along with &lt;code&gt;dunst&lt;/code&gt; (notification daemon) and 4258 + Sam's &lt;a href="https://github.com/sdhand/compton"&gt;&lt;code&gt;compton&lt;/code&gt;&lt;/a&gt; 4259 + (compositor) fork.&lt;/p&gt; 4260 + &lt;p&gt;I am a fan of GNU tools, so I use &lt;code&gt;bash&lt;/code&gt; as my shell, and 4261 + &lt;code&gt;coreutils&lt;/code&gt; to manage files, archives, strings, paths etc. I 4262 + edit files with &lt;code&gt;vim&lt;/code&gt;, chat with &lt;code&gt;weechat&lt;/code&gt;, listen to music 4263 + with &lt;code&gt;cmus&lt;/code&gt;, monitor processes with &lt;code&gt;htop&lt;/code&gt;, manage sessions 4264 + with &lt;code&gt;tmux&lt;/code&gt;, read pdfs in &lt;code&gt;zathura&lt;/code&gt;. I rarely ever leave 4265 + the comfort of my terminal emulator, &lt;code&gt;urxvt&lt;/code&gt;.&lt;/p&gt; 4266 + &lt;p&gt;Most of my academic typesetting is done with TeX, and 4267 + compiled with &lt;code&gt;xelatex&lt;/code&gt;. Other &lt;em&gt;fun&lt;/em&gt; documents are made with 4268 + GIMP :).&lt;/p&gt; 4269 + </description> 4270 + <link>https://oppi.li/posts/my_setup/</link> 4271 + <pubDate>Wed, 06 Nov 2019 00:00:00 +0000</pubDate> 4272 + <guid>https://oppi.li/posts/my_setup/</guid> 4273 + </item> 4274 + <item> 4275 + <title>WPA woes</title> 4276 + <description>&lt;p&gt;I finally got around to installing Void GNU/Linux on my main 4277 + computer. Rolling release, non-systemd, need I say more?&lt;/p&gt; 4278 + &lt;p&gt;As with all GNU/Linux distributions, wireless networks had 4279 + me in a fix. If you can see this post, it means I've managed 4280 + to get online. It turns out, &lt;code&gt;wpa_supplicant&lt;/code&gt; was detecting the 4281 + wrong interface by default (does it ever select the right 4282 + one?). Let us fix that:&lt;/p&gt; 4283 + &lt;pre&gt;&lt;code&gt;$ sudo rm -r /var/service/wpa_supplicant 4284 + $ sudo killall dhcpcd 4285 + &lt;/code&gt;&lt;/pre&gt; 4286 + &lt;p&gt;What is the right interface though?&lt;/p&gt; 4287 + &lt;pre&gt;&lt;code&gt;$ iw dev 4288 + ... 4289 + Interface wlp2s0 4290 + ... 4291 + &lt;/code&gt;&lt;/pre&gt; 4292 + &lt;p&gt;Aha! Let us run &lt;code&gt;wpa_supplicant&lt;/code&gt; on that interface, as a 4293 + background process:&lt;/p&gt; 4294 + &lt;pre&gt;&lt;code&gt;$ sudo wpa_supplicant -B -i wlp2s0 -c /etc/wpa_supplicant/wpa_supplicant.conf 4295 + $ sudo dhcpcd -B wlp2s0 4296 + $ ping google.com 4297 + PING ... 4298 + &lt;/code&gt;&lt;/pre&gt; 4299 + &lt;p&gt;Yay! Make those changes perpetual by enabling the service:&lt;/p&gt; 4300 + &lt;pre&gt;&lt;code&gt;------------------------------------------------------ 4301 + # Add these to /etc/wpa_supplicant/wpa_supplicant.conf 4302 + OPTS=&amp;quot;-B&amp;quot; 4303 + WPA_INTERFACE=&amp;quot;wlp2s0&amp;quot; 4304 + ------------------------------------------------------ 4305 + $ sudo ln -s /etc/sv/wpa_supplicant /var/service/ 4306 + $ sudo ln -s /etc/sv/dhcpcd /var/service/ 4307 + $ sudo sv restart wpa_supplicant 4308 + $ sudo sv restart dhcpcd 4309 + &lt;/code&gt;&lt;/pre&gt; 4310 + </description> 4311 + <link>https://oppi.li/posts/WPA_woes/</link> 4312 + <pubDate>Sat, 12 Oct 2019 00:00:00 +0000</pubDate> 4313 + <guid>https://oppi.li/posts/WPA_woes/</guid> 4314 + </item> 4315 + <item> 4316 + <title>bye bye BDFs</title> 4317 + <description>&lt;p&gt;Glyph Bitmap Distribution Format is no more, as the creators of 4318 + &lt;a href="https://pango.org"&gt;Pango&lt;/a&gt;, one of the most widely used text rendering 4319 + libraries, 4320 + &lt;a href="https://blogs.gnome.org/mclasen/2019/05/25/pango-future-directions/"&gt;announced&lt;/a&gt; 4321 + their plans for Pango 1.44.&lt;/p&gt; 4322 + &lt;p&gt;Until recently, Pango used FreeType to draw fonts. They will be moving over 4323 + to &lt;a href="https://harfbuzz.org"&gt;Harfbuzz&lt;/a&gt;, an evolution of FreeType.&lt;/p&gt; 4324 + &lt;p&gt;&lt;em&gt;Why?&lt;/em&gt;&lt;/p&gt; 4325 + &lt;p&gt;In short, FreeType was hard to work with. It required complex logic, and 4326 + provided no advantage over Harfbuzz (other than being able to fetch 4327 + opentype metrics with ease).&lt;/p&gt; 4328 + &lt;p&gt;Upgrading to Pango v1.44 will break your GTK applications (if you use a 4329 + &lt;code&gt;bdf&lt;/code&gt;/&lt;code&gt;pcf&lt;/code&gt; bitmap font). Harfbuzz &lt;em&gt;does&lt;/em&gt; support bitmap-only OpenType fonts, 4330 + &lt;code&gt;otb&lt;/code&gt;s. Convert your existing fonts over to &lt;code&gt;otb&lt;/code&gt;s using 4331 + &lt;a href="https://fontforge.github.io"&gt;FontForge&lt;/a&gt;. It is to be noted that applications 4332 + such as &lt;code&gt;xterm&lt;/code&gt; and &lt;code&gt;rxvt&lt;/code&gt; use &lt;code&gt;xft&lt;/code&gt; (X FreeType) to render fonts, and will 4333 + remain unaffected by the update.&lt;/p&gt; 4334 + &lt;p&gt;Both &lt;a href="https://github.com/nerdypepper/scientifica"&gt;scientifica&lt;/a&gt; and 4335 + &lt;a href="https://github.com/nerdypepper/curie"&gt;curie&lt;/a&gt; will soon ship with bitmap-only 4336 + OpenType font formats.&lt;/p&gt; 4337 + </description> 4338 + <link>https://oppi.li/posts/bye_bye_BDFs/</link> 4339 + <pubDate>Wed, 07 Aug 2019 00:00:00 +0000</pubDate> 4340 + <guid>https://oppi.li/posts/bye_bye_BDFs/</guid> 4341 + </item> 4342 + <item> 4343 + <title>onivim sucks</title> 4344 + <description>&lt;p&gt;&lt;a href="https://v2.onivim.io"&gt;Onivim&lt;/a&gt; is a 'modern modal editor', combining fancy 4345 + interface and language features with vim-style modal editing. What's wrong you 4346 + ask?&lt;/p&gt; 4347 + &lt;p&gt;Apart from &lt;a href="https://github.com/onivim/oni2/issues/550"&gt;buggy syntax highlighting&lt;/a&gt;, 4348 + &lt;a href="https://github.com/onivim/oni2/issues/519"&gt;broken scrolling&lt;/a&gt; and 4349 + &lt;a href="https://github.com/onivim/oni2/issues?q=is%3Aissue+label%3A%22daily+editor+blocker%22+is%3Aopen"&gt;others&lt;/a&gt;, 4350 + Onivim is &lt;strong&gt;proprietary&lt;/strong&gt; software. It is licensed under a commercial 4351 + &lt;a href="https://github.com/onivim/oni1/blob/master/Outrun-Labs-EULA-v1.1.md"&gt;end user agreement license&lt;/a&gt;, 4352 + which prohibits redistribution in both object code and source code formats.&lt;/p&gt; 4353 + &lt;p&gt;Onivim's core editor logic (bits that belong to vim), have been separated from 4354 + the interface, into &lt;a href="https://github.com/onivim/libvim"&gt;libvim&lt;/a&gt;. libvim is 4355 + licensed under MIT, which means, this 'extension' of vim is perfectly in 4356 + adherence to &lt;a href="http://vimdoc.sourceforge.net/htmldoc/uganda.html#license"&gt;vim's license text&lt;/a&gt;! 4357 + Outrun Labs are exploiting this loophole (distributing vim as a library) to 4358 + commercialize Onivim.&lt;/p&gt; 4359 + &lt;p&gt;Onivim's source code is available on &lt;a href="https://github.com/onivim/oni2"&gt;GitHub&lt;/a&gt;. 4360 + They do mention that the source code trickles down to the 4361 + &lt;a href="https://github.com/onivim/oni2-mit"&gt;oni2-mit&lt;/a&gt; repository, which (not yet) contains 4362 + MIT-licensed code, &lt;strong&gt;18 months&lt;/strong&gt; after each commit to the original repository.&lt;/p&gt; 4363 + &lt;p&gt;Want to contribute to Onivim? Don't. They make a profit out of your contributions. 4364 + Currently, Onivim is priced at $19.99, 'pre-alpha' pricing which is 80% off the 4365 + final price! If you are on the lookout for an editor, I would suggest using 4366 + &lt;a href="https://vim.org"&gt;Vim&lt;/a&gt;, charity ware that actually works, and costs $100 lesser.&lt;/p&gt; 4367 + </description> 4368 + <link>https://oppi.li/posts/onivim_sucks/</link> 4369 + <pubDate>Fri, 02 Aug 2019 00:00:00 +0000</pubDate> 4370 + <guid>https://oppi.li/posts/onivim_sucks/</guid> 4371 + </item> 4372 + <item> 4373 + <title>bash harder with vim</title> 4374 + <description>&lt;p&gt;Bash is tricky, don't let your editor get in your way. Here's a couple of neat 4375 + additions you could make to your &lt;code&gt;vimrc&lt;/code&gt; for a better shell programming 4376 + experience.&lt;/p&gt; 4377 + &lt;h3 id="man-pages-inside-vim"&gt;Man pages inside vim&lt;/h3&gt; 4378 + &lt;p&gt;Source this script to get started:&lt;/p&gt; 4379 + &lt;pre&gt;&lt;code&gt;runtime ftplugin/man.vim 4380 + &lt;/code&gt;&lt;/pre&gt; 4381 + &lt;p&gt;Now, you can open manpages inside vim with &lt;code&gt;:Man&lt;/code&gt;! It adds nicer syntax highlighting 4382 + and the ability to jump around with &lt;code&gt;Ctrl-]&lt;/code&gt; and &lt;code&gt;Ctrl-T&lt;/code&gt;.&lt;/p&gt; 4383 + &lt;p&gt;By default, the manpage is opened in a horizontal split, I prefer using a new tab:&lt;/p&gt; 4384 + &lt;pre&gt;&lt;code&gt;let g:ft_man_open_mode = 'tab' 4385 + &lt;/code&gt;&lt;/pre&gt; 4386 + &lt;h3 id="scratchpad-to-test-your-commands"&gt;Scratchpad to test your commands&lt;/h3&gt; 4387 + &lt;p&gt;I often test my &lt;code&gt;sed&lt;/code&gt; substitutions, here is 4388 + a sample from the script used to generate this site:&lt;/p&gt; 4389 + &lt;pre&gt;&lt;code&gt;# a substitution to convert snake_case to Title Case With Spaces 4390 + echo &amp;quot;$1&amp;quot; | sed -E -e &amp;quot;s/\..+$//g&amp;quot; -e &amp;quot;s/_(.)/ \u\1/g&amp;quot; -e &amp;quot;s/^(.)/\u\1/g&amp;quot; 4391 + &lt;/code&gt;&lt;/pre&gt; 4392 + &lt;p&gt;Instead of dropping into a new shell, just test it out directly from vim!&lt;/p&gt; 4393 + &lt;ul&gt; 4394 + &lt;li&gt;Yank the line into a register: 4395 + &lt;/li&gt; 4396 + &lt;/ul&gt; 4397 + &lt;pre&gt;&lt;code&gt;yy 4398 + &lt;/code&gt;&lt;/pre&gt; 4399 + &lt;ul&gt; 4400 + &lt;li&gt;Paste it into the command-line window: 4401 + &lt;/li&gt; 4402 + &lt;/ul&gt; 4403 + &lt;pre&gt;&lt;code&gt;q:p 4404 + &lt;/code&gt;&lt;/pre&gt; 4405 + &lt;ul&gt; 4406 + &lt;li&gt;Make edits as required: 4407 + &lt;/li&gt; 4408 + &lt;/ul&gt; 4409 + &lt;pre&gt;&lt;code&gt;syntax off # previously run commands 4410 + edit index.html # in a buffer! 4411 + w | so % 4412 + !echo &amp;quot;new_post.md&amp;quot; | sed -E -e &amp;quot;s/\..+$//g&amp;quot; --snip-- 4413 + ^--- note the use of '!' 4414 + &lt;/code&gt;&lt;/pre&gt; 4415 + &lt;ul&gt; 4416 + &lt;li&gt;Hit enter with the cursor on the line containing your command! 4417 + &lt;/li&gt; 4418 + &lt;/ul&gt; 4419 + &lt;pre&gt;&lt;code&gt;$ vim 4420 + New Post # output 4421 + Press ENTER or type command to continue 4422 + &lt;/code&gt;&lt;/pre&gt; 4423 + </description> 4424 + <link>https://oppi.li/posts/bash_harder_with_vim/</link> 4425 + <pubDate>Tue, 30 Jul 2019 00:00:00 +0000</pubDate> 4426 + <guid>https://oppi.li/posts/bash_harder_with_vim/</guid> 4427 + </item> 4428 + <item> 4429 + <title>hold position!</title> 4430 + <description>&lt;p&gt;Often times, when I run a vim command that makes &amp;quot;big&amp;quot; changes to a file (a 4431 + macro or a &lt;code&gt;:vimgrep&lt;/code&gt; command) I lose my original position and feel disoriented.&lt;/p&gt; 4432 + &lt;p&gt;&lt;em&gt;Save position with &lt;code&gt;winsaveview()&lt;/code&gt;!&lt;/em&gt;&lt;/p&gt; 4433 + &lt;p&gt;The &lt;code&gt;winsaveview()&lt;/code&gt; command returns a &lt;code&gt;Dictionary&lt;/code&gt; that contains information 4434 + about the view of the current window. This includes the cursor line number, 4435 + cursor coloumn, the top most line in the window and a couple of other values, 4436 + none of which concern us.&lt;/p&gt; 4437 + &lt;p&gt;Before running our command (one that jumps around the buffer, a lot), we save 4438 + our view, and restore it once its done, with &lt;code&gt;winrestview&lt;/code&gt;.&lt;/p&gt; 4439 + &lt;pre&gt;&lt;code&gt;let view = winsaveview() 4440 + s/\s\+$//gc &amp;quot; find and (confirm) replace trailing blanks 4441 + winrestview(view) &amp;quot; restore our original view! 4442 + &lt;/code&gt;&lt;/pre&gt; 4443 + &lt;p&gt;It might seem a little overkill in the above example, just use `` (double 4444 + backticks) instead, but it comes in handy when you run your file through 4445 + heavier filtering.&lt;/p&gt; 4446 + </description> 4447 + <link>https://oppi.li/posts/hold_position!/</link> 4448 + <pubDate>Tue, 30 Jul 2019 00:00:00 +0000</pubDate> 4449 + <guid>https://oppi.li/posts/hold_position!/</guid> 4450 + </item> 4451 + <item> 4452 + <title>get better at yanking and putting in vim</title> 4453 + <description>&lt;p&gt;a couple of nifty tricks to help you copy-paste better:&lt;/p&gt; 4454 + &lt;ol&gt; 4455 + &lt;li&gt; 4456 + &lt;p&gt;reselecting previously selected text (i use this to fix botched selections):&lt;/p&gt; 4457 + &lt;pre&gt;&lt;code&gt;gv &amp;quot; :h gv for more 4458 + &amp;quot; you can use `o` in visual mode to go to the `Other` end of the selection 4459 + &amp;quot; use a motion to fix the selection 4460 + &lt;/code&gt;&lt;/pre&gt; 4461 + &lt;/li&gt; 4462 + &lt;li&gt; 4463 + &lt;p&gt;reselecting previously yanked text:&lt;/p&gt; 4464 + &lt;pre&gt;&lt;code&gt;`[v`] 4465 + `[ &amp;quot; marks the beginning of the previously yanked text :h `[ 4466 + `] &amp;quot; marks the end :h `] 4467 + v &amp;quot; visual select everything in between 4468 + 4469 + nnoremap gb `[v`] &amp;quot; &amp;quot;a quick map to perform the above 4470 + &lt;/code&gt;&lt;/pre&gt; 4471 + &lt;/li&gt; 4472 + &lt;li&gt; 4473 + &lt;p&gt;pasting and indenting text (in one go):&lt;/p&gt; 4474 + &lt;pre&gt;&lt;code&gt;]p &amp;quot; put (p) and adjust indent to current line 4475 + ]P &amp;quot; put the text before the cursor (P) and adjust indent to current line 4476 + &lt;/code&gt;&lt;/pre&gt; 4477 + &lt;/li&gt; 4478 + &lt;/ol&gt; 4479 + </description> 4480 + <link>https://oppi.li/posts/get_better_at_yanking_and_putting_in_vim/</link> 4481 + <pubDate>Tue, 30 Jul 2019 00:00:00 +0000</pubDate> 4482 + <guid>https://oppi.li/posts/get_better_at_yanking_and_putting_in_vim/</guid> 4483 + </item> 4484 + </channel> 4485 + </rss>
+1 -1
_site/style.css
··· 1 - html{font-family:serif;line-height:1.2}h1,h2,h3,h4{margin-top:0;line-height:1.2;font-weight:bold}h1{font-size:1.8rem;margin-top:3rem}h2{font-size:1.5rem;margin-top:2.5rem}h3{font-size:1.25rem;margin-top:2rem}h4{font-size:1.1rem;margin-top:1.5rem}p{margin-top:1rem}ul{margin-top:1rem;padding-left:1.5rem;list-style-type:disc}ol{margin-top:1rem;padding-left:1.5rem;list-style-type:decimal}li{margin-top:.25rem}blockquote{margin-top:1.5rem;margin-bottom:1.5rem;padding-left:1rem}pre{margin-top:1.5rem;margin-bottom:1.5rem;padding:.5rem;border:1px solid;overflow-x:auto}img{border:1px solid}a{color:revert;text-decoration-line:underline}hr{margin-top:2rem;margin-bottom:2rem}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mt-4{margin-top:calc(var(--spacing)*4)}.flex{display:flex}.max-w-xl{max-width:var(--container-xl)}.grow{flex-grow:1}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-right{text-align:right}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}html{font-family:serif;line-height:1.2}h1,h2,h3,h4{margin-top:0;line-height:1.2;font-weight:bold}h1{font-size:1.8rem;margin-top:3rem}h2{font-size:1.5rem;margin-top:2.5rem}h3{font-size:1.25rem;margin-top:2rem}h4{font-size:1.1rem;margin-top:1.5rem}p{margin-top:1rem}ul{margin-top:1rem;padding-left:1.5rem;list-style-type:disc}ol{margin-top:1rem;padding-left:1.5rem;list-style-type:decimal}li{margin-top:.25rem}blockquote{margin-top:1.5rem;margin-bottom:1.5rem;padding-left:1rem}pre{margin-top:1.5rem;margin-bottom:1.5rem;padding:.5rem;border:1px solid;overflow-x:auto}img{border:1px solid}a{color:revert;text-decoration-line:underline}hr{margin-top:2rem;margin-bottom:2rem}@media (prefers-color-scheme:dark){html{background-color:#000000;color:#d0d0d0}a{color:#7eb3f8}a:visited{color:#c084fc}html{background-color:#000000;color:#d0d0d0}a{color:#7eb3f8}a:visited{color:#c084fc}}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mt-2{margin-top:calc(var(--spacing)*2)}.mt-4{margin-top:calc(var(--spacing)*4)}.grid{display:grid}.w-fit{width:fit-content}.max-w-xl{max-width:var(--container-xl)}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-right{text-align:right}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-y-reverse:0;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mt-4{margin-top:calc(var(--spacing)*4)}.mt-10{margin-top:calc(var(--spacing)*10)}.mt-12{margin-top:calc(var(--spacing)*12)}.flex{display:flex}.w-48{width:calc(var(--spacing)*48)}.max-w-xl{max-width:var(--container-xl)}.aspect-1\/1{aspect-ratio:1}.grow{flex-grow:1}.flex-col{flex-direction:column}.flex-row{flex-direction:row}.items-center{align-items:center}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.gap-12{gap:calc(var(--spacing)*12)}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*4)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*4)*calc(1 - var(--tw-space-y-reverse)))}.p-2{padding:calc(var(--spacing)*2)}.px-2{padding-inline:calc(var(--spacing)*2)}.text-justify{text-align:justify}.text-right{text-align:right}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}@media (min-width:48rem){.md\:flex-row{flex-direction:row}}}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false} 1 + html{font-family:serif;line-height:1.2}h1,h2,h3,h4{margin-top:0;line-height:1.2;font-weight:bold}h1{font-size:1.8rem;margin-top:3rem}h2{font-size:1.5rem;margin-top:2.5rem}h3{font-size:1.25rem;margin-top:2rem}h4{font-size:1.1rem;margin-top:1.5rem}p{margin-top:1rem}ul{margin-top:1rem;padding-left:1.5rem;list-style-type:disc}ol{margin-top:1rem;padding-left:1.5rem;list-style-type:decimal}li{margin-top:.25rem}blockquote{margin-top:1.5rem;margin-bottom:1.5rem;padding-left:1rem}pre{margin-top:1.5rem;margin-bottom:1.5rem;padding:.5rem;border:1px solid;overflow-x:auto}img{border:1px solid}a{color:revert;text-decoration-line:underline}hr{margin-top:2rem;margin-bottom:2rem}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mt-4{margin-top:calc(var(--spacing)*4)}.flex{display:flex}.max-w-xl{max-width:var(--container-xl)}.grow{flex-grow:1}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-right{text-align:right}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}html{font-family:serif;line-height:1.2}h1,h2,h3,h4{margin-top:0;line-height:1.2;font-weight:bold}h1{font-size:1.8rem;margin-top:3rem}h2{font-size:1.5rem;margin-top:2.5rem}h3{font-size:1.25rem;margin-top:2rem}h4{font-size:1.1rem;margin-top:1.5rem}p{margin-top:1rem}ul{margin-top:1rem;padding-left:1.5rem;list-style-type:disc}ol{margin-top:1rem;padding-left:1.5rem;list-style-type:decimal}li{margin-top:.25rem}blockquote{margin-top:1.5rem;margin-bottom:1.5rem;padding-left:1rem}pre{margin-top:1.5rem;margin-bottom:1.5rem;padding:.5rem;border:1px solid;overflow-x:auto}img{border:1px solid}a{color:revert;text-decoration-line:underline}hr{margin-top:2rem;margin-bottom:2rem}@media (prefers-color-scheme:dark){html{background-color:#000000;color:#d0d0d0}a{color:#7eb3f8}a:visited{color:#c084fc}html{background-color:#000000;color:#d0d0d0}a{color:#7eb3f8}a:visited{color:#c084fc}}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mt-2{margin-top:calc(var(--spacing)*2)}.mt-4{margin-top:calc(var(--spacing)*4)}.grid{display:grid}.w-fit{width:fit-content}.max-w-xl{max-width:var(--container-xl)}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-right{text-align:right}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-y-reverse:0;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mt-4{margin-top:calc(var(--spacing)*4)}.mt-10{margin-top:calc(var(--spacing)*10)}.mt-12{margin-top:calc(var(--spacing)*12)}.flex{display:flex}.w-48{width:calc(var(--spacing)*48)}.max-w-xl{max-width:var(--container-xl)}.aspect-1\/1{aspect-ratio:1}.grow{flex-grow:1}.flex-col{flex-direction:column}.flex-row{flex-direction:row}.items-center{align-items:center}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.gap-12{gap:calc(var(--spacing)*12)}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*4)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*4)*calc(1 - var(--tw-space-y-reverse)))}.p-2{padding:calc(var(--spacing)*2)}.px-2{padding-inline:calc(var(--spacing)*2)}.text-justify{text-align:justify}.text-right{text-align:right}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}@media (min-width:48rem){.md\:flex-row{flex-direction:row}}}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}
+1 -1
_site/weeklies/index.html
··· 1 1 <!DOCTYPE html> 2 - <html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><meta name="description" content="oppiliappan&#x27;s μsings" /><meta name="author" content="Akshay Oppiliappan" /><meta name="color-scheme" content="light dark" /><meta name="twitter:card" content="summary" /><title>weeklies</title><link rel="stylesheet" href="/style.css?v=1598ce9b" /><style>html { font-size-adjust: ex-height 0.53; }</style><meta property="og:type" content="website" /><meta property="og:site_name" content="oppi.li" /><meta property="og:title" content="weeklies" /><meta property="og:description" content="oppiliappan&#x27;s μsings" /></head><body><div><nav class="max-w-xl mx-auto"><a href="/">home</a><span> :: <a href="#">weeklies</a></span></nav><main class="px-2"><div class="max-w-xl mx-auto"><h1 class="text-center">weeklies</h1><p class="text-center">1 of 1320 weeks documented</p><div class="grid grid-cols-3 mt-4"><div><h2>2026</h2><div class="grid grid-cols-4 mt-2 tabular-nums w-fit" style="column-gap: 1ch"><span class="text-right">1</span><span class="text-right">2</span><span class="text-right">3</span><span class="text-right">4</span><span class="text-right">5</span><span class="text-right">6</span><span class="text-right">7</span><span class="text-right">8</span><span class="text-right">9</span><span class="text-right">10</span><span class="text-right">11</span><span class="text-right">12</span><span class="text-right">13</span><span class="text-right">14</span><span class="text-right">15</span><a class="text-right" href="2026-16">16</a><span class="text-right">17</span><span class="text-right">18</span><span class="text-right">19</span><span class="text-right">20</span><span class="text-right">21</span><span class="text-right">22</span><span class="text-right">23</span><span class="text-right">24</span><span class="text-right">25</span><span class="text-right">26</span><span class="text-right">27</span><span class="text-right">28</span><span class="text-right">29</span><span class="text-right">30</span><span class="text-right">31</span><span class="text-right">32</span><span class="text-right">33</span><span class="text-right">34</span><span class="text-right">35</span><span class="text-right">36</span><span class="text-right">37</span><span class="text-right">38</span><span class="text-right">39</span><span class="text-right">40</span><span class="text-right">41</span><span class="text-right">42</span><span class="text-right">43</span><span class="text-right">44</span><span class="text-right">45</span><span class="text-right">46</span><span class="text-right">47</span><span class="text-right">48</span><span class="text-right">49</span><span class="text-right">50</span><span class="text-right">51</span><span class="text-right">52</span></div></div></div></div></main></div></body></html> 2 + <html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><meta name="description" content="oppiliappan&#x27;s μsings" /><meta name="author" content="Akshay Oppiliappan" /><meta name="color-scheme" content="light dark" /><meta name="twitter:card" content="summary" /><title>weeklies</title><link rel="stylesheet" href="/style.css?v=7cfa1d5c" /><style>html { font-size-adjust: ex-height 0.53; }</style><meta property="og:type" content="website" /><meta property="og:site_name" content="oppi.li" /><meta property="og:title" content="weeklies" /><meta property="og:description" content="oppiliappan&#x27;s μsings" /></head><body><div><nav class="max-w-xl mx-auto"><a href="/">home</a><span> :: <a href="#">weeklies</a></span></nav><main class="px-2"><div class="max-w-xl mx-auto"><h1 class="text-center">weeklies</h1><div class="text-center space-x-2"><span>1/1321 weeks documented</span><span>·</span><a href="/posts/index.rss">rss</a></div><div class="grid grid-cols-3 mt-4"><div><h2>2026</h2><div class="grid grid-cols-4 mt-2 tabular-nums w-fit" style="column-gap: 1ch"><span class="text-right">1</span><span class="text-right">2</span><span class="text-right">3</span><span class="text-right">4</span><span class="text-right">5</span><span class="text-right">6</span><span class="text-right">7</span><span class="text-right">8</span><span class="text-right">9</span><span class="text-right">10</span><span class="text-right">11</span><span class="text-right">12</span><span class="text-right">13</span><span class="text-right">14</span><span class="text-right">15</span><a class="text-right" href="2026-16">16</a><span class="text-right">17</span><span class="text-right">18</span><span class="text-right">19</span><span class="text-right">20</span><span class="text-right">21</span><span class="text-right">22</span><span class="text-right">23</span><span class="text-right">24</span><span class="text-right">25</span><span class="text-right">26</span><span class="text-right">27</span><span class="text-right">28</span><span class="text-right">29</span><span class="text-right">30</span><span class="text-right">31</span><span class="text-right">32</span><span class="text-right">33</span><span class="text-right">34</span><span class="text-right">35</span><span class="text-right">36</span><span class="text-right">37</span><span class="text-right">38</span><span class="text-right">39</span><span class="text-right">40</span><span class="text-right">41</span><span class="text-right">42</span><span class="text-right">43</span><span class="text-right">44</span><span class="text-right">45</span><span class="text-right">46</span><span class="text-right">47</span><span class="text-right">48</span><span class="text-right">49</span><span class="text-right">50</span><span class="text-right">51</span><span class="text-right">52</span></div></div></div></div></main></div></body></html>
+87
_site/weeklies/index.rss
··· 1 + <?xml version="1.0" encoding="UTF-8"?> 2 + <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"> 3 + <channel> 4 + <title>oppiliappan's μsings</title> 5 + <link>https://oppi.li</link> 6 + <description>programming, design, software</description> 7 + <atom:link href="https://oppi.li/weeklies/index.rss" rel="self" type="application/rss+xml" /> 8 + <language>en-us</language> 9 + <copyright>Creative Commons BY-NC-SA 4.0</copyright> 10 + <item> 11 + <title>2026-16</title> 12 + <description>&lt;h2 id="fp-launchpad-at-iit-madras"&gt;FP Launchpad at IIT Madras&lt;/h2&gt; 13 + &lt;p&gt;I had the pleasure of attending the inaugration of &lt;a href="https://fplaunchpad.org/"&gt;FP 14 + Launchpad&lt;/a&gt;, courtesy of &lt;a href="https://kcsrk.info/"&gt;KC 15 + Sivaramakrishnan&lt;/a&gt; and &lt;a href="https://anil.recoil.org"&gt;Anil 16 + Madhavapeddy&lt;/a&gt;. It was a packed 17 + schedule with back-to-back presentations. My personal 18 + favourites:&lt;/p&gt; 19 + &lt;ul&gt; 20 + &lt;li&gt;&lt;a href="https://fplaunchpad.org/2026/03/30/fp-launchpad-kickoff.html#raghavan"&gt;Towards verifiable governance with LLMs and 21 + Lean&lt;/a&gt; 22 + &lt;/li&gt; 23 + &lt;li&gt;&lt;a href="https://fplaunchpad.org/2026/03/30/fp-launchpad-kickoff.html#krishnamurthi"&gt;A Programming Language for Lightweight 24 + Diagramming&lt;/a&gt; 25 + &lt;/li&gt; 26 + &lt;/ul&gt; 27 + &lt;p&gt;It was truly rousing to see FP grab hold in Indian 28 + universities. I briefly attempted to run a series of &lt;a href="https://github.com/DSCRV/lisk"&gt;Scheme 29 + &amp;amp; Haskell&lt;/a&gt; tutorials for my 30 + juniors at university several years ago!&lt;/p&gt; 31 + &lt;h2 id="renewed-interest-in-ocaml"&gt;Renewed interest in OCaml&lt;/h2&gt; 32 + &lt;p&gt;After attending FP Launchpad, my interest in OCaml was at an 33 + all time high. So I've rewritten &lt;a href="https://tangled.org/oppi.li/site/blob/dbc61fb36b60a574dd8e99af7caf8abfb2be2638/generate.sh"&gt;the ugly bash 34 + program&lt;/a&gt; 35 + that was used to generate this website &lt;a href="https://tangled.org/oppi.li/site"&gt;in 36 + OCaml&lt;/a&gt;, using the 37 + wonderful &lt;a href="https://github.com/samoht/tw"&gt;tw&lt;/a&gt; library by 38 + &lt;a href="https://gazagnaire.org/"&gt;Thomas Gazagnaire&lt;/a&gt;. I've now added 39 + a section for &lt;a href="/weeklies"&gt;weeklies&lt;/a&gt;, to get myself to write 40 + more often.&lt;/p&gt; 41 + &lt;p&gt;I've also been poking at 42 + &lt;a href="https://tangled.org/patrick.sirref.org/bruit"&gt;bruit&lt;/a&gt; to 43 + &lt;a href="https://tangled.org/oppi.li/bruit/commit/87bdc3254ee028007805e6b7135fa0da83287fb9"&gt;have it work on the 44 + web&lt;/a&gt;, 45 + so I can eventually show-off my OCaml implementation of 46 + &lt;a href="https://wryl.tech/projects/modal.html"&gt;Modal&lt;/a&gt; on a &lt;a href="https://docs.tangled.org/hosting-websites-on-tangled.html#hosting-websites-on-tangled"&gt;Tangled 47 + site&lt;/a&gt;.&lt;/p&gt; 48 + &lt;p&gt;I've started familiarizing myself with OCaml's 49 + &lt;a href="https://github.com/ocaml-ppx/ppxlib"&gt;metaprogramming 50 + ecosystem&lt;/a&gt; to emit 51 + &lt;a href="https://github.com/sidprasad/spytial"&gt;spytial diagrams&lt;/a&gt; 52 + from OCaml data structures. It is ever so slightly different 53 + from 54 + &lt;a href="https://docs.rs/proc-macro2/latest/proc_macro2/"&gt;proc_macro2&lt;/a&gt; 55 + in that it operates on a full 56 + &lt;a href="https://ocaml.org/manual/5.4/api/compilerlibref/Parsetree.html"&gt;Parsetree&lt;/a&gt; 57 + and not a plain 58 + &lt;a href="https://docs.rs/proc-macro2/latest/proc_macro2/struct.TokenStream.html"&gt;TokenStream&lt;/a&gt;.&lt;/p&gt; 59 + &lt;h2 id="building-a-web-of-trust"&gt;Building a web-of-trust&lt;/h2&gt; 60 + &lt;p&gt;I've been taking a few (cautious) steps towards implementing 61 + &lt;a href="https://github.com/mitchellh/vouch/"&gt;vouching&lt;/a&gt; on Tangled. 62 + More on that next week!&lt;/p&gt; 63 + &lt;h2 id="improving-my-music-setup"&gt;Improving my music setup&lt;/h2&gt; 64 + &lt;p&gt;&lt;a href="https://tangled.org/devins.page/tinysub"&gt;tinysub&lt;/a&gt;, a 65 + subsonic-compatible web player popped up on my Tangled feed, 66 + and I instantly took to setting it up on my homeserver. 67 + Using &lt;a href="https://github.com/Y2Z/monolith"&gt;monolith&lt;/a&gt;, I was 68 + able to build it down to a single 100kB HTML file, assets 69 + included.&lt;/p&gt; 70 + &lt;p&gt;I then rigged up my navidrome server to scrobble listens 71 + directly to my 72 + &lt;a href="https://atproto.com/guides/self-hosting#pds"&gt;PDS&lt;/a&gt; using the 73 + &lt;a href="https://tangled.org/oppi.li/rocksky/commit/47babb790cd34d70c7a3638bf8d145013cfef7fd#flake.nix-N42"&gt;rocksky 74 + CLI&lt;/a&gt;. 75 + My scrobbles are now aggregated by 76 + &lt;a href="https://rocksky.app"&gt;rocksky.app&lt;/a&gt; and 77 + &lt;a href="https://teal.fm"&gt;teal.fm&lt;/a&gt;.&lt;/p&gt; 78 + &lt;h2 id="travel"&gt;Travel&lt;/h2&gt; 79 + &lt;p&gt;I am now back in London! I've spent the weekend trying to 80 + revive my houseplants... with little luck.&lt;/p&gt; 81 + </description> 82 + <link>https://oppi.li/weeklies/2026-16/</link> 83 + <pubDate>Sun, 19 Apr 2026 00:00:00 +0000</pubDate> 84 + <guid>https://oppi.li/weeklies/2026-16/</guid> 85 + </item> 86 + </channel> 87 + </rss>
+14 -2
bin/home.ml
··· 92 92 ~tw:Tw.[ flex; items_center; justify_between ] 93 93 [ 94 94 h2 [ txt "posts" ]; 95 - a ~tw:Tw.[ mt 10 ] ~at:At.[ href "/posts" ] [ txt "view all" ]; 95 + div 96 + ~at:At.[ style "gap: 1ch" ] 97 + ~tw:Tw.[ flex; items_center; mt 10 ] 98 + [ 99 + a ~at:At.[ href "/posts" ] [ txt "view all" ]; 100 + a ~at:At.[ href "/posts/index.rss" ] [ txt "rss" ]; 101 + ]; 96 102 ]; 97 103 posts; 98 104 ]) ··· 105 111 ~tw:Tw.[ flex; items_center; justify_between ] 106 112 [ 107 113 h2 [ txt "weeklies" ]; 108 - a ~tw:Tw.[ mt 10 ] ~at:At.[ href "/weeklies" ] [ txt "view all" ]; 114 + div 115 + ~at:At.[ style "gap: 1ch" ] 116 + ~tw:Tw.[ flex; items_center; mt 10 ] 117 + [ 118 + a ~at:At.[ href "/weeklies" ] [ txt "view all" ]; 119 + a ~at:At.[ href "/weeklies/index.rss" ] [ txt "rss" ]; 120 + ]; 109 121 ]; 110 122 posts; 111 123 ])
+2
bin/main.ml
··· 11 11 ~weeklies_preview:(Weeklies.Cat.preview ~limit:1 weeklies) 12 12 in 13 13 Posts.Cat.write ~out_dir:"_site" posts; 14 + Posts.Cat.write_rss ~out_dir:"_site" posts; 14 15 Weeklies.Cat.write ~out_dir:"_site" weeklies; 16 + Weeklies.Cat.write_rss ~out_dir:"_site" weeklies; 15 17 Home.write ~out_dir:"_site" home; 16 18 let css = 17 19 Tw.Css.concat
+2
bin/posts.ml
··· 48 48 span [ txt (Printf.sprintf "%d entries" (List.length posts)) ]; 49 49 span [ txt "\u{00B7}" ]; 50 50 span [ txt (Printf.sprintf "%d years" years) ]; 51 + span [ txt "\u{00B7}" ]; 52 + a ~at:At.[ href "/posts/index.rss" ] [ txt "rss" ]; 51 53 ]; 52 54 div ~tw:Tw.[ mt 4 ] (view_rows sorted); 53 55 ])
+8 -3
bin/weeklies.ml
··· 59 59 ~tw:Tw.[ max_w_xl; mx_auto ] 60 60 [ 61 61 h1 ~tw:Tw.[ text_center ] [ txt "weeklies" ]; 62 - p 63 - ~tw:Tw.[ text_center ] 62 + div 63 + ~tw:Tw.[ text_center; space_x 2. ] 64 64 [ 65 - txt (Printf.sprintf "%d of %d weeks documented" count weeks_span); 65 + span 66 + [ 67 + txt (Printf.sprintf "%d/%d weeks documented" count weeks_span); 68 + ]; 69 + span [ txt "\u{00B7}" ]; 70 + a ~at:At.[ href "/posts/index.rss" ] [ txt "rss" ]; 66 71 ]; 67 72 div ~tw:Tw.[ grid; grid_cols 3; mt 4 ] (List.concat_map by_year years); 68 73 ])
+43
lib/category.ml
··· 18 18 val render : t -> Tw_html.t 19 19 val preview : t -> limit:int -> Tw_html.t 20 20 val css : t -> Tw.Css.t 21 + val rss : t -> string 21 22 val write : out_dir:string -> t -> unit 23 + val write_rss : out_dir:string -> t -> unit 22 24 end 23 25 24 26 let rec mkdir_p dir = ··· 93 95 Tw.Css.concat 94 96 (Typography.stylesheet 95 97 :: List.map (fun (_, page) -> snd (Tw_html.css page)) t.pages) 98 + 99 + let compare_date a b = 100 + match (P.date a, P.date b) with 101 + | Some da, Some db -> CalendarLib.Date.compare db da 102 + | Some _, None -> -1 103 + | None, Some _ -> 1 104 + | None, None -> 0 105 + 106 + let rss t = 107 + let cat = N.name in 108 + let feed_url = Layout.base_url ^ "/" ^ cat ^ "/index.rss" in 109 + let items = 110 + List.sort compare_date t.posts 111 + |> List.map (fun p -> 112 + let link = Layout.base_url ^ "/" ^ cat ^ "/" ^ P.slug p ^ "/" in 113 + P.rss_item ~link p) 114 + in 115 + Printf.sprintf 116 + {|<?xml version="1.0" encoding="UTF-8"?> 117 + <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"> 118 + <channel> 119 + <title>%s</title> 120 + <link>%s</link> 121 + <description>%s</description> 122 + <atom:link href="%s" rel="self" type="application/rss+xml" /> 123 + <language>en-us</language> 124 + <copyright>%s</copyright> 125 + %s 126 + </channel> 127 + </rss>|} 128 + Layout.rss_title Layout.base_url Layout.rss_description feed_url 129 + Layout.rss_copyright 130 + (String.concat "\n " items) 131 + 132 + let write_rss ~out_dir t = 133 + let cat = N.name in 134 + let dir = Filename.concat out_dir cat in 135 + mkdir_p dir; 136 + let file = Filename.concat dir "index.rss" in 137 + Out_channel.with_open_text file (fun oc -> 138 + Out_channel.output_string oc (rss t)) 96 139 97 140 let write ~out_dir t = 98 141 List.iter (fun (path, page) -> write_page ~out_dir path page) t.pages
+31
lib/content.ml
··· 10 10 val date : t -> CalendarLib.Date.t option 11 11 val toc : t -> toc_item list 12 12 val html : t -> Tw_html.t 13 + val rss_item : link:string -> t -> string 13 14 end 14 15 15 16 module MarkdownPage : Page = struct ··· 155 156 ~default:(t.path |> Filename.basename |> Filename.remove_extension) 156 157 157 158 let html t = t.html 159 + 160 + let xml_escape s = 161 + let b = Buffer.create (String.length s) in 162 + String.iter 163 + (function 164 + | '&' -> Buffer.add_string b "&amp;" 165 + | '<' -> Buffer.add_string b "&lt;" 166 + | '>' -> Buffer.add_string b "&gt;" 167 + | c -> Buffer.add_char b c) 168 + s; 169 + Buffer.contents b 170 + 171 + let rss_item ~link t = 172 + let title = xml_escape (title t) in 173 + let description = xml_escape (Omd.to_html t.doc) in 174 + let pub_date = 175 + match date t with 176 + | None -> "" 177 + | Some d -> 178 + CalendarLib.Printer.Date.sprint "%a, %d %b %Y 00:00:00 +0000" d 179 + in 180 + Printf.sprintf 181 + {|<item> 182 + <title>%s</title> 183 + <description>%s</description> 184 + <link>%s</link> 185 + <pubDate>%s</pubDate> 186 + <guid>%s</guid> 187 + </item>|} 188 + title description link pub_date link 158 189 end
+4
lib/layout.ml
··· 1 1 let site_description = "oppiliappan's μsings" 2 + let base_url = "https://oppi.li" 3 + let rss_title = site_description 4 + let rss_description = "programming, design, software" 5 + let rss_copyright = "Creative Commons BY-NC-SA 4.0" 2 6 3 7 let extra_head = 4 8 Tw_html.rawf [ {|<style>html { font-size-adjust: ex-height 0.53; }</style>|} ]