PageSourceSearch

https://koaning.io/

html koaning.io collected 2026-10-03 21:38:26 UTC 182,584 bytes, 2,310 lines download raw bytes

1<!doctype html>
2<html lang="en" class="light">
3    <head>
4        <meta charset="UTF-8" />
5        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6        <title>Vincent D. Warmerdam's Blog</title>
7        
8
9        <!-- Fonts -->
10        <link rel="preconnect" href="https://fonts.googleapis.com" />
11        <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
12        <link
13            href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:ital,wght@0,400;0,500;0,600;0,700;1,400&family=JetBrains+Mono:wght@400;700&display=swap"
14            rel="stylesheet"
15        />
16
17        <!-- Favicons -->
18        <link
19            rel="apple-touch-icon"
20            sizes="180x180"
21            href="/static/favicon_io/apple-touch-icon.png"
22        />
23        <link
24            rel="icon"
25            type="image/png"
26            sizes="32x32"
27            href="/static/favicon_io/favicon-32x32.png"
28        />
29        <link
30            rel="icon"
31            type="image/png"
32            sizes="16x16"
33            href="/static/favicon_io/favicon-16x16.png"
34        />
35        <link rel="manifest" href="/static/favicon_io/site.webmanifest" />
36
37        
37<script src="https://cdn.tailwindcss.com"></script>
37
38        
38<script src="https://unpkg.com/alpinejs" defer></script>
38
39        
39<script
40            defer
41            data-domain="koaning.io"
42            src="https://plausible.io/js/script.js"
43        ></script>
43
44        
44<script>
45            tailwind.config = {
46                darkMode: "class",
47                theme: {
48                    extend: {
49                        colors: {
50                            primary: "#0969da",
51                            secondary: "#24292f",
52                            "dark-primary": "#58a6ff",
53                            "dark-secondary": "#c9d1d9",
54                            "dark-bg": "#0d1117",
55                            "dark-card": "#161b22",
56                        },
57                        fontFamily: {
58                            sans: ["IBM Plex Sans", "Helvetica", "Arial", "sans-serif"],
59                            mono: [
60                                "JetBrains Mono",
61                                "Courier New",
62                                "Consolas",
63                                "monospace",
64                            ],
65                        },
66                    },
67                },
68            };
69        </script>
69
70
71        <link
72            rel="stylesheet"
73            href="https://cdn.jsdelivr.net/npm/[email protected]/dist/katex.min.css"
74            integrity="sha384-zh0CIslj+VczCZtlzBcjt5ppRcsAmDnRem7ESsYwWwg3m/OaJ2l4x7YBZl9Kxxib"
75            crossorigin="anonymous"
76        />
77        
77<script
78            defer
79            src="https://cdn.jsdelivr.net/npm/[email protected]/dist/katex.min.js"
80            integrity="sha384-Rma6DA2IPUwhNxmrB/7S3Tno0YY7sFu9WSYMCuulLhIqYSGZ2gKCJWIqhBWqMQfh"
81            crossorigin="anonymous"
82        ></script>
82
83        
83<script
84            defer
85            src="https://cdn.jsdelivr.net/npm/[email protected]/dist/contrib/auto-render.min.js"
86            integrity="sha384-hCXGrW6PitJEwbkoStFjeJxv+fSOOQKOPbJxSfM6G5sWZjAyWhXiTIIAmQqnlLlh"
87            crossorigin="anonymous"
88        ></script>
88
89        
89<script>
90            document.addEventListener("DOMContentLoaded", function () {
91                renderMathInElement(document.body, {
92                    // customised options
93                    // • auto-render specific keys, e.g.:
94                    delimiters: [
95                        { left: "$$", right: "$$", display: true },
96                        { left: "$", right: "$", display: false },
97                        { left: "\\(", right: "\\)", display: false },
98                        { left: "\\[", right: "\\]", display: true },
99                    ],
100                    // • rendering keys, e.g.:
101                    throwOnError: false,
102                });
103            });
104        </script>
104
105
106        <!-- Prism CSS - Simple setup with one theme -->
107        <link
108            rel="stylesheet"
109            href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css"
110        />
111
112        <!-- Prism JS - Load required components directly -->
113        
113<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js"></script>
113
114        
114<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-python.min.js"></script>
114
115        
115<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-bash.min.js"></script>
115
116        
116<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-toml.min.js"></script>
116
117        
117<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-yaml.min.js"></script>
117
118        
118<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-json.min.js"></script>
118
119        
119<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-javascript.min.js"></script>
119
120        
120<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-typescript.min.js"></script>
120
121        
121<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-markdown.min.js"></script>
121
122        
122<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-sql.min.js"></script>
122
123        
123<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-docker.min.js"></script>
123
124        
124<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-rust.min.js"></script>
124
125        
125<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-go.min.js"></script>
125
126        <style>
127            /* Base styles with fluid typography */
128            :root {
129                --font-base: "IBM Plex Sans", Helvetica, Arial, sans-serif;
130                --size-step-0: clamp(1rem, 0.96rem + 0.22vw, 1.125rem);
131                --size-step-1: clamp(1.125rem, 1.06rem + 0.33vw, 1.313rem);
132                --size-step-2: clamp(1.266rem, 1.17rem + 0.48vw, 1.563rem);
133                --size-step-3: clamp(1.424rem, 1.28rem + 0.72vw, 1.875rem);
134                --space-s: clamp(0.75rem, 0.69rem + 0.29vw, 0.938rem);
135                --space-m: clamp(1rem, 0.92rem + 0.39vw, 1.25rem);
136                --space-l: clamp(1.5rem, 1.38rem + 0.58vw, 1.875rem);
137                --color-primary: #089ce7;
138                --color-dark: #252525;
139                --color-light: #f9f9f9;
140            }
141
142            /* Apply monospace font globally */
143            body {
144                font-family: var(--font-base);
145            }
146
147            .prose {
148                font-family: var(--font-base);
149                font-size: var(--size-step-0);
150                line-height: 1.7;
151            }
152            .prose {
153                max-width: 65ch;
154                color: #24292f;
155            }
156            /* Interactive (.mdx) posts: keep body text at a readable measure, but
157               let the marimo widget break out (centered) wider than the text
158               column. The WidgetDAG is content-sized and its flex layout shrinks
159               its nodes to fit the container, so it needs its natural width or the
160               convolution previews get squeezed. */
161            .prose.prose-wide-widgets {
162                max-width: none;
163            }
164            .prose.prose-wide-widgets > :where(p, h1, h2, h3, h4, h5, h6, ul, ol, blockquote, pre, table) {
165                max-width: 65ch;
166            }
167            /* Make the widget a block-level horizontal-scroll container so an
168               oversized graph scrolls within the widget instead of the page. */
169            .prose.prose-wide-widgets .marimo-island-host {
170                display: block;
171                overflow-x: auto;
172            }
173            /* Keep the WidgetDAG at its natural size: render at content width and
174               stop the flex layout (and the prose img rule) from shrinking the
175               convolution previews to fit a narrow container. */
176            .prose.prose-wide-widgets [data-wdag-root] {
177                width: max-content;
178            }
179            .prose.prose-wide-widgets [data-wdag-root] img {
180                flex-shrink: 0;
181                max-width: none;
182            }
183            /* Only break out beyond the text frame on viewports wide enough to
184               hold the widget; on narrow screens it stays in-column. */
185            @media (min-width: 900px) {
186                .prose.prose-wide-widgets .marimo-island-host {
187                    width: max-content;
188                    max-width: min(900px, 96vw);
189                    position: relative;
190                    left: 50%;
191                    transform: translateX(-50%);
192                }
193            }
194            .dark .prose {
195                color: #c9d1d9;
196            }
197            .dark .katex,
198            .dark .katex-display {
199                color: #c9d1d9;
200            }
201
202            /* Typography */
203            .prose h1 {
204                font-size: var(--size-step-3);
205                font-weight: 600;
206                line-height: 1.2;
207                letter-spacing: -0.02em;
208                text-wrap: balance;
209                margin-top: 1.5em;
210                margin-bottom: 0.5em;
211                color: #24292f;
212            }
213
214            .prose h2 {
215                font-size: var(--size-step-2);
216                font-weight: 600;
217                line-height: 1.2;
218                letter-spacing: -0.02em;
219                text-wrap: balance;
220                margin-top: 1.5em;
221                margin-bottom: 0.5em;
222                color: #24292f;
223            }
224
225            .prose h3,
226            .prose h4 {
227                font-size: var(--size-step-1);
228                font-weight: 600;
229                line-height: 1.2;
230                text-wrap: balance;
231                margin-top: 1.5em;
232                margin-bottom: 0.5em;
233                color: #24292f;
234            }
235
236            .dark .prose h1,
237            .dark .prose h2,
238            .dark .prose h3,
239            .dark .prose h4 {
240                color: #c9d1d9;
241            }
242
243            /* Paragraph spacing */
244            .prose p {
245                margin-top: 1.4em;
246                margin-bottom: 1.4em;
247                text-wrap: pretty;
248            }
249
250            .prose p:first-child {
251                margin-top: 0;
252            }
253
254            .prose p:last-child {
255                margin-bottom: 0;
256            }
257
258            /* Links */
259            .prose a {
260                color: #0969da;
261                text-decoration: underline;
262                text-decoration-color: rgba(9, 105, 218, 0.3);
263                text-underline-offset: 2px;
264                transition: all 0.2s ease;
265            }
266            .prose a:hover {
267                color: #0550ae;
268                text-decoration-color: #0550ae;
269            }
270            .dark .prose a {
271                color: #58a6ff;
272                text-decoration-color: rgba(88, 166, 255, 0.3);
273            }
274            .dark .prose a:hover {
275                color: #79c0ff;
276                text-decoration-color: #79c0ff;
277            }
278
279            /* Code blocks */
280            .prose pre {
281                background-color: #f8f9fb;
282                border: 1px solid #e2e6ea;
283                border-radius: 8px;
284                padding: 1.25em;
285                overflow-x: auto;
286                position: relative;
287                margin-top: 1.5em;
288                margin-bottom: 1.5em;
289            }
290            .dark .prose pre {
291                background-color: #161b22;
292                border-color: #30363d;
293            }
294
295            .prose pre code {
296                background-color: transparent;
297                border: none;
298                padding: 0;
299                border-radius: 0;
300                font-size: 0.8125em;
301                line-height: 1.6;
302                color: #24292f;
303                display: block;
304            }
305
306            .dark .prose pre code {
307                color: #e6edf3;
308            }
309
310            .prose code {
311                font-family: "JetBrains Mono", SFMono-Regular, C
311onsolas, Liberation Mono, Menlo, monospace;
312                font-size: 0.875em;
313                padding: 0.15em 0.4em;
314                background-color: #f0f3f6;
315                border: 1px solid #e2e6ea;
316                border-radius: 4px;
317                color: #24292f;
318            }
319            .dark .prose code {
320                background-color: #1c2128;
321                border-color: #30363d;
322                color: #e6edf3;
323            }
324
325            /* Fixed code line highlighting */
326            .prose pre code .line {
327                display: block;
328                margin: 0;
329                padding: 0 1em;
330                margin-left: -1em;
331                margin-right: -1em;
332                min-height: 1.5em;
333                line-height: 1.5;
334                transition: background-color 0.2s ease;
335            }
336
337            .prose pre code .line:empty::before {
338                content: "\200B"; /* Zero-width space to maintain line height */
339            }
340
341            .prose pre code .line:hover {
342                background-color: rgba(56, 139, 253, 0.1);
343            }
344
345            .dark .prose pre code .line:hover {
346                background-color: rgba(56, 139, 253, 0.15);
347            }
348
349            /* Code block titles */
350            .code-block-wrapper {
351                margin: 1em 0;
352                border-radius: 8px;
353                overflow: hidden;
354                border: 1px solid #e2e6ea;
355            }
356
357            .dark .code-block-wrapper {
358                border-color: #30363d;
359            }
360
361            .code-block-wrapper .prose pre,
362            .code-block-wrapper pre {
363                border: none;
364                border-radius: 0;
365            }
366
367            .code-block-title {
368                background-color: #f0f3f6;
369                color: #57606a;
370                padding: 0.5rem 1rem;
371                font-size: 0.8125rem;
372                font-family: "JetBrains Mono", SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
373                border-bottom: 1px solid #e2e6ea;
374            }
375
376            /* Dark mode code block titles */
377            .dark .code-block-title {
378                background-color: #1c2128;
379                color: #8b949e;
380                border-bottom: 1px solid #30363d;
381            }
382
383            /* Ensure code blocks in wrappers have no top margin */
384            .code-block-wrapper pre {
385                margin-top: 0;
386                border-top-left-radius: 0;
387                border-top-right-radius: 0;
388            }
389
390            /* Lists - fix rendering */
391            .prose ul {
392                list-style-type: disc;
393                margin-left: 1.5em;
394                margin-top: 1em;
395                margin-bottom: 1em;
396            }
397
398            .prose ol {
399                list-style-type: decimal;
400                margin-left: 1.5em;
401                margin-top: 1em;
402                margin-bottom: 1em;
403            }
404
405            .prose li {
406                margin-bottom: 0.5em;
407                text-wrap: pretty;
408            }
409
410            .prose li > ul,
411            .prose li > ol {
412                margin-top: 0.5em;
413                margin-bottom: 0.5em;
414            }
415
416            /* Figure and figcaption styles */
417            figure {
418                margin: 2rem auto 2.5rem auto;
419                display: block;
420                max-width: 100%;
421                text-align: center;
422            }
423
424            figure img {
425                margin: 0 auto;
426                display: inline-block;
427                max-width: 100%;
428                height: auto;
429            }
430
431            figcaption {
432                text-align: center;
433                font-size: 0.875rem;
434                color: #6b7280;
435                margin-top: 0.5rem;
436                padding: 0 1rem;
437            }
438
439            .dark figcaption {
440                color: #9ca3af;
441            }
442
443            /* Image spacing and centering */
444            .prose img {
445                margin: 2rem auto 2.5rem auto;
446                display: block;
447                max-width: 100%;
448                height: auto;
449            }
450
451            .prose p + img,
452            .prose img + p {
453                margin-top: 1.5em;
454            }
455
456            /* Copy button for code blocks */
457            .code-copy-button {
458                position: absolute;
459                top: 0.5rem;
460                right: 0.5rem;
461                width: 2rem;
462                height: 2rem;
463                display: flex;
464                align-items: center;
465                justify-content: center;
466                background-color: rgba(255, 255, 255, 0.8);
467                border: 1px solid #e2e6ea;
468                border-radius: 6px;
469                color: #57606a;
470                cursor: pointer;
471                opacity: 0;
472                transition:
473                    opacity 0.15s,
474                    background-color 0.15s;
475            }
476
477            pre:hover .code-copy-button {
478                opacity: 1;
479            }
480
481            .code-copy-button:hover {
482                background-color: #f0f3f6;
483                color: #24292f;
484            }
485
486            .code-copy-button svg {
487                width: 0.875rem;
488                height: 0.875rem;
489            }
490
491            .dark .code-copy-button {
492                background-color: rgba(22, 27, 34, 0.8);
493                border-color: #30363d;
494                color: #8b949e;
495            }
496
497            .dark .code-copy-button:hover {
498                background-color: #1c2128;
499                color: #e6edf3;
500            }
501
502            /* Blockquotes */
503            .prose blockquote {
504                border-left: 4px solid #d0d7de;
505                padding-left: 1em;
506                color: #57606a;
507                margin-left: 0;
508                margin-top: 1.5em;
509                margin-bottom: 1.5em;
510            }
511            .dark .prose blockquote {
512                border-left-color: #30363d;
513                color: #8b949e;
514            }
515
516            /* Custom Prism.js styling */
517            pre.language-python,
518            pre.language-javascript,
519            pre.language-bash,
520            pre.language-css,
521            pre.language-json,
522            pre.language-html,
523            pre.language-toml,
524            pre.language-yaml,
525            pre.language-yml,
526            pre.language-typescript,
527            pre.language-ts,
528            pre.language-jsx,
529            pre.language-tsx,
530            pre.language-markdown,
531            pre.language-md,
532            pre.language-sql,
533            pre.language-docker,
534            pre.language-dockerfile,
535            pre.language-rust,
536            pre.language-rs,
537            pre.language-go {
538                position: relative;
539            }
540
541            .token.comment,
542            .token.prolog,
543            .token.doctype,
544            .token.cdata {
545                color: #6e7781;
546            }
547
548            .token.punctuation {
549                color: #24292f;
550            }
551
552            .token.property,
553            .token.tag,
554            .token.boolean,
555            .token.number,
556            .token.constant,
557            .token.symbol,
558            .token.deleted {
559                color: #0550ae;
560            }
561
562            .token.selector,
563            .token.attr-name,
564            .token.string,
565            .token.char,
566            .token.builtin,
567            .token.inserted {
568                color: #0a3069;
569            }
570
571            .token.operator,
572            .token.entity,
573            .token.url,
574            .language-css .token.string,
575            .style .token.string {
576                color: #8250df;
577            }
578
579            .token.atrule,
580            .token.attr-value,
581            .token.keyword {
582                color: #116329;
583            }
584
585            .token.function,
586            .token.class-name {
587                color: #953800;
588            }
589
590            .token.regex,
591            .token.important,
592            .token.variable {
593                color: #953800;
594            }
595
596            .dark .token.comment,
597            .dark .token.prolog,
598            .dark .token.doctype,
599            .dark .token.cdata {
600                color: #8b949e;
601            }
602
603            .dark .token.punctuation {
604                color: #c9d1d9;
605            }
606
607            .dark .token.property,
608            .dark .token.tag,
609            .dark .token.boolean,
610            .dark .token.number,
611            .dark .token.constant,
612            .dark .token.symbol,
613            .dark .token.deleted {
614                color: #79c0ff;
615            }
616
617            .dark .token.selector,
618            .dark .token.attr-name,
619            .dark .token.string,
620            .dark .token.char,
621            .dark .token.builtin,
622            .dark .token.inserted {
623                color: #a5d6ff;
624            }
625
626            .dark .token.operator,
627            .dark .token.entity,
628            .dark .token.url,
629            .dark .language-css .token.string,
630            .dark .style .token.string {
631                color: #d2a8ff;
632            }
633
634            /* Fix Tailwind conflict with Prism TOML tokens */
635            .prose pre code .token.table {
636                display: inline !important;
637            }
638
639            .dark .token.atrule,
640            .dark .token.attr-value,
641            .dark .token.keyword {
642                color: #7ee787;
643            }
644
645            .dark .token.function,
646            .dark .token.class-name {
647                color: #d2a8ff;
648            }
649
650            .dark .token.regex,
651            .dark .token.important,
652            .dark .token.variable {
653                color: #f0883e;
654            }
655
656            /* Responsive iframe container */
657            .prose .video-container,
658            .prose .iframe-container {
659                position: relative;
660                width: 100%;
661                padding-bottom: 56.25%; /* 16:9 aspect ratio */
662                height: 0;
663                overflow: hidden;
664                margin: 2rem 0;
665            }
666
667            .prose .video-container iframe,
668            .prose .iframe-container iframe {
669                position: absolute;
670                top: 0;
671                left: 0;
672                width: 100%;
673                height: 100%;
674                border: 0;
675            }
676
677            /* Direct iframe styling for backwards compatibility */
678            .prose iframe {
679                width: 100%;
680                max-width: 100%;
681                height: auto;
682                aspect-ratio: 16 / 9;
683                margin: 2rem 0;
684            }
685
686            /* Table styling */
687            .prose table {
688                width: 100%;
689                border-collapse: collapse;
690                margin: 2rem 0;
691                font-size: 0.95em;
692                border-radius: 8px;
693                box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
694            }
695
696            .prose thead {
697                background-color: #f6f8fa;
698                border-bottom: 2px solid #d0d7de;
699            }
700
701            .dark .prose thead {
702                background-color: #161b22;
703                border-bottom-color: #30363d;
704            }
705
706            .prose th {
707                padding: 0.75rem 1rem;
708                text-align: left;
709                font-weight: 600;
710                color: #24292f;
711            }
712
713            .dark .prose th {
714                color: #f0f6fc;
715            }
716
717            .prose td {
718                padding: 0.75rem 1rem;
719                text-align: left;
720                border-bottom: 1px solid #d0d7de;
721            }
722
723            .dark .prose td {
724                border-bottom-color: #30363d;
725            }
726
727            .prose tbody tr:last-child td {
728                border-bottom: none;
729            }
730
731            .prose tbody tr {
732                background-color: #ffffff;
733                transition: background-color 0.2s ease;
734            }
735
736            .dark .prose tbody tr {
737                background-color: #0d1117;
738            }
739
740            .prose tbody tr:hover {
741                background-color: #f3f4f6;
742            }
743
744            .dark .prose tbody tr:hover {
745                background-color: #1c2128;
746            }
747
748            /* Responsive tables */
749            @media (max-width: 640px) {
750                .prose table {
751                    font-size: 0.875em;
752                }
753
754                .prose th,
755                .prose td {
756                    padding: 0.5rem 0.75rem;
757                }
758
759                .prose iframe {
760                    margin: 1.5rem 0;
761                }
762
763                .prose .video-container,
764                .prose .iframe-container {
765                    margin: 1.5rem 0;
766                }
767            }
768        </style>
769        
769<script>
770            // Set theme based on user preference
771            function setTheme(theme) {
772                if (theme === "dark") {
773                    document.documentElement.classList.add("dark");
774                    document.documentElement.classList.remove("light");
775                    localStorage.setItem("theme", "dark");
776                } else {
777                    document.documentElement.classList.add("light");
778                    document.documentElement.classList.remove("dark");
779                    localStorage.setItem("theme", "light");
780                }
781            }
782
783            // Check for saved theme preference or use the system preference
784            const savedTheme = localStorage.getItem("theme");
785            if (savedTheme) {
786                setTheme(savedTheme);
787            } else if (
788                window.matchMedia("(prefers-color-scheme: dark)").matches
789            ) {
790                setTheme("dark");
791            } else {
792                setTheme("light");
793            }
794        </script>
794
795        <!-- Prism JS - Core first, then components -->
796        
796<script>
797            // Theme toggle
798            document.addEventListener("DOMContentLoaded", function () {
799                document
800                    .getElementById("theme-toggle")
801                    .addEventListener("click", function () {
802                        const currentTheme =
803                            localStorage.getItem("theme") || "light";
804                        if (currentTheme === "light") {
805                            setTheme("dark");
806                        } else {
807                            setTheme("light");
808                        }
809                    });
810            });
811
812            // Function to copy text to clipboard with fallback
813            function copyTextToClipboard(text, callback) {
814                // Fallback for browsers that don't support clipboard API
815                if (!navigator.clipboard) {
816                    const textArea = document.createElement("textarea");
817                    textArea.value = text;
818
819                    // Make the textarea out of viewport
820                    textArea.style.position = "fixed";
821                    textArea.style.left = "-999999px";
822                    textArea.style.top = "-999999px";
823                    document.body.appendChild(textArea);
824                    textArea.focus();
825                    textArea.select();
826
827                    try {
828                        const successful = document.execCommand("copy");
829                        callback(successful);
830                    } catch (err) {
831                        console.error("Fallback: Could not copy text: ", err);
832                        callback(false);
833                    }
834
835                    document.body.removeChild(textArea);
836                    return;
837                }
838
839                // Use Clipboard API if available
840                navigator.clipboard
841                    .writeText(text)
842                    .then(() => callback(true))
843                    .catch((err) => {
844                        console.error(
845                            "Clipboard API: Could not copy text: ",
846                            err,
847                        );
848                        callback(false);
849                    });
850            }
851
852            // Function to add copy buttons to code blocks
853            function addCopyButtons() {
854                document.querySelectorAll("pre").forEach(function (pre) {
855                    // Skip if a copy button is already added
856                    if (pre.querySelector(".code-copy-button")) return;
857
858                    // Add copy button with icon
859                    const copyButton = document.createElement("button");
860                    copyButton.className = "code-copy-button";
861                    copyButton.innerHTML = `
862                    <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
863                        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3" />
864                    </svg>
865                `;
866
867                    // Add tooltip
868                    copyButton.title = "Copy to clipboard";
869
870                    // Add click event to copy code
871                    copyButton.addEventListener("click", function (e) {
872                        e.preventDefault();
873                        e.stopPropagation();
874
875                        // Get the code content, preserving newlines properly
876                        const codeElement = pre.querySelector("code") || pre;
877                        const code =
878                            codeElement.innerText || codeElement.textContent;
879
880                        // Copy to clipboard with fallback
881                        copyTextToClipboard(code, function (success) {
882                            if (success) {
883                                // Show success state
884                                copyButton.innerHTML = `
885                                <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
886                                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
887                                </svg>
888                            `;
889                                setTimeout(() => {
890                                    copyButton.innerHTML = `
891                                    <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
892                                        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3" />
893                                    </svg>
894                                `;
895                                }, 2000);
896                            } else {
897                                // Show error state
898                                copyButton.innerHTML = `
899                                <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
900                                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
901                                </svg>
902                            `;
903                                setTimeout(() => {
904                                    copyButton.innerHTML = `
905                                    <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
906                                        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3" />
907                                    </svg>
908                                `;
909                                }, 2000);
910                            }
911                        });
912                    });
913
914                    // Append the button to the pre element
915                    pre.appendChild(copyButton);
916                });
917            }
918
919            // Add language classes and copy buttons when DOM is loaded
920            document.addEventListener("DOMContentLoaded", function () {
921                // Process code blocks for Prism.js
922                document.querySelectorAll("pre code").forEach(function (code) {
923                    // Find the language from the class
924                    let lang = "text";
925                    const classMatch = code.className.match(/language-(\w+)/);
926                    if (classMatch) {
927                        lang = classMatch[1];
928                    } else {
929                        // Add a default language class if none exists
930                        code.classList.add("language-text");
931                    }
932
933                    // Make sure the parent pre element also has the language class
934                    if (
935                        !code.parentElement.classList.contains(
936                            `language-${lang}`,
937                        )
938                    ) {
939                        code.parentElement.classList.add(`language-${lang}`);
940                    }
941                });
942
943                // Highlight code blocks
944                if (window.Prism) {
945                    Prism.highlightAll();
946                }
947
948                // Fixed line wrapping for hover highlighting
949                // Wait a bit for Prism to finish its work
950                setTimeout(function () {
951                    document
952                        .querySelectorAll("pre code")
953                        .forEach(function (code) {
954                            // Only process if not already processed
955                            if (code.querySelector(".line")) return;
956
957                            // Get the HTML content after Prism highlighting
958                            const content = code.innerHTML;
959
960                            // Split by newlines but preserve the line breaks
961                            const lines = content.split("\n");
962
963                            // Wrap each line, preserving empty lines
964                            const wrappedLines = lines.map(
965                                function (line, index) {
966                                    // For last empty line, don't wrap
967                                    if (
968                                        index === lines.length - 1 &&
969                                        line === ""
970                                    ) {
971                                        return "";
972                                    }
973                                    // Use span with proper display handling
974                                    return (
975                                        '<span class="line">
975' + line + "</span>"
976                                    );
977                                },
978                            );
979
980                            // Join without adding extra newlines
981                            code.innerHTML = wrappedLines.join("");
982                        });
983                }, 100);
984
985                // Add copy buttons
986                addCopyButtons();
987            });
988
989            // Add copy buttons when Prism is done highlighting
990            if (window.Prism) {
991                Prism.hooks.add("complete", function () {
992                    addCopyButtons();
993                });
994            }
995        </script>
995
996    </head>
997    <body
998        class="bg-white dark:bg-dark-bg text-secondary dark:text-dark-secondary min-h-screen"
999    >
1000        
1001        <header class="container mx-auto max-w-3xl px-4 sm:px-6">
1002            <!-- Newspaper-style header -->
1003            <div
1004                class="mb-4 pb-4 border-b-4 border-gray-800 dark:border-gray-600"
1005            >
1006                <div class="flex justify-between items-center">
1007                    <a href="/" class="hover:no-underline">
1008                        <h1
1009                            class="text-5xl font-bold text-gray-900 dark:text-gray-100 mb-2 pt-4"
1010                        >
1011                            koaning.io
1012                        </h1>
1013                    </a>
1014                    <button
1015                        id="theme-toggle"
1016                        class="text-gray-600 dark:text-gray-400 hover:text-primary dark:hover:text-dark-primary"
1017                    >
1018                        <svg
1019                            xmlns="http://www.w3.org/2000/svg"
1020                            width="20"
1021                            height="20"
1022                            fill="currentColor"
1023                            class="bi bi-moon-stars dark:hidden"
1024                            viewBox="0 0 16 16"
1025                        >
1026                            <path
1027                                d="M6 .278a.768.768 0 0 1 .08.858 7.208 7.208 0 0 0-.878 3.46c0 4.021 3.278 7.277 7.318 7.277.527 0 1.04-.055 1.533-.16a.787.787 0 0 1 .81.316.733.733 0 0 1-.031.893A8.349 8.349 0 0 1 8.344 16C3.734 16 0 12.286 0 7.71 0 4.266 2.114 1.312 5.124.06A.752.752 0 0 1 6 .278zM4.858 1.311A7.269 7.269 0 0 0 1.025 7.71c0 4.02 3.279 7.276 7.319 7.276a7.316 7.316 0 0 0 5.205-2.162c-.337.042-.68.063-1.029.063-4.61 0-8.343-3.714-8.343-8.29 0-1.167.242-2.278.681-3.286z"
1028                            />
1029                            <path
1030                                d="M10.794 3.148a.217.217 0 0 1 .412 0l.387 1.162c.173.518.579.924 1.097 1.097l1.162.387a.217.217 0 0 1 0 .412l-1.162.387a1.734 1.734 0 0 0-1.097 1.097l-.387 1.162a.217.217 0 0 1-.412 0l-.387-1.162A1.734 1.734 0 0 0 9.31 6.593l-1.162-.387a.217.217 0 0 1 0-.412l1.162-.387a1.734 1.734 0 0 0 1.097-1.097l.387-1.162a.217.217 0 0 1 .412 0l.387 1.162zM13.863.099a.145.145 0 0 1 .274 0l.258.774c.115.346.386.617.732.732l.774.258a.145.145 0 0 1 0 .274l-.774.258a1.156 1.156 0 0 0-.732.732l-.258.774a.145.145 0 0 1-.274 0l-.258-.774a.145.145 0 0 1-.732-.732l-.774-.258a.145.145 0 0 1 0-.274l.774-.258c.346-.115.617-.386.732-.732L13.863.1z"
1031                            />
1032                        </svg>
1033                        <svg
1034                            xmlns="http://www.w3.org/2000/svg"
1035                            width="20"
1036                            height="20"
1037                            fill="currentColor"
1038                            class="bi bi-sun hidden dark:block"
1039                            viewBox="0 0 16 16"
1040                        >
1041                            <path
1042                                d="M8 11a3 3 0 1 1 0-6 3 3 0 0 1 0 6zm0 1a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM8 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 0zm0 13a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 13zm8-5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 .5.5zM3 8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2A.5.5 0 0 1 3 8zm10.657-5.657a.5.5 0 0 1 0 .707l-1.414 1.415a.5.5 0 0 1-.707-.707l1.414-1.414a.5.5 0 0 1 .707 0zm-9.193 9.193a.5.5 0 0 1 0 .707L3.05 13.657a.5.5 0 0 1-.707-.707l1.414-1.414a.5.5 0 0 1 .707 0zm9.193 2.121a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .707zM4.464 4.465a.5.5 0 0 1-.707 0L2.343 3.05a.5.5 0 1 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .708z"
1043                            />
1044                        </svg>
1045                    </button>
1046                </div>
1047                <div class="flex justify-between items-center">
1048                    <p class="text-lg text-gray-700 dark:text-gray-300">
1049                        Blog of a data person
1050                    </p>
1051                    <!-- Desktop menu -->
1052                    <div
1053                        class="mt-3 hidden md:flex space-x-6 text-gray-700 dark:text-gray-300"
1054                    >
1055                        <a
1056                            href="/posts"
1057                            class="hover:text-primary dark:hover:text-dark-primary"
1058                            >All Posts</a
1059                        >
1060                        <a
1061                            href="/apps/"
1062                            class="hover:text-primary dark:hover:text-dark-primary"
1063                            >Apps</a
1064                        >
1065                        <a
1066                            href="/about/"
1067                            class="hover:text-primary dark:hover:text-dark-primary"
1068                            >About</a
1069                        >
1070                        <a
1071                            href="/feed.xml"
1072                            class="hover:text-primary dark:hover:text-dark-primary"
1073                            >RSS</a
1074                        >
1075                        <a
1076                            href="https://koaning.substack.com/subscribe?utm_source=menu&simple=true&next=https%3A%2F%2Fkoaning.substack.com%2F"
1077                            class="hover:text-primary dark:hover:text-dark-primary"
1078                            >Subscribe</a
1079                        >
1080                    </div>
1081
1082                    <!-- Mobile hamburger menu -->
1083                    <div class="md:hidden" x-data="{ open: false }">
1084                        <button
1085                            @click="open = !open"
1086                            class="text-gray-700 dark:text-gray-300 hover:text-primary dark:hover:text-dark-primary focus:outline-none mt-3 z-20"
1087                        >
1088                            <svg
1089                                xmlns="http://www.w3.org/2000/svg"
1090                                class="h-6 w-6"
1091                                fill="none"
1092                                viewBox="0 0 24 24"
1093                                stroke="currentColor"
1094                            >
1095                                <path
1096                                    stroke-linecap="round"
1097                                    stroke-linejoin="round"
1098                                    stroke-width="2"
1099                                    d="M4 6h16M4 12h16M4 18h16"
1100                                />
1101                            </svg>
1102                        </button>
1103
1104                        <div
1105                            x-show="open"
1106                            @click.away="open = false"
1107                            class="absolute right-0 mt-2 py-2 w-48 bg-white dark:bg-dark-card rounded-md shadow-lg z-10"
1108                        >
1109                            <a
1110                                href="/"
1111                                class="block px-4 py-2 text-gray-700 dark:text-gray-300 hover:text-primary dark:hover:text-dark-primary"
1112                                >Home</a
1113                            >
1114                            <a
1115                                href="/posts"
1116                                class="block px-4 py-2 text-gray-700 dark:text-gray-300 hover:text-primary dark:hover:text-dark-primary"
1117                                >All Posts</a
1118                            >
1119                            <a
1120                                href="/apps/"
1121                                class="block px-4 py-2 text-gray-700 dark:text-gray-300 hover:text-primary dark:hover:text-dark-primary"
1122                                >Apps</a
1123                            >
1124                            <a
1125                                href="/about/"
1126                                class="block px-4 py-2 text-gray-700 dark:text-gray-300 hover:text-primary dark:hover:text-dark-primary"
1127                                >About</a
1128                            >
1129                            <a
1130                                href="/feed.xml"
1131                                class="block px-4 py-2 text-gray-700 dark:text-gray-300 hover:text-primary dark:hover:text-dark-primary"
1132                                >RSS</a
1133                            >
1134                            <a
1135                                href="https://koaning.substack.com/subscribe?utm_source=menu&simple=true&next=https%3A%2F%2Fkoaning.substack.com%2F"
1136                                class="block px-4 py-2 text-gray-700 dark:text-gray-300 hover:text-primary dark:hover:text-dark-primary"
1137                                >Subscribe</a
1138                            >
1139                        </div>
1140                    </div>
1141                </div>
1142            </div>
1143        </header>
1144        
1145
1146        <main class="container mx-auto max-w-3xl px-4 sm:px-6 py-8">
1147            
1148<div class="max-w-3xl mx-auto">
1149    
1150        
1151        <!-- Featured article (fully rendered) -->
1152        <article
1153            class="mb-10 pb-8 border-b border-gray-300 dark:border-gray-700"
1154        >
1155            <h2
1156                class="text-3xl font-bold text-gray-900 dark:text-gray-100 mb-3"
1157            >
1158                <a
1159                    href="/posts/widget-dag"
1160                    class="hover:text-primary dark:hover:text-dark-primary transition-colors"
1161                >
1162                    widget dag
1163                </a>
1164            </h2>
1165            <div class="flex items-center space-x-4 mb-4">
1166                <div class="text-gray-600 dark:text-gray-400 text-sm">
1167                    2026-07-22
1168                </div>
1169                
1170                <div class="flex flex-wrap gap-2">
1171                    
1172                    <a
1173                        href="/posts/?tag=python"
1174                        class="inline-block bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 text-xs px-2 py-1 rounded-full hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors"
1175                    >
1176                        python
1177                    </a>
1178                    
1179                    <a
1180                        href="/posts/?tag=machine-learning"
1181                        class="inline-block bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 text-xs px-2 py-1 rounded-full hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors"
1182                    >
1183                        machine-learning
1184                    </a>
1185                    
1186                </div>
1187                
1188            </div>
1189
1190            <div class="prose prose-lg dark:prose-invert max-w-none my-6 prose-wide-widgets">
1191                <marimo-mdx-island class="marimo-island-host" data-marimo-host="mdx" data-marimo-theme-mode="auto" data-marimo-app-id="marimo-72b1d94c11ad" data-marimo-cell-index="0" data-marimo-payload-encoding="base64url" data-marimo-payload="eyJwcm90b2NvbFZlcnNpb24iOjEsImFwcCI6eyJpZCI6Im1hcmltby03MmIxZDk0YzExYWQiLCJydW50aW1lQ2VsbENvdW50Ijo5LCJhc3NldHMiOnsibW9kdWxlU2NyaXB0cyI6WyJodHRwczovL2Nkbi5qc2RlbGl2ci5uZXQvbnBtL0BtYXJpbW8tdGVhbS9pc2xhbmRzQDAuMjMuMTUvZGlzdC9tYWluLmpzIl0sImxpbmtzIjpbeyJocmVmIjoiaHR0cHM6Ly9jZG4uanNkZWxpdnIubmV0L25wbS9AbWFyaW1vLXRlYW0vaXNsYW5kc0AwLjIzLjE1L2Rpc3Qvc3R5bGUuY3NzIiwicmVsIjoic3R5bGVzaGVldCIsInRpdGxlIjoibWFyaW1vLWlzbGFuZHMiLCJjcm9zc29yaWdpbiI6ImFub255bW91cyJ9LHsicmVsIjoicHJlY29ubmVjdCIsImhy
1191ZWYiOiJodHRwczovL2ZvbnRzLmdvb2dsZWFwaXMuY29tIn0seyJyZWwiOiJwcmVjb25uZWN0IiwiaHJlZiI6Imh0dHBzOi8vZm9udHMuZ3N0YXRpYy5jb20iLCJjcm9zc29yaWdpbiI6IiJ9LHsiaHJlZiI6Imh0dHBzOi8vZm9udHMuZ29vZ2xlYXBpcy5jb20vY3NzMj9mYW1pbHk9RmlyYStNb25vOndnaHRANDAwOzUwMDs3MDAmZmFtaWx5PUxvcmEmZmFtaWx5PVBUK1NhbnM6d2dodEA0MDA7NzAwJmRpc3BsYXk9c3dhcCIsInJlbCI6InN0eWxlc2hlZXQifSx7InJlbCI6InN0eWxlc2hlZXQiLCJocmVmIjoiaHR0cHM6Ly9jZG4uanNkZWxpdnIubmV0L25wbS9rYXRleEAwLjE2LjEwL2Rpc3Qva2F0ZXgubWluLmNzcyIsImludGVncml0eSI6InNoYTM4NC13Y0l4a2Y0azU1OEFqTTNZejNCQkZRVWJrL3pnSVlDMlIwUXBlZVliK1R3bEJWTXJsZ0xxd1JqUnRHWmlLN3d3IiwiY3Jvc3NvcmlnaW4iOiJhbm9ueW1vdXMifV0sImhlYWRUYWdzIjpbeyJ0YWciOiJtYXJpbW8tZmlsZW5hbWUiLCJhdHRycyI6eyJoaWRkZW4iOiIifSwidGV4dCI6ImRvY3VtZW50Lm1keCJ9XSwidmVyc2lvbiI6IjAuMjMuMTUifSwibm90ZWJvb2tDb2RlIjoiIyAvLy8gc2NyaXB0XG4jIHJlcXVpcmVzLXB5dGhvbiA9IFwiPj0zLjEyXCJcbiMgZGVwZW5kZW5jaWVzID0gW1xuIyAgICAgXCJtYXJpbW8-PTAuMjMuMTNcIixcbiMgICAgIFwibW9odG1sXCIsXG4jICAgICBcIm51bXB5XCIsXG4jICAgICBcInBpbGxvd1wiLFxuIyAgICAgXCJ3aWdnbHlzdHVmZj09MC41LjIxXCIsXG4jIF1cbiMgLy8vXG5pbXBvcnQgbWFyaW1vXG5cbl9fZ2VuZXJhdGVkX3dpdGggPSBcIjAuMjMuMTVcIlxuYXBwID0gbWFyaW1vLkFwcCgpXG5cblxuQGFwcC5jZWxsXG5kZWYgXygpOlxuICAgIGZyb20gcGF0aGxpYiBpbXBvcnQgUGF0aFxuICAgIGltcG9ydCBzeXNcblxuICAgIGltcG9ydCBtYXJpbW8gYXMgbW9cblxuICAgICMgUHJlZmVyIHRoZSBsb2NhbCBjaGVja291dCBzbyB0aGlzIGRlbW8gdHJhY2tzIHRoZSBpbi1yZXBvIFdpZGdldERBRyBldmVuXG4gICAgIyBiZWZvcmUgaXQgbGFuZHMgaW4gYSBwdWJsaXNoZWQgd2lnZ2x5c3R1ZmYgcmVsZWFzZS5cbiAgICByZXBvX3Jvb3QgPSBQYXRoKF9fZmlsZV9fKS5yZXNvbHZlKCkucGFyZW50c1sxXVxuICAgIGlmIChyZXBvX3Jvb3QgLyBcIndpZ2dseXN0dWZmXCIpLmV4aXN0cygpOlxuICAgICAgICBzeXMucGF0aC5pbnNlcnQoMCwgc3RyKHJlcG9fcm9vdCkpXG5cbiAgICBmcm9tIHdpZ2dseXN0dWZmIGltcG9ydCBQYWludCwgV2lkZ2V0REFHXG5cbiAgICByZXR1cm4gUGFpbnQsIFdpZGdldERBRywgbW9cblxuXG5AYXBwLmNlbGxcbmRlZiBfKFdpZGdldERBRywgYW5nbGUsIGNvbnYsIGNvbnYyLCBrZXJuZWwsIGtlcm5lbDIsIHBhaW50KTpcbiAgICBXaWRnZXREQUcuZnJvbV93aWRnZXRzKFtwYWludCwgYW5nbGUsIGtlcm5lbCwgY29udiwga2VybmVsMiwgY29udjJdKVxuICAgIHJldHVyblxuXG5cbkBhcHAuY2VsbFxuZGVmIF8oUGFpbnQsIG1vKTpcbiAgICAjIFRoZSBsaXZlIGRyYXdpbmcgc3VyZmFjZSAtLSBpdCBJUyB0aGUgXCJwYWludFwiIG5vZGUgZW1iZWRkZWQgaW4gdGhlIERBRyBiZWxvdy5cbiAgICAjIHN0b3JlX2JhY2tncm91bmQ9RmFsc2Uga2VlcHMgdGhlIGNhbnZhcyB0cmFuc3BhcmVudCwgc28gdGhlIGFscGhhIGNoYW5uZWwgb2ZcbiAgICAjIHRoZSBleHBvcnRlZCBQTkcgaXMgYSBjbGVhbiBpbmsgbWFzayAob3BhcXVlIHdoZXJlIHlvdSBkcmV3LCBlbHNlIHRyYW5zcGFyZW50KS5cbiAgICBwYWludCA9IG1vLnVpLmFueXdpZGdldChQYWludCh3aWR0aD0yNjAsIGhlaWdodD0yNjAsIGNvbG9yX3BpY2tlcj1GYWxzZSwgc3RvcmVfYmFja2dyb3VuZD1GYWxzZSkpXG4gICAgcmV0dXJuIChwYWludCwpXG5cblxuQGFwcC5jZWxsXG5kZWYgXyhtbyk6XG4gICAgIyBGaXJzdCBjb252b2x1dGlvbiBrZXJuZWwgLS0gYSBuYXRpdmUgbW8udWkubWF0cml4IGVkaXRvciAoZHJhZyBhbiBlbnRyeSB0b1xuICAgICMgY2hhbmdlIGl0KS4gSXQgbGl2ZXMgaW4gaXRzIG93biBjZWxsIHNvIHRoZSBEQUcgY2FuIHdpcmUgaXQgdG8gYGNvbnZgIG9ubHkuXG4gICAga2VybmVsID0gbW8udWkubWF0cml4KFtbMCwgLTEsIDBdLCBbLTEsIDUsIC0xXSwgWzAsIC0xLCAwXV0pXG4gICAgcmV0dXJuIChrZXJuZWwsKVxuXG5cbkBhcHAuY2VsbFxuZGVmIF8obW8pOlxuICAgICMgU2Vjb25kIGtlcm5lbCwgaW4gaXRzIG93biBjZWxsIHRvbyAtLSBpdCBmZWVkcyBgY29udjJgLlxuICAgIGtlcm5lbDIgPSBtby51aS5tYXRyaXgoW1sxLCAxLCAxXSwgWzEsIDEsIDFdLCBbMSwgMSwgMV1dKVxuICAgIHJldHVybiAoa2VybmVsMiwpXG5cblxuQGFwcC5jZWxsXG5kZWYgXyhtbyk6XG4gICAgIyBBIHBsYWluIG1vLnVpLnNsaWRlciAtLSBpdCBsaXZlcyBpbiB0aGUgREFHIGFzIGEgbm9kZSBqdXN0IGxpa2UgdGhlIHdpZGdldHNcbiAgICAjIGFib3ZlLCBhbmQgcm90YXRlcyB0aGUgZHJhd24gZGlnaXQgYmVmb3JlIGl0IGlzIGNvbnZvbHZlZC5cbiAgICBhbmdsZSA9IG1vLnVpLnNsaWRlcigtMTgwLCAxODAsIHZhbHVlPTAsIHN0ZXA9NSwgbGFiZWw9XCJyb3RhdGXCsFwiLCBzaG93X3ZhbHVlPVRydWUpXG4gICAgcmV0dXJuIChhbmdsZSwpXG5cblxuQGFwcC5jZWxsXG5kZWYgXygpOlxuICAgICMgU2hhcmVkIGhlbHBlcnMgLS0gZXhwb3J0ZWQgKG5vIGxlYWRpbmcgdW5kZXJzY29yZSkgc28gdGhlIHR3byBjb252b2x1dGlvblxuICAgICMgY2VsbHMgYmVsb3cgY2FuIGJvdGggdXNlIHRoZW0uXG4gICAgaW1wb3J0IGJhc2U2NFxuICAgIGltcG9ydCBpb1xuXG4gICAgaW1wb3J0IG51bXB5IGFzIG5wXG4gICAgZnJvbSBtb2h0bWwgaW1wb3J0IGltZ1xuICAgIGZyb20gUElMIGltcG9ydCBJbWFnZVxuXG4gICAgZGVmIHRvX2ltZyhhLCB3aWR0aD0xODApOlxuICAgICAgICBhID0gbnAuYXNhcnJheShhLCBkdHlwZT1mbG9hdClcbiAgICAgICAgbG8sIGhpID0gZmxvYXQoYS5taW4oKSksIGZsb2F0KGEubWF4KCkpXG4gICAgICAgIHU4ID0gKChhIC0gbG8pIC8gKGhpIC0gbG8pICogMjU1IGlmIGhpID4gbG8gZWxzZSBucC56ZXJvc19saWtlKGEpKS5hc3R5cGUoXCJ1aW50OFwiKVxuICAgICAgICBidWYgPSBpby5CeXRlc0lPKClcbiAgICAgICAgSW1hZ2UuZnJvbWFycmF5KHU4KS5zYXZlKGJ1ZiwgZm9ybWF0PVwiUE5HXCIpXG4gICAgICAgI
1191HNyYyA9IFwiZGF0YTppbWFnZS9wbmc7YmFzZTY0LFwiICsgYmFzZTY0LmI2NGVuY29kZShidWYuZ2V0dmFsdWUoKSkuZGVjb2RlKClcbiAgICAgICAgcmV0dXJuIGltZyhcbiAgICAgICAgICAgIHNyYz1zcmMsXG4gICAgICAgICAgICBzdHlsZT1mXCJ3aWR0aDp7d2lkdGh9cHg7aW1hZ2UtcmVuZGVyaW5nOnBpeGVsYXRlZDtib3JkZXI6MXB4IHNvbGlkICNjY2NcIixcbiAgICAgICAgKVxuXG4gICAgZGVmIGNvbnZvbHZlKGEsIGspOlxuICAgICAgICBmcm9tIG51bXB5LmxpYi5zdHJpZGVfdHJpY2tzIGltcG9ydCBzbGlkaW5nX3dpbmRvd192aWV3XG5cbiAgICAgICAga2gsIGt3ID0gay5zaGFwZVxuICAgICAgICByZXR1cm4gbnAuZWluc3VtKFwiaWprbCxrbC0-aWpcIiwgc2xpZGluZ193aW5kb3dfdmlldyhhLCAoa2gsIGt3KSksIGspXG5cbiAgICByZXR1cm4gY29udm9sdmUsIG5wLCB0b19pbWdcblxuXG5AYXBwLmNlbGxcbmRlZiBfKGFuZ2xlLCBjb252b2x2ZSwga2VybmVsLCBucCwgcGFpbnQsIHRvX2ltZyk6XG4gICAgIyBUaGUgZHJhd2luZyAtPiByb3RhdGUgYnkgdGhlIHNsaWRlciAtPiBhIDI4eDI4IGFycmF5IC0-IGNvbnZvbHZlIHdpdGggYGtlcm5lbGAuXG4gICAgIyBUaGUgYWxwaGEgY2hhbm5lbCBpcyB0aGUgaW5rIG1hc2sgKGJyaWdodCB3aGVyZSB5b3UgZHJldyk7IHJvdGF0aW5nIHRoZVxuICAgICMgZnVsbC1yZXMgUElMICh0cmFuc3BhcmVudCBmaWxsKSBrZWVwcyB0aGF0IG1hc2sgY2xlYW4uIGBjb252YCBpcyB0aGUgaW1hZ2VcbiAgICAjIG5vZGU7IGBvdXQxYCBpcyB0aGUgYXJyYXkgdGhlIG5leHQgY2VsbCBwaWNrcyB1cCAodGhhdCByZWZlcmVuY2UgaXMgdGhlIGVkZ2VcbiAgICAjIGBjb252IC0-IGNvbnYyYCB0aGUgREFHIGRyYXdzKS5cbiAgICBfcGlsID0gcGFpbnQuZ2V0X3BpbCgpLmNvbnZlcnQoXCJSR0JBXCIpLnJvdGF0ZShhbmdsZS52YWx1ZSlcbiAgICBfYXJyID0gbnAuYXJyYXkoX3BpbC5yZXNpemUoKDI4LCAyOCkpKVsuLi4sIDNdLmFzdHlwZShmbG9hdClcbiAgICBvdXQxID0gY29udm9sdmUoX2FyciwgbnAuYXNhcnJheShrZXJuZWwudmFsdWUsIGR0eXBlPWZsb2F0KSlcbiAgICBjb252ID0gdG9faW1nKG91dDEpXG4gICAgcmV0dXJuIGNvbnYsIG91dDFcblxuXG5AYXBwLmNlbGxcbmRlZiBfKGNvbnZvbHZlLCBrZXJuZWwyLCBucCwgb3V0MSwgdG9faW1nKTpcbiAgICAjIFNlY29uZCBjb252b2x1dGlvbjogYG91dDFgIChmcm9tIHRoZSBjZWxsIGFib3ZlKSBibHVycmVkIGJ5IGBrZXJuZWwyYC5cbiAgICBvdXQyID0gY29udm9sdmUob3V0MSwgbnAuYXNhcnJheShrZXJuZWwyLnZhbHVlLCBkdHlwZT1mbG9hdCkpXG4gICAgY29udjIgPSB0b19pbWcob3V0MilcbiAgICByZXR1cm4gKGNvbnYyLClcblxuXG5pZiBfX25hbWVfXyA9PSBcIl9fbWFpbl9fXCI6XG4gICAgYXBwLnJ1bigpIn0sImNlbGwiOnsiaHRtbCI6IjxtYXJpbW8taXNsYW5kXG4gICAgZGF0YS1hcHAtaWQ9XCJtYXJpbW8tNzJiMWQ5NGMxMWFkXCJcbiAgICBkYXRhLWNlbGwtaWQ9XCJIYm9sXCJcbiAgICBkYXRhLXJlYWN0aXZlPVwidHJ1ZVwiXG4-XG4gICAgPG1hcmltby1jZWxsLW91dHB1dD5cbiAgICA8c3Bhbj48L3NwYW4-XG4gICAgPC9tYXJpbW8tY2VsbC1vdXRwdXQ-XG4gICAgPG1hcmltby1jZWxsLWNvZGUgaGlkZGVuPmZyb20lMjBwYXRobGliJTIwaW1wb3J0JTIwUGF0aCUwQWltcG9ydCUyMHN5cyUwQSUwQWltcG9ydCUyMG1hcmltbyUyMGFzJTIwbW8lMEElMEElMjMlMjBQcmVmZXIlMjB0aGUlMjBsb2NhbCUyMGNoZWNrb3V0JTIwc28lMjB0aGlzJTIwZGVtbyUyMHRyYWNrcyUyMHRoZSUyMGluLXJlcG8lMjBXaWRnZXREQUclMjBldmVuJTBBJTIzJTIwYmVmb3JlJTIwaXQlMjBsYW5kcyUyMGluJTIwYSUyMHB1Ymxpc2hlZCUyMHdpZ2dseXN0dWZmJTIwcmVsZWFzZS4lMEFyZXBvX3Jvb3QlMjAlM0QlMjBQYXRoKF9fZmlsZV9fKS5yZXNvbHZlKCkucGFyZW50cyU1QjElNUQlMEFpZiUyMChyZXBvX3Jvb3QlMjAlMkYlMjAlMjJ3aWdnbHlzdHVmZiUyMikuZXhpc3RzKCklM0ElMEElMjAlMjAlMjAlMjBzeXMucGF0aC5pbnNlcnQoMCUyQyUyMHN0cihyZXBvX3Jvb3QpKSUwQSUwQWZyb20lMjB3aWdnbHlzdHVmZiUyMGltcG9ydCUyMFBhaW50JTJDJTIwV2lkZ2V0REFHPC9tYXJpbW8tY2VsbC1jb2RlPlxuPC9tYXJpbW8taXNsYW5kPiIsImluZGV4IjowLCJvcHRpb25zIjp7Imxhbmd1YWdlIjoicHl0aG9uIiwicmVuZGVyIjp7InNvdXJjZSI6ZmFsc2UsIm91dHB1dCI6dHJ1ZSwiaW5jbHVkZSI6dHJ1ZSwiZWRpdG9yIjpmYWxzZSwiZXJyb3IiOnRydWUsInNlcnZlck91dHB1dCI6dHJ1ZX0sImV4ZWN1dGlvbiI6eyJlbmFibGVkIjp0cnVlfSwibWFyaW1vIjp7ImRpc2FibGVkIjpmYWxzZSwidW5wYXJzYWJsZSI6ZmFsc2V9fX19"></marimo-mdx-island>
1192<p>There's something really cool (and new!) that I can host on my blog now thanks to
1193<a href="https://github.com/koaning/wigglystuff">wigglystuff</a>, <a href="https://marimo.io">marimo</a> and <a href="https://github.com/marimo-team/mdx-marimo">marimo-mdx</a>. It's notebooks represented as graphs, like this one:</p>
1194<marimo-mdx-island class="marimo-island-host" data-marimo-host="mdx" data-marimo-theme-mode="auto" data-marimo-app-id="marimo-72b1d94c11ad" data-marimo-cell-index="1" data-marimo-payload-encoding="base64url" data-marimo-payload="eyJwcm90b2NvbFZlcnNpb24iOjEsImFwcCI6eyJpZCI6Im1hcmltby03MmIxZDk0YzExYWQiLCJydW50aW1lQ2VsbENvdW50Ijo5LCJhc3NldHMiOnsibW9kdWxlU2NyaXB0cyI6WyJodHRwczovL2Nkbi5qc2RlbGl2ci5uZXQvbnBtL0BtYXJpbW8tdGVhbS9pc2xhbmRzQDAuMjMuMTUvZGlzdC9tYWluLmpzIl0sImxpbmtzIjpbeyJocmVmIjoiaHR0cHM6Ly9jZG4uanNkZWxpdnIubmV0L25wbS9AbWFyaW1vLXRlYW0vaXNsYW5kc0AwLjIzLjE1L2Rpc3Qvc3R5bGUuY3NzIiwicmVsIjoic3R5bGVzaGVldCIsInRpdGxlIjoibWFyaW1vLWlzbGFuZHMiLCJjcm9zc29yaWdpbiI6ImFub255bW91cyJ9LHsicmVsIjoicHJlY29ubmVjdCIsImhy
1194ZWYiOiJodHRwczovL2ZvbnRzLmdvb2dsZWFwaXMuY29tIn0seyJyZWwiOiJwcmVjb25uZWN0IiwiaHJlZiI6Imh0dHBzOi8vZm9udHMuZ3N0YXRpYy5jb20iLCJjcm9zc29yaWdpbiI6IiJ9LHsiaHJlZiI6Imh0dHBzOi8vZm9udHMuZ29vZ2xlYXBpcy5jb20vY3NzMj9mYW1pbHk9RmlyYStNb25vOndnaHRANDAwOzUwMDs3MDAmZmFtaWx5PUxvcmEmZmFtaWx5PVBUK1NhbnM6d2dodEA0MDA7NzAwJmRpc3BsYXk9c3dhcCIsInJlbCI6InN0eWxlc2hlZXQifSx7InJlbCI6InN0eWxlc2hlZXQiLCJocmVmIjoiaHR0cHM6Ly9jZG4uanNkZWxpdnIubmV0L25wbS9rYXRleEAwLjE2LjEwL2Rpc3Qva2F0ZXgubWluLmNzcyIsImludGVncml0eSI6InNoYTM4NC13Y0l4a2Y0azU1OEFqTTNZejNCQkZRVWJrL3pnSVlDMlIwUXBlZVliK1R3bEJWTXJsZ0xxd1JqUnRHWmlLN3d3IiwiY3Jvc3NvcmlnaW4iOiJhbm9ueW1vdXMifV0sImhlYWRUYWdzIjpbeyJ0YWciOiJtYXJpbW8tZmlsZW5hbWUiLCJhdHRycyI6eyJoaWRkZW4iOiIifSwidGV4dCI6ImRvY3VtZW50Lm1keCJ9XSwidmVyc2lvbiI6IjAuMjMuMTUifSwibm90ZWJvb2tDb2RlIjoiIyAvLy8gc2NyaXB0XG4jIHJlcXVpcmVzLXB5dGhvbiA9IFwiPj0zLjEyXCJcbiMgZGVwZW5kZW5jaWVzID0gW1xuIyAgICAgXCJtYXJpbW8-PTAuMjMuMTNcIixcbiMgICAgIFwibW9odG1sXCIsXG4jICAgICBcIm51bXB5XCIsXG4jICAgICBcInBpbGxvd1wiLFxuIyAgICAgXCJ3aWdnbHlzdHVmZj09MC41LjIxXCIsXG4jIF1cbiMgLy8vXG5pbXBvcnQgbWFyaW1vXG5cbl9fZ2VuZXJhdGVkX3dpdGggPSBcIjAuMjMuMTVcIlxuYXBwID0gbWFyaW1vLkFwcCgpXG5cblxuQGFwcC5jZWxsXG5kZWYgXygpOlxuICAgIGZyb20gcGF0aGxpYiBpbXBvcnQgUGF0aFxuICAgIGltcG9ydCBzeXNcblxuICAgIGltcG9ydCBtYXJpbW8gYXMgbW9cblxuICAgICMgUHJlZmVyIHRoZSBsb2NhbCBjaGVja291dCBzbyB0aGlzIGRlbW8gdHJhY2tzIHRoZSBpbi1yZXBvIFdpZGdldERBRyBldmVuXG4gICAgIyBiZWZvcmUgaXQgbGFuZHMgaW4gYSBwdWJsaXNoZWQgd2lnZ2x5c3R1ZmYgcmVsZWFzZS5cbiAgICByZXBvX3Jvb3QgPSBQYXRoKF9fZmlsZV9fKS5yZXNvbHZlKCkucGFyZW50c1sxXVxuICAgIGlmIChyZXBvX3Jvb3QgLyBcIndpZ2dseXN0dWZmXCIpLmV4aXN0cygpOlxuICAgICAgICBzeXMucGF0aC5pbnNlcnQoMCwgc3RyKHJlcG9fcm9vdCkpXG5cbiAgICBmcm9tIHdpZ2dseXN0dWZmIGltcG9ydCBQYWludCwgV2lkZ2V0REFHXG5cbiAgICByZXR1cm4gUGFpbnQsIFdpZGdldERBRywgbW9cblxuXG5AYXBwLmNlbGxcbmRlZiBfKFdpZGdldERBRywgYW5nbGUsIGNvbnYsIGNvbnYyLCBrZXJuZWwsIGtlcm5lbDIsIHBhaW50KTpcbiAgICBXaWRnZXREQUcuZnJvbV93aWRnZXRzKFtwYWludCwgYW5nbGUsIGtlcm5lbCwgY29udiwga2VybmVsMiwgY29udjJdKVxuICAgIHJldHVyblxuXG5cbkBhcHAuY2VsbFxuZGVmIF8oUGFpbnQsIG1vKTpcbiAgICAjIFRoZSBsaXZlIGRyYXdpbmcgc3VyZmFjZSAtLSBpdCBJUyB0aGUgXCJwYWludFwiIG5vZGUgZW1iZWRkZWQgaW4gdGhlIERBRyBiZWxvdy5cbiAgICAjIHN0b3JlX2JhY2tncm91bmQ9RmFsc2Uga2VlcHMgdGhlIGNhbnZhcyB0cmFuc3BhcmVudCwgc28gdGhlIGFscGhhIGNoYW5uZWwgb2ZcbiAgICAjIHRoZSBleHBvcnRlZCBQTkcgaXMgYSBjbGVhbiBpbmsgbWFzayAob3BhcXVlIHdoZXJlIHlvdSBkcmV3LCBlbHNlIHRyYW5zcGFyZW50KS5cbiAgICBwYWludCA9IG1vLnVpLmFueXdpZGdldChQYWludCh3aWR0aD0yNjAsIGhlaWdodD0yNjAsIGNvbG9yX3BpY2tlcj1GYWxzZSwgc3RvcmVfYmFja2dyb3VuZD1GYWxzZSkpXG4gICAgcmV0dXJuIChwYWludCwpXG5cblxuQGFwcC5jZWxsXG5kZWYgXyhtbyk6XG4gICAgIyBGaXJzdCBjb252b2x1dGlvbiBrZXJuZWwgLS0gYSBuYXRpdmUgbW8udWkubWF0cml4IGVkaXRvciAoZHJhZyBhbiBlbnRyeSB0b1xuICAgICMgY2hhbmdlIGl0KS4gSXQgbGl2ZXMgaW4gaXRzIG93biBjZWxsIHNvIHRoZSBEQUcgY2FuIHdpcmUgaXQgdG8gYGNvbnZgIG9ubHkuXG4gICAga2VybmVsID0gbW8udWkubWF0cml4KFtbMCwgLTEsIDBdLCBbLTEsIDUsIC0xXSwgWzAsIC0xLCAwXV0pXG4gICAgcmV0dXJuIChrZXJuZWwsKVxuXG5cbkBhcHAuY2VsbFxuZGVmIF8obW8pOlxuICAgICMgU2Vjb25kIGtlcm5lbCwgaW4gaXRzIG93biBjZWxsIHRvbyAtLSBpdCBmZWVkcyBgY29udjJgLlxuICAgIGtlcm5lbDIgPSBtby51aS5tYXRyaXgoW1sxLCAxLCAxXSwgWzEsIDEsIDFdLCBbMSwgMSwgMV1dKVxuICAgIHJldHVybiAoa2VybmVsMiwpXG5cblxuQGFwcC5jZWxsXG5kZWYgXyhtbyk6XG4gICAgIyBBIHBsYWluIG1vLnVpLnNsaWRlciAtLSBpdCBsaXZlcyBpbiB0aGUgREFHIGFzIGEgbm9kZSBqdXN0IGxpa2UgdGhlIHdpZGdldHNcbiAgICAjIGFib3ZlLCBhbmQgcm90YXRlcyB0aGUgZHJhd24gZGlnaXQgYmVmb3JlIGl0IGlzIGNvbnZvbHZlZC5cbiAgICBhbmdsZSA9IG1vLnVpLnNsaWRlcigtMTgwLCAxODAsIHZhbHVlPTAsIHN0ZXA9NSwgbGFiZWw9XCJyb3RhdGXCsFwiLCBzaG93X3ZhbHVlPVRydWUpXG4gICAgcmV0dXJuIChhbmdsZSwpXG5cblxuQGFwcC5jZWxsXG5kZWYgXygpOlxuICAgICMgU2hhcmVkIGhlbHBlcnMgLS0gZXhwb3J0ZWQgKG5vIGxlYWRpbmcgdW5kZXJzY29yZSkgc28gdGhlIHR3byBjb252b2x1dGlvblxuICAgICMgY2VsbHMgYmVsb3cgY2FuIGJvdGggdXNlIHRoZW0uXG4gICAgaW1wb3J0IGJhc2U2NFxuICAgIGltcG9ydCBpb1xuXG4gICAgaW1wb3J0IG51bXB5IGFzIG5wXG4gICAgZnJvbSBtb2h0bWwgaW1wb3J0IGltZ1xuICAgIGZyb20gUElMIGltcG9ydCBJbWFnZVxuXG4gICAgZGVmIHRvX2ltZyhhLCB3aWR0aD0xODApOlxuICAgICAgICBhID0gbnAuYXNhcnJheShhLCBkdHlwZT1mbG9hdClcbiAgICAgICAgbG8sIGhpID0gZmxvYXQoYS5taW4oKSksIGZsb2F0KGEubWF4KCkpXG4gICAgICAgIHU4ID0gKChhIC0gbG8pIC8gKGhpIC0gbG8pICogMjU1IGlmIGhpID4gbG8gZWxzZSBucC56ZXJvc19saWtlKGEpKS5hc3R5cGUoXCJ1aW50OFwiKVxuICAgICAgICBidWYgPSBpby5CeXRlc0lPKClcbiAgICAgICAgSW1hZ2UuZnJvbWFycmF5KHU4KS5zYXZlKGJ1ZiwgZm9ybWF0PVwiUE5HXCIpXG4gICAgICAgI
1194HNyYyA9IFwiZGF0YTppbWFnZS9wbmc7YmFzZTY0LFwiICsgYmFzZTY0LmI2NGVuY29kZShidWYuZ2V0dmFsdWUoKSkuZGVjb2RlKClcbiAgICAgICAgcmV0dXJuIGltZyhcbiAgICAgICAgICAgIHNyYz1zcmMsXG4gICAgICAgICAgICBzdHlsZT1mXCJ3aWR0aDp7d2lkdGh9cHg7aW1hZ2UtcmVuZGVyaW5nOnBpeGVsYXRlZDtib3JkZXI6MXB4IHNvbGlkICNjY2NcIixcbiAgICAgICAgKVxuXG4gICAgZGVmIGNvbnZvbHZlKGEsIGspOlxuICAgICAgICBmcm9tIG51bXB5LmxpYi5zdHJpZGVfdHJpY2tzIGltcG9ydCBzbGlkaW5nX3dpbmRvd192aWV3XG5cbiAgICAgICAga2gsIGt3ID0gay5zaGFwZVxuICAgICAgICByZXR1cm4gbnAuZWluc3VtKFwiaWprbCxrbC0-aWpcIiwgc2xpZGluZ193aW5kb3dfdmlldyhhLCAoa2gsIGt3KSksIGspXG5cbiAgICByZXR1cm4gY29udm9sdmUsIG5wLCB0b19pbWdcblxuXG5AYXBwLmNlbGxcbmRlZiBfKGFuZ2xlLCBjb252b2x2ZSwga2VybmVsLCBucCwgcGFpbnQsIHRvX2ltZyk6XG4gICAgIyBUaGUgZHJhd2luZyAtPiByb3RhdGUgYnkgdGhlIHNsaWRlciAtPiBhIDI4eDI4IGFycmF5IC0-IGNvbnZvbHZlIHdpdGggYGtlcm5lbGAuXG4gICAgIyBUaGUgYWxwaGEgY2hhbm5lbCBpcyB0aGUgaW5rIG1hc2sgKGJyaWdodCB3aGVyZSB5b3UgZHJldyk7IHJvdGF0aW5nIHRoZVxuICAgICMgZnVsbC1yZXMgUElMICh0cmFuc3BhcmVudCBmaWxsKSBrZWVwcyB0aGF0IG1hc2sgY2xlYW4uIGBjb252YCBpcyB0aGUgaW1hZ2VcbiAgICAjIG5vZGU7IGBvdXQxYCBpcyB0aGUgYXJyYXkgdGhlIG5leHQgY2VsbCBwaWNrcyB1cCAodGhhdCByZWZlcmVuY2UgaXMgdGhlIGVkZ2VcbiAgICAjIGBjb252IC0-IGNvbnYyYCB0aGUgREFHIGRyYXdzKS5cbiAgICBfcGlsID0gcGFpbnQuZ2V0X3BpbCgpLmNvbnZlcnQoXCJSR0JBXCIpLnJvdGF0ZShhbmdsZS52YWx1ZSlcbiAgICBfYXJyID0gbnAuYXJyYXkoX3BpbC5yZXNpemUoKDI4LCAyOCkpKVsuLi4sIDNdLmFzdHlwZShmbG9hdClcbiAgICBvdXQxID0gY29udm9sdmUoX2FyciwgbnAuYXNhcnJheShrZXJuZWwudmFsdWUsIGR0eXBlPWZsb2F0KSlcbiAgICBjb252ID0gdG9faW1nKG91dDEpXG4gICAgcmV0dXJuIGNvbnYsIG91dDFcblxuXG5AYXBwLmNlbGxcbmRlZiBfKGNvbnZvbHZlLCBrZXJuZWwyLCBucCwgb3V0MSwgdG9faW1nKTpcbiAgICAjIFNlY29uZCBjb252b2x1dGlvbjogYG91dDFgIChmcm9tIHRoZSBjZWxsIGFib3ZlKSBibHVycmVkIGJ5IGBrZXJuZWwyYC5cbiAgICBvdXQyID0gY29udm9sdmUob3V0MSwgbnAuYXNhcnJheShrZXJuZWwyLnZhbHVlLCBkdHlwZT1mbG9hdCkpXG4gICAgY29udjIgPSB0b19pbWcob3V0MilcbiAgICByZXR1cm4gKGNvbnYyLClcblxuXG5pZiBfX25hbWVfXyA9PSBcIl9fbWFpbl9fXCI6XG4gICAgYXBwLnJ1bigpIn0sImNlbGwiOnsiaHRtbCI6IjxtYXJpbW8taXNsYW5kXG4gICAgZGF0YS1hcHAtaWQ9XCJtYXJpbW8tNzJiMWQ5NGMxMWFkXCJcbiAgICBkYXRhLWNlbGwtaWQ9XCJNSlVlXCJcbiAgICBkYXRhLXJlYWN0aXZlPVwidHJ1ZVwiXG4-XG4gICAgPG1hcmltby1jZWxsLW91dHB1dD5cbiAgICA8bWFyaW1vLW1pbWUtcmVuZGVyZXIgZGF0YS1taW1lPScmcXVvdDt0ZXh0L21hcmtkb3duJnF1b3Q7JyBkYXRhLWRhdGE9JyZxdW90OyYjOTI7dTAwM2NzcGFuIGNsYXNzPSYjOTI7JnF1b3Q7bWFya2Rvd24gcHJvc2UgZGFyazpwcm9zZS1pbnZlcnQgY29udGVudHMmIzkyOyZxdW90OyYjOTI7dTAwM2UmIzkyO3UwMDNjZGl2IGRhdGEtd2RhZy1yb290IHN0eWxlPSYjOTI7JnF1b3Q7cG9zaXRpb246cmVsYXRpdmU7ZGlzcGxheTppbmxpbmUtYmxvY2smIzkyOyZxdW90OyYjOTI7dTAwM2UmIzkyO3UwMDNjZGl2IHN0eWxlPSYjeDI3O2Rpc3BsYXk6IGZsZXg7ZmxleDogMTtmbGV4LWRpcmVjdGlvbjogcm93O2p1c3RpZnktY29udGVudDogZmxleC1zdGFydDthbGlnbi1pdGVtczogY2VudGVyO2ZsZXgtd3JhcDogbm93cmFwO2dhcDogNnJlbSYjeDI3OyYjOTI7dTAwM2UmIzkyO3UwMDNjZGl2IHN0eWxlPSYjeDI3O2Rpc3BsYXk6IGZsZXg7ZmxleDogMTtmbGV4LWRpcmVjdGlvbjogY29sdW1uO2p1c3RpZnktY29udGVudDogZmxleC1zdGFydDthbGlnbi1pdGVtczogY2VudGVyO2ZsZXgtd3JhcDogbm93cmFwO2dhcDogMS41cmVtJiN4Mjc7JiM5Mjt1MDAzZSYjOTI7dTAwM2NzcGFuIGNsYXNzPSYjOTI7JnF1b3Q7bWFya2Rvd24gcHJvc2UgZGFyazpwcm9zZS1pbnZlcnQgY29udGVudHMmIzkyOyZxdW90OyYjOTI7dTAwM2UmIzkyO3UwMDNjZGl2IGRhdGEtd2RhZy1ub2RlPSYjOTI7JnF1b3Q7cGFpbnQmIzkyOyZxdW90OyBzdHlsZT0mIzkyOyZxdW90O2Rpc3BsYXk6aW5saW5lLWZsZXg7ZmxleC1kaXJlY3Rpb246Y29sdW1uO2FsaWduLWl0ZW1zOmNlbnRlcjtnYXA6NHB4JiM5MjsmcXVvdDsmIzkyO3UwMDNlJiM5Mjt1MDAzY21hcmltby11aS1lbGVtZW50IG9iamVjdC1pZD0mI3gyNzt2YmxBLTAmI3gyNzsgcmFuZG9tLWlkPSYjeDI3O2I0ZDgxYjZmLTcyZDAtNzViNi0wOTY0LTQ2MTUyNDhkOTIxYiYjeDI3OyYjOTI7dTAwM2UmIzkyO3UwMDNjbWFyaW1vLWFueXdpZGdldCBkYXRhLWluaXRpYWwtdmFsdWU9JiN4Mjc7eyZhbXA7cXVvdDttb2RlbF9pZCZhbXA7cXVvdDs6JmFtcDtxdW90OzA3NjZlNDU5ZmUzYzRjNmM4N2QzMDk0NGE1ZTgwYzZmJmFtcDtxdW90O30mI3gyNzsgZGF0YS1sYWJlbD0mI3gyNztudWxsJiN4Mjc7IGRhdGEtbW9kZWwtaWQ9JiN4Mjc7JmFtcDtxdW90OzA3NjZlNDU5ZmUzYzRjNmM4N2QzMDk0NGE1ZTgwYzZmJmFtcDtxdW90OyYjeDI3OyYjOTI7dTAwM2UmIzkyO3UwMDNjL21hcmltby1hbnl3aWRnZXQmIzkyO3UwMDNlJiM5Mjt1MDAzYy9tYXJpbW8tdWktZWxlbWVudCYjOTI7dTAwM2UmIzkyO3UwMDNjc3BhbiBzdHlsZT0mIzkyOyZxdW90O2ZvbnQ6MTFweCBtb25vc3BhY2U7Y29sb3I6Izg4OCYjOTI7JnF1b3Q7JiM5Mjt1MDAzZXBhaW50JiM5Mjt1MDAzYy9zcGFuJiM5Mjt1MDAzZSYjOTI7dTAwM2MvZGl2JiM5Mjt1MDAzZSYjOTI7dTAwM2Mvc3BhbiYjOTI7dTAwM2UmIzkyO3UwMDNjc3BhbiBjbGFzcz0mIzkyOyZxdW90O21hcmtkb3duIHByb3NlIGRhcms6cHJvc2UtaW52ZXJ0IGNvbnRlbn
1194RzJiM5MjsmcXVvdDsmIzkyO3UwMDNlJiM5Mjt1MDAzY2RpdiBkYXRhLXdkYWctbm9kZT0mIzkyOyZxdW90O2FuZ2xlJiM5MjsmcXVvdDsgc3R5bGU9JiM5MjsmcXVvdDtkaXNwbGF5OmlubGluZS1mbGV4O2ZsZXgtZGlyZWN0aW9uOmNvbHVtbjthbGlnbi1pdGVtczpjZW50ZXI7Z2FwOjRweCYjOTI7JnF1b3Q7JiM5Mjt1MDAzZSYjOTI7dTAwM2NtYXJpbW8tdWktZWxlbWVudCBvYmplY3QtaWQ9JiN4Mjc7UEtyaS0wJiN4Mjc7IHJhbmRvbS1pZD0mI3gyNzsyNWVmNTU5Mi0xMTBmLTlhZmQtNGYwMC1iNmI3YzMyMDZiNDAmI3gyNzsmIzkyO3UwMDNlJiM5Mjt1MDAzY21hcmltby1zbGlkZXIgZGF0YS1pbml0aWFsLXZhbHVlPSYjeDI3OzAmI3gyNzsgZGF0YS1sYWJlbD0mI3gyNzsmYW1wO3F1b3Q7JmFtcDsjOTI7dTAwM2NzcGFuIGNsYXNzPSZhbXA7IzkyOyZhbXA7cXVvdDttYXJrZG93biBwcm9zZSBkYXJrOnByb3NlLWludmVydCBjb250ZW50cyZhbXA7IzkyOyZhbXA7cXVvdDsmYW1wOyM5Mjt1MDAzZSZhbXA7IzkyO3UwMDNjc3BhbiBjbGFzcz0mYW1wOyM5MjsmYW1wO3F1b3Q7cGFyYWdyYXBoJmFtcDsjOTI7JmFtcDtxdW90OyZhbXA7IzkyO3UwMDNlcm90YXRlwrAmYW1wOyM5Mjt1MDAzYy9zcGFuJmFtcDsjOTI7dTAwM2UmYW1wOyM5Mjt1MDAzYy9zcGFuJmFtcDsjOTI7dTAwM2UmYW1wO3F1b3Q7JiN4Mjc7IGRhdGEtc3RhcnQ9JiN4Mjc7LTE4MCYjeDI3OyBkYXRhLXN0b3A9JiN4Mjc7MTgwJiN4Mjc7IGRhdGEtc3RlcD0mI3gyNzs1JiN4Mjc7IGRhdGEtc3RlcHM9JiN4Mjc7W10mI3gyNzsgZGF0YS1kZWJvdW5jZT0mI3gyNztmYWxzZSYjeDI3OyBkYXRhLWRpc2FibGVkPSYjeDI3O2ZhbHNlJiN4Mjc7IGRhdGEtb3JpZW50YXRpb249JiN4Mjc7JmFtcDtxdW90O2hvcml6b250YWwmYW1wO3F1b3Q7JiN4Mjc7IGRhdGEtc2hvdy12YWx1ZT0mI3gyNzt0cnVlJiN4Mjc7IGRhdGEtaW5jbHVkZS1pbnB1dD0mI3gyNztmYWxzZSYjeDI3OyBkYXRhLWZ1bGwtd2lkdGg9JiN4Mjc7ZmFsc2UmI3gyNzsmIzkyO3UwMDNlJiM5Mjt1MDAzYy9tYXJpbW8tc2xpZGVyJiM5Mjt1MDAzZSYjOTI7dTAwM2MvbWFyaW1vLXVpLWVsZW1lbnQmIzkyO3UwMDNlJiM5Mjt1MDAzY3NwYW4gc3R5bGU9JiM5MjsmcXVvdDtmb250OjExcHggbW9ub3NwYWNlO2NvbG9yOiM4ODgmIzkyOyZxdW90OyYjOTI7dTAwM2VhbmdsZSYjOTI7dTAwM2Mvc3BhbiYjOTI7dTAwM2UmIzkyO3UwMDNjL2RpdiYjOTI7dTAwM2UmIzkyO3UwMDNjL3NwYW4mIzkyO3UwMDNlJiM5Mjt1MDAzY3NwYW4gY2xhc3M9JiM5MjsmcXVvdDttYXJrZG93biBwcm9zZSBkYXJrOnByb3NlLWludmVydCBjb250ZW50cyYjOTI7JnF1b3Q7JiM5Mjt1MDAzZSYjOTI7dTAwM2NkaXYgZGF0YS13ZGFnLW5vZGU9JiM5MjsmcXVvdDtrZXJuZWwmIzkyOyZxdW90OyBzdHlsZT0mIzkyOyZxdW90O2Rpc3BsYXk6aW5saW5lLWZsZXg7ZmxleC1kaXJlY3Rpb246Y29sdW1uO2FsaWduLWl0ZW1zOmNlbnRlcjtnYXA6NHB4JiM5MjsmcXVvdDsmIzkyO3UwMDNlJiM5Mjt1MDAzY21hcmltby11aS1lbGVtZW50IG9iamVjdC1pZD0mI3gyNztia0hDLTAmI3gyNzsgcmFuZG9tLWlkPSYjeDI3OzJmMGVkMGI1LWFmZWQtNDI3NS1kZDUwLWM5YTRmYzdjMDdhNSYjeDI3OyYjOTI7dTAwM2UmIzkyO3UwMDNjbWFyaW1vLW1hdHJpeCBkYXRhLWluaXRpYWwtdmFsdWU9JiN4Mjc7W1swLC0xLDBdLFstMSw1LC0xXSxbMCwtMSwwXV0mI3gyNzsgZGF0YS1sYWJlbD0mI3gyNztudWxsJiN4Mjc7IGRhdGEtc3RlcD0mI3gyNztbWzEuMCwxLjAsMS4wXSxbMS4wLDEuMCwxLjBdLFsxLjAsMS4wLDEuMF1dJiN4Mjc7IGRhdGEtcHJlY2lzaW9uPSYjeDI3OzAmI3gyNzsgZGF0YS1zeW1tZXRyaWM9JiN4Mjc7ZmFsc2UmI3gyNzsgZGF0YS1kZWJvdW5jZT0mI3gyNztmYWxzZSYjeDI3OyBkYXRhLXNjaWVudGlmaWM9JiN4Mjc7ZmFsc2UmI3gyNzsgZGF0YS1kaXNhYmxlZD0mI3gyNztbW2ZhbHNlLGZhbHNlLGZhbHNlXSxbZmFsc2UsZmFsc2UsZmFsc2VdLFtmYWxzZSxmYWxzZSxmYWxzZV1dJiN4Mjc7JiM5Mjt1MDAzZSYjOTI7dTAwM2MvbWFyaW1vLW1hdHJpeCYjOTI7dTAwM2UmIzkyO3UwMDNjL21hcmltby11aS1lbGVtZW50JiM5Mjt1MDAzZSYjOTI7dTAwM2NzcGFuIHN0eWxlPSYjOTI7JnF1b3Q7Zm9udDoxMXB4IG1vbm9zcGFjZTtjb2xvcjojODg4JiM5MjsmcXVvdDsmIzkyO3UwMDNla2VybmVsJiM5Mjt1MDAzYy9zcGFuJiM5Mjt1MDAzZSYjOTI7dTAwM2MvZGl2JiM5Mjt1MDAzZSYjOTI7dTAwM2Mvc3BhbiYjOTI7dTAwM2UmIzkyO3UwMDNjL2RpdiYjOTI7dTAwM2UmIzkyO3UwMDNjZGl2IHN0eWxlPSYjeDI3O2Rpc3BsYXk6IGZsZXg7ZmxleDogMTtmbGV4LWRpcmVjdGlvbjogY29sdW1uO2p1c3RpZnktY29udGVudDogZmxleC1zdGFydDthbGlnbi1pdGVtczogY2VudGVyO2ZsZXgtd3JhcDogbm93cmFwO2dhcDogMS41cmVtJiN4Mjc7JiM5Mjt1MDAzZSYjOTI7dTAwM2NzcGFuIGNsYXNzPSYjOTI7JnF1b3Q7bWFya2Rvd24gcHJvc2UgZGFyazpwcm9zZS1pbnZlcnQgY29udGVudHMmIzkyOyZxdW90OyYjOTI7dTAwM2UmIzkyO3UwMDNjZGl2IGRhdGEtd2RhZy1ub2RlPSYjOTI7JnF1b3Q7Y29udiYjOTI7JnF1b3Q7IHN0eWxlPSYjOTI7JnF1b3Q7ZGlzcGxheTppbmxpbmUtZmxleDtmbGV4LWRpcmVjdGlvbjpjb2x1bW47YWxpZ24taXRlbXM6Y2VudGVyO2dhcDo0cHgmIzkyOyZxdW90OyYjOTI7dTAwM2UmIzkyO3UwMDNjaW1nIHNyYz0mIzkyOyZxdW90O2RhdGE6aW1hZ2UvcG5nO2Jhc2U2NCxpVkJPUncwS0dnb0FBQUFOU1VoRVVnQUFBQm9BQUFBYUNBQUFBQUNNSVJNU0FBQUFEMGxFUVZSNG5HTmdHQVdqWUtnQ0FBSytBQUYzK0Z6bEFBQUFBRWxGVGtTdVFtQ0MmIzkyOyZxdW90OyBzdHlsZT0mIzkyOyZxdW90O3dpZHRoOjE4MHB4O2ltYWdlLXJlbmRlcmluZzpwaXhlbGF0ZWQ7Ym9yZGVyOjFweCBzb2xpZCAjY2NjJiM5MjsmcXVvdDsvJiM5Mjt1MDAzZSYjOTI7dTAwM2NzcGFuIHN0eWxlPSYjOTI7JnF1b3Q7Zm9udDoxMXB4IG1vbm9zcGFjZTtjb2xvcjojODg4JiM5MjsmcXVvdDsmIzkyO3UwMDNlY29udiYjOTI7dTAwM2Mvc3BhbiYjOTI7dTAwM2UmIzkyO3UwMDNjL2RpdiYjOTI7dTAwM2UmIzkyO3UwMDNjL3NwYW4mIzkyO3UwMDNlJiM5Mjt1MDAzY3NwYW4gY2xhc3M9JiM5MjsmcXVvdDttYXJrZG93biBwcm9zZSBkYXJrOnByb3NlLWludmVydCBjb250ZW50cyYjOTI7JnF1b3Q7JiM5Mjt1MDAzZSYjOTI7dTAwM2NkaXYgZGF0YS13ZGFnLW5vZGU9JiM5MjsmcXVvdDtrZXJuZWwyJiM5MjsmcXVvdDsgc3R5bGU9JiM5MjsmcXVvdDtkaXNwbGF5OmlubGluZS1mbGV4O2ZsZXgtZGlyZWN0aW9uOmNvbHVtbjthbGlnbi1pdGVtczpjZW50ZXI7Z2FwOjRweCYjOTI7JnF1b3Q7JiM5Mjt1MDAzZSYjOTI7dTAwM2NtYXJpbW8tdWktZWxlbWVudCBvYmplY3QtaWQ9JiN4Mjc7bEVRYS0wJiN4Mjc7IHJhbmRvbS1pZD0mI3gyNzs3NDA1ODRiMy05OTY5LWEwMDItNmVmMC1kZmNlOGI2NWQ4NDImI3gyNzsmIzkyO3UwMDNlJiM5Mjt1MDAzY21hcmltby1tYXRyaXggZGF0YS1pbml0aWFsLXZhbHVlPSYjeDI3O1tbMSwxLDFdLFsxLDEsMV0sWzEsMSwxXV0mI3gyNzsgZGF0YS1sYWJlbD0mI3gyNztudWxsJiN4Mjc7IGRhdGEtc3RlcD0mI3gyNztbWzEuMCwxLjAsMS4wXSxbMS4wLDEuMCwxLjBdLFsxLjAsMS4wLDEuMF1dJiN4Mjc7IGRhdGEtcHJlY2lzaW9uPSYjeDI3OzAmI3gyNzsgZGF0YS1zeW1tZXRyaWM9JiN4Mjc7ZmFsc2UmI3gyNzsgZGF0YS1kZWJvdW5jZT0mI3gyNztmYWxzZSYjeDI3OyBkYXRhLXNjaWVudGlmaWM9JiN4Mjc7ZmFsc2UmI3gyNzsgZGF0YS1kaXNhYmxlZD0mI3gyNztbW2ZhbHNlLGZhbHNlLGZhbHNlXSxbZmFsc2UsZmFsc2UsZmFsc2VdLFtmYWxzZSxmYWxzZSxmYWxzZV1dJiN4Mjc7JiM5Mjt1MDAzZSYjOTI7dTAwM2MvbWFyaW1vLW1hdHJpeCYjOTI7dTAwM2UmIzkyO3UwMDNjL21hcmltby11aS1lbGVtZW50JiM5Mjt1MDAzZSYjOTI7dTAwM2NzcGFuIHN0eWxlPSYjOTI7JnF1b3Q7Zm9udDoxMXB4IG1vbm9zcGFjZTtjb2xvcjojODg4JiM5MjsmcXVvdDsmIzkyO3UwMDNla2VybmVsMiYjOTI7dTAwM2Mvc3BhbiYjOTI7dTAwM2UmIzkyO3UwMDNjL2RpdiYjOTI7dTAwM2UmIzkyO3UwMDNjL3NwYW4mIzkyO3UwMDNlJiM5Mjt1MDAzYy9kaXYmIzkyO3UwMDNlJiM5Mjt1MDAzY2Rp
1194diBzdHlsZT0mI3gyNztkaXNwbGF5OiBmbGV4O2ZsZXg6IDE7ZmxleC1kaXJlY3Rpb246IGNvbHVtbjtqdXN0aWZ5LWNvbnRlbnQ6IGZsZXgtc3RhcnQ7YWxpZ24taXRlbXM6IGNlbnRlcjtmbGV4LXdyYXA6IG5vd3JhcDtnYXA6IDEuNXJlbSYjeDI3OyYjOTI7dTAwM2UmIzkyO3UwMDNjc3BhbiBjbGFzcz0mIzkyOyZxdW90O21hcmtkb3duIHByb3NlIGRhcms6cHJvc2UtaW52ZXJ0IGNvbnRlbnRzJiM5MjsmcXVvdDsmIzkyO3UwMDNlJiM5Mjt1MDAzY2RpdiBkYXRhLXdkYWctbm9kZT0mIzkyOyZxdW90O2NvbnYyJiM5MjsmcXVvdDsgc3R5bGU9JiM5MjsmcXVvdDtkaXNwbGF5OmlubGluZS1mbGV4O2ZsZXgtZGlyZWN0aW9uOmNvbHVtbjthbGlnbi1pdGVtczpjZW50ZXI7Z2FwOjRweCYjOTI7JnF1b3Q7JiM5Mjt1MDAzZSYjOTI7dTAwM2NpbWcgc3JjPSYjOTI7JnF1b3Q7ZGF0YTppbWFnZS9wbmc7YmFzZTY0LGlWQk9SdzBLR2dvQUFBQU5TVWhFVWdBQUFCZ0FBQUFZQ0FBQUFBREZIR0lrQUFBQUQwbEVRVlI0bkdOZ0dBV2pnUG9BQUFKWUFBR2d4WXdEQUFBQUFFbEZUa1N1UW1DQyYjOTI7JnF1b3Q7IHN0eWxlPSYjOTI7JnF1b3Q7d2lkdGg6MTgwcHg7aW1hZ2UtcmVuZGVyaW5nOnBpeGVsYXRlZDtib3JkZXI6MXB4IHNvbGlkICNjY2MmIzkyOyZxdW90Oy8mIzkyO3UwMDNlJiM5Mjt1MDAzY3NwYW4gc3R5bGU9JiM5MjsmcXVvdDtmb250OjExcHggbW9ub3NwYWNlO2NvbG9yOiM4ODgmIzkyOyZxdW90OyYjOTI7dTAwM2Vjb252MiYjOTI7dTAwM2Mvc3BhbiYjOTI7dTAwM2UmIzkyO3UwMDNjL2RpdiYjOTI7dTAwM2UmIzkyO3UwMDNjL3NwYW4mIzkyO3UwMDNlJiM5Mjt1MDAzYy9kaXYmIzkyO3UwMDNlJiM5Mjt1MDAzYy9kaXYmIzkyO3UwMDNlJiM5Mjt1MDAzY21hcmltby11aS1lbGVtZW50IG9iamVjdC1pZD0mI3gyNztNSlVlLTAmI3gyNzsgcmFuZG9tLWlkPSYjeDI3OzBhYjkxOWM3LWYzYWUtMGFlNC1lYThjLWJlZTdlN2I5NzIxNyYjeDI3OyYjOTI7dTAwM2UmIzkyO3UwMDNjbWFyaW1vLWFueXdpZGdldCBkYXRhLWluaXRpYWwtdmFsdWU9JiN4Mjc7eyZhbXA7cXVvdDttb2RlbF9pZCZhbXA7cXVvdDs6JmFtcDtxdW90OzMxMmNiNDZmNzNmZDQxMGQ5MjMyMmNkZDhlNWI2N2I1JmFtcDtxdW90O30mI3gyNzsgZGF0YS1sYWJlbD0mI3gyNztudWxsJiN4Mjc7IGRhdGEtbW9kZWwtaWQ9JiN4Mjc7JmFtcDtxdW90OzMxMmNiNDZmNzNmZDQxMGQ5MjMyMmNkZDhlNWI2N2I1JmFtcDtxdW90OyYjeDI3OyYjOTI7dTAwM2UmIzkyO3UwMDNjL21hcmltby1hbnl3aWRnZXQmIzkyO3UwMDNlJiM5Mjt1MDAzYy9tYXJpbW8tdWktZWxlbWVudCYjOTI7dTAwM2UmIzkyO3UwMDNjL2RpdiYjOTI7dTAwM2UmIzkyO3UwMDNjL3NwYW4mIzkyO3UwMDNlJnF1b3Q7Jz48L21hcmltby1taW1lLXJlbmRlcmVyPlxuICAgIDwvbWFyaW1vLWNlbGwtb3V0cHV0PlxuICAgIDxtYXJpbW8tY2VsbC1jb2RlIGhpZGRlbj5XaWRnZXREQUcuZnJvbV93aWRnZXRzKCU1QnBhaW50JTJDJTIwYW5nbGUlMkMlMjBrZXJuZWwlMkMlMjBjb252JTJDJTIwa2VybmVsMiUyQyUyMGNvbnYyJTVEKTwvbWFyaW1vLWNlbGwtY29kZT5cbjwvbWFyaW1vLWlzbGFuZD4iLCJpbmRleCI6MSwib3B0aW9ucyI6eyJsYW5ndWFnZSI6InB5dGhvbiIsInJlbmRlciI6eyJzb3VyY2UiOmZhbHNlLCJvdXRwdXQiOnRydWUsImluY2x1ZGUiOnRydWUsImVkaXRvciI6ZmFsc2UsImVycm9yIjp0cnVlLCJzZXJ2ZXJPdXRwdXQiOnRydWV9LCJleGVjdXRpb24iOnsiZW5hYmxlZCI6dHJ1ZX0sIm1hcmltbyI6eyJkaXNhYmxlZCI6ZmFsc2UsInVucGFyc2FibGUiOmZhbHNlfX19fQ"></marimo-mdx-island>
1195<p>It takes a few seconds to boot the WASM but once loaded you'll see a <a href="https://molab.marimo.io/github/koaning/wigglystuff/blob/main/demos/widget_dag.py/wasm?utm_source=wigglystuff">graph widget</a> that can embed the variables and will automatically figure out the order in which they run.
1196If you use it, it is similar to a <code>mo.hstack</code> call in marimo but it also tracks the
1197order in which variables are defined. This let's us leverage marimo's internal
1198tracking to figure out which variables need to exist before the other ones
1199to construct the graph.</p>
1200<p>It uses pyodide/wasm under the hood and there's even a way to get this running
1201on a static site generated with Python that does not natively use <code>.mdx</code> files.</p>
1202<marimo-mdx-island class="marimo-island-host" data-marimo-host="mdx" data-marimo-theme-mode="auto" data-marimo-app-id="marimo-72b1d94c11ad" data-marimo-cell-index="2" data-marimo-payload-encoding="base64url" data-marimo-payload="eyJwcm90b2NvbFZlcnNpb24iOjEsImFwcCI6eyJpZCI6Im1hcmltby03MmIxZDk0YzExYWQiLCJydW50aW1lQ2VsbENvdW50Ijo5LCJhc3NldHMiOnsibW9kdWxlU2NyaXB0cyI6WyJodHRwczovL2Nkbi5qc2RlbGl2ci5uZXQvbnBtL0BtYXJpbW8tdGVhbS9pc2xhbmRzQDAuMjMuMTUvZGlzdC9tYWluLmpzIl0sImxpbmtzIjpbeyJocmVmIjoiaHR0cHM6Ly9jZG4uanNkZWxpdnIubmV0L25wbS9AbWFyaW1vLXRlYW0vaXNsYW5kc0AwLjIzLjE1L2Rpc3Qvc3R5bGUuY3NzIiwicmVsIjoic3R5bGVzaGVldCIsInRpdGxlIjoibWFyaW1vLWlzbGFuZHMiLCJjcm9zc29yaWdpbiI6ImFub255bW91cyJ9LHsicmVsIjoicHJlY29ubmVjdCIsImhyZWYiOiJodHRwczovL2ZvbnRzLmdvb2dsZWFwaXMuY29tIn0seyJyZWwiOiJwcmVjb25uZWN0IiwiaHJlZiI6Imh0dHBzOi8vZm9udHMuZ3N0YXRpYy5jb20iLCJjcm9zc29yaWdpbiI6IiJ9LHsiaHJlZiI6Imh0dHBzOi8vZm9udHMuZ29vZ2xlYXBpcy5jb20vY3NzMj9mYW1pbHk9RmlyYStNb25vOndnaHRANDAwOzUwMDs3MDAmZmFtaWx5PUxvcmEmZmFtaWx5PVBUK1NhbnM6d2dodEA0MDA7NzAwJmRpc3BsYXk9c3dhcCIsInJlbCI6InN0eWxlc2hlZXQifSx7InJlbCI6InN0eWxlc2hlZXQiLCJocmVmIjoiaHR0cHM6Ly9jZG4uanNkZWxpdnIubmV0L25wbS9rYXRleEAwLjE2LjEwL2Rpc3Qva2F0ZXgubWluLmNzcyIsImludGVncml0eSI6InNoYTM4NC13Y0l4a2Y0azU1OEFqTTNZejNCQkZRVWJrL3pnSVlDMlIwUXBlZVliK1R3bEJWTXJsZ0xxd1JqUnRHWmlLN3d3IiwiY3Jvc3NvcmlnaW4iOiJhbm9ueW1vdXMifV0sImhlYWRUYWdzIjpbeyJ0YWciOiJtYXJpbW8tZmlsZW5hbWUiLCJhdHRycyI6eyJoaWRkZW4iOiIifSwidGV4dCI6ImRvY3VtZW50Lm1keCJ9XSwidmVyc2lvbiI6IjAuMjMuMTUifSwibm90ZWJvb2tDb2RlIjoiIyAvLy8gc2NyaXB0XG4jIHJlcXVpcmVzLXB5dGhvbiA9IFwiPj0zLjEyXCJcbiMgZGVwZW5kZW5jaWVzID0gW1xuIyAgICAgXCJtYXJpbW8-PTAuMjMuMTNcIixcbiMgICAgIFwibW9odG1sXCIsXG4jICAgICBcIm51bXB5XCIsXG4jICAgICBcInBpbGxvd1wiLFxuIyAgICAgXCJ3aWdnbHlzdHVmZj09MC41LjIxXCIsXG4jIF1cbiMgLy8vXG5pbXBvcnQgbWFyaW1vXG5cbl9fZ2VuZXJhdGVkX3dpdGggPSBcIjAuMjMuMTVcIlxuYXBwID0gbWFyaW1vLkFwcCgpXG5cblxuQGFwcC5jZWxsXG5kZWYgXygpOlxuICAgIGZyb20gcGF0aGxpYiBpbXBvcnQgUGF0aFxuICAgIGltcG9ydCBzeXNcblxuICAgIGltcG9ydCBtYXJpbW8gYXMgbW9cblxuICAgICMgUHJlZmVyIHRoZSBsb2NhbCBjaGVja291dCBzbyB0aGlzIGRlbW8gdHJhY2tzIHRoZSBpbi1yZXBvIFdpZGdldERBRyBldmVuXG4gICAgIyBiZWZvcmUgaXQgbGFuZHMgaW4gYSBwdWJsaXNoZWQgd2lnZ2x5c3R1ZmYgcmVsZWFzZS5cbiAgICByZXBvX3Jvb3QgPSBQYXRoKF9fZmlsZV9fKS5yZXNvbHZlKCkucGFyZW50c1sxXVxuICAgIGlmIChyZXBvX3Jvb3QgLyBcIndpZ2dseXN0dWZmXCIpLmV4aXN0cygpOlxuICAgICAgICBzeXMucGF0aC5pbnNlcnQoMCwgc3RyKHJlcG9fcm9vdCkpXG5cbiAgICBmcm9tIHdpZ2dseXN0dWZmIGltcG9ydCBQYWludCwgV2lkZ2
1202V0REFHXG5cbiAgICByZXR1cm4gUGFpbnQsIFdpZGdldERBRywgbW9cblxuXG5AYXBwLmNlbGxcbmRlZiBfKFdpZGdldERBRywgYW5nbGUsIGNvbnYsIGNvbnYyLCBrZXJuZWwsIGtlcm5lbDIsIHBhaW50KTpcbiAgICBXaWRnZXREQUcuZnJvbV93aWRnZXRzKFtwYWludCwgYW5nbGUsIGtlcm5lbCwgY29udiwga2VybmVsMiwgY29udjJdKVxuICAgIHJldHVyblxuXG5cbkBhcHAuY2VsbFxuZGVmIF8oUGFpbnQsIG1vKTpcbiAgICAjIFRoZSBsaXZlIGRyYXdpbmcgc3VyZmFjZSAtLSBpdCBJUyB0aGUgXCJwYWludFwiIG5vZGUgZW1iZWRkZWQgaW4gdGhlIERBRyBiZWxvdy5cbiAgICAjIHN0b3JlX2JhY2tncm91bmQ9RmFsc2Uga2VlcHMgdGhlIGNhbnZhcyB0cmFuc3BhcmVudCwgc28gdGhlIGFscGhhIGNoYW5uZWwgb2ZcbiAgICAjIHRoZSBleHBvcnRlZCBQTkcgaXMgYSBjbGVhbiBpbmsgbWFzayAob3BhcXVlIHdoZXJlIHlvdSBkcmV3LCBlbHNlIHRyYW5zcGFyZW50KS5cbiAgICBwYWludCA9IG1vLnVpLmFueXdpZGdldChQYWludCh3aWR0aD0yNjAsIGhlaWdodD0yNjAsIGNvbG9yX3BpY2tlcj1GYWxzZSwgc3RvcmVfYmFja2dyb3VuZD1GYWxzZSkpXG4gICAgcmV0dXJuIChwYWludCwpXG5cblxuQGFwcC5jZWxsXG5kZWYgXyhtbyk6XG4gICAgIyBGaXJzdCBjb252b2x1dGlvbiBrZXJuZWwgLS0gYSBuYXRpdmUgbW8udWkubWF0cml4IGVkaXRvciAoZHJhZyBhbiBlbnRyeSB0b1xuICAgICMgY2hhbmdlIGl0KS4gSXQgbGl2ZXMgaW4gaXRzIG93biBjZWxsIHNvIHRoZSBEQUcgY2FuIHdpcmUgaXQgdG8gYGNvbnZgIG9ubHkuXG4gICAga2VybmVsID0gbW8udWkubWF0cml4KFtbMCwgLTEsIDBdLCBbLTEsIDUsIC0xXSwgWzAsIC0xLCAwXV0pXG4gICAgcmV0dXJuIChrZXJuZWwsKVxuXG5cbkBhcHAuY2VsbFxuZGVmIF8obW8pOlxuICAgICMgU2Vjb25kIGtlcm5lbCwgaW4gaXRzIG93biBjZWxsIHRvbyAtLSBpdCBmZWVkcyBgY29udjJgLlxuICAgIGtlcm5lbDIgPSBtby51aS5tYXRyaXgoW1sxLCAxLCAxXSwgWzEsIDEsIDFdLCBbMSwgMSwgMV1dKVxuICAgIHJldHVybiAoa2VybmVsMiwpXG5cblxuQGFwcC5jZWxsXG5kZWYgXyhtbyk6XG4gICAgIyBBIHBsYWluIG1vLnVpLnNsaWRlciAtLSBpdCBsaXZlcyBpbiB0aGUgREFHIGFzIGEgbm9kZSBqdXN0IGxpa2UgdGhlIHdpZGdldHNcbiAgICAjIGFib3ZlLCBhbmQgcm90YXRlcyB0aGUgZHJhd24gZGlnaXQgYmVmb3JlIGl0IGlzIGNvbnZvbHZlZC5cbiAgICBhbmdsZSA9IG1vLnVpLnNsaWRlcigtMTgwLCAxODAsIHZhbHVlPTAsIHN0ZXA9NSwgbGFiZWw9XCJyb3RhdGXCsFwiLCBzaG93X3ZhbHVlPVRydWUpXG4gICAgcmV0dXJuIChhbmdsZSwpXG5cblxuQGFwcC5jZWxsXG5kZWYgXygpOlxuICAgICMgU2hhcmVkIGhlbHBlcnMgLS0gZXhwb3J0ZWQgKG5vIGxlYWRpbmcgdW5kZXJzY29yZSkgc28gdGhlIHR3byBjb252b2x1dGlvblxuICAgICMgY2VsbHMgYmVsb3cgY2FuIGJvdGggdXNlIHRoZW0uXG4gICAgaW1wb3J0IGJhc2U2NFxuICAgIGltcG9ydCBpb1xuXG4gICAgaW1wb3J0IG51bXB5IGFzIG5wXG4gICAgZnJvbSBtb2h0bWwgaW1wb3J0IGltZ1xuICAgIGZyb20gUElMIGltcG9ydCBJbWFnZVxuXG4gICAgZGVmIHRvX2ltZyhhLCB3aWR0aD0xODApOlxuICAgICAgICBhID0gbnAuYXNhcnJheShhLCBkdHlwZT1mbG9hdClcbiAgICAgICAgbG8sIGhpID0gZmxvYXQoYS5taW4oKSksIGZsb2F0KGEubWF4KCkpXG4gICAgICAgIHU4ID0gKChhIC0gbG8pIC8gKGhpIC0gbG8pICogMjU1IGlmIGhpID4gbG8gZWxzZSBucC56ZXJvc19saWtlKGEpKS5hc3R5cGUoXCJ1aW50OFwiKVxuICAgICAgICBidWYgPSBpby5CeXRlc0lPKClcbiAgICAgICAgSW1hZ2UuZnJvbWFycmF5KHU4KS5zYXZlKGJ1ZiwgZm9ybWF0PVwiUE5HXCIpXG4gICAgICAgIHNyYyA9IFwiZGF0YTppbWFnZS9wbmc7YmFzZTY0LFwiICsgYmFzZTY0LmI2NGVuY29kZShidWYuZ2V0dmFsdWUoKSkuZGVjb2RlKClcbiAgICAgICAgcmV0dXJuIGltZyhcbiAgICAgICAgICAgIHNyYz1zcmMsXG4gICAgICAgICAgICBzdHlsZT1mXCJ3aWR0aDp7d2lkdGh9cHg7aW1hZ2UtcmVuZGVyaW5nOnBpeGVsYXRlZDtib3JkZXI6MXB4IHNvbGlkICNjY2NcIixcbiAgICAgICAgKVxuXG4gICAgZGVmIGNvbnZvbHZlKGEsIGspOlxuICAgICAgICBmcm9tIG51bXB5LmxpYi5zdHJpZGVfdHJpY2tzIGltcG9ydCBzbGlkaW5nX3dpbmRvd192aWV3XG5cbiAgICAgICAga2gsIGt3ID0gay5zaGFwZVxuICAgICAgICByZXR1cm4gbnAuZWluc3VtKFwiaWprbCxrbC0-aWpcIiwgc2xpZGluZ193aW5kb3dfdmlldyhhLCAoa2gsIGt3KSksIGspXG5cbiAgICByZXR1cm4gY29udm9sdmUsIG5wLCB0b19pbWdcblxuXG5AYXBwLmNlbGxcbmRlZiBfKGFuZ2xlLCBjb252b2x2ZSwga2VybmVsLCBucCwgcGFpbnQsIHRvX2ltZyk6XG4gICAgIyBUaGUgZHJhd2luZyAtPiByb3RhdGUgYnkgdGhlIHNsaWRlciAtPiBhIDI4eDI4IGFycmF5IC0-IGNvbnZvbHZlIHdpdGggYGtlcm5lbGAuXG4gICAgIyBUaGUgYWxwaGEgY2hhbm5lbCBpcyB0aGUgaW5rIG1hc2sgKGJyaWdodCB3aGVyZSB5b3UgZHJldyk7IHJvdGF0aW5nIHRoZVxuICAgICMgZnVsbC1yZXMgUElMICh0cmFuc3BhcmVudCBmaWxsKSBrZWVwcyB0aGF0IG1hc2sgY2xlYW4uIGBjb252YCBpcyB0aGUgaW1hZ2VcbiAgICAjIG5vZGU7IGBvdXQxYCBpcyB0aGUgYXJyYXkgdGhlIG5leHQgY2VsbCBwaWNrcyB1cCAodGhhdCByZWZlcmVuY2UgaXMgdGhlIGVkZ2VcbiAgICAjIGBjb252IC0-IGNvbnYyYCB0aGUgREFHIGRyYXdzKS5cbiAgICBfcGlsID0gcGFpbnQuZ2V0X3BpbCgpLmNvbnZlcnQoXCJSR0JBXCIpLnJvdGF0ZShhbmdsZS52YWx1ZSlcbiAgICBfYXJyID0gbnAuYXJyYXkoX3BpbC5yZXNpemUoKDI4LCAyOCkpKVsuLi4sIDNdLmFzdHlwZShmbG9hdClcbiAgICBvdXQxID0gY29udm9sdmUoX2FyciwgbnAuYXNhcnJheShrZXJuZWwudmFsdWUsIGR0eXBlPWZsb2F0KSlcbiAgICBjb252ID0gdG9faW1nKG91dDEpXG4gICAgcmV0dXJuIGNvbnYsIG91dDFcblxuXG5AYXBwLmNlbGxcbmRlZiBfKGNvbnZvbHZlLCBrZXJuZWwyLCBucCwgb3V0MSwgdG9faW1nKTpcbiAgICAjIFNlY29uZCBjb252b2x1dGlvbj
1202ogYG91dDFgIChmcm9tIHRoZSBjZWxsIGFib3ZlKSBibHVycmVkIGJ5IGBrZXJuZWwyYC5cbiAgICBvdXQyID0gY29udm9sdmUob3V0MSwgbnAuYXNhcnJheShrZXJuZWwyLnZhbHVlLCBkdHlwZT1mbG9hdCkpXG4gICAgY29udjIgPSB0b19pbWcob3V0MilcbiAgICByZXR1cm4gKGNvbnYyLClcblxuXG5pZiBfX25hbWVfXyA9PSBcIl9fbWFpbl9fXCI6XG4gICAgYXBwLnJ1bigpIn0sImNlbGwiOnsiaHRtbCI6IjxtYXJpbW8taXNsYW5kXG4gICAgZGF0YS1hcHAtaWQ9XCJtYXJpbW8tNzJiMWQ5NGMxMWFkXCJcbiAgICBkYXRhLWNlbGwtaWQ9XCJ2YmxBXCJcbiAgICBkYXRhLXJlYWN0aXZlPVwidHJ1ZVwiXG4-XG4gICAgPG1hcmltby1jZWxsLW91dHB1dD5cbiAgICA8c3Bhbj48L3NwYW4-XG4gICAgPC9tYXJpbW8tY2VsbC1vdXRwdXQ-XG4gICAgPG1hcmltby1jZWxsLWNvZGUgaGlkZGVuPiUyMyUyMFRoZSUyMGxpdmUlMjBkcmF3aW5nJTIwc3VyZmFjZSUyMC0tJTIwaXQlMjBJUyUyMHRoZSUyMCUyMnBhaW50JTIyJTIwbm9kZSUyMGVtYmVkZGVkJTIwaW4lMjB0aGUlMjBEQUclMjBiZWxvdy4lMEElMjMlMjBzdG9yZV9iYWNrZ3JvdW5kJTNERmFsc2UlMjBrZWVwcyUyMHRoZSUyMGNhbnZhcyUyMHRyYW5zcGFyZW50JTJDJTIwc28lMjB0aGUlMjBhbHBoYSUyMGNoYW5uZWwlMjBvZiUwQSUyMyUyMHRoZSUyMGV4cG9ydGVkJTIwUE5HJTIwaXMlMjBhJTIwY2xlYW4lMjBpbmslMjBtYXNrJTIwKG9wYXF1ZSUyMHdoZXJlJTIweW91JTIwZHJldyUyQyUyMGVsc2UlMjB0cmFuc3BhcmVudCkuJTBBcGFpbnQlMjAlM0QlMjBtby51aS5hbnl3aWRnZXQoUGFpbnQod2lkdGglM0QyNjAlMkMlMjBoZWlnaHQlM0QyNjAlMkMlMjBjb2xvcl9waWNrZXIlM0RGYWxzZSUyQyUyMHN0b3JlX2JhY2tncm91bmQlM0RGYWxzZSkpPC9tYXJpbW8tY2VsbC1jb2RlPlxuPC9tYXJpbW8taXNsYW5kPiIsImluZGV4IjoyLCJvcHRpb25zIjp7Imxhbmd1YWdlIjoicHl0aG9uIiwicmVuZGVyIjp7InNvdXJjZSI6ZmFsc2UsIm91dHB1dCI6dHJ1ZSwiaW5jbHVkZSI6dHJ1ZSwiZWRpdG9yIjpmYWxzZSwiZXJyb3IiOnRydWUsInNlcnZlck91dHB1dCI6dHJ1ZX0sImV4ZWN1dGlvbiI6eyJlbmFibGVkIjp0cnVlfSwibWFyaW1vIjp7ImRpc2FibGVkIjpmYWxzZSwidW5wYXJzYWJsZSI6ZmFsc2V9fX19"></marimo-mdx-island>
1203<marimo-mdx-island class="marimo-island-host" data-marimo-host="mdx" data-marimo-theme-mode="auto" data-marimo-app-id="marimo-72b1d94c11ad" data-marimo-cell-index="3" data-marimo-payload-encoding="base64url" data-marimo-payload="eyJwcm90b2NvbFZlcnNpb24iOjEsImFwcCI6eyJpZCI6Im1hcmltby03MmIxZDk0YzExYWQiLCJydW50aW1lQ2VsbENvdW50Ijo5LCJhc3NldHMiOnsibW9kdWxlU2NyaXB0cyI6WyJodHRwczovL2Nkbi5qc2RlbGl2ci5uZXQvbnBtL0BtYXJpbW8tdGVhbS9pc2xhbmRzQDAuMjMuMTUvZGlzdC9tYWluLmpzIl0sImxpbmtzIjpbeyJocmVmIjoiaHR0cHM6Ly9jZG4uanNkZWxpdnIubmV0L25wbS9AbWFyaW1vLXRlYW0vaXNsYW5kc0AwLjIzLjE1L2Rpc3Qvc3R5bGUuY3NzIiwicmVsIjoic3R5bGVzaGVldCIsInRpdGxlIjoibWFyaW1vLWlzbGFuZHMiLCJjcm9zc29yaWdpbiI6ImFub255bW91cyJ9LHsicmVsIjoicHJlY29ubmVjdCIsImhyZWYiOiJodHRwczovL2ZvbnRzLmdvb2dsZWFwaXMuY29tIn0seyJyZWwiOiJwcmVjb25uZWN0IiwiaHJlZiI6Imh0dHBzOi8vZm9udHMuZ3N0YXRpYy5jb20iLCJjcm9zc29yaWdpbiI6IiJ9LHsiaHJlZiI6Imh0dHBzOi8vZm9udHMuZ29vZ2xlYXBpcy5jb20vY3NzMj9mYW1pbHk9RmlyYStNb25vOndnaHRANDAwOzUwMDs3MDAmZmFtaWx5PUxvcmEmZmFtaWx5PVBUK1NhbnM6d2dodEA0MDA7NzAwJmRpc3BsYXk9c3dhcCIsInJlbCI6InN0eWxlc2hlZXQifSx7InJlbCI6InN0eWxlc2hlZXQiLCJocmVmIjoiaHR0cHM6Ly9jZG4uanNkZWxpdnIubmV0L25wbS9rYXRleEAwLjE2LjEwL2Rpc3Qva2F0ZXgubWluLmNzcyIsImludGVncml0eSI6InNoYTM4NC13Y0l4a2Y0azU1OEFqTTNZejNCQkZRVWJrL3pnSVlDMlIwUXBlZVliK1R3bEJWTXJsZ0xxd1JqUnRHWmlLN3d3IiwiY3Jvc3NvcmlnaW4iOiJhbm9ueW1vdXMifV0sImhlYWRUYWdzIjpbeyJ0YWciOiJtYXJpbW8tZmlsZW5hbWUiLCJhdHRycyI6eyJoaWRkZW4iOiIifSwidGV4dCI6ImRvY3VtZW50Lm1keCJ9XSwidmVyc2lvbiI6IjAuMjMuMTUifSwibm90ZWJvb2tDb2RlIjoiIyAvLy8gc2NyaXB0XG4jIHJlcXVpcmVzLXB5dGhvbiA9IFwiPj0zLjEyXCJcbiMgZGVwZW5kZW5jaWVzID0gW1xuIyAgICAgXCJtYXJpbW8-PTAuMjMuMTNcIixcbiMgICAgIFwibW9odG1sXCIsXG4jICAgICBcIm51bXB5XCIsXG4jICAgICBcInBpbGxvd1wiLFxuIyAgICAgXCJ3aWdnbHlzdHVmZj09MC41LjIxXCIsXG4jIF1cbiMgLy8vXG5pbXBvcnQgbWFyaW1vXG5cbl9fZ2VuZXJhdGVkX3dpdGggPSBcIjAuMjMuMTVcIlxuYXBwID0gbWFyaW1vLkFwcCgpXG5cblxuQGFwcC5jZWxsXG5kZWYgXygpOlxuICAgIGZyb20gcGF0aGxpYiBpbXBvcnQgUGF0aFxuICAgIGltcG9ydCBzeXNcblxuICAgIGltcG9ydCBtYXJpbW8gYXMgbW9cblxuICAgICMgUHJlZmVyIHRoZSBsb2NhbCBjaGVja291dCBzbyB0aGlzIGRlbW8gdHJhY2tzIHRoZSBpbi1yZXBvIFdpZGdldERBRyBldmVuXG4gICAgIyBiZWZvcmUgaXQgbGFuZHMgaW4gYSBwdWJsaXNoZWQgd2lnZ2x5c3R1ZmYgcmVsZWFzZS5cbiAgICByZXBvX3Jvb3QgPSBQYXRoKF9fZmlsZV9fKS5yZXNvbHZlKCkucGFyZW50c1sxXVxuICAgIGlmIChyZXBvX3Jvb3QgLyBcIndpZ2dseXN0dWZmXCIpLmV4aXN0cygpOlxuICAgICAgICBzeXMucGF0aC5pbnNlcnQoMCwgc3RyKHJlcG9fcm9vdCkpXG5cbiAgICBmcm9tIHdpZ2dseXN0dWZmIGltcG9ydCBQYWludCwgV2lkZ2V0REFHXG5cbiAgICByZXR1cm4gUGFpbnQsIFdpZGdldERBRywgbW9cblxuXG5AYXBwLmNlbGxcbmRlZiBfKFdpZGdldERBRywgYW5nbG
1203UsIGNvbnYsIGNvbnYyLCBrZXJuZWwsIGtlcm5lbDIsIHBhaW50KTpcbiAgICBXaWRnZXREQUcuZnJvbV93aWRnZXRzKFtwYWludCwgYW5nbGUsIGtlcm5lbCwgY29udiwga2VybmVsMiwgY29udjJdKVxuICAgIHJldHVyblxuXG5cbkBhcHAuY2VsbFxuZGVmIF8oUGFpbnQsIG1vKTpcbiAgICAjIFRoZSBsaXZlIGRyYXdpbmcgc3VyZmFjZSAtLSBpdCBJUyB0aGUgXCJwYWludFwiIG5vZGUgZW1iZWRkZWQgaW4gdGhlIERBRyBiZWxvdy5cbiAgICAjIHN0b3JlX2JhY2tncm91bmQ9RmFsc2Uga2VlcHMgdGhlIGNhbnZhcyB0cmFuc3BhcmVudCwgc28gdGhlIGFscGhhIGNoYW5uZWwgb2ZcbiAgICAjIHRoZSBleHBvcnRlZCBQTkcgaXMgYSBjbGVhbiBpbmsgbWFzayAob3BhcXVlIHdoZXJlIHlvdSBkcmV3LCBlbHNlIHRyYW5zcGFyZW50KS5cbiAgICBwYWludCA9IG1vLnVpLmFueXdpZGdldChQYWludCh3aWR0aD0yNjAsIGhlaWdodD0yNjAsIGNvbG9yX3BpY2tlcj1GYWxzZSwgc3RvcmVfYmFja2dyb3VuZD1GYWxzZSkpXG4gICAgcmV0dXJuIChwYWludCwpXG5cblxuQGFwcC5jZWxsXG5kZWYgXyhtbyk6XG4gICAgIyBGaXJzdCBjb252b2x1dGlvbiBrZXJuZWwgLS0gYSBuYXRpdmUgbW8udWkubWF0cml4IGVkaXRvciAoZHJhZyBhbiBlbnRyeSB0b1xuICAgICMgY2hhbmdlIGl0KS4gSXQgbGl2ZXMgaW4gaXRzIG93biBjZWxsIHNvIHRoZSBEQUcgY2FuIHdpcmUgaXQgdG8gYGNvbnZgIG9ubHkuXG4gICAga2VybmVsID0gbW8udWkubWF0cml4KFtbMCwgLTEsIDBdLCBbLTEsIDUsIC0xXSwgWzAsIC0xLCAwXV0pXG4gICAgcmV0dXJuIChrZXJuZWwsKVxuXG5cbkBhcHAuY2VsbFxuZGVmIF8obW8pOlxuICAgICMgU2Vjb25kIGtlcm5lbCwgaW4gaXRzIG93biBjZWxsIHRvbyAtLSBpdCBmZWVkcyBgY29udjJgLlxuICAgIGtlcm5lbDIgPSBtby51aS5tYXRyaXgoW1sxLCAxLCAxXSwgWzEsIDEsIDFdLCBbMSwgMSwgMV1dKVxuICAgIHJldHVybiAoa2VybmVsMiwpXG5cblxuQGFwcC5jZWxsXG5kZWYgXyhtbyk6XG4gICAgIyBBIHBsYWluIG1vLnVpLnNsaWRlciAtLSBpdCBsaXZlcyBpbiB0aGUgREFHIGFzIGEgbm9kZSBqdXN0IGxpa2UgdGhlIHdpZGdldHNcbiAgICAjIGFib3ZlLCBhbmQgcm90YXRlcyB0aGUgZHJhd24gZGlnaXQgYmVmb3JlIGl0IGlzIGNvbnZvbHZlZC5cbiAgICBhbmdsZSA9IG1vLnVpLnNsaWRlcigtMTgwLCAxODAsIHZhbHVlPTAsIHN0ZXA9NSwgbGFiZWw9XCJyb3RhdGXCsFwiLCBzaG93X3ZhbHVlPVRydWUpXG4gICAgcmV0dXJuIChhbmdsZSwpXG5cblxuQGFwcC5jZWxsXG5kZWYgXygpOlxuICAgICMgU2hhcmVkIGhlbHBlcnMgLS0gZXhwb3J0ZWQgKG5vIGxlYWRpbmcgdW5kZXJzY29yZSkgc28gdGhlIHR3byBjb252b2x1dGlvblxuICAgICMgY2VsbHMgYmVsb3cgY2FuIGJvdGggdXNlIHRoZW0uXG4gICAgaW1wb3J0IGJhc2U2NFxuICAgIGltcG9ydCBpb1xuXG4gICAgaW1wb3J0IG51bXB5IGFzIG5wXG4gICAgZnJvbSBtb2h0bWwgaW1wb3J0IGltZ1xuICAgIGZyb20gUElMIGltcG9ydCBJbWFnZVxuXG4gICAgZGVmIHRvX2ltZyhhLCB3aWR0aD0xODApOlxuICAgICAgICBhID0gbnAuYXNhcnJheShhLCBkdHlwZT1mbG9hdClcbiAgICAgICAgbG8sIGhpID0gZmxvYXQoYS5taW4oKSksIGZsb2F0KGEubWF4KCkpXG4gICAgICAgIHU4ID0gKChhIC0gbG8pIC8gKGhpIC0gbG8pICogMjU1IGlmIGhpID4gbG8gZWxzZSBucC56ZXJvc19saWtlKGEpKS5hc3R5cGUoXCJ1aW50OFwiKVxuICAgICAgICBidWYgPSBpby5CeXRlc0lPKClcbiAgICAgICAgSW1hZ2UuZnJvbWFycmF5KHU4KS5zYXZlKGJ1ZiwgZm9ybWF0PVwiUE5HXCIpXG4gICAgICAgIHNyYyA9IFwiZGF0YTppbWFnZS9wbmc7YmFzZTY0LFwiICsgYmFzZTY0LmI2NGVuY29kZShidWYuZ2V0dmFsdWUoKSkuZGVjb2RlKClcbiAgICAgICAgcmV0dXJuIGltZyhcbiAgICAgICAgICAgIHNyYz1zcmMsXG4gICAgICAgICAgICBzdHlsZT1mXCJ3aWR0aDp7d2lkdGh9cHg7aW1hZ2UtcmVuZGVyaW5nOnBpeGVsYXRlZDtib3JkZXI6MXB4IHNvbGlkICNjY2NcIixcbiAgICAgICAgKVxuXG4gICAgZGVmIGNvbnZvbHZlKGEsIGspOlxuICAgICAgICBmcm9tIG51bXB5LmxpYi5zdHJpZGVfdHJpY2tzIGltcG9ydCBzbGlkaW5nX3dpbmRvd192aWV3XG5cbiAgICAgICAga2gsIGt3ID0gay5zaGFwZVxuICAgICAgICByZXR1cm4gbnAuZWluc3VtKFwiaWprbCxrbC0-aWpcIiwgc2xpZGluZ193aW5kb3dfdmlldyhhLCAoa2gsIGt3KSksIGspXG5cbiAgICByZXR1cm4gY29udm9sdmUsIG5wLCB0b19pbWdcblxuXG5AYXBwLmNlbGxcbmRlZiBfKGFuZ2xlLCBjb252b2x2ZSwga2VybmVsLCBucCwgcGFpbnQsIHRvX2ltZyk6XG4gICAgIyBUaGUgZHJhd2luZyAtPiByb3RhdGUgYnkgdGhlIHNsaWRlciAtPiBhIDI4eDI4IGFycmF5IC0-IGNvbnZvbHZlIHdpdGggYGtlcm5lbGAuXG4gICAgIyBUaGUgYWxwaGEgY2hhbm5lbCBpcyB0aGUgaW5rIG1hc2sgKGJyaWdodCB3aGVyZSB5b3UgZHJldyk7IHJvdGF0aW5nIHRoZVxuICAgICMgZnVsbC1yZXMgUElMICh0cmFuc3BhcmVudCBmaWxsKSBrZWVwcyB0aGF0IG1hc2sgY2xlYW4uIGBjb252YCBpcyB0aGUgaW1hZ2VcbiAgICAjIG5vZGU7IGBvdXQxYCBpcyB0aGUgYXJyYXkgdGhlIG5leHQgY2VsbCBwaWNrcyB1cCAodGhhdCByZWZlcmVuY2UgaXMgdGhlIGVkZ2VcbiAgICAjIGBjb252IC0-IGNvbnYyYCB0aGUgREFHIGRyYXdzKS5cbiAgICBfcGlsID0gcGFpbnQuZ2V0X3BpbCgpLmNvbnZlcnQoXCJSR0JBXCIpLnJvdGF0ZShhbmdsZS52YWx1ZSlcbiAgICBfYXJyID0gbnAuYXJyYXkoX3BpbC5yZXNpemUoKDI4LCAyOCkpKVsuLi4sIDNdLmFzdHlwZShmbG9hdClcbiAgICBvdXQxID0gY29udm9sdmUoX2FyciwgbnAuYXNhcnJheShrZXJuZWwudmFsdWUsIGR0eXBlPWZsb2F0KSlcbiAgICBjb252ID0gdG9faW1nKG91dDEpXG4gICAgcmV0dXJuIGNvbnYsIG91dDFcblxuXG5AYXBwLmNlbGxcbmRlZiBfKGNvbnZvbHZlLCBrZXJuZWwyLCBucCwgb3V0MSwgdG9faW1nKTpcbiAgICAjIFNlY29uZCBjb252b2x1dGlvbj
1203ogYG91dDFgIChmcm9tIHRoZSBjZWxsIGFib3ZlKSBibHVycmVkIGJ5IGBrZXJuZWwyYC5cbiAgICBvdXQyID0gY29udm9sdmUob3V0MSwgbnAuYXNhcnJheShrZXJuZWwyLnZhbHVlLCBkdHlwZT1mbG9hdCkpXG4gICAgY29udjIgPSB0b19pbWcob3V0MilcbiAgICByZXR1cm4gKGNvbnYyLClcblxuXG5pZiBfX25hbWVfXyA9PSBcIl9fbWFpbl9fXCI6XG4gICAgYXBwLnJ1bigpIn0sImNlbGwiOnsiaHRtbCI6IjxtYXJpbW8taXNsYW5kXG4gICAgZGF0YS1hcHAtaWQ9XCJtYXJpbW8tNzJiMWQ5NGMxMWFkXCJcbiAgICBkYXRhLWNlbGwtaWQ9XCJia0hDXCJcbiAgICBkYXRhLXJlYWN0aXZlPVwidHJ1ZVwiXG4-XG4gICAgPG1hcmltby1jZWxsLW91dHB1dD5cbiAgICA8c3Bhbj48L3NwYW4-XG4gICAgPC9tYXJpbW8tY2VsbC1vdXRwdXQ-XG4gICAgPG1hcmltby1jZWxsLWNvZGUgaGlkZGVuPiUyMyUyMEZpcnN0JTIwY29udm9sdXRpb24lMjBrZXJuZWwlMjAtLSUyMGElMjBuYXRpdmUlMjBtby51aS5tYXRyaXglMjBlZGl0b3IlMjAoZHJhZyUyMGFuJTIwZW50cnklMjB0byUwQSUyMyUyMGNoYW5nZSUyMGl0KS4lMjBJdCUyMGxpdmVzJTIwaW4lMjBpdHMlMjBvd24lMjBjZWxsJTIwc28lMjB0aGUlMjBEQUclMjBjYW4lMjB3aXJlJTIwaXQlMjB0byUyMCU2MGNvbnYlNjAlMjBvbmx5LiUwQWtlcm5lbCUyMCUzRCUyMG1vLnVpLm1hdHJpeCglNUIlNUIwJTJDJTIwLTElMkMlMjAwJTVEJTJDJTIwJTVCLTElMkMlMjA1JTJDJTIwLTElNUQlMkMlMjAlNUIwJTJDJTIwLTElMkMlMjAwJTVEJTVEKTwvbWFyaW1vLWNlbGwtY29kZT5cbjwvbWFyaW1vLWlzbGFuZD4iLCJpbmRleCI6Mywib3B0aW9ucyI6eyJsYW5ndWFnZSI6InB5dGhvbiIsInJlbmRlciI6eyJzb3VyY2UiOmZhbHNlLCJvdXRwdXQiOnRydWUsImluY2x1ZGUiOnRydWUsImVkaXRvciI6ZmFsc2UsImVycm9yIjp0cnVlLCJzZXJ2ZXJPdXRwdXQiOnRydWV9LCJleGVjdXRpb24iOnsiZW5hYmxlZCI6dHJ1ZX0sIm1hcmltbyI6eyJkaXNhYmxlZCI6ZmFsc2UsInVucGFyc2FibGUiOmZhbHNlfX19fQ"></marimo-mdx-island>
1204<marimo-mdx-island class="marimo-island-host" data-marimo-host="mdx" data-marimo-theme-mode="auto" data-marimo-app-id="marimo-72b1d94c11ad" data-marimo-cell-index="4" data-marimo-payload-encoding="base64url" data-marimo-payload="eyJwcm90b2NvbFZlcnNpb24iOjEsImFwcCI6eyJpZCI6Im1hcmltby03MmIxZDk0YzExYWQiLCJydW50aW1lQ2VsbENvdW50Ijo5LCJhc3NldHMiOnsibW9kdWxlU2NyaXB0cyI6WyJodHRwczovL2Nkbi5qc2RlbGl2ci5uZXQvbnBtL0BtYXJpbW8tdGVhbS9pc2xhbmRzQDAuMjMuMTUvZGlzdC9tYWluLmpzIl0sImxpbmtzIjpbeyJocmVmIjoiaHR0cHM6Ly9jZG4uanNkZWxpdnIubmV0L25wbS9AbWFyaW1vLXRlYW0vaXNsYW5kc0AwLjIzLjE1L2Rpc3Qvc3R5bGUuY3NzIiwicmVsIjoic3R5bGVzaGVldCIsInRpdGxlIjoibWFyaW1vLWlzbGFuZHMiLCJjcm9zc29yaWdpbiI6ImFub255bW91cyJ9LHsicmVsIjoicHJlY29ubmVjdCIsImhyZWYiOiJodHRwczovL2ZvbnRzLmdvb2dsZWFwaXMuY29tIn0seyJyZWwiOiJwcmVjb25uZWN0IiwiaHJlZiI6Imh0dHBzOi8vZm9udHMuZ3N0YXRpYy5jb20iLCJjcm9zc29yaWdpbiI6IiJ9LHsiaHJlZiI6Imh0dHBzOi8vZm9udHMuZ29vZ2xlYXBpcy5jb20vY3NzMj9mYW1pbHk9RmlyYStNb25vOndnaHRANDAwOzUwMDs3MDAmZmFtaWx5PUxvcmEmZmFtaWx5PVBUK1NhbnM6d2dodEA0MDA7NzAwJmRpc3BsYXk9c3dhcCIsInJlbCI6InN0eWxlc2hlZXQifSx7InJlbCI6InN0eWxlc2hlZXQiLCJocmVmIjoiaHR0cHM6Ly9jZG4uanNkZWxpdnIubmV0L25wbS9rYXRleEAwLjE2LjEwL2Rpc3Qva2F0ZXgubWluLmNzcyIsImludGVncml0eSI6InNoYTM4NC13Y0l4a2Y0azU1OEFqTTNZejNCQkZRVWJrL3pnSVlDMlIwUXBlZVliK1R3bEJWTXJsZ0xxd1JqUnRHWmlLN3d3IiwiY3Jvc3NvcmlnaW4iOiJhbm9ueW1vdXMifV0sImhlYWRUYWdzIjpbeyJ0YWciOiJtYXJpbW8tZmlsZW5hbWUiLCJhdHRycyI6eyJoaWRkZW4iOiIifSwidGV4dCI6ImRvY3VtZW50Lm1keCJ9XSwidmVyc2lvbiI6IjAuMjMuMTUifSwibm90ZWJvb2tDb2RlIjoiIyAvLy8gc2NyaXB0XG4jIHJlcXVpcmVzLXB5dGhvbiA9IFwiPj0zLjEyXCJcbiMgZGVwZW5kZW5jaWVzID0gW1xuIyAgICAgXCJtYXJpbW8-PTAuMjMuMTNcIixcbiMgICAgIFwibW9odG1sXCIsXG4jICAgICBcIm51bXB5XCIsXG4jICAgICBcInBpbGxvd1wiLFxuIyAgICAgXCJ3aWdnbHlzdHVmZj09MC41LjIxXCIsXG4jIF1cbiMgLy8vXG5pbXBvcnQgbWFyaW1vXG5cbl9fZ2VuZXJhdGVkX3dpdGggPSBcIjAuMjMuMTVcIlxuYXBwID0gbWFyaW1vLkFwcCgpXG5cblxuQGFwcC5jZWxsXG5kZWYgXygpOlxuICAgIGZyb20gcGF0aGxpYiBpbXBvcnQgUGF0aFxuICAgIGltcG9ydCBzeXNcblxuICAgIGltcG9ydCBtYXJpbW8gYXMgbW9cblxuICAgICMgUHJlZmVyIHRoZSBsb2NhbCBjaGVja291dCBzbyB0aGlzIGRlbW8gdHJhY2tzIHRoZSBpbi1yZXBvIFdpZGdldERBRyBldmVuXG4gICAgIyBiZWZvcmUgaXQgbGFuZHMgaW4gYSBwdWJsaXNoZWQgd2lnZ2x5c3R1ZmYgcmVsZWFzZS5cbiAgICByZXBvX3Jvb3QgPSBQYXRoKF9fZmlsZV9fKS5yZXNvbHZlKCkucGFyZW50c1sxXVxuICAgIGlmIChyZXBvX3Jvb3QgLyBcIndpZ2dseXN0dWZmXCIpLmV4aXN0cygpOlxuICAgICAgICBzeXMucGF0aC5pbnNlcnQoMCwgc3RyKHJlcG9fcm9vdCkpXG5cbiAgICBmcm9tIHdpZ2dseXN0dWZmIGltcG9ydCBQYWludCwgV2lkZ2V0REFHXG5cbiAgICByZXR1cm4gUGFpbnQsIFdpZGdldERBRywgbW9cblxuXG5AYXBwLmNlbGxcbmRlZiBfKFdpZGdldERBRywgYW5nbGUsIGNvbnYsIGNvbnYyLCBrZXJuZWwsIGtlcm5lbDIsIHBhaW50KTpcbiAgICBXaWRnZXREQUcuZnJvbV93aWRnZXRzKFtwYWludCwgYW5nbGUsIGtlcm5lbCwgY29udiwga2VybmVsMiwgY29udjJdKVxuICAgIHJldHVyblxuXG5cbkBhcHAuY2VsbFxuZGVmIF8oUGFpbnQsIG1vKTpcbiAgICAjIFRoZSBsaXZlIGRyYXdpbmcgc3VyZmFjZSAtLSBpdCBJUyB0aGUgXCJwYWludFwiIG5vZGUgZW1iZWRkZWQgaW4gdGhlIERBRyBiZWxvdy5cbiAgICAjIHN0b3JlX2JhY2tncm91bmQ9RmFsc2Uga2VlcHMgdGhlIGNhbnZhcyB0cmFuc3B
1204hcmVudCwgc28gdGhlIGFscGhhIGNoYW5uZWwgb2ZcbiAgICAjIHRoZSBleHBvcnRlZCBQTkcgaXMgYSBjbGVhbiBpbmsgbWFzayAob3BhcXVlIHdoZXJlIHlvdSBkcmV3LCBlbHNlIHRyYW5zcGFyZW50KS5cbiAgICBwYWludCA9IG1vLnVpLmFueXdpZGdldChQYWludCh3aWR0aD0yNjAsIGhlaWdodD0yNjAsIGNvbG9yX3BpY2tlcj1GYWxzZSwgc3RvcmVfYmFja2dyb3VuZD1GYWxzZSkpXG4gICAgcmV0dXJuIChwYWludCwpXG5cblxuQGFwcC5jZWxsXG5kZWYgXyhtbyk6XG4gICAgIyBGaXJzdCBjb252b2x1dGlvbiBrZXJuZWwgLS0gYSBuYXRpdmUgbW8udWkubWF0cml4IGVkaXRvciAoZHJhZyBhbiBlbnRyeSB0b1xuICAgICMgY2hhbmdlIGl0KS4gSXQgbGl2ZXMgaW4gaXRzIG93biBjZWxsIHNvIHRoZSBEQUcgY2FuIHdpcmUgaXQgdG8gYGNvbnZgIG9ubHkuXG4gICAga2VybmVsID0gbW8udWkubWF0cml4KFtbMCwgLTEsIDBdLCBbLTEsIDUsIC0xXSwgWzAsIC0xLCAwXV0pXG4gICAgcmV0dXJuIChrZXJuZWwsKVxuXG5cbkBhcHAuY2VsbFxuZGVmIF8obW8pOlxuICAgICMgU2Vjb25kIGtlcm5lbCwgaW4gaXRzIG93biBjZWxsIHRvbyAtLSBpdCBmZWVkcyBgY29udjJgLlxuICAgIGtlcm5lbDIgPSBtby51aS5tYXRyaXgoW1sxLCAxLCAxXSwgWzEsIDEsIDFdLCBbMSwgMSwgMV1dKVxuICAgIHJldHVybiAoa2VybmVsMiwpXG5cblxuQGFwcC5jZWxsXG5kZWYgXyhtbyk6XG4gICAgIyBBIHBsYWluIG1vLnVpLnNsaWRlciAtLSBpdCBsaXZlcyBpbiB0aGUgREFHIGFzIGEgbm9kZSBqdXN0IGxpa2UgdGhlIHdpZGdldHNcbiAgICAjIGFib3ZlLCBhbmQgcm90YXRlcyB0aGUgZHJhd24gZGlnaXQgYmVmb3JlIGl0IGlzIGNvbnZvbHZlZC5cbiAgICBhbmdsZSA9IG1vLnVpLnNsaWRlcigtMTgwLCAxODAsIHZhbHVlPTAsIHN0ZXA9NSwgbGFiZWw9XCJyb3RhdGXCsFwiLCBzaG93X3ZhbHVlPVRydWUpXG4gICAgcmV0dXJuIChhbmdsZSwpXG5cblxuQGFwcC5jZWxsXG5kZWYgXygpOlxuICAgICMgU2hhcmVkIGhlbHBlcnMgLS0gZXhwb3J0ZWQgKG5vIGxlYWRpbmcgdW5kZXJzY29yZSkgc28gdGhlIHR3byBjb252b2x1dGlvblxuICAgICMgY2VsbHMgYmVsb3cgY2FuIGJvdGggdXNlIHRoZW0uXG4gICAgaW1wb3J0IGJhc2U2NFxuICAgIGltcG9ydCBpb1xuXG4gICAgaW1wb3J0IG51bXB5IGFzIG5wXG4gICAgZnJvbSBtb2h0bWwgaW1wb3J0IGltZ1xuICAgIGZyb20gUElMIGltcG9ydCBJbWFnZVxuXG4gICAgZGVmIHRvX2ltZyhhLCB3aWR0aD0xODApOlxuICAgICAgICBhID0gbnAuYXNhcnJheShhLCBkdHlwZT1mbG9hdClcbiAgICAgICAgbG8sIGhpID0gZmxvYXQoYS5taW4oKSksIGZsb2F0KGEubWF4KCkpXG4gICAgICAgIHU4ID0gKChhIC0gbG8pIC8gKGhpIC0gbG8pICogMjU1IGlmIGhpID4gbG8gZWxzZSBucC56ZXJvc19saWtlKGEpKS5hc3R5cGUoXCJ1aW50OFwiKVxuICAgICAgICBidWYgPSBpby5CeXRlc0lPKClcbiAgICAgICAgSW1hZ2UuZnJvbWFycmF5KHU4KS5zYXZlKGJ1ZiwgZm9ybWF0PVwiUE5HXCIpXG4gICAgICAgIHNyYyA9IFwiZGF0YTppbWFnZS9wbmc7YmFzZTY0LFwiICsgYmFzZTY0LmI2NGVuY29kZShidWYuZ2V0dmFsdWUoKSkuZGVjb2RlKClcbiAgICAgICAgcmV0dXJuIGltZyhcbiAgICAgICAgICAgIHNyYz1zcmMsXG4gICAgICAgICAgICBzdHlsZT1mXCJ3aWR0aDp7d2lkdGh9cHg7aW1hZ2UtcmVuZGVyaW5nOnBpeGVsYXRlZDtib3JkZXI6MXB4IHNvbGlkICNjY2NcIixcbiAgICAgICAgKVxuXG4gICAgZGVmIGNvbnZvbHZlKGEsIGspOlxuICAgICAgICBmcm9tIG51bXB5LmxpYi5zdHJpZGVfdHJpY2tzIGltcG9ydCBzbGlkaW5nX3dpbmRvd192aWV3XG5cbiAgICAgICAga2gsIGt3ID0gay5zaGFwZVxuICAgICAgICByZXR1cm4gbnAuZWluc3VtKFwiaWprbCxrbC0-aWpcIiwgc2xpZGluZ193aW5kb3dfdmlldyhhLCAoa2gsIGt3KSksIGspXG5cbiAgICByZXR1cm4gY29udm9sdmUsIG5wLCB0b19pbWdcblxuXG5AYXBwLmNlbGxcbmRlZiBfKGFuZ2xlLCBjb252b2x2ZSwga2VybmVsLCBucCwgcGFpbnQsIHRvX2ltZyk6XG4gICAgIyBUaGUgZHJhd2luZyAtPiByb3RhdGUgYnkgdGhlIHNsaWRlciAtPiBhIDI4eDI4IGFycmF5IC0-IGNvbnZvbHZlIHdpdGggYGtlcm5lbGAuXG4gICAgIyBUaGUgYWxwaGEgY2hhbm5lbCBpcyB0aGUgaW5rIG1hc2sgKGJyaWdodCB3aGVyZSB5b3UgZHJldyk7IHJvdGF0aW5nIHRoZVxuICAgICMgZnVsbC1yZXMgUElMICh0cmFuc3BhcmVudCBmaWxsKSBrZWVwcyB0aGF0IG1hc2sgY2xlYW4uIGBjb252YCBpcyB0aGUgaW1hZ2VcbiAgICAjIG5vZGU7IGBvdXQxYCBpcyB0aGUgYXJyYXkgdGhlIG5leHQgY2VsbCBwaWNrcyB1cCAodGhhdCByZWZlcmVuY2UgaXMgdGhlIGVkZ2VcbiAgICAjIGBjb252IC0-IGNvbnYyYCB0aGUgREFHIGRyYXdzKS5cbiAgICBfcGlsID0gcGFpbnQuZ2V0X3BpbCgpLmNvbnZlcnQoXCJSR0JBXCIpLnJvdGF0ZShhbmdsZS52YWx1ZSlcbiAgICBfYXJyID0gbnAuYXJyYXkoX3BpbC5yZXNpemUoKDI4LCAyOCkpKVsuLi4sIDNdLmFzdHlwZShmbG9hdClcbiAgICBvdXQxID0gY29udm9sdmUoX2FyciwgbnAuYXNhcnJheShrZXJuZWwudmFsdWUsIGR0eXBlPWZsb2F0KSlcbiAgICBjb252ID0gdG9faW1nKG91dDEpXG4gICAgcmV0dXJuIGNvbnYsIG91dDFcblxuXG5AYXBwLmNlbGxcbmRlZiBfKGNvbnZvbHZlLCBrZXJuZWwyLCBucCwgb3V0MSwgdG9faW1nKTpcbiAgICAjIFNlY29uZCBjb252b2x1dGlvbjogYG91dDFgIChmcm9tIHRoZSBjZWxsIGFib3ZlKSBibHVycmVkIGJ5IGBrZXJuZWwyYC5cbiAgICBvdXQyID0gY29udm9sdmUob3V0MSwgbnAuYXNhcnJheShrZXJuZWwyLnZhbHVlLCBkdHlwZT1mbG9hdCkpXG4gICAgY29udjIgPSB0b19pbWcob3V0MilcbiAgICByZXR1cm4gKGNvbnYyLClcblxuXG5pZiBfX25hbWVfXyA9PSBcIl9fbWFpbl9fXCI6XG4gICAgYXBwLnJ1bigpIn0sImNlbGwiOnsiaHRtbCI6IjxtYXJpbW8taXNsYW5kXG4gICAgZGF0YS1hcHAtaWQ9XCJtYXJpbW8tNzJiMWQ5NGMxMWFkXCJcbiAgICBkYXRhLWNlbGwtaWQ9XCJsRVFhXCJcbiAgICBkYXRhLXJlYWN0aXZlPVwidHJ1ZVwiXG4-XG4gICAgPG1hcmltby1jZWxsLW91dHB1dD5cbiAgICA8c3Bhbj48L3NwYW4-XG4gICAgPC9tYXJpbW8tY2VsbC1vdXRwdXQ-XG4gICAgPG1hcmltby1jZWxsLWNvZGUgaGlkZGVuPiUyMyUyMFNlY29uZCUyMGtlcm5lbCUyQyUyMGluJTIwaXRzJTIwb3duJTIwY2VsbCUyMHRvbyUyMC0tJTIwaXQlMjBmZWVkcyUyMCU2MGNvbnYyJTYwLiUwQWtlcm5lbDIlMjAlM0QlMjBtby51aS5tYXRyaXgoJTVCJTVCMSUyQyUyMDElMkMlMjAxJTVEJTJDJTIwJTVCMSUyQyUyMDElMkMlMjAxJTVEJTJDJTIwJTVCMSUyQyUyMDElMkMlMjAxJTVEJTVEKTwvbWFyaW1vLWNlbGwtY29kZT5cbjwvbWFyaW1vLWlzbGFuZD4iLCJpbmRleCI6NCwib3B0aW9ucyI6eyJsYW5ndWFnZSI6InB5dGhvbiIsInJlbmRlciI6eyJzb3
1204VyY2UiOmZhbHNlLCJvdXRwdXQiOnRydWUsImluY2x1ZGUiOnRydWUsImVkaXRvciI6ZmFsc2UsImVycm9yIjp0cnVlLCJzZXJ2ZXJPdXRwdXQiOnRydWV9LCJleGVjdXRpb24iOnsiZW5hYmxlZCI6dHJ1ZX0sIm1hcmltbyI6eyJkaXNhYmxlZCI6ZmFsc2UsInVucGFyc2FibGUiOmZhbHNlfX19fQ"></marimo-mdx-island>
1205<marimo-mdx-island class="marimo-island-host" data-marimo-host="mdx" data-marimo-theme-mode="auto" data-marimo-app-id="marimo-72b1d94c11ad" data-marimo-cell-index="5" data-marimo-payload-encoding="base64url" data-marimo-payload="eyJwcm90b2NvbFZlcnNpb24iOjEsImFwcCI6eyJpZCI6Im1hcmltby03MmIxZDk0YzExYWQiLCJydW50aW1lQ2VsbENvdW50Ijo5LCJhc3NldHMiOnsibW9kdWxlU2NyaXB0cyI6WyJodHRwczovL2Nkbi5qc2RlbGl2ci5uZXQvbnBtL0BtYXJpbW8tdGVhbS9pc2xhbmRzQDAuMjMuMTUvZGlzdC9tYWluLmpzIl0sImxpbmtzIjpbeyJocmVmIjoiaHR0cHM6Ly9jZG4uanNkZWxpdnIubmV0L25wbS9AbWFyaW1vLXRlYW0vaXNsYW5kc0AwLjIzLjE1L2Rpc3Qvc3R5bGUuY3NzIiwicmVsIjoic3R5bGVzaGVldCIsInRpdGxlIjoibWFyaW1vLWlzbGFuZHMiLCJjcm9zc29yaWdpbiI6ImFub255bW91cyJ9LHsicmVsIjoicHJlY29ubmVjdCIsImhyZWYiOiJodHRwczovL2ZvbnRzLmdvb2dsZWFwaXMuY29tIn0seyJyZWwiOiJwcmVjb25uZWN0IiwiaHJlZiI6Imh0dHBzOi8vZm9udHMuZ3N0YXRpYy5jb20iLCJjcm9zc29yaWdpbiI6IiJ9LHsiaHJlZiI6Imh0dHBzOi8vZm9udHMuZ29vZ2xlYXBpcy5jb20vY3NzMj9mYW1pbHk9RmlyYStNb25vOndnaHRANDAwOzUwMDs3MDAmZmFtaWx5PUxvcmEmZmFtaWx5PVBUK1NhbnM6d2dodEA0MDA7NzAwJmRpc3BsYXk9c3dhcCIsInJlbCI6InN0eWxlc2hlZXQifSx7InJlbCI6InN0eWxlc2hlZXQiLCJocmVmIjoiaHR0cHM6Ly9jZG4uanNkZWxpdnIubmV0L25wbS9rYXRleEAwLjE2LjEwL2Rpc3Qva2F0ZXgubWluLmNzcyIsImludGVncml0eSI6InNoYTM4NC13Y0l4a2Y0azU1OEFqTTNZejNCQkZRVWJrL3pnSVlDMlIwUXBlZVliK1R3bEJWTXJsZ0xxd1JqUnRHWmlLN3d3IiwiY3Jvc3NvcmlnaW4iOiJhbm9ueW1vdXMifV0sImhlYWRUYWdzIjpbeyJ0YWciOiJtYXJpbW8tZmlsZW5hbWUiLCJhdHRycyI6eyJoaWRkZW4iOiIifSwidGV4dCI6ImRvY3VtZW50Lm1keCJ9XSwidmVyc2lvbiI6IjAuMjMuMTUifSwibm90ZWJvb2tDb2RlIjoiIyAvLy8gc2NyaXB0XG4jIHJlcXVpcmVzLXB5dGhvbiA9IFwiPj0zLjEyXCJcbiMgZGVwZW5kZW5jaWVzID0gW1xuIyAgICAgXCJtYXJpbW8-PTAuMjMuMTNcIixcbiMgICAgIFwibW9odG1sXCIsXG4jICAgICBcIm51bXB5XCIsXG4jICAgICBcInBpbGxvd1wiLFxuIyAgICAgXCJ3aWdnbHlzdHVmZj09MC41LjIxXCIsXG4jIF1cbiMgLy8vXG5pbXBvcnQgbWFyaW1vXG5cbl9fZ2VuZXJhdGVkX3dpdGggPSBcIjAuMjMuMTVcIlxuYXBwID0gbWFyaW1vLkFwcCgpXG5cblxuQGFwcC5jZWxsXG5kZWYgXygpOlxuICAgIGZyb20gcGF0aGxpYiBpbXBvcnQgUGF0aFxuICAgIGltcG9ydCBzeXNcblxuICAgIGltcG9ydCBtYXJpbW8gYXMgbW9cblxuICAgICMgUHJlZmVyIHRoZSBsb2NhbCBjaGVja291dCBzbyB0aGlzIGRlbW8gdHJhY2tzIHRoZSBpbi1yZXBvIFdpZGdldERBRyBldmVuXG4gICAgIyBiZWZvcmUgaXQgbGFuZHMgaW4gYSBwdWJsaXNoZWQgd2lnZ2x5c3R1ZmYgcmVsZWFzZS5cbiAgICByZXBvX3Jvb3QgPSBQYXRoKF9fZmlsZV9fKS5yZXNvbHZlKCkucGFyZW50c1sxXVxuICAgIGlmIChyZXBvX3Jvb3QgLyBcIndpZ2dseXN0dWZmXCIpLmV4aXN0cygpOlxuICAgICAgICBzeXMucGF0aC5pbnNlcnQoMCwgc3RyKHJlcG9fcm9vdCkpXG5cbiAgICBmcm9tIHdpZ2dseXN0dWZmIGltcG9ydCBQYWludCwgV2lkZ2V0REFHXG5cbiAgICByZXR1cm4gUGFpbnQsIFdpZGdldERBRywgbW9cblxuXG5AYXBwLmNlbGxcbmRlZiBfKFdpZGdldERBRywgYW5nbGUsIGNvbnYsIGNvbnYyLCBrZXJuZWwsIGtlcm5lbDIsIHBhaW50KTpcbiAgICBXaWRnZXREQUcuZnJvbV93aWRnZXRzKFtwYWludCwgYW5nbGUsIGtlcm5lbCwgY29udiwga2VybmVsMiwgY29udjJdKVxuICAgIHJldHVyblxuXG5cbkBhcHAuY2VsbFxuZGVmIF8oUGFpbnQsIG1vKTpcbiAgICAjIFRoZSBsaXZlIGRyYXdpbmcgc3VyZmFjZSAtLSBpdCBJUyB0aGUgXCJwYWludFwiIG5vZGUgZW1iZWRkZWQgaW4gdGhlIERBRyBiZWxvdy5cbiAgICAjIHN0b3JlX2JhY2tncm91bmQ9RmFsc2Uga2VlcHMgdGhlIGNhbnZhcyB0cmFuc3BhcmVudCwgc28gdGhlIGFscGhhIGNoYW5uZWwgb2ZcbiAgICAjIHRoZSBleHBvcnRlZCBQTkcgaXMgYSBjbGVhbiBpbmsgbWFzayAob3BhcXVlIHdoZXJlIHlvdSBkcmV3LCBlbHNlIHRyYW5zcGFyZW50KS5cbiAgICBwYWludCA9IG1vLnVpLmFueXdpZGdldChQYWludCh3aWR0aD0yNjAsIGhlaWdodD0yNjAsIGNvbG9yX3BpY2tlcj1GYWxzZSwgc3RvcmVfYmFja2dyb3VuZD1GYWxzZSkpXG4gICAgcmV0dXJuIChwYWludCwpXG5cblxuQGFwcC5jZWxsXG5kZWYgXyhtbyk6XG4gICAgIyBGaXJzdCBjb252b2x1dGlvbiBrZXJuZWwgLS0gYSBuYXRpdmUgbW8udWkubWF0cml4IGVkaXRvciAoZHJhZyBhbiBlbnRyeSB0b1xuICAgICMgY2hhbmdlIGl0KS4gSXQgbGl2ZXMgaW4gaXRzIG93biBjZWxsIHNvIHRoZSBEQUcgY2FuIHdpcmUgaXQgdG8gYGNvbnZgIG9ubHkuXG4gICAga2VybmVsID0gbW8udWkubWF0cml4KFtbMCwgLTEsIDBdLCBbLTEsIDUsIC0xXSwgWzAsIC0xLCAwXV0pXG4gICAgcmV0dXJuIChrZXJuZWwsKVxuXG5cbkBhcHAuY2VsbFxuZGVmIF8obW8pOlxuICAgICMgU2Vjb25kIGtlcm5lbCwgaW4gaXRzIG93biBjZWxsIHRvbyAtLSBpdCBmZWVkcyBgY29udjJgLlxuICAgIGtlcm5lbDIgPSBtby51aS5tYXRyaXgoW1sxLCAxLCAxXSwgWzEsIDEsIDFdLCBbMSwgMSwgMV1dKVxuICAgIHJldHVybiAoa2VybmVsMiwpXG5cblxuQGFwcC5jZWxsXG5kZWYgXyhtbyk6XG4gICAgIyBBIHBsYWluIG1vLnVpLnNsaWRlciAtLSBpdCBsaXZlcyBpbiB0aGUgREFHIGFzIGEgbm9kZSBqdXN0IGxpa2UgdGhlIHdpZGdldHNcbiAgICAjIGFib3ZlLCBhbmQgcm90YXRlcyB0aGUgZHJhd24gZGlnaXQgYmVmb3JlIGl0IGlzIGNvbnZvbHZlZC5cbiAgICBhbmdsZSA9IG1vLnVpLnNsaWRlcigtMTgwLCAxODAsIHZhbHVlPTAsIHN0ZXA9NSwgbGFiZWw9XCJyb3RhdGXCsFwiLCBzaG93X3ZhbHVlPVRydWUpXG4gICAgcmV0dXJuIChhbmdsZSwpXG5cblxuQGFwcC5jZWxsXG5kZWYgXygpOlxuICAgICMgU2hhcmVkIGhlbHBlcnMgLS0gZXhwb3J0ZWQgKG5vIGxlYWRpbmcgdW5kZXJzY29yZSkgc28gdGhlIHR3byBjb252b2x1dGlvblxuICAgICMgY2VsbHMgYmVsb3cgY2FuIGJvdGggdXNlIHRoZW0uXG4gICAgaW1wb3J0IGJhc2U2NFxuICAgIGltcG9ydCBpb1xuXG4gICAgaW1wb3J0IG51bXB5IGFzIG5wXG4gICAgZnJvbSBtb2h0bWwgaW1wb3J0IGltZ1xuICAgIGZyb20gUElMIGltcG9ydCBJbWFnZVxuXG4gICAgZGVmIHRvX2ltZyhhLCB3aWR0aD0xODApOlxuICAgICAgICBhID0gbnAuYXNhcnJheShhLCBkdHlwZT1mbG9hdClcbiAgICAgICAgbG8sIGhpID0gZmxvYXQoYS5taW4oKSksIGZsb2F0KGEubWF4KCkpXG4gICAgICAgI
1205HU4ID0gKChhIC0gbG8pIC8gKGhpIC0gbG8pICogMjU1IGlmIGhpID4gbG8gZWxzZSBucC56ZXJvc19saWtlKGEpKS5hc3R5cGUoXCJ1aW50OFwiKVxuICAgICAgICBidWYgPSBpby5CeXRlc0lPKClcbiAgICAgICAgSW1hZ2UuZnJvbWFycmF5KHU4KS5zYXZlKGJ1ZiwgZm9ybWF0PVwiUE5HXCIpXG4gICAgICAgIHNyYyA9IFwiZGF0YTppbWFnZS9wbmc7YmFzZTY0LFwiICsgYmFzZTY0LmI2NGVuY29kZShidWYuZ2V0dmFsdWUoKSkuZGVjb2RlKClcbiAgICAgICAgcmV0dXJuIGltZyhcbiAgICAgICAgICAgIHNyYz1zcmMsXG4gICAgICAgICAgICBzdHlsZT1mXCJ3aWR0aDp7d2lkdGh9cHg7aW1hZ2UtcmVuZGVyaW5nOnBpeGVsYXRlZDtib3JkZXI6MXB4IHNvbGlkICNjY2NcIixcbiAgICAgICAgKVxuXG4gICAgZGVmIGNvbnZvbHZlKGEsIGspOlxuICAgICAgICBmcm9tIG51bXB5LmxpYi5zdHJpZGVfdHJpY2tzIGltcG9ydCBzbGlkaW5nX3dpbmRvd192aWV3XG5cbiAgICAgICAga2gsIGt3ID0gay5zaGFwZVxuICAgICAgICByZXR1cm4gbnAuZWluc3VtKFwiaWprbCxrbC0-aWpcIiwgc2xpZGluZ193aW5kb3dfdmlldyhhLCAoa2gsIGt3KSksIGspXG5cbiAgICByZXR1cm4gY29udm9sdmUsIG5wLCB0b19pbWdcblxuXG5AYXBwLmNlbGxcbmRlZiBfKGFuZ2xlLCBjb252b2x2ZSwga2VybmVsLCBucCwgcGFpbnQsIHRvX2ltZyk6XG4gICAgIyBUaGUgZHJhd2luZyAtPiByb3RhdGUgYnkgdGhlIHNsaWRlciAtPiBhIDI4eDI4IGFycmF5IC0-IGNvbnZvbHZlIHdpdGggYGtlcm5lbGAuXG4gICAgIyBUaGUgYWxwaGEgY2hhbm5lbCBpcyB0aGUgaW5rIG1hc2sgKGJyaWdodCB3aGVyZSB5b3UgZHJldyk7IHJvdGF0aW5nIHRoZVxuICAgICMgZnVsbC1yZXMgUElMICh0cmFuc3BhcmVudCBmaWxsKSBrZWVwcyB0aGF0IG1hc2sgY2xlYW4uIGBjb252YCBpcyB0aGUgaW1hZ2VcbiAgICAjIG5vZGU7IGBvdXQxYCBpcyB0aGUgYXJyYXkgdGhlIG5leHQgY2VsbCBwaWNrcyB1cCAodGhhdCByZWZlcmVuY2UgaXMgdGhlIGVkZ2VcbiAgICAjIGBjb252IC0-IGNvbnYyYCB0aGUgREFHIGRyYXdzKS5cbiAgICBfcGlsID0gcGFpbnQuZ2V0X3BpbCgpLmNvbnZlcnQoXCJSR0JBXCIpLnJvdGF0ZShhbmdsZS52YWx1ZSlcbiAgICBfYXJyID0gbnAuYXJyYXkoX3BpbC5yZXNpemUoKDI4LCAyOCkpKVsuLi4sIDNdLmFzdHlwZShmbG9hdClcbiAgICBvdXQxID0gY29udm9sdmUoX2FyciwgbnAuYXNhcnJheShrZXJuZWwudmFsdWUsIGR0eXBlPWZsb2F0KSlcbiAgICBjb252ID0gdG9faW1nKG91dDEpXG4gICAgcmV0dXJuIGNvbnYsIG91dDFcblxuXG5AYXBwLmNlbGxcbmRlZiBfKGNvbnZvbHZlLCBrZXJuZWwyLCBucCwgb3V0MSwgdG9faW1nKTpcbiAgICAjIFNlY29uZCBjb252b2x1dGlvbjogYG91dDFgIChmcm9tIHRoZSBjZWxsIGFib3ZlKSBibHVycmVkIGJ5IGBrZXJuZWwyYC5cbiAgICBvdXQyID0gY29udm9sdmUob3V0MSwgbnAuYXNhcnJheShrZXJuZWwyLnZhbHVlLCBkdHlwZT1mbG9hdCkpXG4gICAgY29udjIgPSB0b19pbWcob3V0MilcbiAgICByZXR1cm4gKGNvbnYyLClcblxuXG5pZiBfX25hbWVfXyA9PSBcIl9fbWFpbl9fXCI6XG4gICAgYXBwLnJ1bigpIn0sImNlbGwiOnsiaHRtbCI6IjxtYXJpbW8taXNsYW5kXG4gICAgZGF0YS1hcHAtaWQ9XCJtYXJpbW8tNzJiMWQ5NGMxMWFkXCJcbiAgICBkYXRhLWNlbGwtaWQ9XCJQS3JpXCJcbiAgICBkYXRhLXJlYWN0aXZlPVwidHJ1ZVwiXG4-XG4gICAgPG1hcmltby1jZWxsLW91dHB1dD5cbiAgICA8c3Bhbj48L3NwYW4-XG4gICAgPC9tYXJpbW8tY2VsbC1vdXRwdXQ-XG4gICAgPG1hcmltby1jZWxsLWNvZGUgaGlkZGVuPiUyMyUyMEElMjBwbGFpbiUyMG1vLnVpLnNsaWRlciUyMC0tJTIwaXQlMjBsaXZlcyUyMGluJTIwdGhlJTIwREFHJTIwYXMlMjBhJTIwbm9kZSUyMGp1c3QlMjBsaWtlJTIwdGhlJTIwd2lkZ2V0cyUwQSUyMyUyMGFib3ZlJTJDJTIwYW5kJTIwcm90YXRlcyUyMHRoZSUyMGRyYXduJTIwZGlnaXQlMjBiZWZvcmUlMjBpdCUyMGlzJTIwY29udm9sdmVkLiUwQWFuZ2xlJTIwJTNEJTIwbW8udWkuc2xpZGVyKC0xODAlMkMlMjAxODAlMkMlMjB2YWx1ZSUzRDAlMkMlMjBzdGVwJTNENSUyQyUyMGxhYmVsJTNEJTIycm90YXRlJUMyJUIwJTIyJTJDJTIwc2hvd192YWx1ZSUzRFRydWUpPC9tYXJpbW8tY2VsbC1jb2RlPlxuPC9tYXJpbW8taXNsYW5kPiIsImluZGV4Ijo1LCJvcHRpb25zIjp7Imxhbmd1YWdlIjoicHl0aG9uIiwicmVuZGVyIjp7InNvdXJjZSI6ZmFsc2UsIm91dHB1dCI6dHJ1ZSwiaW5jbHVkZSI6dHJ1ZSwiZWRpdG9yIjpmYWxzZSwiZXJyb3IiOnRydWUsInNlcnZlck91dHB1dCI6dHJ1ZX0sImV4ZWN1dGlvbiI6eyJlbmFibGVkIjp0cnVlfSwibWFyaW1vIjp7ImRpc2FibGVkIjpmYWxzZSwidW5wYXJzYWJsZSI6ZmFsc2V9fX19"></marimo-mdx-island>
1206<marimo-mdx-island class="marimo-island-host" data-marimo-host="mdx" data-marimo-theme-mode="auto" data-marimo-app-id="marimo-72b1d94c11ad" data-marimo-cell-index="6" data-marimo-payload-encoding="base64url" data-marimo-payload="eyJwcm90b2NvbFZlcnNpb24iOjEsImFwcCI6eyJpZCI6Im1hcmltby03MmIxZDk0YzExYWQiLCJydW50aW1lQ2VsbENvdW50Ijo5LCJhc3NldHMiOnsibW9kdWxlU2NyaXB0cyI6WyJodHRwczovL2Nkbi5qc2RlbGl2ci5uZXQvbnBtL0BtYXJpbW8tdGVhbS9pc2xhbmRzQDAuMjMuMTUvZGlzdC9tYWluLmpzIl0sImxpbmtzIjpbeyJocmVmIjoiaHR0cHM6Ly9jZG4uanNkZWxpdnIubmV0L25wbS9AbWFyaW1vLXRlYW0vaXNsYW5kc0AwLjIzLjE1L2Rpc3Qvc3R5bGUuY3NzIiwicmVsIjoic3R5bGVzaGVldCIsInRpdGxlIjoibWFyaW1vLWlzbGFuZHMiLCJjcm9zc29yaWdpbiI6ImFub255bW91cyJ9LHsicmVsIjoicHJlY29ubmVjdCIsImhyZWYiOiJodHRwczovL2ZvbnRzLmdvb2dsZWFwaXMuY29tIn0seyJyZWwiOiJwcmVjb25uZWN0IiwiaHJlZiI6Imh0dHBzOi8vZm9udHMuZ3N0YXRpYy5jb20iLCJjcm9zc29yaWdpbiI6IiJ9LHsiaHJlZiI6Imh0dHBzOi8vZm9udHMuZ29vZ2xlYXBpcy5jb20vY3NzMj9mYW1pbHk9RmlyYStNb25vOndnaHRANDAwOzUwMDs3MDAmZmFtaWx5PUxvcmEmZmFtaWx5PVBUK1NhbnM6d2dodEA0MDA7NzAwJmRpc3BsYXk9c3dhcCIsInJlbCI6InN0eWxlc2hlZXQifSx7InJlbCI6InN0eWxlc2hlZXQiLCJocmVmIjoiaHR0cHM6Ly9jZG4uanNkZWxpdnIubmV0L25wbS9rYXRleEAwLjE2LjEwL2Rpc3Qva2F0ZXgubWluLmNzcyIsImludGVncml0eSI6InNoYTM4NC13Y0l4a2Y0azU1OEFqTTNZejNCQkZRVWJrL3pnSVlDMlIwUXBlZVliK1R3bEJWTXJsZ0xxd1JqUnRHWmlLN3d3IiwiY3Jvc3NvcmlnaW4iOiJhbm9ueW1vdXMifV0sImhlYWRUYWdzIjpbeyJ0YWciOiJtYXJpbW8tZmlsZW5hbWUiLCJhdHRycyI6eyJoaWRkZW4iOiIifSwidGV4dCI6ImRvY3VtZW50Lm1keCJ9XSwidmVyc2lvbiI6IjAuMjMuMTUifSwibm90ZWJvb2tDb2RlIjoiIyAvLy8gc2NyaXB0XG4jIHJlcXVpcmVzLXB5dGhvbiA9IFwiPj0zLjEyXCJcbiMgZGVwZW5kZW5jaWVzID0gW1xuIyAgICAgXCJtYXJpbW8-PTAuMjMuMTNcIixcbiMgICAgIFwibW9odG1sXCIsXG4jICAgICBcIm51bXB5XCIsXG4jICAgICBcInBpbGxvd1wiLFxuIyAgICAgXCJ3aWdnbHlzdHVmZj09MC41LjIxXCIsXG4jIF1cbiMgLy8vXG5pbXBvcnQgbWFyaW1vXG5cbl9fZ2VuZXJhdGVkX3dpdGggPSBcIjAuMjMuMTVcIlxuYXBwID0gbWFyaW1vLkFwcCgpXG5cblxuQGFwcC5jZWxsXG5kZWYgXygpOlxuICAgIGZyb20gcGF0aGxpYiBpbXBvcnQgUGF0aFxuICAgIGltcG9ydCBzeXNcblxuICAgIGltcG9ydCBtYXJpbW8gYXMgbW9cblxuICAgICMgUHJlZmVyIHRoZSBsb2NhbCBjaGVja291dCBzbyB0aGlzIGRlbW8gdHJhY2tzIHRoZSBpbi1yZXBvIFdpZGdldERBRyBldmVuXG4gICAgIyBiZWZvcmUgaXQgbGFuZHMgaW4gYSBwdWJsaXNoZWQgd2lnZ2x5c3R1ZmYgcmVsZWFzZS5cbiAgICByZXBvX3Jvb3QgPSBQYXRoKF9fZmlsZV9fKS5yZXNvbHZlKCkucGFyZW50c1sxXVxuICAgIGlmIChyZXBvX3Jvb3QgLyBcIndpZ2dseXN0dWZmXCIpLmV4aXN0cygpOlxuICAgICAgICBzeXMucGF0aC5pbnNlcnQoMCwgc3RyKHJlcG9fcm9vdCkpXG5cbiAgICBmcm9tIHdpZ2dseXN0dWZmIGltcG9ydCBQYWludCwgV2lkZ2
1206V0REFHXG5cbiAgICByZXR1cm4gUGFpbnQsIFdpZGdldERBRywgbW9cblxuXG5AYXBwLmNlbGxcbmRlZiBfKFdpZGdldERBRywgYW5nbGUsIGNvbnYsIGNvbnYyLCBrZXJuZWwsIGtlcm5lbDIsIHBhaW50KTpcbiAgICBXaWRnZXREQUcuZnJvbV93aWRnZXRzKFtwYWludCwgYW5nbGUsIGtlcm5lbCwgY29udiwga2VybmVsMiwgY29udjJdKVxuICAgIHJldHVyblxuXG5cbkBhcHAuY2VsbFxuZGVmIF8oUGFpbnQsIG1vKTpcbiAgICAjIFRoZSBsaXZlIGRyYXdpbmcgc3VyZmFjZSAtLSBpdCBJUyB0aGUgXCJwYWludFwiIG5vZGUgZW1iZWRkZWQgaW4gdGhlIERBRyBiZWxvdy5cbiAgICAjIHN0b3JlX2JhY2tncm91bmQ9RmFsc2Uga2VlcHMgdGhlIGNhbnZhcyB0cmFuc3BhcmVudCwgc28gdGhlIGFscGhhIGNoYW5uZWwgb2ZcbiAgICAjIHRoZSBleHBvcnRlZCBQTkcgaXMgYSBjbGVhbiBpbmsgbWFzayAob3BhcXVlIHdoZXJlIHlvdSBkcmV3LCBlbHNlIHRyYW5zcGFyZW50KS5cbiAgICBwYWludCA9IG1vLnVpLmFueXdpZGdldChQYWludCh3aWR0aD0yNjAsIGhlaWdodD0yNjAsIGNvbG9yX3BpY2tlcj1GYWxzZSwgc3RvcmVfYmFja2dyb3VuZD1GYWxzZSkpXG4gICAgcmV0dXJuIChwYWludCwpXG5cblxuQGFwcC5jZWxsXG5kZWYgXyhtbyk6XG4gICAgIyBGaXJzdCBjb252b2x1dGlvbiBrZXJuZWwgLS0gYSBuYXRpdmUgbW8udWkubWF0cml4IGVkaXRvciAoZHJhZyBhbiBlbnRyeSB0b1xuICAgICMgY2hhbmdlIGl0KS4gSXQgbGl2ZXMgaW4gaXRzIG93biBjZWxsIHNvIHRoZSBEQUcgY2FuIHdpcmUgaXQgdG8gYGNvbnZgIG9ubHkuXG4gICAga2VybmVsID0gbW8udWkubWF0cml4KFtbMCwgLTEsIDBdLCBbLTEsIDUsIC0xXSwgWzAsIC0xLCAwXV0pXG4gICAgcmV0dXJuIChrZXJuZWwsKVxuXG5cbkBhcHAuY2VsbFxuZGVmIF8obW8pOlxuICAgICMgU2Vjb25kIGtlcm5lbCwgaW4gaXRzIG93biBjZWxsIHRvbyAtLSBpdCBmZWVkcyBgY29udjJgLlxuICAgIGtlcm5lbDIgPSBtby51aS5tYXRyaXgoW1sxLCAxLCAxXSwgWzEsIDEsIDFdLCBbMSwgMSwgMV1dKVxuICAgIHJldHVybiAoa2VybmVsMiwpXG5cblxuQGFwcC5jZWxsXG5kZWYgXyhtbyk6XG4gICAgIyBBIHBsYWluIG1vLnVpLnNsaWRlciAtLSBpdCBsaXZlcyBpbiB0aGUgREFHIGFzIGEgbm9kZSBqdXN0IGxpa2UgdGhlIHdpZGdldHNcbiAgICAjIGFib3ZlLCBhbmQgcm90YXRlcyB0aGUgZHJhd24gZGlnaXQgYmVmb3JlIGl0IGlzIGNvbnZvbHZlZC5cbiAgICBhbmdsZSA9IG1vLnVpLnNsaWRlcigtMTgwLCAxODAsIHZhbHVlPTAsIHN0ZXA9NSwgbGFiZWw9XCJyb3RhdGXCsFwiLCBzaG93X3ZhbHVlPVRydWUpXG4gICAgcmV0dXJuIChhbmdsZSwpXG5cblxuQGFwcC5jZWxsXG5kZWYgXygpOlxuICAgICMgU2hhcmVkIGhlbHBlcnMgLS0gZXhwb3J0ZWQgKG5vIGxlYWRpbmcgdW5kZXJzY29yZSkgc28gdGhlIHR3byBjb252b2x1dGlvblxuICAgICMgY2VsbHMgYmVsb3cgY2FuIGJvdGggdXNlIHRoZW0uXG4gICAgaW1wb3J0IGJhc2U2NFxuICAgIGltcG9ydCBpb1xuXG4gICAgaW1wb3J0IG51bXB5IGFzIG5wXG4gICAgZnJvbSBtb2h0bWwgaW1wb3J0IGltZ1xuICAgIGZyb20gUElMIGltcG9ydCBJbWFnZVxuXG4gICAgZGVmIHRvX2ltZyhhLCB3aWR0aD0xODApOlxuICAgICAgICBhID0gbnAuYXNhcnJheShhLCBkdHlwZT1mbG9hdClcbiAgICAgICAgbG8sIGhpID0gZmxvYXQoYS5taW4oKSksIGZsb2F0KGEubWF4KCkpXG4gICAgICAgIHU4ID0gKChhIC0gbG8pIC8gKGhpIC0gbG8pICogMjU1IGlmIGhpID4gbG8gZWxzZSBucC56ZXJvc19saWtlKGEpKS5hc3R5cGUoXCJ1aW50OFwiKVxuICAgICAgICBidWYgPSBpby5CeXRlc0lPKClcbiAgICAgICAgSW1hZ2UuZnJvbWFycmF5KHU4KS5zYXZlKGJ1ZiwgZm9ybWF0PVwiUE5HXCIpXG4gICAgICAgIHNyYyA9IFwiZGF0YTppbWFnZS9wbmc7YmFzZTY0LFwiICsgYmFzZTY0LmI2NGVuY29kZShidWYuZ2V0dmFsdWUoKSkuZGVjb2RlKClcbiAgICAgICAgcmV0dXJuIGltZyhcbiAgICAgICAgICAgIHNyYz1zcmMsXG4gICAgICAgICAgICBzdHlsZT1mXCJ3aWR0aDp7d2lkdGh9cHg7aW1hZ2UtcmVuZGVyaW5nOnBpeGVsYXRlZDtib3JkZXI6MXB4IHNvbGlkICNjY2NcIixcbiAgICAgICAgKVxuXG4gICAgZGVmIGNvbnZvbHZlKGEsIGspOlxuICAgICAgICBmcm9tIG51bXB5LmxpYi5zdHJpZGVfdHJpY2tzIGltcG9ydCBzbGlkaW5nX3dpbmRvd192aWV3XG5cbiAgICAgICAga2gsIGt3ID0gay5zaGFwZVxuICAgICAgICByZXR1cm4gbnAuZWluc3VtKFwiaWprbCxrbC0-aWpcIiwgc2xpZGluZ193aW5kb3dfdmlldyhhLCAoa2gsIGt3KSksIGspXG5cbiAgICByZXR1cm4gY29udm9sdmUsIG5wLCB0b19pbWdcblxuXG5AYXBwLmNlbGxcbmRlZiBfKGFuZ2xlLCBjb252b2x2ZSwga2VybmVsLCBucCwgcGFpbnQsIHRvX2ltZyk6XG4gICAgIyBUaGUgZHJhd2luZyAtPiByb3RhdGUgYnkgdGhlIHNsaWRlciAtPiBhIDI4eDI4IGFycmF5IC0-IGNvbnZvbHZlIHdpdGggYGtlcm5lbGAuXG4gICAgIyBUaGUgYWxwaGEgY2hhbm5lbCBpcyB0aGUgaW5rIG1hc2sgKGJyaWdodCB3aGVyZSB5b3UgZHJldyk7IHJvdGF0aW5nIHRoZVxuICAgICMgZnVsbC1yZXMgUElMICh0cmFuc3BhcmVudCBmaWxsKSBrZWVwcyB0aGF0IG1hc2sgY2xlYW4uIGBjb252YCBpcyB0aGUgaW1hZ2VcbiAgICAjIG5vZGU7IGBvdXQxYCBpcyB0aGUgYXJyYXkgdGhlIG5leHQgY2VsbCBwaWNrcyB1cCAodGhhdCByZWZlcmVuY2UgaXMgdGhlIGVkZ2VcbiAgICAjIGBjb252IC0-IGNvbnYyYCB0aGUgREFHIGRyYXdzKS5cbiAgICBfcGlsID0gcGFpbnQuZ2V0X3BpbCgpLmNvbnZlcnQoXCJSR0JBXCIpLnJvdGF0ZShhbmdsZS52YWx1ZSlcbiAgICBfYXJyID0gbnAuYXJyYXkoX3BpbC5yZXNpemUoKDI4LCAyOCkpKVsuLi4sIDNdLmFzdHlwZShmbG9hdClcbiAgICBvdXQxID0gY29udm9sdmUoX2FyciwgbnAuYXNhcnJheShrZXJuZWwudmFsdWUsIGR0eXBlPWZsb2F0KSlcbiAgICBjb252ID0gdG9faW1nKG91dDEpXG4gICAgcmV0dXJuIGNvbnYsIG91dDFcblxuXG5AYXBwLmNlbGxcbmRlZiBfKGNvbnZvbHZlLCBrZXJuZWwyLCBucCwgb3V0MSwgdG9faW1nKTpcbiAgICAjIFNlY29uZCBjb252b2x1dGlvbj
1206ogYG91dDFgIChmcm9tIHRoZSBjZWxsIGFib3ZlKSBibHVycmVkIGJ5IGBrZXJuZWwyYC5cbiAgICBvdXQyID0gY29udm9sdmUob3V0MSwgbnAuYXNhcnJheShrZXJuZWwyLnZhbHVlLCBkdHlwZT1mbG9hdCkpXG4gICAgY29udjIgPSB0b19pbWcob3V0MilcbiAgICByZXR1cm4gKGNvbnYyLClcblxuXG5pZiBfX25hbWVfXyA9PSBcIl9fbWFpbl9fXCI6XG4gICAgYXBwLnJ1bigpIn0sImNlbGwiOnsiaHRtbCI6IjxtYXJpbW8taXNsYW5kXG4gICAgZGF0YS1hcHAtaWQ9XCJtYXJpbW8tNzJiMWQ5NGMxMWFkXCJcbiAgICBkYXRhLWNlbGwtaWQ9XCJYcmVmXCJcbiAgICBkYXRhLXJlYWN0aXZlPVwidHJ1ZVwiXG4-XG4gICAgPG1hcmltby1jZWxsLW91dHB1dD5cbiAgICA8c3Bhbj48L3NwYW4-XG4gICAgPC9tYXJpbW8tY2VsbC1vdXRwdXQ-XG4gICAgPG1hcmltby1jZWxsLWNvZGUgaGlkZGVuPiUyMyUyMFNoYXJlZCUyMGhlbHBlcnMlMjAtLSUyMGV4cG9ydGVkJTIwKG5vJTIwbGVhZGluZyUyMHVuZGVyc2NvcmUpJTIwc28lMjB0aGUlMjB0d28lMjBjb252b2x1dGlvbiUwQSUyMyUyMGNlbGxzJTIwYmVsb3clMjBjYW4lMjBib3RoJTIwdXNlJTIwdGhlbS4lMEFpbXBvcnQlMjBiYXNlNjQlMEFpbXBvcnQlMjBpbyUwQSUwQWltcG9ydCUyMG51bXB5JTIwYXMlMjBucCUwQWZyb20lMjBtb2h0bWwlMjBpbXBvcnQlMjBpbWclMEFmcm9tJTIwUElMJTIwaW1wb3J0JTIwSW1hZ2UlMEElMEFkZWYlMjB0b19pbWcoYSUyQyUyMHdpZHRoJTNEMTgwKSUzQSUwQSUyMCUyMCUyMCUyMGElMjAlM0QlMjBucC5hc2FycmF5KGElMkMlMjBkdHlwZSUzRGZsb2F0KSUwQSUyMCUyMCUyMCUyMGxvJTJDJTIwaGklMjAlM0QlMjBmbG9hdChhLm1pbigpKSUyQyUyMGZsb2F0KGEubWF4KCkpJTBBJTIwJTIwJTIwJTIwdTglMjAlM0QlMjAoKGElMjAtJTIwbG8pJTIwJTJGJTIwKGhpJTIwLSUyMGxvKSUyMColMjAyNTUlMjBpZiUyMGhpJTIwJTNFJTIwbG8lMjBlbHNlJTIwbnAuemVyb3NfbGlrZShhKSkuYXN0eXBlKCUyMnVpbnQ4JTIyKSUwQSUyMCUyMCUyMCUyMGJ1ZiUyMCUzRCUyMGlvLkJ5dGVzSU8oKSUwQSUyMCUyMCUyMCUyMEltYWdlLmZyb21hcnJheSh1OCkuc2F2ZShidWYlMkMlMjBmb3JtYXQlM0QlMjJQTkclMjIpJTBBJTIwJTIwJTIwJTIwc3JjJTIwJTNEJTIwJTIyZGF0YSUzQWltYWdlJTJGcG5nJTNCYmFzZTY0JTJDJTIyJTIwJTJCJTIwYmFzZTY0LmI2NGVuY29kZShidWYuZ2V0dmFsdWUoKSkuZGVjb2RlKCklMEElMjAlMjAlMjAlMjByZXR1cm4lMjBpbWcoJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwc3JjJTNEc3JjJTJDJTBBJTIwJTIwJTIwJTIwJTIwJTIwJTIwJTIwc3R5bGUlM0RmJTIyd2lkdGglM0ElN0J3aWR0aCU3RHB4JTNCaW1hZ2UtcmVuZGVyaW5nJTNBcGl4ZWxhdGVkJTNCYm9yZGVyJTNBMXB4JTIwc29saWQlMjAlMjNjY2MlMjIlMkMlMEElMjAlMjAlMjAlMjApJTBBJTBBZGVmJTIwY29udm9sdmUoYSUyQyUyMGspJTNBJTBBJTIwJTIwJTIwJTIwZnJvbSUyMG51bXB5LmxpYi5zdHJpZGVfdHJpY2tzJTIwaW1wb3J0JTIwc2xpZGluZ193aW5kb3dfdmlldyUwQSUwQSUyMCUyMCUyMCUyMGtoJTJDJTIwa3clMjAlM0QlMjBrLnNoYXBlJTBBJTIwJTIwJTIwJTIwcmV0dXJuJTIwbnAuZWluc3VtKCUyMmlqa2wlMkNrbC0lM0VpaiUyMiUyQyUyMHNsaWRpbmdfd2luZG93X3ZpZXcoYSUyQyUyMChraCUyQyUyMGt3KSklMkMlMjBrKTwvbWFyaW1vLWNlbGwtY29kZT5cbjwvbWFyaW1vLWlzbGFuZD4iLCJpbmRleCI6Niwib3B0aW9ucyI6eyJsYW5ndWFnZSI6InB5dGhvbiIsInJlbmRlciI6eyJzb3VyY2UiOmZhbHNlLCJvdXRwdXQiOnRydWUsImluY2x1ZGUiOnRydWUsImVkaXRvciI6ZmFsc2UsImVycm9yIjp0cnVlLCJzZXJ2ZXJPdXRwdXQiOnRydWV9LCJleGVjdXRpb24iOnsiZW5hYmxlZCI6dHJ1ZX0sIm1hcmltbyI6eyJkaXNhYmxlZCI6ZmFsc2UsInVucGFyc2FibGUiOmZhbHNlfX19fQ"></marimo-mdx-island>
1207<marimo-mdx-island class="marimo-island-host" data-marimo-host="mdx" data-marimo-theme-mode="auto" data-marimo-app-id="marimo-72b1d94c11ad" data-marimo-cell-index="7" data-marimo-payload-encoding="base64url" data-marimo-payload="eyJwcm90b2NvbFZlcnNpb24iOjEsImFwcCI6eyJpZCI6Im1hcmltby03MmIxZDk0YzExYWQiLCJydW50aW1lQ2VsbENvdW50Ijo5LCJhc3NldHMiOnsibW9kdWxlU2NyaXB0cyI6WyJodHRwczovL2Nkbi5qc2RlbGl2ci5uZXQvbnBtL0BtYXJpbW8tdGVhbS9pc2xhbmRzQDAuMjMuMTUvZGlzdC9tYWluLmpzIl0sImxpbmtzIjpbeyJocmVmIjoiaHR0cHM6Ly9jZG4uanNkZWxpdnIubmV0L25wbS9AbWFyaW1vLXRlYW0vaXNsYW5kc0AwLjIzLjE1L2Rpc3Qvc3R5bGUuY3NzIiwicmVsIjoic3R5bGVzaGVldCIsInRpdGxlIjoibWFyaW1vLWlzbGFuZHMiLCJjcm9zc29yaWdpbiI6ImFub255bW91cyJ9LHsicmVsIjoicHJlY29ubmVjdCIsImhyZWYiOiJodHRwczovL2ZvbnRzLmdvb2dsZWFwaXMuY29tIn0seyJyZWwiOiJwcmVjb25uZWN0IiwiaHJlZiI6Imh0dHBzOi8vZm9udHMuZ3N0YXRpYy5jb20iLCJjcm9zc29yaWdpbiI6IiJ9LHsiaHJlZiI6Imh0dHBzOi8vZm9udHMuZ29vZ2xlYXBpcy5jb20vY3NzMj9mYW1pbHk9RmlyYStNb25vOndnaHRANDAwOzUwMDs3MDAmZmFtaWx5PUxvcmEmZmFtaWx5PVBUK1NhbnM6d2dodEA0MDA7NzAwJmRpc3BsYXk9c3dhcCIsInJlbCI6InN0eWxlc2hlZXQifSx7InJlbCI6InN0eWxlc2hlZXQiLCJocmVmIjoiaHR0cHM6Ly9jZG4uanNkZWxpdnIubmV0L25wbS9rYXRleEAwLjE2LjEwL2Rpc3Qva2F0ZXgubWluLmNzcyIsImludGVncml0eSI6InNoYTM4NC13Y0l4a2Y0azU1OEFqTTNZejNCQkZRVWJrL3pnSVlDMlIwUXBlZVliK1R3bEJWTXJsZ0xxd1JqUnRHWmlLN3d3IiwiY3Jvc3NvcmlnaW4iOiJhbm9ueW1vdXMifV0sImhlYWRUYWdzIjpbeyJ0YWciOiJtYXJpbW8tZmlsZW5hbWUiLCJhdHRycyI6eyJoaWRkZW4iOiIifSwidGV4dCI6ImRvY3VtZW50Lm1keCJ9XSwidmVyc2lvbiI6IjAuMjMuMTUifSwibm90ZWJvb2tDb2RlIjoiIyAvLy8gc2NyaXB0XG4jIHJlcXVpcmVzLXB5dGhvbiA9IFwiPj0zLjEyXCJcbiMgZGVwZW5kZW5jaWVzID0gW1xuIyAgICAgXCJtYXJpbW8-PTAuMjMuMTNcIixcbiMgICAgIFwibW9odG1sXCIsXG4jICAgICBcIm51bXB5XCIsXG4jICAgICBcInBpbGxvd1wiLFxuIyAgICAgXCJ3aWdnbHlzdHVmZj09MC41LjIxXCIsXG4jIF1cbiMgLy8vXG5pbXBvcnQgbWFyaW1vXG5cbl9fZ2VuZXJhdGVkX3dpdGggPSBcIjAuMjMuMTVcIlxuYXBwID0gbWFyaW1vLkFwcCgpXG5cblxuQGFwcC5jZWxsXG5kZWYgXygpOlxuICAgIGZyb20gcGF0aGxpYiBpbXBvcnQgUGF0aFxuICAgIGltcG9ydCBzeXNcblxuICAgIGltcG9ydCBtYXJpbW8gYXMgbW9cblxuICAgICMgUHJlZmVyIHRoZSBsb2NhbCBjaGVja291dCBzbyB0aGlzIGRlbW8gdHJhY2tzIHRoZSBpbi1yZXBvIFdpZGdldERBRyBldmVuXG4gICAgIyBiZWZvcmUgaXQgbGFuZHMgaW4gYSBwdWJsaXNoZWQgd2lnZ2x5c3R1ZmYgcmVsZWFzZS5cbiAgICByZXBvX3Jvb3QgPSBQYXRoKF9fZmlsZV9fKS5yZXNvbHZlKCkucGFyZW50c1sxXVxuICAgIGlmIChyZXBvX3Jvb3QgLyBcIndpZ2dseXN0dWZmXCIpLmV4aXN0cygpOlxuICAgICAgICBzeXMucGF0aC5pbnNlcnQoMCwgc3RyKHJlcG9fcm9vdCkpXG5cbiAgICBmcm9tIHdpZ2dseXN0dWZmIGltcG9ydCBQYWludCwgV2lkZ2
1207V0REFHXG5cbiAgICByZXR1cm4gUGFpbnQsIFdpZGdldERBRywgbW9cblxuXG5AYXBwLmNlbGxcbmRlZiBfKFdpZGdldERBRywgYW5nbGUsIGNvbnYsIGNvbnYyLCBrZXJuZWwsIGtlcm5lbDIsIHBhaW50KTpcbiAgICBXaWRnZXREQUcuZnJvbV93aWRnZXRzKFtwYWludCwgYW5nbGUsIGtlcm5lbCwgY29udiwga2VybmVsMiwgY29udjJdKVxuICAgIHJldHVyblxuXG5cbkBhcHAuY2VsbFxuZGVmIF8oUGFpbnQsIG1vKTpcbiAgICAjIFRoZSBsaXZlIGRyYXdpbmcgc3VyZmFjZSAtLSBpdCBJUyB0aGUgXCJwYWludFwiIG5vZGUgZW1iZWRkZWQgaW4gdGhlIERBRyBiZWxvdy5cbiAgICAjIHN0b3JlX2JhY2tncm91bmQ9RmFsc2Uga2VlcHMgdGhlIGNhbnZhcyB0cmFuc3BhcmVudCwgc28gdGhlIGFscGhhIGNoYW5uZWwgb2ZcbiAgICAjIHRoZSBleHBvcnRlZCBQTkcgaXMgYSBjbGVhbiBpbmsgbWFzayAob3BhcXVlIHdoZXJlIHlvdSBkcmV3LCBlbHNlIHRyYW5zcGFyZW50KS5cbiAgICBwYWludCA9IG1vLnVpLmFueXdpZGdldChQYWludCh3aWR0aD0yNjAsIGhlaWdodD0yNjAsIGNvbG9yX3BpY2tlcj1GYWxzZSwgc3RvcmVfYmFja2dyb3VuZD1GYWxzZSkpXG4gICAgcmV0dXJuIChwYWludCwpXG5cblxuQGFwcC5jZWxsXG5kZWYgXyhtbyk6XG4gICAgIyBGaXJzdCBjb252b2x1dGlvbiBrZXJuZWwgLS0gYSBuYXRpdmUgbW8udWkubWF0cml4IGVkaXRvciAoZHJhZyBhbiBlbnRyeSB0b1xuICAgICMgY2hhbmdlIGl0KS4gSXQgbGl2ZXMgaW4gaXRzIG93biBjZWxsIHNvIHRoZSBEQUcgY2FuIHdpcmUgaXQgdG8gYGNvbnZgIG9ubHkuXG4gICAga2VybmVsID0gbW8udWkubWF0cml4KFtbMCwgLTEsIDBdLCBbLTEsIDUsIC0xXSwgWzAsIC0xLCAwXV0pXG4gICAgcmV0dXJuIChrZXJuZWwsKVxuXG5cbkBhcHAuY2VsbFxuZGVmIF8obW8pOlxuICAgICMgU2Vjb25kIGtlcm5lbCwgaW4gaXRzIG93biBjZWxsIHRvbyAtLSBpdCBmZWVkcyBgY29udjJgLlxuICAgIGtlcm5lbDIgPSBtby51aS5tYXRyaXgoW1sxLCAxLCAxXSwgWzEsIDEsIDFdLCBbMSwgMSwgMV1dKVxuICAgIHJldHVybiAoa2VybmVsMiwpXG5cblxuQGFwcC5jZWxsXG5kZWYgXyhtbyk6XG4gICAgIyBBIHBsYWluIG1vLnVpLnNsaWRlciAtLSBpdCBsaXZlcyBpbiB0aGUgREFHIGFzIGEgbm9kZSBqdXN0IGxpa2UgdGhlIHdpZGdldHNcbiAgICAjIGFib3ZlLCBhbmQgcm90YXRlcyB0aGUgZHJhd24gZGlnaXQgYmVmb3JlIGl0IGlzIGNvbnZvbHZlZC5cbiAgICBhbmdsZSA9IG1vLnVpLnNsaWRlcigtMTgwLCAxODAsIHZhbHVlPTAsIHN0ZXA9NSwgbGFiZWw9XCJyb3RhdGXCsFwiLCBzaG93X3ZhbHVlPVRydWUpXG4gICAgcmV0dXJuIChhbmdsZSwpXG5cblxuQGFwcC5jZWxsXG5kZWYgXygpOlxuICAgICMgU2hhcmVkIGhlbHBlcnMgLS0gZXhwb3J0ZWQgKG5vIGxlYWRpbmcgdW5kZXJzY29yZSkgc28gdGhlIHR3byBjb252b2x1dGlvblxuICAgICMgY2VsbHMgYmVsb3cgY2FuIGJvdGggdXNlIHRoZW0uXG4gICAgaW1wb3J0IGJhc2U2NFxuICAgIGltcG9ydCBpb1xuXG4gICAgaW1wb3J0IG51bXB5IGFzIG5wXG4gICAgZnJvbSBtb2h0bWwgaW1wb3J0IGltZ1xuICAgIGZyb20gUElMIGltcG9ydCBJbWFnZVxuXG4gICAgZGVmIHRvX2ltZyhhLCB3aWR0aD0xODApOlxuICAgICAgICBhID0gbnAuYXNhcnJheShhLCBkdHlwZT1mbG9hdClcbiAgICAgICAgbG8sIGhpID0gZmxvYXQoYS5taW4oKSksIGZsb2F0KGEubWF4KCkpXG4gICAgICAgIHU4ID0gKChhIC0gbG8pIC8gKGhpIC0gbG8pICogMjU1IGlmIGhpID4gbG8gZWxzZSBucC56ZXJvc19saWtlKGEpKS5hc3R5cGUoXCJ1aW50OFwiKVxuICAgICAgICBidWYgPSBpby5CeXRlc0lPKClcbiAgICAgICAgSW1hZ2UuZnJvbWFycmF5KHU4KS5zYXZlKGJ1ZiwgZm9ybWF0PVwiUE5HXCIpXG4gICAgICAgIHNyYyA9IFwiZGF0YTppbWFnZS9wbmc7YmFzZTY0LFwiICsgYmFzZTY0LmI2NGVuY29kZShidWYuZ2V0dmFsdWUoKSkuZGVjb2RlKClcbiAgICAgICAgcmV0dXJuIGltZyhcbiAgICAgICAgICAgIHNyYz1zcmMsXG4gICAgICAgICAgICBzdHlsZT1mXCJ3aWR0aDp7d2lkdGh9cHg7aW1hZ2UtcmVuZGVyaW5nOnBpeGVsYXRlZDtib3JkZXI6MXB4IHNvbGlkICNjY2NcIixcbiAgICAgICAgKVxuXG4gICAgZGVmIGNvbnZvbHZlKGEsIGspOlxuICAgICAgICBmcm9tIG51bXB5LmxpYi5zdHJpZGVfdHJpY2tzIGltcG9ydCBzbGlkaW5nX3dpbmRvd192aWV3XG5cbiAgICAgICAga2gsIGt3ID0gay5zaGFwZVxuICAgICAgICByZXR1cm4gbnAuZWluc3VtKFwiaWprbCxrbC0-aWpcIiwgc2xpZGluZ193aW5kb3dfdmlldyhhLCAoa2gsIGt3KSksIGspXG5cbiAgICByZXR1cm4gY29udm9sdmUsIG5wLCB0b19pbWdcblxuXG5AYXBwLmNlbGxcbmRlZiBfKGFuZ2xlLCBjb252b2x2ZSwga2VybmVsLCBucCwgcGFpbnQsIHRvX2ltZyk6XG4gICAgIyBUaGUgZHJhd2luZyAtPiByb3RhdGUgYnkgdGhlIHNsaWRlciAtPiBhIDI4eDI4IGFycmF5IC0-IGNvbnZvbHZlIHdpdGggYGtlcm5lbGAuXG4gICAgIyBUaGUgYWxwaGEgY2hhbm5lbCBpcyB0aGUgaW5rIG1hc2sgKGJyaWdodCB3aGVyZSB5b3UgZHJldyk7IHJvdGF0aW5nIHRoZVxuICAgICMgZnVsbC1yZXMgUElMICh0cmFuc3BhcmVudCBmaWxsKSBrZWVwcyB0aGF0IG1hc2sgY2xlYW4uIGBjb252YCBpcyB0aGUgaW1hZ2VcbiAgICAjIG5vZGU7IGBvdXQxYCBpcyB0aGUgYXJyYXkgdGhlIG5leHQgY2VsbCBwaWNrcyB1cCAodGhhdCByZWZlcmVuY2UgaXMgdGhlIGVkZ2VcbiAgICAjIGBjb252IC0-IGNvbnYyYCB0aGUgREFHIGRyYXdzKS5cbiAgICBfcGlsID0gcGFpbnQuZ2V0X3BpbCgpLmNvbnZlcnQoXCJSR0JBXCIpLnJvdGF0ZShhbmdsZS52YWx1ZSlcbiAgICBfYXJyID0gbnAuYXJyYXkoX3BpbC5yZXNpemUoKDI4LCAyOCkpKVsuLi4sIDNdLmFzdHlwZShmbG9hdClcbiAgICBvdXQxID0gY29udm9sdmUoX2FyciwgbnAuYXNhcnJheShrZXJuZWwudmFsdWUsIGR0eXBlPWZsb2F0KSlcbiAgICBjb252ID0gdG9faW1nKG91dDEpXG4gICAgcmV0dXJuIGNvbnYsIG91dDFcblxuXG5AYXBwLmNlbGxcbmRlZiBfKGNvbnZvbHZlLCBrZXJuZWwyLCBucCwgb3V0MSwgdG9faW1nKTpcbiAgICAjIFNlY29uZCBjb252b2x1dGlvbj
1207ogYG91dDFgIChmcm9tIHRoZSBjZWxsIGFib3ZlKSBibHVycmVkIGJ5IGBrZXJuZWwyYC5cbiAgICBvdXQyID0gY29udm9sdmUob3V0MSwgbnAuYXNhcnJheShrZXJuZWwyLnZhbHVlLCBkdHlwZT1mbG9hdCkpXG4gICAgY29udjIgPSB0b19pbWcob3V0MilcbiAgICByZXR1cm4gKGNvbnYyLClcblxuXG5pZiBfX25hbWVfXyA9PSBcIl9fbWFpbl9fXCI6XG4gICAgYXBwLnJ1bigpIn0sImNlbGwiOnsiaHRtbCI6IjxtYXJpbW8taXNsYW5kXG4gICAgZGF0YS1hcHAtaWQ9XCJtYXJpbW8tNzJiMWQ5NGMxMWFkXCJcbiAgICBkYXRhLWNlbGwtaWQ9XCJTRlBMXCJcbiAgICBkYXRhLXJlYWN0aXZlPVwidHJ1ZVwiXG4-XG4gICAgPG1hcmltby1jZWxsLW91dHB1dD5cbiAgICA8c3Bhbj48L3NwYW4-XG4gICAgPC9tYXJpbW8tY2VsbC1vdXRwdXQ-XG4gICAgPG1hcmltby1jZWxsLWNvZGUgaGlkZGVuPiUyMyUyMFRoZSUyMGRyYXdpbmclMjAtJTNFJTIwcm90YXRlJTIwYnklMjB0aGUlMjBzbGlkZXIlMjAtJTNFJTIwYSUyMDI4eDI4JTIwYXJyYXklMjAtJTNFJTIwY29udm9sdmUlMjB3aXRoJTIwJTYwa2VybmVsJTYwLiUwQSUyMyUyMFRoZSUyMGFscGhhJTIwY2hhbm5lbCUyMGlzJTIwdGhlJTIwaW5rJTIwbWFzayUyMChicmlnaHQlMjB3aGVyZSUyMHlvdSUyMGRyZXcpJTNCJTIwcm90YXRpbmclMjB0aGUlMEElMjMlMjBmdWxsLXJlcyUyMFBJTCUyMCh0cmFuc3BhcmVudCUyMGZpbGwpJTIwa2VlcHMlMjB0aGF0JTIwbWFzayUyMGNsZWFuLiUyMCU2MGNvbnYlNjAlMjBpcyUyMHRoZSUyMGltYWdlJTBBJTIzJTIwbm9kZSUzQiUyMCU2MG91dDElNjAlMjBpcyUyMHRoZSUyMGFycmF5JTIwdGhlJTIwbmV4dCUyMGNlbGwlMjBwaWNrcyUyMHVwJTIwKHRoYXQlMjByZWZlcmVuY2UlMjBpcyUyMHRoZSUyMGVkZ2UlMEElMjMlMjAlNjBjb252JTIwLSUzRSUyMGNvbnYyJTYwJTIwdGhlJTIwREFHJTIwZHJhd3MpLiUwQV9waWwlMjAlM0QlMjBwYWludC5nZXRfcGlsKCkuY29udmVydCglMjJSR0JBJTIyKS5yb3RhdGUoYW5nbGUudmFsdWUpJTBBX2FyciUyMCUzRCUyMG5wLmFycmF5KF9waWwucmVzaXplKCgyOCUyQyUyMDI4KSkpJTVCLi4uJTJDJTIwMyU1RC5hc3R5cGUoZmxvYXQpJTBBb3V0MSUyMCUzRCUyMGNvbnZvbHZlKF9hcnIlMkMlMjBucC5hc2FycmF5KGtlcm5lbC52YWx1ZSUyQyUyMGR0eXBlJTNEZmxvYXQpKSUwQWNvbnYlMjAlM0QlMjB0b19pbWcob3V0MSk8L21hcmltby1jZWxsLWNvZGU-XG48L21hcmltby1pc2xhbmQ-IiwiaW5kZXgiOjcsIm9wdGlvbnMiOnsibGFuZ3VhZ2UiOiJweXRob24iLCJyZW5kZXIiOnsic291cmNlIjpmYWxzZSwib3V0cHV0Ijp0cnVlLCJpbmNsdWRlIjp0cnVlLCJlZGl0b3IiOmZhbHNlLCJlcnJvciI6dHJ1ZSwic2VydmVyT3V0cHV0Ijp0cnVlfSwiZXhlY3V0aW9uIjp7ImVuYWJsZWQiOnRydWV9LCJtYXJpbW8iOnsiZGlzYWJsZWQiOmZhbHNlLCJ1bnBhcnNhYmxlIjpmYWxzZX19fX0"></marimo-mdx-island>
1208<marimo-mdx-island class="marimo-island-host" data-marimo-host="mdx" data-marimo-theme-mode="auto" data-marimo-app-id="marimo-72b1d94c11ad" data-marimo-cell-index="8" data-marimo-payload-encoding="base64url" data-marimo-payload="eyJwcm90b2NvbFZlcnNpb24iOjEsImFwcCI6eyJpZCI6Im1hcmltby03MmIxZDk0YzExYWQiLCJydW50aW1lQ2VsbENvdW50Ijo5LCJhc3NldHMiOnsibW9kdWxlU2NyaXB0cyI6WyJodHRwczovL2Nkbi5qc2RlbGl2ci5uZXQvbnBtL0BtYXJpbW8tdGVhbS9pc2xhbmRzQDAuMjMuMTUvZGlzdC9tYWluLmpzIl0sImxpbmtzIjpbeyJocmVmIjoiaHR0cHM6Ly9jZG4uanNkZWxpdnIubmV0L25wbS9AbWFyaW1vLXRlYW0vaXNsYW5kc0AwLjIzLjE1L2Rpc3Qvc3R5bGUuY3NzIiwicmVsIjoic3R5bGVzaGVldCIsInRpdGxlIjoibWFyaW1vLWlzbGFuZHMiLCJjcm9zc29yaWdpbiI6ImFub255bW91cyJ9LHsicmVsIjoicHJlY29ubmVjdCIsImhyZWYiOiJodHRwczovL2ZvbnRzLmdvb2dsZWFwaXMuY29tIn0seyJyZWwiOiJwcmVjb25uZWN0IiwiaHJlZiI6Imh0dHBzOi8vZm9udHMuZ3N0YXRpYy5jb20iLCJjcm9zc29yaWdpbiI6IiJ9LHsiaHJlZiI6Imh0dHBzOi8vZm9udHMuZ29vZ2xlYXBpcy5jb20vY3NzMj9mYW1pbHk9RmlyYStNb25vOndnaHRANDAwOzUwMDs3MDAmZmFtaWx5PUxvcmEmZmFtaWx5PVBUK1NhbnM6d2dodEA0MDA7NzAwJmRpc3BsYXk9c3dhcCIsInJlbCI6InN0eWxlc2hlZXQifSx7InJlbCI6InN0eWxlc2hlZXQiLCJocmVmIjoiaHR0cHM6Ly9jZG4uanNkZWxpdnIubmV0L25wbS9rYXRleEAwLjE2LjEwL2Rpc3Qva2F0ZXgubWluLmNzcyIsImludGVncml0eSI6InNoYTM4NC13Y0l4a2Y0azU1OEFqTTNZejNCQkZRVWJrL3pnSVlDMlIwUXBlZVliK1R3bEJWTXJsZ0xxd1JqUnRHWmlLN3d3IiwiY3Jvc3NvcmlnaW4iOiJhbm9ueW1vdXMifV0sImhlYWRUYWdzIjpbeyJ0YWciOiJtYXJpbW8tZmlsZW5hbWUiLCJhdHRycyI6eyJoaWRkZW4iOiIifSwidGV4dCI6ImRvY3VtZW50Lm1keCJ9XSwidmVyc2lvbiI6IjAuMjMuMTUifSwibm90ZWJvb2tDb2RlIjoiIyAvLy8gc2NyaXB0XG4jIHJlcXVpcmVzLXB5dGhvbiA9IFwiPj0zLjEyXCJcbiMgZGVwZW5kZW5jaWVzID0gW1xuIyAgICAgXCJtYXJpbW8-PTAuMjMuMTNcIixcbiMgICAgIFwibW9odG1sXCIsXG4jICAgICBcIm51bXB5XCIsXG4jICAgICBcInBpbGxvd1wiLFxuIyAgICAgXCJ3aWdnbHlzdHVmZj09MC41LjIxXCIsXG4jIF1cbiMgLy8vXG5pbXBvcnQgbWFyaW1vXG5cbl9fZ2VuZXJhdGVkX3dpdGggPSBcIjAuMjMuMTVcIlxuYXBwID0gbWFyaW1vLkFwcCgpXG5cblxuQGFwcC5jZWxsXG5kZWYgXygpOlxuICAgIGZyb20gcGF0aGxpYiBpbXBvcnQgUGF0aFxuICAgIGltcG9ydCBzeXNcblxuICAgIGltcG9ydCBtYXJpbW8gYXMgbW9cblxuICAgICMgUHJlZmVyIHRoZSBsb2NhbCBjaGVja291dCBzbyB0aGlzIGRlbW8gdHJhY2tzIHRoZSBpbi1yZXBvIFdpZGdldERBRyBldmVuXG4gICAgIyBiZWZvcmUgaXQgbGFuZHMgaW4gYSBwdWJsaXNoZWQgd2lnZ2x5c3R1ZmYgcmVsZWFzZS5cbiAgICByZXBvX3Jvb3QgPSBQYXRoKF9fZmlsZV9fKS5yZXNvbHZlKCkucGFyZW50c1sxXVxuICAgIGlmIChyZXBvX3Jvb3QgLyBcIndpZ2dseXN0dWZmXCIpLmV4aXN0cygpOlxuICAgICAgICBzeXMucGF0aC5pbnNlcnQoMCwgc3RyKHJlcG9fcm9vdCkpXG5cbiAgICBmcm9tIHdpZ2dseXN0dWZmIGltcG9ydCBQYWludCwgV2lkZ2
1208V0REFHXG5cbiAgICByZXR1cm4gUGFpbnQsIFdpZGdldERBRywgbW9cblxuXG5AYXBwLmNlbGxcbmRlZiBfKFdpZGdldERBRywgYW5nbGUsIGNvbnYsIGNvbnYyLCBrZXJuZWwsIGtlcm5lbDIsIHBhaW50KTpcbiAgICBXaWRnZXREQUcuZnJvbV93aWRnZXRzKFtwYWludCwgYW5nbGUsIGtlcm5lbCwgY29udiwga2VybmVsMiwgY29udjJdKVxuICAgIHJldHVyblxuXG5cbkBhcHAuY2VsbFxuZGVmIF8oUGFpbnQsIG1vKTpcbiAgICAjIFRoZSBsaXZlIGRyYXdpbmcgc3VyZmFjZSAtLSBpdCBJUyB0aGUgXCJwYWludFwiIG5vZGUgZW1iZWRkZWQgaW4gdGhlIERBRyBiZWxvdy5cbiAgICAjIHN0b3JlX2JhY2tncm91bmQ9RmFsc2Uga2VlcHMgdGhlIGNhbnZhcyB0cmFuc3BhcmVudCwgc28gdGhlIGFscGhhIGNoYW5uZWwgb2ZcbiAgICAjIHRoZSBleHBvcnRlZCBQTkcgaXMgYSBjbGVhbiBpbmsgbWFzayAob3BhcXVlIHdoZXJlIHlvdSBkcmV3LCBlbHNlIHRyYW5zcGFyZW50KS5cbiAgICBwYWludCA9IG1vLnVpLmFueXdpZGdldChQYWludCh3aWR0aD0yNjAsIGhlaWdodD0yNjAsIGNvbG9yX3BpY2tlcj1GYWxzZSwgc3RvcmVfYmFja2dyb3VuZD1GYWxzZSkpXG4gICAgcmV0dXJuIChwYWludCwpXG5cblxuQGFwcC5jZWxsXG5kZWYgXyhtbyk6XG4gICAgIyBGaXJzdCBjb252b2x1dGlvbiBrZXJuZWwgLS0gYSBuYXRpdmUgbW8udWkubWF0cml4IGVkaXRvciAoZHJhZyBhbiBlbnRyeSB0b1xuICAgICMgY2hhbmdlIGl0KS4gSXQgbGl2ZXMgaW4gaXRzIG93biBjZWxsIHNvIHRoZSBEQUcgY2FuIHdpcmUgaXQgdG8gYGNvbnZgIG9ubHkuXG4gICAga2VybmVsID0gbW8udWkubWF0cml4KFtbMCwgLTEsIDBdLCBbLTEsIDUsIC0xXSwgWzAsIC0xLCAwXV0pXG4gICAgcmV0dXJuIChrZXJuZWwsKVxuXG5cbkBhcHAuY2VsbFxuZGVmIF8obW8pOlxuICAgICMgU2Vjb25kIGtlcm5lbCwgaW4gaXRzIG93biBjZWxsIHRvbyAtLSBpdCBmZWVkcyBgY29udjJgLlxuICAgIGtlcm5lbDIgPSBtby51aS5tYXRyaXgoW1sxLCAxLCAxXSwgWzEsIDEsIDFdLCBbMSwgMSwgMV1dKVxuICAgIHJldHVybiAoa2VybmVsMiwpXG5cblxuQGFwcC5jZWxsXG5kZWYgXyhtbyk6XG4gICAgIyBBIHBsYWluIG1vLnVpLnNsaWRlciAtLSBpdCBsaXZlcyBpbiB0aGUgREFHIGFzIGEgbm9kZSBqdXN0IGxpa2UgdGhlIHdpZGdldHNcbiAgICAjIGFib3ZlLCBhbmQgcm90YXRlcyB0aGUgZHJhd24gZGlnaXQgYmVmb3JlIGl0IGlzIGNvbnZvbHZlZC5cbiAgICBhbmdsZSA9IG1vLnVpLnNsaWRlcigtMTgwLCAxODAsIHZhbHVlPTAsIHN0ZXA9NSwgbGFiZWw9XCJyb3RhdGXCsFwiLCBzaG93X3ZhbHVlPVRydWUpXG4gICAgcmV0dXJuIChhbmdsZSwpXG5cblxuQGFwcC5jZWxsXG5kZWYgXygpOlxuICAgICMgU2hhcmVkIGhlbHBlcnMgLS0gZXhwb3J0ZWQgKG5vIGxlYWRpbmcgdW5kZXJzY29yZSkgc28gdGhlIHR3byBjb252b2x1dGlvblxuICAgICMgY2VsbHMgYmVsb3cgY2FuIGJvdGggdXNlIHRoZW0uXG4gICAgaW1wb3J0IGJhc2U2NFxuICAgIGltcG9ydCBpb1xuXG4gICAgaW1wb3J0IG51bXB5IGFzIG5wXG4gICAgZnJvbSBtb2h0bWwgaW1wb3J0IGltZ1xuICAgIGZyb20gUElMIGltcG9ydCBJbWFnZVxuXG4gICAgZGVmIHRvX2ltZyhhLCB3aWR0aD0xODApOlxuICAgICAgICBhID0gbnAuYXNhcnJheShhLCBkdHlwZT1mbG9hdClcbiAgICAgICAgbG8sIGhpID0gZmxvYXQoYS5taW4oKSksIGZsb2F0KGEubWF4KCkpXG4gICAgICAgIHU4ID0gKChhIC0gbG8pIC8gKGhpIC0gbG8pICogMjU1IGlmIGhpID4gbG8gZWxzZSBucC56ZXJvc19saWtlKGEpKS5hc3R5cGUoXCJ1aW50OFwiKVxuICAgICAgICBidWYgPSBpby5CeXRlc0lPKClcbiAgICAgICAgSW1hZ2UuZnJvbWFycmF5KHU4KS5zYXZlKGJ1ZiwgZm9ybWF0PVwiUE5HXCIpXG4gICAgICAgIHNyYyA9IFwiZGF0YTppbWFnZS9wbmc7YmFzZTY0LFwiICsgYmFzZTY0LmI2NGVuY29kZShidWYuZ2V0dmFsdWUoKSkuZGVjb2RlKClcbiAgICAgICAgcmV0dXJuIGltZyhcbiAgICAgICAgICAgIHNyYz1zcmMsXG4gICAgICAgICAgICBzdHlsZT1mXCJ3aWR0aDp7d2lkdGh9cHg7aW1hZ2UtcmVuZGVyaW5nOnBpeGVsYXRlZDtib3JkZXI6MXB4IHNvbGlkICNjY2NcIixcbiAgICAgICAgKVxuXG4gICAgZGVmIGNvbnZvbHZlKGEsIGspOlxuICAgICAgICBmcm9tIG51bXB5LmxpYi5zdHJpZGVfdHJpY2tzIGltcG9ydCBzbGlkaW5nX3dpbmRvd192aWV3XG5cbiAgICAgICAga2gsIGt3ID0gay5zaGFwZVxuICAgICAgICByZXR1cm4gbnAuZWluc3VtKFwiaWprbCxrbC0-aWpcIiwgc2xpZGluZ193aW5kb3dfdmlldyhhLCAoa2gsIGt3KSksIGspXG5cbiAgICByZXR1cm4gY29udm9sdmUsIG5wLCB0b19pbWdcblxuXG5AYXBwLmNlbGxcbmRlZiBfKGFuZ2xlLCBjb252b2x2ZSwga2VybmVsLCBucCwgcGFpbnQsIHRvX2ltZyk6XG4gICAgIyBUaGUgZHJhd2luZyAtPiByb3RhdGUgYnkgdGhlIHNsaWRlciAtPiBhIDI4eDI4IGFycmF5IC0-IGNvbnZvbHZlIHdpdGggYGtlcm5lbGAuXG4gICAgIyBUaGUgYWxwaGEgY2hhbm5lbCBpcyB0aGUgaW5rIG1hc2sgKGJyaWdodCB3aGVyZSB5b3UgZHJldyk7IHJvdGF0aW5nIHRoZVxuICAgICMgZnVsbC1yZXMgUElMICh0cmFuc3BhcmVudCBmaWxsKSBrZWVwcyB0aGF0IG1hc2sgY2xlYW4uIGBjb252YCBpcyB0aGUgaW1hZ2VcbiAgICAjIG5vZGU7IGBvdXQxYCBpcyB0aGUgYXJyYXkgdGhlIG5leHQgY2VsbCBwaWNrcyB1cCAodGhhdCByZWZlcmVuY2UgaXMgdGhlIGVkZ2VcbiAgICAjIGBjb252IC0-IGNvbnYyYCB0aGUgREFHIGRyYXdzKS5cbiAgICBfcGlsID0gcGFpbnQuZ2V0X3BpbCgpLmNvbnZlcnQoXCJSR0JBXCIpLnJvdGF0ZShhbmdsZS52YWx1ZSlcbiAgICBfYXJyID0gbnAuYXJyYXkoX3BpbC5yZXNpemUoKDI4LCAyOCkpKVsuLi4sIDNdLmFzdHlwZShmbG9hdClcbiAgICBvdXQxID0gY29udm9sdmUoX2FyciwgbnAuYXNhcnJheShrZXJuZWwudmFsdWUsIGR0eXBlPWZsb2F0KSlcbiAgICBjb252ID0gdG9faW1nKG91dDEpXG4gICAgcmV0dXJuIGNvbnYsIG91dDFcblxuXG5AYXBwLmNlbGxcbmRlZiBfKGNvbnZvbHZlLCBrZXJuZWwyLCBucCwgb3V0MSwgdG9faW1nKTpcbiAgICAjIFNlY29uZCBjb252b2x1dGlvbj
1208ogYG91dDFgIChmcm9tIHRoZSBjZWxsIGFib3ZlKSBibHVycmVkIGJ5IGBrZXJuZWwyYC5cbiAgICBvdXQyID0gY29udm9sdmUob3V0MSwgbnAuYXNhcnJheShrZXJuZWwyLnZhbHVlLCBkdHlwZT1mbG9hdCkpXG4gICAgY29udjIgPSB0b19pbWcob3V0MilcbiAgICByZXR1cm4gKGNvbnYyLClcblxuXG5pZiBfX25hbWVfXyA9PSBcIl9fbWFpbl9fXCI6XG4gICAgYXBwLnJ1bigpIn0sImNlbGwiOnsiaHRtbCI6IjxtYXJpbW8taXNsYW5kXG4gICAgZGF0YS1hcHAtaWQ9XCJtYXJpbW8tNzJiMWQ5NGMxMWFkXCJcbiAgICBkYXRhLWNlbGwtaWQ9XCJCWXRDXCJcbiAgICBkYXRhLXJlYWN0aXZlPVwidHJ1ZVwiXG4-XG4gICAgPG1hcmltby1jZWxsLW91dHB1dD5cbiAgICA8c3Bhbj48L3NwYW4-XG4gICAgPC9tYXJpbW8tY2VsbC1vdXRwdXQ-XG4gICAgPG1hcmltby1jZWxsLWNvZGUgaGlkZGVuPiUyMyUyMFNlY29uZCUyMGNvbnZvbHV0aW9uJTNBJTIwJTYwb3V0MSU2MCUyMChmcm9tJTIwdGhlJTIwY2VsbCUyMGFib3ZlKSUyMGJsdXJyZWQlMjBieSUyMCU2MGtlcm5lbDIlNjAuJTBBb3V0MiUyMCUzRCUyMGNvbnZvbHZlKG91dDElMkMlMjBucC5hc2FycmF5KGtlcm5lbDIudmFsdWUlMkMlMjBkdHlwZSUzRGZsb2F0KSklMEFjb252MiUyMCUzRCUyMHRvX2ltZyhvdXQyKTwvbWFyaW1vLWNlbGwtY29kZT5cbjwvbWFyaW1vLWlzbGFuZD4iLCJpbmRleCI6OCwib3B0aW9ucyI6eyJsYW5ndWFnZSI6InB5dGhvbiIsInJlbmRlciI6eyJzb3VyY2UiOmZhbHNlLCJvdXRwdXQiOnRydWUsImluY2x1ZGUiOnRydWUsImVkaXRvciI6ZmFsc2UsImVycm9yIjp0cnVlLCJzZXJ2ZXJPdXRwdXQiOnRydWV9LCJleGVjdXRpb24iOnsiZW5hYmxlZCI6dHJ1ZX0sIm1hcmltbyI6eyJkaXNhYmxlZCI6ZmFsc2UsInVucGFyc2FibGUiOmZhbHNlfX19fQ"></marimo-mdx-island>
1209<h2>Generate this yourself</h2>
1210<p>The page starts life as an ordinary marimo notebook. You edit it as a <code>.py</code>
1211file and let marimo export it to the MDX flavor that marimo-mdx understands:</p>
1212<pre><code class="language-bash">marimo export md widget_dag.py --flavor mdx -o post.mdx
1213</code></pre>
1214<p>That <code>.mdx</code> is a <code>marimo-config</code> fence that declares the dependencies, followed
1215by <code>python marimo</code> cells. To turn it into the interactive page above you need a
1216tiny <a href="https://deno.com/">Deno</a> script that compiles the <code>.mdx</code> with the
1217<a href="https://github.com/marimo-team/mdx-marimo">marimo-mdx</a> remark plugin.</p>
1218<pre><code class="language-ts">// mdx_to_html.ts -- reads .mdx on stdin, writes an HTML fragment on stdout
1219
1220import { evaluate } from &quot;npm:@mdx-js/mdx@3&quot;;
1221import { remarkMarimo } from &quot;npm:@marimo-team/mdx-marimo/remark&quot;;
1222import { h } from &quot;npm:preact@10&quot;;
1223import * as runtime from &quot;npm:preact@10/jsx-runtime&quot;;
1224import { renderToString } from &quot;npm:preact-render-to-string@6&quot;;
1225
1226const src = await new Response(Deno.stdin.readable).text();
1227const { default: MDX } = await evaluate(src, { ...runtime, remarkPlugins: [remarkMarimo] });
1228
1229console.log(renderToString(h(MDX, {})));
1230</code></pre>
1231<p>As luck would have it, you don't need to install node for this to work. <code>uvx</code> can fetch Deno, and Deno fetches the
1232npm packages for you.</p>
1233<pre><code class="language-bash">cat post.mdx | uvx deno run -A --node-modules-dir=auto mdx_to_html.ts > post.html
1234</code></pre>
1235<p>Finally, for a static Python site, make sure you load the browser runtime once on the page so pyodide hydrates the
1236cells into live widgets:</p>
1237<pre><code class="language-html">&lt;link rel=&quot;stylesheet&quot; href=&quot;https://esm.sh/@marimo-team/mdx-marimo/styles.css&quot;>
1238&lt;script type=&quot;module&quot; src=&quot;https://esm.sh/@marimo-team/mdx-marimo/element/auto&quot;>&lt;/script>
1239</code></pre>
1240<p>And there you go, nested marimo notebook outputs can be hosted on a static markdown site!</p>
1241<link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin>
1242<link rel="preconnect" href="https://esm.sh" crossorigin>
1243<link rel="modulepreload" href="https://cdn.jsdelivr.net/npm/@marimo-team/[email protected]/dist/main.js">
1244<link rel="stylesheet" href="https://esm.sh/@marimo-team/[email protected]/styles.css">
1245<script type="module" src="https://esm.sh/@marimo-team/[email protected]/element/auto"></script>
1245
1246
1247            </div>
1248        </article>
1249        
1250        <!-- Featured article (fully rendered) -->
1251        <article
1252            class="mb-10 pb-8 border-b border-gray-300 dark:border-gray-700"
1253        >
1254            <h2
1255                class="text-3xl font-bold text-gray-900 dark:text-gray-100 mb-3"
1256            >
1257                <a
1258                    href="/posts/scrape-permissions"
1259                    class="hover:text-primary dark:hover:text-dark-primary transition-colors"
1260                >
1261                    scrape permissions
1262                </a>
1263            </h2>
1264            <div class="flex items-center space-x-4 mb-4">
1265                <div class="text-gray-600 dark:text-gray-400 text-sm">
1266                    2026-07-21
1267                </div>
1268                
1269                <div class="flex flex-wrap gap-2">
1270                    
1271                    <a
1272                        href="/posts/?tag=tools"
1273                        class="inline-block bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 text-xs px-2 py-1 rounded-full hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors"
1274                    >
1275                        tools
1276                    </a>
1277                    
1278                    <a
1279                        href="/posts/?tag=cli"
1280                        class="inline-block bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 text-xs px-2 py-1 rounded-full hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors"
1281                    >
1282                        cli
1283                    </a>
1284                    
1285                    <a
1286                        href="/posts/?tag=datasets"
1287                        class="inline-block bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 text-xs px-2 py-1 rounded-full hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors"
1288                    >
1289                        datasets
1290                    </a>
1291                    
1292                </div>
1293                
1294            </div>
1295
1296            <div class="prose prose-lg dark:prose-invert max-w-none my-6">
1297                <p><a href="https://www.youtube.com/watch?v=2CjA-03yK8I">git-scraping</a> is awesome. It let's you re-use CI as a pattern to scrape data from the internet and store it publicly in your repository. Here's an example:</p>
1298<pre><code class="language-yml">name: scrape
1299on:
1300  schedule:
1301    - cron: &#x27;0 * * * *&#x27;   # hourly
1302  workflow_dispatch:
1303permissions:
1304  contents: write         # needed to push the data back
1305jobs:
1306  scrape:
1307    runs-on: ubuntu-latest
1308    steps:
1309      - uses: actions/checkout@v4
1310      - run: curl -s https://example.com/data.json &gt; data.json
1311      - run: |
1312          git config user.name &quot;Automated&quot;
1313          git config user.email &quot;[email protected]&quot;
1314          git add -A
1315          git commit -m &quot;Latest data: $(date -u)&quot; || exit 0
1316          git push
1317</code></pre>
1318<p>The best part of this is that you only need Git. Everyone already has access to Git. And it also means that everyone can have access to important analytics. No need to add another tool! Not to mention the fact that integrating with a GitHub account is dead simple c
1318ompared to integrating with any analytics tool.</p>
1319<h2>permissions</h2>
1320<p>One small tip if you do this at a company: don't forget about the permission flag. At some point security will, rightfully, be hardened. And you typically don't want CI jobs to make extra merge requests to your repo. So you'll need to do this:</p>
1321<pre><code class="language-yml">name: scrape
1322on:
1323  schedule:
1324    - cron: &#x27;0 * * * *&#x27;
1325  workflow_dispatch:
1326permissions:
1327  contents: write         # this part!
1328jobs:
1329  scrape:
1330    runs-on: ubuntu-latest
1331    steps:
1332      - uses: actions/checkout@v4
1333      - run: curl -s https://example.com/data.json &gt; data.json
1334      - run: |
1335          git config user.name &quot;Automated&quot;
1336          git config user.email &quot;[email protected]&quot;
1337          git add -A
1338          git commit -m &quot;Latest data: $(date -u)&quot; || exit 0
1339          git push
1340</code></pre>
1341<p>Without that permission, jobs may fail in the future.</p>
1342
1343            </div>
1344        </article>
1345        
1346        <!-- Featured article (fully rendered) -->
1347        <article
1348            class="mb-10 pb-8 border-b border-gray-300 dark:border-gray-700"
1349        >
1350            <h2
1351                class="text-3xl font-bold text-gray-900 dark:text-gray-100 mb-3"
1352            >
1353                <a
1354                    href="/posts/worthy-of-study"
1355                    class="hover:text-primary dark:hover:text-dark-primary transition-colors"
1356                >
1357                    worthy of study
1358                </a>
1359            </h2>
1360            <div class="flex items-center space-x-4 mb-4">
1361                <div class="text-gray-600 dark:text-gray-400 text-sm">
1362                    2026-07-17
1363                </div>
1364                
1365                <div class="flex flex-wrap gap-2">
1366                    
1367                    <a
1368                        href="/posts/?tag=javascript"
1369                        class="inline-block bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 text-xs px-2 py-1 rounded-full hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors"
1370                    >
1371                        javascript
1372                    </a>
1373                    
1374                    <a
1375                        href="/posts/?tag=web"
1376                        class="inline-block bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 text-xs px-2 py-1 rounded-full hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors"
1377                    >
1378                        web
1379                    </a>
1380                    
1381                    <a
1382                        href="/posts/?tag=llm"
1383                        class="inline-block bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 text-xs px-2 py-1 rounded-full hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors"
1384                    >
1385                        llm
1386                    </a>
1387                    
1388                    <a
1389                        href="/posts/?tag=maths"
1390                        class="inline-block bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 text-xs px-2 py-1 rounded-full hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors"
1391                    >
1392                        maths
1393                    </a>
1394                    
1395                </div>
1396                
1397            </div>
1398
1399            <div class="prose prose-lg dark:prose-invert max-w-none my-6">
1400                <p>I was working with an agent to produce <a href="https://github.com/koaning/wigglystuff">a new widget</a>. I gave it a research task, gave it a direction, and when it came back I immediately stopped prompting it further to investigate what it did. The artifact that was generated 
1400wasn't merely functional, it did something that I did not understand and was therefore <strong>worthy of study</strong>.</p>
1401<p>It's an important moment that people show not ignore. But to help explain the situation, try playing with the widget below:</p>
1402<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/katex.min.css">
1403<style>
1404  body {
1405    font: 16px/1.5 system-ui, sans-serif;
1406    max-width: 720px;
1407    margin: 2rem auto;
1408    padding: 0 1rem;
1409    color: #1a1a1a;
1410  }
1411  h1 { font-size: 1.25rem; }
1412  .formula { font-size: 1.6rem; user-select: none; touch-action: none; }
1413  /* KaTeX stacks fraction rows in vlist/mord spans that intercept clicks.
1414     Punch them through, then re-enable only the draggable tagged spans —
1415     this is the single CSS rule that makes \frac-internal params draggable. */
1416  .formula .katex-html * { pointer-events: none; }
1417  .formula .katex-html [data-tangle-param] { pointer-events: auto; }
1418  /* Color each tagged span by name via CSS custom properties. */
1419  .katex-html [data-tangle-param] {
1420    padding: 0 .15em;
1421    border-radius: .25em;
1422    cursor: ew-resize;
1423    background: var(--bg, transparent);
1424    color: var(--fg, inherit);
1425    transition: outline .05s;
1426  }
1427  [data-tangle-param="x"]     { --bg: #ffe08a; --fg: #5a4500; }
1428  [data-tangle-param="mu"]    { --bg: #b6f0c8; --fg: #094025; }
1429  [data-tangle-param="sigma"] { --bg: #c8d8ff; --fg: #16265e; }
1430  [data-tangle-param].is-active { outline: 2px solid currentColor; }
1431  .readout {
1432    margin-top: 1rem;
1433    font-family: ui-monospace, SFMono-Regular, monospace;
1434    background: #f4f4f4;
1435    padding: .5rem .75rem;
1436    border-radius: .4em;
1437  }
1438  .hint { color: #666; font-size: .9rem; }
1439</style>
1440</head>
1441<body>
1442<p class="hint">Drag left/right to scrub a value. Works for touch, mouse, and pen.</p>
1443<div class="formula" id="host"></div>
1444<pre class="readout" id="readout">f(0) = ?</pre>
1445<script type="module">
1446  import katex from "https://cdn.jsdelivr.net/npm/[email protected]/dist/katex.mjs";
1447
1448  const params = {
1449    x:     { value: 0,   step: 0.1, min: -5,   max: 5,  pixels_per_step: 8  },
1450    mu:    { value: 0,   step: 0.1, min: -3,   max: 3,  pixels_per_step: 12 },
1451    sigma: { value: 1,   step: 0.1, min: 0.2,  max: 4,  pixels_per_step: 12 },
1452  };
1453
1454  const fmt = (v, d = 2) => (Math.abs(v) < 1e-3 ? 0 : v).toFixed(d);
1455
1456  function pdf(x, mu, sigma) {
1457    const s = Math.max(sigma, 1e-6);
1458    return Math.exp(-((x - mu) ** 2) / (2 * s * s)) / (s * Math.sqrt(2 * Math.PI));
1459  }
1460
1461  // each \tangle{name} becomes a KaTeX \htmlData node - the only bridge
1462  // from a LaTeX symbol to an addressable HTML span with a data-* attr.
1463  const LATEX = String.raw`
1464    f(x) = \frac{1}{\tangle{sigma}\sqrt{2\pi}}\,e^{-\frac{(\tangle{x}-\tangle{mu})^{2}}{2\tangle{sigma}^{2}}}
1465  `;
1466
1467  function expandTemplate() {
1468    return LATEX.replace(/\\tangle\{([A-Za-z][A-Za-z0-9_-]*)\}/g, (_, name) =>
1469      `\\htmlData{tangle-param=${name}}{${fmt(params[name].value)}}`
1470    );
1471  }
1472
1473  let drag = null;
1474
1475  function render() {
1476    katex.render(expandTemplate(), host, {
1477      displayMode: true,
1478      throwOnError: true,
1479      trust: (ctx) => ctx.command === "\\htmlData",
1480      strict: (err) => (err === "htmlExtension" ? "ignore" : "warn"),
1481    });
1482    for (const name of Object.keys(params)) {
1483      for (const el of host.querySelectorAll(`[data-tangle-param="${name}"]`)) {
1484        el.classList.toggle("is-active", drag && drag.name === name);
1485      }
1486    }
1487    readout.textContent =
1488      `f(${fmt(params.x.value)}) = ${fmt(pdf(params.x.value, params.mu.value, params.sigma.value), 4)}`;
1489  }
1490
1491  // Pointer Events delegated from the host. KaTeX rebuilds the spans on every
1492  // render, so we never attach per-node listeners; the drag state lives in
1493  // a closure and the move/up handlers live on document.
1494  host.addEventListener("pointerdown", (e) => {
1495    const t = e.target.closest("[data-tangle-param]");
1496    if (!t || (e.pointerType === "mouse" && e.button !== 0)) return;
1497    e.preventDefault();
1498    const name = t.dataset.tangleParam;
1499    drag = {
1500      name,
1501      pointerId: e.pointerId,
1502      startX: e.clientX,
1503      startValue: params[name].value,
1504      spec: params[name],
1505      moved: false,
1506    };
1507    render();
1508  });
1509
1510  document.addEventListener("pointermove", (e) => {
1511    if (!drag || e.pointerId !== drag.pointerId) return;
1512    const dx = e.clientX - drag.startX;
1513    if (Math.abs(dx) >= 3) drag.moved = true;
1514    if (!drag.moved) return;
1515    e.preventDefault();
1516    const steps = Math.trunc(dx / drag.spec.pixels_per_step);
1517    const next = Math.max(
1518      drag.spec.min,
1519      Math.min(drag.spec.max, drag.startValue + steps * drag.spec.step),
1520    );
1521    const rounded = Number(next.toFixed(2));
1522    if (params[drag.name].value === rounded) return;
1523    params[drag.name].value = rounded;
1524    render();
1525  });
1526
1527  document.addEventListener("pointerup", (e) => {
1528    if (!drag || e.pointerId !== drag.pointerId) return;
1529    drag = null;
1530    render();
1531  });
1532
1533  document.addEventListener("pointercancel", (e) => {
1534    if (!drag || e.pointerId !== drag.pointerId) return;
1535    drag = null;
1536    render();
1537  });
1538
1539  render();
1540</script>
1540
1541<p>That's interesting, right? It feels like you're able to interact with Latex on the web. But how does this work? <a href="https://www.mathjax.org/">Mathjax</a> puts the entire rendered latex in a single <code>&lt;svg&gt;</code> element. So that's impossible to bind events on that, even in hindsight.</p>
1542<p>So how does the widget work?</p>
1543<h2>Details of Katex</h2>
1544<p>The trick is to use a small, nearly hidden, feature of <a href="https://katex.org/">katex</a> instead.</p>
1545<p>$$ f(x) = \frac{1}{\sigma\sqrt{2\pi}} e^{\frac{(x - \mu)^2}{2\sigma}} $$</p>
1546<p>See that math, this is the latex that generated it.</p>
1547<pre><code class="language-">$$ f(x) = \frac{1}{\sigma\sqrt{2\pi}} e^{\frac{(x - \mu)^2}{2\sigma}} $$
1548</code></pre>
1549<p>But in Katex you're able to add more information! You could do something like below around $e$.</p>
1550<pre><code class="language-">\htmlData{param=mu}{e}
1551</code></pre>
1552<p>This isn't standard latex, and it requires adding some extra katex settings.</p>
1553<pre><code class="language-js">katex.render(expandTemplate(), host, {
1554  displayMode: true,
1555  throwOnError: true,
1556  trust: (ctx) =&gt; ctx.command === &quot;\\htmlData&quot;,
1557  strict: (err) =&gt; (err === &quot;htmlExtension&quot; ? &quot;ignore&quot; : &quot;warn&quot;),
1558});
1559</code></pre>
1560<p>Now, if you look at how $e$ is actually rendered, you'll notice this:</p>
1561<pre><code class="language-html">&lt;span class=&quot;enclosing mtight&quot; data-param=&quot;mu&quot;&gt;
1562    &lt;span class=&quot;mord mtight&quot;&gt;e&lt;/span&gt;
1563&lt;/span&gt;
1564</code></pre>
1565<p>See that? Two things:</p>
1566<ol>
1567<li>Katex renders every element in it's own <code>span</code> instead of everything in a single <code>svg</code></li>
1568<li>On top of that katex also provides a mechanism to add data params to the span object. And <em>that</em> let's you bind events!</li>
1569</ol>
1570<h2>Meta observation</h2>
1571<p>To be clear, I used an agent to build this widget. But while it was clanking I took a moment to step back and to recognize that the generated code <strong>was worthy of study</strong>. It was a moment <strong>worthy of pause</strong>.</p>
1572<p>Not because the coding quality was amazing, but because it reveiled a pattern that I was completely unaware of. And <strong>understanding that pattern</strong> is going to help me longer term.</p>
1573<p>It's allmost like the coding agents makes you a wizard but you still need to understand what spells you're able to cast. And after doing a small deep dive, by recognizing that something was worthy of study, it feels like I gained a new spell that I can cast in the future. You can do a lot of cool things with maths/animations using this API!</p>
1574
1575            </div>
1576        </article>
1577        
1578        <!-- Featured article (fully rendered) -->
1579        <article
1580            class="mb-10 pb-8 border-b border-gray-300 dark:border-gray-700"
1581        >
1582            <h2
1583                class="text-3xl font-bold text-gray-900 dark:text-gray-100 mb-3"
1584            >
1585                <a
1586                    href="/posts/vonoroi-keycaps"
1587                    class="hover:text-primary dark:hover:text-dark-primary transition-colors"
1588                >
1589                    vonoroi keycaps
1590                </a>
1591            </h2>
1592            <div class="flex items-center space-x-4 mb-4">
1593                <div class="text-gray-600 dark:text-gray-400 text-sm">
1594                    2026-07-04
1595                </div>
1596                
1597                <div class="flex flex-wrap gap-2">
1598                    
1599                    <a
1600                        href="/posts/?tag=keyboard"
1601                        class="inline-block bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 text-xs px-2 py-1 rounded-full hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors"
1602                    >
1603                        keyboard
1604                    </a>
1605                    
1606                    <a
1607                        href="/posts/?tag=design"
1608                        class="inline-block bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 text-xs px-2 py-1 rounded-full hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors"
1609                    >
1610                        design
1611                    </a>
1612                    
1613                    <a
1614                        href="/posts/?tag=fun"
1615                        class="inline-block bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 text-xs px-2 py-1 rounded-full hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors"
1616                    >
1617                        fun
1618                    </a>
1619                    
1620                </div>
1621                
1622            </div>
1623
1624            <div class="prose prose-lg dark:prose-invert max-w-none my-6">
1625                <p>Found a little gem on <a href="https://www.reddit.com/r/keycapdesigners/comments/1uh989j/voronoi_on_wooting/">Reddit</a>.</p>
1626<figure>
1627    <img src="/posts/vonoroi-keycaps/9b827b15-da5a-41bb-8462-a22acdfa046b.
1627jpeg" alt="Uploaded image" />
1628    <figcaption>I mean, I don't know if this is fun to actually type on, and the spacebar doesn't actually follow a vonoroi ... but still!</figcaption>
1629</figure>
1630
1631            </div>
1632        </article>
1633        
1634        <!-- Featured article (fully rendered) -->
1635        <article
1636            class="mb-10 pb-8 border-b border-gray-300 dark:border-gray-700"
1637        >
1638            <h2
1639                class="text-3xl font-bold text-gray-900 dark:text-gray-100 mb-3"
1640            >
1641                <a
1642                    href="/posts/mossballs"
1643                    class="hover:text-primary dark:hover:text-dark-primary transition-colors"
1644                >
1645                    mossballs
1646                </a>
1647            </h2>
1648            <div class="flex items-center space-x-4 mb-4">
1649                <div class="text-gray-600 dark:text-gray-400 text-sm">
1650                    2026-07-02
1651                </div>
1652                
1653                <div class="flex flex-wrap gap-2">
1654                    
1655                    <a
1656                        href="/posts/?tag=games"
1657                        class="inline-block bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 text-xs px-2 py-1 rounded-full hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors"
1658                    >
1659                        games
1660                    </a>
1661                    
1662                </div>
1663                
1664            </div>
1665
1666            <div class="prose prose-lg dark:prose-invert max-w-none my-6">
1667                <p>I been toying with a tiny game made with <a href="https://www.lexaloffle.com/pico-8.php">PICO-8</a>. You can play it below.</p>
1668<iframe src="/posts/mossballs/mossballs.html" style="border: none; width: 100%; max-width: 560px; aspect-ratio: 1 / 1; height: auto; display: block; margin: 0 auto;"></iframe>
1669<p>Few fun facts about it.</p>
1670<ul>
1671<li>pico8 is a png format that can contain constrained games (up to 65,536 characters, and up to 8,192 tokens). You are capped at how big the game can be and there's an awesome community of folks making mini-games with it.</li>
1672<li>You can against an AI in this game. Alpha-zero kinds of algorithms will never fit on the small cartridge so I needed a heuristic. I ended up running an auto-research loop for it but it sort-of failed without a lot of manual steering. Part of the story here is that auto-researh excells when the signal isn't noisy. But game simulations are just that.</li>
1673</ul>
1674<p>You can also download the cartridge if you want to play on your own device locally.</p>
1675<p style="text-align: center;">
1676<a href="/posts/mossballs/mossballs.p8.png" download style="display: inline-block; margin-top: 1rem; padding: 0.5rem 1rem; border: 1px solid currentColor; border-radius: 4px; text-decoration: none;">Download the cartridge</a>
1677</p>
1678
1679            </div>
1680        </article>
1681        
1682        <!-- Featured article (fully rendered) -->
1683        <article
1684            class="mb-10 pb-8 border-b border-gray-300 dark:border-gray-700"
1685        >
1686            <h2
1687                class="text-3xl font-bold text-gray-900 dark:text-gray-100 mb-3"
1688            >
1689                <a
1690                    href="/posts/lucide-icons"
1691                    class="hover:text-primary dark:hover:text-dark-primary transition-colors"
1692                >
1693                    lucide icons
1694                </a>
1695            </h2>
1696            <div class="flex items-center space-x-4 mb-4">
1697                <div class="text-gray-600 dark:text-gray-400 text-sm">
1698                    2026-06-30
1699                </div>
1700                
1701                <div class="flex flex-wrap gap-2">
1702                    
1703                    <a
1704                        href="/posts/?tag=design"
1705                        class="inline-block bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 text-xs px-2 py-1 rounded-full hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors"
1706                    >
1707                        design
1708                    </a>
1709                    
1710                    <a
1711                        href="/posts/?tag=resources"
1712                        class="inline-block bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 text-xs px-2 py-1 rounded-full hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors"
1713                    >
1714                        resources
1715                    </a>
1716                    
1717                </div>
1718                
1719            </div>
1720
1721            <div class="prose prose-lg dark:prose-invert max-w-none my-6">
1722                <p><a href="https://lucide.dev/icons/">Lucide</a> is a fantastic resource for icons. Just wanted to make a note for myself because I keep forgetting the name.</p>
1723
1724            </div>
1725        </article>
1726        
1727        <!-- Featured article (fully rendered) -->
1728        <article
1729            class="mb-10 pb-8 border-b border-gray-300 dark:border-gray-700"
1730        >
1731            <h2
1732                class="text-3xl font-bold text-gray-900 dark:text-gray-100 mb-3"
1733            >
1734                <a
1735                    href="/posts/google-trends-for-hacker-news"
1736                    class="hover:text-primary dark:hover:text-dark-primary transition-colors"
1737                >
1738                    google trends for hacker news
1739                </a>
1740            </h2>
1741            <div class="flex items-center space-x-4 mb-4">
1742                <div class="text-gray-600 dark:text-gray-400 text-sm">
1743                    2026-06-29
1744                </div>
1745                
1746                <div class="flex flex-wrap gap-2">
1747                    
1748                    <a
1749                        href="/posts/?tag=tools"
1750                        class="inline-block bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 text-xs px-2 py-1 rounded-full hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors"
1751                    >
1752                        tools
1753                    </a>
1754                    
1755                </div>
1756                
1757            </div>
1758
1759            <div class="prose prose-lg dark:prose-invert max-w-none my-6">
1760                <p>Someone <a href="https://news.ycombinator.com/item?id=48673671">made Google Trends for Hacker News</a> by indexing 18 years of comments. The tool lives at <a href="https://hackernewstrends.com">hackernewstrends.com</a> and lets you overlay how often terms show up over time. Mostly bookmarking this for myself, but it's a fun way to confirm hunches about when a topic actually took off.</p>
1761<figure>
1762    <img src="/posts/google-trends-for-hacker-news/00fe9d5c-20be-467f-aa29-5a6752917bfa.webp" alt="hackernewstrends.com comparing how often openai and anthropic a
1762ppear in Hacker News comments over time" />
1763    <figcaption>openai vs. anthropic, as told by HN comments.</figcaption>
1764</figure>
1765
1766            </div>
1767        </article>
1768        
1769        <!-- Featured article (fully rendered) -->
1770        <article
1771            class="mb-10 pb-8 border-b border-gray-300 dark:border-gray-700"
1772        >
1773            <h2
1774                class="text-3xl font-bold text-gray-900 dark:text-gray-100 mb-3"
1775            >
1776                <a
1777                    href="/posts/fixed-your-headline"
1778                    class="hover:text-primary dark:hover:text-dark-primary transition-colors"
1779                >
1780                    fixed your headline
1781                </a>
1782            </h2>
1783            <div class="flex items-center space-x-4 mb-4">
1784                <div class="text-gray-600 dark:text-gray-400 text-sm">
1785                    2026-06-29
1786                </div>
1787                
1788                <div class="flex flex-wrap gap-2">
1789                    
1790                    <a
1791                        href="/posts/?tag=fun"
1792                        class="inline-block bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 text-xs px-2 py-1 rounded-full hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors"
1793                    >
1794                        fun
1795                    </a>
1796                    
1797                    <a
1798                        href="/posts/?tag=maths"
1799                        class="inline-block bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 text-xs px-2 py-1 rounded-full hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors"
1800                    >
1801                        maths
1802                    </a>
1803                    
1804                </div>
1805                
1806            </div>
1807
1808            <div class="prose prose-lg dark:prose-invert max-w-none my-6">
1809                <p>A Billboard chart headline made the rounds: "Olivia Rodrigo becomes the first woman in history to debut her first ten top 10 Hot 100 hits in the top 10."</p>
1810<p><a href="https://bsky.app/profile/chordbug.bsky.social/post/3mp24yja3b223">Lynn</a> quote-posted it a theorem that certainly made me giggle.</p>
1811<blockquote>
1812<p>A hit song on the Billboard 100 charts is termed $k$-good if it is <em>ever</em> in a weekly top-$k$, and $k$-immediate if it <em>debuts</em> in the top-$k$. An artist is $(n,k)$-Olivian if their $n$ first $k$-good hits are all also $k$-immediate.</p>
1813<p><strong>Headline 1.</strong> Olivia Rodrigo is the first $(10,10)$-Olivian woman.</p>
1814</blockquote>
1815
1816            </div>
1817        </article>
1818        
1819        <!-- Featured article (fully rendered) -->
1820        <article
1821            class="mb-10 pb-8 border-b border-gray-300 dark:border-gray-700"
1822        >
1823            <h2
1824                class="text-3xl font-bold text-gray-900 dark:text-gray-100 mb-3"
1825            >
1826                <a
1827                    href="/posts/gmk-mtnu-granite"
1828                    class="hover:text-primary dark:hover:text-dark-primary transition-colors"
1829                >
1830                    GMK MTNU Granite
1831                </a>
1832            </h2>
1833            <div class="flex items-center space-x-4 mb-4">
1834                <div class="text-gray-600 dark:text-gray-400 text-sm">
1835                    2026-06-29
1836                </div>
1837                
1838                <div class="flex flex-wrap gap-2">
1839                    
1840                    <a
1841                        href="/posts/?tag=keyboard"
1842                        class="inline-block bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 text-xs px-2 py-1 rounded-full hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors"
1843                    >
1844                        keyboard
1845                    </a>
1846                    
1847                </div>
1848                
1849            </div>
1850
1851            <div class="prose prose-lg dark:prose-invert max-w-none my-6">
1852                <p>Just some keycap inspiration. The <a href="https://oblotzky.industries/products/gmk-mtnu-granite">GMK MTNU Granite</a> set.</p>
1853<figure>
1854<img src="/posts/gmk-mtnu-granite/granite-1.webp" alt="GMK MTNU Granite keycaps, top-down view" style="margin: 0 auto; display: block;">
1855</figure>
1856<figure>
1857<img src="/posts/gmk-mtnu-granite/granite-2.webp" alt="GMK MTNU Granite keycaps, angled view" style="margin: 0 auto; display: block;">
1858</figure>
1859
1860            </div>
1861        </article>
1862        
1863        <!-- Featured article (fully rendered) -->
1864        <article
1865            class="mb-10 pb-8 border-b border-gray-300 dark:border-gray-700"
1866        >
1867            <h2
1868                class="text-3xl font-bold text-gray-900 dark:text-gray-100 mb-3"
1869            >
1870                <a
1871                    href="/posts/graduate-before-you-abandon"
1872                    class="hover:text-primary dark:hover:text-dark-primary transition-colors"
1873                >
1874                    graduate before you abandon
1875                </a>
1876            </h2>
1877            <div class="flex items-center space-x-4 mb-4">
1878                <div class="text-gray-600 dark:text-gray-400 text-sm">
1879                    2026-05-15
1880                </div>
1881                
1882                <div class="flex flex-wrap gap-2">
1883                    
1884                    <a
1885                        href="/posts/?tag=python"
1886                        class="inline-block bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 text-xs px-2 py-1 rounded-full hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors"
1887                    >
1888                        python
1889                    </a>
1890                    
1891                    <a
1892                        href="/posts/?tag=tools"
1893                        class="inline-block bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 text-xs px-2 py-1 rounded-full hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors"
1894                    >
1895                        tools
1896                    </a>
1897                    
1898                    <a
1899                        href="/posts/?tag=marimo"
1900                        class="inline-block bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 text-xs px-2 py-1 rounded-full hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors"
1901                    >
1902                        marimo
1903                    </a>
1904                    
1905                </div>
1906                
1907            </div>
1908
1909            <div class="prose prose-lg dark:prose-invert max-w-none my-6">
1910                <p>I made this YouTube video the other day about how all code, eventually, is abandoned. It doesn't die, because it still exists, but it simply won't have people working on it anymore.</p>
1911<p>But there's one thing that can prevent it, the code can graduate! It can be that you had an idea so useful that another project, a much larger one, will grab it to run with it.</p>
1912<p>It's happened twice to me. A few widgets from <a href="https://github.com/koaning/wigglystuff">wigglystuff</a> just got ported to <a href="https://marimo.io/gallery">marimo</a> just like how a few <a href="https://github.com/koaning/scikit-lego">scikit-lego</a> components (or at least their spirit) have found their way into <a href="https://scikit-learn.org/stable/">scikit-learn</a>.</p>
1913<p>I wish it could happen to more people because this kind of pollination across projects is exactly what keeps the creativity alive.</p>
1914<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/xwuQTYnZo7c?si=grMnimux0X2aUnUY" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture;
1914 web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
1915
1916            </div>
1917        </article>
1918        
1919    
1920</div>
1921
1922        </main>
1923
1924        <footer class="border-t border-gray-200 dark:border-gray-700 py-8 mt-8">
1925            <div
1926                class="container mx-auto px-4 sm:px-6 text-center text-gray-600 dark:text-gray-400"
1927            >
1928                <p>&copy; 2026 Vincent D. Warmerdam. All rights reserved.</p>
1929            </div>
1930        </footer>
1931
1932        <!-- Search Modal -->
1933        <div
1934            id="searchModal"
1935            class="fixed inset-0 bg-black bg-opacity-50 hidden z-50 flex items-start justify-center pt-20"
1936        >
1937            <div
1938                class="bg-white dark:bg-gray-900 rounded-lg shadow-xl w-full max-w-2xl mx-4"
1939            >
1940                <!-- Search Input -->
1941                <div class="relative">
1942                    <div
1943                        class="absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none"
1944                    >
1945                        <svg
1946                            class="h-5 w-5 text-gray-400"
1947                            fill="none"
1948                            stroke="currentColor"
1949                            viewBox="0 0 24 24"
1950                        >
1951                            <path
1952                                stroke-linecap="round"
1953                                stroke-linejoin="round"
1954                                stroke-width="2"
1955                                d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
1956                            ></path>
1957                        </svg>
1958                    </div>
1959                    <input
1960                        type="text"
1961                        id="modalSearchInput"
1962                        placeholder="Search posts..."
1963                        class="w-full pl-12 pr-4 py-4 text-lg border-0 border-b border-gray-200 dark:border-gray-700 bg-transparent text-gray-900 dark:text-gray-100 placeholder-gray-500 dark:placeholder-gray-400 focus:outline-none focus:border-gray-400 rounded-t-lg"
1964                    />
1965                    <div
1966                        class="absolute inset-y-0 right-0 pr-4 flex items-center"
1967                    >
1968                        <span
1969                            class="text-xs text-gray-400 bg-gray-100 dark:bg-gray-800 px-2 py-1 rounded border"
1970                            >ESC</span
1971                        >
1972                    </div>
1973                </div>
1974
1975                <!-- Results -->
1976                <div id="modalResults" class="max-h-96 overflow-y-auto">
1977                    <div id="modalNoResults" class="hidden p-8 text-center">
1978                        <div class="text-gray-500 dark:text-gray-400">
1979                            <p class="text-sm">
1980                                No posts found. Try a different search term.
1981                            </p>
1982                        </div>
1983                    </div>
1984
1985                    <div
1986                        id="modalResultsList"
1987                        class="divide-y divide-gray-200 dark:divide-gray-700"
1988                    >
1989                        <!-- Results will be populated here -->
1990                    </div>
1991                </div>
1992
1993                <!-- Footer -->
1994                <div
1995                    class="px-4 py-3 bg-gray-50 dark:bg-gray-800 rounded-b-lg border-t border-gray-200 dark:border-gray-700"
1996                >
1997                    <div
1998                        class="flex justify-between text-xs text-gray-500 dark:text-gray-400"
1999                    >
2000                        <span>↑↓ to navigate</span>
2001                        <span>↵ to select</span>
2002                        <span>ESC to close</span>
2003                    </div>
2004                </div>
2005            </div>
2006        </div>
2007
2008        
2008<script>
2009            document.addEventListener("DOMContentLoaded", function () {
2010                const modal = document.getElementById("searchModal");
2011                const modalInput = document.getElementById("modalSearchInput");
2012                const modalResults = document.getElementById("modalResults");
2013                const modalNoResults =
2014                    document.getElementById("modalNoResults");
2015                const modalResultsList =
2016                    document.getElementById("modalResultsList");
2017
2018                let modalSelectedIndex = -1;
2019                let modalVisibleItems = [];
2020                let allPosts = [];
2021
2022                // Available commands
2023                const commands = [
2024                    {
2025                        title: "Toggle Dark Mode",
2026                        description: "Switch between light and dark themes",
2027                        action: () => {
2028                            const currentTheme =
2029                                localStorage.getItem("theme") || "light";
2030                            setTheme(
2031                                currentTheme === "light" ? "dark" : "light",
2032                            );
2033                        },
2034                        icon: "🌙",
2035                    },
2036                    {
2037                        title: "Toggle Light Mode",
2038                        description: "Switch between light and dark themes",
2039                        action: () => {
2040                            const currentTheme =
2041                                localStorage.getItem("theme") || "light";
2042                            setTheme(
2043                                currentTheme === "dark" ? "light" : "dark",
2044                            );
2045                        },
2046                        icon: "☀️",
2047                    },
2048                ];
2049
2050                // Load posts data from the posts page
2051                function loadPostsData() {
2052                    if (allPosts.length > 0) return Promise.resolve();
2053
2054                    return fetch("/posts/")
2055                        .then((response) => response.text())
2056                        .then((html) => {
2057                            const parser = new DOMParser();
2058                            const doc = parser.parseFromString(
2059                                html,
2060                                "text/html",
2061                            );
2062                            const postItems =
2063                                doc.querySelectorAll(".post-item");
2064
2065                            allPosts = Array.from(postItems).map((item) => ({
2066                                title: item.dataset.title,
2067                                description: item.dataset.description || "",
2068                                tags: item.dataset.tags
2069                                    ? item.dataset.tags
2070                                          .split(",")
2071                                          .filter((t) => t.trim())
2072                                    : [],
2073                                year: item.dataset.year,
2074                                url: item.dataset.url,
2075                                displayTitle: item
2076                                    .querySelector("h3")
2077                                    .textContent.trim(),
2078                                displayDate: item
2079                                    .querySelector("time")
2080                                    .textContent.trim(),
2081                            }));
2082                        })
2083                        .catch((err) => {
2084                            console.error("Failed to load posts data:", err);
2085                        });
2086                }
2087
2088                function updateModalSelection() {
2089                    modalVisibleItems.forEach((item, index) => {
2090                        const element = modalResultsList.children[index];
2091                        if (element) {
2092                            if (index === modalSelectedIndex) {
2093                                element.classList.add(
2094                                    "bg-gray-100",
2095                                    "dark:bg-gray-800",
2096                                );
2097                            } else {
2098                                element.classList.remove(
2099                                    "bg-gray-100",
2100                                    "dark:bg-gray-800",
2101                                );
2102                            }
2103                        }
2104                    });
2105                }
2106
2107                function searchPosts(query) {
2108                    if (!query.trim()) {
2109                        modalResultsList.innerHTML = "";
2110                        modalNoResults.classList.add("hidden");
2111                        modalVisibleItems = [];
2112                        modalSelectedIndex = -1;
2113                        return;
2114                    }
2115
2116                    const searchTerm = query.toLowerCase();
2117
2118                    // Search commands
2119                    const matchingCommands = commands.filter(
2120                        (cmd) =>
2121                            cmd.title.toLowerCase().includes(searchTerm) ||
2122                            cmd.description
2123                                .toLowerCase()
2124                                .includes(searchTerm) ||
2125                            searchTerm.includes("dark") ||
2126                            searchTerm.includes("light") ||
2127                            searchTerm.includes("theme"),
2128                    );
2129
2130                    // Search posts
2131                    const matchingPosts = allPosts.filter(
2132                        (post) =>
2133                            post.title.includes(searchTerm) ||
2134                            post.description.includes(searchTerm),
2135                    );
2136
2137                    modalVisibleItems = [...matchingCommands, ...matchingPosts];
2138                    modalSelectedIndex = -1;
2139
2140                    if (modalVisibleItems.length === 0) {
2141                        modalResultsList.innerHTML = "";
2142                        modalNoResults.classList.remove("hidden");
2143                    } else {
2144                        modalNoResults.classList.add("hidden");
2145
2146                        const commandsHtml = matchingCommands
2147                            .map(
2148                                (cmd, index) => `
2149                    <div class="p-4 cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors border-l-4 border-blue-500" data-command="${index}">
2150                        <div class="flex items-center gap-4">
2151                            <span class="text-lg w-8 flex-shrink-0">${cmd.icon}</span>
2152                            <div class="flex-1">
2153                                <h3 class="text-base font-semibold text-gray-900 dark:text-gray-100">
2154                                    ${cmd.title}
2155                                </h3>
2156                                <p class="text-sm text-gray-600 dark:text-gray-400">${cmd.description}</p>
2157                            </div>
2158                            <span class="text-xs text-gray-400 bg-gray-100 dark:bg-gray-800 px-2 py-1 rounded border">CMD</span>
2159                        </div>
2160                    </div>
2161                `,
2162                            )
2163                            .join("");
2164
2165                        const postsHtml = matchingPosts
2166                            .map(
2167                                (post, index) => `
2168                    <div class="p-4 cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors" data-url="${post.url}">
2169                        <div class="flex items-center gap-4">
2170                            <time class="text-sm font-mono text-gray-500 dark:text-gray-500 w-24 flex-shrink-0">
2171                                ${post.displayDate}
2172                            </time>
2173                            <h3 class="text-base font-semibold text-gray-900 dark:text-gray-100 flex-1">
2174                                ${post.displayTitle}
2175                            </h3>
2176                            ${
2177                                post.tags.length > 0
2178                                    ? `
2179                                <div class="flex gap-1 flex-shrink-0">
2180                                    ${post.tags
2181                                        .slice(0, 2)
2182                                        .map(
2183                                            (tag) => `
2184                                        <span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 border border-gray-200 dark:border-gray-600">
2185                                            ${tag}
2186                                        </span>
2187                                    `,
2188                                        )
2189                                        .join("")}
2190                                    ${post.tags.length > 2 ? `<span class="text-xs text-gray-400">+${post.tags.length - 2}</span>` : ""}
2191                                </div>
2192                            `
2193                                    : ""
2194                            }
2195                        </div>
2196                    </div>
2197                `,
2198                            )
2199                            .join("");
2200
2201                        modalResultsList.innerHTML = commandsHtml + postsHtml;
2202
2203                        // Add click handlers for commands
2204                        modalResultsList
2205                            .querySelectorAll("[data-command]")
2206                            .forEach((element, index) => {
2207                                element.addEventListener("click", () => {
2208                                    matchingCommands[index].action();
2209                                    modal.classList.add("hidden");
2210                                    modalInput.value = "";
2211                                    modalResultsList.innerHTML = "";
2212                                    modalSelectedIndex = -1;
2213                                });
2214                            });
2215
2216                        // Add click handlers for posts
2217                        modalResultsList
2218                            .querySelectorAll("[data-url]")
2219                            .forEach((element, index) => {
2220                                element.addEventListener("click", () => {
2221                                    window.location.href = element.dataset.url;
2222                                });
2223                            });
2224                    }
2225                }
2226
2227                // Keyboard shortcuts
2228                document.addEventListener("keydown", function (e) {
2229                    // Cmd+K or Ctrl+K to open
2230                    if ((e.metaKey || e.ctrlKey) && e.key === "k") {
2231                        e.preventDefault();
2232                        loadPostsData().then(() => {
2233                            modal.classList.remove("hidden");
2234                            modalInput.focus();
2235                        });
2236                    }
2237
2238                    // ESC to close
2239                    if (
2240                        e.key === "Escape" &&
2241                        !modal.classList.contains("hidden")
2242                    ) {
2243                        e.preventDefault();
2244                        modal.classList.add("hidden");
2245                        modalInput.value = "";
2246                        modalResultsList.innerHTML = "";
2247                        modalSelectedIndex = -1;
2248                    }
2249                });
2250
2251                // Modal input events
2252                modalInput.addEventListener("input", function () {
2253                    searchPosts(this.value);
2254                });
2255
2256                modalInput.addEventListener("keydown", function (e) {
2257                    if (e.key === "ArrowDown") {
2258                        e.preventDefault();
2259                        if (modalVisibleItems.length > 0) {
2260                            modalSelectedIndex = Math.min(
2261                                modalSelectedIndex + 1,
2262                                modalVisibleItems.length - 1,
2263                            );
2264                            updateModalSelection();
2265                        }
2266                    } else if (e.key === "ArrowUp") {
2267                        e.preventDefault();
2268                        if (modalVisibleItems.length > 0) {
2269                            modalSelectedIndex = Math.max(
2270                                modalSelectedIndex - 1,
2271                                0,
2272                            );
2273                            updateModalSelection();
2274                        }
2275                    } else if (e.key === "Enter") {
2276                        e.preventDefault();
2277                        if (
2278                            modalSelectedIndex >= 0 &&
2279                            modalSelectedIndex < modalVisibleItems.length
2280                        ) {
2281                            const selectedItem =
2282                                modalVisibleItems[modalSelectedIndex];
2283                            if (selectedItem.action) {
2284                                // It's a command
2285                                selectedItem.action();
2286                                modal.classList.add("hidden");
2287                                modalInput.value = "";
2288                                modalResultsList.innerHTML = "";
2289                                modalSelectedIndex = -1;
2290                            } else {
2291                                // It's a post
2292                                window.location.href = selectedItem.url;
2293                            }
2294                        }
2295                    }
2296                });
2297
2298                // Click outside to close
2299                modal.addEventListener("click", function (e) {
2300                    if (e.target === modal) {
2301                        modal.classList.add("hidden");
2302                        modalInput.value = "";
2303                        modalResultsList.innerHTML = "";
2304                        modalSelectedIndex = -1;
2305                    }
2306                });
2307            });
2308        </script>
2308
2309    </body>
2310</html>

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.