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 "npm:@mdx-js/mdx@3"; 1221import { remarkMarimo } from "npm:@marimo-team/mdx-marimo/remark"; 1222import { h } from "npm:preact@10"; 1223import * as runtime from "npm:preact@10/jsx-runtime"; 1224import { renderToString } from "npm:preact-render-to-string@6"; 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"><link rel="stylesheet" href="https://esm.sh/@marimo-team/mdx-marimo/styles.css"> 1238<script type="module" src="https://esm.sh/@marimo-team/mdx-marimo/element/auto"></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: '0 * * * *' # 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 > data.json 1311 - run: | 1312 git config user.name "Automated" 1313 git config user.email "[email protected]" 1314 git add -A 1315 git commit -m "Latest data: $(date -u)" || 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: '0 * * * *' 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 > data.json 1334 - run: | 1335 git config user.name "Automated" 1336 git config user.email "[email protected]" 1337 git add -A 1338 git commit -m "Latest data: $(date -u)" || 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><svg></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) => ctx.command === "\\htmlData", 1557 strict: (err) => (err === "htmlExtension" ? "ignore" : "warn"), 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"><span class="enclosing mtight" data-param="mu"> 1562 <span class="mord mtight">e</span> 1563</span> 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>© 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.