···11+<?xml version="1.0" encoding="UTF-8"?>
22+<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
33+ <channel>
44+ <title>oppiliappan's μsings</title>
55+ <link>https://oppi.li</link>
66+ <description>programming, design, software</description>
77+ <atom:link href="https://oppi.li/posts/index.rss" rel="self" type="application/rss+xml" />
88+ <language>en-us</language>
99+ <copyright>Creative Commons BY-NC-SA 4.0</copyright>
1010+ <item>
1111+ <title>mounting the atmosphere</title>
1212+ <description><p><a href="https://tangled.sh/@oppi.li/pdsfs">pdsfs</a> is a tool that
1313+mounts <a href="https://atproto.com">atproto</a> PDS repositories as a
1414+<a href="https://en.wikipedia.org/wiki/Filesystem_in_Userspace">FUSE</a>
1515+filesystem.</p>
1616+<p>A <a href="https://en.wikipedia.org/wiki/Filesystem_in_Userspace">PDS
1717+repository</a>
1818+contains all data published by a user to the atmosphere. It
1919+is exportable as a CAR (content-addressable archive) file.
2020+pdsfs is a tool that mounts this CAR file as a readonly-FUSE
2121+filesystem, allowing quick and easy exploration.</p>
2222+<p>To motivate the need for such a program, we could begin by
2323+mounting a repository:</p>
2424+<pre><code>λ pdsfs oppi.li
2525+mounted at &quot;mnt&quot;
2626+hit enter to unmount and exit...
2727+</code></pre>
2828+<p>oppi.li is my handle in the atmosphere. The tool does some
2929+hardwork to determine the location of my PDS repository
3030+given my handle, but that is not important. Let's have a
3131+look around:</p>
3232+<pre><code>λ ls mnt/
3333+did:plc:qfpnj4og54vl56wngdriaxug/
3434+</code></pre>
3535+<p>The <code>did:plc:stuff</code> is my
3636+<a href="https://en.wikipedia.org/wiki/Filesystem_in_Userspace">DID</a>.
3737+Digging deeper:</p>
3838+<pre><code>λ ls mnt/did\:plc\:qfpnj4og54vl56wngdriaxug/
3939+app.bsky.actor.profile/ place.stream.chat.message/
4040+app.bsky.actor.status/ place.stream.chat.profile/
4141+app.bsky.feed.generator/ place.stream.key/
4242+app.bsky.feed.like/ place.stream.livestream/
4343+app.bsky.feed.post/ sh.tangled.actor.profile/
4444+app.bsky.feed.repost/ sh.tangled.feed.reaction/
4545+app.bsky.graph.block/ sh.tangled.feed.star/
4646+app.bsky.graph.follow/ sh.tangled.graph.follow/
4747+app.rocksky.album/ sh.tangled.knot/
4848+app.rocksky.artist/ sh.tangled.knot.member/
4949+.
5050+.
5151+.
5252+</code></pre>
5353+<p>We have some data from the repository now. These are
5454+&quot;collections&quot;. If I want to publish a post to Bluesky, I
5555+would write content to the <code>app.bsky.feed.post</code> collection
5656+in my PDS. This will then be indexed by a Bluesky appview
5757+(such as <a href="https://bsky.app">bsky.app</a> or
5858+<a href="https://zeppelin.social">zeppelin.social</a>) and show up
5959+under my profile there.</p>
6060+<p>pdsfs is kind enough to deserialize the in the PDS
6161+repository (stored as CBOR normally) to JSON on the
6262+filesystem:</p>
6363+<pre><code>λ cat sh.tangled.repo/3ljidbevrjh22 | jq
6464+{
6565+ &quot;$type&quot;: &quot;sh.tangled.repo&quot;,
6666+ &quot;addedAt&quot;: &quot;2025-03-03T16:04:13Z&quot;,
6767+ &quot;knot&quot;: &quot;knot1.tangled.sh&quot;,
6868+ &quot;name&quot;: &quot;hello-world&quot;,
6969+ &quot;owner&quot;: &quot;did:plc:3danwc67lo7obz2fmdg6jxcr&quot;
7070+}
7171+</code></pre>
7272+<p>Thanks pdsfs!</p>
7373+<p>I publish my music listening habits to my PDS to the
7474+<code>app.rocksky.scrobble</code> collection, because
7575+<a href="https://rocksky.app">Rocksky</a> recognizes and indexes this
7676+collection. I have wired up my personal navidrome instance
7777+to write data of this form into my PDS everytime I listen to
7878+a track. Here are my top artists in order:</p>
7979+<pre><code>λ jq -r '.artist' app.rocksky.scrobble/* | sort | uniq -c | sort -nr
8080+ 117 Thank You Scientist
8181+ 45 FKJ
8282+ 34 Covet
8383+ 33 VOLA
8484+ 23 Sam Cooke
8585+ 22 Dark Tranquillity
8686+ 21 Piero Piccioni
8787+ 12 Bloodywood
8888+ 11 Frank Sinatra
8989+ 10 Dream Theater
9090+</code></pre>
9191+<p>It is true, I love Sam Cooke.</p>
9292+<p>pdsfs allows mounting multiple repositories at a time. Allow
9393+me to introduce my friends:</p>
9494+<pre><code>λ pdsfs icyphox.sh anil.recoil.org steveklabnik.com tangled.sh
9595+using cached CAR file for...did:plc:hwevmowznbiukdf6uk5dwrrq
9696+using cached CAR file for...did:plc:nhyitepp3u4u6fcfboegzcjw
9797+download complete for...did:plc:3danwc67lo7obz2fmdg6jxcr
9898+download complete for...did:plc:wshs7t2adsemcrrd4snkeqli
9999+mounted at &quot;mnt&quot;
100100+hit enter to unmount and exit...
101101+102102+# -- in a separate shell --
103103+104104+λ cat ./mnt/*/app.bsky.actor.profile/* \
105105+ | jq -r '&quot;\(.displayName)\n\(.description)\n---&quot;' \
106106+ | sed '/^$/d'
107107+Steve Klabnik
108108+#rustlang, #jj-vcs, atproto, shitposts, urbanism. I
109109+contain multitudes. Working on #ruelang but just for
110110+fun. Currently in Austin, TX, but from Pittsburgh.
111111+Previously in Bushwick, the Mission, LA.
112112+---
113113+Anirudh Oppiliappan
114114+building @tangled.sh — code collaboration platform built
115115+on atproto helsinki, finland · https://anirudh.fi ·
116116+(somewhat) effective altruist
117117+---
118118+Anil Madhavapeddy
119119+Professor of Planetary Computing at the University of
120120+Cambridge @cst.cam.ac.uk, where I co-lead the
121121+@eeg.cl.cam.ac.uk, and am also to found at
122122+@conservation.cam.ac.uk. Homepage at
123123+https://anil.recoil.org
124124+---
125125+Tangled
126126+https://tangled.sh is a git collaboration platform built
127127+on atproto. Social coding, but for real this time!
128128+Discord: chat.tangled.sh IRC: #tangled @ libera.chat
129129+Built by @oppi.li &amp; @icyphox.sh
130130+---
131131+</code></pre>
132132+<p>All my friends use <a href="https://tangled.sh">tangled.sh</a>, which
133133+requires them to publish their ssh public key to their PDii
134134+(PDSes?). Perhaps I would like to add their keys to my
135135+allowed_signers file to verify their commit signatures:</p>
136136+<pre><code>λ for dir in ./*/sh.tangled.publicKey;
137137+ do cat $dir/$(ls -r $dir | head -n1) | jq -r '.key';
138138+ done | tee allowed_signers
139139+ssh-rsa AAAAB3NzaC1yc2EAAA...dHPqc= steveklabnik@DESKTOP-VV370NK
140140+ssh-ed25519 AAAAC3NzaC1lZD...g9bAdk icy@wyndle
141141+ssh-ed25519 AAAAC3NzaC1lZD...BqlM1u anil@recoil.org
142142+</code></pre>
143143+<p>FUSE is quite liberating in that it allows you to represent
144144+anything as a filesystem. When applications like <code>ls</code> and
145145+<code>cat</code> are executed, the syscalls to <code>open</code> and <code>read</code> are
146146+rerouted to your custom fs implementation (pdsfs in this
147147+case). The custom fs implementation is free to as it
148148+pleases, in fact the first iteration of pdsfs accessed the
149149+network for each open/read call to fetch live data from
150150+PDii.</p>
151151+</description>
152152+ <link>https://oppi.li/posts/mounting_the_atmosphere/</link>
153153+ <pubDate>Thu, 31 Jul 2025 00:00:00 +0000</pubDate>
154154+ <guid>https://oppi.li/posts/mounting_the_atmosphere/</guid>
155155+ </item>
156156+ <item>
157157+ <title>configuring jujutsu</title>
158158+ <description><p>There are a lot of reasons to use
159159+<a href="https://github.com/jj-vcs/jj">jujutsu</a>, but this post is
160160+not about that. I have this terrible habit of turning every
161161+knob on a tool before I even fully absorb how it works; and
162162+boy does <code>jj</code> have a lot of knobs.</p>
163163+<h3 id="templates">Templates</h3>
164164+<p><code>jj</code> let you tweak nearly every single character of its
165165+output. In fact, <code>jj</code> includes a full-blown templating
166166+language to do so. Lets start from the basics:</p>
167167+<p>Format all timestamps in relative fashion, like &quot;5 hours
168168+ago&quot;:</p>
169169+<pre><code class="language-toml">[template-aliases]
170170+&quot;format_timestamp(timestamp)&quot; = &quot;timestamp.ago()&quot;;
171171+</code></pre>
172172+<p>The default nodes in the log graph are a bit large for my
173173+taste, we can modify the <code>log_node</code> template to fix that:</p>
174174+<pre><code class="language-toml">[templates]
175175+log_node = '''
176176+ label(&quot;node&quot;,
177177+ coalesce(
178178+ if(!self, label(&quot;elided&quot;, &quot;~&quot;)),
179179+ if(current_working_copy, label(&quot;working_copy&quot;, &quot;@&quot;)),
180180+ if(conflict, label(&quot;conflict&quot;, &quot;×&quot;)),
181181+ if(immutable, label(&quot;immutable&quot;, &quot;*&quot;)),
182182+ label(&quot;normal&quot;, &quot;·&quot;)
183183+ )
184184+ )
185185+'''
186186+</code></pre>
187187+<p>This uses smaller iconography in <code>jj log</code> (these icons are
188188+also more widely available in fonts, in my experience):</p>
189189+<pre><code>@ wuuownsw me@oppi.li 21 minutes ago 30d1bd12
190190+│ (no description set)
191191+· plmznxvy me@oppi.li 5 hours ago push-plmznxvyqrqw git_head() 051c142e
192192+│ appview: pulls: bump sourceRev for stacks without causing resubmits
193193+│ * towvwqxk x@icyphox.sh 4 hours ago master a588f625
194194+│ │ appview: pages/markup: don't double camo in post process
195195+│ * ryzqnyvs me@oppi.li 4 hours ago ea4b520a
196196+│ │ appview: fix stack merging
197197+│ * kqvutzxr me@oppi.li 4 hours ago ff73ca23
198198+├─╯ appview: rework RepoLanguages
199199+* vwpqwmms jeynesbrook@gmail.com 8 hours ago d759587b
200200+│ appview: repo: inject language percentage into repo index
201201+~
202202+</code></pre>
203203+<p>Much nicer! And speaking of <code>log</code>, I find it hard to parse
204204+the output when every change occupies two lines instead of
205205+just one line, we can remedy that quickly; in fact, <code>jj</code> has
206206+a builtin template for single-line log outputs:</p>
207207+<pre><code>λ jj log -T builtin_log_oneline
208208+@ wuuownsw me@oppi.li 22 minutes ago 30d1bd12 (no description set)
209209+· plmznxvy me@oppi.li 5 hours ago push-plmznxvyqrqw git_head() 051c142e appview: pulls: bump sourceRev for stacks without causing resubmits
210210+│ * towvwqxk x@icyphox.sh 4 hours ago master a588f625 appview: pages/markup: don't double camo in post process
211211+│ * ryzqnyvs me@oppi.li 4 hours ago ea4b520a appview: fix stack merging
212212+│ * kqvutzxr me@oppi.li 4 hours ago ff73ca23 appview: rework RepoLanguages
213213+├─╯
214214+* vwpqwmms jeynesbrook 8 hours ago d759587b appview: repo: inject language percentage into repo index
215215+│
216216+~
217217+</code></pre>
218218+<p>Bit too long! I personally do not always need author and
219219+time information when quickly scrolling through the log, so
220220+I created my own template based on <code>builtin_log_oneline</code>:</p>
221221+<pre><code class="language-toml">[templates]
222222+log = '''
223223+ if(root,
224224+ format_root_commit(self),
225225+ label(if(current_working_copy, &quot;working_copy&quot;),
226226+ concat(
227227+ separate(&quot; &quot;,
228228+ format_short_change_id_with_hidden_and_divergent_info(self),
229229+ if(empty, label(&quot;empty&quot;, &quot;(empty)&quot;)),
230230+ if(description,
231231+ description.first_line(),
232232+ label(if(empty, &quot;empty&quot;), description_placeholder),
233233+ ),
234234+ bookmarks,
235235+ tags,
236236+ working_copies,
237237+ if(git_head, label(&quot;git_head&quot;, &quot;HEAD&quot;)),
238238+ if(conflict, label(&quot;conflict&quot;, &quot;conflict&quot;)),
239239+ if(config(&quot;ui.show-cryptographic-signatures&quot;).as_boolean(),
240240+ format_short_cryptographic_signature(signature)),
241241+ ) ++ &quot;\n&quot;,
242242+ ),
243243+ )
244244+ )
245245+'''
246246+</code></pre>
247247+<p>Which produces:</p>
248248+<pre><code>@ wuuownsw (no description set)
249249+· plmznxvy appview: pulls: bump sourceRev for stacks without causing resubmits push-plmznxvyqrqw HEAD
250250+│ * towvwqxk appview: pages/markup: don't double camo in post process master
251251+│ * ryzqnyvs appview: fix stack merging
252252+│ * kqvutzxr appview: rework RepoLanguages
253253+├─╯
254254+* vwpqwmms appview: repo: inject language percentage into repo index
255255+│
256256+~
257257+</code></pre>
258258+<p>Sweet! To get a more detailed log, you can always use a
259259+different template for the output: <code>jj log -T builtin_log_detailed</code>.</p>
260260+<p>With git, I set <code>commit.verbose</code> to true, this lets me view
261261+the diff when composing a commit messaege in my <code>$EDITOR</code>:</p>
262262+<pre><code class="language-bash">λ git commit
263263+# no message passed, opens $EDITOR to compose message
264264+265265+# -- inside vim --
266266+267267+# Please enter the commit message for your changes. Lines starting
268268+# with '#' will be ignored, and an empty message aborts the commit.
269269+#
270270+# On branch trunk
271271+# Changes to be committed:
272272+# new file: scripts/handle.js
273273+#
274274+# ------------------------ &gt;8 ------------------------
275275+# Do not modify or remove the line above.
276276+# Everything below it will be ignored.
277277+diff --git a/scripts/handle.js b/scripts/handle.js
278278+new file mode 100644
279279+index 0000000..d8a07f3
280280+--- /dev/null
281281++++ b/scripts/handle.js
282282+@@ -0,0 +1,104 @@
283283++// Run using node handle.js
284284++// Install axios using npm install axios
285285++// nodejs v18+
286286++const axios = require(&quot;axios&quot;);
287287+.
288288+.
289289+.
290290+</code></pre>
291291+<p>The equivalent in <code>jj</code> requires modifying the
292292+<code>draft_commit_description</code> template:</p>
293293+<pre><code class="language-toml">[templates]
294294+draft_commit_description ='''
295295+ concat(
296296+ coalesce(description, default_commit_description, &quot;\n&quot;),
297297+ surround(
298298+ &quot;\nJJ: This commit contains the following changes:\n&quot;, &quot;&quot;,
299299+ indent(&quot;JJ: &quot;, diff.stat(72)),
300300+ ),
301301+ &quot;\nJJ: ignore-rest\n&quot;,
302302+ diff.git(),
303303+ )
304304+'''
305305+</code></pre>
306306+<h3 id="revsets">Revsets</h3>
307307+<p>As a not-so-power-user, I presently only use revsets to
308308+improve my <code>jj log</code> experience. In the context of <code>jj log</code>,
309309+giving it a revset means &quot;here is a bag of revisions, graph
310310+them for me neatly&quot;.</p>
311311+<p>I find myself using the &quot;rebase&quot; flow quite often:</p>
312312+<ul>
313313+<li>I hack on a stack of changes
314314+</li>
315315+<li><code>trunk</code> is updated by my fellow collaborators
316316+</li>
317317+<li>I rebase my stack using <code>jj rebase -s &lt;mine&gt; -d &lt;trunk&gt;</code>
318318+</li>
319319+</ul>
320320+<p>And I scrub through the log output to roughly figure out how
321321+much work it would be to rebase, what I need for this is:</p>
322322+<ul>
323323+<li>the changes added to <code>trunk</code> since I last diverged from it
324324+</li>
325325+<li>the changes add to my stack since I last diverged from
326326+<code>trunk</code>
327327+</li>
328328+</ul>
329329+<pre><code> X--Y--Z my stack
330330+ /
331331+ F--A--B--C--D trunk
332332+</code></pre>
333333+<p>If you want <code>jj log</code> to print this (and by &quot;this&quot;, I mean,
334334+the graph above, exactly as presented), you have to supply
335335+it a <code>revset</code> argument that grabs X, Y, Z, A, B, C, D and F.
336336+Some examples of revsets are:</p>
337337+<pre><code>@ # the rev marking the working copy
338338+x # the rev identified by shorthand `x`
339339+x | y # the set of two revs, [x, y]
340340+@ | trunk() # the set of two revs, [@, trunk()]
341341+.. # everything in this repository
342342+all() # also everything in this repository
343343+fork_point(@ | trunk()) # the rev where @ and trunk() forked off
344344+</code></pre>
345345+<p>And the revset that captures &quot;ahead-behind&quot; style output is:</p>
346346+<pre><code>trunk()..@ | @..trunk() | trunk() | @:: | fork_point(trunk() | @)
347347+</code></pre>
348348+<p>Which includes:</p>
349349+<ul>
350350+<li><code>trunk()..@</code>: the new changes that my collaborators have added
351351+</li>
352352+<li><code>@..trunk()</code>: the new changes that I have added
353353+</li>
354354+<li><code>trunk()</code>: the trunk rev itself
355355+</li>
356356+<li><code>@::</code>: all descendants of <code>@</code>
357357+</li>
358358+<li><code>fork_point(trunk() | @)</code>: the rev from which the two
359359+streams of work diverged
360360+</li>
361361+</ul>
362362+<p>Rougly, when working on a stack, this is what I can see by
363363+supplying the above revset expression (simplified output):</p>
364364+<pre><code class="language-bash">λ jj log -r 'trunk()..@ | @..trunk() | trunk() | @:: | fork_point(trunk() | @)'
365365+@ xdihgmke &lt;-- me
366366+· vurstull
367367+· plmznxvy
368368+· ihefghyy
369369+│ * towvwqxk trunk &lt;-- trunk
370370+│ * ryzqnyvs
371371+│ * kqvutzxr
372372+├─╯
373373+* vwpqwmms &lt;-- fork-point
374374+</code></pre>
375375+<p>And sometimes, when I am editing older parts of my stack
376376+(<code>@::</code> helps us grab <code>xdihgmke</code> and <code>vurstull</code>):</p>
377377+<pre><code class="language-bash">λ jj log -r 'trunk()..@ | @..trunk() | trunk() | @:: | fork_point(trunk() | @)'
378378+· xdihgmke
379379+· vurstull
380380+@ plmznxvy &lt;-- me
381381+· ihefghyy
382382+│ * towvwqxk trunk &lt;-- trunk
383383+│ * ryzqnyvs
384384+│ * kqvutzxr
385385+├─╯
386386+* vwpqwmms &lt;-- fork-point
387387+</code></pre>
388388+<p>To rebase, I would run:</p>
389389+<pre><code>jj rebase -s ihefghyy -d towvwqxk
390390+</code></pre>
391391+<h3 id="aliases">Aliases</h3>
392392+<p>I imagine that most git power users are already familiar
393393+with aliases. The only alias I see myself using often is:</p>
394394+<pre><code class="language-toml">[aliases]
395395+tug = [&quot;bookmark&quot;, &quot;move&quot;, &quot;--from&quot;, &quot;heads(::@- &amp; bookmarks())&quot;, &quot;--to&quot;, &quot;@-&quot;];
396396+</code></pre>
397397+<p>In action:</p>
398398+<pre><code class="language-bash"># ugh my bookmark is way behind
399399+λ jj log
400400+@ xdihgmke
401401+· vurstull
402402+· cyzmakil
403403+· plmznxvy
404404+· ihefghyy some-bookmark
405405+│
406406+~
407407+408408+λ jj tug
409409+410410+# ready to push!
411411+λ jj log
412412+@ xdihgmke
413413+· vurstull some-bookmark*
414414+· cyzmakil
415415+· plmznxvy
416416+· ihefghyy
417417+│
418418+~
419419+</code></pre>
420420+<p>This alias was stolen from this <a href="https://gist.github.com/thoughtpolice/8f2fd36ae17cd11b8e7bd93a70e31ad6#file-jjconfig-toml">wonderful
421421+gist</a>
422422+by <a href="https://github.com/jj-vcs/jj/blob/main/docs/testimonials.md?plain=1#L120">Austin
423423+Seipp</a>.</p>
424424+<h3 id="experimental-features">Experimental features</h3>
425425+<p>I use one experimental feature, that is only available on
426426+some of the newer versions of jujutsu:</p>
427427+<pre><code class="language-bash"># built from master
428428+λ jj version
429429+jj 0.29.0-8c7ca30074767257d75e3842581b61e764d022cf
430430+</code></pre>
431431+<pre><code class="language-toml">[git]
432432+write-change-id-header = true
433433+</code></pre>
434434+<p>This writes an extra commit-header (not to be confused with
435435+commit-trailer, which goes directly in the commit body) with
436436+the jujutsu change-id, as seen by inspecting the commit
437437+object:</p>
438438+<pre><code class="language-bash">λ git cat-file commit 4edebe96159bf81c3be662d0a2b4c7b08a062968
439439+tree a9b7e9e3eed8a22c35829bf586d7560ec8396124
440440+parent edc0d2750d4848bc05cfd3255ee1ca916bea9156
441441+author oppiliappan &lt;me@oppi.li&gt; 1747919102 +0100
442442+committer oppiliappan &lt;me@oppi.li&gt; 1747919102 +0100
443443+change-id skrrxvvxlpzrqzpxlxksvryrykpxkvon &lt;-- this
444444+</code></pre>
445445+<p>This allows code forges to extract change-ids from commits,
446446+and most crucially: <strong>allows tracking changes across force
447447+pushes</strong>. This enables the <a href="https://gist.github.com/thoughtpolice/9c45287550a56b2047c6311fbadebed2">interdiff
448448+format</a>
449449+of code-review. Stacking, commit-wise-review, and interdiff
450450+are all supported on <a href="https://tangled.sh">tangled</a> (you can
451451+read more
452452+<a href="https://bsky.app/profile/tangled.sh/post/3lptwcb47kc2u">here</a>),
453453+if you submit a branch with this experimental feature
454454+enabled.</p>
455455+<h3 id="misc">Misc</h3>
456456+<p>Couple of other quality of life additions:</p>
457457+<pre><code class="language-toml">[ui]
458458+default-command = &quot;status&quot;
459459+pager = &quot;delta&quot;
460460+</code></pre>
461461+<p>I also use nix and home-manager to configure jj, you can
462462+find my configuration <a href="https://plonk.li/r/YQ">here</a>.</p>
463463+</description>
464464+ <link>https://oppi.li/posts/configuring_jujutsu/</link>
465465+ <pubDate>Sat, 24 May 2025 00:00:00 +0000</pubDate>
466466+ <guid>https://oppi.li/posts/configuring_jujutsu/</guid>
467467+ </item>
468468+ <item>
469469+ <title>tales from mainframe modernization</title>
470470+ <description><p>At my last workplace, I wrote transpilers (or just
471471+<a href="https://people.csail.mit.edu/rachit/post/transpiler/">compilers</a>
472472+if you prefer) from mainframe languages (COBOL, JCL, BASIC etc.) to
473473+Java (in Rust!).</p>
474474+<p>Legacy code is full of surprises. In the roughly 200k lines
475475+of COBOL that I had the (dis)pleasure of working with, I saw
476476+some wonderful hacks to get around the limitations of the
477477+system. Mainframes are also chock full of history.</p>
478478+<h3 id="base-10-numerics">Base-10 numerics</h3>
479479+<p>This is the first thing that stood out to me when I looked
480480+at COBOL code, a data-definition (the phrase for &quot;variable&quot;)
481481+in COBOL is declared like so:</p>
482482+<pre><code> ,-- name
483483+ | ,- type
484484+ __|___ __|_
485485+ 01 HEIGHT PIC 9(3).
486486+ -- ---
487487+ | |
488488+ | `- picture clause (keyword)
489489+ `- level number
490490+</code></pre>
491491+<p>That statement declares a variable called <code>HEIGHT</code> with type
492492+<code>9(3)</code>, which is shorthand for <code>999</code>, which indicates
493493+&quot;3-digit number&quot;. The possible values for this variable are
494494+<code>0</code> to <code>999</code>!</p>
495495+<h3 id="internationalisation">Internationalisation</h3>
496496+<p>Below is another data-definition in COBOL, declaring 3
497497+variables:</p>
498498+<pre><code class="language-cobol">01 FOO-PERSON.
499499+ 05 FOO-NAME PIC X(5).
500500+ 05 FOO-HEIGHT PIC 9(3).
501501+</code></pre>
502502+<p>What that means is:</p>
503503+<ul>
504504+<li><code>FOO-PERSON</code>: a &quot;group&quot; variable consisting of two other
505505+variables
506506+</li>
507507+<li><code>FOO-NAME</code>: an alphanumeric type with 5 characters
508508+</li>
509509+<li><code>FOO-HEIGHT</code>: a numeric type with 3 digits (remember, base 10
510510+and not base 2)
511511+</li>
512512+</ul>
513513+<p>COBOL has an interesting construct called &quot;REDEFINES&quot;:</p>
514514+<pre><code class="language-cobol">01 FOO-PERSON.
515515+ 05 FOO-NAME PIC X(5).
516516+ 05 FOO-HEIGHT PIC 9(3).
517517+518518+01 FOO-PERSONNE REDEFINES FOO-PERSON.
519519+ 05 FOO-NOM PIC X(5).
520520+ 05 FOO-TAILLE PIC 9(3).
521521+</code></pre>
522522+<p><code>FOO-PERSON</code> and <code>FOO-PERSONNE</code> refer to the same region of
523523+memory.</p>
524524+<p>I helped modernise a codebase that had clearly been worked
525525+on by a Spanish consultancy at some point, and they had
526526+decided to redefine all data definitions in Spanish.</p>
527527+<h3 id="string-parsing">String parsing</h3>
528528+<p>Here's another fun one:</p>
529529+<pre><code class="language-cobol"> 01 FOO-PERSON.
530530+ 05 FOO-NAME PIC X(5).
531531+ 05 FOO-HEIGHT PIC 9(3).
532532+ .
533533+ .
534534+ .
535535+536536+ MOVE &quot;PETER&quot; TO FOO-NAME.
537537+ MOVE 175 TO FOO-HEIGHT.
538538+539539+ *&gt; display the entire memory region
540540+ DISPLAY FOO-PERSON.
541541+ *&gt; PETER175
542542+543543+ *&gt; subscripting the first 7 bytes...
544544+ DISPLAY FOO-PERSON (1:7)
545545+ *&gt; PETER17
546546+</code></pre>
547547+<p>So data-definitions simply describe names for regions. Which
548548+enables a clever way to parse strings:</p>
549549+<pre><code class="language-cobol"> 01 DATE.
550550+ 05 DD PIC 9(2).
551551+ 05 FILLER PIC X.
552552+ 05 MMM PIC A(3).
553553+ 05 FILLER PIC X.
554554+ 05 YYYY PIC 9(4).
555555+556556+ .
557557+ .
558558+ .
559559+560560+ MOVE &quot;03 MAR 2025&quot; TO DATE.
561561+ DISPLAY &quot;DAY: &quot; DD. *&gt; DAY: 03
562562+ DISPLAY &quot;MONTH: &quot; MMM. *&gt; MONTH: MAR
563563+ DISPLAY &quot;YEAR: &quot; YYYY. *&gt; YEAR: 2025
564564+565565+ *&gt; also works:
566566+ MOVE &quot;03-MAR-2025&quot; TO DATE.
567567+</code></pre>
568568+<h3 id="early-exit">Early exit</h3>
569569+<p>I'd see this peppered around in a few places; which I later
570570+realized was a way to trigger an abnormal end to a batch job
571571+(possibly triggering an error handling routine in the outer
572572+job control system):</p>
573573+<pre><code class="language-cobol"> 01 CONSTANT-ZERO S9(9)V9 VALUE 0.
574574+ 01 ABEND S9(9)V9.
575575+576576+ .
577577+ .
578578+ .
579579+580580+ COMPUTE ABEND = CONSTANT-ZERO / CONSTANT-ZERO.
581581+</code></pre>
582582+<h3 id="all-the-numbers">All the numbers</h3>
583583+<p>I have yet to find an explanation for this one, but I once
584584+found a file with just the first 800 natural numbers defined
585585+as string constants:</p>
586586+<pre><code class="language-cobol"> 01 TC0001 X(5) &quot;00001&quot;.
587587+ 01 TC0002 X(5) &quot;00002&quot;.
588588+ 01 TC0003 X(5) &quot;00003&quot;.
589589+ .
590590+ .
591591+ *&gt; .... 800 lines later ....
592592+ .
593593+ .
594594+ 01 TC0800 X(5) &quot;00800&quot;.
595595+</code></pre>
596596+<p>The file was definitely not generated, and I can't imagine
597597+text editors on the mainframe were all that advanced either.</p>
598598+<h3 id="dd---disk-destroyer"><code>dd</code> - disk destroyer</h3>
599599+<p>The <code>DD</code> statement in the JCL subsystem stands for &quot;data
600600+definition&quot;, which is largely used to describe files and IO
601601+streams used by a batch job. The <code>dd</code> command [^dd] on UNIX is
602602+named after this statement!</p>
603603+<p>[^dd]: <a href="https://en.wikipedia.org/wiki/Dd_%28Unix%29#History">Wikipedia - dd (Unix)</a></p>
604604+</description>
605605+ <link>https://oppi.li/posts/tales_from_mainframe_modernization/</link>
606606+ <pubDate>Wed, 21 May 2025 00:00:00 +0000</pubDate>
607607+ <guid>https://oppi.li/posts/tales_from_mainframe_modernization/</guid>
608608+ </item>
609609+ <item>
610610+ <title>OSC-52</title>
611611+ <description><p>I use <code>ssh</code> a lot. Copying text from the remote machine to
612612+the host machine always sucked. But OSC-52 makes that easy.</p>
613613+<p>OSC-52 is an ANSI escape sequence to write text to the
614614+terminal emulator. The terminal emulator, if it understands
615615+what is going on, will in turn write this text to the system
616616+clipboard.</p>
617617+<p>What this means is some <code>printf</code> magic can send text to your
618618+clipboard. I store this one-liner in a script called
619619+<code>oclip</code>:</p>
620620+<pre><code class="language-bash">printf &quot;\033]52;c;%s\007&quot; &quot;$(base64 &lt;&amp;0)&quot;
621621+</code></pre>
622622+<p>and I run it with:</p>
623623+<pre><code class="language-bash">remote $ cat some_file.txt | oclip
624624+625625+# some_file.txt's contents are now the host's clipboard
626626+</code></pre>
627627+<h3 id="the-catch">The catch</h3>
628628+<p>Your terminal emulator must support OSC-52, <code>alacritty</code> and
629629+<code>termux</code> seem to support this out of the box. In <code>st</code>,
630630+OSC-52 works with this change to <code>config.h</code>:</p>
631631+<pre><code>int allowwindowops = 1;
632632+</code></pre>
633633+<p>If you are using <code>tmux</code>, you need to flip this switch on:</p>
634634+<pre><code>set -s set-clipboard on
635635+</code></pre>
636636+<p>If you are inside <code>nvim</code>, it may work as expected as long as
637637+<code>$SSH_TTY</code> is set. I sometimes physically start a session,
638638+and <code>ssh</code> into the same session later from another machine,
639639+and <code>$SSH_TTY</code> remains unset, so I force OSC-52 in <code>nvim</code> at
640640+all times (see
641641+<a href="https://neovim.io/doc/user/provider.html#clipboard-osc52">nvimdoc</a>):</p>
642642+<pre><code class="language-lua">vim.g.clipboard = {
643643+ name = 'OSC 52',
644644+ copy = {
645645+ ['+'] = require('vim.ui.clipboard.osc52').copy('+'),
646646+ ['*'] = require('vim.ui.clipboard.osc52').copy('*'),
647647+ },
648648+ paste = {
649649+ ['+'] = require('vim.ui.clipboard.osc52').paste('+'),
650650+ ['*'] = require('vim.ui.clipboard.osc52').paste('*'),
651651+ },
652652+}
653653+</code></pre>
654654+<p>If you are inside <code>nvim</code> inside <code>tmux</code> inside an <code>ssh</code>
655655+session inside <code>st</code>, you neeed all of the above tweaks.
656656+<code>nvim</code> will pass the contents around to <code>tmux</code>, which in
657657+turn will pass the contents to <code>st</code>, which should pass it to
658658+your system clipboard.</p>
659659+</description>
660660+ <link>https://oppi.li/posts/OSC-52/</link>
661661+ <pubDate>Wed, 27 Nov 2024 00:00:00 +0000</pubDate>
662662+ <guid>https://oppi.li/posts/OSC-52/</guid>
663663+ </item>
664664+ <item>
665665+ <title>introducing tablespoon</title>
666666+ <description><p><a href="https://git.peppe.rs/languages/tbsp">tbsp</a> (tree-based
667667+source-processing language) is an awk-like language that
668668+operates on tree-sitter syntax trees. To motivate the need
669669+for such a program, we could begin by writing a
670670+markdown-to-html converter using <code>tbsp</code> and
671671+<a href="https://github.com/tree-sitter-grammars/tree-sitter-markdown">tree-sitter-md</a>.
672672+We need some markdown to begin with:</p>
673673+<pre><code># 1 heading
674674+675675+content of first paragraph
676676+677677+## 1.1 heading
678678+679679+content of nested paragraph
680680+</code></pre>
681681+<p>For future reference, this markdown is parsed like so by
682682+tree-sitter-md (visualization generated by
683683+<a href="https://git.peppe.rs/cli/tree-viz">tree-viz</a>):</p>
684684+<pre><code>document
685685+| section
686686+| | atx_heading
687687+| | | atx_h1_marker &quot;#&quot;
688688+| | | heading_content inline &quot;1 heading&quot;
689689+| | paragraph
690690+| | | inline &quot;content of first paragraph&quot;
691691+| | section
692692+| | | atx_heading
693693+| | | | atx_h2_marker &quot;##&quot;
694694+| | | | heading_content inline &quot;1.1 heading&quot;
695695+| | | paragraph
696696+| | | | inline &quot;content of nested paragraph&quot;
697697+</code></pre>
698698+<p>Onto the converter itself. Every <code>tbsp</code> program is written as
699699+a collection of stanzas. Typically, we start with a stanza
700700+like so:</p>
701701+<pre><code>BEGIN {
702702+ int depth = 0;
703703+704704+ print(&quot;&lt;html&gt;\n&quot;);
705705+ print(&quot;&lt;body&gt;\n&quot;);
706706+}
707707+</code></pre>
708708+<p>The stanza begins with a &quot;pattern&quot;, in this case, <code>BEGIN</code>,
709709+and is followed a block of code. This block specifically, is
710710+executed right at the beginning, before traversing the parse
711711+tree. In this stanza, we set a &quot;depth&quot; variable to keep
712712+track of nesting of markdown headers, and begin our html
713713+document by printing the <code>&lt;html&gt;</code> and <code>&lt;body&gt;</code> tags.</p>
714714+<p>We can follow this stanza with an <code>END</code> stanza, that is
715715+executed after the traversal:</p>
716716+<pre><code>END {
717717+ print(&quot;&lt;/body&gt;\n&quot;);
718718+ print(&quot;&lt;/html&gt;\n&quot;);
719719+}
720720+</code></pre>
721721+<p>In this stanza, we close off the tags we opened at the start
722722+of the document. We can move onto the interesting bits of
723723+the conversion now:</p>
724724+<pre><code>enter section {
725725+ depth += 1;
726726+}
727727+leave section {
728728+ depth -= 1;
729729+}
730730+</code></pre>
731731+<p>The above stanzas begin with <code>enter</code> and <code>leave</code> clauses,
732732+followed by the name of a tree-sitter node kind: <code>section</code>.
733733+The <code>section</code> identifier is visible in the
734734+tree-visualization above, it encompasses a markdown-section,
735735+and is created for every markdown header. To understand how
736736+<code>tbsp</code> executes above stanzas:</p>
737737+<pre><code>document ... depth = 0
738738+| section &lt;-------- enter section (1) ... depth = 1
739739+| | atx_heading
740740+| | | inline
741741+| | paragraph
742742+| | | inline
743743+| | section &lt;----- enter section (2) ... depth = 2
744744+| | | atx_heading
745745+| | | | inline
746746+| | | paragraph
747747+| | | | inline
748748+| | | &lt;----------- leave section (2) ... depth = 1
749749+| | &lt;-------------- leave section (1) ... depth = 0
750750+</code></pre>
751751+<p>The following stanzas should be self-explanatory now:</p>
752752+<pre><code>enter atx_heading {
753753+ print(&quot;&lt;h&quot;);
754754+ print(depth);
755755+ print(&quot;&gt;&quot;);
756756+}
757757+leave atx_heading {
758758+ print(&quot;&lt;/h&quot;);
759759+ print(depth);
760760+ print(&quot;&gt;\n&quot;);
761761+}
762762+763763+enter inline {
764764+ print(text(node));
765765+}
766766+</code></pre>
767767+<p>But an explanation is included nonetheless:</p>
768768+<pre><code>document ... depth = 0
769769+| section &lt;-------- enter section (1) ... depth = 1
770770+| | atx_heading &lt;- enter atx_heading ... print &quot;&lt;h1&gt;&quot;
771771+| | | inline &lt;--- enter inline ... print ..
772772+| | | &lt;----------- leave atx_heading ... print &quot;&lt;/h1&gt;&quot;
773773+| | paragraph
774774+| | | inline &lt;--- enter inline ... print ..
775775+| | section &lt;----- enter section (2) ... depth = 2
776776+| | | atx_heading enter atx_heading ... print &quot;&lt;h2&gt;&quot;
777777+| | | | inline &lt;- enter inline ... print ..
778778+| | | | &lt;-------- leave atx_heading ... print &quot;&lt;/h2&gt;&quot;
779779+| | | paragraph
780780+| | | | inline &lt;- enter inline ... print ..
781781+| | | &lt;----------- leave section (2) ... depth = 1
782782+| | &lt;-------------- leave section (1) ... depth = 0
783783+</code></pre>
784784+<p>The
785785+<a href="https://git.peppe.rs/languages/tbsp/tree/examples">examples</a>
786786+directory contains a complete markdown-to-html converter,
787787+along with a few other motivating examples.</p>
788788+<h3 id="usage">Usage</h3>
789789+<p>The <code>tbsp</code> evaluator is written in rust, use cargo to build
790790+and run:</p>
791791+<pre><code>cargo build --release
792792+./target/release/tbsp --help
793793+</code></pre>
794794+<p><code>tbsp</code> requires three inputs:</p>
795795+<ul>
796796+<li>a <code>tbsp</code> program, referred to as &quot;program file&quot;
797797+</li>
798798+<li>a language
799799+</li>
800800+<li>an input file or some input text at stdin
801801+</li>
802802+</ul>
803803+<p>You can run the interpreter like so (this program prints an
804804+overview of a rust file):</p>
805805+<pre><code>$ ./target/release/tbsp \
806806+ -f./examples/code-overview/overview.tbsp \
807807+ -l rust \
808808+ src/main.rs
809809+module
810810+ └╴struct Cli
811811+ └╴trait Cli
812812+ └╴fn program
813813+ └╴fn language
814814+ └╴fn file
815815+ └╴fn try_consume_stdin
816816+ └╴fn main
817817+</code></pre>
818818+</description>
819819+ <link>https://oppi.li/posts/introducing_tablespoon/</link>
820820+ <pubDate>Thu, 01 Aug 2024 00:00:00 +0000</pubDate>
821821+ <guid>https://oppi.li/posts/introducing_tablespoon/</guid>
822822+ </item>
823823+ <item>
824824+ <title>snip snap</title>
825825+ <description><p>I regularly switch between exactly two things while working,
826826+a &quot;current&quot; and an &quot;alternate&quot; item; a lot of tools I use
827827+seem to support this flow.</p>
828828+<h4 id="git">git</h4>
829829+<p>Pass <code>-</code> to <code>git-checkout</code> to switch to the previously
830830+active branch:</p>
831831+<pre><code class="language-bash">$ git branch
832832+* foo
833833+ bar
834834+835835+$ git checkout bar
836836+$ git branch
837837+ foo
838838+* bar
839839+840840+$ git checkout -
841841+$ git branch
842842+* foo
843843+ bar
844844+</code></pre>
845845+<h4 id="bash---cd">bash - cd</h4>
846846+<p>This may not be exclusive to <code>bash</code>:</p>
847847+<pre><code class="language-bash">~/foo $ cd ~/bar
848848+~/bar $ cd -
849849+~/foo $
850850+</code></pre>
851851+<p>This is especially handy in combination with my <a href="../curing_a_case_of_git-UX/">git-worktree
852852+flow</a>:</p>
853853+<pre><code class="language-bash">~/main-branch $ gwj feature
854854+~/feat-branch $ cd -
855855+~/main-branch $
856856+</code></pre>
857857+<h4 id="bash---jobs">bash - jobs</h4>
858858+<p>I often suspend multiple <code>vim</code> sessions with <code>Ctrl-Z</code>:</p>
859859+<pre><code class="language-bash">$ jobs
860860+[1]+ Stopped vim transpiler/src/transform.rs
861861+[2]- Stopped git commit --verbose
862862+</code></pre>
863863+<p>In the above example: I suspended <code>vim</code> when working on
864864+<code>transform.rs</code>, and then began working on a commit by
865865+running <code>git commit</code> without a message flag (lets you craft
866866+a message in <code>$EDITOR</code>). To bring the current job to the
867867+foreground, you can use <code>fg</code>:</p>
868868+<pre><code class="language-bash">$ fg
869869+</code></pre>
870870+<p>With a job identifier:</p>
871871+<pre><code class="language-bash">$ fg %2 # resumes interactive git commit
872872+</code></pre>
873873+<p>Or switch to &quot;last&quot; job, or the second-most-recently-resumed
874874+job:</p>
875875+<pre><code class="language-bash">$ fg %-
876876+$ %- # shorthand
877877+878878+</code></pre>
879879+<h4 id="vim">vim</h4>
880880+<p>Switch to the last active buffer with <code>Ctrl+^</code>. In
881881+command-mode, <code>#</code> refers to the last active buffer, you can
882882+use this as an argument to a few commands:</p>
883883+<pre><code class="language-vimscript">:b# &quot; switch to alternate buffer (same as Ctrl+^)
884884+:vsp# &quot; create a vertical split with the alternate buffer
885885+:read# &quot; read contents of alternate buffer into current buffer
886886+:!wc # &quot; pass file name of alternate buffer to the command `wc`
887887+</code></pre>
888888+<p>See <code>:help c_#</code> for more.</p>
889889+<h4 id="tmux">tmux</h4>
890890+<p>Switch to the last active tmux session with
891891+<code>&lt;prefix&gt;+shift+L</code>.</p>
892892+<h4 id="qutebrowser">qutebrowser</h4>
893893+<p>Switch to the last active tab with <code>g$</code>.</p>
894894+</description>
895895+ <link>https://oppi.li/posts/snip_snap/</link>
896896+ <pubDate>Wed, 29 May 2024 00:00:00 +0000</pubDate>
897897+ <guid>https://oppi.li/posts/snip_snap/</guid>
898898+ </item>
899899+ <item>
900900+ <title>plain text journaling</title>
901901+ <description><p>I cobbled together a journaling system with {neo,}vim,
902902+coreutils and <a href="http://www.fresse.org/dateutils">dateutils</a>.
903903+This system is loosely based on <a href="https://www.rydercarroll.com/">Ryder
904904+Caroll's</a> Bullet Journal
905905+method.</p>
906906+<p><a href="https://cdn.oppi.li/SpF.png"><img src="https://cdn.oppi.li/SpF.png" alt="" /></a></p>
907907+<h3 id="the-format">The format</h3>
908908+<p>The journal for a given year is a directory:</p>
909909+<pre><code class="language-bash">λ ls journal/
910910+2022/ 2023/
911911+</code></pre>
912912+<p>In each directory are 12 files, one for each month of the
913913+year, numbered like so:</p>
914914+<pre><code class="language-bash">λ ls journal/2023/
915915+01 02 03 04 05 06 07 08 09 10 11 12
916916+</code></pre>
917917+<p>We can now begin writing stuff down:</p>
918918+<pre><code class="language-bash">λ vim journal/2023/1
919919+</code></pre>
920920+<p>Every month must start with a calendar of course, fill that
921921+in with:</p>
922922+<pre><code class="language-vim">:read !cal -m
923923+</code></pre>
924924+<p>Your entry for January might look like this:</p>
925925+<pre><code class="language-bash">λ cat journal/2023/01
926926+ January 2023
927927+Mo Tu We Th Fr Sa Su
928928+ 1
929929+ 2 3 4 5 6 7 8
930930+ 9 10 11 12 13 14 15
931931+16 17 18 19 20 21 22
932932+23 24 25 26 27 28 29
933933+30 31
934934+</code></pre>
935935+<p>I prefer planning week by week, as opposed to creating a
936936+task-list every day, here's what I have for the first couple
937937+of weeks:</p>
938938+<pre><code> January 2023
939939+Mo Tu We Th Fr Sa Su
940940+ 1
941941+ 2 3 4 5 6 7 8
942942+ 9 10 11 12 13 14 15
943943+16 17 18 19 20 21 22
944944+23 24 25 26 27 28 29
945945+30 31
946946+947947+948948+week 1
949949+950950+done apply leaves
951951+done dload boarding pass
952952+moved reply to dan
953953+954954+955955+week 2
956956+957957+todo reply to dan
958958+todo pack bags
959959+done travel insurance
960960+todo weigh luggage
961961+</code></pre>
962962+<p>I start the week by writing a header and each item that week
963963+is placed on its own line. The items are prefixed with a
964964+<code>todo</code> or a <code>done</code> signifier.</p>
965965+<h3 id="form-over-function">Form over function</h3>
966966+<p>Right off the bat, the signifiers look very noisy, Even more
967967+so once we start introducing variety (I use &quot;event&quot;, &quot;note&quot;
968968+and &quot;moved&quot;):</p>
969969+<pre><code>week 1
970970+971971+todo apply leaves
972972+done dload boarding pass
973973+todo reply to dan
974974+event fr trip
975975+note weight 68.6
976976+</code></pre>
977977+<p>We can clean this up with &quot;abbreviations&quot; (<code>:h abbreviations</code>):</p>
978978+<pre><code class="language-vim">:iabbrev todo ·
979979+:iabbrev done ×
980980+</code></pre>
981981+<p>Now, typing this:</p>
982982+<pre><code>todo apply leaves
983983+</code></pre>
984984+<p>Automatically inserts:</p>
985985+<pre><code>· apply leaves
986986+</code></pre>
987987+<p>You can use <code>x</code> and <code>o</code> as well, but <code>×</code> (U+00D7,
988988+MULTIPLICATION SIGN) and <code>·</code> (U+00B7, MIDDLE DOT) are more
989989+... <em>gourmet</em>.</p>
990990+<p>The other signifiers I use are:</p>
991991+<ul>
992992+<li><code>-</code> for note
993993+</li>
994994+<li><code>o</code> for event
995995+</li>
996996+<li><code>&gt;</code> for moved.
997997+</li>
998998+</ul>
999999+<p>Nit #2 is the lack of order. We can employ vim to introduce
10001000+grouping and sorting. Select the list of entries for this
10011001+week:</p>
10021002+<pre><code class="language-vim">vip &quot; line-wise select inner paragraph
10031003+:'&lt;,'&gt;sort &quot; the markers '&lt; and '&gt; are automatically inserted,
10041004+ &quot; they mark the start and end of the selection
10051005+</code></pre>
10061006+<p>We end up with:</p>
10071007+<pre><code>week 1
10081008+10091009+· apply leaves
10101010+· reply to dan
10111011+× dload boarding pass
10121012+</code></pre>
10131013+<p>The lines are grouped by their signifiers, segregating todo
10141014+items from completed items. Luckily, MIDDLE DOT is lesser
10151015+than MULTIPLICATION SIGN, so todo items are placed at the
10161016+top. The same goes for <code>o</code> and <code>x</code> symbols, either set of
10171017+signifiers will result in the same sorting order.</p>
10181018+<p>We can shorten this select-paragraph-invoke-sort dance by
10191019+setting the <code>formatprg</code> variable:</p>
10201020+<pre><code class="language-vim">:set formatprg=sort\ -V
10211021+</code></pre>
10221022+<p>Now, hitting <code>gqip</code> should automatically group and sort the
10231023+items for the week under the cursor, moving todo items to
10241024+the top. Finding signifier glyphs that suit your sorting
10251025+preference is a fun exercise.</p>
10261026+<h3 id="syntax-highlighting">Syntax highlighting</h3>
10271027+<p>Adding color to items introduces another layer of visual
10281028+distinction. In truth, I like to deck it out just because.</p>
10291029+<p>First, create a few syntax groups:</p>
10301030+<pre><code class="language-vim">:syntax match JournalAll /.*/ &quot; captures the entire buffer
10311031+:syntax match JournalDone /^×.*/ &quot; lines containing 'done' items: ×
10321032+:syntax match JournalTodo /^·.*/ &quot; lines containing 'todo' items: ·
10331033+:syntax match JournalEvent /^o.*/ &quot; lines containing 'event' items: o
10341034+:syntax match JournalNote /^- .*/ &quot; lines containing 'note' items: -
10351035+:syntax match JournalMoved /^&gt;.*/ &quot; lines containing 'moved' items: &gt;
10361036+</code></pre>
10371037+<p>Add highlights to each group:</p>
10381038+<pre><code class="language-vim">:highlight JournalAll ctermfg=12 &quot; bright black
10391039+:highlight JournalDone ctermfg=12 &quot; bright black
10401040+:highlight JournalEvent ctermfg=6 &quot; cyan
10411041+:highlight JournalMoved ctermfg=5 &quot; magenta
10421042+:highlight JournalNote ctermfg=3 &quot; yellow
10431043+</code></pre>
10441044+<p>In my terminal, this is rendered like so:</p>
10451045+<p><a href="https://u.peppe.rs/Du6.png"><img src="https://u.peppe.rs/Du6.png" alt="" /></a></p>
10461046+<h3 id="habit-tracking">Habit tracking</h3>
10471047+<p>While this is not a part of my journaling system anymore, a
10481048+few headers and an awk script is all it takes to track
10491049+habits. My weekly entries would include a couple of habit
10501050+headers like so:</p>
10511051+<pre><code>week 1 --------------
10521052+10531053+× wake up on time
10541054+× water the plants
10551055+10561056+spend 7.5 7 10
10571057+---------------------
10581058+10591059+10601060+week 2 --------------
10611061+10621062+· make the bed
10631063+· go to bed
10641064+10651065+spend 30 2.75 6
10661066+---------------------
10671067+</code></pre>
10681068+<p>Here, under the <code>spend</code> header in week 1, are a list of
10691069+expenditures accumulated over the week. The monthly spend is
10701070+calculated with this awk script:</p>
10711071+<pre><code class="language-awk">BEGIN {spend=0;}
10721072+/spend/ {for(i=1;i&lt;=$NF;i++) spend+=$i;}
10731073+END { printf spend &quot;eur&quot;}
10741074+</code></pre>
10751075+<p>And invoked like so:</p>
10761076+<pre><code>λ awk -f spend.awk journal/2023/01
10771077+63.25eur
10781078+</code></pre>
10791079+<h3 id="reflection">Reflection</h3>
10801080+<p>Journaling is not just about planning what is to come, but
10811081+also reflecting on what has passed. It would make sense to
10821082+simultaneously look at the past few weeks' entries while
10831083+making your current one. To open multiple months of entries
10841084+at the same time:</p>
10851085+<pre><code>λ vim -O journal/2023/0{1,2,3}
10861086+</code></pre>
10871087+<p>Opens 3 months, side-by-side, in vertical splits:</p>
10881088+<pre><code>JANUARY ------------ │ FEBRUARY ----------- │ MARCH --------------
10891089+ │ │
10901090+Mo Tu We Th Fr Sa Su │ Mo Tu We Th Fr Sa Su │ Mo Tu We Th Fr Sa Su
10911091+ 1 │ 1 2 3 4 5 │ 1 2 3 4 5
10921092+ 2 3 4 5 6 7 8 │ 6 7 8 9 10 11 12 │ 6 7 8 9 10 11 12
10931093+ 9 10 11 12 13 14 15 │ 13 14 15 16 17 18 19 │ 13 14 15 16 17 18 19
10941094+16 17 18 19 20 21 22 │ 20 21 22 23 24 25 26 │ 20 21 22 23 24 25 26
10951095+23 24 25 26 27 28 29 │ 27 28 │ 27 28 29 30 31
10961096+30 31 │ │
10971097+ │ │
10981098+ │ │
10991099+WEEK 1 ------------- │ WEEK 1 ------------- │ WEEK 1 -------------
11001100+ │ │
11011101+&gt; latex setup │ &gt; forex │ - weight: 64
11021102+× make the bed │ × clean shoes │ &gt; close sg-pr
11031103+× 03: dentist │ × buy clothes │ × facewash
11041104+× integrate tsg │ × draw │ × groceries
11051105+ │ │
11061106+ │ │
11071107+WEEK 2 ------------- │ WEEK 2 ------------- │ WEEK 2 -------------
11081108+ │ │
11091109+× latex setup │ - viral fever │ &gt; close sg-pr
11101110+× send invoice │ × forex │ × plan meet
11111111+× stack-graph pr │ × activate sim │ × sg storage
11121112+ │ × bitlbee │
11131113+</code></pre>
11141114+<h3 id="reducing-friction">Reducing friction</h3>
11151115+<p>Journaling already requires a solid amount of discipline and
11161116+consistency. The added friction of typing <code>vim journal/$CURRENT_YEAR/$CURRENT_MONTH</code> each time is doing no
11171117+favors.</p>
11181118+<p>To open the current month based on system time:</p>
11191119+<pre><code class="language-bash">λ vim $(date +&quot;%Y/%m&quot;)
11201120+</code></pre>
11211121+<p>To open all the months within a 2 month window of today, is
11221122+a little trickier. The command we wish to generate is (if
11231123+today is 2023/12):</p>
11241124+<pre><code class="language-bash">λ vim -O 2023/10 2023/11 2023/12 2024/01 2024/02
11251125+</code></pre>
11261126+<p>And that is where <code>dateseq</code> from
11271127+<a href="http://www.fresse.org/dateutils">dateutils</a> comes in handy,
11281128+for example:</p>
11291129+<pre><code class="language-bash">λ dateseq 2012-02-01 2012-03-01
11301130+2012-02-01
11311131+2012-02-02
11321132+2012-02-03
11331133+...
11341134+2012-02-28
11351135+2012-02-29
11361136+2012-03-01
11371137+</code></pre>
11381138+<p>This script opens all months within a 2 month window of
11391139+today:</p>
11401140+<pre><code class="language-bash">λ vim -O $(
11411141+ dateseq \
11421142+ &quot;$(date --date &quot;2 months ago&quot; +%Y/%m)&quot; \
11431143+ &quot;$(date --date &quot;2 months&quot; +%Y/%m)&quot; \
11441144+ -i %Y/%m \
11451145+ -f %Y/%m
11461146+)
11471147+</code></pre>
11481148+<h3 id="fin">Fin</h3>
11491149+<p>You can find a sample vimrc file here:
11501150+<a href="https://git.peppe.rs/cli/journal/tree">cli/journal</a>, along
11511151+with a nix flake file to kick things off.</p>
11521152+<p>Plain text journaling can be just as much fun as a pen and
11531153+paper. Throw in some ASCII art for each month, use swankier
11541154+signifiers, or louder syntax highlighting. Don't expect
11551155+forgiveness from org-mode users though.</p>
11561156+<p><a href="https://cdn.oppi.li/ZCK.png"><img src="https://cdn.oppi.li/ZCK.png" alt="" /></a></p>
11571157+</description>
11581158+ <link>https://oppi.li/posts/plain_text_journaling/</link>
11591159+ <pubDate>Sun, 18 Jun 2023 00:00:00 +0000</pubDate>
11601160+ <guid>https://oppi.li/posts/plain_text_journaling/</guid>
11611161+ </item>
11621162+ <item>
11631163+ <title>curing a case of git-UX</title>
11641164+ <description><p>Git worktrees are great, but they fall behind the venerable
11651165+<code>git checkout</code> sometimes. I attempted to fix that with
11661166+<a href="https://github.com/junegunn/fzf">fzf</a> and
11671167+a bit of bash.</p>
11681168+<p><a href="https://asciinema.org/a/D297ztKRzpE4gAHbPTPmkqYps"><img src="https://asciinema.org/a/D297ztKRzpE4gAHbPTPmkqYps.svg" alt="" /></a></p>
11691169+<p>Fear not if you haven't heard of &quot;worktrees&quot;, I have
11701170+included a primer here.<br />
11711171+<a href="#what-makes-them-clunky">Skip the primer
11721172+-&gt;</a>.</p>
11731173+<h3 id="why-worktrees">Why Worktrees?</h3>
11741174+<p>Picture this. You are whacking away on a feature branch.
11751175+Halfway there, in fact. Your friend asks you fix something
11761176+urgently. You proceed to do one of three things:</p>
11771177+<ul>
11781178+<li>create a temporary branch, make a WIP commit, begin
11791179+working on the fix
11801180+</li>
11811181+<li>stash away your changes, begin working on the fix
11821182+</li>
11831183+<li>unfriend said friend for disturbing your flow
11841184+</li>
11851185+</ul>
11861186+<p>All of these options are ... subpar. With the temporary
11871187+branch, you are forced to create a partial, non-working
11881188+commit, and then reset said commit once done with the fix.
11891189+With the stash approach, you are required to now keep a
11901190+mental model of the stash, be aware of untracked files that
11911191+don't get stashed by default, etc. Why won't git just let
11921192+you work on two things at the same time without <em>thinking</em>
11931193+so much?</p>
11941194+<p>That is exactly what worktrees let you do. Worktrees let you
11951195+have more than one checkout at a time, each checkout in a
11961196+separate directory. Like creating a new clone, but safer (it
11971197+disallows checking out the same branch twice) and a lot more
11981198+space efficient (the new working tree is &quot;linked&quot; to the
11991199+&quot;main&quot; worktree, and a good amount of stuff is shared). When
12001200+your friend asks you to make the fix, you proceed like so:</p>
12011201+<ol>
12021202+<li>Create a new working tree with:
12031203+</li>
12041204+</ol>
12051205+<pre><code class="language-bash"># git worktree add -b &lt;branch-name&gt; &lt;path&gt; &lt;from&gt;
12061206+git worktree add -b fix-stuff /path/to/tree master
12071207+</code></pre>
12081208+<ol start="2">
12091209+<li><code>cd</code> into <code>/path/to/tree</code>
12101210+</li>
12111211+<li>Fix, test, commit, push, party
12121212+</li>
12131213+<li>Go back to your work, <code>cd -</code>
12141214+</li>
12151215+</ol>
12161216+<p>Easy as cake. You didn't have to settle for a partially
12171217+working commit, you didn't to deal with this &quot;stash&quot; thing,
12181218+<em>and</em> you didn't have to unfriend your friend. Treating each
12191219+branch as a directory just <em>feels</em> more intuitive, more
12201220+UNIX-y.</p>
12211221+<p>A few weeks later, you find yourself singing in praise of
12221222+worktrees, working on several things simultaneously. And at
12231223+the same time, cursing them for being a little ... clunky.</p>
12241224+<h3 id="what-makes-them-clunky">What makes them clunky?</h3>
12251225+<p>Worktrees are great at what they claim to do. They stay out
12261226+of the way when you need a checkout posthaste. However, as
12271227+you start using them regularly, you realize they are not as
12281228+flexible as <code>git checkout</code> or <code>git switch</code>.</p>
12291229+<h4 id="branch-hopping">Branch-hopping</h4>
12301230+<p>You can <code>git checkout &lt;branch&gt;</code> from anywhere within a git
12311231+repository. You can't &quot;jump&quot; to a worktree in the same
12321232+fashion. The closest you can get, is to run <code>git worktree list</code>, copy the path corresponding to your branch, and <code>cd</code>
12331233+into it.</p>
12341234+<p>Branch-hopping with the good ol' git-checkout:</p>
12351235+<pre><code class="language-bash"># anywhere, anytime
12361236+λ git checkout feature/is-ascii-octdigit
12371237+</code></pre>
12381238+<p>Meanwhile, in worktree world:</p>
12391239+<pre><code class="language-bash"># keeping these paths in your head is hard
12401240+λ git worktree list
12411241+~/worktrees/rustc/master eac6c33bc63 [master]
12421242+~/worktrees/rustc/improve-std-char-docs 94cba88553e [improve-std-char-docs]
12431243+~/worktrees/rustc/is-ascii-octdigit bc57be3af7a [feature/is-ascii-octdigit]
12441244+~/my/other/path/oh/god op57or3ns7n [fix/some-error]
12451245+12461246+λ cd ~/worktrees/rustc/is-ascii-octdigit
12471247+</code></pre>
12481248+<h4 id="branch-previewing">Branch-previewing</h4>
12491249+<p>You can &quot;preview&quot; branches with <code>git branch -v</code>. However, to
12501250+get an idea of what &quot;recent activity&quot; on a worktree looks
12511251+like, you might need some juggling. You can't glean much
12521252+info about a worktree in a jiffy.</p>
12531253+<p>Branch-previewing with the good ol' git-branch:</p>
12541254+<pre><code class="language-bash">λ git branch -v
12551255++ feature/is-ascii-octdigit bc57be3af7a introduce {char, u8}::is_ ...
12561256++ improve-std-char-docs 94cba88553e add whitespace in assert ...
12571257+* master eac6c33bc63 Auto merge of #100869 - n ...
12581258+</code></pre>
12591259+<p>Meanwhile in worktree wonderland:</p>
12601260+<pre><code>λ git worktree list
12611261+~/worktrees/rustc/master eac6c33bc63 [master]
12621262+~/worktrees/rustc/improve-std-char-docs 94cba88553e [improve-std-char-docs]
12631263+~/worktrees/rustc/is-ascii-octdigit bc57be3af7a [feature/is-ascii-octdigit]
12641264+12651265+# aha, so ../is-ascii-octdigit corresponds to `feature/is-ascii-octdigit`
12661266+λ git log feature/is-ascii-octdigit
12671267+bc57be3af7a introduce {char, u8}::is_ascii_octdigit
12681268+eac6c33bc63 Auto merge of #100869 - nnethercote:repl ...
12691269+b32223fec10 Auto merge of #100707 - dzvon:fix-typo, ...
12701270+aa857eb953e Auto merge of #100537 - petrochenkov:pic ...
12711271+12721272+# extra work to make the branch &lt;-&gt; worktree correspondence
12731273+</code></pre>
12741274+<h4 id="shell-completions">Shell completions</h4>
12751275+<p>Lastly, you can bank on shell completions to fill in your
12761276+branch whilst using <code>git checkout</code>. Worktrees have no such
12771277+conveniences.</p>
12781278+<p>We can mend these minor faults with fzf.</p>
12791279+<h3 id="unclunkifying-worktrees">Unclunkifying worktrees</h3>
12801280+<p>I'd suggest looking up
12811281+<a href="https://github.com/junegunn/fzf">fzf</a> (or
12821282+<a href="https://github.com/lotabout/skim">skim</a> or
12831283+<a href="https://github.com/jhawthorn/fzy">fzy</a>). These things make
12841284+it cake-easy to add interactivity to your shell. Onto fixing
12851285+the first minor fault, the inability to &quot;jump&quot; to a worktree
12861286+from anywhere within a git repository.</p>
12871287+<p>I have a little function called <code>gwj</code> which stands for &quot;git
12881288+worktree jump&quot;. The idea is to list all the worktrees,
12891289+select one with fzf, and <code>cd</code> to it upon selection:</p>
12901290+<pre><code class="language-bash">gwj () {
12911291+ local out
12921292+ out=$(git worktree list | fzf | awk '{print $1}')
12931293+ cd $out
12941294+}
12951295+</code></pre>
12961296+<p>That is all of it really. Head into a git repository:</p>
12971297+<pre><code class="language-bash"># here, &quot;master&quot; is a directory, which contains my main
12981298+# worktree: a checkout of the master branch on rust-lang/rust
12991299+λ cd ~/worktrees/rustc/master/library/core/src
13001300+λ # hack away
13011301+</code></pre>
13021302+<p>Preferably one with a few worktrees:</p>
13031303+<pre><code class="language-bash">λ git worktree list
13041304+~/worktrees/rustc/master eac6c33bc63 [master]
13051305+~/worktrees/rustc/improve-std-char-docs 94cba88553e [improve-std-char-docs]
13061306+~/worktrees/rustc/is-ascii-octdigit bc57be3af7a [feature/is-ascii-octdigit]
13071307+</code></pre>
13081308+<p>And hit <code>gwj</code> (pretend that the pipe, |, is your cursor):</p>
13091309+<pre><code class="language-bash">λ gwj
13101310+&gt; |
13111311+ 4/4
13121312+&gt; ~/worktrees/rustc/master eac6c33bc63 [master]
13131313+ ~/worktrees/rustc/improve-std-char-docs 94cba88553e [improve-std-char-docs]
13141314+ ~/worktrees/rustc/is-ascii-octdigit bc57be3af7a [feature/is-ascii-octdigit]
13151315+</code></pre>
13161316+<p>Approximately type in your branch of choice:</p>
13171317+<pre><code class="language-bash">λ gwj
13181318+&gt; docs|
13191319+ 4/4
13201320+&gt; ~/worktrees/rustc/improve-std-char-docs 94cba88553e [improve-std-char-docs]
13211321+</code></pre>
13221322+<p>And hit enter. You should find yourself in the selected
13231323+worktree.</p>
13241324+<p>Onward, to the next fault, lack of preview-bility. We can
13251325+utilize fzf's aptly named <code>--preview</code> flag, to, well,
13261326+preview our worktree before performing a selection:</p>
13271327+<pre><code class="language-bash">gwj () {
13281328+ local out
13291329+ out=$(
13301330+ git worktree list |
13311331+ fzf --preview='git log --oneline -n10 {2}' |
13321332+ awk '{print $1}'
13331333+ )
13341334+ cd $out
13351335+}
13361336+</code></pre>
13371337+<p>Once again, hit <code>gwj</code> inside a git repository with linked worktrees:</p>
13381338+<pre><code class="language-bash">λ gwj
13391339+╭─────────────────────────────────────────────────────────╮
13401340+│ eac6c33bc63 Auto merge of 100869 nnethercote:replace... │
13411341+│ b32223fec10 Auto merge of 100707 dzvon:fix-typo, r=d... │
13421342+│ aa857eb953e Auto merge of 100537 petrochenkov:picche... │
13431343+│ 3892b7074da Auto merge of 100210 mystor:proc_macro_d... │
13441344+│ db00199d999 Auto merge of 101249 matthiaskrgr:rollup... │
13451345+│ 14d216d33ba Rollup merge of 101240 JohnTitor:JohnTit... │
13461346+│ 3da66f03531 Rollup merge of 101236 thomcc:winfs-noze... │
13471347+│ 0620f6e90af Rollup merge of 101230 davidtwco:transla... │
13481348+│ c30c42ee299 Rollup merge of 101229 mgeisler:link-try... │
13491349+│ e5356712b9e Rollup merge of 101165 ldm0:drain_to_ite... │
13501350+╰─────────────────────────────────────────────────────────╯
13511351+&gt;
13521352+ 4/4
13531353+&gt; /home/np/worktrees/compiler/master eac6c...
13541354+ /home/np/worktrees/compiler/improve-std-char-docs 94cba...
13551355+ /home/np/worktrees/compiler/is-ascii-octdigit bc57b...
13561356+</code></pre>
13571357+<p>A fancy preview of the last 10 commits on the branch that
13581358+the selected worktree corresponds to. In other words, sight
13591359+for sore eyes. Our little script is already shaping up to be
13601360+useful, you hit <code>gwj</code>, browse through your worktrees,
13611361+preview each one and automatically <code>cd</code> to your selection.
13621362+But we are not done yet.</p>
13631363+<p>The last fault was lack shell completions. A quick review of
13641364+what a shell completion really does:</p>
13651365+<pre><code class="language-bash">λ git checkout f&lt;tab&gt;
13661366+feature/is-ascii-octdigit
13671367+fix/some-error
13681368+format-doc-tests
13691369+13701370+λ git checkout feat&lt;tab&gt;
13711371+13721372+λ git checkout feature/is-ascii-octdigit
13731373+</code></pre>
13741374+<p>Each time you hit &quot;tab&quot;, the shell produces a few
13751375+&quot;completion candidates&quot;, and once you have just a single
13761376+candidate left, the shell inserts that for you directly into
13771377+your edit line. Of course, this process varies from shell to
13781378+shell.</p>
13791379+<p>fzf narrows down your options as you type into the prompt,
13801380+but you still have to:</p>
13811381+<ol>
13821382+<li>Type <code>gwj</code>
13831383+</li>
13841384+<li>Hit enter
13851385+</li>
13861386+<li>Type out a query and narrow down your search
13871387+</li>
13881388+<li>Hit enter
13891389+</li>
13901390+</ol>
13911391+<p>We can speed that up a bit, have fzf narrow down the
13921392+candidates on startup, just like our shell does:</p>
13931393+<pre><code class="language-bash">gwj () {
13941394+ local out query
13951395+ query=&quot;${1:- }&quot;
13961396+ out=$(
13971397+ git worktree list |
13981398+ fzf --preview='git log --oneline -n10 {2}' --query &quot;$query&quot; -1 |
13991399+ awk '{print $1}'
14001400+ )
14011401+ cd $out
14021402+}
14031403+</code></pre>
14041404+<p>The change is extremely tiny, blink-and-you'll-miss-it kinda
14051405+tiny. We added a little <code>--query</code> flag, that allows you to
14061406+prefill the prompt, and the <code>-1</code> flag, that avoids the
14071407+interactive finder if only one match exists on startup:</p>
14081408+<pre><code class="language-bash"># skip through the fzf prompt:
14091409+λ gwj master
14101410+# cd -- ~/worktrees/rustc/master
14111411+14121412+# more than one option, we end up in the interactive finder
14131413+λ gwj improve
14141414+╭─────────────────────────────────────────────────────────╮
14151415+│ eac6c33bc63 Auto merge of 100869 nnethercote:replace... │
14161416+│ b32223fec10 Auto merge of 100707 dzvon:fix-typo, r=d... │
14171417+│ aa857eb953e Auto merge of 100537 petrochenkov:picche... │
14181418+╰─────────────────────────────────────────────────────────╯
14191419+&gt; improve
14201420+ 2/2
14211421+&gt; /home/np/worktrees/compiler/improve-const-perf eac6c...
14221422+ /home/np/worktrees/compiler/improve-std-char-docs 94cba...
14231423+</code></pre>
14241424+<p>Throw some error handling in there, hook up a similar script
14251425+to improve the UX of <code>git worktree remove</code>, go wild. A few
14261426+more helpers I've got:</p>
14271427+<pre><code class="language-bash"># gwa /path/to/branch-name
14281428+# creates a new branch and &quot;switches&quot; to it
14291429+function gwa () {
14301430+ git worktree add &quot;$1&quot; &amp;&amp; cd &quot;$1&quot;
14311431+}
14321432+14331433+alias gwls=&quot;git worktree list&quot;
14341434+</code></pre>
14351435+</description>
14361436+ <link>https://oppi.li/posts/curing_a_case_of_git-UX/</link>
14371437+ <pubDate>Sat, 03 Sep 2022 00:00:00 +0000</pubDate>
14381438+ <guid>https://oppi.li/posts/curing_a_case_of_git-UX/</guid>
14391439+ </item>
14401440+ <item>
14411441+ <title>programming on 34 keys</title>
14421442+ <description><p>Minimizing your keyboard layout is a slippery slope. A few
14431443+months ago, I built the
14441444+<a href="https://github.com/icyphox/ferricy">Ferricy</a>, a
14451445+34-key-split-ortho-ergo keyboard. The Ferricy is a fork of
14461446+the <a href="https://github.com/davidphilipbarr/Sweep/tree/main/Sweep%20Bling%20MX">Ferris Sweep MX
14471447+Bling</a>.</p>
14481448+<p><img src="https://cdn.oppi.li/otz.jpg" alt="The Ferricy, designed by icyphox" /></p>
14491449+<p>My daily use consists of a bit of prose and a lot of
14501450+program, my layout has evolved accordingly.</p>
14511451+<h1 id="base-layer">Base Layer</h1>
14521452+<p><img src="https://cdn.oppi.li/base.png" alt="Colemak with no mods" /></p>
14531453+<p>The base layer contains alphabets, four symbols and four
14541454+whitespace keys:</p>
14551455+<ul>
14561456+<li>Alphas: Stock Colemak, with no modifications whatsoever
14571457+</li>
14581458+<li>Symbols: <code>. , / ;</code>
14591459+</li>
14601460+<li>Whitespace: tab, space, enter, backspace (from left to
14611461+right)
14621462+</li>
14631463+</ul>
14641464+<h1 id="layers">Layers</h1>
14651465+<p>Keyboard input is complex and it is impossible to skirt
14661466+around it. You can either use a keyboard with enough keys to
14671467+supply all possible inputs (a mechanical burden), or you can
14681468+use firmware to supply all possible inputs (a cognitive
14691469+burden). Layers are a cognitive burden.</p>
14701470+<p>I use 3 layers, heavily inspired by
14711471+<a href="https://github.com/manna-harbour/miryoku">Miryoku</a>, but
14721472+tuned for programming. Excluding the base Colemak layer:</p>
14731473+<ul>
14741474+<li><code>NAV</code>: activated on holding <code>space</code> (left thumb)
14751475+</li>
14761476+<li><code>NUM</code>: activated on holding <code>tab</code> (left thumb)
14771477+</li>
14781478+<li><code>SYM</code>: activated on holding <code>enter</code> (right thumb)
14791479+</li>
14801480+</ul>
14811481+<h2 id="the-nav-layer">The <code>NAV</code> Layer</h2>
14821482+<p>As the name suggests, this layer is focused on navigation.
14831483+Arrow keys and the likes.</p>
14841484+<p><img src="https://cdn.oppi.li/nav.png" alt="NAV, on holding space" /></p>
14851485+<p>Using Vim and Colemak means you lose out on HJKL navigation.
14861486+However, on activating the <code>NAV</code> layer, the right home-row is
14871487+converted into arrow keys. In essence, by holding space, I
14881488+can navigate Vim with the home-row, or Firefox, or my PDF
14891489+reader. I no longer need to look for software that allows
14901490+Vim navigation keys, because it is baked into the firmware!</p>
14911491+<p>My Vim motions are not limited to HJKL. In fact, my Vim
14921492+motions are rarely HJKL. I tend to use <code>}</code> (next paragraph)
14931493+and <code>)</code> (next sentence) more often. As a result, these have
14941494+found their way into my <code>NAV</code> layer, over the likes of
14951495+<code>PgDown</code> and <code>End</code>. Having brackets at my index and middle
14961496+fingers is nice for programming too.</p>
14971497+<h2 id="the-sym-layer">The <code>SYM</code> Layer</h2>
14981498+<p><img src="https://cdn.oppi.li/sym.png" alt="SYM, on holding enter" /></p>
14991499+<p>This layer contains all the symbols that you would find by
15001500+hitting <code>Shift</code> and a key on the number row. Probably
15011501+noteworthy to Vim users: the symbols are arranged in the
15021502+form of a mirrored numpad for exactly one reason: to move
15031503+<code>$</code> to the left of <code>^</code>. It has always annoyed me that <code>$</code>
15041504+moves the cursor to the end of the line and <code>^</code> moves it to
15051505+the beginning, but their position on a typical number row
15061506+are reversed, 4 comes before 6.</p>
15071507+<h2 id="the-num-layer">The <code>NUM</code> layer</h2>
15081508+<p><img src="https://cdn.oppi.li/num.png" alt="NUM, on holding tab" /></p>
15091509+<p>Another deviation from Miryoku, the numpad just feels <em>right</em>
15101510+on my <em>right</em> hand.</p>
15111511+<h1 id="zmk-combos">ZMK Combos</h1>
15121512+<p>If you have been paying close attention, you might have
15131513+noticed that <code>escape</code> didn't make it to any layer. <code>escape</code>
15141514+is too crucial to put on a non-base layer, but at the same
15151515+time, not as important to deserve a place on the base layer.
15161516+That is where ZMK's combos come in. Combos let you tap any
15171517+number of keys, and combine them to form a single key. I
15181518+have combos set up for underscore, minus, escape and
15191519+caps-word (more on caps-word later):</p>
15201520+<p><img src="https://cdn.oppi.li/combos.png" alt="Combos are almost piano-like" /></p>
15211521+<h1 id="home-row-mods">Home-row Mods</h1>
15221522+<p>Inherited from Miryoku, I have home-row mods for activating
15231523+<code>Super</code>, <code>Alt</code>, <code>Shift</code>, <code>Ctrl</code> and <code>Hyper</code> (<code>Ctrl + Shift + Alt + Super</code>). The idea is to send <code>T</code> on tap and <code>Ctrl</code> on
15241524+hold. Home-row mods are fairly popular, so I'll not go into
15251525+the details.</p>
15261526+<p><img src="https://cdn.oppi.li/homerow.png" alt="Super, Alt, Shift, Ctrl, Hyper; on the left half, and mirrored on the right half" /></p>
15271527+<p><code>Hyper</code> bridges the gap between firmware and software. You
15281528+can never configure key combination that, opens Firefox, for
15291529+example, through firmware alone. However, with the <code>Hyper</code>
15301530+key, and some <code>sxhkd</code> magic, you can emulate that. Pressing
15311531+<code>Hyper + F</code> on a keyboard is just two keys, but the key
15321532+codes sent are <code>Ctrl + Shift + Alt + Super + F</code>. That key
15331533+combination is not intercepted by any application as a
15341534+shortcut, except for the following <code>sxhkd</code> stanza:</p>
15351535+<pre><code class="language-bash">super + alt + shift + ctrl + f
15361536+ xdotool search &quot;Mozilla Firefox&quot; windowactivate
15371537+</code></pre>
15381538+<p>Alternatively, you can intercept unused <code>F</code> keys: <code>F13</code>
15391539+through <code>F24</code>.</p>
15401540+<p>Home-row mods are mirrored on each half because it would be
15411541+impossible to hit <code>Ctrl + T</code> if not; they lie on the same
15421542+key.</p>
15431543+<h1 id="caps-word">Caps-word</h1>
15441544+<p>Caps-word is a clever caps-lock, built into ZMK. Typing out
15451545+constants such as <code>PORT</code> with home-row mods would look like
15461546+this:</p>
15471547+<ul>
15481548+<li>hold <code>e</code> (shift) on left hand, and tap <code>p</code> on right hand
15491549+</li>
15501550+<li>hold <code>e</code> (shift) on left hand, and tap <code>o</code> on right hand
15511551+</li>
15521552+<li>hold <code>s</code> (shift) on right hand, and tap <code>r</code> on left hand
15531553+</li>
15541554+<li>hold <code>s</code> (shift) on right hand, and tap <code>t</code> on left hand
15551555+</li>
15561556+</ul>
15571557+<p>This hold-alternate-hold dance gets tiring quickly. With
15581558+caps-word, however:</p>
15591559+<ul>
15601560+<li>toggle <code>caps_word</code>
15611561+</li>
15621562+<li>type out <code>p</code>, <code>o</code>, <code>r</code>, <code>t</code>
15631563+</li>
15641564+<li>hit a <em>break</em> character (space, enter will do)
15651565+</li>
15661566+<li>continue
15671567+</li>
15681568+</ul>
15691569+<p>Caps-word automatically disables capitalization upon
15701570+encountering a breaking character, (which are space, enter
15711571+or any modifier, by default) right in the firmware!</p>
15721572+<h1 id="findings">Findings</h1>
15731573+<p>34-keys has been reasonably comfortable to use, for both
15741574+prose and program. My palms do not move across the desk at
15751575+all, as I reach for keys. I mostly write Rust and Bash, and
15761576+my layout has evolved to accomodate special characters from
15771577+their grammars (angled brackets and hyphens, specifically).
15781578+If you are on a similar journey, I would suggest focusing on
15791579+accuracy and comfort over speed. Speed comes with time.</p>
15801580+</description>
15811581+ <link>https://oppi.li/posts/programming_on_34_keys/</link>
15821582+ <pubDate>Sun, 28 Aug 2022 00:00:00 +0000</pubDate>
15831583+ <guid>https://oppi.li/posts/programming_on_34_keys/</guid>
15841584+ </item>
15851585+ <item>
15861586+ <title>a reference counted afterlife</title>
15871587+ <description><p>I took interest in the Egyptian rendition of the afterlife
15881588+recently.</p>
15891589+<h3 id="parts-of-the-soul">Parts of the Soul</h3>
15901590+<p>Ancient Egyptians believed that the soul comprised of
15911591+several components:</p>
15921592+<ul>
15931593+<li><em>ren</em>
15941594+</li>
15951595+<li><em>ka</em>
15961596+</li>
15971597+<li><em>ib</em>
15981598+</li>
15991599+<li><em>ba</em>
16001600+</li>
16011601+<li><em>sheut</em>
16021602+</li>
16031603+</ul>
16041604+<p>Egyptians emphasized on preserving the different parts of
16051605+the soul. Mummification for example, served to preserve the
16061606+physical part of the soul. The other components have their
16071607+respective preservation strategies.</p>
16081608+<p>Of all of these bits, I find <em>ren</em>, which simply means
16091609+<em>name</em>, to be the most interesting. <em>Ba</em>, the human-headed
16101610+chicken that represents <em>personality</em>, is a close favourite.</p>
16111611+<p><em>Ren</em> is the name given to a person at birth. Egyptians
16121612+believed that this portion of the soul would continue to
16131613+live on for as long as it was spoken. If you were someone
16141614+worthy of continued existence, your name would be inscribed
16151615+all over the place. If you were the type to snatch away
16161616+bread from children, your name would be condemned from
16171617+memory, forgotten.</p>
16181618+<h3 id="garbage-collection">Garbage-collection</h3>
16191619+<p>The concept of <em>ren</em> seems to be perfectly analogous to
16201620+reference counted garbage-collection.</p>
16211621+<ul>
16221622+<li>A name (<em>ren</em>) is assigned to an object (person) on
16231623+initialization (at birth)
16241624+</li>
16251625+<li>Names are used to refer to objects
16261626+</li>
16271627+<li>Objects go out of existence when there are no more
16281628+references to them
16291629+</li>
16301630+</ul>
16311631+<p>The concept of <em>ren</em> seems to model human-memory. The
16321632+similarity with garbage-collection is now easily explained,
16331633+because garbage-collection models a program's memory.</p>
16341634+<p>Perhaps some cheeky Egyptian has attained immortality by
16351635+creating a <em>ren</em>-cycle.</p>
16361636+</description>
16371637+ <link>https://oppi.li/posts/a_reference_counted_afterlife/</link>
16381638+ <pubDate>Tue, 02 Aug 2022 00:00:00 +0000</pubDate>
16391639+ <guid>https://oppi.li/posts/a_reference_counted_afterlife/</guid>
16401640+ </item>
16411641+ <item>
16421642+ <title>lotus58</title>
16431643+ <description><p>Earlier this month, I decided that I would laugh at Indian
16441644+customs in the face by building a split-ergo mechanical
16451645+keyboard from scratch rather than purchasing a Moonlander.</p>
16461646+<p><img src="https://cdn.oppi.li/i8k.jpg" alt="The finished product" /></p>
16471647+<h2 id="sourcing-the-parts">Sourcing the parts</h2>
16481648+<p>If you, like me, live in India, you might find this section
16491649+useful. My approach to finding parts:</p>
16501650+<ul>
16511651+<li>Check reputed, local online stores
16521652+</li>
16531653+<li>Check physical hardware stores
16541654+</li>
16551655+<li>Import the part
16561656+</li>
16571657+</ul>
16581658+<h3 id="pcbs">PCBs</h3>
16591659+<p>This was by far the hardest component to procure.
16601660+Fabrication services have certain <em>capabilities</em>.
16611661+Capabilities are the limitations of a fabrication service.
16621662+For example, a service may be capable of drilling holes no
16631663+smaller than 0.3mm in diameter. Most sites have a
16641664+verification process to check if their capabilities meet
16651665+your design's requirements. I tried a few local PCB
16661666+fabrication services:</p>
16671667+<ul>
16681668+<li>Lion PCB: Capabilities did not meet my requirements
16691669+</li>
16701670+<li>PCBPower: Capabilities did not meet my requirements
16711671+</li>
16721672+<li>Circuitwala: Capabilities did not meet my requirements
16731673+</li>
16741674+</ul>
16751675+<p>I settled for JLCPCB, a Chinese service. PCBs themselves
16761676+were 16 USD, shipping was another 35 USD, and customs was
16771677+another 3.5K INR (ouch).</p>
16781678+<h3 id="case-material">Case material</h3>
16791679+<p>I don't really have a case for the Lotus58, it is more of a
16801680+&quot;plastic sandwich&quot;. I purchased acrylic plates from Robu.
16811681+Cheap, fast, solid, and customizable. I cannot recommend
16821682+Robu enough. A full set of plates (2 top plates and 2 bottom
16831683+plates) cost me about 500 INR. I also bought a pair of
16841684+laptop height raisers on Amazon to create a budget tenting
16851685+setup.</p>
16861686+<h3 id="electronics">Electronics</h3>
16871687+<p>You'll need a few rather specific electronic components such
16881688+as hotswap sockets and TRRS mounts, the rest are commonly
16891689+available:</p>
16901690+<ul>
16911691+<li>Hotswap sockets: StacksKB
16921692+</li>
16931693+<li>TRRS mounts (PJ 320A): StacksKB
16941694+</li>
16951695+<li>Diodes (1N4841): StacksKB
16961696+</li>
16971697+<li>M2 screws: StacksKB
16981698+</li>
16991699+<li>M2 spacers: ThinkRobotics
17001700+</li>
17011701+<li>Arduino Pro Micro: Robu
17021702+</li>
17031703+</ul>
17041704+<p>I skimped out on optional components such as OLEDs and
17051705+rotary encoders.</p>
17061706+<h3 id="switches-and-keycaps">Switches and Keycaps</h3>
17071707+<p>Arguably the most fun part of the build:</p>
17081708+<ul>
17091709+<li>Gateron Oil King switches: Rectangles
17101710+</li>
17111711+<li>DSA blanks: Meckeys
17121712+</li>
17131713+</ul>
17141714+<h2 id="building-the-keyboard">Building the keyboard</h2>
17151715+<p>The the build is extremely straightforward. Through hole
17161716+components are easy to solder. Be wary of component
17171717+placement and orientation. Check thrice, solder once. Few
17181718+debugging tips:</p>
17191719+<ul>
17201720+<li>if a single key does not actuate, check the hotswap for
17211721+poor soldering (reflow the joint), and the switch pins for
17221722+deformation during installation
17231723+</li>
17241724+<li>if an entire column or row activates on a single
17251725+key-press, a connection has been shorted
17261726+</li>
17271727+<li>if only some of the keys on a given row actuate, a diode
17281728+has been soldered on the wrong way
17291729+</li>
17301730+</ul>
17311731+<h2 id="the-typing-experience">The typing experience</h2>
17321732+<p>I decidede to give QWERTY the boot and learn Colemak along
17331733+with the new keyboard. The first few weeks were terrible
17341734+because I could neither type QWERTY nor Colemak, but I got
17351735+the hang of it pretty quickly. Typing websites do help, but
17361736+it is best to simply use it in your daily workflow. No site
17371737+can help you get accustomed to the various things you use
17381738+your keyboard for such as switching windows or navigating
17391739+vim/tmux.</p>
17401740+<h3 id="colemak">Colemak</h3>
17411741+<p>Alt layouts such as Colemak are definitely worth it. I find
17421742+that Colemak reduces finger movement a lot, a good portion
17431743+of the keys on the left hand are the same as QWERTY, it
17441744+is fairly easy to pick up as well.</p>
17451745+<h3 id="vim">Vim</h3>
17461746+<p>Using an alt layout means most programs with keyboard
17471747+shortcuts are not going to work as expected, <code>HJKL</code> on vim for
17481748+movements being one of them. I took the short route out by
17491749+creating a new layer with arrow keys on the home row:</p>
17501750+<pre><code>default homerow:
17511751+H N E I
17521752+17531753+&quot;nav&quot; layer:
17541754+&lt; v ^ &gt;
17551755+</code></pre>
17561756+<p>The remaining commands in vim are largely mnemonics.
17571757+Navigating with home-row arrow keys also means that I can
17581758+use &quot;HJKL&quot; globally, to scroll a website, for example.</p>
17591759+<h3 id="cutting-down-to-34-keys">Cutting down to 34 keys</h3>
17601760+<p>A couple months into my ergo journey, I realized that I
17611761+could get away by moving my fingers even lesser. I moved
17621762+modifiers such as <code>Super</code>, <code>Ctrl</code>, <code>Alt</code> and <code>Shift</code> to the
17631763+home-row as QMK Mod Taps. The rest of the keys are cleverly
17641764+placed in layers, not too much unlike the Miryoku layout.
17651765+Even for someone that writes Rust (a symbol-heavy grammar),
17661766+I find 34-keys to be sufficient.</p>
17671767+<h2 id="conclusion">Conclusion</h2>
17681768+<p>I have been bitten by the ergomech bug.</p>
17691769+<p><img src="https://cdn.oppi.li/XM3.jpg" alt="The Lotus58 in action" /></p>
17701770+</description>
17711771+ <link>https://oppi.li/posts/lotus58/</link>
17721772+ <pubDate>Mon, 13 Jun 2022 00:00:00 +0000</pubDate>
17731773+ <guid>https://oppi.li/posts/lotus58/</guid>
17741774+ </item>
17751775+ <item>
17761776+ <title>lightweight linting</title>
17771777+ <description><p><a href="https://tree-sitter.github.io/tree-sitter/using-parsers#pattern-matching-with-queries">Tree-sitter</a>
17781778+queries allow you to search for patterns in syntax trees,
17791779+much like a regex would, in text. Combine that with some Rust
17801780+glue to write simple, custom linters.</p>
17811781+<h3 id="tree-sitter-syntax-trees">Tree-sitter syntax trees</h3>
17821782+<p>Here is a quick crash course on syntax trees generated by
17831783+tree-sitter. Syntax trees produced by tree-sitter are
17841784+represented by S-expressions. The generated S-expression for
17851785+the following Rust code,</p>
17861786+<pre><code class="language-rust">fn main() {
17871787+ let x = 2;
17881788+}
17891789+</code></pre>
17901790+<p>would be:</p>
17911791+<pre><code class="language-scheme">(source_file
17921792+ (function_item
17931793+ name: (identifier)
17941794+ parameters: (parameters)
17951795+ body:
17961796+ (block
17971797+ (let_declaration
17981798+ pattern: (identifier)
17991799+ value: (integer_literal)))))
18001800+</code></pre>
18011801+<p>Syntax trees generated by tree-sitter have a couple of other
18021802+cool properties: they are <em>lossless</em> syntax trees. Given a
18031803+lossless syntax tree, you can regenerate the original source
18041804+code in its entirety. Consider the following addition to our
18051805+example:</p>
18061806+<pre><code class="language-rust"> fn main() {
18071807++ // a comment goes here
18081808+ let x = 2;
18091809+ }
18101810+</code></pre>
18111811+<p>The tree-sitter syntax tree preserves the comment, while the
18121812+typical abstract syntax tree wouldn't:</p>
18131813+<pre><code class="language-scheme"> (source_file
18141814+ (function_item
18151815+ name: (identifier)
18161816+ parameters: (parameters)
18171817+ body:
18181818+ (block
18191819++ (line_comment)
18201820+ (let_declaration
18211821+ pattern: (identifier)
18221822+ value: (integer_literal)))))
18231823+</code></pre>
18241824+<h3 id="tree-sitter-queries">Tree-sitter queries</h3>
18251825+<p>Tree-sitter provides a DSL to match over CSTs. These queries
18261826+resemble our S-expression syntax trees, here is a query to
18271827+match all line comments in a Rust CST:</p>
18281828+<pre><code class="language-scheme">(line_comment)
18291829+18301830+; matches the following rust code
18311831+; // a comment goes here
18321832+</code></pre>
18331833+<p>Neat, eh? But don't take my word for it, give it a go on the
18341834+<a href="https://tree-sitter.github.io/tree-sitter/playground">tree-sitter
18351835+playground</a>.
18361836+Type in a query like so:</p>
18371837+<pre><code class="language-scheme">; the web playground requires you to specify a &quot;capture&quot;
18381838+; you will notice the capture and the nodes it captured
18391839+; turn blue
18401840+(line_comment) @capture
18411841+</code></pre>
18421842+<p>Here's another to match <code>let</code> expressions that
18431843+bind an integer to an identifier:</p>
18441844+<pre><code class="language-scheme">(let_declaration
18451845+ pattern: (identifier)
18461846+ value: (integer_literal))
18471847+18481848+; matches:
18491849+; let foo = 2;
18501850+</code></pre>
18511851+<p>We can <em>capture</em> nodes into variables:</p>
18521852+<pre><code class="language-scheme">(let_declaration
18531853+ pattern: (identifier) @my-capture
18541854+ value: (integer_literal))
18551855+18561856+; matches:
18571857+; let foo = 2;
18581858+18591859+; captures:
18601860+; foo
18611861+</code></pre>
18621862+<p>And apply certain <em>predicates</em> to captures:</p>
18631863+<pre><code class="language-scheme">((let_declaration
18641864+ pattern: (identifier) @my-capture
18651865+ value: (integer_literal))
18661866+ (#eq? @my-capture &quot;foo&quot;))
18671867+18681868+; matches:
18691869+; let foo = 2;
18701870+18711871+; and not:
18721872+; let bar = 2;
18731873+</code></pre>
18741874+<p>The <code>#match?</code> predicate checks if a capture matches a regex:</p>
18751875+<pre><code class="language-scheme">((let_declaration
18761876+ pattern: (identifier) @my-capture
18771877+ value: (integer_literal))
18781878+ (#match? @my-capture &quot;foo|bar&quot;))
18791879+18801880+; matches both `foo` and `bar`:
18811881+; let foo = 2;
18821882+; let bar = 2;
18831883+</code></pre>
18841884+<p>Exhibit indifference, as a stoic programmer would, with the
18851885+<em>wildcard</em> pattern:</p>
18861886+<pre><code class="language-scheme">(let_declaration
18871887+ pattern: (identifier)
18881888+ value: (_))
18891889+18901890+; matches:
18911891+; let foo = &quot;foo&quot;;
18921892+; let foo = 42;
18931893+; let foo = bar;
18941894+</code></pre>
18951895+<p><a href="https://tree-sitter.github.io/tree-sitter/using-parsers#pattern-matching-with-queries">The
18961896+documentation</a>
18971897+does the tree-sitter query DSL more justice, but we now know
18981898+enough to write our first lint.</p>
18991899+<h3 id="write-you-a-tree-sitter-lint">Write you a tree-sitter lint</h3>
19001900+<p>Strings in <code>std::env</code> functions are error prone:</p>
19011901+<pre><code class="language-rust">std::env::remove_var(&quot;RUST_BACKTACE&quot;);
19021902+ // ^^^^ &quot;TACE&quot; instead of &quot;TRACE&quot;
19031903+</code></pre>
19041904+<p>I prefer this instead:</p>
19051905+<pre><code class="language-rust">// somewhere in a module that is well spellchecked
19061906+static BACKTRACE: &amp;str = &quot;RUST_BACKTRACE&quot;;
19071907+19081908+// rest of the codebase
19091909+std::env::remove_var(BACKTRACE);
19101910+</code></pre>
19111911+<p>Let's write a lint to find <code>std::env</code> functions that use
19121912+strings. Put aside the effectiveness of this lint for the
19131913+moment, and take a stab at writing a tree-sitter query. For
19141914+reference, a function call like so:</p>
19151915+<pre><code class="language-rust">remove_var(&quot;RUST_BACKTRACE&quot;)
19161916+</code></pre>
19171917+<p>Produces the following S-expression:</p>
19181918+<pre><code class="language-scheme">(call_expression
19191919+ function: (identifier)
19201920+ arguments: (arguments (string_literal)))
19211921+</code></pre>
19221922+<p>We are definitely looking for a <code>call_expression</code>:</p>
19231923+<pre><code class="language-scheme">(call_expression) @raise
19241924+</code></pre>
19251925+<p>Whose function name matches <code>std::env::var</code> or
19261926+<code>std::env::remove_var</code> at the very least (I know, I know,
19271927+this isn't the most optimal regex):</p>
19281928+<pre><code class="language-scheme">((call_expression
19291929+ function: (_) @fn-name) @raise
19301930+ (#match? @fn-name &quot;std::env::(var|remove_var)&quot;))
19311931+</code></pre>
19321932+<p>Let's turn that <code>std::</code> prefix optional:</p>
19331933+<pre><code class="language-scheme">((call_expression
19341934+ function: (_) @fn-name) @raise
19351935+ (#match? @fn-name &quot;(std::|)env::(var|remove_var)&quot;))
19361936+</code></pre>
19371937+<p>And ensure that <code>arguments</code> is a string:</p>
19381938+<pre><code class="language-scheme">((call_expression
19391939+ function: (_) @fn-name
19401940+ arguments: (arguments (string_literal)))
19411941+ (#match? @fn-name &quot;(std::|)env::(var|remove_var)&quot;))
19421942+</code></pre>
19431943+<h3 id="running-our-linter">Running our linter</h3>
19441944+<p>We could always plug our query into the web playground, but
19451945+let's go a step further:</p>
19461946+<pre><code class="language-bash">cargo new --bin toy-lint
19471947+</code></pre>
19481948+<p>Add <code>tree-sitter</code> and <code>tree-sitter-rust</code> to your
19491949+dependencies:</p>
19501950+<pre><code class="language-toml"># within Cargo.toml
19511951+[dependencies]
19521952+tree-sitter = &quot;0.20&quot;
19531953+19541954+[dependencies.tree-sitter-rust]
19551955+git = &quot;https://github.com/tree-sitter/tree-sitter-rust&quot;
19561956+</code></pre>
19571957+<p>Let's load in some Rust code to work with. As <a href="https://en.wikipedia.org/wiki/Self-reference">an ode to
19581958+Gödel</a>
19591959+(G<code>ode</code>l?), why not load in our linter itself:</p>
19601960+<pre><code class="language-rust">fn main() {
19611961+ let src = include_str!(&quot;main.rs&quot;);
19621962+}
19631963+</code></pre>
19641964+<p>Most tree-sitter APIs require a reference to a <code>Language</code>
19651965+struct, we will be working with Rust if you haven't
19661966+already guessed:</p>
19671967+<pre><code class="language-rust">use tree_sitter::Language;
19681968+19691969+let rust_lang: Language = tree_sitter_rust::language();
19701970+</code></pre>
19711971+<p>Enough scaffolding, let's parse some Rust:</p>
19721972+<pre><code class="language-rust">use tree_sitter::Parser;
19731973+19741974+let mut parser = Parser::new();
19751975+parser.set_language(rust_lang).unwrap();
19761976+19771977+let parse_tree = parser.parse(&amp;src, None).unwrap();
19781978+</code></pre>
19791979+<p>The second argument to <code>Parser::parse</code> may be of interest.
19801980+Tree-sitter has this cool feature that allows for quick
19811981+reparsing of existing parse trees if they contain edits. If
19821982+you do happen to want to reparse a source file, you can pass
19831983+in the old tree:</p>
19841984+<pre><code class="language-rust">// if you wish to reparse instead of parse
19851985+old_tree.edit(/* redacted */);
19861986+19871987+// generate shiny new reparsed tree
19881988+let new_tree = parser.parse(&amp;src, Some(old_tree)).unwrap()
19891989+</code></pre>
19901990+<p>Anyhow (<a href="http://github.com/dtolnay/anyhow">hah!</a>), now that we have a parse tree, we can inspect it:</p>
19911991+<pre><code class="language-rust">println!(&quot;{}&quot;, parse_tree.root_node().to_sexp());
19921992+</code></pre>
19931993+<p>Or better yet, run a query on it:</p>
19941994+<pre><code class="language-rust">use tree_sitter::Query;
19951995+19961996+let query = Query::new(
19971997+ rust_lang,
19981998+ r#&quot;
19991999+ ((call_expression
20002000+ function: (_) @fn-name
20012001+ arguments: (arguments (string_literal))) @raise
20022002+ (#match? @fn-name &quot;(std::|)env::(var|remove_var)&quot;))
20032003+ &quot;#
20042004+)
20052005+.unwrap();
20062006+</code></pre>
20072007+<p>A <code>QueryCursor</code> is tree-sitter's way of maintaining state as
20082008+we iterate through the matches or captures produced by
20092009+running a query on the parse tree. Observe:</p>
20102010+<pre><code class="language-rust">use tree_sitter::QueryCursor;
20112011+20122012+let mut query_cursor = QueryCursor::new();
20132013+let all_matches = query_cursor.matches(
20142014+ &amp;query,
20152015+ parse_tree.root_node(),
20162016+ src.as_bytes(),
20172017+);
20182018+</code></pre>
20192019+<p>We begin by passing our query to the cursor, followed by the
20202020+&quot;root node&quot;, which is another way of saying, &quot;start from the
20212021+top&quot;, and lastly, the source itself. If you have already
20222022+taken a look at the C API, you will notice that the last
20232023+argument, the source (known as the <code>TextProvider</code>), is not
20242024+required. The Rust bindings seem to require this argument to
20252025+provide predicate functionality such as <code>#match?</code> and
20262026+<code>#eq?</code>.</p>
20272027+<p>Do something with the matches:</p>
20282028+<pre><code class="language-rust">// get the index of the capture named &quot;raise&quot;
20292029+let raise_idx = query.capture_index_for_name(&quot;raise&quot;).unwrap();
20302030+20312031+for each_match in all_matches {
20322032+ // iterate over all captures called &quot;raise&quot;
20332033+ // ignore captures such as &quot;fn-name&quot;
20342034+ for capture in each_match
20352035+ .captures
20362036+ .iter()
20372037+ .filter(|c| c.idx == raise_idx)
20382038+ {
20392039+ let range = capture.node.range();
20402040+ let text = &amp;src[range.start_byte..range.end_byte];
20412041+ let line = range.start_point.row;
20422042+ let col = range.start_point.column;
20432043+ println!(
20442044+ &quot;[Line: {}, Col: {}] Offending source code: `{}`&quot;,
20452045+ line, col, text
20462046+ );
20472047+ }
20482048+}
20492049+</code></pre>
20502050+<p>Lastly, add the following line to your source code, to get
20512051+the linter to catch something:</p>
20522052+<pre><code class="language-rust">env::remove_var(&quot;RUST_BACKTRACE&quot;);
20532053+</code></pre>
20542054+<p>And <code>cargo run</code>:</p>
20552055+<pre><code class="language-shell">λ cargo run
20562056+ Compiling toy-lint v0.1.0 (/redacted/path/to/toy-lint)
20572057+ Finished dev [unoptimized + debuginfo] target(s) in 0.74s
20582058+ Running `target/debug/toy-lint`
20592059+[Line: 40, Col: 4] Offending source code: `env::remove_var(&quot;RUST_BACKTRACE&quot;)`
20602060+</code></pre>
20612061+<p>Thank you tree-sitter!</p>
20622062+<h3 id="bonus">Bonus</h3>
20632063+<p>Keen readers will notice that I avoided <code>std::env::set_var</code>.
20642064+Because <code>set_var</code> is called with two arguments, a &quot;key&quot; and
20652065+a &quot;value&quot;, unlike <code>env::var</code> and <code>env::remove_var</code>. As a
20662066+result, it requires more juggling:</p>
20672067+<pre><code class="language-scheme">((call_expression
20682068+ function: (_) @fn-name
20692069+ arguments: (arguments . (string_literal)? . (string_literal) .)) @raise
20702070+ (#match? @fn-name &quot;(std::|)env::(var|remove_var|set_var)&quot;))
20712071+</code></pre>
20722072+<p>The interesting part of this query is the humble <code>.</code>, the
20732073+<em>anchor</em> operator. Anchors help constrain child nodes in
20742074+certain ways. In this case, it ensures that we match exactly
20752075+two <code>string_literal</code>s who are siblings or exactly one
20762076+<code>string_literal</code> with no siblings. Unfortunately, this query
20772077+also matches the following invalid Rust code:</p>
20782078+<pre><code class="language-rust">// remove_var accepts only 1 arg!
20792079+std::env::remove_var(&quot;RUST_BACKTRACE&quot;, &quot;1&quot;);
20802080+</code></pre>
20812081+<h3 id="notes">Notes</h3>
20822082+<p>All-in-all, the query DSL does a great job in lowering the
20832083+bar to writing language tools. The knowledge gained from
20842084+mastering the query DSL can be applied to other languages
20852085+that have tree-sitter grammars too. This query
20862086+detects <code>to_json</code> methods that do not accept additional
20872087+arguments, in Ruby:</p>
20882088+<pre><code class="language-scheme">((method
20892089+ name: (identifier) @fn
20902090+ !parameters)
20912091+ (#is? @fn &quot;to_json&quot;))
20922092+</code></pre>
20932093+</description>
20942094+ <link>https://oppi.li/posts/lightweight_linting/</link>
20952095+ <pubDate>Wed, 26 Jan 2022 00:00:00 +0000</pubDate>
20962096+ <guid>https://oppi.li/posts/lightweight_linting/</guid>
20972097+ </item>
20982098+ <item>
20992099+ <title>novice nix: flake templates</title>
21002100+ <description><p>Flakes are very handy to setup entirely pure,
21012101+project-specific dependencies (not just dependencies, but
21022102+build steps, shell environments and more) in a declarative
21032103+way. Writing Flake expressions can get repetitive though,
21042104+oftentimes, you'd much rather start off with a skeleton.
21052105+Luckily, <code>nix</code> already supports templates!</p>
21062106+<p>You might already be familiar with <code>nix flake init</code>, that
21072107+drops a &quot;default&quot; flake expression into your current working
21082108+directory. If you head over to the manpage:</p>
21092109+<pre><code class="language-bash">nix flake init --help
21102110+</code></pre>
21112111+<p>You will read that <code>nix flake init</code> creates a flake using
21122112+the &quot;default template&quot;. Additionally, you can create a flake
21132113+from a specific template by passing the <code>-t</code> flag. Where
21142114+does this default originate from?</p>
21152115+<h2 id="flake-registries">Flake Registries</h2>
21162116+<p>Quick detour into registries! Registries are a way to alias
21172117+popular flakes using identifiers:</p>
21182118+<pre><code class="language-bash"># list a few predefined registries
21192119+$ nix registry list
21202120+. . .
21212121+global flake:nixpkgs github:NixOS/nixpkgs
21222122+global flake:patchelf github:NixOS/patchelf
21232123+global flake:nix-serve github:edolstra/nix-serve
21242124+global flake:templates github:NixOS/templates
21252125+global flake:nickel github:tweag/nickel
21262126+. . .
21272127+21282128+# you can do
21292129+$ nix flake show nickel
21302130+21312131+# instead of
21322132+$ nix flake show github:tweag/nickel
21332133+21342134+# which is short for
21352135+$ nix flake show git+https://github.com/tweag/nickel
21362136+</code></pre>
21372137+<p>You might notice a registry called <code>templates</code> aliased to
21382138+<code>github:NixOS/templates</code>. Take a peek with <code>nix flake show</code>:</p>
21392139+<pre><code class="language-bash">$ nix flake show templates
21402140+github:NixOS/templates/79f48a7b822f35c068c5e235da2e9fbd154cecee
21412141+├───defaultTemplate: template: A very basic flake
21422142+└───templates
21432143+ ├───bash-hello: template: An over-engineered Hello World in bash
21442144+ ├───c-hello: template: An over-engineered Hello World in C
21452145+ ├───rust-web-server: template: A Rust web server including a NixOS module
21462146+ ├───simpleContainer: template: A NixOS container running apache-httpd
21472147+ └───trivial: template: A very basic flake
21482148+</code></pre>
21492149+<p>Aha! There is a flake output called <code>defaultTemplate</code>. This
21502150+is the template being sourced when you run <code>nix flake init</code>.
21512151+Astute readers may conclude the following:</p>
21522152+<pre><code class="language-bash">$ nix flake init
21532153+21542154+# is equivalent to
21552155+$ nix flake init -t templates#defaultTemplate
21562156+21572157+# is equivalent to
21582158+$ nix flake init -t github:NixOS/templates#defaultTemplate
21592159+21602160+# which is short for
21612161+$ nix flake init -t git+https://github.com/NixOS/templates#defaultTemplate
21622162+</code></pre>
21632163+<p>Similarly, the other templates can be accessed via:</p>
21642164+<pre><code class="language-bash">$ nix flake init -t templates#c-hello
21652165+$ nix flake init -t templates#simpleContainer
21662166+# I think you get the drift ...
21672167+</code></pre>
21682168+<h2 id="rolling-your-own-templates">Rolling your own templates</h2>
21692169+<p>Alright, so all we need to do is:</p>
21702170+<ul>
21712171+<li>create a flake with a <code>templates</code> output
21722172+</li>
21732173+<li>populate our template directories with content
21742174+</li>
21752175+<li>(<strong>optionally</strong>) alias our custom templates flake to an
21762176+identifier using registries, for easier access
21772177+</li>
21782178+</ul>
21792179+<p>Start off by creating a directory to store your templates in
21802180+(we will be converting this to a registry later):</p>
21812181+<pre><code class="language-bash">$ mkdir ~/mytemplates
21822182+</code></pre>
21832183+<p>A flake that exposes a &quot;template&quot; as its output looks
21842184+something like this:</p>
21852185+<pre><code class="language-nix"># inside ~/mytemplates/flake.nix
21862186+21872187+{
21882188+ description = &quot;Pepper's flake templates&quot;;
21892189+21902190+ outputs = { self, ... }: {
21912191+ templates = {
21922192+ latex-report = {
21932193+ path = ./latex-report-template;
21942194+ description = &quot;A latex whitepaper project&quot;;
21952195+ };
21962196+ rust-hello = {
21972197+ path = ./rust-hello-template;
21982198+ description = &quot;Simple Hello World in Rust&quot;;
21992199+ };
22002200+ };
22012201+ };
22022202+}
22032203+</code></pre>
22042204+<p>The <code>path</code> attribute to each template is what gets copied
22052205+over when you initialize a flake. Running <code>nix flake init -t .#latex-report</code> will initialize the current directory with
22062206+the contents of <code>./latex-report-template</code> (we are yet to
22072207+populate these directories).</p>
22082208+<p>The output of <code>nix flake show</code> should be something like:</p>
22092209+<pre><code class="language-bash">$ nix flake show
22102210+path:/home/np/code/nix-stuff/template-tests?narHash=sha256-{...}
22112211+└───templates
22122212+ ├───latex-report: template: A latex whitepaper project
22132213+ └───rust-hello: template: Simple Hello World in Rust
22142214+</code></pre>
22152215+<p>Populate your template directories with content, here are my
22162216+template directories for example:</p>
22172217+<pre><code class="language-bash">$ tree mytemplates
22182218+mytemplates/
22192219+├── flake.nix
22202220+├── latex-report-template
22212221+│ ├── flake.nix
22222222+│ ├── makefile
22232223+│ └── src
22242224+│ ├── meta.sty
22252225+│ └── report.tex
22262226+└── rust-hello-template
22272227+ ├── Cargo.toml
22282228+ ├── flake.nix
22292229+ └── src
22302230+ └── main.rs
22312231+</code></pre>
22322232+<p>And that's it! Start using your templates with:</p>
22332233+<pre><code class="language-bash">$ nix flake init -t ~/mytemplates#rust-hello
22342234+$ tree .
22352235+.
22362236+├── Cargo.toml
22372237+├── flake.nix
22382238+└── src
22392239+ └── main.rs
22402240+</code></pre>
22412241+<p>To avoid writing <code>~/mytemplates</code> each time, simply alias it
22422242+to a registry:</p>
22432243+<pre><code class="language-bash"># alias it to `biscuits`
22442244+$ nix registry add biscuits ~/mytemplates
22452245+22462246+# you will see it listed under `user` registries
22472247+$ nix registry list
22482248+. . .
22492249+user flake:biscuits path:/home/np/template-tests
22502250+. . .
22512251+22522252+$ nix flake init -t biscuits#latex-report
22532253+</code></pre>
22542254+<h2 id="extending-the-official-templates">Extending the official templates</h2>
22552255+<p>I personally, would like the <code>biscuits</code> registry to include
22562256+not just my homemade templates, but also the templates from
22572257+<code>NixOS/templates</code> (and maybe a couple of other repositories
22582258+in the wild):</p>
22592259+<pre><code class="language-nix"> {
22602260+ description = &quot;Pepper's flake templates&quot;;
22612261+22622262++ inputs = {
22632263++ official-templates.url = github:NixOS/templates;
22642264++ other-templates.url = github:some-other/templates;
22652265++ };
22662266+22672267+ outputs = { self, official-templates, other-templates ... }: {
22682268+22692269+ templates = {
22702270+ latex-report = {
22712271+ path = ./latex-report-template;
22722272+ description = &quot;A latex whitepaper project&quot;;
22732273+ };
22742274+ rust-hello = {
22752275+ path = ./rust-hello-template;
22762276+ description = &quot;Simple Hello World in Rust, with overloaded Rust toolchain&quot;;
22772277+ };
22782278+ }
22792279++ // official-templates.templates
22802280++ // other-templates.templates;
22812281+22822282+ };
22832283+ }
22842284+</code></pre>
22852285+<p>Running <code>nix flake show biscuits</code> will now list templates
22862286+from the <code>biscuits</code> registry as well as the ones from
22872287+<code>NixOS/templates</code>. Ensure that the names don't collide
22882288+though.</p>
22892289+</description>
22902290+ <link>https://oppi.li/posts/novice_nix:_flake_templates/</link>
22912291+ <pubDate>Tue, 05 Oct 2021 00:00:00 +0000</pubDate>
22922292+ <guid>https://oppi.li/posts/novice_nix:_flake_templates/</guid>
22932293+ </item>
22942294+ <item>
22952295+ <title>SDL2 devlog</title>
22962296+ <description><p>I have been working on an editor for the <a href="https://git.peppe.rs/graphics/obi/about">One Bit
22972297+Image</a> file format
22982298+in Rust and SDL2. This entry in my blog follows my progress
22992299+on the editor. The days are listed in reverse chronological
23002300+order, begin from the bottom, if this is your first time on
23012301+this page.</p>
23022302+<h3 id="day-20">Day 20</h3>
23032303+<p>More <code>lisp</code> stuff! I added a new brush, for rectangular
23042304+selections. While selection doesn't do much on its own, the
23052305+selected area can be passed onto a <code>lisp</code> procedure, for
23062306+example, a procedure to draw horizontal black and white
23072307+lines:</p>
23082308+<p><img src="https://cdn.oppi.li/frU.mp4" alt="Day 20" /></p>
23092309+<h3 id="day-19">Day 19</h3>
23102310+<p>Attempted <a href="https://peppe.rs/art/conduit.png">some isometric
23112311+art</a> within the editor.
23122312+The angles displayed alongside the line brush are handly,
23132313+however, having only a rectangular grid did not help. I
23142314+implemented an isometric grid today. Isometric grids in
23152315+pixel art differ in that the tangent of the isometric angle
23162316+is exactly 0.5! For every pixel down, you go exactly two
23172317+pixels sideways. The math works out really well in the
23182318+drawing procedures too, dealing with floating points is a
23192319+pain.</p>
23202320+<p><img src="https://cdn.oppi.li/1Kb.png" alt="Day 19" /></p>
23212321+<h3 id="day-18">Day 18</h3>
23222322+<p>I added basic support for guides, they can be added and
23232323+activated from the <code>lisp</code> REPL. Another long standing
23242324+improvement I wanted to make was reworking the pixmap
23252325+drawing procedure. The old procedure draws a square for each
23262326+pixel in the pixmap, coloured according to its value in the
23272327+pixmap. Naturally, this means, for an <strong>NxN</strong> pixmap, there
23282328+are <strong>N²</strong> calls to SDL! I reworked this procedure to
23292329+compress each line of the pixmap using RLE (run length
23302330+encoding), and call out to SDL for each run in the line.
23312331+This drastically improved drawing speeds on larger grids.
23322332+The following is a comparison between the two procedures,
23332333+the leftmost picture is the rendered image, the middle
23342334+picture is the optimized drawing procedure (draws each run
23352335+instead of pixel), and the right most picture is the
23362336+primitive drawing procedure (draws each pixel):</p>
23372337+<p><img src="https://cdn.oppi.li/U4B.png" alt="Day 18" /></p>
23382338+<h3 id="day-17">Day 17</h3>
23392339+<p>I decided to give the text-only statusline a touch up, by
23402340+adding a active color and dither level preview. Aligning the
23412341+&quot;widget&quot; to the right of statusline involved a lot more than
23422342+I thought, so I created a ghetto CSS-like rectangle
23432343+placement system to position containers inside containers:</p>
23442344+<pre><code class="language-rust">// roughly something like this
23452345+let statusline =
23462346+ Container::new(Offset::Left(0), Offset::Bottom(40))
23472347+ .width(Size::Max)
23482348+ .height(Size::Absolute(20));
23492349+23502350+let mut primary = Container::uninit()
23512351+ .width(Size::Absolute(16))
23522352+ .height(Size::Absolute(16));
23532353+23542354+container.place(
23552355+ &amp;mut padding_box,
23562356+ HorAlign::Right,
23572357+ VertAlign::Center
23582358+);
23592359+</code></pre>
23602360+<p>The result (brush preview on the bottom right):</p>
23612361+<p><img src="https://cdn.oppi.li/OtU.mp4" alt="Day 17" /></p>
23622362+<h3 id="day-16">Day 16</h3>
23632363+<p>The embedded lisp is coming along nicely, users can load
23642364+a custom <code>rc.lisp</code>, which is evaluated on startup. To
23652365+disable to grid on start, for example:</p>
23662366+<pre><code class="language-scheme">;;; rc.lisp
23672367+(toggle-grid)
23682368+</code></pre>
23692369+<p>Some aliases to switch between brushes:</p>
23702370+<pre><code class="language-scheme">;;; rc.lisp
23712371+(define (brush kind)
23722372+ (cond
23732373+ ((eq? kind 'f) (brush-fill))
23742374+ ((eq? kind 'c) (brush-circle))
23752375+ ((eq? kind 'l) (brush-line))
23762376+ ((eq? kind 'l+) (brush-line-extend))
23772377+ (else (brush-circle))))
23782378+</code></pre>
23792379+<p>The following script draws a straight line along a given
23802380+axis, at a given distance from the canvas boundary:</p>
23812381+<p><img src="https://cdn.oppi.li/b3i.mp4" alt="Day 16" /></p>
23822382+<h3 id="day-15">Day 15</h3>
23832383+<p>I began writing a standard library for the lisp, in lisp. It
23842384+includes basic list operations: <code>car</code>, <code>cdr</code>, <code>null?</code>,
23852385+<code>list</code>, higher order functions: <code>map</code>, <code>filter</code>, <code>fold</code>:</p>
23862386+<pre><code class="language-lisp">(define (member? item ls)
23872387+ (fold #f
23882388+ (lambda (acc x) (or acc (eq? item x)))
23892389+ ls))
23902390+</code></pre>
23912391+<h3 id="day-14">Day 14</h3>
23922392+<p>I attempted a <a href="https://peppe.rs/art/ramen_noodles.png">small art
23932393+piece</a> using the
23942394+editor, while it was largely usable, I felt a certain lack
23952395+of feedback. The brushes just didn't relay as much info as
23962396+I'd have liked, for example, the approximate points of the
23972397+line or the angle made by the line against the x-axis.
23982398+Unfortunately, the existing infrastructure around brushes
23992399+and line drawing didn't easily allow for this either. I went
24002400+ahead and reimplemented brushes, and added a new flood fill
24012401+brush too:</p>
24022402+<p><img src="https://cdn.oppi.li/8q.mp4" alt="Day 14" /></p>
24032403+<h3 id="day-13">Day 13</h3>
24042404+<p>I added a few more forms to the <code>lisp</code> evaluator. It handles
24052405+recursion, definitions, variable mutation and more. The
24062406+prelude contains 20 subroutines so far, including
24072407+comparision and logic operators. The REPL interface on the
24082408+SDL side requires some UX tweaks; environment based
24092409+completion, readline motions sound doable.</p>
24102410+<p><img src="https://cdn.oppi.li/u3.mp4" alt="Day 13" /></p>
24112411+<h3 id="day-12">Day 12</h3>
24122412+<p>I lifted most of
24132413+<a href="https://github.com/murarth/ketos">murarth/ketos</a> into the
24142414+editor. <code>ketos</code>'s implementation of <code>lisp</code> is too vast for
24152415+my use case. For example, the editor does not need data
24162416+types to handle raw strings or byte strings. I have got a
24172417+basic evaluator running inside the SDL2 context (notice the
24182418+<code>lisp</code> REPL at the bottom of the window). Over the
24192419+following days, I intend to create a set of prelude
24202420+functions to manipulate the pixmap. Users can implement
24212421+their own brushes, dithering patterns, keybinds and more
24222422+(hopefully).</p>
24232423+<p><img src="https://cdn.oppi.li/y0.mp4" alt="Day 12" /></p>
24242424+<h3 id="day-11">Day 11</h3>
24252425+<p>I intend to supplement the editor with scripting language
24262426+and an inbuilt REPL for the same. I began by implementing a
24272427+text box widget from scratch, with history and readline like
24282428+editing:</p>
24292429+<p><img src="https://cdn.oppi.li/Mh.mp4" alt="Day 11" /></p>
24302430+<h3 id="day-10">Day 10</h3>
24312431+<p>I started reading up on dithering methods and half-toning, I
24322432+wanted to create a dithering brush that would automatically
24332433+produce popular dithering patterns. The method that caught
24342434+my eye (and also the one used most often in pixel art), was
24352435+Bayer's ordered dithering. When applied to a black and white
24362436+image, each pixel, based on its intensity, is mapped to a
24372437+4x4 grid of pixels. A completely empty (completely black)
24382438+4x4 grid represents zero intensity, and a filled 4x4 grid
24392439+represents full intensity. Bayer's ordered dithering can
24402440+produce 15 steps of intensity between zero and full (by
24412441+switching on exactly 1 pixel more at each level), thus,
24422442+being able to draw 17 &quot;shades&quot; from white to black. Creating
24432443+a dithering brush from here was fairly trivial. Our pixmap
24442444+is supposed to represent the final dithered image, it must
24452445+be divided into 4x4 grids. Each grid is colored based on the
24462446+intensity of the brush passing over it:</p>
24472447+<p><img src="https://cdn.oppi.li/Mn.png" alt="Day 10" /></p>
24482448+<h3 id="day-9">Day 9</h3>
24492449+<p>I started working towards an interface. I like the idea of a
24502450+largely read-only HUD, i. e., an interface that simply
24512451+describes the state of the application. Changes to this
24522452+state are initiated via keybinds or text commands. I am
24532453+proud of the symmetry indicator; <code>-</code> for horizontal
24542454+symmetry, <code>|</code> for vertical symmetry, <code>+</code> for radial
24552455+symmetry.</p>
24562456+<p><img src="https://cdn.oppi.li/hx.png" alt="Day 9" /></p>
24572457+<h3 id="day-8">Day 8</h3>
24582458+<p>One of my favourite features of GIMP was symmetric editing.
24592459+I added some coordinate geometry primitives to my pixmap
24602460+abstraction, allowing for mirroring and reflecting figures
24612461+about lines or points. The result was an ergonomic function
24622462+that applies symmetry to any painting operation, (undo/redo
24632463+works as expected):</p>
24642464+<pre><code class="language-rust">let line = self.pixmap.get_line(start, end);
24652465+let sym_line = self.symmetry.apply(&amp;line);
24662466+for point on line.extend(sym_line) {
24672467+ // draw to window
24682468+}
24692469+</code></pre>
24702470+<p><img src="https://cdn.oppi.li/B1.mp4" alt="Day 8" /></p>
24712471+<h3 id="day-7">Day 7</h3>
24722472+<p>Bresenham saves the day again! This time, I implemented his
24732473+line drawing algorithm, to, well, draw lines. Each point on
24742474+the line is then &quot;buffed&quot; based on the active brush size.
24752475+Today's changes fit in very well with the undo system and
24762476+the brush size feature. Creating the right abstractions, one
24772477+at a time :)</p>
24782478+<p><img src="https://cdn.oppi.li/xt.mp4" alt="Day 7" /></p>
24792479+<h3 id="day-6">Day 6</h3>
24802480+<p>I extended Bresenham's algorithm to draw not just circle
24812481+outlines, but also generate their fills. Unlike Bresenham's
24822482+algorithm, this variant generates points for two quadrants
24832483+at once, these points are mirrored over the dividing axis to
24842484+generate the other two quadrants.</p>
24852485+<p><img src="https://cdn.oppi.li/f3.png" alt="Day 6" /></p>
24862486+<h3 id="day-5">Day 5</h3>
24872487+<p>I discovered and implemented Bresenham's algorithm for
24882488+efficient circle drawing. The algorithm allowed for sized
24892489+circular brushes, something I really liked from GIMP. Very
24902490+convenient that the Wikipedia page for Bresenham's algorithm
24912491+also includes a section about optimizing for integer based
24922492+arithmetic. I managed to abstract out another giant
24932493+component of the application, the pixmap. Any image is just
24942494+a grid of pixels (a pixmap), where the pixel's value is
24952495+decided by the application (1-bit in my case). I could
24962496+potentially extend the application to a 24-bit image editor!</p>
24972497+<p><img src="https://cdn.oppi.li/Kh.mp4" alt="Day 5" /></p>
24982498+<h3 id="day-4">Day 4</h3>
24992499+<p>I created a generic &quot;undo stack&quot; data structure that allows
25002500+for infinite &quot;undos&quot; and &quot;redos&quot;. Every modification
25012501+operation to the grid is persisted to the application state.
25022502+A couple of keybinds allow the user to revert and re-apply
25032503+these operations! I expect abstracting this component will
25042504+come in handy down the line.</p>
25052505+<p><img src="https://cdn.oppi.li/w5.mp4" alt="Day 4" /></p>
25062506+<h3 id="day-3">Day 3</h3>
25072507+<p>I implemented the bare minimum required to call the program
25082508+an &quot;editor&quot;. The application displays a grid, tracks mouse
25092509+events, paints white to the canvas on left click, and black
25102510+to the canvas on right click. I created a make-shift MVC
25112511+architecture à la Elm in Rust.</p>
25122512+<p><img src="https://cdn.oppi.li/GF.mp4" alt="Day 3" /></p>
25132513+<h3 id="day-2">Day 2</h3>
25142514+<p>I started figuring out event handling today. Implemented a
25152515+couple of keybinds to zoom in/out of the drawing area.
25162516+Conversions of SDL2 coordinates (measured in signed 32 bit
25172517+integers) to my internal &quot;drawing area&quot; coordinates
25182518+(measured in unsigned 32 bit integers) is very annoying.
25192519+Hopefully the unchecked conversions won't haunt me later.</p>
25202520+<p><img src="https://cdn.oppi.li/L4.mp4" alt="Day 2" /></p>
25212521+<h3 id="day-1">Day 1</h3>
25222522+<p>Getting started with Rust and SDL2 is very straightforward.
25232523+The <code>rust-sdl2</code> library contains some detailed examples that
25242524+allowed me to get all the way to drawing a grid from a
25252525+<code>Vec&lt;bool&gt;</code>:</p>
25262526+<p><img src="https://cdn.oppi.li/Ma.png" alt="Day 1" /></p>
25272527+</description>
25282528+ <link>https://oppi.li/posts/SDL2_devlog/</link>
25292529+ <pubDate>Sun, 11 Apr 2021 00:00:00 +0000</pubDate>
25302530+ <guid>https://oppi.li/posts/SDL2_devlog/</guid>
25312531+ </item>
25322532+ <item>
25332533+ <title>self-hosting git</title>
25342534+ <description><p>Earlier this week, I began migrating my repositories from
25352535+Github to <a href="https://git.zx2c4.com/cgit/about/">cgit</a>. If you care at
25362536+all about big corporates turning open-source into a T-shirt
25372537+farming service, this is the way to go.</p>
25382538+<h3 id="offerings">Offerings</h3>
25392539+<p>cgit is <em>very</em> bare bones. It is
25402540+<a href="https://tools.ietf.org/html/rfc3875">cgi-based</a> web
25412541+interface to git, and nothing more. You may browse
25422542+repositories, view diffs, commit logs and even clone via
25432543+http. If you are looking to replace Github with cgit, keep
25442544+in mind that cgit does not handle issues or pull/merge
25452545+requests. If people wish to contribute to your work, they
25462546+would have to send you a patch via email.</p>
25472547+<h3 id="setup">Setup</h3>
25482548+<p>Installing cgit is fairly straightforward, if you would
25492549+like to compile it from source:</p>
25502550+<pre><code class="language-sh"># fetch
25512551+git clone https://git.zx2c4.com &amp;&amp; cd cgit
25522552+git submodule init
25532553+git submodule update
25542554+25552555+# install
25562556+make NO_LUA=1
25572557+sudo make install
25582558+</code></pre>
25592559+<p>This would drop the cgit cgi script (and the default css)
25602560+into <code>/var/www/htdocs/cgit</code>. You may configure cgit by
25612561+editing <code>/etc/cgitrc</code>. I specify the <code>NO_LUA</code> flag to
25622562+compile without lua support, exclude that flag if you would
25632563+like to extend cgit via lua scripts.</p>
25642564+<h3 id="going-live">Going live</h3>
25652565+<p>You might want to use,
25662566+<a href="https://github.com/gnosek/fcgiwrap">fcgiwrap</a>, a
25672567+<a href="http://www.nongnu.org/fastcgi">fastcgi</a> wrapper for <code>cgi</code>
25682568+scripts,</p>
25692569+<pre><code class="language-sh">sudo apt install fcgiwrap
25702570+sudo systemctl start fcgiwrap.socket
25712571+</code></pre>
25722572+<p>Expose the cgit cgi script to the web via <code>nginx</code>:</p>
25732573+<pre><code># nginx.conf
25742574+server {
25752575+ listen 80;
25762576+ server_name git.example.com;
25772577+25782578+ # serve static files
25792579+ location ~* ^.+\.(css|png|ico)$ {
25802580+ root /var/www/htdocs/cgit;
25812581+ }
25822582+25832583+ location / {
25842584+ fastcgi_pass unix:/run/fcgiwrap.socket;
25852585+ fastcgi_param SCRIPT_FILENAME /var/www/htdocs/cgit/cgit.cgi; # the default location of the cgit cgi script
25862586+ fastcgi_param PATH_INFO $uri;
25872587+ fastcgi_param QUERY_STRING $args;
25882588+ }
25892589+}
25902590+</code></pre>
25912591+<p>Point cgit to your git repositories:</p>
25922592+<pre><code># /etc/cgitrc
25932593+scan-path=/path/to/git/repos
25942594+</code></pre>
25952595+<p><em><strong>Note</strong></em>: <em><code>scan-path</code> works best if you stick it at the end of your
25962596+<code>cgitrc</code></em>.</p>
25972597+<p>You may now create remote repositories at
25982598+<code>/path/to/git/repos</code>, via:</p>
25992599+<pre><code>git init --bare
26002600+</code></pre>
26012601+<p>Add the remote to your local repository:</p>
26022602+<pre><code>git remote set-url origin user@remote:/above/path
26032603+git push origin master
26042604+</code></pre>
26052605+<h3 id="configuration">Configuration</h3>
26062606+<p>cgit is fairly easy to configure, all configuration
26072607+options can be found <a href="https://git.zx2c4.com/cgit/tree/cgitrc.5.txt">in the
26082608+manual</a>, here
26092609+are a couple of cool ones though:</p>
26102610+<p><strong>enable-commit-graph</strong>: Generates a text based graphical
26112611+representation of the commit history, similar to <code>git log --graph --oneline</code>.</p>
26122612+<pre><code>| * | Add support for configuration file
26132613+* | | simplify command parsing logic
26142614+* | | Refactor parsers
26152615+* | | Add basic tests
26162616+* | | Merge remote-tracking branch 'origin/master' in...
26172617+|\| |
26182618+| * | add installation instructions for nix
26192619+| * | switch to pancurses backendv0.2.2
26202620+| * | bump to v0.2.2
26212621+* | | Merge branch 'master' into feature/larger-names...
26222622+|\| |
26232623+| * | enable feature based compilation to support win...
26242624+| * | remove dependency on rustc v1.45, bump to v0.2....
26252625+| * | Merge branch 'feature/windows' of https://git...
26262626+| |\ \
26272627+| | * | add windows to github actions
26282628+| | * | switch to crossterm backend
26292629+| | * | Merge branch 'fix/duplicate-habits'
26302630+| | |\ \
26312631+| | | * | move duplicate check to command parsing blo...
26322632+</code></pre>
26332633+<p><strong>section-from-path</strong>: This option paired with <code>scan-path</code>
26342634+will automatically generate sections in your cgit index
26352635+page, from the path to each repo. For example, the directory
26362636+structure used to generate sections on <a href="https://git.peppe.rs">my cgit
26372637+instance</a> looks like this:</p>
26382638+<pre><code>├── cli
26392639+│ ├── dijo
26402640+│ ├── eva
26412641+│ ├── pista
26422642+│ ├── taizen
26432643+│ └── xcursorlocate
26442644+├── config
26452645+│ ├── dotfiles
26462646+│ └── nixos
26472647+├── fonts
26482648+│ ├── curie
26492649+│ └── scientifica
26502650+├── languages
26512651+│ └── lisk
26522652+├── libs
26532653+│ ├── cutlass
26542654+│ └── fondant
26552655+├── terminfo
26562656+├── university
26572657+│ └── furby
26582658+└── web
26592659+ └── isostatic
26602660+</code></pre>
26612661+<h3 id="ease-of-use">Ease of use</h3>
26622662+<p>As I mentioned before, <code>cgit</code> is simply a view into your git
26632663+repositories, you will have to manually create new
26642664+repositories by entering your remote and using <code>git init --bare</code>. Here are a couple of scripts I wrote to perform
26652665+actions on remotes, think of it as a smaller version of
26662666+Github's <code>gh</code> program.</p>
26672667+<p>You may save these scripts as <code>git-script-name</code> and drop
26682668+them in your <code>$PATH</code>, and git will automatically add an
26692669+alias called <code>script-name</code>, callable via:</p>
26702670+<pre><code>git script-name
26712671+</code></pre>
26722672+<h4 id="git-new-repo">git-new-repo</h4>
26732673+<p>Creates a new repository on your remote,
26742674+the first arg may be a path (section/repo-name) or just the
26752675+repo name:</p>
26762676+<pre><code>#! /usr/bin/env bash
26772677+#
26782678+# usage:
26792679+# git new-repo section/repo-name
26802680+#
26812681+# example:
26822682+# git new-repo fonts/scientifica
26832683+# creates: user@remote:fonts/scientifica
26842684+26852685+if [ $# -eq 0 ]; then
26862686+ echo &quot;requires an arg&quot;
26872687+ exit 1
26882688+fi
26892689+26902690+ssh user@remote git init --bare &quot;$1&quot;;
26912691+</code></pre>
26922692+<h4 id="git-set-desc">git-set-desc</h4>
26932693+<p>To set a one line repository
26942694+description. It simply copies the local <code>.git/description</code>,
26952695+into <code>remote/description</code>. <code>cgit</code> displays the contents of
26962696+this file on the index page:</p>
26972697+<pre><code>#! /usr/bin/env bash
26982698+#
26992699+# usage:
27002700+# enter repo description into .git/description and run:
27012701+# git set-desc
27022702+27032703+remote=$(git remote get-url --push origin)
27042704+scp .git/description &quot;$remote/description&quot;
27052705+</code></pre>
27062706+</description>
27072707+ <link>https://oppi.li/posts/self-hosting_git/</link>
27082708+ <pubDate>Sat, 17 Oct 2020 00:00:00 +0000</pubDate>
27092709+ <guid>https://oppi.li/posts/self-hosting_git/</guid>
27102710+ </item>
27112711+ <item>
27122712+ <title>nixOS</title>
27132713+ <description><p>I have been eyeing operating systems with functional package
27142714+managers for a while now, aka, NixOS or Guix. Reproducible
27152715+builds, declarative and rollback-able system configuration,
27162716+system consistency, all sound pretty cool. I have been using
27172717+NixOS for about a month now.</p>
27182718+<h3 id="installation">Installation</h3>
27192719+<p>I went with their minimal installation ISO. The installation
27202720+was pretty smooth from start to end, no hitches there. The
27212721+entire <a href="https://nixos.org/manual/nixos/stable/">manual</a> is
27222722+available offline, and is accessible during the
27232723+installation. Very handy.</p>
27242724+<h3 id="setup">Setup</h3>
27252725+<p>The entire system is configured via
27262726+<code>/etc/nixos/configuration.nix</code>. Wifi, <code>libinput</code> gestures,
27272727+audio, locale settings, there are options for literally
27282728+everything. You can declaratively write down the packages
27292729+you want installed too. With fresh installs of most distros,
27302730+I usually fumble with getting things like screen backlight
27312731+and media keys to work. If I do manage to fix it, I can't
27322732+carry it forward to future installations trivially. Getting
27332733+all my hardware to work on NixOS is as easy as:</p>
27342734+<pre><code>{
27352735+ server.xserver.libinput.enable = true; # touchpad
27362736+ programs.light.enable = true; # backlight
27372737+ hardware.pulseaudio.enable = true; # audio
27382738+ networking.wireless.enable = true; # wifi
27392739+}
27402740+</code></pre>
27412741+<h3 id="developing-with-nix">Developing with Nix</h3>
27422742+<p>Nix makes it easy to enter environments that aren't affected
27432743+by your system configuration using <code>nix-shell</code>.</p>
27442744+<p>Builds may be generated by specifying a <code>default.nix</code> file,
27452745+and running <code>nix-build</code>. Conventional package managers
27462746+require you to specify a dependency list, but there is no
27472747+guarantee that this list is complete. The package will build
27482748+on your machine even if you forget a dependency. However,
27492749+with Nix, packages are installed to <code>/nix/store</code>, and not
27502750+global paths such as <code>/usr/bin/...</code>, if your project builds,
27512751+it means you have included every last one.</p>
27522752+<p>Issues on most my projects have been &quot;unable to build
27532753+because <code>libxcb</code> is missing&quot;, or &quot;this version of <code>openssl</code>
27542754+is too old&quot;. Tools like <code>cargo</code> and <code>pip</code> are poor package
27552755+managers. While they <em>can</em> guarantee that Rust or Python
27562756+dependencies are met, they make assumptions about the
27572757+target system.</p>
27582758+<p>For example, <a href="https://github.com/nerdypepper/site">this
27592759+website</a> is now built
27602760+using Nix, anyone using Nix may simply, clone the repository
27612761+and run <code>./generate.sh</code>, and it would <em>just work</em>, while
27622762+keeping your global namespace clean™:</p>
27632763+<pre><code class="language-bash">#! /usr/bin/env nix-shell
27642764+#! nix-shell -i bash -p eva pandoc esh
27652765+27662766+# some bash magic ;)
27672767+</code></pre>
27682768+<p>Dependencies are included with the <code>-p</code> flag, the shell
27692769+script is executed with an interpreter, specified with the
27702770+<code>-i</code> flag.</p>
27712771+<h3 id="impressions">Impressions</h3>
27722772+<p>NixOS is by no means, simple. As a newcomer, using Nix was
27732773+not easy, heck, I had to learn a purely functional, lazy
27742774+language to just build programs. There is a lot to be
27752775+desired on the tooling front as well. A well fleshed out LSP
27762776+plugin would be nice (<a href="https://github.com/nix-community/rnix-lsp">rnix-lsp looks
27772777+promising</a>).</p>
27782778+<p>Being able to rollback changes at a system level is cool.
27792779+Package broke something? Just <code>nixos-rebuild switch --rollback</code>! Deleted <code>nix</code> by mistake? Find the binary in
27802780+<code>/nix/store</code> and rollback! You aren't punished for not
27812781+thinking twice.</p>
27822782+<p>I don't see myself switching to anything else in the near
27832783+future, NixOS does a lot of things right. If I ever need to
27842784+reinstall NixOS, I can generate an <a href="https://github.com/nix-community/nixos-generators">image of my current
27852785+system</a>.</p>
27862786+<p><a href="https://cdn.oppi.li/6m.png"><img src="https://cdn.oppi.li/6m.png" alt="" /></a></p>
27872787+</description>
27882788+ <link>https://oppi.li/posts/nixOS/</link>
27892789+ <pubDate>Tue, 01 Sep 2020 00:00:00 +0000</pubDate>
27902790+ <guid>https://oppi.li/posts/nixOS/</guid>
27912791+ </item>
27922792+ <item>
27932793+ <title>gripes with go</title>
27942794+ <description><p>You've read a lot of posts about the shortcomings of the Go
27952795+programming language, so what's one more.</p>
27962796+<ol>
27972797+<li><a href="#lack-of-sum-types">Lack of sum types</a>
27982798+</li>
27992799+<li><a href="#type-assertions">Type assertions</a>
28002800+</li>
28012801+<li><a href="#date-and-time">Date and Time</a>
28022802+</li>
28032803+<li><a href="#statements-over-expressions">Statements over Expressions</a>
28042804+</li>
28052805+<li><a href="#erroring-out-on-unused-variables">Erroring out on unused variables</a>
28062806+</li>
28072807+<li><a href="#error-handling">Error handling</a>
28082808+</li>
28092809+</ol>
28102810+<h3 id="lack-of-sum-types">Lack of Sum types</h3>
28112811+<p>A &quot;Sum&quot; type is a data type that can hold one of many states
28122812+at a given time, similar to how a boolean can hold a true or
28132813+a false, not too different from an <code>enum</code> type in C. Go
28142814+lacks <code>enum</code> types unfortunately, and you are forced to
28152815+resort to crafting your own substitute.</p>
28162816+<p>A type to represent gender for example:</p>
28172817+<pre><code class="language-go">type Gender int
28182818+28192819+const (
28202820+ Male Gender = iota // assigns Male to 0
28212821+ Female // assigns Female to 1
28222822+ Other // assigns Other to 2
28232823+)
28242824+28252825+fmt.Println(&quot;My gender is &quot;, Male)
28262826+// My gender is 0
28272827+// Oops! We have to implement String() for Gender ...
28282828+28292829+func (g Gender) String() string {
28302830+ switch (g) {
28312831+ case 0: return &quot;Male&quot;
28322832+ case 1: return &quot;Female&quot;
28332833+ default: return &quot;Other&quot;
28342834+ }
28352835+}
28362836+28372837+// You can accidentally do stupid stuff like:
28382838+gender := Male + 1
28392839+</code></pre>
28402840+<p>The Haskell equivalent of the same:</p>
28412841+<pre><code class="language-haskell">data Gender = Male
28422842+ | Female
28432843+ | Other
28442844+ deriving (Show)
28452845+28462846+print $ &quot;My gender is &quot; ++ (show Male)
28472847+</code></pre>
28482848+<h3 id="type-assertions">Type Assertions</h3>
28492849+<p>A downcast with an optional error check? What could go
28502850+wrong?</p>
28512851+<p>Type assertions in Go allow you to do:</p>
28522852+<pre><code class="language-go">var x interface{} = 7
28532853+y, goodToGo := x.(int)
28542854+if goodToGo {
28552855+ fmt.Println(y)
28562856+}
28572857+</code></pre>
28582858+<p>The error check however is optional:</p>
28592859+<pre><code class="language-go">var x interface{} = 7
28602860+var y := x.(float64)
28612861+fmt.Println(y)
28622862+// results in a runtime error:
28632863+// panic: interface conversion: interface {} is int, not float64
28642864+</code></pre>
28652865+<h3 id="date-and-time">Date and Time</h3>
28662866+<p>Anyone that has written Go previously, will probably already
28672867+know what I am getting at here. For the uninitiated, parsing
28682868+and formatting dates in Go requires a &quot;layout&quot;. This
28692869+&quot;layout&quot; is based on magical reference date:</p>
28702870+<pre><code>Mon Jan 2 15:04:05 MST 2006
28712871+</code></pre>
28722872+<p>Which is the date produced when you write the first seven
28732873+natural numbers like so:</p>
28742874+<pre><code>01/02 03:04:05 '06 -0700
28752875+</code></pre>
28762876+<p>Parsing a string in <code>YYYY-MM-DD</code> format would look something
28772877+like:</p>
28782878+<pre><code class="language-go">const layout = &quot;2006-01-02&quot;
28792879+time.Parse(layout, &quot;2020-08-01&quot;)
28802880+</code></pre>
28812881+<p>This so-called &quot;intuitive&quot; method of formatting dates
28822882+doesn't allow you to print <code>0000 hrs</code> as <code>2400 hrs</code>, it
28832883+doesn't allow you to omit the leading zero in 24 hour
28842884+formats. It is rife with inconveniences, if only there were
28852885+a <a href="https://man7.org/linux/man-pages/man3/strftime.3.html">tried and
28862886+tested</a>
28872887+date formatting convention ...</p>
28882888+<h3 id="statements-over-expressions">Statements over Expressions</h3>
28892889+<p>Statements have side effects, expressions return values.
28902890+More often than not, expressions are easier to understand at
28912891+a glance: evaluate the LHS and assign the same to the RHS.</p>
28922892+<p>Rust allows you to create local namespaces, and treats
28932893+blocks (<code>{}</code>) as expressions:</p>
28942894+<pre><code class="language-rust">let twenty_seven = {
28952895+ let three = 1 + 2;
28962896+ let nine = three * three;
28972897+ nine * three
28982898+};
28992899+</code></pre>
29002900+<p>The Go equivalent of the same:</p>
29012901+<pre><code class="language-go">twenty_seven := nil
29022902+29032903+three := 1 + 2
29042904+nine := three * three
29052905+twenty_seven = nine * three
29062906+</code></pre>
29072907+<h3 id="erroring-out-on-unused-variables">Erroring out on unused variables</h3>
29082908+<p>Want to quickly prototype something? Go says no! In all
29092909+seriousness, a warning would suffice, I don't want to have
29102910+to go back and comment each unused import out, only to come
29112911+back and uncomment them a few seconds later.</p>
29122912+<h3 id="error-handling">Error handling</h3>
29132913+<pre><code class="language-go">if err != nil { ... }
29142914+</code></pre>
29152915+<p>Need I say more? I will, for good measure:</p>
29162916+<ol>
29172917+<li>Error handling is optional
29182918+</li>
29192919+<li>Errors are propagated via a clunky <code>if</code> + <code>return</code> statement
29202920+</li>
29212921+</ol>
29222922+<p>I prefer Haskell's &quot;Monadic&quot; error handling, which is
29232923+employed by Rust as well:</p>
29242924+<pre><code class="language-rust">// 1. error handling is compulsory
29252925+// 2. errors are propagated with the `?` operator
29262926+fn foo() -&gt; Result&lt;String, io::Error&gt; {
29272927+ let mut f = File::open(&quot;foo.txt&quot;)?; // return if error
29282928+ let mut s = String::new();
29292929+29302930+ f.read_to_string(&amp;mut s)?; // return if error
29312931+29322932+ Ok(s) // all good, return a string inside a `Result` context
29332933+}
29342934+29352935+fn main() {
29362936+ // `contents` is an enum known as Result:
29372937+ let contents = foo();
29382938+ match contents {
29392939+ Ok(c) =&gt; println!(c),
29402940+ Err(e) =&gt; eprintln!(e)
29412941+ }
29422942+}
29432943+</code></pre>
29442944+<h3 id="conclusion">Conclusion</h3>
29452945+<p>I did not want to conclude without talking about stylistic
29462946+choices, lack of metaprogramming, bizzare export rules, but,
29472947+I am too busy converting my <code>interface{}</code> types into actual
29482948+generic code for Go v2.</p>
29492949+</description>
29502950+ <link>https://oppi.li/posts/gripes_with_go/</link>
29512951+ <pubDate>Sat, 01 Aug 2020 00:00:00 +0000</pubDate>
29522952+ <guid>https://oppi.li/posts/gripes_with_go/</guid>
29532953+ </item>
29542954+ <item>
29552955+ <title>turing complete type systems</title>
29562956+ <description><p>Rust's type system is Turing complete:</p>
29572957+<ul>
29582958+<li><a href="https://github.com/doctorn/trait-eval/">FizzBuzz with Rust Traits</a>
29592959+</li>
29602960+<li><a href="https://github.com/Ashymad/fortraith">A Forth implementation with Rust Traits</a>
29612961+</li>
29622962+</ul>
29632963+<p>It is impossible to determine if a program written in a
29642964+generally Turing complete system will ever stop. That is, it
29652965+is impossible to write a program <code>f</code> that determines if a
29662966+program <code>g</code>, where <code>g</code> is written in a Turing complete
29672967+programming language, will ever halt. The <a href="https://en.wikipedia.org/wiki/Halting_problem">Halting
29682968+Problem</a> is
29692969+in fact, an <a href="https://en.wikipedia.org/wiki/Undecidable_problem">undecidable
29702970+problem</a>.</p>
29712971+<p><em>How is any of this relevant?</em></p>
29722972+<p>Rust performs compile-time type inference. The type checker,
29732973+in turn, compiles and infers types, I would describe it as a
29742974+compiler inside a compiler. It is possible that <code>rustc</code> may
29752975+never finish compiling your Rust program! I lied, <code>rustc</code>
29762976+stops after a while, after hitting the recursion limit.</p>
29772977+<p>I understand that this post lacks content.</p>
29782978+</description>
29792979+ <link>https://oppi.li/posts/turing_complete_type_systems/</link>
29802980+ <pubDate>Wed, 17 Jun 2020 00:00:00 +0000</pubDate>
29812981+ <guid>https://oppi.li/posts/turing_complete_type_systems/</guid>
29822982+ </item>
29832983+ <item>
29842984+ <title>auto-currying rust functions</title>
29852985+ <description><p>This post contains a gentle introduction to procedural
29862986+macros in Rust and a guide to writing a procedural macro to
29872987+curry Rust functions. The source code for the entire library
29882988+can be found <a href="https://github.com/nerdypepper/cutlass">here</a>.
29892989+It is also available on <a href="https://crates.io/crates/cutlass">crates.io</a>.</p>
29902990+<p>The following links might prove to be useful before getting
29912991+started:</p>
29922992+<ul>
29932993+<li><a href="https://doc.rust-lang.org/reference/procedural-macros.html">Procedural Macros</a>
29942994+</li>
29952995+<li><a href="https://en.wikipedia.org/wiki/Currying">Currying</a>
29962996+</li>
29972997+</ul>
29982998+<p>Or you can pretend you read them, because I have included
29992999+a primer here :)</p>
30003000+<h3 id="contents">Contents</h3>
30013001+<ol>
30023002+<li><a href="#currying">Currying</a>
30033003+</li>
30043004+<li><a href="#procedural-macros">Procedural Macros</a>
30053005+</li>
30063006+<li><a href="#definitions">Definitions</a>
30073007+</li>
30083008+<li><a href="#refinement">Refinement</a>
30093009+</li>
30103010+<li><a href="#the-in-betweens">The In-betweens</a><br />
30113011+ 5.1 <a href="#dependencies">Dependencies</a><br />
30123012+ 5.2 <a href="#the-attribute-macro">The attribute macro</a><br />
30133013+ 5.3 <a href="#function-body">Function Body</a><br />
30143014+ 5.4 <a href="#function-signature">Function Signature</a><br />
30153015+ 5.5 <a href="#getting-it-together">Getting it together</a>
30163016+</li>
30173017+<li><a href="#debugging-and-testing">Debugging and Testing</a>
30183018+</li>
30193019+<li><a href="#notes">Notes</a>
30203020+</li>
30213021+<li><a href="#conclusion">Conclusion</a>
30223022+</li>
30233023+</ol>
30243024+<h3 id="currying">Currying</h3>
30253025+<p>Currying is the process of transformation of a function call
30263026+like <code>f(a, b, c)</code> to <code>f(a)(b)(c)</code>. A curried function
30273027+returns a concrete value only when it receives all its
30283028+arguments! If it does recieve an insufficient amount of
30293029+arguments, say 1 of 3, it returns a <em>curried function</em>, that
30303030+returns after receiving 2 arguments.</p>
30313031+<pre><code>curry(f(a, b, c)) = h(a)(b)(c)
30323032+30333033+h(x) = g &lt;- curried function that takes upto 2 args (g)
30343034+g(y) = k &lt;- curried function that takes upto 1 arg (k)
30353035+k(z) = v &lt;- a value (v)
30363036+30373037+Keen readers will conclude the following,
30383038+h(x)(y)(z) = g(y)(z) = k(z) = v
30393039+</code></pre>
30403040+<p>Mathematically, if <code>f</code> is a function that takes two
30413041+arguments <code>x</code> and <code>y</code>, such that <code>x ϵ X</code>, and <code>y ϵ Y</code> , we
30423042+write it as:</p>
30433043+<pre><code>f: (X × Y) -&gt; Z
30443044+</code></pre>
30453045+<p>where <code>×</code> denotes the Cartesian product of set <code>X</code> and <code>Y</code>,
30463046+and curried <code>f</code> (denoted by <code>h</code> here) is written as:</p>
30473047+<pre><code>h: X -&gt; (Y -&gt; Z)
30483048+</code></pre>
30493049+<h3 id="procedural-macros">Procedural Macros</h3>
30503050+<p>These are functions that take code as input and spit out
30513051+modified code as output. Powerful stuff. Rust has three
30523052+kinds of proc-macros:</p>
30533053+<ul>
30543054+<li>Function like macros
30553055+</li>
30563056+<li>Derive macros: <code>#[derive(...)]</code>, used to automatically
30573057+implement traits for structs/enums
30583058+</li>
30593059+<li>and Attribute macros: <code>#[test]</code>, usually slapped onto
30603060+functions
30613061+</li>
30623062+</ul>
30633063+<p>We will be using Attribute macros to convert a Rust function
30643064+into a curried Rust function, which we should be able to
30653065+call via: <code>function(arg1)(arg2)</code>.</p>
30663066+<h3 id="definitions">Definitions</h3>
30673067+<p>Being respectable programmers, we define the input to and
30683068+the output from our proc-macro. Here's a good non-trivial
30693069+function to start out with:</p>
30703070+<pre><code class="language-rust">fn add(x: u32, y: u32, z: u32) -&gt; u32 {
30713071+ return x + y + z;
30723072+}
30733073+</code></pre>
30743074+<p>Hmm, what would our output look like? What should our
30753075+proc-macro generate ideally? Well, if we understood currying
30763076+correctly, we should accept an argument and return a
30773077+function that accepts an argument and returns ... you get
30783078+the point. Something like this should do:</p>
30793079+<pre><code class="language-rust">fn add_curried1(x: u32) -&gt; ? {
30803080+ return fn add_curried2 (y: u32) -&gt; ? {
30813081+ return fn add_curried3 (z: u32) -&gt; u32 {
30823082+ return x + y + z;
30833083+ }
30843084+ }
30853085+}
30863086+</code></pre>
30873087+<p>A couple of things to note:</p>
30883088+<p><strong>Return types</strong><br />
30893089+We have placed <code>?</code>s in place of return
30903090+types. Let's try to fix that. <code>add_curried3</code> returns the
30913091+'value', so <code>u32</code> is accurate. <code>add_curried2</code> returns
30923092+<code>add_curried3</code>. What is the type of <code>add_curried3</code>? It is a
30933093+function that takes in a <code>u32</code> and returns a <code>u32</code>. So a
30943094+<code>fn(u32) -&gt; u32</code> will do right? No, I'll explain why in the
30953095+next point, but for now, we will make use of the <code>Fn</code> trait,
30963096+our return type is <code>impl Fn(u32) -&gt; u32</code>. This basically
30973097+tells the compiler that we will be returning something
30983098+function-like, a.k.a, behaves like a <code>Fn</code>. Cool!</p>
30993099+<p>If you have been following along, you should be able to tell
31003100+that the return type of <code>add_curried1</code> is:</p>
31013101+<pre><code>impl Fn(u32) -&gt; (impl Fn(u32) -&gt; u32)
31023102+</code></pre>
31033103+<p>We can drop the parentheses because <code>-&gt;</code> is right associative:</p>
31043104+<pre><code>impl Fn(u32) -&gt; impl Fn(u32) -&gt; u32
31053105+31063106+</code></pre>
31073107+<p><strong>Accessing environment</strong><br />
31083108+A function cannot access it's environment. Our solution
31093109+will not work. <code>add_curried3</code> attempts to access <code>x</code>, which
31103110+is not allowed! A closure<a href="%5Bhttps://doc.rust-lang.org/book/ch13-01-closures.html%5D(https://doc.rust-lang.org/book/ch13-01-closures.html)">^closure</a> however, can. If we are
31113111+returning a closure, our return type must be <code>impl Fn</code>, and
31123112+not <code>fn</code>. The difference between the <code>Fn</code> trait and
31133113+function pointers is beyond the scope of this post.</p>
31143114+<h3 id="refinement">Refinement</h3>
31153115+<p>Armed with knowledge, we refine our expected output, this
31163116+time, employing closures:</p>
31173117+<pre><code class="language-rust">fn add(x: u32) -&gt; impl Fn(u32) -&gt; impl Fn(u32) -&gt; u32 {
31183118+ return move |y| move |z| x + y + z;
31193119+}
31203120+</code></pre>
31213121+<p>Alas, that does not compile either! It errors out with the
31223122+following message:</p>
31233123+<pre><code>error[E0562]: `impl Trait` not allowed outside of function
31243124+and inherent method return types
31253125+ --&gt; src/main.rs:17:37
31263126+ |
31273127+ | fn add(x: u32) -&gt; impl Fn(u32) -&gt; impl Fn(u32) -&gt; u32
31283128+ | ^^^^^^^^^^^^^^^^^^^
31293129+31303130+</code></pre>
31313131+<p>You are allowed to return an <code>impl Fn</code> only inside a
31323132+function. We are currently returning it from another return!
31333133+Or at least, that was the most I could make out of the error
31343134+message.</p>
31353135+<p>We are going to have to cheat a bit to fix this issue; with
31363136+type aliases and a convenient nightly feature [^features]:</p>
31373137+<p>[^features]: <a href="https://caniuse.rs">caniuse.rs</a> contains an
31383138+indexed list of features and their status.</p>
31393139+<pre><code class="language-rust">#![feature(type_alias_impl_trait)] // allows us to use `impl Fn` in type aliases!
31403140+31413141+type T0 = u32; // the return value when zero args are to be applied
31423142+type T1 = impl Fn(u32) -&gt; T0; // the return value when one arg is to be applied
31433143+type T2 = impl Fn(u32) -&gt; T1; // the return value when two args are to be applied
31443144+31453145+fn add(x: u32) -&gt; T2 {
31463146+ return move |y| move |z| x + y + z;
31473147+}
31483148+</code></pre>
31493149+<p>Drop that into a cargo project, call <code>add(4)(5)(6)</code>, cross
31503150+your fingers, and run <code>cargo +nightly run</code>. You should see a
31513151+15 unless you forgot to print it!</p>
31523152+<h3 id="the-in-betweens">The In-Betweens</h3>
31533153+<p>Let us write the magical bits that take us from function to
31543154+curried function.</p>
31553155+<p>Initialize your workspace with <code>cargo new --lib currying</code>.
31563156+Proc-macro crates are libraries with exactly one export, the
31573157+macro itself. Add a <code>tests</code> directory to your crate root.
31583158+Your directory should look something like this:</p>
31593159+<pre><code>.
31603160+├── Cargo.toml
31613161+├── src
31623162+│ └── lib.rs
31633163+└── tests
31643164+ └── smoke.rs
31653165+</code></pre>
31663166+<h4 id="dependencies">Dependencies</h4>
31673167+<p>We will be using a total of 3 external crates:</p>
31683168+<ul>
31693169+<li><a href="https://docs.rs/proc-macro2/1.0.12/proc_macro2/">proc_macro2</a>
31703170+</li>
31713171+<li><a href="https://docs.rs/syn/1.0.18/syn/index.html">syn</a>
31723172+</li>
31733173+<li><a href="https://docs.rs/quote/1.0.4/quote/index.html">quote</a>
31743174+</li>
31753175+</ul>
31763176+<p>Here's a sample <code>Cargo.toml</code>:</p>
31773177+<pre><code># Cargo.toml
31783178+31793179+[dependencies]
31803180+proc-macro2 = &quot;1.0.9&quot;
31813181+quote = &quot;1.0&quot;
31823182+31833183+[dependencies.syn]
31843184+version = &quot;1.0&quot;
31853185+features = [&quot;full&quot;]
31863186+31873187+[lib]
31883188+proc-macro = true # this is important!
31893189+</code></pre>
31903190+<p>We will be using an external <code>proc-macro2</code> crate as well as
31913191+an internal <code>proc-macro</code> crate. Not confusing at all!</p>
31923192+<h4 id="the-attribute-macro">The attribute macro</h4>
31933193+<p>Drop this into <code>src/lib.rs</code>, to get the ball rolling.</p>
31943194+<pre><code class="language-rust">// src/lib.rs
31953195+31963196+use proc_macro::TokenStream; // 1
31973197+use quote::quote;
31983198+use syn::{parse_macro_input, ItemFn};
31993199+32003200+#[proc_macro_attribute] // 2
32013201+pub fn curry(_attr: TokenStream, item: TokenStream) -&gt; TokenStream {
32023202+ let parsed = parse_macro_input!(item as ItemFn); // 3
32033203+ generate_curry(parsed).into() // 4
32043204+}
32053205+32063206+fn generate_curry(parsed: ItemFn) -&gt; proc_macro2::TokenStream {}
32073207+</code></pre>
32083208+<p><strong>1. Imports</strong></p>
32093209+<p>A <code>Tokenstream</code> holds (hopefully valid) Rust code, this
32103210+is the type of our input and output. Note that we are
32113211+importing this type from <code>proc_macro</code> and not <code>proc_macro2</code>.</p>
32123212+<p><code>quote!</code> from the <code>quote</code> crate is a macro that allows us to
32133213+quickly produce <code>TokenStream</code>s. Much like the LISP <code>quote</code>
32143214+procedure, you can use the <code>quote!</code> macro for symbolic
32153215+transformations.</p>
32163216+<p><code>ItemFn</code> from the <code>syn</code> crate holds the parsed <code>TokenStream</code>
32173217+of a Rust function. <code>parse_macro_input!</code> is a helper macro
32183218+provided by <code>syn</code>.</p>
32193219+<p><strong>2. The lone export</strong></p>
32203220+<p>Annotate the only <code>pub</code> of our crate with
32213221+<code>#[proc_macro_attribute]</code>. This tells rustc that <code>curry</code> is
32223222+a procedural macro, and allows us to use it as
32233223+<code>#[crate_name::curry]</code> in other crates. Note the signature
32243224+of the <code>curry</code> function. <code>_attr</code> is the <code>TokenStream</code>
32253225+representing the attribute itself, <code>item</code> refers to the
32263226+thing we slapped our macro into, in this case a function
32273227+(like <code>add</code>). The return value is a modified <code>TokenStream</code>,
32283228+this will contain our curried version of <code>add</code>.</p>
32293229+<p><strong>3. The helper macro</strong></p>
32303230+<p>A <code>TokenStream</code> is a little hard to work with, which is why
32313231+we have the <code>syn</code> crate, which provides types to represent
32323232+Rust tokens. An <code>RArrow</code> struct to represent the return
32333233+arrow on a function and so on. One of those types is
32343234+<code>ItemFn</code>, that represents an entire Rust function. The
32353235+<code>parse_macro_input!</code> automatically puts the input to our
32363236+macro into an <code>ItemFn</code>. What a gentleman!</p>
32373237+<p>**4. Returning <code>TokenStream</code>s **</p>
32383238+<p>We haven't filled in <code>generate_curry</code> yet, but we can see
32393239+that it returns a <code>proc_macro2::TokenStream</code> and not a
32403240+<code>proc_macro::TokenStream</code>, so drop a <code>.into()</code> to convert
32413241+it.</p>
32423242+<p>Lets move on, and fill in <code>generate_curry</code>, I would suggest
32433243+keeping the documentation for
32443244+<a href="https://docs.rs/syn/1.0.19/syn/struct.ItemFn.html"><code>syn::ItemFn</code></a>
32453245+and
32463246+<a href="https://docs.rs/syn/1.0.19/syn/struct.Signature.html"><code>syn::Signature</code></a>
32473247+open.</p>
32483248+<pre><code class="language-rust">// src/lib.rs
32493249+32503250+fn generate_curry(parsed: ItemFn) -&gt; proc_macro2::TokenStream {
32513251+ let fn_body = parsed.block; // function body
32523252+ let sig = parsed.sig; // function signature
32533253+ let vis = parsed.vis; // visibility, pub or not
32543254+ let fn_name = sig.ident; // function name/identifier
32553255+ let fn_args = sig.inputs; // comma separated args
32563256+ let fn_return_type = sig.output; // return type
32573257+}
32583258+</code></pre>
32593259+<p>We are simply extracting the bits of the function, we will
32603260+be reusing the original function's visibility and name. Take
32613261+a look at what <code>syn::Signature</code> can tell us about a
32623262+function:</p>
32633263+<pre><code> .-- syn::Ident (ident)
32643264+ /
32653265+ fn add(x: u32, y: u32) -&gt; u32
32663266+ (fn_token) / ~~~~~~~,~~~~~~ ~~~~~~
32673267+syn::token::Fn --' / \ (output)
32683268+ ' `- syn::ReturnType
32693269+ Punctuated&lt;FnArg, Comma&gt; (inputs)
32703270+</code></pre>
32713271+<p>Enough analysis, lets produce our first bit of Rust code.</p>
32723272+<h4 id="function-body">Function Body</h4>
32733273+<p>Recall that the body of a curried <code>add</code> should look like
32743274+this:</p>
32753275+<pre><code class="language-rust">return move |y| move |z| x + y + z;
32763276+</code></pre>
32773277+<p>And in general:</p>
32783278+<pre><code class="language-rust">return move |arg2| move |arg3| ... |argN| &lt;function body here&gt;
32793279+</code></pre>
32803280+<p>We already have the function's body, provided by <code>fn_body</code>,
32813281+in our <code>generate_curry</code> function. All that's left to add is
32823282+the <code>move |arg2| move |arg3| ...</code> stuff, for which we need
32833283+to extract the argument identifiers
32843284+(doc:
32853285+<a href="https://docs.rs/syn/1.0.18/syn/punctuated/struct.Punctuated.html">Punctuated</a>,
32863286+<a href="https://docs.rs/syn/1.0.18/syn/enum.FnArg.html">FnArg</a>,
32873287+<a href="https://docs.rs/syn/1.0.18/syn/struct.PatType.html">PatType</a>):</p>
32883288+<pre><code class="language-rust">// src/lib.rs
32893289+use syn::punctuated::Punctuated;
32903290+use syn::{parse_macro_input, FnArg, Pat, ItemFn, Block};
32913291+32923292+fn extract_arg_idents(fn_args: Punctuated&lt;FnArg, syn::token::Comma&gt;) -&gt; Vec&lt;Box&lt;Pat&gt;&gt; {
32933293+ return fn_args.into_iter().map(extract_arg_pat).collect::&lt;Vec&lt;_&gt;&gt;();
32943294+}
32953295+</code></pre>
32963296+<p>Alright, so we are iterating over function args
32973297+(<code>Punctuated</code> is a collection that you can iterate over) and
32983298+mapping an <code>extract_arg_pat</code> to every item. What's
32993299+<code>extract_arg_pat</code>?</p>
33003300+<pre><code class="language-rust">// src/lib.rs
33013301+33023302+fn extract_arg_pat(a: FnArg) -&gt; Box&lt;Pat&gt; {
33033303+ match a {
33043304+ FnArg::Typed(p) =&gt; p.pat,
33053305+ _ =&gt; panic!(&quot;Not supported on types with `self`!&quot;),
33063306+ }
33073307+}
33083308+</code></pre>
33093309+<p><code>FnArg</code> is an enum type as you might have guessed. The
33103310+<code>Typed</code> variant encompasses args that are written as <code>name: type</code> and the other variant, <code>Reciever</code> refers to <code>self</code>
33113311+types. Ignore those for now, keep it simple.</p>
33123312+<p>Every <code>FnArg::Typed</code> value contains a <code>pat</code>, which is in
33133313+essence, the name of the argument. The type of the arg is
33143314+accessible via <code>p.ty</code> (we will be using this later).</p>
33153315+<p>With that done, we should be able to write the codegen for
33163316+the function body:</p>
33173317+<pre><code class="language-rust">// src/lib.rs
33183318+33193319+fn generate_body(fn_args: &amp;[Box&lt;Pat&gt;], body: Box&lt;Block&gt;) -&gt; proc_macro2::TokenStream {
33203320+ quote! {
33213321+ return #( move |#fn_args| )* #body
33223322+ }
33233323+}
33243324+</code></pre>
33253325+<p>That is some scary looking syntax! Allow me to explain. The
33263326+<code>quote!{ ... }</code> returns a <code>proc_macro2::TokenStream</code>, if we
33273327+wrote <code>quote!{ let x = 1 + 2; }</code>, it wouldn't create a new
33283328+variable <code>x</code> with value 3, it would literally produce a
33293329+stream of tokens with that expression.</p>
33303330+<p>The <code>#</code> enables variable interpolation. <code>#body</code> will look
33313331+for <code>body</code> in the current scope, take its value, and insert
33323332+it in the returned <code>TokenStream</code>. Kinda like quasi quoting
33333333+in LISPs, you have written one.</p>
33343334+<p>What about <code>#( move |#fn_args| )*</code>? That is repetition.
33353335+<code>quote</code> iterates through <code>fn_args</code>, and drops a <code>move</code> behind
33363336+each one, it then places pipes (<code>|</code>), around it.</p>
33373337+<p>Let us test our first bit of codegen! Modify <code>generate_curry</code> like so:</p>
33383338+<pre><code class="language-rust">// src/lib.rs
33393339+33403340+ fn generate_curry(parsed: ItemFn) -&gt; TokenStream {
33413341+ let fn_body = parsed.block;
33423342+ let sig = parsed.sig;
33433343+ let vis = parsed.vis;
33443344+ let fn_name = sig.ident;
33453345+ let fn_args = sig.inputs;
33463346+ let fn_return_type = sig.output;
33473347+33483348++ let arg_idents = extract_arg_idents(fn_args.clone());
33493349++ let first_ident = &amp;arg_idents.first().unwrap();
33503350+33513351++ // remember, our curried body starts with the second argument!
33523352++ let curried_body = generate_body(&amp;arg_idents[1..], fn_body.clone());
33533353++ println!(&quot;{}&quot;, curried_body);
33543354+33553355+ return TokenStream::new();
33563356+ }
33573357+</code></pre>
33583358+<p>Add a little test to <code>tests/</code>:</p>
33593359+<pre><code class="language-rust">// tests/smoke.rs
33603360+33613361+#[currying::curry]
33623362+fn add(x: u32, y: u32, z: u32) -&gt; u32 {
33633363+ x + y + z
33643364+}
33653365+33663366+#[test]
33673367+fn works() {
33683368+ assert!(true);
33693369+}
33703370+</code></pre>
33713371+<p>You should find something like this in the output of <code>cargo test</code>:</p>
33723372+<pre><code>return move | y | move | z | { x + y + z }
33733373+</code></pre>
33743374+<p>Glorious <code>println!</code> debugging!</p>
33753375+<h4 id="function-signature">Function signature</h4>
33763376+<p>This section gets into the more complicated bits of the
33773377+macro, generating type aliases and the function signature.
33783378+By the end of this section, we should have a full working
33793379+auto-currying macro!</p>
33803380+<p>Recall what our generated type aliases should look like, for
33813381+our <code>add</code> function:</p>
33823382+<pre><code class="language-rust">type T0 = u32;
33833383+type T1 = impl Fn(u32) -&gt; T0;
33843384+type T2 = impl Fn(u32) -&gt; T1;
33853385+</code></pre>
33863386+<p>In general:</p>
33873387+<pre><code class="language-rust">type T0 = &lt;return type&gt;;
33883388+type T1 = impl Fn(&lt;type of arg N&gt;) -&gt; T0;
33893389+type T2 = impl Fn(&lt;type of arg N - 1&gt;) -&gt; T1;
33903390+.
33913391+.
33923392+.
33933393+type T(N-1) = impl Fn(&lt;type of arg 2&gt;) -&gt; T(N-2);
33943394+</code></pre>
33953395+<p>To codegen that, we need the types of:</p>
33963396+<ul>
33973397+<li>all our inputs (arguments)
33983398+</li>
33993399+<li>the output (the return type)
34003400+</li>
34013401+</ul>
34023402+<p>To fetch the types of all our inputs, we can simply reuse
34033403+the bits we wrote to fetch the names of all our inputs!
34043404+(doc: <a href="https://docs.rs/syn/1.0.18/syn/enum.Type.html">Type</a>)</p>
34053405+<pre><code class="language-rust">// src/lib.rs
34063406+34073407+use syn::{parse_macro_input, Block, FnArg, ItemFn, Pat, ReturnType, Type};
34083408+34093409+fn extract_type(a: FnArg) -&gt; Box&lt;Type&gt; {
34103410+ match a {
34113411+ FnArg::Typed(p) =&gt; p.ty, // notice `ty` instead of `pat`
34123412+ _ =&gt; panic!(&quot;Not supported on types with `self`!&quot;),
34133413+ }
34143414+}
34153415+34163416+fn extract_arg_types(fn_args: Punctuated&lt;FnArg, syn::token::Comma&gt;) -&gt; Vec&lt;Box&lt;Type&gt;&gt; {
34173417+ return fn_args.into_iter().map(extract_type).collect::&lt;Vec&lt;_&gt;&gt;();
34183418+34193419+}
34203420+</code></pre>
34213421+<p>A good reader would have looked at the docs for output
34223422+member of the <code>syn::Signature</code> struct. It has the type
34233423+<code>syn::ReturnType</code>. So there is no extraction to do here
34243424+right? There are actually a couple of things we have to
34253425+ensure here:</p>
34263426+<ol>
34273427+<li>
34283428+<p>We need to ensure that the function returns! A function
34293429+that does not return is pointless in this case, and I
34303430+will tell you why, in the <a href="#notes">Notes</a> section.</p>
34313431+</li>
34323432+<li>
34333433+<p>A <code>ReturnType</code> encloses the arrow of the return as well,
34343434+we need to get rid of that. Recall:</p>
34353435+<pre><code class="language-rust"></code></pre>
34363436+</li>
34373437+</ol>
34383438+<p>type T0 = u32
34393439+// and not
34403440+type T0 = -&gt; u32</p>
34413441+<pre><code>
34423442+Here is the snippet that handles extraction of the
34433443+return type (doc: [syn::ReturnType](https://docs.rs/syn/1.0.19/syn/enum.ReturnType.html)):
34443444+34453445+```rust
34463446+// src/lib.rs
34473447+34483448+fn extract_return_type(a: ReturnType) -&gt; Box&lt;Type&gt; {
34493449+match a {
34503450+ ReturnType::Type(_, p) =&gt; p,
34513451+ _ =&gt; panic!(&quot;Not supported on functions without return types!&quot;),
34523452+}
34533453+}
34543454+</code></pre>
34553455+<p>You might notice that we are making extensive use of the
34563456+<code>panic!</code> macro. Well, that is because it is a good idea to
34573457+quit on receiving an unsatisfactory <code>TokenStream</code>.</p>
34583458+<p>With all our types ready, we can get on with generating type
34593459+aliases:</p>
34603460+<pre><code class="language-rust">// src/lib.rs
34613461+34623462+use quote::{quote, format_ident};
34633463+34643464+fn generate_type_aliases(
34653465+ fn_arg_types: &amp;[Box&lt;Type&gt;],
34663466+ fn_return_type: Box&lt;Type&gt;,
34673467+ fn_name: &amp;syn::Ident,
34683468+) -&gt; Vec&lt;proc_macro2::TokenStream&gt; { // 1
34693469+34703470+ let type_t0 = format_ident!(&quot;_{}_T0&quot;, fn_name); // 2
34713471+ let mut type_aliases = vec![quote! { type #type_t0 = #fn_return_type }];
34723472+34733473+ // 3
34743474+ for (i, t) in (1..).zip(fn_arg_types.into_iter().rev()) {
34753475+ let p = format_ident!(&quot;_{}_{}&quot;, fn_name, format!(&quot;T{}&quot;, i - 1));
34763476+ let n = format_ident!(&quot;_{}_{}&quot;, fn_name, format!(&quot;T{}&quot;, i));
34773477+34783478+ type_aliases.push(quote! {
34793479+ type #n = impl Fn(#t) -&gt; #p
34803480+ });
34813481+ }
34823482+34833483+ return type_aliases;
34843484+}
34853485+34863486+</code></pre>
34873487+<p><strong>1. The return value</strong><br />
34883488+We are returning a <code>Vec&lt;proc_macro2::TokenStream&gt;</code>, i. e., a
34893489+list of <code>TokenStream</code>s, where each item is a type alias.</p>
34903490+<p><strong>2. Format identifier?</strong><br />
34913491+I've got some explanation to do on this line. Clearly, we
34923492+are trying to write the first type alias, and initialize our
34933493+<code>TokenStream</code> vector with <code>T0</code>, because it is different from
34943494+the others:</p>
34953495+<pre><code class="language-rust">type T0 = something
34963496+// the others are of the form
34973497+type Tr = impl Fn(something) -&gt; something
34983498+</code></pre>
34993499+<p><code>format_ident!</code> is similar to <code>format!</code>. Instead of
35003500+returning a formatted string, it returns a <code>syn::Ident</code>.
35013501+Therefore, <code>type_t0</code> is actually an identifier for, in the
35023502+case of our <code>add</code> function, <code>_add_T0</code>. Why is this
35033503+formatting important? Namespacing.</p>
35043504+<p>Picture this, we have two functions, <code>add</code> and <code>subtract</code>,
35053505+that we wish to curry with our macro:</p>
35063506+<pre><code class="language-rust">#[curry]
35073507+fn add(...) -&gt; u32 { ... }
35083508+35093509+#[curry]
35103510+fn sub(...) -&gt; u32 { ... }
35113511+</code></pre>
35123512+<p>Here is the same but with macros expanded:</p>
35133513+<pre><code class="language-rust">type T0 = u32;
35143514+type T1 = impl Fn(u32) -&gt; T0;
35153515+fn add( ... ) -&gt; T1 { ... }
35163516+35173517+type T0 = u32;
35183518+type T1 = impl Fn(u32) -&gt; T0;
35193519+fn sub( ... ) -&gt; T1 { ... }
35203520+</code></pre>
35213521+<p>We end up with two definitions of <code>T0</code>! Now, if we do the
35223522+little <code>format_ident!</code> dance we did up there:</p>
35233523+<pre><code class="language-rust">type _add_T0 = u32;
35243524+type _add_T1 = impl Fn(u32) -&gt; _add_T0;
35253525+fn add( ... ) -&gt; _add_T1 { ... }
35263526+35273527+type _sub_T0 = u32;
35283528+type _sub_T1 = impl Fn(u32) -&gt; _sub_T0;
35293529+fn sub( ... ) -&gt; _sub_T1 { ... }
35303530+</code></pre>
35313531+<p>Voilà! The type aliases don't tread on each other. Remember
35323532+to import <code>format_ident</code> from the <code>quote</code> crate.</p>
35333533+<p><strong>3. The TokenStream Vector</strong></p>
35343534+<p>We iterate over our types in reverse order (<code>T0</code> is the
35353535+last return, <code>T1</code> is the second last, so on), assign a
35363536+number to each iteration with <code>zip</code>, generate type names
35373537+with <code>format_ident</code>, push a <code>TokenStream</code> with the help of
35383538+<code>quote</code> and variable interpolation.</p>
35393539+<p>If you are wondering why we used <code>(1..).zip()</code> instead of
35403540+<code>.enumerate()</code>, it's because we wanted to start counting
35413541+from 1 instead of 0 (we are already done with <code>T0</code>!).</p>
35423542+<h4 id="getting-it-together">Getting it together</h4>
35433543+<p>I promised we'd have a fully working macro by the end of
35443544+last section. I lied, we have to tie everything together in
35453545+our <code>generate_curry</code> function:</p>
35463546+<pre><code class="language-rust">// src/lib.rs
35473547+35483548+ fn generate_curry(parsed: ItemFn) -&gt; proc_macro2::TokenStream {
35493549+ let fn_body = parsed.block;
35503550+ let sig = parsed.sig;
35513551+ let vis = parsed.vis;
35523552+ let fn_name = sig.ident;
35533553+ let fn_args = sig.inputs;
35543554+ let fn_return_type = sig.output;
35553555+35563556+ let arg_idents = extract_arg_idents(fn_args.clone());
35573557+ let first_ident = &amp;arg_idents.first().unwrap();
35583558+ let curried_body = generate_body(&amp;arg_idents[1..], fn_body.clone());
35593559+35603560++ let arg_types = extract_arg_types(fn_args.clone());
35613561++ let first_type = &amp;arg_types.first().unwrap();
35623562++ let type_aliases = generate_type_aliases(
35633563++ &amp;arg_types[1..],
35643564++ extract_return_type(fn_return_type),
35653565++ &amp;fn_name,
35663566++ );
35673567+35683568++ let return_type = format_ident!(&quot;_{}_{}&quot;, &amp;fn_name, format!(&quot;T{}&quot;, type_aliases.len() - 1));
35693569+35703570++ return quote! {
35713571++ #(#type_aliases);* ;
35723572++ #vis fn #fn_name (#first_ident: #first_type) -&gt; #return_type {
35733573++ #curried_body ;
35743574++ }
35753575++ };
35763576+ }
35773577+</code></pre>
35783578+<p>Most of the additions are self explanatory, I'll go through
35793579+the return statement with you. We are returning a <code>quote!{ ... }</code>, so a <code>proc_macro2::TokenStream</code>. We are iterating
35803580+through the <code>type_aliases</code> variable, which you might recall,
35813581+is a <code>Vec&lt;TokenStream&gt;</code>. You might notice the sneaky
35823582+semicolon before the <code>*</code>. This basically tells <code>quote</code>, to
35833583+insert an item, then a semicolon, and then the next one,
35843584+another semicolon, and so on. The semicolon is a separator.
35853585+We need to manually insert another semicolon at the end of
35863586+it all, <code>quote</code> doesn't insert a separator at the end of the
35873587+iteration.</p>
35883588+<p>We retain the visibility and name of our original function.
35893589+Our curried function takes as args, just the first argument
35903590+of our original function. The return type of our curried
35913591+function is actually, the last type alias we create. If you
35923592+think back to our manually curried <code>add</code> function, we
35933593+returned <code>T2</code>, which was in fact, the last type alias we
35943594+created.</p>
35953595+<p>I am sure, at this point, you are itching to test this out,
35963596+but before that, let me introduce you to some good methods
35973597+of debugging proc-macro code.</p>
35983598+<h3 id="debugging-and-testing">Debugging and Testing</h3>
35993599+<p>Install <code>cargo-expand</code> via:</p>
36003600+<pre><code>cargo install cargo-expand
36013601+</code></pre>
36023602+<p><code>cargo-expand</code> is a neat little tool that expands your macro
36033603+in places where it is used, and lets you view the generated
36043604+code! For example:</p>
36053605+<pre><code class="language-shell"># create a bin package hello
36063606+$ cargo new hello
36073607+36083608+# view the expansion of the println! macro
36093609+$ cargo expand
36103610+36113611+#![feature(prelude_import)]
36123612+#[prelude_import]
36133613+use std::prelude::v1::*;
36143614+#[macro_use]
36153615+extern crate std;
36163616+fn main() {
36173617+ {
36183618+ ::std::io::_print(::core::fmt::Arguments::new_v1(
36193619+ &amp;[&quot;Hello, world!\n&quot;],
36203620+ &amp;match () {
36213621+ () =&gt; [],
36223622+ },
36233623+ ));
36243624+ };
36253625+}
36263626+</code></pre>
36273627+<p>Writing proc-macros without <code>cargo-expand</code> is tantamount to
36283628+driving a vehicle without rear view mirrors! Keep an eye on
36293629+what is going on behind your back.</p>
36303630+<p>Now, your macro won't always compile, you might just recieve
36313631+the bee movie script as an error. <code>cargo-expand</code> will not
36323632+work in such cases. I would suggest printing out your
36333633+variables to inspect them. <code>TokenStream</code> implements
36343634+<code>Display</code> as well as <code>Debug</code>. We don't always have to be
36353635+respectable programmers. Just print it.</p>
36363636+<p>Enough of that, lets get testing:</p>
36373637+<pre><code class="language-rust">// tests/smoke.rs
36383638+36393639+#![feature(type_alias_impl_trait)]
36403640+36413641+#[crate_name::curry]
36423642+fn add(x: u32, y: u32, z: u32) -&gt; u32 {
36433643+ x + y + z
36443644+}
36453645+36463646+#[test]
36473647+fn works() {
36483648+ assert_eq!(15, add(4)(5)(6));
36493649+}
36503650+</code></pre>
36513651+<p>Run <code>cargo +nightly test</code>. You should see a pleasing
36523652+message:</p>
36533653+<pre><code>running 1 test
36543654+test tests::works ... ok
36553655+</code></pre>
36563656+<p>Take a look at the expansion for our curry macro, via
36573657+<code>cargo +nightly expand --tests smoke</code>:</p>
36583658+<pre><code class="language-rust">type _add_T0 = u32;
36593659+type _add_T1 = impl Fn(u32) -&gt; _add_T0;
36603660+type _add_T2 = impl Fn(u32) -&gt; _add_T1;
36613661+fn add(x: u32) -&gt; _add_T2 {
36623662+ return (move |y| {
36633663+ move |z| {
36643664+ return x + y + z;
36653665+ }
36663666+ });
36673667+}
36683668+36693669+// a bunch of other stuff generated by #[test] and assert_eq!
36703670+</code></pre>
36713671+<p>A sight for sore eyes.</p>
36723672+<p>Here is a more complex example that generates ten multiples
36733673+of the first ten natural numbers:</p>
36743674+<pre><code class="language-rust">#[curry]
36753675+fn product(x: u32, y: u32) -&gt; u32 {
36763676+ x * y
36773677+}
36783678+36793679+fn multiples() -&gt; Vec&lt;Vec&lt;u32&gt;&gt;{
36803680+ let v = (1..=10).map(product);
36813681+ return (1..=10)
36823682+ .map(|x| v.clone().map(|f| f(x)).collect())
36833683+ .collect();
36843684+}
36853685+</code></pre>
36863686+<h3 id="notes">Notes</h3>
36873687+<p>I didn't quite explain why we use <code>move |arg|</code> in our
36883688+closure. This is because we want to take ownership of the
36893689+variable supplied to us. Take a look at this example:</p>
36903690+<pre><code class="language-rust">let v = add(5);
36913691+let g;
36923692+{
36933693+ let x = 5;
36943694+ g = v(x);
36953695+}
36963696+println!(&quot;{}&quot;, g(2));
36973697+</code></pre>
36983698+<p>Variable <code>x</code> goes out of scope before <code>g</code> can return a
36993699+concrete value. If we take ownership of <code>x</code> by <code>move</code>ing it
37003700+into our closure, we can expect this to work reliably. In
37013701+fact, rustc understands this, and forces you to use <code>move</code>.</p>
37023702+<p>This usage of <code>move</code> is exactly why <strong>a curried function
37033703+without a return is useless</strong>. Every variable we pass to our
37043704+curried function gets moved into its local scope. Playing
37053705+with these variables cannot cause a change outside this
37063706+scope. Returning is our only method of interaction with
37073707+anything beyond this function.</p>
37083708+<h3 id="conclusion">Conclusion</h3>
37093709+<p>Currying may not seem to be all that useful. Curried
37103710+functions are unwieldy in Rust because the standard library
37113711+is not built around currying. If you enjoy the possibilities
37123712+posed by currying, consider taking a look at Haskell or
37133713+Scheme.</p>
37143714+<p>My original intention with <a href="https://peppe.rs">peppe.rs</a> was
37153715+to post condensed articles, a micro blog, but this one
37163716+turned out extra long.</p>
37173717+<p>Perhaps I should call it a 'macro' blog :)</p>
37183718+</description>
37193719+ <link>https://oppi.li/posts/auto-currying_rust_functions/</link>
37203720+ <pubDate>Fri, 08 May 2020 00:00:00 +0000</pubDate>
37213721+ <guid>https://oppi.li/posts/auto-currying_rust_functions/</guid>
37223722+ </item>
37233723+ <item>
37243724+ <title>pixel art in GIMP</title>
37253725+ <description><p>I've always been an admirer of pixel art, because of it's
37263726+simplicity and it's resemblance to bitmap font design.
37273727+Recently, I decided to take the dive and make some art of my
37283728+own.</p>
37293729+<p>I used GIMP because I am fairly familiar with it. Aseprite
37303730+seems to be the editor of choice for animated pixel art
37313731+though.</p>
37323732+<h3 id="setting-up-the-canvas">Setting up the canvas</h3>
37333733+<p>Picking a canvas size is daunting. Too small, and you won't
37343734+be able to fit in enough detail to make a legible piece. Too
37353735+big and you've got too many pixels to work with!</p>
37363736+<p>I would suggest starting out with anywhere between 100x100
37373737+and 200x200. <a href="https://cdn.oppi.li/u9.png">Here's</a> a sample
37383738+configuration.</p>
37393739+<p>Sometimes I use a 10x10 grid, <code>View &gt; Show Grid</code> and <code>Edit &gt; Preferences &gt; Default Grid &gt; Spacing</code>, but that can get
37403740+jarring, so I throw down a couple of guides, drag right or
37413741+down from the left or top gutters for vertical and
37423742+horizontal guides respectively.</p>
37433743+<h3 id="choosing-a-brush">Choosing a Brush</h3>
37443744+<p>The most important part of our setup is the brush. Use the
37453745+Pencil Tool (<code>n</code> on the keyboard) for hard edge drawings.
37463746+Here's a small comparison if you don't know the difference
37473747+between a hard edge and a soft edge:</p>
37483748+<p><img src="https://cdn.oppi.li/kz.png" alt="Hard edge vs Soft Edge" /></p>
37493749+<p>I turn the size down all the way to 1 (<code>[</code> on the keyboard).
37503750+Set <code>Dynamics</code> off. <a href="https://cdn.oppi.li/Fs.png">Here's</a> a
37513751+sample brush configuration.</p>
37523752+<h3 id="laying-down-the-pixels">Laying down the pixels!</h3>
37533753+<p>With the boring stuff out of the way, we can start with our
37543754+piece. I usually follow a three step process:</p>
37553755+<ul>
37563756+<li>draw a rough outline
37573757+</li>
37583758+<li>fill in the shadows
37593759+</li>
37603760+<li>add highlights
37613761+</li>
37623762+</ul>
37633763+<p>But this process is better explained with an example: an
37643764+onigiri. Let us start off with a 100x100 canvas.</p>
37653765+<h4 id="drawing-the-outline">Drawing the outline</h4>
37663766+<p>For the most part, our figure will be symmetric. If you are
37673767+on GIMP 2.10+, you can take advantage of the Symmetry
37683768+Painting feature. Go ahead and enable vertical symmetry,
37693769+<code>Window &gt; Dockable Dialogs &gt; Symmetry Painting</code> and
37703770+<code>Symmetry Painting &gt; Symmetry &gt; Mirror &gt; Vertical</code>.</p>
37713771+<p>If you are running an older version of GIMP, draw in the
37723772+left side, duplicate the layer, flip it horizontally, and
37733773+merge it with the original.</p>
37743774+<p>Your outline might look something like this:</p>
37753775+<p><img src="https://cdn.oppi.li/mn.png" alt="" /></p>
37763776+<p>Go ahead and fill it in with the fill tool (<code>Shift + b</code> on
37773777+the keyboard), add in some seaweed as well, preferably on a
37783778+different layer. You can toggle symmetry on and off to save
37793779+yourself some time.</p>
37803780+<p><img src="https://cdn.oppi.li/xu.png" alt="" /></p>
37813781+<h4 id="shadows">Shadows</h4>
37823782+<p>For now, let us focus on the shadows on the object itself,
37833783+we'll come back to the shadows cast by the object on the
37843784+surface later.</p>
37853785+<p>Shadows on any surface always follow the shape of the
37863786+surface. A spherical onigiri would have a circular shadow:</p>
37873787+<p><img src="https://cdn.oppi.li/FU.png" alt="" /></p>
37883788+<p>A couple of noticeable changes:</p>
37893789+<p><strong>Layers</strong>: The layer containing the seaweed has been hidden.<br />
37903790+<strong>Color</strong>: The color of the shadow is just a slightly
37913791+lighter version of the original object (reduce the Value on
37923792+the HSV scale).<br />
37933793+<strong>Area</strong>: The shadow does not go all the way (notice the bottom
37943794+edges).</p>
37953795+<p>The shadow does not go all the way because we will be
37963796+filling in that area with another, darker shadow! An image
37973797+might explain better:</p>
37983798+<p><img src="https://cdn.oppi.li/Br.png" alt="" /></p>
37993799+<p>To emulate soft lights, reduce the value by 2 to 3 points
38003800+every iteration. Notice how area <code>1</code> is much larger than
38013801+area <code>4</code>. This is because an onigiri resembles a bottom
38023802+heavy oblate spheroid, a sphere that is slightly fatter
38033803+around the lower bottom, and areas <code>1</code> and <code>2</code> catch more
38043804+light than areas <code>3</code> and <code>4</code>.</p>
38053805+<p>Do the same with the seaweed. The seaweed, being a smaller,
38063806+flatter object, doesn't cast much of a shadow, so stop with
38073807+1 or 2 iterations of the gradient:</p>
38083808+<p><img src="https://cdn.oppi.li/T3.png" alt="" /></p>
38093809+<p>We're getting there!</p>
38103810+<h4 id="highlights">Highlights</h4>
38113811+<p>This step handles the details on the strongly illuminated
38123812+portions of the object. Seaweed is a bit glossy, lighten the
38133813+edges to make it seem shiny. The rice is not as shiny, but
38143814+it does form an uneven surface. Add in some shadows to
38153815+promote the idea of rice grains. Here is the finished
38163816+result:</p>
38173817+<p><img src="https://cdn.oppi.li/VE.png" alt="" /></p>
38183818+<h3 id="finishing-touches">Finishing Touches</h3>
38193819+<p>Some color correction and <code>a e s t h e t i c</code> Japanese text
38203820+later, our piece is complete!</p>
38213821+<p><img src="https://cdn.oppi.li/cn.png" alt="" /></p>
38223822+<p>Hold on, why is it so tiny? Well, that's because our canvas
38233823+was 100x100, head over to <code>Image &gt; Scale Image</code>, set
38243824+<code>Quality &gt; Interpolation</code> to <code>None</code> and scale it up to
38253825+700x700, et voilà!</p>
38263826+<p><img src="https://cdn.oppi.li/CH.png" alt="" /></p>
38273827+</description>
38283828+ <link>https://oppi.li/posts/pixel_art_in_GIMP/</link>
38293829+ <pubDate>Wed, 08 Apr 2020 00:00:00 +0000</pubDate>
38303830+ <guid>https://oppi.li/posts/pixel_art_in_GIMP/</guid>
38313831+ </item>
38323832+ <item>
38333833+ <title>rapid refactoring with vim</title>
38343834+ <description><p>Last weekend, I was tasked with refactoring the 96 unit
38353835+tests on
38363836+<a href="https://github.com/ruma/ruma-events/pull/70">ruma-events</a>
38373837+to use strictly typed json objects using <code>serde_json::json!</code>
38383838+instead of raw strings. It was rather painless thanks to
38393839+vim :)</p>
38403840+<p>Here's a small sample of what had to be done (note the lines
38413841+prefixed with the arrow):</p>
38423842+<pre><code class="language-rust">→ use serde_json::{from_str};
38433843+38443844+ #[test]
38453845+ fn deserialize() {
38463846+ assert_eq!(
38473847+→ from_str::&lt;Action&gt;(r#&quot;{&quot;set_tweak&quot;: &quot;highlight&quot;}&quot;#),
38483848+ Action::SetTweak(Tweak::Highlight { value: true })
38493849+ );
38503850+ }
38513851+</code></pre>
38523852+<p>had to be converted to:</p>
38533853+<pre><code class="language-rust">→ use serde_json::{from_value};
38543854+38553855+ #[test]
38563856+ fn deserialize() {
38573857+ assert_eq!(
38583858+→ from_value::&lt;Action&gt;(json!({&quot;set_tweak&quot;: &quot;highlight&quot;})),
38593859+ Action::SetTweak(Tweak::Highlight { value: true })
38603860+ );
38613861+ }
38623862+</code></pre>
38633863+<h3 id="the-arglist">The arglist</h3>
38643864+<p>For the initial pass, I decided to handle imports, this was
38653865+a simple find and replace operation, done to all the files
38663866+containing tests. Luckily, modules (and therefore files)
38673867+containing tests in Rust are annotated with the
38683868+<code>#[cfg(test)]</code> attribute. I opened all such files:</p>
38693869+<pre><code class="language-bash"># `grep -l pattern files` lists all the files
38703870+# matching the pattern
38713871+38723872+vim $(grep -l 'cfg\(test\)' ./**/*.rs)
38733873+38743874+# expands to something like:
38753875+vim push_rules.rs room/member.rs key/verification/lib.rs
38763876+</code></pre>
38773877+<p>Starting vim with more than one file at the shell prompt
38783878+populates the arglist. Hit <code>:args</code> to see the list of
38793879+files currently ready to edit. The square [brackets]
38803880+indicate the current file. Navigate through the arglist
38813881+with <code>:next</code> and <code>:prev</code>. I use tpope's vim-unimpaired
38823882+<a href="https://github.com/tpope/vim-unimpaired">^un</a>, which adds <code>]a</code> and <code>[a</code>, mapped to <code>:next</code> and
38833883+<code>:prev</code>.</p>
38843884+<p>It also handles various other mappings, <code>]q</code> and <code>[q</code> to
38853885+navigate the quickfix list for example</p>
38863886+<p>All that's left to do is the find and replace, for which we
38873887+will be using vim's <code>argdo</code>, applying a substitution to
38883888+every file in the arglist:</p>
38893889+<pre><code>:argdo s/from_str/from_value/g
38903890+</code></pre>
38913891+<h3 id="the-quickfix-list">The quickfix list</h3>
38923892+<p>Next up, replacing <code>r#&quot; ... &quot;#</code> with <code>json!( ... )</code>. I
38933893+couldn't search and replace that trivially, so I went with a
38943894+macro call [^macro] instead, starting with the cursor on
38953895+'r', represented by the caret, in my attempt to breakdown
38963896+the process:</p>
38973897+<p>[^macro]: <code>:help recording</code></p>
38983898+<pre><code>BUFFER: r#&quot; ... &quot;#;
38993899+ ^
39003900+39013901+ACTION: vllsjson!(
39023902+39033903+BUFFER json!( ... &quot;#;
39043904+ ^
39053905+39063906+ACTION: &lt;esc&gt;$F#
39073907+39083908+BUFFER: json!( ... &quot;#;
39093909+ ^
39103910+39113911+ACTION: vhs)&lt;esc&gt;
39123912+39133913+BUFFER: json!( ... );
39143914+</code></pre>
39153915+<p>Here's the recorded [^rec] macro in all its glory:
39163916+<code>vllsjson!(&lt;esc&gt;$F#vhs)&lt;esc&gt;</code>.</p>
39173917+<p>[^rec]: When I'm recording a macro, I prefer starting out by
39183918+storing it in register <code>q</code>, and then copying it over to
39193919+another register if it works as intended. I think of <code>qq</code> as
39203920+'quick record'.</p>
39213921+<p>Great! So now we just go ahead, find every occurrence of
39223922+<code>r#</code> and apply the macro right? Unfortunately, there were
39233923+more than a few occurrences of raw strings that had to stay
39243924+raw strings. Enter, the quickfix list.</p>
39253925+<p>The idea behind the quickfix list is to jump from one
39263926+position in a file to another (maybe in a different file),
39273927+much like how the arglist lets you jump from one file to
39283928+another.</p>
39293929+<p>One of the easiest ways to populate this list with a bunch
39303930+of positions is to use <code>vimgrep</code>:</p>
39313931+<pre><code># basic usage
39323932+:vimgrep pattern files
39333933+39343934+# search for raw strings
39353935+:vimgrep 'r#' ./**/*.rs
39363936+</code></pre>
39373937+<p>Like <code>:next</code> and <code>:prev</code>, you can navigate the quickfix list
39383938+with <code>:cnext</code> and <code>:cprev</code>. Every time you move up or down
39393939+the list, vim indicates your index:</p>
39403940+<pre><code>(1 of 131): r#&quot;{&quot;set_tweak&quot;: &quot;highlight&quot;}&quot;#;
39413941+</code></pre>
39423942+<p>And just like <code>argdo</code>, you can <code>cdo</code> to apply commands to
39433943+<em>every</em> match in the quickfix list:</p>
39443944+<pre><code>:cdo norm! @q
39453945+</code></pre>
39463946+<p>But, I had to manually pick out matches, and it involved
39473947+some button mashing.</p>
39483948+<h3 id="external-filtering">External Filtering</h3>
39493949+<p>Some code reviews later, I was asked to format all the json
39503950+inside the <code>json!</code> macro. All you have to do is pass a
39513951+visual selection through a pretty json printer. Select the
39523952+range to be formatted in visual mode, and hit <code>:</code>, you will
39533953+notice the command line displaying what seems to be
39543954+gibberish:</p>
39553955+<pre><code>:'&lt;,'&gt;
39563956+</code></pre>
39573957+<p><code>'&lt;</code> and <code>'&gt;</code> are <em>marks</em> [^mark-motions]. More
39583958+specifically, they are marks that vim sets automatically
39593959+every time you make a visual selection, denoting the start
39603960+and end of the selection.</p>
39613961+<p>[^mark-motions]: <code>:help mark-motions</code></p>
39623962+<p>A range is one or more line specifiers separated by a <code>,</code>:</p>
39633963+<pre><code>:1,7 lines 1 through 7
39643964+:32 just line 32
39653965+:. the current line
39663966+:.,$ the current line to the last line
39673967+:'a,'b mark 'a' to mark 'b'
39683968+</code></pre>
39693969+<p>Most <code>:</code> commands can be prefixed by ranges. <code>:help usr_10.txt</code> for more on that.</p>
39703970+<p>Alright, lets pass json through <code>python -m json.tool</code>, a
39713971+json formatter that accepts <code>stdin</code> (note the use of <code>!</code> to
39723972+make use of an external program):</p>
39733973+<pre><code>:'&lt;,'&gt;!python -m json.tool
39743974+</code></pre>
39753975+<p>Unfortunately that didn't quite work for me because the
39763976+range included some non-json text as well, a mix of regex
39773977+and macros helped fix that. I think you get the drift.</p>
39783978+<p>Another fun filter I use from time to time is <code>:!sort</code>, to
39793979+sort css attributes, or <code>:!uniq</code> to remove repeated imports.</p>
39803980+</description>
39813981+ <link>https://oppi.li/posts/rapid_refactoring_with_vim/</link>
39823982+ <pubDate>Tue, 31 Mar 2020 00:00:00 +0000</pubDate>
39833983+ <guid>https://oppi.li/posts/rapid_refactoring_with_vim/</guid>
39843984+ </item>
39853985+ <item>
39863986+ <title>font size fallacies</title>
39873987+ <description><p>I am not an expert with fonts, but I do have some
39883988+experience <a href="https://github.com/nerdypepper/scientifica">^exp</a>, and common sense. This post aims to debunk some
39893989+misconceptions about font sizes!</p>
39903990+<p>11 px on your display is <em>probably not</em> 11 px on my display.
39913991+Let's do some quick math. I have two displays, 1366x768 @
39923992+21&quot; and another with 1920x1080 @ 13&quot;, call them <code>A</code> and
39933993+<code>B</code> for now.</p>
39943994+<p>Display <code>A</code> has 1,049,088 pixels. A pixel is a square, of
39953995+side say, <code>s</code> cm. The total area covered by my 21&quot; display
39963996+is about 1,066 cm^2 (41x26). Thus,</p>
39973997+<pre><code>Display A
39983998+Dimensions: 1366x768 @ 21&quot; (41x26 sq. cm)
39993999+1,049,088 s^2 = 1066
40004000+ s = 0.0318 cm (side of a pixel on Display A)
40014001+</code></pre>
40024002+<p>Bear with me, as I repeat the number crunching for Display
40034003+<code>B</code>:</p>
40044004+<pre><code>Display B
40054005+Dimensions: 1920x1080 @ 13&quot; (29.5x16.5 sq. cm)
40064006+2,073,600 s^2 = 486.75
40074007+ s = 0.0153 cm (side of a pixel on Display B)
40084008+</code></pre>
40094009+<p>The width of a pixel on Display <code>A</code> is <em>double</em> the width of a
40104010+pixel on Display <code>B</code>. The area occupied by a pixel on Display
40114011+<code>A</code> is <em>4 times</em> the area occupied by a pixel on Display <code>B</code>.</p>
40124012+<p><em>The size of a pixel varies from display to display!</em></p>
40134013+<p>A 5x11 bitmap font on Display <code>A</code> would be around 4 mm tall
40144014+whereas the same bitmap font on Display <code>B</code> would be around
40154015+1.9 mm tall. A 11 px tall character on <code>B</code> is visually
40164016+equivalent to a 5 px character on <code>A</code>. When you view a
40174017+screenshot of Display <code>A</code> on Display <code>B</code>, the contents are
40184018+shrunk down by a factor of 2!</p>
40194019+<p>So screen resolution is not enough, how else do we measure
40204020+size? Pixel Density! Keen readers will realize that the 5^th
40214021+grade math problem we solved up there showcases pixel
40224022+density, or, pixels per cm (PPCM). Usually we deal with
40234023+pixels per inch (PPI).</p>
40244024+<p><strong>Note:</strong> PPI is not to be confused with DPI <a href="https://en.wikipedia.org/wiki/Dots_per_inch">^dpi</a> (dots
40254025+per inch). DPI is defined for printers.</p>
40264026+<p>In our example, <code>A</code> is a 75 ppi display and <code>B</code> is around
40274027+165 ppi <a href="https://www.sven.de/dpi/">^ppi</a>. A low ppi display appears to be
40284028+'pixelated', because the pixels are more prominent, much
40294029+like Display <code>A</code>. A higher ppi usually means you can view
40304030+larger images and render crispier fonts. The average desktop
40314031+display can stuff 100-200 pixels per inch. Smart phones
40324032+usually fall into the 400-600 ppi (XXXHDPI) category. The
40334033+human eye fails to differentiate detail past 300 ppi.</p>
40344034+<p><em>So ... streaming an 8K video on a 60&quot; TV provides the same
40354035+clarity as a HD video on a smart phone?</em></p>
40364036+<p>Absolutely. Well, clarity is subjective, but the amount of
40374037+detail you can discern on mobile displays has always been
40384038+limited. Salty consumers of the Xperia 1 <a href="https://en.wikipedia.org/wiki/Sony_Xperia_1">^sony</a> will say
40394039+otherwise.</p>
40404040+<p>Maybe I will talk about font rendering in another post, but
40414041+thats all for now. Don't judge a font size by its
40424042+screenshot.</p>
40434043+</description>
40444044+ <link>https://oppi.li/posts/font_size_fallacies/</link>
40454045+ <pubDate>Mon, 16 Mar 2020 00:00:00 +0000</pubDate>
40464046+ <guid>https://oppi.li/posts/font_size_fallacies/</guid>
40474047+ </item>
40484048+ <item>
40494049+ <title>termux tandem</title>
40504050+ <description><p>I learnt about <code>termux</code> from a friend on IRC recently.
40514051+It looked super gimmicky to me at first, but it eventually
40524052+proved to be useful. Here's what I use it for:</p>
40534053+<h3 id="rsync">rsync</h3>
40544054+<p>Ever since I degoogled my android device, syncing files
40554055+between my phone and my PC has always been a pain. I'm
40564056+looking at you MTP. But, with <code>termux</code> and <code>sshd</code> all set up,
40574057+it's as simple as:</p>
40584058+<pre><code>$ arp
40594059+Address HWtype HWad ...
40604060+192.168.43.187 ether d0:0 ...
40614061+40624062+$ rsync -avz 192.168.43.187:~/frogs ~/pics/frogs
40634063+</code></pre>
40644064+<h3 id="ssh-tmux">ssh &amp; tmux</h3>
40654065+<p>My phone doubles as a secondary view into my main machine
40664066+with <code>ssh</code> and <code>tmux</code>. When I am away from my PC (read:
40674067+sitting across the room), I check build status and IRC
40684068+messages by <code>ssh</code>ing into a tmux session running the said
40694069+build or weechat.</p>
40704070+<h3 id="file-uploads">file uploads</h3>
40714071+<p>Not being able to access my (ssh-only) file host was
40724072+crippling. With a <code>bash</code> instance on my phone, I just copied
40734073+over my ssh keys, and popped in a file upload script (a
40744074+glorified <code>scp</code>). Now I just have to figure out a way to
40754075+clean up these file names ...</p>
40764076+<pre><code>~/storage/pictures/ $ ls
40774077+02muf5g7b2i41.jpg 7alt3cwg77841.jpg cl4bsrge7id11.png
40784078+mtZabXG.jpg p8d5c584f2841.jpg vjUxGjq.jpg
40794079+</code></pre>
40804080+<h3 id="cmus">cmus</h3>
40814081+<p>Alright, I don't really listen to music via <code>cmus</code>, but I
40824082+did use it a couple times when my default music player was
40834083+acting up. <code>cmus</code> is a viable option:</p>
40844084+<p><a href="https://cdn.oppi.li/CP.jpg"><img src="https://cdn.oppi.li/CP.jpg" alt="" /></a></p>
40854085+</description>
40864086+ <link>https://oppi.li/posts/termux_tandem/</link>
40874087+ <pubDate>Sat, 07 Mar 2020 00:00:00 +0000</pubDate>
40884088+ <guid>https://oppi.li/posts/termux_tandem/</guid>
40894089+ </item>
40904090+ <item>
40914091+ <title>call to ARMs</title>
40924092+ <description><p>My 4th semester involves ARM programming. And proprietary
40934093+tooling (Keil C). But we don't do that here.</p>
40944094+<h3 id="building">Building</h3>
40954095+<p>Assembling and linking ARM binaries on non-ARM architecture
40964096+devices is fairly trivial. I went along with the GNU cross
40974097+bare metal toolchain binutils, which provides <code>arm-as</code> and
40984098+<code>arm-ld</code> (among a bunch of other utils that I don't care
40994099+about for now).</p>
41004100+<p>Assemble <code>.s</code> files with:</p>
41014101+<pre><code class="language-shell">arm-none-eabi-as main.s -g -march=armv8.1-a -o main.out
41024102+</code></pre>
41034103+<p>The <code>-g</code> flag generates extra debugging information that
41044104+<code>gdb</code> picks up. The <code>-march</code> option establishes target
41054105+architecture.</p>
41064106+<p>Link <code>.o</code> files with:</p>
41074107+<pre><code class="language-shell">arm-none-eabi-ld main.out -o main
41084108+</code></pre>
41094109+<h3 id="running-and-debugging">Running (and Debugging)</h3>
41104110+<p>Things get interesting here. <code>gdb</code> on your x86 machine
41114111+cannot read nor execute binaries compiled for ARM. So, we
41124112+simulate an ARM processor using <code>qemu</code>. Now qemu allows you
41134113+to run <code>gdbserver</code> on startup. Connecting our local <code>gdb</code>
41144114+instance to <code>gdbserver</code> gives us a view into the program's
41154115+execution. Easy!</p>
41164116+<p>Run <code>qemu</code>, with <code>gdbserver</code> on port <code>1234</code>, with our ARM
41174117+binary, <code>main</code>:</p>
41184118+<pre><code class="language-shell">qemu-arm -singlestep -g 1234 main
41194119+</code></pre>
41204120+<p>Start up <code>gdb</code> on your machine, and connect to <code>qemu</code>'s
41214121+<code>gdbserver</code>:</p>
41224122+<pre><code>(gdb) set architecture armv8-a
41234123+(gdb) target remote localhost:1234
41244124+(gdb) file main
41254125+Reading symbols from main... # yay!
41264126+</code></pre>
41274127+<h3 id="gdb-enhanced">GDB Enhanced</h3>
41284128+<p><code>gdb</code> is cool, but it's not nearly as comfortable as well
41294129+fleshed out emulators/IDEs like Keil. Watching registers,
41304130+CPSR and memory chunks update <em>is</em> pretty fun.</p>
41314131+<p>I came across <code>gdb</code>'s TUI mode (hit <code>C-x C-a</code> or type <code>tui enable</code> at the prompt). TUI mode is a godsend. It highlights
41324132+the current line of execution, shows you disassembly
41334133+outputs, updated registers, active breakpoints and more.</p>
41344134+<p><em>But</em>, it is an absolute eyesore.</p>
41354135+<p>Say hello to <a href="https://github.com/hugsy/gef">GEF</a>! &quot;GDB
41364136+Enhanced Features&quot; teaches our old dog some cool new tricks.
41374137+Here are some additions that made my ARM debugging
41384138+experience loads better:</p>
41394139+<ul>
41404140+<li>Memory watches
41414141+</li>
41424142+<li>Register watches, with up to 7 levels of deref (overkill,
41434143+I agree)
41444144+</li>
41454145+<li>Stack tracing
41464146+</li>
41474147+</ul>
41484148+<p>And it's pretty! See for yourself:</p>
41494149+<p><a href="https://cdn.oppi.li/wq.png"><img src="https://cdn.oppi.li/wq.png" alt="" /></a></p>
41504150+<h3 id="editing">Editing</h3>
41514151+<p>Vim, with <code>syntax off</code> because it
41524152+dosen't handle GNU ARM syntax too well.</p>
41534153+</description>
41544154+ <link>https://oppi.li/posts/call_to_ARMs/</link>
41554155+ <pubDate>Fri, 07 Feb 2020 00:00:00 +0000</pubDate>
41564156+ <guid>https://oppi.li/posts/call_to_ARMs/</guid>
41574157+ </item>
41584158+ <item>
41594159+ <title>color conundrum</title>
41604160+ <description><p>This piece aims to highlight (pun intended) some of the
41614161+reasons behind my <a href="https://cdn.oppi.li/bF.png">color
41624162+free</a> editor setup.</p>
41634163+<p>Imagine highlighting an entire book because <em>all</em> of it is
41644164+important. That is exactly what (most) syntax highlighting
41654165+does. It is difficult for the human eye to filter out noise
41664166+in rainbow barf. Use color to draw attention, not diverge
41674167+it.</p>
41684168+<p>At the same time, a book devoid of color is <em>boring!</em> What
41694169+is the takeaway from this 10 line paragraph? What are the
41704170+technical terms used?</p>
41714171+<p>Prose and code are certainly different, but the fickle
41724172+minded human eye is the same. The eye constantly looks for a
41734173+frame of reference, a focal point. It grows tired when it
41744174+can't find one.</p>
41754175+<p>The following comparison does a better job of explaining
41764176+(none, ample and over-the-top highlighting, from left to
41774177+right):</p>
41784178+<p><a href="https://cdn.oppi.li/lt.png"><img src="https://cdn.oppi.li/lt.png" alt="" /></a></p>
41794179+<p>Without highlighting (far left), it is hard to differentiate
41804180+between comments and code! The florid color scheme (far
41814181+right) is no good either, it contains too many attention
41824182+grabbers. The center sample is a healthy balance of both.
41834183+Function calls and constants stand out, and repetitive
41844184+keywords and other noise (<code>let</code>, <code>as</code>) are mildly dimmed
41854185+out. Comments and non-code text (sign column, status text)
41864186+are dimmed further.</p>
41874187+<p>I'll stop myself before I rant about color contrast and
41884188+combinations.</p>
41894189+</description>
41904190+ <link>https://oppi.li/posts/color_conundrum/</link>
41914191+ <pubDate>Mon, 30 Dec 2019 00:00:00 +0000</pubDate>
41924192+ <guid>https://oppi.li/posts/color_conundrum/</guid>
41934193+ </item>
41944194+ <item>
41954195+ <title>static sites with bash</title>
41964196+ <description><p>After going through a bunch of static site generators
41974197+(<a href="https://blog.getpelican.com/">pelican</a>,
41984198+<a href="https://gohugo.io">hugo</a>,
41994199+<a href="https://github.com/icyphox/vite">vite</a>), I decided to roll
42004200+my own. If you are more of the 'show me the code' kinda guy,
42014201+<a href="https://github.com/nerdypepper/site">here</a> you go.</p>
42024202+<h3 id="text-formatting">Text formatting</h3>
42034203+<p>I chose to write in markdown, and convert
42044204+to html with <a href="https://kristaps.bsd.lv/lowdown/">lowdown</a>.</p>
42054205+<h3 id="directory-structure">Directory structure</h3>
42064206+<p>I host my site on GitHub pages, so
42074207+<code>docs/</code> has to be the entry point. Markdown formatted posts
42084208+go into <code>posts/</code>, get converted into html, and end up in
42094209+<code>docs/index.html</code>, something like this:</p>
42104210+<pre><code class="language-bash">posts=$(ls -t ./posts) # chronological order!
42114211+for f in $posts; do
42124212+ file=&quot;./posts/&quot;$f # `ls` mangled our file paths
42134213+ echo &quot;generating post $file&quot;
42144214+42154215+ html=$(lowdown &quot;$file&quot;)
42164216+ echo -e &quot;html&quot; &gt;&gt; docs/index.html
42174217+done
42184218+</code></pre>
42194219+<h3 id="assets">Assets</h3>
42204220+<p>Most static site generators recommend dropping image
42214221+assets into the site source itself. That does have it's
42224222+merits, but I prefer hosting images separately:</p>
42234223+<pre><code class="language-bash"># strip file extension
42244224+ext=&quot;${1##*.}&quot;
42254225+42264226+# generate a random file name
42274227+id=$( cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 2 | head -n 1 )
42284228+id=&quot;$id.$ext&quot;
42294229+42304230+# copy to my file host
42314231+scp -P 443 &quot;$1&quot; emerald:files/&quot;$id&quot;
42324232+echo &quot;https://cdn.oppi.li/$id&quot;
42334233+</code></pre>
42344234+<h3 id="templating">Templating</h3>
42354235+<p><a href="https://github.com/NerdyPepper/site/blob/master/generate.sh"><code>generate.sh</code></a>
42364236+brings the above bits and pieces together (with some extra
42374237+cruft to avoid javascript). It uses <code>sed</code> to produce nice
42384238+titles from the file names (removes underscores,
42394239+title-case), and <code>date(1)</code> to add the date to each post
42404240+listing!</p>
42414241+</description>
42424242+ <link>https://oppi.li/posts/static_sites_with_bash/</link>
42434243+ <pubDate>Fri, 22 Nov 2019 00:00:00 +0000</pubDate>
42444244+ <guid>https://oppi.li/posts/static_sites_with_bash/</guid>
42454245+ </item>
42464246+ <item>
42474247+ <title>my setup</title>
42484248+ <description><p>Decided to do one of these because everyone does one of
42494249+these.</p>
42504250+<p><img src="https://cdn.oppi.li/Hb.png" alt="" /></p>
42514251+<p>My entire setup is managed with GNU <code>stow</code>, making it easier
42524252+to replicate on fresh installations. You can find my
42534253+configuration files on <a href="https://github.com/nerdypepper">GitHub</a>.</p>
42544254+<p>I run Void Linux (glibc) on my
42554255+<a href="https://store.hp.com/us/en/mdp/laptops/envy-13">HP Envy 13&quot; (2018)</a>.
42564256+To keep things simple, I run a raw X session with <code>2bwm</code> as my
42574257+window manager, along with <code>dunst</code> (notification daemon) and
42584258+Sam's <a href="https://github.com/sdhand/compton"><code>compton</code></a>
42594259+(compositor) fork.</p>
42604260+<p>I am a fan of GNU tools, so I use <code>bash</code> as my shell, and
42614261+<code>coreutils</code> to manage files, archives, strings, paths etc. I
42624262+edit files with <code>vim</code>, chat with <code>weechat</code>, listen to music
42634263+with <code>cmus</code>, monitor processes with <code>htop</code>, manage sessions
42644264+with <code>tmux</code>, read pdfs in <code>zathura</code>. I rarely ever leave
42654265+the comfort of my terminal emulator, <code>urxvt</code>.</p>
42664266+<p>Most of my academic typesetting is done with TeX, and
42674267+compiled with <code>xelatex</code>. Other <em>fun</em> documents are made with
42684268+GIMP :).</p>
42694269+</description>
42704270+ <link>https://oppi.li/posts/my_setup/</link>
42714271+ <pubDate>Wed, 06 Nov 2019 00:00:00 +0000</pubDate>
42724272+ <guid>https://oppi.li/posts/my_setup/</guid>
42734273+ </item>
42744274+ <item>
42754275+ <title>WPA woes</title>
42764276+ <description><p>I finally got around to installing Void GNU/Linux on my main
42774277+computer. Rolling release, non-systemd, need I say more?</p>
42784278+<p>As with all GNU/Linux distributions, wireless networks had
42794279+me in a fix. If you can see this post, it means I've managed
42804280+to get online. It turns out, <code>wpa_supplicant</code> was detecting the
42814281+wrong interface by default (does it ever select the right
42824282+one?). Let us fix that:</p>
42834283+<pre><code>$ sudo rm -r /var/service/wpa_supplicant
42844284+$ sudo killall dhcpcd
42854285+</code></pre>
42864286+<p>What is the right interface though?</p>
42874287+<pre><code>$ iw dev
42884288+ ...
42894289+ Interface wlp2s0
42904290+ ...
42914291+</code></pre>
42924292+<p>Aha! Let us run <code>wpa_supplicant</code> on that interface, as a
42934293+background process:</p>
42944294+<pre><code>$ sudo wpa_supplicant -B -i wlp2s0 -c /etc/wpa_supplicant/wpa_supplicant.conf
42954295+$ sudo dhcpcd -B wlp2s0
42964296+$ ping google.com
42974297+PING ...
42984298+</code></pre>
42994299+<p>Yay! Make those changes perpetual by enabling the service:</p>
43004300+<pre><code>------------------------------------------------------
43014301+# Add these to /etc/wpa_supplicant/wpa_supplicant.conf
43024302+OPTS=&quot;-B&quot;
43034303+WPA_INTERFACE=&quot;wlp2s0&quot;
43044304+------------------------------------------------------
43054305+$ sudo ln -s /etc/sv/wpa_supplicant /var/service/
43064306+$ sudo ln -s /etc/sv/dhcpcd /var/service/
43074307+$ sudo sv restart wpa_supplicant
43084308+$ sudo sv restart dhcpcd
43094309+</code></pre>
43104310+</description>
43114311+ <link>https://oppi.li/posts/WPA_woes/</link>
43124312+ <pubDate>Sat, 12 Oct 2019 00:00:00 +0000</pubDate>
43134313+ <guid>https://oppi.li/posts/WPA_woes/</guid>
43144314+ </item>
43154315+ <item>
43164316+ <title>bye bye BDFs</title>
43174317+ <description><p>Glyph Bitmap Distribution Format is no more, as the creators of
43184318+<a href="https://pango.org">Pango</a>, one of the most widely used text rendering
43194319+libraries,
43204320+<a href="https://blogs.gnome.org/mclasen/2019/05/25/pango-future-directions/">announced</a>
43214321+their plans for Pango 1.44.</p>
43224322+<p>Until recently, Pango used FreeType to draw fonts. They will be moving over
43234323+to <a href="https://harfbuzz.org">Harfbuzz</a>, an evolution of FreeType.</p>
43244324+<p><em>Why?</em></p>
43254325+<p>In short, FreeType was hard to work with. It required complex logic, and
43264326+provided no advantage over Harfbuzz (other than being able to fetch
43274327+opentype metrics with ease).</p>
43284328+<p>Upgrading to Pango v1.44 will break your GTK applications (if you use a
43294329+<code>bdf</code>/<code>pcf</code> bitmap font). Harfbuzz <em>does</em> support bitmap-only OpenType fonts,
43304330+<code>otb</code>s. Convert your existing fonts over to <code>otb</code>s using
43314331+<a href="https://fontforge.github.io">FontForge</a>. It is to be noted that applications
43324332+such as <code>xterm</code> and <code>rxvt</code> use <code>xft</code> (X FreeType) to render fonts, and will
43334333+remain unaffected by the update.</p>
43344334+<p>Both <a href="https://github.com/nerdypepper/scientifica">scientifica</a> and
43354335+<a href="https://github.com/nerdypepper/curie">curie</a> will soon ship with bitmap-only
43364336+OpenType font formats.</p>
43374337+</description>
43384338+ <link>https://oppi.li/posts/bye_bye_BDFs/</link>
43394339+ <pubDate>Wed, 07 Aug 2019 00:00:00 +0000</pubDate>
43404340+ <guid>https://oppi.li/posts/bye_bye_BDFs/</guid>
43414341+ </item>
43424342+ <item>
43434343+ <title>onivim sucks</title>
43444344+ <description><p><a href="https://v2.onivim.io">Onivim</a> is a 'modern modal editor', combining fancy
43454345+interface and language features with vim-style modal editing. What's wrong you
43464346+ask?</p>
43474347+<p>Apart from <a href="https://github.com/onivim/oni2/issues/550">buggy syntax highlighting</a>,
43484348+<a href="https://github.com/onivim/oni2/issues/519">broken scrolling</a> and
43494349+<a href="https://github.com/onivim/oni2/issues?q=is%3Aissue+label%3A%22daily+editor+blocker%22+is%3Aopen">others</a>,
43504350+Onivim is <strong>proprietary</strong> software. It is licensed under a commercial
43514351+<a href="https://github.com/onivim/oni1/blob/master/Outrun-Labs-EULA-v1.1.md">end user agreement license</a>,
43524352+which prohibits redistribution in both object code and source code formats.</p>
43534353+<p>Onivim's core editor logic (bits that belong to vim), have been separated from
43544354+the interface, into <a href="https://github.com/onivim/libvim">libvim</a>. libvim is
43554355+licensed under MIT, which means, this 'extension' of vim is perfectly in
43564356+adherence to <a href="http://vimdoc.sourceforge.net/htmldoc/uganda.html#license">vim's license text</a>!
43574357+Outrun Labs are exploiting this loophole (distributing vim as a library) to
43584358+commercialize Onivim.</p>
43594359+<p>Onivim's source code is available on <a href="https://github.com/onivim/oni2">GitHub</a>.
43604360+They do mention that the source code trickles down to the
43614361+<a href="https://github.com/onivim/oni2-mit">oni2-mit</a> repository, which (not yet) contains
43624362+MIT-licensed code, <strong>18 months</strong> after each commit to the original repository.</p>
43634363+<p>Want to contribute to Onivim? Don't. They make a profit out of your contributions.
43644364+Currently, Onivim is priced at $19.99, 'pre-alpha' pricing which is 80% off the
43654365+final price! If you are on the lookout for an editor, I would suggest using
43664366+<a href="https://vim.org">Vim</a>, charity ware that actually works, and costs $100 lesser.</p>
43674367+</description>
43684368+ <link>https://oppi.li/posts/onivim_sucks/</link>
43694369+ <pubDate>Fri, 02 Aug 2019 00:00:00 +0000</pubDate>
43704370+ <guid>https://oppi.li/posts/onivim_sucks/</guid>
43714371+ </item>
43724372+ <item>
43734373+ <title>bash harder with vim</title>
43744374+ <description><p>Bash is tricky, don't let your editor get in your way. Here's a couple of neat
43754375+additions you could make to your <code>vimrc</code> for a better shell programming
43764376+experience.</p>
43774377+<h3 id="man-pages-inside-vim">Man pages inside vim</h3>
43784378+<p>Source this script to get started:</p>
43794379+<pre><code>runtime ftplugin/man.vim
43804380+</code></pre>
43814381+<p>Now, you can open manpages inside vim with <code>:Man</code>! It adds nicer syntax highlighting
43824382+and the ability to jump around with <code>Ctrl-]</code> and <code>Ctrl-T</code>.</p>
43834383+<p>By default, the manpage is opened in a horizontal split, I prefer using a new tab:</p>
43844384+<pre><code>let g:ft_man_open_mode = 'tab'
43854385+</code></pre>
43864386+<h3 id="scratchpad-to-test-your-commands">Scratchpad to test your commands</h3>
43874387+<p>I often test my <code>sed</code> substitutions, here is
43884388+a sample from the script used to generate this site:</p>
43894389+<pre><code># a substitution to convert snake_case to Title Case With Spaces
43904390+echo &quot;$1&quot; | sed -E -e &quot;s/\..+$//g&quot; -e &quot;s/_(.)/ \u\1/g&quot; -e &quot;s/^(.)/\u\1/g&quot;
43914391+</code></pre>
43924392+<p>Instead of dropping into a new shell, just test it out directly from vim!</p>
43934393+<ul>
43944394+<li>Yank the line into a register:
43954395+</li>
43964396+</ul>
43974397+<pre><code>yy
43984398+</code></pre>
43994399+<ul>
44004400+<li>Paste it into the command-line window:
44014401+</li>
44024402+</ul>
44034403+<pre><code>q:p
44044404+</code></pre>
44054405+<ul>
44064406+<li>Make edits as required:
44074407+</li>
44084408+</ul>
44094409+<pre><code>syntax off # previously run commands
44104410+edit index.html # in a buffer!
44114411+w | so %
44124412+!echo &quot;new_post.md&quot; | sed -E -e &quot;s/\..+$//g&quot; --snip--
44134413+^--- note the use of '!'
44144414+</code></pre>
44154415+<ul>
44164416+<li>Hit enter with the cursor on the line containing your command!
44174417+</li>
44184418+</ul>
44194419+<pre><code>$ vim
44204420+New Post # output
44214421+Press ENTER or type command to continue
44224422+</code></pre>
44234423+</description>
44244424+ <link>https://oppi.li/posts/bash_harder_with_vim/</link>
44254425+ <pubDate>Tue, 30 Jul 2019 00:00:00 +0000</pubDate>
44264426+ <guid>https://oppi.li/posts/bash_harder_with_vim/</guid>
44274427+ </item>
44284428+ <item>
44294429+ <title>hold position!</title>
44304430+ <description><p>Often times, when I run a vim command that makes &quot;big&quot; changes to a file (a
44314431+macro or a <code>:vimgrep</code> command) I lose my original position and feel disoriented.</p>
44324432+<p><em>Save position with <code>winsaveview()</code>!</em></p>
44334433+<p>The <code>winsaveview()</code> command returns a <code>Dictionary</code> that contains information
44344434+about the view of the current window. This includes the cursor line number,
44354435+cursor coloumn, the top most line in the window and a couple of other values,
44364436+none of which concern us.</p>
44374437+<p>Before running our command (one that jumps around the buffer, a lot), we save
44384438+our view, and restore it once its done, with <code>winrestview</code>.</p>
44394439+<pre><code>let view = winsaveview()
44404440+s/\s\+$//gc &quot; find and (confirm) replace trailing blanks
44414441+winrestview(view) &quot; restore our original view!
44424442+</code></pre>
44434443+<p>It might seem a little overkill in the above example, just use `` (double
44444444+backticks) instead, but it comes in handy when you run your file through
44454445+heavier filtering.</p>
44464446+</description>
44474447+ <link>https://oppi.li/posts/hold_position!/</link>
44484448+ <pubDate>Tue, 30 Jul 2019 00:00:00 +0000</pubDate>
44494449+ <guid>https://oppi.li/posts/hold_position!/</guid>
44504450+ </item>
44514451+ <item>
44524452+ <title>get better at yanking and putting in vim</title>
44534453+ <description><p>a couple of nifty tricks to help you copy-paste better:</p>
44544454+<ol>
44554455+<li>
44564456+<p>reselecting previously selected text (i use this to fix botched selections):</p>
44574457+<pre><code>gv &quot; :h gv for more
44584458+ &quot; you can use `o` in visual mode to go to the `Other` end of the selection
44594459+ &quot; use a motion to fix the selection
44604460+</code></pre>
44614461+</li>
44624462+<li>
44634463+<p>reselecting previously yanked text:</p>
44644464+<pre><code>`[v`]
44654465+`[ &quot; marks the beginning of the previously yanked text :h `[
44664466+`] &quot; marks the end :h `]
44674467+ v &quot; visual select everything in between
44684468+44694469+nnoremap gb `[v`] &quot; &quot;a quick map to perform the above
44704470+</code></pre>
44714471+</li>
44724472+<li>
44734473+<p>pasting and indenting text (in one go):</p>
44744474+<pre><code>]p &quot; put (p) and adjust indent to current line
44754475+]P &quot; put the text before the cursor (P) and adjust indent to current line
44764476+</code></pre>
44774477+</li>
44784478+</ol>
44794479+</description>
44804480+ <link>https://oppi.li/posts/get_better_at_yanking_and_putting_in_vim/</link>
44814481+ <pubDate>Tue, 30 Jul 2019 00:00:00 +0000</pubDate>
44824482+ <guid>https://oppi.li/posts/get_better_at_yanking_and_putting_in_vim/</guid>
44834483+ </item>
44844484+ </channel>
44854485+</rss>
+1-1
_site/style.css
···11-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}
11+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}
···11+<?xml version="1.0" encoding="UTF-8"?>
22+<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
33+ <channel>
44+ <title>oppiliappan's μsings</title>
55+ <link>https://oppi.li</link>
66+ <description>programming, design, software</description>
77+ <atom:link href="https://oppi.li/weeklies/index.rss" rel="self" type="application/rss+xml" />
88+ <language>en-us</language>
99+ <copyright>Creative Commons BY-NC-SA 4.0</copyright>
1010+ <item>
1111+ <title>2026-16</title>
1212+ <description><h2 id="fp-launchpad-at-iit-madras">FP Launchpad at IIT Madras</h2>
1313+<p>I had the pleasure of attending the inaugration of <a href="https://fplaunchpad.org/">FP
1414+Launchpad</a>, courtesy of <a href="https://kcsrk.info/">KC
1515+Sivaramakrishnan</a> and <a href="https://anil.recoil.org">Anil
1616+Madhavapeddy</a>. It was a packed
1717+schedule with back-to-back presentations. My personal
1818+favourites:</p>
1919+<ul>
2020+<li><a href="https://fplaunchpad.org/2026/03/30/fp-launchpad-kickoff.html#raghavan">Towards verifiable governance with LLMs and
2121+Lean</a>
2222+</li>
2323+<li><a href="https://fplaunchpad.org/2026/03/30/fp-launchpad-kickoff.html#krishnamurthi">A Programming Language for Lightweight
2424+Diagramming</a>
2525+</li>
2626+</ul>
2727+<p>It was truly rousing to see FP grab hold in Indian
2828+universities. I briefly attempted to run a series of <a href="https://github.com/DSCRV/lisk">Scheme
2929+&amp; Haskell</a> tutorials for my
3030+juniors at university several years ago!</p>
3131+<h2 id="renewed-interest-in-ocaml">Renewed interest in OCaml</h2>
3232+<p>After attending FP Launchpad, my interest in OCaml was at an
3333+all time high. So I've rewritten <a href="https://tangled.org/oppi.li/site/blob/dbc61fb36b60a574dd8e99af7caf8abfb2be2638/generate.sh">the ugly bash
3434+program</a>
3535+that was used to generate this website <a href="https://tangled.org/oppi.li/site">in
3636+OCaml</a>, using the
3737+wonderful <a href="https://github.com/samoht/tw">tw</a> library by
3838+<a href="https://gazagnaire.org/">Thomas Gazagnaire</a>. I've now added
3939+a section for <a href="/weeklies">weeklies</a>, to get myself to write
4040+more often.</p>
4141+<p>I've also been poking at
4242+<a href="https://tangled.org/patrick.sirref.org/bruit">bruit</a> to
4343+<a href="https://tangled.org/oppi.li/bruit/commit/87bdc3254ee028007805e6b7135fa0da83287fb9">have it work on the
4444+web</a>,
4545+so I can eventually show-off my OCaml implementation of
4646+<a href="https://wryl.tech/projects/modal.html">Modal</a> on a <a href="https://docs.tangled.org/hosting-websites-on-tangled.html#hosting-websites-on-tangled">Tangled
4747+site</a>.</p>
4848+<p>I've started familiarizing myself with OCaml's
4949+<a href="https://github.com/ocaml-ppx/ppxlib">metaprogramming
5050+ecosystem</a> to emit
5151+<a href="https://github.com/sidprasad/spytial">spytial diagrams</a>
5252+from OCaml data structures. It is ever so slightly different
5353+from
5454+<a href="https://docs.rs/proc-macro2/latest/proc_macro2/">proc_macro2</a>
5555+in that it operates on a full
5656+<a href="https://ocaml.org/manual/5.4/api/compilerlibref/Parsetree.html">Parsetree</a>
5757+and not a plain
5858+<a href="https://docs.rs/proc-macro2/latest/proc_macro2/struct.TokenStream.html">TokenStream</a>.</p>
5959+<h2 id="building-a-web-of-trust">Building a web-of-trust</h2>
6060+<p>I've been taking a few (cautious) steps towards implementing
6161+<a href="https://github.com/mitchellh/vouch/">vouching</a> on Tangled.
6262+More on that next week!</p>
6363+<h2 id="improving-my-music-setup">Improving my music setup</h2>
6464+<p><a href="https://tangled.org/devins.page/tinysub">tinysub</a>, a
6565+subsonic-compatible web player popped up on my Tangled feed,
6666+and I instantly took to setting it up on my homeserver.
6767+Using <a href="https://github.com/Y2Z/monolith">monolith</a>, I was
6868+able to build it down to a single 100kB HTML file, assets
6969+included.</p>
7070+<p>I then rigged up my navidrome server to scrobble listens
7171+directly to my
7272+<a href="https://atproto.com/guides/self-hosting#pds">PDS</a> using the
7373+<a href="https://tangled.org/oppi.li/rocksky/commit/47babb790cd34d70c7a3638bf8d145013cfef7fd#flake.nix-N42">rocksky
7474+CLI</a>.
7575+My scrobbles are now aggregated by
7676+<a href="https://rocksky.app">rocksky.app</a> and
7777+<a href="https://teal.fm">teal.fm</a>.</p>
7878+<h2 id="travel">Travel</h2>
7979+<p>I am now back in London! I've spent the weekend trying to
8080+revive my houseplants... with little luck.</p>
8181+</description>
8282+ <link>https://oppi.li/weeklies/2026-16/</link>
8383+ <pubDate>Sun, 19 Apr 2026 00:00:00 +0000</pubDate>
8484+ <guid>https://oppi.li/weeklies/2026-16/</guid>
8585+ </item>
8686+ </channel>
8787+</rss>
···1818 val render : t -> Tw_html.t
1919 val preview : t -> limit:int -> Tw_html.t
2020 val css : t -> Tw.Css.t
2121+ val rss : t -> string
2122 val write : out_dir:string -> t -> unit
2323+ val write_rss : out_dir:string -> t -> unit
2224end
23252426let rec mkdir_p dir =
···9395 Tw.Css.concat
9496 (Typography.stylesheet
9597 :: List.map (fun (_, page) -> snd (Tw_html.css page)) t.pages)
9898+9999+ let compare_date a b =
100100+ match (P.date a, P.date b) with
101101+ | Some da, Some db -> CalendarLib.Date.compare db da
102102+ | Some _, None -> -1
103103+ | None, Some _ -> 1
104104+ | None, None -> 0
105105+106106+ let rss t =
107107+ let cat = N.name in
108108+ let feed_url = Layout.base_url ^ "/" ^ cat ^ "/index.rss" in
109109+ let items =
110110+ List.sort compare_date t.posts
111111+ |> List.map (fun p ->
112112+ let link = Layout.base_url ^ "/" ^ cat ^ "/" ^ P.slug p ^ "/" in
113113+ P.rss_item ~link p)
114114+ in
115115+ Printf.sprintf
116116+ {|<?xml version="1.0" encoding="UTF-8"?>
117117+<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
118118+ <channel>
119119+ <title>%s</title>
120120+ <link>%s</link>
121121+ <description>%s</description>
122122+ <atom:link href="%s" rel="self" type="application/rss+xml" />
123123+ <language>en-us</language>
124124+ <copyright>%s</copyright>
125125+ %s
126126+ </channel>
127127+</rss>|}
128128+ Layout.rss_title Layout.base_url Layout.rss_description feed_url
129129+ Layout.rss_copyright
130130+ (String.concat "\n " items)
131131+132132+ let write_rss ~out_dir t =
133133+ let cat = N.name in
134134+ let dir = Filename.concat out_dir cat in
135135+ mkdir_p dir;
136136+ let file = Filename.concat dir "index.rss" in
137137+ Out_channel.with_open_text file (fun oc ->
138138+ Out_channel.output_string oc (rss t))
9613997140 let write ~out_dir t =
98141 List.iter (fun (path, page) -> write_page ~out_dir path page) t.pages
+31
lib/content.ml
···1010 val date : t -> CalendarLib.Date.t option
1111 val toc : t -> toc_item list
1212 val html : t -> Tw_html.t
1313+ val rss_item : link:string -> t -> string
1314end
14151516module MarkdownPage : Page = struct
···155156 ~default:(t.path |> Filename.basename |> Filename.remove_extension)
156157157158 let html t = t.html
159159+160160+ let xml_escape s =
161161+ let b = Buffer.create (String.length s) in
162162+ String.iter
163163+ (function
164164+ | '&' -> Buffer.add_string b "&"
165165+ | '<' -> Buffer.add_string b "<"
166166+ | '>' -> Buffer.add_string b ">"
167167+ | c -> Buffer.add_char b c)
168168+ s;
169169+ Buffer.contents b
170170+171171+ let rss_item ~link t =
172172+ let title = xml_escape (title t) in
173173+ let description = xml_escape (Omd.to_html t.doc) in
174174+ let pub_date =
175175+ match date t with
176176+ | None -> ""
177177+ | Some d ->
178178+ CalendarLib.Printer.Date.sprint "%a, %d %b %Y 00:00:00 +0000" d
179179+ in
180180+ Printf.sprintf
181181+ {|<item>
182182+ <title>%s</title>
183183+ <description>%s</description>
184184+ <link>%s</link>
185185+ <pubDate>%s</pubDate>
186186+ <guid>%s</guid>
187187+ </item>|}
188188+ title description link pub_date link
158189end