---
title: Citry
url: https://citry.dev/
description: "Build checked, isolated web components in Python with server-rendered HTML and optional browser behavior."
---
<section class="landing-hero" markdown="1">
<div class="landing-hero__grid" markdown="1">
<div class="landing-hero__copy" markdown="1">
# The complete frontend stack for Python.

<p class="landing-hero__lede">
Citry is a free, open source <strong>HTML-first frontend framework</strong> for Python web
applications.
</p>
<p class="landing-hero__lede">
HTML, JS, CSS, event endpoints, server state, translations... one component holds all of it.

</p>
<p class="landing-hero__lede">
No NPM, no NodeJS build, no server-browser API
hassle.
</p>

<div class="landing-actions">
  <a class="landing-button landing-button--primary" href="/docs/">
    Start building
    <svg class="landing-button__arrow" viewBox="0 0 16 16" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 8h11M9 4l4 4-4 4"></path></svg>
  </a>
  <a class="landing-button" href="/playground/">Try the playground</a>
</div>

<div class="landing-install">
  <code>pip install citry</code>
  <button class="landing-copy" type="button" data-copy-install>Copy</button>
</div>




<div markdown="0"><div class="social-links landing-social"><a class="social-links__link" href="https://github.com/citry-dev/citry" aria-label="GitHub" target="_blank" rel="noopener"><svg viewBox="0 0 16 16" width="18" height="18" fill="currentColor" aria-hidden="true"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .271.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"></path></svg></a><a class="social-links__link" href="https://pypi.org/project/citry/" aria-label="PyPI" target="_blank" rel="noopener"><svg viewBox="0 0 448 512" width="18" height="18" fill="currentColor" aria-hidden="true"><path d="M439.8 200.5c-7.7-30.9-22.3-54.2-53.4-54.2h-40.1v47.4c0 36.8-31.2 67.8-66.8 67.8H172.7c-29.2 0-53.4 25-53.4 54.3v101.8c0 29 25.2 46 53.4 54.3 33.8 9.9 66.3 11.7 106.8 0 26.9-7.8 53.4-23.5 53.4-54.3v-40.7H226.2v-13.6h160.2c31.1 0 42.6-21.7 53.4-54.2 11.2-33.5 10.7-65.7 0-108.6M286.2 444.7a20.4 20.4 0 1 1 0-40.7 20.4 20.4 0 1 1 0 40.7M167.8 248.1h106.8c29.7 0 53.4-24.5 53.4-54.3V91.9c0-29-24.4-50.7-53.4-55.6-35.8-5.9-74.7-5.6-106.8.1-45.2 8-53.4 24.7-53.4 55.6v40.7h106.9v13.6h-147c-31.1 0-58.3 18.7-66.8 54.2-9.8 40.7-10.2 66.1 0 108.6 7.6 31.6 25.7 54.2 56.8 54.2H101v-48.8c0-35.3 30.5-66.4 66.8-66.4m-6.6-183.4a20.4 20.4 0 1 1 0 40.8 20.4 20.4 0 1 1 0-40.8"></path></svg></a><a class="social-links__link" href="https://discord.gg/NaQ8QPyHtD" aria-label="Discord" target="_blank" rel="noopener"><svg viewBox="0 0 576 512" width="18" height="18" fill="currentColor" aria-hidden="true"><path d="M492.5 69.8c-.2-.3-.4-.6-.8-.7-38.1-17.5-78.4-30-119.7-37.1-.4-.1-.8 0-1.1.1s-.6.4-.8.8c-5.5 9.9-10.5 20.2-14.9 30.6-44.6-6.8-89.9-6.8-134.4 0-4.5-10.5-9.5-20.7-15.1-30.6-.2-.3-.5-.6-.8-.8s-.7-.2-1.1-.2C162.5 39 122.2 51.5 84.1 69c-.3.1-.6.4-.8.7C7.1 183.5-13.8 294.6-3.6 404.2c0 .3.1.5.2.8s.3.4.5.6c44.4 32.9 94 58 146.8 74.2.4.1.8.1 1.1 0s.7-.4.9-.7c11.3-15.4 21.4-31.8 30-48.8.1-.2.2-.5.2-.8s0-.5-.1-.8-.2-.5-.4-.6-.4-.3-.7-.4c-15.8-6.1-31.2-13.4-45.9-21.9-.3-.2-.5-.4-.7-.6s-.3-.6-.3-.9 0-.6.2-.9.3-.5.6-.7c3.1-2.3 6.2-4.7 9.1-7.1.3-.2.6-.4.9-.4s.7 0 1 .1c96.2 43.9 200.4 43.9 295.5 0 .3-.1.7-.2 1-.2s.7.2.9.4c2.9 2.4 6 4.9 9.1 7.2.2.2.4.4.6.7s.2.6.2.9-.1.6-.3.9-.4.5-.6.6c-14.7 8.6-30 15.9-45.9 21.8-.2.1-.5.2-.7.4s-.3.4-.4.7-.1.5-.1.8.1.5.2.8c8.8 17 18.8 33.3 30 48.8.2.3.6.6.9.7s.8.1 1.1 0c52.9-16.2 102.6-41.3 147.1-74.2.2-.2.4-.4.5-.6s.2-.5.2-.8c12.3-126.8-20.5-236.9-86.9-334.5zm-302 267.7c-29 0-52.8-26.6-52.8-59.2s23.4-59.2 52.8-59.2c29.7 0 53.3 26.8 52.8 59.2 0 32.7-23.4 59.2-52.8 59.2m195.4 0c-29 0-52.8-26.6-52.8-59.2s23.4-59.2 52.8-59.2c29.7 0 53.3 26.8 52.8 59.2 0 32.7-23.2 59.2-52.8 59.2"></path></svg></a></div></div>




</div>

<div class="landing-code landing-hero__code" markdown="1">
<div class="landing-code__bar">
  <span class="landing-code__dot"></span>
  <span>product_card.py</span>
</div>

```citry
from citry import Component, SlotInput

class ProductCard(Component):
    class Kwargs:
        tags: list[str]
        likes: int = 0
        accent: str = "#175cd3"

    class Slots:
        body: SlotInput
        footer: SlotInput | None = None

    class State(Kwargs):
        pass

    class Events:
        def like(self, state: ProductCard.State):
            return ProductCard(
                tags=state.tags,
                likes=state.likes + 1,
            )

    def template_data(self, kwargs: Kwargs, slots: Slots):
        return {
            "likes": kwargs.likes,
            "tags": kwargs.tags,
        }

    def js_data(self, kwargs: Kwargs, slots: Slots):
        return {"likes": kwargs.likes}

    def css_data(self, kwargs: Kwargs, slots: Slots):
        return {"accent": kwargs.accent}

    template = """
      <article
        class="card"
        x-data="{ open: false }"
      >
        <c-slot name="body" />

        <c-for each="tag in tags">
          <c-Tag
            c-label="tag"
            $c-props="{ highlight: open }"
            @click="open = !open"
          />
        </c-for>
        <c-empty>
          <p>{{ tr("product-card-no-tags") }}</p>
        </c-empty>

        <button type="button" @c-click="like">
          Like <span x-text="likes">{{ likes }}</span>
        </button>

        <c-slot name="footer">
          No footer yet
        </c-slot>
      </article>
    """

    js = """
      $component(({ els, data }) => {
        const cardEl = els[0];
        animateLikes(cardEl, data.likes);
      });
    """

    css = """
      .card {
        border-left: 3px solid var(--accent);
      }

      .tag--active {
        color: var(--accent);
      }
    """

    messages = """
      product-card-no-tags = No tags yet.
    """

    class Dependencies:
        js = ["https://cdn.jsdelivr.net/npm/alpinejs@3.14.1/dist/cdn.min.js"]
        css = ["https://unpkg.com/normalize.css@8.0.1/normalize.css"]


html = str(ProductCard(
    tags=["new", "sale"],
    slots={"body": "Aurora Lamp"}
))

```

</div>
</div>
</section>

<!-- TODO: Add "Used by" belt -->

<section class="landing-sponsor-belt" aria-labelledby="landing-sponsor-title">
  <h2 id="landing-sponsor-title">Development sponsored by</h2>
  <ul>
    <li>
      <a href="https://www.ohne-makler.net/" target="_blank" rel="noopener">
        <img src="/static/img/sponsors/ohne-makler.svg" alt="Ohne Makler"/>
      </a>
    </li>
  </ul>
</section>

<!-- TODO: Overview video how it all works (incl VSCode, UI Lib, etc)
-->

<section class="landing-section landing-section--band" id="crash-course" markdown="1">


<figure class="youtube-video">
  <iframe class="youtube-video__frame" src="https://www.youtube-nocookie.com/embed/d3nPqvDdNB0" title="50-minute Citry and Django code-along" loading="lazy" referrerpolicy="strict-origin-when-cross-origin" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
  <figcaption class="youtube-video__caption">
    <a href="https://www.youtube.com/watch?v=d3nPqvDdNB0" target="_blank" rel="noopener">
      Watch the 50-minute Citry and Django code-along on YouTube
    </a>
  </figcaption>
</figure>


</section>

<section class="landing-section" id="proof" markdown="1">
## One file holds the entire component end-to-end.

<p class="landing-section__intro">
Inputs, slots, markup, translated messages, server events and state, browser behavior, and
styles, all live together. No context switching.
</p>




<div markdown="0"><p style="font-size: 0.85rem; margin-top: 3rem;"> Point at any marked line below to see what it does: </p><div class="landing-tour" data-landing-tour data-tour-source="ZnJvbSBjaXRyeSBpbXBvcnQgQ29tcG9uZW50LCBTbG90SW5wdXQKCmNsYXNzIFByb2R1Y3RDYXJkKENvbXBvbmVudCk6CiAgICBjbGFzcyBLd2FyZ3M6CiAgICAgICAgdGFnczogbGlzdFtzdHJdCiAgICAgICAgbGlrZXM6IGludCA9IDAKICAgICAgICBhY2NlbnQ6IHN0ciA9ICIjMTc1Y2QzIgoKICAgIGNsYXNzIFNsb3RzOgogICAgICAgIGJvZHk6IFNsb3RJbnB1dAogICAgICAgIGZvb3RlcjogU2xvdElucHV0IHwgTm9uZSA9IE5vbmUKCiAgICBjbGFzcyBTdGF0ZShLd2FyZ3MpOgogICAgICAgIHBhc3MKCiAgICBjbGFzcyBFdmVudHM6CiAgICAgICAgZGVmIGxpa2Uoc2VsZiwgc3RhdGU6IFByb2R1Y3RDYXJkLlN0YXRlKToKICAgICAgICAgICAgcmV0dXJuIFByb2R1Y3RDYXJkKAogICAgICAgICAgICAgICAgdGFncz1zdGF0ZS50YWdzLAogICAgICAgICAgICAgICAgbGlrZXM9c3RhdGUubGlrZXMgKyAxLAogICAgICAgICAgICApCgogICAgZGVmIHRlbXBsYXRlX2RhdGEoc2VsZiwga3dhcmdzOiBLd2FyZ3MsIHNsb3RzOiBTbG90cyk6CiAgICAgICAgcmV0dXJuIHsKICAgICAgICAgICAgImxpa2VzIjoga3dhcmdzLmxpa2VzLAogICAgICAgICAgICAidGFncyI6IGt3YXJncy50YWdzLAogICAgICAgIH0KCiAgICBkZWYganNfZGF0YShzZWxmLCBrd2FyZ3M6IEt3YXJncywgc2xvdHM6IFNsb3RzKToKICAgICAgICByZXR1cm4geyJsaWtlcyI6IGt3YXJncy5saWtlc30KCiAgICBkZWYgY3NzX2RhdGEoc2VsZiwga3dhcmdzOiBLd2FyZ3MsIHNsb3RzOiBTbG90cyk6CiAgICAgICAgcmV0dXJuIHsiYWNjZW50Ijoga3dhcmdzLmFjY2VudH0KCiAgICB0ZW1wbGF0ZSA9ICIiIgogICAgICA8YXJ0aWNsZQogICAgICAgIGNsYXNzPSJjYXJkIgogICAgICAgIHgtZGF0YT0ieyBvcGVuOiBmYWxzZSB9IgogICAgICA+CiAgICAgICAgPGMtc2xvdCBuYW1lPSJib2R5IiAvPgoKICAgICAgICA8Yy1mb3IgZWFjaD0idGFnIGluIHRhZ3MiPgogICAgICAgICAgPGMtVGFnCiAgICAgICAgICAgIGMtbGFiZWw9InRhZyIKICAgICAgICAgICAgJGMtcHJvcHM9InsgaGlnaGxpZ2h0OiBvcGVuIH0iCiAgICAgICAgICAgIEBjbGljaz0ib3BlbiA9ICFvcGVuIgogICAgICAgICAgLz4KICAgICAgICA8L2MtZm9yPgogICAgICAgIDxjLWVtcHR5PgogICAgICAgICAgPHA+e3sgdHIoInByb2R1Y3QtY2FyZC1uby10YWdzIikgfX08L3A+CiAgICAgICAgPC9jLWVtcHR5PgoKICAgICAgICA8YnV0dG9uIHR5cGU9ImJ1dHRvbiIgQGMtY2xpY2s9Imxpa2UiPgogICAgICAgICAgTGlrZSA8c3BhbiB4LXRleHQ9Imxpa2VzIj57eyBsaWtlcyB9fTwvc3Bhbj4KICAgICAgICA8L2J1dHRvbj4KCiAgICAgICAgPGMtc2xvdCBuYW1lPSJmb290ZXIiPgogICAgICAgICAgTm8gZm9vdGVyIHlldAogICAgICAgIDwvYy1zbG90PgogICAgICA8L2FydGljbGU+CiAgICAiIiIKCiAgICBqcyA9ICIiIgogICAgICAkY29tcG9uZW50KCh7IGVscywgZGF0YSB9KSA9PiB7CiAgICAgICAgY29uc3QgY2FyZEVsID0gZWxzWzBdOwogICAgICAgIGFuaW1hdGVMaWtlcyhjYXJkRWwsIGRhdGEubGlrZXMpOwogICAgICB9KTsKICAgICIiIgoKICAgIGNzcyA9ICIiIgogICAgICAuY2FyZCB7CiAgICAgICAgYm9yZGVyLWxlZnQ6IDNweCBzb2xpZCB2YXIoLS1hY2NlbnQpOwogICAgICB9CgogICAgICAudGFnLS1hY3RpdmUgewogICAgICAgIGNvbG9yOiB2YXIoLS1hY2NlbnQpOwogICAgICB9CiAgICAiIiIKCiAgICBtZXNzYWdlcyA9ICIiIgogICAgICBwcm9kdWN0LWNhcmQtbm8tdGFncyA9IE5vIHRhZ3MgeWV0LgogICAgIiIiCgogICAgY2xhc3MgRGVwZW5kZW5jaWVzOgogICAgICAgIGpzID0gWyJodHRwczovL2Nkbi5qc2RlbGl2ci5uZXQvbnBtL2FscGluZWpzQDMuMTQuMS9kaXN0L2Nkbi5taW4uanMiXQogICAgICAgIGNzcyA9IFsiaHR0cHM6Ly91bnBrZy5jb20vbm9ybWFsaXplLmNzc0A4LjAuMS9ub3JtYWxpemUuY3NzIl0KCgpodG1sID0gc3RyKFByb2R1Y3RDYXJkKAogICAgdGFncz1bIm5ldyIsICJzYWxlIl0sCiAgICBzbG90cz17ImJvZHkiOiAiQXVyb3JhIExhbXAifQopKQo="><div class="landing-code landing-tour__code"><div class="landing-code__bar"><span class="landing-code__dot"></span><span>product_card.py</span></div><div class="highlight"><pre><span></span><span id="tourline-1" class="landing-tour__line"><span class="kn">from</span><span class="w"> </span><span class="nn">citry</span><span class="w"> </span><span class="kn">import</span> <span class="n">Component</span><span class="p">,</span> <span class="n">SlotInput</span></span><span id="tourline-2" class="landing-tour__line"></span><span id="tourline-3" class="landing-tour__line"><span class="k">class</span><span class="w"> </span><span class="nc">ProductCard</span><span class="p">(</span><span class="n">Component</span><span class="p">):</span></span><span id="tourline-4" class="landing-tour__line is-marked" data-tour="kwargs" data-tour-start="">    <span class="k">class</span><span class="w"> </span><span class="nc">Kwargs</span><span class="p">:</span></span><span id="tourline-5" class="landing-tour__line is-marked" data-tour="kwargs">        <span class="n">tags</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span></span><span id="tourline-6" class="landing-tour__line is-marked" data-tour="kwargs">        <span class="n">likes</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">0</span></span><span id="tourline-7" class="landing-tour__line is-marked" data-tour="kwargs">        <span class="n">accent</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&quot;#175cd3&quot;</span></span><span id="tourline-8" class="landing-tour__line"></span><span id="tourline-9" class="landing-tour__line is-marked" data-tour="slots" data-tour-start="">    <span class="k">class</span><span class="w"> </span><span class="nc">Slots</span><span class="p">:</span></span><span id="tourline-10" class="landing-tour__line is-marked" data-tour="slots">        <span class="n">body</span><span class="p">:</span> <span class="n">SlotInput</span></span><span id="tourline-11" class="landing-tour__line is-marked" data-tour="slots">        <span class="n">footer</span><span class="p">:</span> <span class="n">SlotInput</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span></span><span id="tourline-12" class="landing-tour__line"></span><span id="tourline-13" class="landing-tour__line is-marked" data-tour="state" data-tour-start="">    <span class="k">class</span><span class="w"> </span><span class="nc">State</span><span class="p">(</span><span class="n">Kwargs</span><span class="p">):</span></span><span id="tourline-14" class="landing-tour__line is-marked" data-tour="state">        <span class="k">pass</span></span><span id="tourline-15" class="landing-tour__line"></span><span id="tourline-16" class="landing-tour__line is-marked" data-tour="events" data-tour-start="">    <span class="k">class</span><span class="w"> </span><span class="nc">Events</span><span class="p">:</span></span><span id="tourline-17" class="landing-tour__line is-marked" data-tour="events">        <span class="k">def</span><span class="w"> </span><span class="nf">like</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">state</span><span class="p">:</span> <span class="n">ProductCard</span><span class="o">.</span><span class="n">State</span><span class="p">):</span></span><span id="tourline-18" class="landing-tour__line is-marked" data-tour="events">            <span class="k">return</span> <span class="n">ProductCard</span><span class="p">(</span></span><span id="tourline-19" class="landing-tour__line is-marked" data-tour="events">                <span class="n">tags</span><span class="o">=</span><span class="n">state</span><span class="o">.</span><span class="n">tags</span><span class="p">,</span></span><span id="tourline-20" class="landing-tour__line is-marked" data-tour="events">                <span class="n">likes</span><span class="o">=</span><span class="n">state</span><span class="o">.</span><span class="n">likes</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span></span><span id="tourline-21" class="landing-tour__line is-marked" data-tour="events">            <span class="p">)</span></span><span id="tourline-22" class="landing-tour__line"></span><span id="tourline-23" class="landing-tour__line is-marked" data-tour="data" data-tour-start="">    <span class="k">def</span><span class="w"> </span><span class="nf">template_data</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">kwargs</span><span class="p">:</span> <span class="n">Kwargs</span><span class="p">,</span> <span class="n">slots</span><span class="p">:</span> <span class="n">Slots</span><span class="p">):</span></span><span id="tourline-24" class="landing-tour__line is-marked" data-tour="data">        <span class="k">return</span> <span class="p">{</span></span><span id="tourline-25" class="landing-tour__line is-marked" data-tour="data">            <span class="s2">&quot;likes&quot;</span><span class="p">:</span> <span class="n">kwargs</span><span class="o">.</span><span class="n">likes</span><span class="p">,</span></span><span id="tourline-26" class="landing-tour__line is-marked" data-tour="data">            <span class="s2">&quot;tags&quot;</span><span class="p">:</span> <span class="n">kwargs</span><span class="o">.</span><span class="n">tags</span><span class="p">,</span></span><span id="tourline-27" class="landing-tour__line is-marked" data-tour="data">        <span class="p">}</span></span><span id="tourline-28" class="landing-tour__line is-marked" data-tour="data"></span><span id="tourline-29" class="landing-tour__line is-marked" data-tour="data">    <span class="k">def</span><span class="w"> </span><span class="nf">js_data</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">kwargs</span><span class="p">:</span> <span class="n">Kwargs</span><span class="p">,</span> <span class="n">slots</span><span class="p">:</span> <span class="n">Slots</span><span class="p">):</span></span><span id="tourline-30" class="landing-tour__line is-marked" data-tour="data">        <span class="k">return</span> <span class="p">{</span><span class="s2">&quot;likes&quot;</span><span class="p">:</span> <span class="n">kwargs</span><span class="o">.</span><span class="n">likes</span><span class="p">}</span></span><span id="tourline-31" class="landing-tour__line is-marked" data-tour="data"></span><span id="tourline-32" class="landing-tour__line is-marked" data-tour="data">    <span class="k">def</span><span class="w"> </span><span class="nf">css_data</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">kwargs</span><span class="p">:</span> <span class="n">Kwargs</span><span class="p">,</span> <span class="n">slots</span><span class="p">:</span> <span class="n">Slots</span><span class="p">):</span></span><span id="tourline-33" class="landing-tour__line is-marked" data-tour="data">        <span class="k">return</span> <span class="p">{</span><span class="s2">&quot;accent&quot;</span><span class="p">:</span> <span class="n">kwargs</span><span class="o">.</span><span class="n">accent</span><span class="p">}</span></span><span id="tourline-34" class="landing-tour__line"></span><span id="tourline-35" class="landing-tour__line">    <span class="nv">template</span> <span class="o">=</span> <span class="sd">&quot;&quot;&quot;</span></span><span id="tourline-36" class="landing-tour__line is-marked" data-tour="alpine" data-tour-start="">      <span class="p">&lt;</span><span class="nt">article</span></span><span id="tourline-37" class="landing-tour__line is-marked" data-tour="alpine">        <span class="na">class</span><span class="o">=</span><span class="s">&quot;card&quot;</span></span><span id="tourline-38" class="landing-tour__line is-marked" data-tour="alpine">        <span class="na">x-data</span><span class="o">=</span><span class="p">&quot;{</span><span class="w"> </span><span class="nx">open</span><span class="o">:</span><span class="w"> </span><span class="kc">false</span><span class="w"> </span><span class="p">}&quot;</span></span><span id="tourline-39" class="landing-tour__line is-marked" data-tour="alpine">      <span class="p">&gt;</span></span><span id="tourline-40" class="landing-tour__line is-marked" data-tour="slot-body" data-tour-start="">        <span class="p">&lt;</span><span class="nt">c-slot</span> <span class="na">name</span><span class="o">=</span><span class="s">&quot;body&quot;</span> <span class="p">/&gt;</span></span><span id="tourline-41" class="landing-tour__line"></span><span id="tourline-42" class="landing-tour__line is-marked" data-tour="control" data-tour-start="">        <span class="p">&lt;</span><span class="nt">c-for</span> <span class="na">each</span><span class="o">=</span><span class="p">&quot;</span><span class="n">tag</span> <span class="ow">in</span> <span class="n">tags</span><span class="p">&quot;&gt;</span></span><span id="tourline-43" class="landing-tour__line is-marked" data-tour="control">          <span class="p">&lt;</span><span class="nt">c-Tag</span></span><span id="tourline-44" class="landing-tour__line is-marked" data-tour="control">            <span class="na">c-label</span><span class="o">=</span><span class="p">&quot;</span><span class="n">tag</span><span class="p">&quot;</span></span><span id="tourline-45" class="landing-tour__line is-marked" data-tour="control">            <span class="na">$c-props</span><span class="o">=</span><span class="p">&quot;{</span><span class="w"> </span><span class="nx">highlight</span><span class="o">:</span><span class="w"> </span><span class="nx">open</span><span class="w"> </span><span class="p">}&quot;</span></span><span id="tourline-46" class="landing-tour__line is-marked" data-tour="control">            <span class="na">@click</span><span class="o">=</span><span class="p">&quot;</span><span class="nx">open</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">!</span><span class="nx">open</span><span class="p">&quot;</span></span><span id="tourline-47" class="landing-tour__line is-marked" data-tour="control">          <span class="p">/&gt;</span></span><span id="tourline-48" class="landing-tour__line is-marked" data-tour="control">        <span class="p">&lt;/</span><span class="nt">c-for</span><span class="p">&gt;</span></span><span id="tourline-49" class="landing-tour__line is-marked" data-tour="control">        <span class="p">&lt;</span><span class="nt">c-empty</span><span class="p">&gt;</span></span><span id="tourline-50" class="landing-tour__line is-marked" data-tour="translated-text" data-tour-start="">          <span class="p">&lt;</span><span class="nt">p</span><span class="p">&gt;{{</span> <span class="n">tr</span><span class="p">(</span><span class="s2">&quot;product-card-no-tags&quot;</span><span class="p">)</span> <span class="p">}}&lt;/</span><span class="nt">p</span><span class="p">&gt;</span></span><span id="tourline-51" class="landing-tour__line">        <span class="p">&lt;/</span><span class="nt">c-empty</span><span class="p">&gt;</span></span><span id="tourline-52" class="landing-tour__line"></span><span id="tourline-53" class="landing-tour__line is-marked" data-tour="handlers" data-tour-start="">        <span class="p">&lt;</span><span class="nt">button</span> <span class="na">type</span><span class="o">=</span><span class="s">&quot;button&quot;</span> <span class="na">@c-click</span><span class="o">=</span><span class="p">&quot;</span><span class="nf">like</span><span class="p">&quot;&gt;</span></span><span id="tourline-54" class="landing-tour__line is-marked" data-tour="handlers">          Like <span class="p">&lt;</span><span class="nt">span</span> <span class="na">x-text</span><span class="o">=</span><span class="p">&quot;</span><span class="nx">likes</span><span class="p">&quot;&gt;{{</span> <span class="n">likes</span> <span class="p">}}&lt;/</span><span class="nt">span</span><span class="p">&gt;</span></span><span id="tourline-55" class="landing-tour__line is-marked" data-tour="handlers">        <span class="p">&lt;/</span><span class="nt">button</span><span class="p">&gt;</span></span><span id="tourline-56" class="landing-tour__line"></span><span id="tourline-57" class="landing-tour__line is-marked" data-tour="slot-footer" data-tour-start="">        <span class="p">&lt;</span><span class="nt">c-slot</span> <span class="na">name</span><span class="o">=</span><span class="s">&quot;footer&quot;</span><span class="p">&gt;</span></span><span id="tourline-58" class="landing-tour__line is-marked" data-tour="slot-footer">          No footer yet</span><span id="tourline-59" class="landing-tour__line is-marked" data-tour="slot-footer">        <span class="p">&lt;/</span><span class="nt">c-slot</span><span class="p">&gt;</span></span><span id="tourline-60" class="landing-tour__line">      <span class="p">&lt;/</span><span class="nt">article</span><span class="p">&gt;</span></span><span id="tourline-61" class="landing-tour__line">    <span class="sd">&quot;&quot;&quot;</span></span><span id="tourline-62" class="landing-tour__line"></span><span id="tourline-63" class="landing-tour__line is-marked" data-tour="js" data-tour-start="">    <span class="nv">js</span> <span class="o">=</span> <span class="sd">&quot;&quot;&quot;</span></span><span id="tourline-64" class="landing-tour__line is-marked" data-tour="js"><span class="w">      </span><span class="nx">$component</span><span class="p">(({</span><span class="w"> </span><span class="nx">els</span><span class="p">,</span><span class="w"> </span><span class="nx">data</span><span class="w"> </span><span class="p">})</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="p">{</span></span><span id="tourline-65" class="landing-tour__line is-marked" data-tour="js"><span class="w">        </span><span class="kd">const</span><span class="w"> </span><span class="nx">cardEl</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">els</span><span class="p">[</span><span class="mf">0</span><span class="p">];</span></span><span id="tourline-66" class="landing-tour__line is-marked" data-tour="js"><span class="w">        </span><span class="nx">animateLikes</span><span class="p">(</span><span class="nx">cardEl</span><span class="p">,</span><span class="w"> </span><span class="nx">data</span><span class="p">.</span><span class="nx">likes</span><span class="p">);</span></span><span id="tourline-67" class="landing-tour__line is-marked" data-tour="js"><span class="w">      </span><span class="p">});</span></span><span id="tourline-68" class="landing-tour__line is-marked" data-tour="js"><span class="w">    </span><span class="sd">&quot;&quot;&quot;</span></span><span id="tourline-69" class="landing-tour__line"></span><span id="tourline-70" class="landing-tour__line is-marked" data-tour="css" data-tour-start="">    <span class="nv">css</span> <span class="o">=</span> <span class="sd">&quot;&quot;&quot;</span></span><span id="tourline-71" class="landing-tour__line is-marked" data-tour="css"><span class="w">      </span><span class="p">.</span><span class="nc">card</span><span class="w"> </span><span class="p">{</span></span><span id="tourline-72" class="landing-tour__line is-marked" data-tour="css"><span class="w">        </span><span class="k">border-left</span><span class="p">:</span><span class="w"> </span><span class="mi">3</span><span class="kt">px</span><span class="w"> </span><span class="kc">solid</span><span class="w"> </span><span class="nf">var</span><span class="p">(</span><span class="nv">--accent</span><span class="p">);</span></span><span id="tourline-73" class="landing-tour__line is-marked" data-tour="css"><span class="w">      </span><span class="p">}</span></span><span id="tourline-74" class="landing-tour__line is-marked" data-tour="css"></span><span id="tourline-75" class="landing-tour__line is-marked" data-tour="css"><span class="w">      </span><span class="p">.</span><span class="nc">tag--active</span><span class="w"> </span><span class="p">{</span></span><span id="tourline-76" class="landing-tour__line is-marked" data-tour="css"><span class="w">        </span><span class="k">color</span><span class="p">:</span><span class="w"> </span><span class="nf">var</span><span class="p">(</span><span class="nv">--accent</span><span class="p">);</span></span><span id="tourline-77" class="landing-tour__line is-marked" data-tour="css"><span class="w">      </span><span class="p">}</span></span><span id="tourline-78" class="landing-tour__line is-marked" data-tour="css"><span class="w">    </span><span class="sd">&quot;&quot;&quot;</span></span><span id="tourline-79" class="landing-tour__line"></span><span id="tourline-80" class="landing-tour__line is-marked" data-tour="messages" data-tour-start="">    <span class="nv">messages</span> <span class="o">=</span> <span class="sd">&quot;&quot;&quot;</span></span><span id="tourline-81" class="landing-tour__line is-marked" data-tour="messages">      <span class="no">product-card-no-tags</span><span class="p"> = </span><span class="l">No tags yet.</span></span><span id="tourline-82" class="landing-tour__line is-marked" data-tour="messages">    <span class="sd">&quot;&quot;&quot;</span></span><span id="tourline-83" class="landing-tour__line"></span><span id="tourline-84" class="landing-tour__line is-marked" data-tour="deps" data-tour-start="">    <span class="k">class</span><span class="w"> </span><span class="nc">Dependencies</span><span class="p">:</span></span><span id="tourline-85" class="landing-tour__line is-marked" data-tour="deps">        <span class="n">js</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&quot;https://cdn.jsdelivr.net/npm/alpinejs@3.14.1/dist/cdn.min.js&quot;</span><span class="p">]</span></span><span id="tourline-86" class="landing-tour__line is-marked" data-tour="deps">        <span class="n">css</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&quot;https://unpkg.com/normalize.css@8.0.1/normalize.css&quot;</span><span class="p">]</span></span><span id="tourline-87" class="landing-tour__line"></span><span id="tourline-88" class="landing-tour__line"></span><span id="tourline-89" class="landing-tour__line is-marked" data-tour="render" data-tour-start=""><span class="n">html</span> <span class="o">=</span> <span class="nb">str</span><span class="p">(</span><span class="n">ProductCard</span><span class="p">(</span></span><span id="tourline-90" class="landing-tour__line is-marked" data-tour="render">    <span class="n">tags</span><span class="o">=</span><span class="p">[</span><span class="s2">&quot;new&quot;</span><span class="p">,</span> <span class="s2">&quot;sale&quot;</span><span class="p">],</span></span><span id="tourline-91" class="landing-tour__line is-marked" data-tour="render">    <span class="n">slots</span><span class="o">=</span><span class="p">{</span><span class="s2">&quot;body&quot;</span><span class="p">:</span> <span class="s2">&quot;Aurora Lamp&quot;</span><span class="p">}</span></span><span id="tourline-92" class="landing-tour__line is-marked" data-tour="render"><span class="p">))</span></span></pre></div></div><div class="landing-tour__notes"><p class="landing-tour__hint" data-tour-hint> Point at a marked line to see what it does. </p><div class="landing-tour__note" data-tour-note="kwargs"><span class="landing-tour__note-label">Inputs</span><strong>Declared inputs</strong><p>Every input this component accepts, with its type and any default. Passing an unknown name, or leaving out a required one, is reported when the component renders rather than quietly producing a gap in the page.</p></div><div class="landing-tour__note" data-tour-note="slots"><span class="landing-tour__note-label">Slots</span><strong>Openings the caller fills</strong><p>Named places a caller passes markup into. <code>body</code> is required and <code>footer</code> is optional, so the contract covers content as well as data.</p></div><div class="landing-tour__note" data-tour-note="state"><span class="landing-tour__note-label">State</span><strong>State that survives a call</strong><p>Server-side state available across Python event handler calls. Travels between the server and the browser. Inheriting <code>Kwargs</code> makes the <code>State</code> carry the same fields.</p></div><div class="landing-tour__note" data-tour-note="events"><span class="landing-tour__note-label">Events</span><strong>Python that runs on interaction</strong><p>A public method here can be called from the browser using <code>@c-event="like"</code>. <code>like</code> reads the current state and renders the updated component, which the browser then displays.</p></div><div class="landing-tour__note" data-tour-note="data"><span class="landing-tour__note-label">Data</span><strong>Use Python variables in templates, browser behavior, and CSS</strong><p><code>template_data</code> prepares template variables, <code>js_data</code> seeds Alpine variables from JSON, and <code>css_data</code> creates CSS variables scoped to this one instance.</p></div><div class="landing-tour__note" data-tour-note="alpine"><span class="landing-tour__note-label">Browser state</span><strong>State that never leaves the page</strong><p><code>x-data</code> holds what only the browser cares about. Opening and closing the card needs no server, so it never asks one.</p></div><div class="landing-tour__note" data-tour-note="slot-body"><span class="landing-tour__note-label">Slot</span><strong>Where filled content lands</strong><p><code>&lt;c-slot&gt;</code> marks the spot the caller's content drops into, inside markup this component still controls.</p></div><div class="landing-tour__note" data-tour-note="control"><span class="landing-tour__note-label">Control flow</span><strong>A loop, a child, and the empty case</strong><p><code>&lt;c-for&gt;</code> repeats a child component, while <code>&lt;c-empty&gt;</code> runs when there are no tags at all. The child component <code>&lt;c-Tag&gt;</code> receives <code>label</code> as Python value, and <code>highlight</code> as Alpine (browser) value through <code>$c-props</code>. You can listen to children's Alpine events with regular <code>@click</code>.</p></div><div class="landing-tour__note" data-tour-note="translated-text"><span class="landing-tour__note-label">Translated text</span><strong>First-class support for i18n and l10n</strong><p><code>tr()</code> translates text to this render's locale. Translation keys are defined as Fluent syntax in this same component file. </p></div><div class="landing-tour__note" data-tour-note="handlers"><span class="landing-tour__note-label">Bindings</span><strong>Alpine and Python, side by side</strong><p><code>@click</code> stays in the browser for instant feedback, while <code>@c-click</code> calls the Python handler set in the value, <code>like</code>.</p></div><div class="landing-tour__note" data-tour-note="slot-footer"><span class="landing-tour__note-label">Fallback</span><strong>What shows when nobody fills it</strong><p>Content between the tags is the fallback for an optional slot, so a caller who skips it still gets something sensible.</p></div><div class="landing-tour__note" data-tour-note="js"><span class="landing-tour__note-label">Script</span><strong>Advanced setup scoped to this component</strong><p>Templates use <code>js_data</code> values directly. Add <code>$component</code> when an imperative library or other setup needs this instance's elements and data.</p></div><div class="landing-tour__note" data-tour-note="css"><span class="landing-tour__note-label">Style</span><strong>Styles reading Python values</strong><p><code>var(--accent)</code> reads the custom property <code>css_data</code> produced, and it is scoped to this instance, so two cards on one page can differ without a second stylesheet.</p></div><div class="landing-tour__note" data-tour-note="messages"><span class="landing-tour__note-label">Messages</span><strong>Write translation keys as Fluent syntax</strong><p>With <a href="https://github.com/projectfluent/fluent" rel="noreferrer noopener" target="_blank">Fluent</a> by Mozilla, you can define translation keys that can easily handle genders, counts, composition, or even formatting. Export the messages to translate the catalog to other locales.</p></div><div class="landing-tour__note" data-tour-note="deps"><span class="landing-tour__note-label">Assets</span><strong>Third-party scripts and styles</strong><p>Libraries this component needs. Citry loads each script only once per page, however many components may use it.</p></div><div class="landing-tour__note" data-tour-note="render"><span class="landing-tour__note-label">Render</span><strong>Rendering is a function call</strong><p>Rendering returns ordinary HTML. This makes components easy to integrate with web frameworks, or test with plain Python.</p></div></div></div></div>



</section>

<section class="landing-section landing-section--band" id="integrated" markdown="1">
## Use with any web server <br/>or standalone.

<p class="landing-section__intro">
Citry's server-side events need a route on your application.
Two lines of code and you're all set. If you don't need events,
you can use Citry without a server.
</p>

See the [web framework integrations](/web-frameworks/) and
[server events](/events/).




<div markdown="0"><div class="landing-picker" data-landing-picker><div class="landing-picker__item"><button class="landing-picker__row is-active" type="button" data-picker-case="fastapi" aria-pressed="true"><span class="landing-picker__number"> 01 </span><span><strong>FastAPI</strong><br/><span class="landing-picker__blurb"> Also Starlette </span></span><svg class="landing-picker__caret" viewBox="0 0 16 16" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 6l4 4 4-4"></path></svg></button><div class="landing-picker__panel" data-picker-panel="fastapi"><div class="landing-code landing-picker__code"><div class="landing-code__bar"><span class="landing-code__dot"></span><span>main.py</span></div><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">contextlib</span><span class="w"> </span><span class="kn">import</span> <span class="n">asynccontextmanager</span>&#10;&#10;<span class="kn">from</span><span class="w"> </span><span class="nn">fastapi</span><span class="w"> </span><span class="kn">import</span> <span class="n">FastAPI</span>&#10;&#10;<span class="kn">from</span><span class="w"> </span><span class="nn">citry</span><span class="w"> </span><span class="kn">import</span> <span class="n">citry</span>&#10;<span class="kn">from</span><span class="w"> </span><span class="nn">citry.contrib.fastapi</span><span class="w"> </span><span class="kn">import</span> <span class="n">mount</span>&#10;&#10;<span class="nd">@asynccontextmanager</span>&#10;<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">lifespan</span><span class="p">(</span><span class="n">_app</span><span class="p">:</span> <span class="n">FastAPI</span><span class="p">):</span>&#10;    <span class="n">citry</span><span class="o">.</span><span class="n">initialize</span><span class="p">()</span>&#10;    <span class="k">yield</span>&#10;&#10;<span class="n">app</span> <span class="o">=</span> <span class="n">FastAPI</span><span class="p">(</span><span class="n">lifespan</span><span class="o">=</span><span class="n">lifespan</span><span class="p">)</span>&#10;<span class="n">mount</span><span class="p">(</span><span class="n">app</span><span class="p">,</span> <span class="n">citry</span><span class="p">)</span>&#10;</pre></div></div></div></div><div class="landing-picker__item"><button class="landing-picker__row" type="button" data-picker-case="flask" aria-pressed="false"><span class="landing-picker__number"> 02 </span><span><strong>Flask</strong><br/><span class="landing-picker__blurb"> The same call shape </span></span><svg class="landing-picker__caret" viewBox="0 0 16 16" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 6l4 4 4-4"></path></svg></button><div class="landing-picker__panel" data-picker-panel="flask"><div class="landing-code landing-picker__code"><div class="landing-code__bar"><span class="landing-code__dot"></span><span>app.py</span></div><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">flask</span><span class="w"> </span><span class="kn">import</span> <span class="n">Flask</span>&#10;&#10;<span class="kn">from</span><span class="w"> </span><span class="nn">citry</span><span class="w"> </span><span class="kn">import</span> <span class="n">citry</span>&#10;<span class="kn">from</span><span class="w"> </span><span class="nn">citry.contrib.flask</span><span class="w"> </span><span class="kn">import</span> <span class="n">mount</span>&#10;&#10;<span class="n">app</span> <span class="o">=</span> <span class="n">Flask</span><span class="p">(</span><span class="vm">__name__</span><span class="p">)</span>&#10;<span class="n">mount</span><span class="p">(</span><span class="n">app</span><span class="p">,</span> <span class="n">citry</span><span class="p">,</span> <span class="n">prefix</span><span class="o">=</span><span class="s2">&quot;/citry&quot;</span><span class="p">)</span>&#10;<span class="n">citry</span><span class="o">.</span><span class="n">initialize</span><span class="p">()</span>&#10;</pre></div></div></div></div><div class="landing-picker__item"><button class="landing-picker__row" type="button" data-picker-case="django" aria-pressed="false"><span class="landing-picker__number"> 03 </span><span><strong>Django</strong><br/><span class="landing-picker__blurb"> Added to your URL conf </span></span><svg class="landing-picker__caret" viewBox="0 0 16 16" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 6l4 4 4-4"></path></svg></button><div class="landing-picker__panel" data-picker-panel="django"><div class="landing-code landing-picker__code"><div class="landing-code__bar"><span class="landing-code__dot"></span><span>urls.py</span></div><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">django.urls</span><span class="w"> </span><span class="kn">import</span> <span class="n">path</span>&#10;&#10;<span class="kn">from</span><span class="w"> </span><span class="nn">citry</span><span class="w"> </span><span class="kn">import</span> <span class="n">citry</span>&#10;<span class="kn">from</span><span class="w"> </span><span class="nn">citry.contrib.django</span><span class="w"> </span><span class="kn">import</span> <span class="n">urlpatterns</span> <span class="k">as</span> <span class="n">citry_urls</span>&#10;&#10;<span class="n">urlpatterns</span> <span class="o">=</span> <span class="p">[</span>&#10;    <span class="n">path</span><span class="p">(</span><span class="s2">&quot;&quot;</span><span class="p">,</span> <span class="n">home_view</span><span class="p">),</span>&#10;    <span class="o">*</span><span class="n">citry_urls</span><span class="p">(</span><span class="n">citry</span><span class="p">,</span> <span class="n">prefix</span><span class="o">=</span><span class="s2">&quot;/citry&quot;</span><span class="p">),</span>&#10;<span class="p">]</span>&#10;</pre></div></div></div></div><div class="landing-picker__item"><button class="landing-picker__row" type="button" data-picker-case="asgi" aria-pressed="false"><span class="landing-picker__number"> 04 </span><span><strong>Bare ASGI</strong><br/><span class="landing-picker__blurb"> No framework required </span></span><svg class="landing-picker__caret" viewBox="0 0 16 16" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 6l4 4 4-4"></path></svg></button><div class="landing-picker__panel" data-picker-panel="asgi"><div class="landing-code landing-picker__code"><div class="landing-code__bar"><span class="landing-code__dot"></span><span>asgi.py</span></div><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">citry</span><span class="w"> </span><span class="kn">import</span> <span class="n">citry</span>&#10;<span class="kn">from</span><span class="w"> </span><span class="nn">citry.contrib.asgi</span><span class="w"> </span><span class="kn">import</span> <span class="n">asgi_app</span>&#10;&#10;<span class="n">citry</span><span class="o">.</span><span class="n">initialize</span><span class="p">()</span>&#10;<span class="n">app</span> <span class="o">=</span> <span class="n">asgi_app</span><span class="p">(</span><span class="n">citry</span><span class="p">)</span>&#10;</pre></div></div></div></div><div class="landing-picker__item"><button class="landing-picker__row" type="button" data-picker-case="wsgi" aria-pressed="false"><span class="landing-picker__number"> 05 </span><span><strong>Bare WSGI</strong><br/><span class="landing-picker__blurb"> For synchronous stacks </span></span><svg class="landing-picker__caret" viewBox="0 0 16 16" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 6l4 4 4-4"></path></svg></button><div class="landing-picker__panel" data-picker-panel="wsgi"><div class="landing-code landing-picker__code"><div class="landing-code__bar"><span class="landing-code__dot"></span><span>wsgi.py</span></div><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">citry</span><span class="w"> </span><span class="kn">import</span> <span class="n">citry</span>&#10;<span class="kn">from</span><span class="w"> </span><span class="nn">citry.contrib.wsgi</span><span class="w"> </span><span class="kn">import</span> <span class="n">wsgi_app</span>&#10;&#10;<span class="n">citry</span><span class="o">.</span><span class="n">initialize</span><span class="p">()</span>&#10;<span class="n">application</span> <span class="o">=</span> <span class="n">wsgi_app</span><span class="p">(</span><span class="n">citry</span><span class="p">)</span>&#10;</pre></div></div></div></div></div></div>




</section>

<section class="landing-section" id="reliability" markdown="1">
## Catch mistakes early.

<!-- Every message below is the real one. Building this page applies each mistake
to the component above, renders it, and prints back exactly what Citry raised.
If a mistake stops being reported, or the report loses its detail, this page
fails to build. -->

<p class="landing-section__intro">
Citry was born out of frustration with Django's silent coerctions and leaky
isolations.
</p>

<p class="landing-section__intro">
In Citry, what you see (in your component) is what you get:
</p>

<ul class="landing-section__intro" style="margin: 1.4rem 0;">
  <li>Variables NEVER leak to other components.</li>
  <li>Data passing is ALWAYS explicit contracts.</li>
  <li>Missing values are ALWAYS error in Citry.</li>
</ul>

Read about [inputs and validation](/concepts/inputs-and-validation/),
[error boundaries](/concepts/error-boundaries/), and
[testing components](/advanced/testing/).




<div markdown="0"><div class="landing-picker" data-landing-picker><div class="landing-picker__item"><button class="landing-picker__row is-active" type="button" data-picker-case="input" aria-pressed="true"><span class="landing-picker__number"> 01 </span><span><strong>Missing input</strong><br/><span class="landing-picker__blurb"> Rejected as the component is called </span></span><svg class="landing-picker__caret" viewBox="0 0 16 16" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 6l4 4 4-4"></path></svg></button><div class="landing-picker__panel" data-picker-panel="input"><div class="landing-code landing-picker__code"><div class="landing-code__bar"><span class="landing-code__dot"></span><span>input.py</span></div><div class="highlight"><pre><span></span><span class="n">card</span> <span class="o">=</span> <span class="n">StatusCard</span><span class="p">(</span>&#10;    <span class="n">complete</span><span class="o">=</span><span class="mi">18</span><span class="p">,</span>&#10;    <span class="n">total</span><span class="o">=</span><span class="mi">25</span><span class="p">,</span>&#10;<span class="p">)</span>&#10;&#10;<span class="c1"># Rendering is where the component&#39;s inputs are checked</span>&#10;<span class="nb">str</span><span class="p">(</span><span class="n">card</span><span class="p">)</span>&#10;</pre></div></div><div class="landing-diagnostic"><p class="landing-diagnostic__mutation"><span>Rejected as the component is called</span></p><span class="landing-diagnostic__type">TypeError</span><pre>An error occurred while rendering components StatusCard:&#10;StatusCard.Kwargs.__init__() missing 1 required positional argument: &#39;title&#39;</pre></div></div></div><div class="landing-picker__item"><button class="landing-picker__row" type="button" data-picker-case="misspelled" aria-pressed="false"><span class="landing-picker__number"> 02 </span><span><strong>Misspelled input</strong><br/><span class="landing-picker__blurb"> Rejected, and the name you meant is offered </span></span><svg class="landing-picker__caret" viewBox="0 0 16 16" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 6l4 4 4-4"></path></svg></button><div class="landing-picker__panel" data-picker-panel="misspelled"><div class="landing-code landing-picker__code"><div class="landing-code__bar"><span class="landing-code__dot"></span><span>misspelled.py</span></div><div class="highlight"><pre><span></span><span class="n">card</span> <span class="o">=</span> <span class="n">StatusCard</span><span class="p">(</span>&#10;    <span class="n">titel</span><span class="o">=</span><span class="s2">&quot;Deploy preview&quot;</span><span class="p">,</span>&#10;    <span class="n">complete</span><span class="o">=</span><span class="mi">18</span><span class="p">,</span>&#10;    <span class="n">total</span><span class="o">=</span><span class="mi">25</span><span class="p">,</span>&#10;<span class="p">)</span>&#10;&#10;<span class="nb">str</span><span class="p">(</span><span class="n">card</span><span class="p">)</span>&#10;</pre></div></div><div class="landing-diagnostic"><p class="landing-diagnostic__mutation"><span>Rejected, and the name you meant is offered</span></p><span class="landing-diagnostic__type">TypeError</span><pre>An error occurred while rendering components StatusCard:&#10;StatusCard.Kwargs.__init__() got an unexpected keyword argument &#39;titel&#39;. Did you mean &#39;title&#39;?</pre></div></div></div><div class="landing-picker__item"><button class="landing-picker__row" type="button" data-picker-case="template" aria-pressed="false"><span class="landing-picker__number"> 03 </span><span><strong>Unknown template value</strong><br/><span class="landing-picker__blurb"> Pointed at the line that asked for it </span></span><svg class="landing-picker__caret" viewBox="0 0 16 16" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 6l4 4 4-4"></path></svg></button><div class="landing-picker__panel" data-picker-panel="template"><div class="landing-code landing-picker__code"><div class="landing-code__bar"><span class="landing-code__dot"></span><span>template.py</span></div><div class="highlight"><pre><span></span><span class="k">class</span><span class="w"> </span><span class="nc">Greeting</span><span class="p">(</span><span class="n">Component</span><span class="p">):</span>&#10;    <span class="k">class</span><span class="w"> </span><span class="nc">Kwargs</span><span class="p">:</span>&#10;        <span class="n">name</span><span class="p">:</span> <span class="nb">str</span>&#10;&#10;    <span class="k">def</span><span class="w"> </span><span class="nf">template_data</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">kwargs</span><span class="p">,</span> <span class="n">slots</span><span class="p">):</span>&#10;        <span class="k">return</span> <span class="p">{</span><span class="s2">&quot;name&quot;</span><span class="p">:</span> <span class="n">kwargs</span><span class="o">.</span><span class="n">name</span><span class="p">}</span>&#10;&#10;    <span class="n">template</span> <span class="o">=</span> <span class="s2">&quot;&lt;p&gt;Hello, {{ naem }}!&lt;/p&gt;&quot;</span>&#10;&#10;<span class="nb">str</span><span class="p">(</span><span class="n">Greeting</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s2">&quot;Ada&quot;</span><span class="p">))</span>&#10;</pre></div></div><div class="landing-diagnostic"><p class="landing-diagnostic__mutation"><span>Pointed at the line that asked for it</span></p><span class="landing-diagnostic__type">KeyError</span><pre>An error occurred while rendering components Greeting:&#10;Error in variable: KeyError: &#39;naem&#39;&#10;&#10;     1 | naem &#10;         ^^^^&#10;&#10;In template of &#39;Greeting&#39;:&#10;&#10;     1 | &lt;p&gt;Hello, {{ naem }}!&lt;/p&gt;&#10;                   ^^^^^^^^^^</pre></div></div></div><div class="landing-picker__item"><button class="landing-picker__row" type="button" data-picker-case="isolation" aria-pressed="false"><span class="landing-picker__number"> 04 </span><span><strong>Data stays in its component</strong><br/><span class="landing-picker__blurb"> A child never inherits the parent&#39;s variables </span></span><svg class="landing-picker__caret" viewBox="0 0 16 16" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 6l4 4 4-4"></path></svg></button><div class="landing-picker__panel" data-picker-panel="isolation"><div class="landing-code landing-picker__code"><div class="landing-code__bar"><span class="landing-code__dot"></span><span>isolation.py</span></div><div class="highlight"><pre><span></span><span class="k">class</span><span class="w"> </span><span class="nc">Child</span><span class="p">(</span><span class="n">Component</span><span class="p">):</span>&#10;    <span class="n">template</span> <span class="o">=</span> <span class="s2">&quot;&lt;span&gt;{{ user_name }}&lt;/span&gt;&quot;</span>&#10;&#10;<span class="k">class</span><span class="w"> </span><span class="nc">Parent</span><span class="p">(</span><span class="n">Component</span><span class="p">):</span>&#10;    <span class="k">def</span><span class="w"> </span><span class="nf">template_data</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">kwargs</span><span class="p">,</span> <span class="n">slots</span><span class="p">):</span>&#10;        <span class="k">return</span> <span class="p">{</span><span class="s2">&quot;user_name&quot;</span><span class="p">:</span> <span class="s2">&quot;Ada&quot;</span><span class="p">}</span>&#10;&#10;    <span class="n">template</span> <span class="o">=</span> <span class="s2">&quot;&lt;div&gt;{{ user_name }}&lt;c-child /&gt;&lt;/div&gt;&quot;</span>&#10;&#10;<span class="nb">str</span><span class="p">(</span><span class="n">Parent</span><span class="p">())</span>&#10;</pre></div></div><div class="landing-diagnostic"><p class="landing-diagnostic__mutation"><span>A child never inherits the parent&#39;s variables</span></p><span class="landing-diagnostic__type">KeyError</span><pre>An error occurred while rendering components Parent &gt; Child:&#10;Error in variable: KeyError: &#39;user_name&#39;&#10;&#10;     1 | user_name &#10;         ^^^^^^^^^&#10;&#10;In template of &#39;Child&#39;:&#10;&#10;     1 | &lt;span&gt;{{ user_name }}&lt;/span&gt;&#10;               ^^^^^^^^^^^^^^^</pre></div></div></div><div class="landing-picker__item"><button class="landing-picker__row" type="button" data-picker-case="unknown" aria-pressed="false"><span class="landing-picker__number"> 05 </span><span><strong>Unknown component</strong><br/><span class="landing-picker__blurb"> Named at the tag that asked for it </span></span><svg class="landing-picker__caret" viewBox="0 0 16 16" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 6l4 4 4-4"></path></svg></button><div class="landing-picker__panel" data-picker-panel="unknown"><div class="landing-code landing-picker__code"><div class="landing-code__bar"><span class="landing-code__dot"></span><span>unknown.py</span></div><div class="highlight"><pre><span></span><span class="k">class</span><span class="w"> </span><span class="nc">Page</span><span class="p">(</span><span class="n">Component</span><span class="p">):</span>&#10;    <span class="nv">template</span> <span class="o">=</span> <span class="sd">&quot;&quot;&quot;</span>&#10;      <span class="p">&lt;</span><span class="nt">c-StatusCrad</span> <span class="na">title</span><span class="o">=</span><span class="s">&quot;Deploy preview&quot;</span> <span class="p">/&gt;</span>&#10;    <span class="sd">&quot;&quot;&quot;</span>&#10;&#10;<span class="nb">str</span><span class="p">(</span><span class="n">Page</span><span class="p">())</span>&#10;</pre></div></div><div class="landing-diagnostic"><p class="landing-diagnostic__mutation"><span>Named at the tag that asked for it</span></p><span class="landing-diagnostic__type">NotRegistered</span><pre>An error occurred while rendering components Page:&#10;No component registered as &#39;statuscrad&#39;.&#10;&#10;In template of &#39;Page&#39;:&#10;&#10;     1 | &#10;     2 | &lt;c-StatusCrad title=&#34;Deploy preview&#34; /&gt;&#10;         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^&#10;     3 | </pre></div></div></div><div class="landing-picker__item"><button class="landing-picker__row" type="button" data-picker-case="mismatched" aria-pressed="false"><span class="landing-picker__number"> 06 </span><span><strong>Mismatched tags</strong><br/><span class="landing-picker__blurb"> The parser names the tag it expected to close </span></span><svg class="landing-picker__caret" viewBox="0 0 16 16" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 6l4 4 4-4"></path></svg></button><div class="landing-picker__panel" data-picker-panel="mismatched"><div class="landing-code landing-picker__code"><div class="landing-code__bar"><span class="landing-code__dot"></span><span>mismatched.py</span></div><div class="highlight"><pre><span></span><span class="k">class</span><span class="w"> </span><span class="nc">Broken</span><span class="p">(</span><span class="n">Component</span><span class="p">):</span>&#10;    <span class="n">template</span> <span class="o">=</span> <span class="s2">&quot;&lt;div&gt;&lt;span&gt;Deploy preview&lt;/div&gt;&quot;</span>&#10;&#10;<span class="nb">str</span><span class="p">(</span><span class="n">Broken</span><span class="p">())</span>&#10;</pre></div></div><div class="landing-diagnostic"><p class="landing-diagnostic__mutation"><span>The parser names the tag it expected to close</span></p><span class="landing-diagnostic__type">SyntaxError</span><pre>In template Broken:&#10;Parse error:  --&gt; 1:26&#10;  |&#10;1 | &lt;div&gt;&lt;span&gt;Deploy preview&lt;/div&gt;&#10;  |                          ^----^&#10;  |&#10;  = Mismatched tags: expected closing tag &#39;&lt;/span&gt;&#39;, found &#39;&lt;/div&gt;&#39;</pre></div></div></div><div class="landing-picker__item"><button class="landing-picker__row" type="button" data-picker-case="unsafe" aria-pressed="false"><span class="landing-picker__number"> 07 </span><span><strong>Unsafe expression</strong><br/><span class="landing-picker__blurb"> Template expressions cannot reach the interpreter </span></span><svg class="landing-picker__caret" viewBox="0 0 16 16" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 6l4 4 4-4"></path></svg></button><div class="landing-picker__panel" data-picker-panel="unsafe"><div class="landing-code landing-picker__code"><div class="landing-code__bar"><span class="landing-code__dot"></span><span>unsafe.py</span></div><div class="highlight"><pre><span></span><span class="k">class</span><span class="w"> </span><span class="nc">Danger</span><span class="p">(</span><span class="n">Component</span><span class="p">):</span>&#10;    <span class="n">template</span> <span class="o">=</span> <span class="s2">&quot;&lt;i&gt;{{ __import__(&#39;os&#39;).system(&#39;ls&#39;) }}&lt;/i&gt;&quot;</span>&#10;&#10;<span class="nb">str</span><span class="p">(</span><span class="n">Danger</span><span class="p">())</span>&#10;</pre></div></div><div class="landing-diagnostic"><p class="landing-diagnostic__mutation"><span>Template expressions cannot reach the interpreter</span></p><span class="landing-diagnostic__type">SecurityError</span><pre>An error occurred while rendering components Danger:&#10;Error in variable: SecurityError: variable &#39;__import__&#39; is unsafe&#10;&#10;     1 | __import__(&#39;os&#39;).system(&#39;ls&#39;) &#10;         ^^^^^^^^^^&#10;&#10;In template of &#39;Danger&#39;:&#10;&#10;     1 | &lt;i&gt;{{ __import__(&#39;os&#39;).system(&#39;ls&#39;) }}&lt;/i&gt;&#10;            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^</pre></div></div></div></div></div>




</section>

<section class="landing-section landing-section--band" id="editor" markdown="1">
## Your editor understands the whole component.

<p class="landing-section__intro">
Citry's VSCode extension connects all parts of the component - Py, HTML, JS, CSS. Surface errors or trace values across the languages. Add completions, diagnostics, and hover hints.
</p>

Install the [VS Code extension](/ide/vscode/).




<div markdown="0"><p style="font-size: 0.85rem; margin-top: 3rem;"> Hover or focus any dotted symbol below. <b>Ctrl-click</b> / <b>⌘-click</b> on a symbol to go to its definition. </p><div class="landing-editor" data-editor-showcase><div class="landing-code landing-editor__code" data-editor-code><div class="landing-code__bar landing-editor__bar"><span class="landing-code__dot"></span><span>editor_invite_panel.py</span><span class="landing-editor__mode">Citry</span></div><div class="landing-editor__hover-layer"><div id="landing-editor-hover" class="landing-editor__hover" role="dialog" aria-label="Type information" data-editor-hover hidden><div class="landing-editor__hover-signature"><code data-editor-hover-signature></code></div><strong data-editor-hover-provenance></strong><p data-editor-hover-description></p><div class="landing-editor__hover-actions"><a href="/ide/vscode/" target="_blank" rel="noopener" data-editor-hover-docs> Open Citry docs </a><a href="#" data-editor-jump hidden> Go to definition </a></div><small data-editor-jump-hint hidden><kbd>Ctrl</kbd>/<kbd>⌘</kbd> + click also jumps </small></div></div><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">typing</span><span class="w"> </span><span class="kn">import</span> <span class="n">TypedDict</span><span class="w">&#10;</span><span class="kn">from</span><span class="w"> </span><span class="nn">citry</span><span class="w"> </span><span class="kn">import</span> <span class="n">CitryRender</span><span class="p">,</span> <span class="n">Component</span><span class="w">&#10;</span><span class="w">&#10;</span><span class="w">&#10;</span><span class="k">class</span><span class="w"> </span><span class="nc">InvitePanel</span><span class="p">(</span><span class="n">Component</span><span class="p">)</span><span class="p">:</span><span class="w">&#10;</span>    <span class="nv">template</span> <span class="o">=</span> <span class="sd">&#34;&#34;&#34;</span>&#10;      <span class="p">&lt;</span><span class="nt">section</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;invite-panel&#34;</span><span class="p">&gt;</span>&#10;        <span class="p">&lt;</span><span class="nt">h2</span><span class="p">&gt;</span><span class="p">{{</span> <button type="button" class="landing-editor__symbol" data-editor-annotation="title-use" data-editor-symbol="title-use" data-editor-signature="(variable) title: str" data-editor-signature-html="&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;variable&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;title&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;str&lt;/span&gt;" data-editor-language="python" data-editor-provenance="Inferred from template_data()" data-editor-description="Go to Definition opens the exact returned dictionary key." data-editor-docs="/ide/vscode/" aria-controls="landing-editor-hover" aria-expanded="false" aria-label="title: (variable) title: str" data-editor-placement="below" data-editor-target="template-title"><span class="n">title</span></button> <span class="p">}}</span><span class="p">&lt;</span><span class="p">/</span><span class="nt">h2</span><span class="p">&gt;</span>&#10;&#10;        <span class="p">&lt;</span><span class="nt">template</span> <span class="na">x-for</span><span class="o">=</span><span class="p">&#34;</span><button type="button" class="landing-editor__symbol" data-editor-annotation="member-binding" data-editor-symbol="member-binding" data-editor-signature="(variable) member: Member" data-editor-signature-html="&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;variable&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;member&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;Member&lt;/span&gt;" data-editor-language="typescript" data-editor-provenance="Introduced by x-for" data-editor-description="This name exists only inside the repeated template subtree." data-editor-docs="/ide/vscode/#complete-alpine-and-component-javascript" aria-controls="landing-editor-hover" aria-expanded="false" aria-label="member: (variable) member: Member"><span class="nx">member</span></button><span class="w"> </span><span class="ow">in</span><span class="w"> </span><button type="button" class="landing-editor__symbol" data-editor-annotation="visible-members-use" data-editor-symbol="visible-members-use" data-editor-signature="(variable) visibleMembers: Member[]" data-editor-signature-html="&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;variable&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;visibleMembers&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;Member&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[]&lt;/span&gt;" data-editor-language="typescript" data-editor-provenance="Assigned by InvitePanel.$component" data-editor-description="Go to Definition follows this name into component JavaScript." data-editor-docs="/ide/vscode/#complete-alpine-and-component-javascript" aria-controls="landing-editor-hover" aria-expanded="false" aria-label="visibleMembers: (variable) visibleMembers: Member[]" data-editor-target="visible-members"><span class="nx">visibleMembers</span></button><span class="p">&#34;</span><span class="p">&gt;</span>&#10;          <span class="p">&lt;</span><span class="nt">c-</span><button type="button" class="landing-editor__symbol" data-editor-annotation="member-chip-use" data-editor-symbol="member-chip-use" data-editor-signature="class MemberChip(Component)" data-editor-signature-html="&lt;span class=&#34;k&#34;&gt;class&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nc&#34;&gt;MemberChip&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Component&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;" data-editor-language="python" data-editor-provenance="Registered Citry component" data-editor-description="Go to Definition opens the component&#39;s Python class." data-editor-docs="/ide/vscode/" aria-controls="landing-editor-hover" aria-expanded="false" aria-label="MemberChip: class MemberChip(Component)" data-editor-target="member-chip"><span class="nt">MemberChip</span></button>&#10;            <button type="button" class="landing-editor__symbol" data-editor-annotation="member-chip-name-use" data-editor-symbol="member-chip-name-use" data-editor-signature="(property) MemberChip.Kwargs.name: str" data-editor-signature-html="&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;property&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;MemberChip&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Kwargs&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;str&lt;/span&gt;" data-editor-language="python" data-editor-provenance="Input accepted by MemberChip" data-editor-description="Go to Definition opens the child&#39;s Kwargs field." data-editor-docs="/ide/vscode/" aria-controls="landing-editor-hover" aria-expanded="false" aria-label="c-name: (property) MemberChip.Kwargs.name: str" data-editor-target="member-chip-name"><span class="na">c-name</span></button><span class="o">=</span><span class="p">&#34;</span><span class="n">member</span><span class="o">.</span><button type="button" class="landing-editor__symbol" data-editor-annotation="member-name-use" data-editor-symbol="member-name-use" data-editor-signature="(property) Member.name: string" data-editor-signature-html="&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;property&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Member.name&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;string&lt;/span&gt;" data-editor-language="typescript" data-editor-provenance="Inferred from the x-for item" data-editor-description="Go to Definition opens the TypedDict field." data-editor-docs="/ide/vscode/#complete-alpine-and-component-javascript" aria-controls="landing-editor-hover" aria-expanded="false" aria-label="name: (property) Member.name: string" data-editor-target="member-name"><span class="n">name</span></button><span class="p">&#34;</span>&#10;            <button type="button" class="landing-editor__symbol" data-editor-annotation="member-chip-status-use" data-editor-symbol="member-chip-status-use" data-editor-signature="(property) MemberChip.Kwargs.status: CitryRender" data-editor-signature-html="&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;property&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;MemberChip&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Kwargs&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;status&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;CitryRender&lt;/span&gt;" data-editor-language="python" data-editor-provenance="Nested-template component input" data-editor-description="Go to Definition opens the child input that receives this rendered fragment." data-editor-docs="/syntax/nested-templates/" aria-controls="landing-editor-hover" aria-expanded="false" aria-label="c-status: (property) MemberChip.Kwargs.status: CitryRender" data-editor-target="member-chip-status"><span class="na">c-status</span></button><span class="o">=</span><span class="p">&#34;</span><span class="p">&lt;&gt;</span>&#10;              <span class="p">&lt;</span><span class="nt">small</span> <button type="button" class="landing-editor__symbol" data-editor-annotation="nested-html-attribute" data-editor-symbol="nested-html-attribute" data-editor-signature="(attribute) HTMLElement.title: string" data-editor-signature-html="&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;attribute&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;HTMLElement.title&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;string&lt;/span&gt;" data-editor-language="typescript" data-editor-provenance="HTML provider inside a nested template" data-editor-description="Citry forwards native HTML completion, hover, and documentation into the fragment." data-editor-docs="/ide/vscode/#look-up-citry-syntax" aria-controls="landing-editor-hover" aria-expanded="false" aria-label="c-title: (attribute) HTMLElement.title: string"><span class="na">c-title</span></button><span class="o">=</span><span class="p">&#39;</span><button type="button" class="landing-editor__symbol" data-editor-annotation="nested-title-use" data-editor-symbol="nested-title-use" data-editor-signature="(variable) title: str" data-editor-signature-html="&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;variable&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;title&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;str&lt;/span&gt;" data-editor-language="python" data-editor-provenance="Inferred from template_data()" data-editor-description="Python-expression navigation remains available inside the nested template." data-editor-docs="/ide/vscode/#complete-template-roots" aria-controls="landing-editor-hover" aria-expanded="false" aria-label="title: (variable) title: str" data-editor-target="template-title"><span class="n">title</span></button><span class="p">&#39;</span><span class="p">&gt;</span>&#10;                Available now&#10;              <span class="p">&lt;</span><span class="p">/</span><span class="nt">small</span><span class="p">&gt;</span>&#10;            <span class="p">&lt;/&gt;</span><span class="p">&#34;</span>&#10;            <button type="button" class="landing-editor__symbol" data-editor-annotation="client-props" data-editor-symbol="client-props" data-editor-signature="(attribute) $c-props: MemberChipProps" data-editor-signature-html="&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;attribute&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;$c&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;props&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;MemberChipProps&lt;/span&gt;" data-editor-language="typescript" data-editor-provenance="Checked against MemberChip" data-editor-description="Unknown, missing, and mistyped child props are reported here." data-editor-docs="/getting-started/client-props-and-handlers/" aria-controls="landing-editor-hover" aria-expanded="false" aria-label="$c-props: (attribute) $c-props: MemberChipProps"><span class="na">$c-props</span></button><span class="o">=</span><span class="p">&#34;</span><span class="p">{</span><span class="w"> </span><button type="button" class="landing-editor__symbol" data-editor-annotation="member-chip-online-use" data-editor-symbol="member-chip-online-use" data-editor-signature="(property) online: boolean" data-editor-signature-html="&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;property&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;online&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;boolean&lt;/span&gt;" data-editor-language="typescript" data-editor-provenance="Client prop declared by MemberChip" data-editor-description="Go to Definition opens the child&#39;s $component prop declaration." data-editor-docs="/getting-started/client-props-and-handlers/" aria-controls="landing-editor-hover" aria-expanded="false" aria-label="online: (property) online: boolean" data-editor-target="member-chip-online"><span class="nx">online</span></button><span class="o">:</span><span class="w"> </span><span class="nx">member</span><span class="p">.</span><button type="button" class="landing-editor__symbol" data-editor-annotation="member-online-use" data-editor-symbol="member-online-use" data-editor-signature="(property) Member.online: boolean" data-editor-signature-html="&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;property&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Member.online&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;boolean&lt;/span&gt;" data-editor-language="typescript" data-editor-provenance="Inferred from the x-for item" data-editor-description="Go to Definition opens the Python Member field." data-editor-docs="/ide/vscode/#complete-alpine-and-component-javascript" aria-controls="landing-editor-hover" aria-expanded="false" aria-label="online: (property) Member.online: boolean" data-editor-target="member-online"><span class="nx">online</span></button><span class="w"> </span><span class="p">}</span><span class="p">&#34;</span>&#10;          <span class="p">/</span><span class="p">&gt;</span>&#10;        <span class="p">&lt;</span><span class="p">/</span><span class="nt">template</span><span class="p">&gt;</span>&#10;&#10;        <span class="p">&lt;</span><span class="nt">p</span><span class="p">&gt;</span><span class="p">{{</span> <button type="button" class="landing-editor__symbol landing-editor__symbol--error" data-editor-annotation="unknown-template-variable" data-editor-symbol="unknown-template-variable" data-editor-signature="&#34;missing_summary&#34; is not defined" data-editor-signature-html="&lt;span class=&#34;s2&#34;&gt;&amp;quot;missing_summary&amp;quot;&lt;/span&gt; &lt;span class=&#34;ow&#34;&gt;is&lt;/span&gt; &lt;span class=&#34;ow&#34;&gt;not&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;defined&lt;/span&gt;" data-editor-language="python" data-editor-provenance="Error · citry.template.unknown-variable" data-editor-description="Template variable &#39;missing_summary&#39; is not available in this template." data-editor-docs="/ide/diagnostics/#citry.template.unknown-variable" aria-controls="landing-editor-hover" aria-expanded="false" aria-label="missing_summary: &#34;missing_summary&#34; is not defined" data-editor-severity="error" data-editor-diagnostic="citry.template.unknown-variable"><span class="n">missing_summary</span></button> <span class="p">}}</span><span class="p">&lt;</span><span class="p">/</span><span class="nt">p</span><span class="p">&gt;</span>&#10;&#10;        <span class="p">&lt;</span><span class="nt">form</span>&#10;          <span class="na">@submit.prevent</span><span class="o">=</span><span class="p">&#34;</span><button type="button" class="landing-editor__symbol" data-editor-annotation="send-event" data-editor-symbol="send-event" data-editor-signature="function $sendEvent(name: &#34;invite&#34;, data: InviteIn): Promise&lt;void&gt;" data-editor-signature-html="&lt;span class=&#34;kd&#34;&gt;function&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;$sendEvent&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;quot;invite&amp;quot;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;data&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;InviteIn&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;Promise&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;ow&#34;&gt;void&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;gt;&lt;/span&gt;" data-editor-language="typescript" data-editor-provenance="Citry browser API" data-editor-description="Calls a typed Python handler without leaving Alpine." data-editor-docs="/events/bindings/" aria-controls="landing-editor-hover" aria-expanded="false" aria-label="$sendEvent: function $sendEvent(name: &#34;invite&#34;, data: InviteIn): Promise&lt;void&gt;"><span class="nx">$sendEvent</span></button><span class="p">(</span><span class="s1">&#39;</span><button type="button" class="landing-editor__symbol" data-editor-annotation="event-name" data-editor-symbol="event-name" data-editor-signature="(event) invite(data: InviteIn): void" data-editor-signature-html="&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;event&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;invite&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;data&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;InviteIn&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;ow&#34;&gt;void&lt;/span&gt;" data-editor-language="typescript" data-editor-provenance="Matches InvitePanel.Events.invite" data-editor-description="Event names complete, validate, and navigate to Python." data-editor-docs="/events/bindings/" aria-controls="landing-editor-hover" aria-expanded="false" aria-label="invite: (event) invite(data: InviteIn): void" data-editor-target="event-invite"><span class="s1">invite</span></button><span class="s1">&#39;</span><span class="p">,</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">email</span><span class="w"> </span><span class="p">}</span><span class="p">)</span><span class="p">&#34;</span>&#10;          <span class="na">:aria-busy</span><span class="o">=</span><span class="p">&#34;</span><button type="button" class="landing-editor__symbol" data-editor-annotation="loading-magic" data-editor-symbol="loading-magic" data-editor-signature="function $loading(event: &#34;invite&#34;): boolean" data-editor-signature-html="&lt;span class=&#34;kd&#34;&gt;function&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;$loading&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;event&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;quot;invite&amp;quot;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;boolean&lt;/span&gt;" data-editor-language="typescript" data-editor-provenance="Citry browser API" data-editor-description="Tracks the named Python event for this component instance." data-editor-docs="/events/bindings/#read-call-state-from-alpine" aria-controls="landing-editor-hover" aria-expanded="false" aria-label="$loading: function $loading(event: &#34;invite&#34;): boolean"><span class="nx">$loading</span></button><span class="p">(</span><span class="s1">&#39;invite&#39;</span><span class="p">)</span><span class="p">&#34;</span>&#10;        <span class="p">&gt;</span>&#10;          <span class="p">&lt;</span><span class="nt">input</span> <span class="na">x-model</span><span class="o">=</span><span class="p">&#34;</span><button type="button" class="landing-editor__symbol" data-editor-annotation="email-use" data-editor-symbol="email-use" data-editor-signature="(variable) email: string" data-editor-signature-html="&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;variable&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;email&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;string&lt;/span&gt;" data-editor-language="typescript" data-editor-provenance="Assigned by InvitePanel.$component" data-editor-description="Go to Definition opens the direct scope assignment." data-editor-docs="/ide/vscode/#complete-alpine-and-component-javascript" aria-controls="landing-editor-hover" aria-expanded="false" aria-label="email: (variable) email: string" data-editor-target="scope-email"><span class="nx">email</span></button><span class="p">&#34;</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;email&#34;</span> <span class="p">/</span><span class="p">&gt;</span>&#10;          <span class="p">&lt;</span><span class="nt">button</span> <span class="na">:disabled</span><span class="o">=</span><span class="p">&#34;</span><button type="button" class="landing-editor__symbol" data-editor-annotation="inviting-use" data-editor-symbol="inviting-use" data-editor-signature="(variable) inviting: boolean" data-editor-signature-html="&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;variable&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;inviting&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;boolean&lt;/span&gt;" data-editor-language="typescript" data-editor-provenance="Inferred from js_data()" data-editor-description="Go to Definition opens the exact returned dictionary key." data-editor-docs="/ide/vscode/#complete-alpine-and-component-javascript" aria-controls="landing-editor-hover" aria-expanded="false" aria-label="inviting: (variable) inviting: boolean" data-editor-target="js-inviting"><span class="nx">inviting</span></button><span class="w"> </span><span class="o">||</span><span class="w"> </span><button type="button" class="landing-editor__symbol landing-editor__symbol--error" data-editor-annotation="unknown-alpine-variable" data-editor-symbol="unknown-alpine-variable" data-editor-signature="&#34;queuedInvite&#34; is not defined" data-editor-signature-html="&lt;span class=&#34;s2&#34;&gt;&amp;quot;queuedInvite&amp;quot;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;is&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;not&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;defined&lt;/span&gt;" data-editor-language="typescript" data-editor-provenance="Error · citry.alpine.unknown-variable" data-editor-description="Alpine variable &#39;queuedInvite&#39; is not available in this component." data-editor-docs="/ide/diagnostics/#citry.alpine.unknown-variable" aria-controls="landing-editor-hover" aria-expanded="false" aria-label="queuedInvite: &#34;queuedInvite&#34; is not defined" data-editor-severity="error" data-editor-diagnostic="citry.alpine.unknown-variable"><span class="nx">queuedInvite</span></button><span class="p">&#34;</span><span class="p">&gt;</span>&#10;            Invite member&#10;          <span class="p">&lt;</span><span class="p">/</span><span class="nt">button</span><span class="p">&gt;</span>&#10;          <span class="p">&lt;</span><span class="nt">small</span> <span class="na">x-show</span><span class="o">=</span><span class="p">&#34;</span><span class="nx">$error</span><span class="p">(</span><span class="s1">&#39;</span><button type="button" class="landing-editor__symbol landing-editor__symbol--error" data-editor-annotation="unknown-event" data-editor-symbol="unknown-event" data-editor-signature="Server event &#34;invte&#34; is not declared" data-editor-signature-html="&lt;span class=&#34;nx&#34;&gt;Server&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;event&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;quot;invte&amp;quot;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;is&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;not&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;declared&lt;/span&gt;" data-editor-language="typescript" data-editor-provenance="Error · citry.browser.unknown-server-event" data-editor-description="Server event &#39;invte&#39; is not declared by this component." data-editor-docs="/ide/diagnostics/#citry.browser.unknown-server-event" aria-controls="landing-editor-hover" aria-expanded="false" aria-label="invte: Server event &#34;invte&#34; is not declared" data-editor-severity="error" data-editor-diagnostic="citry.browser.unknown-server-event"><span class="s1">invte</span></button><span class="s1">&#39;</span><span class="p">)</span><span class="p">&#34;</span><span class="p">&gt;</span>&#10;            Could not send invite.&#10;          <span class="p">&lt;</span><span class="p">/</span><span class="nt">small</span><span class="p">&gt;</span>&#10;        <span class="p">&lt;</span><span class="p">/</span><span class="nt">form</span><span class="p">&gt;</span>&#10;      <span class="p">&lt;</span><span class="p">/</span><span class="nt">section</span><span class="p">&gt;</span>&#10;    <span class="sd">&#34;&#34;&#34;</span><span class="w">&#10;</span><span class="w">&#10;</span>    <span class="nv">js</span> <span class="o">=</span> <span class="sd">&#34;&#34;&#34;</span><span class="w">&#10;      </span><button type="button" class="landing-editor__symbol" data-editor-annotation="component-api" data-editor-symbol="component-api" data-editor-signature="function $component(definition: ComponentDefinition): void" data-editor-signature-html="&lt;span class=&#34;kd&#34;&gt;function&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;$component&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;definition&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;ComponentDefinition&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;ow&#34;&gt;void&lt;/span&gt;" data-editor-language="typescript" data-editor-provenance="Citry component JavaScript API" data-editor-description="Adds typed client props and setup to this component." data-editor-docs="/getting-started/client-props-and-handlers/" aria-controls="landing-editor-hover" aria-expanded="false" aria-label="$component: function $component(definition: ComponentDefinition): void"><span class="nx">$component</span></button><span class="p">(</span><span class="p">{</span><span class="w">&#10;        </span><span class="nx">props</span><span class="o">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">compact</span><span class="o">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">type</span><span class="o">:</span><span class="w"> </span><span class="nb">Boolean</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="p">,</span><span class="w">&#10;        </span><span class="nx">init</span><span class="o">:</span><span class="w"> </span><span class="p">(</span><span class="p">{</span><span class="w"> </span><button type="button" class="landing-editor__symbol" data-editor-annotation="data-parameter" data-editor-symbol="data-parameter" data-editor-signature="(parameter) data: Readonly&lt;InvitePanelData&gt;" data-editor-signature-html="&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;parameter&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;data&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;Readonly&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;InvitePanelData&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;gt;&lt;/span&gt;" data-editor-language="typescript" data-editor-provenance="Inferred from js_data()" data-editor-description="The callback sees the JSON-safe shape produced by Python." data-editor-docs="/ide/vscode/#complete-alpine-and-component-javascript" aria-controls="landing-editor-hover" aria-expanded="false" aria-label="data: (parameter) data: Readonly&lt;InvitePanelData&gt;"><span class="nx">data</span></button><span class="p">,</span><span class="w"> </span><button type="button" class="landing-editor__symbol" data-editor-annotation="scope-parameter" data-editor-symbol="scope-parameter" data-editor-signature="(parameter) scope: AlpineScope" data-editor-signature-html="&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;parameter&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;scope&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;AlpineScope&lt;/span&gt;" data-editor-language="typescript" data-editor-provenance="Citry $component context" data-editor-description="Direct assignments become typed Alpine variables." data-editor-docs="/concepts/client-interactivity/" aria-controls="landing-editor-hover" aria-expanded="false" aria-label="scope: (parameter) scope: AlpineScope"><span class="nx">scope</span></button><span class="p">,</span><span class="w"> </span><button type="button" class="landing-editor__symbol" data-editor-annotation="props-parameter" data-editor-symbol="props-parameter" data-editor-signature="(parameter) props: Readonly&lt;{ compact?: boolean }&gt;" data-editor-signature-html="&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;parameter&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;props&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;Readonly&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;compact?&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;boolean&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;gt;&lt;/span&gt;" data-editor-language="typescript" data-editor-provenance="Declared by InvitePanel.$component" data-editor-description="The callback receives the component&#39;s validated client props." data-editor-docs="/getting-started/client-props-and-handlers/" aria-controls="landing-editor-hover" aria-expanded="false" aria-label="props: (parameter) props: Readonly&lt;{ compact?: boolean }&gt;"><span class="nx">props</span></button><span class="p">,</span><span class="w"> </span><button type="button" class="landing-editor__symbol" data-editor-annotation="effect-parameter" data-editor-symbol="effect-parameter" data-editor-signature="function effect(callback: () =&gt; void): CitryCleanup" data-editor-signature-html="&lt;span class=&#34;kd&#34;&gt;function&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;effect&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;callback&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;ow&#34;&gt;void&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;CitryCleanup&lt;/span&gt;" data-editor-language="typescript" data-editor-provenance="Citry $component context" data-editor-description="Runs a reactive effect and disposes it with the component." data-editor-docs="/getting-started/client-props-and-handlers/" aria-controls="landing-editor-hover" aria-expanded="false" aria-label="effect: function effect(callback: () =&gt; void): CitryCleanup"><span class="nx">effect</span></button><span class="w"> </span><span class="p">}</span><span class="p">)</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">&#10;          </span><span class="nx">scope</span><span class="p">.</span><span class="landing-editor__definition" data-editor-annotation="email-definition" id="landing-editor-definition-scope-email" data-editor-definition="scope-email" tabindex="-1"><span class="nx">email</span></span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">&#34;&#34;</span><span class="p">;</span><span class="w">&#10;          </span><span class="nx">effect</span><span class="p">(</span><span class="p">(</span><span class="p">)</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">&#10;            </span><span class="nx">scope</span><span class="p">.</span><span class="landing-editor__definition" data-editor-annotation="visible-members-definition" id="landing-editor-definition-visible-members" data-editor-definition="visible-members" tabindex="-1"><span class="nx">visibleMembers</span></span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">props</span><span class="p">.</span><span class="nx">compact</span><span class="w">&#10;              </span><span class="o">?</span><span class="w"> </span><span class="nx">data</span><span class="p">.</span><button type="button" class="landing-editor__symbol" data-editor-annotation="data-members-slice-use" data-editor-symbol="data-members-slice-use" data-editor-signature="(property) InvitePanelData.members: Member[]" data-editor-signature-html="&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;property&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;InvitePanelData.members&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;Member&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[]&lt;/span&gt;" data-editor-language="typescript" data-editor-provenance="Inferred from js_data()" data-editor-description="Go to Definition opens the exact returned dictionary key." data-editor-docs="/ide/vscode/#complete-alpine-and-component-javascript" aria-controls="landing-editor-hover" aria-expanded="false" aria-label="members: (property) InvitePanelData.members: Member[]" data-editor-target="js-members"><span class="nx">members</span></button><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="mf">0</span><span class="p">,</span><span class="w"> </span><span class="mf">3</span><span class="p">)</span><span class="w">&#10;              </span><span class="o">:</span><span class="w"> </span><span class="nx">data</span><span class="p">.</span><button type="button" class="landing-editor__symbol" data-editor-annotation="data-members-fallback-use" data-editor-symbol="data-members-fallback-use" data-editor-signature="(property) InvitePanelData.members: Member[]" data-editor-signature-html="&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;property&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;InvitePanelData.members&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;Member&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[]&lt;/span&gt;" data-editor-language="typescript" data-editor-provenance="Inferred from js_data()" data-editor-description="Every use shares the Python key&#39;s inferred Member[] type." data-editor-docs="/ide/vscode/#complete-alpine-and-component-javascript" aria-controls="landing-editor-hover" aria-expanded="false" aria-label="members: (property) InvitePanelData.members: Member[]" data-editor-target="js-members"><span class="nx">members</span></button><span class="p">;</span><span class="w">&#10;          </span><span class="p">}</span><span class="p">)</span><span class="p">;</span><span class="w">&#10;        </span><span class="p">}</span><span class="p">,</span><span class="w">&#10;      </span><span class="p">}</span><span class="p">)</span><span class="p">;</span><span class="w">&#10;    </span><span class="sd">&#34;&#34;&#34;</span><span class="w">&#10;</span><span class="w">&#10;</span>    <span class="k">class</span><span class="w"> </span><span class="nc">Kwargs</span><span class="p">:</span><span class="w">&#10;</span>        <span class="landing-editor__definition" data-editor-annotation="kwarg-title-definition" id="landing-editor-definition-kwarg-title" data-editor-definition="kwarg-title" tabindex="-1"><span class="n">title</span></span><span class="p">:</span> <span class="nb">str</span><span class="w">&#10;</span>        <span class="landing-editor__definition" data-editor-annotation="kwarg-members-definition" id="landing-editor-definition-kwarg-members" data-editor-definition="kwarg-members" tabindex="-1"><span class="n">members</span></span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><button type="button" class="landing-editor__symbol" data-editor-annotation="member-type-use" data-editor-symbol="member-type-use" data-editor-signature="class Member(TypedDict)" data-editor-signature-html="&lt;span class=&#34;k&#34;&gt;class&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nc&#34;&gt;Member&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;TypedDict&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;" data-editor-language="python" data-editor-provenance="Type of each InvitePanel member" data-editor-description="Go to Type Definition opens the TypedDict above." data-editor-docs="/ide/vscode/" aria-controls="landing-editor-hover" aria-expanded="false" aria-label="Member: class Member(TypedDict)" data-editor-target="member-type"><span class="n">Member</span></button><span class="p">]</span><span class="w">&#10;</span><span class="w">&#10;</span>    <span class="k">class</span><span class="w"> </span><span class="nc">Events</span><span class="p">:</span><span class="w">&#10;</span>        <span class="k">def</span><span class="w"> </span><span class="landing-editor__definition" data-editor-annotation="event-definition" id="landing-editor-definition-event-invite" data-editor-definition="event-invite" tabindex="-1"><span class="nf">invite</span></span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">data</span><span class="p">:</span> <button type="button" class="landing-editor__symbol" data-editor-annotation="invite-type-use" data-editor-symbol="invite-type-use" data-editor-signature="class InviteIn(TypedDict)" data-editor-signature-html="&lt;span class=&#34;k&#34;&gt;class&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nc&#34;&gt;InviteIn&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;TypedDict&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;" data-editor-language="python" data-editor-provenance="Payload accepted by invite" data-editor-description="Go to Type Definition opens the event payload declaration." data-editor-docs="/events/" aria-controls="landing-editor-hover" aria-expanded="false" aria-label="InviteIn: class InviteIn(TypedDict)" data-editor-target="invite-type"><span class="n">InviteIn</span></button><span class="p">)</span> <span class="o">-</span><span class="o">&gt;</span> <span class="kc">None</span><span class="p">:</span><span class="w">&#10;</span>            <span class="n">send_invite</span><span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="s2">&#34;</span><span class="s2">email</span><span class="s2">&#34;</span><span class="p">]</span><span class="p">)</span><span class="w">&#10;</span><span class="w">&#10;</span>    <span class="k">def</span><span class="w"> </span><span class="nf">template_data</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">kwargs</span><span class="p">,</span> <span class="n">slots</span><span class="p">)</span><span class="p">:</span><span class="w">&#10;</span>        <span class="k">return</span> <span class="p">{</span><span class="landing-editor__definition" data-editor-annotation="template-title-definition" id="landing-editor-definition-template-title" data-editor-definition="template-title" tabindex="-1"><span class="s2">&#34;</span><span class="s2">title</span><span class="s2">&#34;</span></span><span class="p">:</span> <span class="n">kwargs</span><span class="o">.</span><button type="button" class="landing-editor__symbol" data-editor-annotation="kwargs-title-use" data-editor-symbol="kwargs-title-use" data-editor-signature="(property) InvitePanel.Kwargs.title: str" data-editor-signature-html="&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;property&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;InvitePanel&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Kwargs&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;title&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;str&lt;/span&gt;" data-editor-language="python" data-editor-provenance="Declared by InvitePanel.Kwargs" data-editor-description="Definition follows this access to the input declaration." data-editor-docs="/ide/vscode/" aria-controls="landing-editor-hover" aria-expanded="false" aria-label="title: (property) InvitePanel.Kwargs.title: str" data-editor-target="kwarg-title"><span class="n">title</span></button><span class="p">}</span><span class="w">&#10;</span><span class="w">&#10;</span>    <span class="k">def</span><span class="w"> </span><span class="nf">js_data</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">kwargs</span><span class="p">,</span> <span class="n">slots</span><span class="p">)</span><span class="p">:</span><span class="w">&#10;</span>        <span class="k">return</span> <span class="p">{</span><span class="w">&#10;</span>            <span class="landing-editor__definition" data-editor-annotation="js-members-definition" id="landing-editor-definition-js-members" data-editor-definition="js-members" tabindex="-1"><span class="s2">&#34;</span><span class="s2">members</span><span class="s2">&#34;</span></span><span class="p">:</span> <span class="n">kwargs</span><span class="o">.</span><button type="button" class="landing-editor__symbol" data-editor-annotation="kwargs-members-use" data-editor-symbol="kwargs-members-use" data-editor-signature="(property) InvitePanel.Kwargs.members: list[Member]" data-editor-signature-html="&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;property&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;InvitePanel&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Kwargs&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;members&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;list&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Member&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;" data-editor-language="python" data-editor-provenance="Declared by InvitePanel.Kwargs" data-editor-description="Definition follows this access to the input declaration." data-editor-docs="/ide/vscode/" aria-controls="landing-editor-hover" aria-expanded="false" aria-label="members: (property) InvitePanel.Kwargs.members: list[Member]" data-editor-target="kwarg-members"><span class="n">members</span></button><span class="p">,</span><span class="w">&#10;</span>            <span class="landing-editor__definition" data-editor-annotation="js-inviting-definition" id="landing-editor-definition-js-inviting" data-editor-definition="js-inviting" tabindex="-1"><span class="s2">&#34;</span><span class="s2">inviting</span><span class="s2">&#34;</span></span><span class="p">:</span> <span class="kc">False</span><span class="p">,</span><span class="w">&#10;</span>        <span class="p">}</span><span class="w">&#10;</span><span class="w">&#10;</span><span class="w">&#10;</span><span class="k">class</span><span class="w"> </span><span class="landing-editor__definition" data-editor-annotation="member-chip-definition" id="landing-editor-definition-member-chip" data-editor-definition="member-chip" tabindex="-1"><span class="nc">MemberChip</span></span><span class="p">(</span><span class="n">Component</span><span class="p">)</span><span class="p">:</span><span class="w">&#10;</span>    <span class="k">class</span><span class="w"> </span><span class="nc">Kwargs</span><span class="p">:</span><span class="w">&#10;</span>        <span class="landing-editor__definition" data-editor-annotation="member-chip-name-definition" id="landing-editor-definition-member-chip-name" data-editor-definition="member-chip-name" tabindex="-1"><span class="n">name</span></span><span class="p">:</span> <span class="nb">str</span><span class="w">&#10;</span>        <span class="landing-editor__definition" data-editor-annotation="member-chip-status-definition" id="landing-editor-definition-member-chip-status" data-editor-definition="member-chip-status" tabindex="-1"><span class="n">status</span></span><span class="p">:</span> <span class="n">CitryRender</span><span class="w">&#10;</span><span class="w">&#10;</span>    <span class="nv">template</span> <span class="o">=</span> <span class="sd">&#34;&#34;&#34;</span>&#10;      <span class="p">&lt;</span><span class="nt">span</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;member-chip&#34;</span><span class="p">&gt;</span>&#10;        <span class="p">{{</span> <span class="n">name</span> <span class="p">}}</span>&#10;        <span class="p">{{</span> <span class="n">status</span> <span class="p">}}</span>&#10;      <span class="p">&lt;</span><span class="p">/</span><span class="nt">span</span><span class="p">&gt;</span>&#10;    <span class="sd">&#34;&#34;&#34;</span><span class="w">&#10;</span><span class="w">&#10;</span>    <span class="nv">js</span> <span class="o">=</span> <span class="sd">&#34;&#34;&#34;</span><span class="w">&#10;      </span><span class="nx">$component</span><span class="p">(</span><span class="p">{</span><span class="w">&#10;        </span><span class="nx">props</span><span class="o">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="landing-editor__definition" data-editor-annotation="member-chip-online-definition" id="landing-editor-definition-member-chip-online" data-editor-definition="member-chip-online" tabindex="-1"><span class="nx">online</span></span><span class="o">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nx">type</span><span class="o">:</span><span class="w"> </span><span class="nb">Boolean</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="p">,</span><span class="w">&#10;      </span><span class="p">}</span><span class="p">)</span><span class="p">;</span><span class="w">&#10;    </span><span class="sd">&#34;&#34;&#34;</span><span class="w">&#10;</span><span class="w">&#10;</span><span class="w">&#10;</span><span class="k">class</span><span class="w"> </span><span class="landing-editor__definition" data-editor-annotation="member-type-definition" id="landing-editor-definition-member-type" data-editor-definition="member-type" tabindex="-1"><span class="nc">Member</span></span><span class="p">(</span><span class="n">TypedDict</span><span class="p">)</span><span class="p">:</span><span class="w">&#10;</span>    <span class="landing-editor__definition" data-editor-annotation="member-name-definition" id="landing-editor-definition-member-name" data-editor-definition="member-name" tabindex="-1"><span class="n">name</span></span><span class="p">:</span> <span class="nb">str</span><span class="w">&#10;</span>    <span class="landing-editor__definition" data-editor-annotation="member-online-definition" id="landing-editor-definition-member-online" data-editor-definition="member-online" tabindex="-1"><span class="n">online</span></span><span class="p">:</span> <span class="nb">bool</span><span class="w">&#10;</span><span class="w">&#10;</span><span class="w">&#10;</span><span class="k">class</span><span class="w"> </span><span class="landing-editor__definition" data-editor-annotation="invite-type-definition" id="landing-editor-definition-invite-type" data-editor-definition="invite-type" tabindex="-1"><span class="nc">InviteIn</span></span><span class="p">(</span><span class="n">TypedDict</span><span class="p">)</span><span class="p">:</span><span class="w">&#10;</span>    <span class="n">email</span><span class="p">:</span> <span class="nb">str</span><span class="w">&#10;</span></pre></div><p class="landing-editor__status" data-editor-status aria-live="polite"></p></div><div class="landing-editor__notes"><button class="landing-editor__note" type="button" data-editor-note="title-use"><strong>Data from Python functions is recognized in template variables.</strong><small><code>template_data()</code> returned <code>&#34;title&#34;</code>, which gives the <code>title</code> template variable its type.</small></button><button class="landing-editor__note" type="button" data-editor-note="nested-html-attribute"><strong>Nested templates keep full editor intelligence.</strong><small>Citry treats the inside of <code>c-status</code> as a nested template. A naive HTML linter would see just a string.</small></button><button class="landing-editor__note" type="button" data-editor-note="event-name"><strong>Alpine expressions respect Python declarations.</strong><small><code>$sendEvent</code>&#39;s input is typed from the Python event handler.</small></button><button class="landing-editor__note" type="button" data-editor-note="email-use"><strong>Alpine variables defined in $component are recognized.</strong><small><code>email</code> and <code>visibleMembers</code> were defined in <code>$component</code>. The linter knows these are not unknown variables.</small></button><button class="landing-editor__note" type="button" data-editor-note="unknown-alpine-variable"><strong>Unknown names and event typos are reported as errors.</strong><small>Focus or select an error to see its exact message.</small></button></div></div></div>




</section>

<section class="landing-section" id="capabilities" markdown="1">
## Build fast with 70+ ready-made components.

<p class="landing-section__intro">
<a href="/ui-library/">Citry UI</a>
gives you accessible, themeable components for layout, forms, actions,
navigation, feedback, and data display.
<a href="/ui-library/installation/">Install here.</a>
</p>

<div markdown="0"><div class="landing-composer" data-landing-composer><div class="landing-composer__bar"><div class="landing-composer__bar-copy"><h3 id="landing-composer-palette-title">Citry UI components</h3><p>Drag a component onto the canvas and watch it arrive ready to use.</p></div><button type="button" class="landing-composer__reset" data-composer-reset>Reset</button></div><div class="landing-composer__layout"><aside class="landing-composer__palette" aria-labelledby="landing-composer-palette-title"><div class="landing-composer__palette-group"><h4>Actions</h4><ul><li data-composer-palette-item><button type="button" class="landing-composer__palette-item" data-composer-palette-drag="button" data-composer-kind="action" aria-label="Button. Drag to the sample page or press Enter to place it."><span class="landing-composer__grip" aria-hidden="true">⠿</span><strong>Button</strong></button></li><li data-composer-palette-item><button type="button" class="landing-composer__palette-item" data-composer-palette-drag="button-group" data-composer-kind="content" aria-label="Button group. Drag to the sample page or press Enter to place it."><span class="landing-composer__grip" aria-hidden="true">⠿</span><strong>Button group</strong></button></li></ul></div><div class="landing-composer__palette-group"><h4>Forms and inputs</h4><ul><li data-composer-palette-item><button type="button" class="landing-composer__palette-item" data-composer-palette-drag="checkbox" data-composer-kind="control" aria-label="Checkbox. Drag to the sample page or press Enter to place it."><span class="landing-composer__grip" aria-hidden="true">⠿</span><strong>Checkbox</strong></button></li><li data-composer-palette-item><button type="button" class="landing-composer__palette-item" data-composer-palette-drag="field-input" data-composer-kind="content" aria-label="Field and input. Drag to the sample page or press Enter to place it."><span class="landing-composer__grip" aria-hidden="true">⠿</span><strong>Field and input</strong></button></li><li data-composer-palette-item><button type="button" class="landing-composer__palette-item" data-composer-palette-drag="switch" data-composer-kind="control" aria-label="Switch. Drag to the sample page or press Enter to place it."><span class="landing-composer__grip" aria-hidden="true">⠿</span><strong>Switch</strong></button></li></ul></div><div class="landing-composer__palette-group"><h4>Layout</h4><ul><li data-composer-palette-item><button type="button" class="landing-composer__palette-item" data-composer-palette-drag="group" data-composer-kind="layout" aria-label="Group. Drag to the sample page or press Enter to place it."><span class="landing-composer__grip" aria-hidden="true">⠿</span><strong>Group</strong></button></li><li data-composer-palette-item><button type="button" class="landing-composer__palette-item" data-composer-palette-drag="container" data-composer-kind="layout" aria-label="Container. Drag to the sample page or press Enter to place it."><span class="landing-composer__grip" aria-hidden="true">⠿</span><strong>Container</strong></button></li><li data-composer-palette-item><button type="button" class="landing-composer__palette-item" data-composer-palette-drag="grid" data-composer-kind="layout" aria-label="Grid. Drag to the sample page or press Enter to place it."><span class="landing-composer__grip" aria-hidden="true">⠿</span><strong>Grid</strong></button></li><li data-composer-palette-item><button type="button" class="landing-composer__palette-item" data-composer-palette-drag="divider" data-composer-kind="content" aria-label="Divider. Drag to the sample page or press Enter to place it."><span class="landing-composer__grip" aria-hidden="true">⠿</span><strong>Divider</strong></button></li></ul></div><div class="landing-composer__palette-group"><h4>Data display</h4><ul><li data-composer-palette-item><button type="button" class="landing-composer__palette-item" data-composer-palette-drag="badge" data-composer-kind="content" aria-label="Badge. Drag to the sample page or press Enter to place it."><span class="landing-composer__grip" aria-hidden="true">⠿</span><strong>Badge</strong></button></li><li data-composer-palette-item><button type="button" class="landing-composer__palette-item" data-composer-palette-drag="card" data-composer-kind="content" aria-label="Card. Drag to the sample page or press Enter to place it."><span class="landing-composer__grip" aria-hidden="true">⠿</span><strong>Card</strong></button></li><li data-composer-palette-item><button type="button" class="landing-composer__palette-item" data-composer-palette-drag="list" data-composer-kind="content" aria-label="List. Drag to the sample page or press Enter to place it."><span class="landing-composer__grip" aria-hidden="true">⠿</span><strong>List</strong></button></li></ul></div><div class="landing-composer__palette-group"><h4>Navigation</h4><ul><li data-composer-palette-item><button type="button" class="landing-composer__palette-item" data-composer-palette-drag="tabs" data-composer-kind="content" aria-label="Tabs. Drag to the sample page or press Enter to place it."><span class="landing-composer__grip" aria-hidden="true">⠿</span><strong>Tabs</strong></button></li></ul></div><div class="landing-composer__palette-group"><h4>Feedback and status</h4><ul><li data-composer-palette-item><button type="button" class="landing-composer__palette-item" data-composer-palette-drag="alert" data-composer-kind="content" aria-label="Alert. Drag to the sample page or press Enter to place it."><span class="landing-composer__grip" aria-hidden="true">⠿</span><strong>Alert</strong></button></li><li data-composer-palette-item><button type="button" class="landing-composer__palette-item" data-composer-palette-drag="progress" data-composer-kind="content" aria-label="Progress. Drag to the sample page or press Enter to place it."><span class="landing-composer__grip" aria-hidden="true">⠿</span><strong>Progress</strong></button></li></ul></div></aside><section class="landing-composer__workspace" aria-label="Component sample page"><div class="landing-composer__board" data-composer-board><div class="landing-composer__drag-cue" data-composer-drag-cue aria-hidden="true"> Release over a blue area </div><div class="landing-composer__canvas" data-composer-canvas><button type="button" class="landing-composer__drop landing-composer__drop--root" data-composer-drop data-composer-accepts="layout content action control" aria-pressed="true"><span aria-hidden="true"></span><strong>Drop a component here</strong><small>Start with a Card, Grid, or anything you like.</small></button></div></div></section></div><script type="application/json" data-composer-recipe-bank>"\u003cstyle>\n      @layer citry-ui.theme {\n        :where(.cui-button) {\n          --_cui-button-tone: LinkText;\n          --_cui-button-on-tone: Canvas;\n          --_cui-button-background-default: var(--_cui-button-tone);\n          --_cui-button-foreground-default: var(--_cui-button-on-tone);\n          --_cui-button-border-default: transparent;\n          --_cui-button-hover-default: color-mix(\n            in srgb,\n            var(--_cui-button-tone),\n            CanvasText 12%\n          );\n          --_cui-button-active-default: color-mix(\n            in srgb,\n            var(--_cui-button-tone),\n            CanvasText 22%\n          );\n          --_cui-button-background: var(\n            --cui-button-background,\n            var(--_cui-button-background-default)\n          );\n          --_cui-button-foreground: var(\n            --cui-button-foreground,\n            var(--_cui-button-foreground-default)\n          );\n          --_cui-button-border-color: var(\n            --cui-button-border-color,\n            var(--_cui-button-border-default)\n          );\n          --_cui-button-hover-background: var(\n            --cui-button-hover-background,\n            var(--_cui-button-hover-default)\n          );\n          --_cui-button-active-background: var(\n            --cui-button-active-background,\n            var(--_cui-button-active-default)\n          );\n          --_cui-button-focus-color: var(--cui-button-focus-color, Highlight);\n          --_cui-button-radius: var(--cui-button-radius, 0.5rem);\n          --_cui-button-font-weight: var(--cui-button-font-weight, 600);\n          --_cui-button-gap: var(--cui-button-gap, 0.5rem);\n          --_cui-button-disabled-opacity: var(--cui-button-disabled-opacity, 0.48);\n          --_cui-button-height: var(--cui-button-height, 2.5rem);\n          --_cui-button-inline-padding: var(--cui-button-inline-padding, 1rem);\n          --_cui-button-block-padding: var(--cui-button-block-padding, 0.5rem);\n          --_cui-button-font-size: var(--cui-button-font-size, 0.9375rem);\n\n          position: relative;\n          display: inline-flex;\n          box-sizing: border-box;\n          align-items: center;\n          justify-content: center;\n          gap: var(--_cui-button-gap);\n          min-block-size: var(--_cui-button-height);\n          margin: 0;\n          padding-block: var(--_cui-button-block-padding);\n          padding-inline: var(--_cui-button-inline-padding);\n          border: 1px solid var(--_cui-button-border-color);\n          border-radius: var(--_cui-button-radius);\n          background: var(--_cui-button-background);\n          color: var(--_cui-button-foreground);\n          font: inherit;\n          font-size: var(--_cui-button-font-size);\n          font-weight: var(--_cui-button-font-weight);\n          line-height: 1.25;\n          text-align: center;\n          text-decoration: none;\n          vertical-align: middle;\n          cursor: pointer;\n          isolation: isolate;\n          -webkit-tap-highlight-color: transparent;\n        }\n\n        :where(.cui-button[data-intent=\"neutral\"]) {\n          --_cui-button-tone: CanvasText;\n          --_cui-button-on-tone: Canvas;\n        }\n\n        :where(.cui-button[data-intent=\"success\"]) {\n          --_cui-button-tone: light-dark(#067647, #75e0a7);\n          --_cui-button-on-tone: light-dark(#ffffff, #102a1d);\n        }\n\n        :where(.cui-button[data-intent=\"warn\"]) {\n          --_cui-button-tone: light-dark(#b54708, #fec84b);\n          --_cui-button-on-tone: light-dark(#ffffff, #1d2939);\n        }\n\n        :where(.cui-button[data-intent=\"danger\"]) {\n          --_cui-button-tone: light-dark(#b42318, #fda29b);\n          --_cui-button-on-tone: light-dark(#ffffff, #3f0e0a);\n        }\n\n        :where(.cui-button[data-variant=\"outline\"]) {\n          --_cui-button-background-default: transparent;\n          --_cui-button-foreground-default: var(--_cui-button-tone);\n          --_cui-button-border-default: var(--_cui-button-tone);\n          --_cui-button-hover-default: color-mix(\n            in srgb,\n            var(--_cui-button-tone) 10%,\n            transparent\n          );\n          --_cui-button-active-default: color-mix(\n            in srgb,\n            var(--_cui-button-tone) 18%,\n            transparent\n          );\n        }\n\n        :where(.cui-button[data-variant=\"ghost\"]) {\n          --_cui-button-background-default: transparent;\n          --_cui-button-foreground-default: var(--_cui-button-tone);\n          --_cui-button-border-default: transparent;\n          --_cui-button-hover-default: color-mix(\n            in srgb,\n            var(--_cui-button-tone) 10%,\n            transparent\n          );\n          --_cui-button-active-default: color-mix(\n            in srgb,\n            var(--_cui-button-tone) 18%,\n            transparent\n          );\n        }\n\n        :where(.cui-button[data-size=\"sm\"]) {\n          --_cui-button-height: var(--cui-button-height, 2.25rem);\n          --_cui-button-inline-padding: var(--cui-button-inline-padding, 0.75rem);\n          --_cui-button-block-padding: var(--cui-button-block-padding, 0.375rem);\n          --_cui-button-font-size: var(--cui-button-font-size, 0.875rem);\n        }\n\n        :where(.cui-button[data-size=\"lg\"]) {\n          --_cui-button-height: var(--cui-button-height, 2.75rem);\n          --_cui-button-inline-padding: var(--cui-button-inline-padding, 1.25rem);\n          --_cui-button-block-padding: var(--cui-button-block-padding, 0.625rem);\n          --_cui-button-font-size: var(--cui-button-font-size, 1rem);\n        }\n\n        :where(.cui-button[data-block]) {\n          display: flex;\n          inline-size: 100%;\n        }\n\n        @media (hover: hover) {\n          :where(\n            .cui-button:not(:disabled):not([data-disabled]):not([data-loading]):hover\n          ) {\n            background: var(--_cui-button-hover-background);\n          }\n        }\n\n        :where(\n          .cui-button:not(:disabled):not([data-disabled]):not([data-loading]):active\n        ) {\n          background: var(--_cui-button-active-background);\n        }\n\n        :where(.cui-button:focus-visible) {\n          outline: 0.1875rem solid var(--_cui-button-focus-color);\n          outline-offset: 0.1875rem;\n        }\n\n        :where(\n          .cui-button:disabled,\n          .cui-button[data-disabled],\n          .cui-button[data-loading]\n        ) {\n          cursor: not-allowed;\n        }\n\n        :where(.cui-button:disabled, .cui-button[data-disabled]) {\n          opacity: var(--_cui-button-disabled-opacity);\n        }\n\n        :where(.cui-button__content) {\n          min-inline-size: 0;\n        }\n\n        :where(.cui-button__decoration, .cui-button__loading) {\n          display: inline-flex;\n          flex: 0 0 auto;\n          align-items: center;\n          justify-content: center;\n          inline-size: 1em;\n          block-size: 1em;\n        }\n\n        :where(.cui-button__loading) {\n          position: absolute;\n          inset-block-start: 50%;\n          translate: 0 -50%;\n        }\n\n        :where(.cui-button__loading[hidden]) {\n          display: none;\n        }\n\n        :where(.cui-button[data-loading-position=\"start\"] .cui-button__loading) {\n          inset-inline-start: var(--_cui-button-inline-padding);\n        }\n\n        :where(.cui-button[data-loading-position=\"center\"] .cui-button__loading) {\n          inset-inline-start: 50%;\n          translate: -50% -50%;\n        }\n\n        :where(.cui-button[data-loading-position=\"end\"] .cui-button__loading) {\n          inset-inline-end: var(--_cui-button-inline-padding);\n        }\n\n        :where(\n          .cui-button[data-loading][data-loading-position=\"center\"]\n          .cui-button__decoration\n        ),\n        :where(\n          .cui-button[data-loading][data-loading-position=\"center\"]\n          .cui-button__content\n        ),\n        :where(\n          .cui-button[data-loading][data-loading-position=\"start\"]\n          :is(\n            [data-citry-ui-part=\"start\"],\n            [data-citry-ui-part=\"split-button-primary-start\"]\n          )\n        ),\n        :where(\n          .cui-button[data-loading][data-loading-position=\"end\"]\n          :is(\n            [data-citry-ui-part=\"end\"],\n            [data-citry-ui-part=\"split-button-primary-end\"]\n          )\n        ) {\n          opacity: 0;\n        }\n\n        :where(\n          .cui-button[data-loading][data-loading-position=\"start\"]:not(\n            [data-citry-button-has-start]\n          )\n          .cui-button__content\n        ) {\n          margin-inline-start: calc(1em + var(--_cui-button-gap));\n        }\n\n        :where(\n          .cui-button[data-loading][data-loading-position=\"end\"]:not(\n            [data-citry-button-has-end]\n          )\n          .cui-button__content\n        ) {\n          margin-inline-end: calc(1em + var(--_cui-button-gap));\n        }\n\n        :where(.cui-button__spinner) {\n          display: block;\n          box-sizing: border-box;\n          inline-size: 1em;\n          block-size: 1em;\n          border: 0.125em solid currentColor;\n          border-inline-end-color: transparent;\n          border-radius: 50%;\n          animation: cui-button-spin 700ms linear infinite;\n        }\n\n        @media (prefers-reduced-motion: reduce) {\n          :where(.cui-button__spinner) {\n            border-inline-end-color: currentColor;\n            border-block-start-color: transparent;\n            animation: none;\n          }\n        }\n\n        @media (forced-colors: active) {\n          :where(.cui-button) {\n            border-color: ButtonText;\n          }\n\n          :where(.cui-button:disabled, .cui-button[data-disabled]) {\n            border-color: GrayText;\n            color: GrayText;\n            opacity: 1;\n          }\n        }\n\n        @keyframes cui-button-spin {\n          to {\n            rotate: 1turn;\n          }\n        }\n      }\n    \u003c/style>\u003cstyle data-citry-css-class=\"CButtonGroup_ebe63b\">@layer citry-ui.theme{:where([data-citry-ui-part=button-group]){--_cui-button-group-gap:var(--cui-button-group-gap,.5rem);--_cui-button-group-radius:var(--cui-button-group-radius,.55rem);--_cui-button-group-border-width:var(--cui-button-group-border-width,1px);align-items:stretch;gap:var(--_cui-button-group-gap);vertical-align:middle;flex-direction:row;min-inline-size:0;max-inline-size:100%;display:inline-flex}:where([data-citry-ui-part=button-group][data-orientation=vertical]){flex-direction:column}:where([data-citry-ui-part=button-group][data-grow]){inline-size:100%;display:flex}:where([data-citry-ui-part=button-group][data-grow])>:where([data-citry-ui-part=button]){flex:1 1 0;min-inline-size:0}:where([data-citry-ui-part=button-group][data-attached]){gap:0}:where([data-citry-ui-part=button-group][data-attached])>:where([data-citry-ui-part=button]){border-radius:0;position:relative}:where([data-citry-ui-part=button-group][data-attached][data-orientation=horizontal])>:where([data-citry-ui-part=button]:first-child){border-start-start-radius:var(--_cui-button-group-radius);border-end-start-radius:var(--_cui-button-group-radius)}:where([data-citry-ui-part=button-group][data-attached][data-orientation=horizontal])>:where([data-citry-ui-part=button]:last-child){border-start-end-radius:var(--_cui-button-group-radius);border-end-end-radius:var(--_cui-button-group-radius)}:where([data-citry-ui-part=button-group][data-attached][data-orientation=horizontal])>:where([data-citry-ui-part=button]+[data-citry-ui-part=button]){margin-inline-start:calc(-1 * var(--_cui-button-group-border-width))}:where([data-citry-ui-part=button-group][data-attached][data-orientation=vertical])>:where([data-citry-ui-part=button]:first-child){border-start-start-radius:var(--_cui-button-group-radius);border-start-end-radius:var(--_cui-button-group-radius)}:where([data-citry-ui-part=button-group][data-attached][data-orientation=vertical])>:where([data-citry-ui-part=button]:last-child){border-end-end-radius:var(--_cui-button-group-radius);border-end-start-radius:var(--_cui-button-group-radius)}:where([data-citry-ui-part=button-group][data-attached][data-orientation=vertical])>:where([data-citry-ui-part=button]+[data-citry-ui-part=button]){margin-block-start:calc(-1 * var(--_cui-button-group-border-width))}:where([data-citry-ui-part=button-group])>:where([data-citry-ui-part=button]:focus-visible){z-index:1}}\n\u003c/style>\u003cstyle data-citry-css-class=\"CInput_dc895e\">@layer citry-ui.theme{:where(.cui-input){--_cui-input-background:var(--cui-input-background,Canvas);--_cui-input-foreground:var(--cui-input-foreground,CanvasText);--_cui-input-border-color:var(--cui-input-border-color,color-mix(in srgb, CanvasText 38%, transparent));--_cui-input-hover-border-color:var(--cui-input-hover-border-color,color-mix(in srgb, CanvasText 68%, transparent));--_cui-input-focus-color:var(--cui-input-focus-color,Highlight);--_cui-input-invalid-border-color:var(--cui-input-invalid-border-color,light-dark(#d92d20,#f97066));--_cui-input-disabled-background:var(--cui-input-disabled-background,color-mix(in srgb, CanvasText 7%, Canvas));--_cui-input-placeholder-color:var(--cui-input-placeholder-color,color-mix(in srgb, CanvasText 55%, transparent));--_cui-input-radius:var(--cui-input-radius,.5rem);--_cui-input-height:var(--cui-input-height,2.5rem);--_cui-input-inline-padding:var(--cui-input-inline-padding,.75rem);--_cui-input-block-padding:var(--cui-input-block-padding,.5rem);--_cui-input-font-size:var(--cui-input-font-size,1rem);box-sizing:border-box;inline-size:100%;min-block-size:var(--_cui-input-height);padding-block:var(--_cui-input-block-padding);padding-inline:var(--_cui-input-inline-padding);border:1px solid var(--_cui-input-border-color);border-radius:var(--_cui-input-radius);background:var(--_cui-input-background);color:var(--_cui-input-foreground);font:inherit;font-size:var(--_cui-input-font-size);line-height:1.25}:where(.cui-input[data-variant=filled]){--_cui-input-background:var(--cui-input-background,color-mix(in srgb, CanvasText 7%, Canvas));--_cui-input-border-color:var(--cui-input-border-color,transparent)}:where(.cui-input[data-variant=plain]){--_cui-input-background:var(--cui-input-background,transparent);--_cui-input-border-color:var(--cui-input-border-color,transparent);--_cui-input-radius:var(--cui-input-radius,0)}:where(.cui-input[data-size=sm]){--_cui-input-height:var(--cui-input-height,2.25rem);--_cui-input-inline-padding:var(--cui-input-inline-padding,.625rem);--_cui-input-block-padding:var(--cui-input-block-padding,.375rem);--_cui-input-font-size:var(--cui-input-font-size,.875rem)}:where(.cui-input[data-size=lg]){--_cui-input-height:var(--cui-input-height,2.75rem);--_cui-input-inline-padding:var(--cui-input-inline-padding,.875rem);--_cui-input-block-padding:var(--cui-input-block-padding,.625rem);--_cui-input-font-size:var(--cui-input-font-size,1.0625rem)}@media (hover:hover){:where(.cui-input:not(:disabled):not([data-readonly]):hover){border-color:var(--_cui-input-hover-border-color)}}:where(.cui-input:focus-visible){border-color:var(--_cui-input-focus-color);outline:.1875rem solid color-mix(in srgb, var(--_cui-input-focus-color) 38%, transparent);outline-offset:.125rem}:where(.cui-input[data-invalid]){border-color:var(--_cui-input-invalid-border-color)}:where(.cui-input:disabled){background:var(--_cui-input-disabled-background);cursor:not-allowed}:where(.cui-input[readonly]){cursor:text}:where(.cui-input)::placeholder{color:var(--_cui-input-placeholder-color);opacity:1}@media (forced-colors:active){:where(.cui-input){border-color:buttontext}:where(.cui-input[data-invalid]){border-color:mark}}}\n\u003c/style>\u003cstyle data-citry-css-class=\"CField_81c723\">@layer citry-ui.theme{:where(.cui-field){--_cui-field-gap:var(--cui-field-gap,.5rem);--_cui-field-label-color:var(--cui-field-label-color,CanvasText);--_cui-field-label-weight:var(--cui-field-label-weight,600);--_cui-field-description-color:var(--cui-field-description-color,color-mix(in srgb, CanvasText 68%, transparent));--_cui-field-error-color:var(--cui-field-error-color,light-dark(#b42318,#fda29b));--_cui-field-required-color:var(--cui-field-required-color,var(--_cui-field-error-color));gap:var(--_cui-field-gap);color:canvastext;min-inline-size:0;font-family:ui-sans-serif,system-ui,sans-serif;display:grid}:where(.cui-field[data-orientation=horizontal]){grid-template-columns:minmax(8rem,.35fr) minmax(0,1fr);align-items:start}:where(.cui-field[data-density=comfortable]){--_cui-field-gap:var(--cui-field-gap,.375rem)}:where(.cui-field[data-density=compact]){--_cui-field-gap:var(--cui-field-gap,.25rem)}:where(.cui-field>[data-citry-ui-part=label]){color:var(--_cui-field-label-color);font-weight:var(--_cui-field-label-weight)}:where(.cui-field__required){color:var(--_cui-field-required-color)}:where(.cui-field>[data-citry-ui-part=description]){color:var(--_cui-field-description-color);font-size:.875em}:where(.cui-field>[data-citry-ui-part=error]){color:var(--_cui-field-error-color);font-size:.875em}@media (width\u003c=32rem){:where(.cui-field[data-orientation=horizontal]){grid-template-columns:minmax(0,1fr)}}@media (forced-colors:active){:where(.cui-field[data-invalid]>[data-citry-ui-part=error]){color:mark}}}\n\u003c/style>\u003cstyle data-citry-css-class=\"CCheckbox_05d638\">@layer citry-ui.theme{:where(.cui-checkbox){--_cui-checkbox-background:var(--cui-checkbox-background,Canvas);--_cui-checkbox-foreground:var(--cui-checkbox-foreground,CanvasText);--_cui-checkbox-border-color:var(--cui-checkbox-border-color,color-mix(in srgb, CanvasText 42%, transparent));--_cui-checkbox-hover-border-color:var(--cui-checkbox-hover-border-color,color-mix(in srgb, CanvasText 72%, transparent));--_cui-checkbox-active-color:var(--cui-checkbox-active-color,light-dark(#2563eb,#60a5fa));--_cui-checkbox-indicator-color:var(--cui-checkbox-indicator-color,light-dark(#fff,#0b1220));--_cui-checkbox-focus-color:var(--cui-checkbox-focus-color,Highlight);--_cui-checkbox-invalid-color:var(--cui-checkbox-invalid-color,light-dark(#b42318,#f97066));--_cui-checkbox-disabled-opacity:var(--cui-checkbox-disabled-opacity,.55);--_cui-checkbox-control-size:var(--cui-checkbox-control-size,1.125rem);--_cui-checkbox-radius:var(--cui-checkbox-radius,.3rem);--_cui-checkbox-gap:var(--cui-checkbox-gap,.625rem);--_cui-checkbox-description-color:var(--cui-checkbox-description-color,color-mix(in srgb, CanvasText 68%, transparent));--_cui-checkbox-description-gap:var(--cui-checkbox-description-gap,.2rem);align-items:flex-start;gap:var(--_cui-checkbox-gap);min-inline-size:0;color:var(--_cui-checkbox-foreground);vertical-align:middle;font-family:ui-sans-serif,system-ui,sans-serif;font-size:1rem;line-height:1.4;display:inline-flex}:where(.cui-checkbox[data-label-pos=start]){flex-direction:row-reverse}:where(.cui-checkbox[data-size=sm]){--_cui-checkbox-control-size:var(--cui-checkbox-control-size,1rem);--_cui-checkbox-gap:var(--cui-checkbox-gap,.5rem);font-size:.875rem}:where(.cui-checkbox[data-size=lg]){--_cui-checkbox-control-size:var(--cui-checkbox-control-size,1.25rem);--_cui-checkbox-gap:var(--cui-checkbox-gap,.75rem);font-size:1.0625rem}:where(.cui-checkbox__input){appearance:none;box-sizing:border-box;inline-size:var(--_cui-checkbox-control-size);block-size:var(--_cui-checkbox-control-size);border:1px solid var(--_cui-checkbox-border-color);border-radius:var(--_cui-checkbox-radius);background:var(--_cui-checkbox-background);color:var(--_cui-checkbox-indicator-color);cursor:pointer;font:inherit;flex:none;place-content:center;margin:.15em 0 0;transition:border-color .12s,background-color .12s;display:grid}:where(.cui-checkbox__input):after{content:\"\";box-sizing:border-box;opacity:0;display:block}:where(.cui-checkbox__input:checked){border-color:var(--_cui-checkbox-active-color);background:var(--_cui-checkbox-active-color)}:where(.cui-checkbox__input:checked):after{opacity:1;border:.13em solid;border-width:0 .13em .13em 0;block-size:.62em;inline-size:.32em;transform:translateY(-.04em)rotate(45deg)}:where(.cui-checkbox__input:indeterminate){border-color:var(--_cui-checkbox-active-color);background:var(--_cui-checkbox-active-color)}:where(.cui-checkbox__input:indeterminate):after{opacity:1;background:currentColor;border-radius:999px;block-size:.13em;inline-size:.58em}:where(.cui-checkbox[data-variant=outline] .cui-checkbox__input:checked),:where(.cui-checkbox[data-variant=outline] .cui-checkbox__input:indeterminate){--_cui-checkbox-indicator-color:var(--cui-checkbox-indicator-color,var(--_cui-checkbox-active-color));background:var(--_cui-checkbox-background);color:var(--_cui-checkbox-indicator-color)}:where(.cui-checkbox__input:focus-visible){border-color:var(--_cui-checkbox-focus-color);outline:.1875rem solid color-mix(in srgb, var(--_cui-checkbox-focus-color) 38%, transparent);outline-offset:.125rem}@media (hover:hover){:where(.cui-checkbox:not([data-disabled]) .cui-checkbox__input:hover){border-color:var(--_cui-checkbox-hover-border-color)}}:where(.cui-checkbox[data-invalid] .cui-checkbox__input){border-color:var(--_cui-checkbox-invalid-color)}:where(.cui-checkbox__body){gap:var(--_cui-checkbox-description-gap);min-inline-size:0;display:grid}:where(.cui-checkbox [data-citry-ui-part=label]){min-inline-size:0;color:inherit;cursor:pointer;overflow-wrap:anywhere}:where(.cui-checkbox[data-disabled]),:where(.cui-checkbox:has(>.cui-checkbox__input:disabled)){opacity:var(--_cui-checkbox-disabled-opacity)}:where(.cui-checkbox[data-disabled] .cui-checkbox__input),:where(.cui-checkbox[data-disabled] [data-citry-ui-part=label]),:where(.cui-checkbox>.cui-checkbox__input:disabled),:where(.cui-checkbox:has(>.cui-checkbox__input:disabled) [data-citry-ui-part=label]){cursor:not-allowed}:where(.cui-checkbox [data-citry-ui-part=description]){min-inline-size:0;color:var(--_cui-checkbox-description-color);overflow-wrap:anywhere;font-size:.875em}@media (prefers-reduced-motion:reduce){:where(.cui-checkbox__input){transition:none}}@media (forced-colors:active){:where(.cui-checkbox__input){forced-color-adjust:auto;border-color:buttontext}:where(.cui-checkbox[data-invalid] .cui-checkbox__input){border-color:mark}}@media print{:where(.cui-checkbox__input){print-color-adjust:exact}}}\n\u003c/style>\u003cstyle data-citry-css-class=\"CSwitch_fa49a4\">@layer citry-ui.theme{:where(.cui-switch){--_cui-switch-off-color:var(--cui-switch-off-color,light-dark(#667085,#98a2b3));--_cui-switch-on-color:var(--cui-switch-on-color,light-dark(#175cd3,#84adff));--_cui-switch-thumb-color:var(--cui-switch-thumb-color,Canvas);--_cui-switch-foreground:var(--cui-switch-foreground,CanvasText);--_cui-switch-focus-color:var(--cui-switch-focus-color,Highlight);--_cui-switch-invalid-color:var(--cui-switch-invalid-color,light-dark(#b42318,#f97066));--_cui-switch-disabled-opacity:var(--cui-switch-disabled-opacity,.52);--_cui-switch-width:var(--cui-switch-width,2.5rem);--_cui-switch-height:var(--cui-switch-height,1.5rem);--_cui-switch-padding:var(--cui-switch-padding,.1875rem);--_cui-switch-gap:var(--cui-switch-gap,.625rem);--_cui-switch-duration:var(--cui-switch-duration,.14s);color:var(--_cui-switch-foreground);font:inherit;line-height:1.35;display:inline-grid;position:relative}:where(.cui-switch[data-size=sm]){--_cui-switch-width:var(--cui-switch-width,2rem);--_cui-switch-height:var(--cui-switch-height,1.25rem);font-size:.8125rem}:where(.cui-switch[data-size=lg]){--_cui-switch-width:var(--cui-switch-width,3rem);--_cui-switch-height:var(--cui-switch-height,1.75rem);font-size:1rem}:where(.cui-switch__input){inline-size:var(--_cui-switch-width);block-size:var(--_cui-switch-height);opacity:0;cursor:pointer;z-index:1;margin:0;position:absolute}:where(.cui-switch [data-citry-ui-part=surface]){align-items:flex-start;gap:var(--_cui-switch-gap);cursor:pointer;display:inline-flex}:where(.cui-switch [data-citry-ui-part=track]){box-sizing:border-box;inline-size:var(--_cui-switch-width);block-size:var(--_cui-switch-height);padding:var(--_cui-switch-padding);background:var(--_cui-switch-off-color);transition:background-color var(--_cui-switch-duration) ease;border:1px solid #0000;border-radius:999px;flex:none;align-items:center;display:inline-flex}:where(.cui-switch [data-citry-ui-part=thumb]){box-sizing:border-box;inline-size:calc(var(--_cui-switch-height) - 2 * var(--_cui-switch-padding) - 2px);block-size:calc(var(--_cui-switch-height) - 2 * var(--_cui-switch-padding) - 2px);background:var(--_cui-switch-thumb-color);transition:transform var(--_cui-switch-duration) ease;border-radius:50%;display:block;transform:translate(0);box-shadow:0 1px 2px #00000040}:where(.cui-switch__input:checked+[data-citry-ui-part=surface] [data-citry-ui-part=track]){background:var(--_cui-switch-on-color)}:where(.cui-switch__input:checked+[data-citry-ui-part=surface] [data-citry-ui-part=thumb]){transform:translateX(calc(var(--_cui-switch-width) - var(--_cui-switch-height)))}:where(.cui-switch:dir(rtl) .cui-switch__input:checked+[data-citry-ui-part=surface] [data-citry-ui-part=thumb]){transform:translateX(calc((var(--_cui-switch-width) - var(--_cui-switch-height)) * -1))}:where(.cui-switch__input:focus-visible+[data-citry-ui-part=surface] [data-citry-ui-part=track]){outline:.1875rem solid color-mix(in srgb, var(--_cui-switch-focus-color) 42%, transparent);outline-offset:.125rem}:where(.cui-switch[data-invalid] [data-citry-ui-part=track]){box-shadow:0 0 0 2px var(--_cui-switch-invalid-color)}:where(.cui-switch[data-disabled]){opacity:var(--_cui-switch-disabled-opacity)}:where(.cui-switch[data-disabled] .cui-switch__input),:where(.cui-switch[data-disabled] [data-citry-ui-part=surface]){cursor:not-allowed}:where(.cui-switch [data-citry-ui-part=body]){gap:.2rem;min-inline-size:0;display:grid}:where(.cui-switch [data-citry-ui-part=label],.cui-switch [data-citry-ui-part=description]){overflow-wrap:anywhere;min-inline-size:0}:where(.cui-switch [data-citry-ui-part=description]){color:color-mix(in srgb, currentColor 72%, transparent);font-size:.82em}:where(.cui-switch[data-label-pos=start] [data-citry-ui-part=surface]){flex-direction:row-reverse}@media (prefers-reduced-motion:reduce){:where(.cui-switch [data-citry-ui-part=track],.cui-switch [data-citry-ui-part=thumb]){transition-duration:.01ms}}@media (forced-colors:active){:where(.cui-switch [data-citry-ui-part=track]){background:canvas;border-color:buttontext}:where(.cui-switch__input:checked+[data-citry-ui-part=surface] [data-citry-ui-part=track]){background:highlight}:where(.cui-switch [data-citry-ui-part=thumb]){box-shadow:none;background:buttontext}}@media print{:where(.cui-switch [data-citry-ui-part=track]){background:0 0;border-color:currentColor}:where(.cui-switch__input:checked+[data-citry-ui-part=surface] [data-citry-ui-part=track]){background:currentColor}}}\n\u003c/style>\u003cstyle data-citry-css-class=\"CAlert_0cc501\">@layer citry-ui.theme{:where(.cui-alert){--_cui-alert-intent-color:light-dark(#175cd3,#84adff);--_cui-alert-solid-background:light-dark(#175cd3,#194185);--_cui-alert-solid-foreground:#fff;--_cui-alert-background:var(--cui-alert-background,color-mix(in srgb, var(--_cui-alert-intent-color) 11%, Canvas));--_cui-alert-foreground:var(--cui-alert-foreground,CanvasText);--_cui-alert-border-color:var(--cui-alert-border-color,color-mix(in srgb, var(--_cui-alert-intent-color) 40%, Canvas));--_cui-alert-icon-color:var(--cui-alert-icon-color,var(--_cui-alert-intent-color));--_cui-alert-border-width:var(--cui-alert-border-width,1px);--_cui-alert-radius:var(--cui-alert-radius,.75rem);--_cui-alert-padding:var(--cui-alert-padding,.875rem 1rem);--_cui-alert-gap:var(--cui-alert-gap,.75rem);--_cui-alert-content-gap:var(--cui-alert-content-gap,.25rem);--_cui-alert-actions-gap:var(--cui-alert-actions-gap,.5rem);--_cui-alert-title-font-weight:var(--cui-alert-title-font-weight,650);--_cui-alert-icon-size:1.25rem;align-items:start;gap:var(--_cui-alert-gap);padding:var(--_cui-alert-padding);border-color:var(--_cui-alert-border-color);border-style:solid;border-width:var(--_cui-alert-border-width);border-radius:var(--_cui-alert-radius);background:var(--_cui-alert-background);color:var(--_cui-alert-foreground);grid-template-columns:max-content minmax(0,1fr);font-size:.875rem;line-height:1.5;display:grid;overflow:visible}:where(.cui-alert[data-intent=success]){--_cui-alert-intent-color:light-dark(#067647,#6ce9a6);--_cui-alert-solid-background:light-dark(#067647,#085d3a)}:where(.cui-alert[data-intent=warn]){--_cui-alert-intent-color:light-dark(#b54708,#fec84b);--_cui-alert-solid-background:light-dark(#b54708,#93370d)}:where(.cui-alert[data-intent=error]){--_cui-alert-intent-color:light-dark(#b42318,#fda29b);--_cui-alert-solid-background:light-dark(#b42318,#912018)}:where(.cui-alert[data-variant=solid]){--_cui-alert-background:var(--cui-alert-background,var(--_cui-alert-solid-background));--_cui-alert-foreground:var(--cui-alert-foreground,var(--_cui-alert-solid-foreground));--_cui-alert-border-color:var(--cui-alert-border-color,var(--_cui-alert-solid-background));--_cui-alert-icon-color:var(--cui-alert-icon-color,var(--_cui-alert-solid-foreground));--_cui-alert-border-width:var(--cui-alert-border-width,1px)}:where(.cui-alert[data-variant=outline]){--_cui-alert-background:var(--cui-alert-background,transparent);--_cui-alert-foreground:var(--cui-alert-foreground,CanvasText);--_cui-alert-border-color:var(--cui-alert-border-color,var(--_cui-alert-intent-color));--_cui-alert-icon-color:var(--cui-alert-icon-color,var(--_cui-alert-intent-color));--_cui-alert-border-width:var(--cui-alert-border-width,1px)}:where(.cui-alert[data-size=sm]){--_cui-alert-padding:var(--cui-alert-padding,.625rem .75rem);--_cui-alert-gap:var(--cui-alert-gap,.5rem);--_cui-alert-content-gap:var(--cui-alert-content-gap,.125rem);--_cui-alert-actions-gap:var(--cui-alert-actions-gap,.375rem);--_cui-alert-icon-size:1.125rem;font-size:.8125rem}:where(.cui-alert[data-size=lg]){--_cui-alert-padding:var(--cui-alert-padding,1rem 1.125rem);--_cui-alert-gap:var(--cui-alert-gap,.875rem);--_cui-alert-content-gap:var(--cui-alert-content-gap,.375rem);--_cui-alert-actions-gap:var(--cui-alert-actions-gap,.625rem);--_cui-alert-icon-size:1.5rem;font-size:1rem}:where(.cui-alert__indicator){inline-size:var(--_cui-alert-icon-size);block-size:var(--_cui-alert-icon-size);color:var(--_cui-alert-icon-color);pointer-events:none;user-select:none;place-items:center;display:grid}:where(.cui-alert__indicator[hidden]),:where(.cui-alert__glyph[data-cui-alert-hidden]){display:none!important}:where(.cui-alert__icon){stroke-width:2px;block-size:100%;inline-size:100%;display:block;overflow:visible}:where(.cui-alert__glyph--logical:dir(rtl)){transform-origin:50%;transform:scaleX(-1)}:where(.cui-alert__content){gap:var(--_cui-alert-content-gap);min-inline-size:0;display:grid;overflow:visible}:where(.cui-alert__title),:where(.cui-alert__message){overflow-wrap:anywhere;min-inline-size:0}:where(.cui-alert__title){font-weight:var(--_cui-alert-title-font-weight);line-height:1.35}:where(.cui-alert__title>:first-child),:where(.cui-alert__message>:first-child){margin-block-start:0}:where(.cui-alert__title>:last-child),:where(.cui-alert__message>:last-child){margin-block-end:0}:where(.cui-alert__actions){align-items:center;gap:var(--_cui-alert-actions-gap);overflow-wrap:anywhere;flex-wrap:wrap;grid-column:2;min-inline-size:0;display:flex;overflow:visible}:where(.cui-alert__actions>*){overflow-wrap:anywhere;min-inline-size:0;max-inline-size:100%}:where(.cui-alert__content :any-link),:where(.cui-alert__actions :any-link){color:inherit}:where(.cui-alert:not([data-icon]) .cui-alert__content),:where(.cui-alert:not([data-icon]) .cui-alert__actions){grid-column:1/-1}@media (forced-colors:active){:where(.cui-alert){color:canvastext;background:canvas;border-color:canvastext}:where(.cui-alert__indicator){color:canvastext}}@media print{:where(.cui-alert){color:#000;box-shadow:none;background:0 0;border-color:currentColor}:where(.cui-alert__indicator){color:currentColor}}}\n\u003c/style>\u003cstyle data-citry-css-class=\"CContainer_3b98db\">@layer citry-ui.theme{:where([data-citry-ui-part=container]){--_cui-container-max-width:var(--cui-container-max-width,80rem);--_cui-container-gutter:var(--cui-container-gutter,1rem);box-sizing:border-box;inline-size:100%;max-inline-size:var(--_cui-container-max-width);min-inline-size:0;padding-inline:var(--_cui-container-gutter);margin-inline:auto}:where([data-citry-ui-part=container][data-size=sm]){--_cui-container-max-width:var(--cui-container-max-width,40rem)}:where([data-citry-ui-part=container][data-size=md]){--_cui-container-max-width:var(--cui-container-max-width,48rem)}:where([data-citry-ui-part=container][data-size=lg]){--_cui-container-max-width:var(--cui-container-max-width,64rem)}:where([data-citry-ui-part=container][data-size=xxl]){--_cui-container-max-width:var(--cui-container-max-width,96rem)}:where([data-citry-ui-part=container][data-fluid]){max-inline-size:none}:where([data-citry-ui-part=container][data-gutter=\"0\"]){--_cui-container-gutter:var(--cui-container-gutter,0)}:where([data-citry-ui-part=container][data-gutter=xs]){--_cui-container-gutter:var(--cui-container-gutter,.25rem)}:where([data-citry-ui-part=container][data-gutter=sm]){--_cui-container-gutter:var(--cui-container-gutter,.5rem)}:where([data-citry-ui-part=container][data-gutter=md]){--_cui-container-gutter:var(--cui-container-gutter,.75rem)}:where([data-citry-ui-part=container][data-gutter=xl]){--_cui-container-gutter:var(--cui-container-gutter,1.5rem)}}\n\u003c/style>\u003cstyle data-citry-css-class=\"CBadge_20b1a5\">@layer citry-ui.theme{:where([data-citry-ui-part=badge]){--_cui-badge-background:var(--cui-badge-background,light-dark(#e7e5e4,#44403c));--_cui-badge-foreground:var(--cui-badge-foreground,light-dark(#292524,#fafaf9));--_cui-badge-border-color:var(--cui-badge-border-color,transparent);--_cui-badge-radius:var(--cui-badge-radius,.375rem);--_cui-badge-min-height:var(--cui-badge-min-height,1.5rem);--_cui-badge-padding-inline:var(--cui-badge-padding-inline,.5rem);--_cui-badge-gap:var(--cui-badge-gap,.25rem);--_cui-badge-font-size:var(--cui-badge-font-size,.75rem);--_cui-badge-font-weight:var(--cui-badge-font-weight,650);box-sizing:border-box;max-inline-size:100%;min-block-size:var(--_cui-badge-min-height);justify-content:center;align-items:center;gap:var(--_cui-badge-gap);padding-block:.125rem;padding-inline:var(--_cui-badge-padding-inline);border:1px solid var(--_cui-badge-border-color);border-radius:var(--_cui-badge-radius);background:var(--_cui-badge-background);color:var(--_cui-badge-foreground);font-size:var(--_cui-badge-font-size);font-weight:var(--_cui-badge-font-weight);text-align:center;vertical-align:middle;overflow-wrap:anywhere;line-height:1.2;display:inline-flex}:where([data-citry-ui-part=badge][data-intent=primary]){--_cui-badge-background:var(--cui-badge-background,light-dark(#dbeafe,#1e3a5f));--_cui-badge-foreground:var(--cui-badge-foreground,light-dark(#1e3a8a,#dbeafe))}:where([data-citry-ui-part=badge][data-intent=success]){--_cui-badge-background:var(--cui-badge-background,light-dark(#dcfce7,#163f2a));--_cui-badge-foreground:var(--cui-badge-foreground,light-dark(#14532d,#dcfce7))}:where([data-citry-ui-part=badge][data-intent=warn]){--_cui-badge-background:var(--cui-badge-background,light-dark(#fef3c7,#4b3514));--_cui-badge-foreground:var(--cui-badge-foreground,light-dark(#78350f,#fef3c7))}:where([data-citry-ui-part=badge][data-intent=danger]){--_cui-badge-background:var(--cui-badge-background,light-dark(#fee2e2,#531e24));--_cui-badge-foreground:var(--cui-badge-foreground,light-dark(#7f1d1d,#fee2e2))}:where([data-citry-ui-part=badge][data-variant=solid]){--_cui-badge-background:var(--cui-badge-background,light-dark(#44403c,#d6d3d1));--_cui-badge-foreground:var(--cui-badge-foreground,light-dark(#fff,#1c1917));--_cui-badge-border-color:var(--cui-badge-border-color,transparent)}:where([data-citry-ui-part=badge][data-variant=solid][data-intent=primary]){--_cui-badge-background:var(--cui-badge-background,light-dark(#1d4ed8,#60a5fa));--_cui-badge-foreground:var(--cui-badge-foreground,light-dark(#fff,#172554))}:where([data-citry-ui-part=badge][data-variant=solid][data-intent=success]){--_cui-badge-background:var(--cui-badge-background,light-dark(#15803d,#4ade80));--_cui-badge-foreground:var(--cui-badge-foreground,light-dark(#fff,#052e16))}:where([data-citry-ui-part=badge][data-variant=solid][data-intent=warn]){--_cui-badge-background:var(--cui-badge-background,light-dark(#b45309,#fbbf24));--_cui-badge-foreground:var(--cui-badge-foreground,light-dark(#fff,#422006))}:where([data-citry-ui-part=badge][data-variant=solid][data-intent=danger]){--_cui-badge-background:var(--cui-badge-background,light-dark(#b91c1c,#f87171));--_cui-badge-foreground:var(--cui-badge-foreground,light-dark(#fff,#450a0a))}:where([data-citry-ui-part=badge][data-variant=outline]){--_cui-badge-background:var(--cui-badge-background,transparent);--_cui-badge-border-color:var(--cui-badge-border-color,currentColor)}:where([data-citry-ui-part=badge][data-size=sm]){--_cui-badge-min-height:var(--cui-badge-min-height,1.25rem);--_cui-badge-padding-inline:var(--cui-badge-padding-inline,.375rem);--_cui-badge-gap:var(--cui-badge-gap,.1875rem);--_cui-badge-font-size:var(--cui-badge-font-size,.6875rem)}:where([data-citry-ui-part=badge][data-size=lg]){--_cui-badge-min-height:var(--cui-badge-min-height,1.75rem);--_cui-badge-padding-inline:var(--cui-badge-padding-inline,.625rem);--_cui-badge-gap:var(--cui-badge-gap,.3125rem);--_cui-badge-font-size:var(--cui-badge-font-size,.8125rem)}:where([data-citry-ui-part=badge][data-shape=pill]){--_cui-badge-radius:var(--cui-badge-radius,999px)}:where([data-citry-ui-part=start],[data-citry-ui-part=end]){color:inherit;flex:none;align-items:center;display:inline-flex}:where([data-citry-ui-part=start]>svg,[data-citry-ui-part=end]>svg){block-size:1em;inline-size:1em}:where([data-citry-ui-part=label]){overflow-wrap:anywhere;min-inline-size:0}@media (forced-colors:active){:where([data-citry-ui-part=badge]){--_cui-badge-background:var(--cui-badge-background,Canvas);--_cui-badge-foreground:var(--cui-badge-foreground,CanvasText);--_cui-badge-border-color:var(--cui-badge-border-color,CanvasText)}}@media print{:where([data-citry-ui-part=badge]){--_cui-badge-background:var(--cui-badge-background,transparent);--_cui-badge-foreground:var(--cui-badge-foreground,CanvasText);--_cui-badge-border-color:var(--cui-badge-border-color,currentColor)}}}\n\u003c/style>\u003cstyle data-citry-css-class=\"CRow_c22819\">@layer citry-ui.theme{:where([data-citry-ui-part=row]){--_cui-row-gap:var(--cui-row-gap,.5rem);justify-content:flex-start;align-items:center;gap:var(--_cui-row-gap);flex-flow:row;min-inline-size:0;display:flex}:where([data-citry-ui-part=row][data-wrap]){flex-wrap:wrap}:where([data-citry-ui-part=row][data-gap=\"0\"]){--_cui-row-gap:var(--cui-row-gap,0)}:where([data-citry-ui-part=row][data-gap=xs]){--_cui-row-gap:var(--cui-row-gap,.25rem)}:where([data-citry-ui-part=row][data-gap=md]){--_cui-row-gap:var(--cui-row-gap,.75rem)}:where([data-citry-ui-part=row][data-gap=lg]){--_cui-row-gap:var(--cui-row-gap,1rem)}:where([data-citry-ui-part=row][data-gap=xl]){--_cui-row-gap:var(--cui-row-gap,1.5rem)}:where([data-citry-ui-part=row][data-align=start]){align-items:flex-start}:where([data-citry-ui-part=row][data-align=end]){align-items:flex-end}:where([data-citry-ui-part=row][data-align=stretch]){align-items:stretch}:where([data-citry-ui-part=row][data-align=baseline]){align-items:baseline}:where([data-citry-ui-part=row][data-justify=center]){justify-content:center}:where([data-citry-ui-part=row][data-justify=end]){justify-content:flex-end}:where([data-citry-ui-part=row][data-justify=between]){justify-content:space-between}:where([data-citry-ui-part=row][data-justify=around]){justify-content:space-around}:where([data-citry-ui-part=row][data-justify=evenly]){justify-content:space-evenly}:where([data-citry-ui-part=row][data-reverse]){flex-direction:row-reverse}:where([data-citry-ui-part=row]>*){min-inline-size:0}}\n\u003c/style>\u003cstyle data-citry-css-class=\"CCard_19daae\">@layer citry-ui.theme{:where(.cui-card){--_cui-card-background-default:Canvas;--_cui-card-border-default:transparent;--_cui-card-shadow-default:0 .5rem 1.5rem color-mix(in srgb, CanvasText 14%, transparent);--_cui-card-background:var(--cui-card-background,var(--_cui-card-background-default));--_cui-card-foreground:var(--cui-card-foreground,CanvasText);--_cui-card-border-color:var(--cui-card-border-color,var(--_cui-card-border-default));--_cui-card-shadow:var(--cui-card-shadow,var(--_cui-card-shadow-default));--_cui-card-radius:var(--cui-card-radius,.75rem);--_cui-card-padding:var(--cui-card-padding,1rem);--_cui-card-section-gap:var(--cui-card-section-gap,.75rem);--_cui-card-actions-gap:var(--cui-card-actions-gap,.5rem);--_cui-card-actions-justify:var(--cui-card-actions-justify,flex-start);box-sizing:border-box;border:1px solid var(--_cui-card-border-color);border-radius:var(--_cui-card-radius);background:var(--_cui-card-background);min-inline-size:0;color:var(--_cui-card-foreground);box-shadow:var(--_cui-card-shadow);display:block;position:static;overflow:visible}:where(.cui-card[data-variant=outline]){--_cui-card-border-default:color-mix(in srgb, CanvasText 24%, transparent);--_cui-card-shadow-default:none}:where(.cui-card[data-variant=subtle]){--_cui-card-background-default:color-mix(in srgb, CanvasText 5%, Canvas);--_cui-card-shadow-default:none}:where(.cui-card[data-size=sm]){--_cui-card-padding:var(--cui-card-padding,.75rem);--_cui-card-section-gap:var(--cui-card-section-gap,.5rem);--_cui-card-actions-gap:var(--cui-card-actions-gap,.375rem)}:where(.cui-card[data-size=lg]){--_cui-card-padding:var(--cui-card-padding,1.25rem);--_cui-card-section-gap:var(--cui-card-section-gap,1rem);--_cui-card-actions-gap:var(--cui-card-actions-gap,.625rem)}:where(.cui-card>.cui-card__media){border-start-start-radius:var(--_cui-card-radius);border-start-end-radius:var(--_cui-card-radius);min-inline-size:0;overflow:clip}:where(.cui-card>.cui-card__media:only-child){border-end-end-radius:var(--_cui-card-radius);border-end-start-radius:var(--_cui-card-radius)}:where(.cui-card>.cui-card__media>img,.cui-card>.cui-card__media>picture,.cui-card>.cui-card__media>video,.cui-card>.cui-card__media>picture>img){block-size:auto;max-inline-size:100%;display:block}:where(.cui-card>.cui-card__header,.cui-card>.cui-card__footer){gap:var(--_cui-card-section-gap);min-inline-size:0;padding:var(--_cui-card-padding);grid-template-columns:minmax(0,1fr);align-items:center;display:grid}:where(.cui-card>.cui-card__row--split){grid-template-columns:minmax(0,1fr) auto}:where(.cui-card>.cui-card__header>.cui-card__header-content),:where(.cui-card>.cui-card__footer>.cui-card__footer-content){grid-column:1;min-inline-size:0}:where(.cui-card>.cui-card__body){min-inline-size:0;padding:var(--_cui-card-padding);overflow-wrap:anywhere}:where(.cui-card>.cui-card__header>.cui-card__header-actions,.cui-card>.cui-card__footer>.cui-card__actions){gap:var(--_cui-card-actions-gap);align-items:center;justify-content:var(--_cui-card-actions-justify);flex-wrap:wrap;grid-column:1;min-inline-size:0;display:flex}:where(.cui-card>.cui-card__row--split>.cui-card__header-actions,.cui-card>.cui-card__row--split>.cui-card__actions){grid-column:2}@media (forced-colors:active){:where(.cui-card){box-shadow:none;border-color:canvastext}}@media print{:where(.cui-card){box-shadow:none;border-color:currentColor}}}\n\u003c/style>\u003cstyle data-citry-css-class=\"CGridItem_c385c1\">@layer citry-ui.theme{:where([data-citry-ui-part=grid-item]){--_cui-grid-item-span-sm:initial;--_cui-grid-item-span-md:initial;--_cui-grid-item-span-lg:initial;--_cui-grid-item-span-xl:initial;--_cui-grid-item-span-xxl:initial;--_cui-grid-item-span-through-sm:var(--_cui-grid-item-span-sm,var(--_cui-grid-item-span-base));--_cui-grid-item-span-through-md:var(--_cui-grid-item-span-md,var(--_cui-grid-item-span-through-sm));--_cui-grid-item-span-through-lg:var(--_cui-grid-item-span-lg,var(--_cui-grid-item-span-through-md));--_cui-grid-item-span-through-xl:var(--_cui-grid-item-span-xl,var(--_cui-grid-item-span-through-lg));--_cui-grid-item-span-through-xxl:var(--_cui-grid-item-span-xxl,var(--_cui-grid-item-span-through-xl));--_cui-grid-item-effective-span:var(--_cui-grid-item-span-base);--_cui-grid-item-span:var(--cui-grid-item-span,var(--_cui-grid-item-effective-span));grid-column:span var(--_cui-grid-item-span) / span var(--_cui-grid-item-span);min-block-size:0;min-inline-size:0}@media (width>=40rem){:where([data-citry-ui-part=grid-item]){--_cui-grid-item-effective-span:var(--_cui-grid-item-span-through-sm)}}@media (width>=48rem){:where([data-citry-ui-part=grid-item]){--_cui-grid-item-effective-span:var(--_cui-grid-item-span-through-md)}}@media (width>=64rem){:where([data-citry-ui-part=grid-item]){--_cui-grid-item-effective-span:var(--_cui-grid-item-span-through-lg)}}@media (width>=80rem){:where([data-citry-ui-part=grid-item]){--_cui-grid-item-effective-span:var(--_cui-grid-item-span-through-xl)}}@media (width>=96rem){:where([data-citry-ui-part=grid-item]){--_cui-grid-item-effective-span:var(--_cui-grid-item-span-through-xxl)}}}\n\u003c/style>\u003cstyle data-citry-css-class=\"CGrid_4b2c8c\">@layer citry-ui.theme{:where([data-citry-ui-part=grid]){--_cui-grid-cols-sm:initial;--_cui-grid-cols-md:initial;--_cui-grid-cols-lg:initial;--_cui-grid-cols-xl:initial;--_cui-grid-cols-xxl:initial;--_cui-grid-min-column-input:initial;--_cui-grid-cols-through-sm:var(--_cui-grid-cols-sm,var(--_cui-grid-cols-base));--_cui-grid-cols-through-md:var(--_cui-grid-cols-md,var(--_cui-grid-cols-through-sm));--_cui-grid-cols-through-lg:var(--_cui-grid-cols-lg,var(--_cui-grid-cols-through-md));--_cui-grid-cols-through-xl:var(--_cui-grid-cols-xl,var(--_cui-grid-cols-through-lg));--_cui-grid-cols-through-xxl:var(--_cui-grid-cols-xxl,var(--_cui-grid-cols-through-xl));--_cui-grid-effective-columns:var(--_cui-grid-cols-base);--_cui-grid-columns:var(--cui-grid-columns,var(--_cui-grid-effective-columns));--_cui-grid-gap:var(--cui-grid-gap,.75rem);grid-template-columns:repeat(var(--_cui-grid-columns), minmax(0, 1fr));gap:var(--_cui-grid-gap);min-inline-size:0;display:grid}:where([data-citry-ui-part=grid][data-intrinsic]){--_cui-grid-min-column:var(--cui-grid-min-column,var(--_cui-grid-min-column-input,16rem));grid-template-columns:repeat(auto-fit, minmax(min(100%, var(--_cui-grid-min-column)), 1fr))}:where([data-citry-ui-part=grid][data-gap=\"0\"]){--_cui-grid-gap:var(--cui-grid-gap,0)}:where([data-citry-ui-part=grid][data-gap=xs]){--_cui-grid-gap:var(--cui-grid-gap,.25rem)}:where([data-citry-ui-part=grid][data-gap=sm]){--_cui-grid-gap:var(--cui-grid-gap,.5rem)}:where([data-citry-ui-part=grid][data-gap=lg]){--_cui-grid-gap:var(--cui-grid-gap,1rem)}:where([data-citry-ui-part=grid][data-gap=xl]){--_cui-grid-gap:var(--cui-grid-gap,1.5rem)}:where([data-citry-ui-part=grid]>*){min-block-size:0;min-inline-size:0}@media (width>=40rem){:where([data-citry-ui-part=grid]){--_cui-grid-effective-columns:var(--_cui-grid-cols-through-sm)}}@media (width>=48rem){:where([data-citry-ui-part=grid]){--_cui-grid-effective-columns:var(--_cui-grid-cols-through-md)}}@media (width>=64rem){:where([data-citry-ui-part=grid]){--_cui-grid-effective-columns:var(--_cui-grid-cols-through-lg)}}@media (width>=80rem){:where([data-citry-ui-part=grid]){--_cui-grid-effective-columns:var(--_cui-grid-cols-through-xl)}}@media (width>=96rem){:where([data-citry-ui-part=grid]){--_cui-grid-effective-columns:var(--_cui-grid-cols-through-xxl)}}}\n\u003c/style>\u003cstyle data-citry-css-class=\"CDivider_a9261f\">@layer citry-ui.theme{:where([data-citry-ui-part=divider]){--_cui-divider-color:var(--cui-divider-color,light-dark(#d6d3d1,#57534e));--_cui-divider-thickness:var(--cui-divider-thickness,1px);--_cui-divider-inset:var(--cui-divider-inset,1.5rem);--_cui-divider-label-gap:var(--cui-divider-label-gap,.75rem);--_cui-divider-label-color:var(--cui-divider-label-color,CanvasText);--_cui-divider-label-font-size:var(--cui-divider-label-font-size,.875rem);--_cui-divider-label-font-weight:var(--cui-divider-label-font-weight,600);--_cui-divider-min-length:var(--cui-divider-min-length,1em);box-sizing:border-box;min-inline-size:0;color:var(--_cui-divider-color);border:0;margin:0;padding:0}:where([data-citry-ui-part=divider][data-size=md]){--_cui-divider-thickness:var(--cui-divider-thickness,2px)}:where([data-citry-ui-part=divider][data-size=lg]){--_cui-divider-thickness:var(--cui-divider-thickness,4px)}:where([data-citry-ui-part=divider][data-orientation=horizontal]:not([data-labeled])){border-block-start-width:var(--_cui-divider-thickness);border-block-start-style:solid;border-block-start-color:var(--_cui-divider-color);block-size:0;inline-size:100%}:where([data-citry-ui-part=divider][data-orientation=vertical]){inline-size:0;min-block-size:var(--_cui-divider-min-length);border-inline-start-width:var(--_cui-divider-thickness);border-inline-start-style:solid;border-inline-start-color:var(--_cui-divider-color);align-self:stretch}:where([data-citry-ui-part=divider][data-variant=dashed]:not([data-labeled])){border-block-start-style:dashed}:where([data-citry-ui-part=divider][data-orientation=vertical][data-variant=dashed]){border-block-start-style:none;border-inline-start-style:dashed}:where([data-citry-ui-part=divider][data-variant=dotted]:not([data-labeled])){border-block-start-style:dotted}:where([data-citry-ui-part=divider][data-orientation=vertical][data-variant=dotted]){border-block-start-style:none;border-inline-start-style:dotted}:where([data-citry-ui-part=divider][data-orientation=horizontal][data-inset=start]){inline-size:calc(100% - var(--_cui-divider-inset));margin-inline-start:var(--_cui-divider-inset)}:where([data-citry-ui-part=divider][data-orientation=horizontal][data-inset=end]){inline-size:calc(100% - var(--_cui-divider-inset));margin-inline-end:var(--_cui-divider-inset)}:where([data-citry-ui-part=divider][data-orientation=horizontal][data-inset=both]){inline-size:calc(100% - var(--_cui-divider-inset) - var(--_cui-divider-inset));margin-inline:var(--_cui-divider-inset)}:where([data-citry-ui-part=divider][data-orientation=vertical][data-inset=start]){min-block-size:0;block-size:calc(100% - var(--_cui-divider-inset));margin-block-start:var(--_cui-divider-inset)}:where([data-citry-ui-part=divider][data-orientation=vertical][data-inset=end]){min-block-size:0;block-size:calc(100% - var(--_cui-divider-inset));margin-block-end:var(--_cui-divider-inset)}:where([data-citry-ui-part=divider][data-orientation=vertical][data-inset=both]){min-block-size:0;block-size:calc(100% - var(--_cui-divider-inset) - var(--_cui-divider-inset));margin-block:var(--_cui-divider-inset)}:where([data-citry-ui-part=divider][data-labeled]){align-items:center;gap:var(--_cui-divider-label-gap);inline-size:100%;display:flex}:where([data-citry-ui-part=divider]>[data-citry-ui-part=line]){box-sizing:border-box;border:0;border-block-start-width:var(--_cui-divider-thickness);border-block-start-style:solid;border-block-start-color:var(--_cui-divider-color);flex:1 1 0;min-inline-size:0;margin:0;padding:0}:where([data-citry-ui-part=divider][data-variant=dashed]>[data-citry-ui-part=line]){border-block-start-style:dashed}:where([data-citry-ui-part=divider][data-variant=dotted]>[data-citry-ui-part=line]){border-block-start-style:dotted}:where([data-citry-ui-part=divider]>[data-citry-ui-part=label]){min-inline-size:0;color:var(--_cui-divider-label-color);font-size:var(--_cui-divider-label-font-size);font-weight:var(--_cui-divider-label-font-weight);overflow-wrap:anywhere;line-height:1.3}:where([data-citry-ui-part=divider][data-label-pos=start]>[data-citry-ui-part=line]:first-child),:where([data-citry-ui-part=divider][data-label-pos=end]>[data-citry-ui-part=line]:last-child){flex:0 8%}@media (forced-colors:active){:where([data-citry-ui-part=divider]){--_cui-divider-color:var(--cui-divider-color,CanvasText);--_cui-divider-label-color:var(--cui-divider-label-color,CanvasText);forced-color-adjust:auto}}@media print{:where([data-citry-ui-part=divider]){--_cui-divider-color:var(--cui-divider-color,currentColor);--_cui-divider-label-color:var(--cui-divider-label-color,currentColor)}}}\n\u003c/style>\u003cstyle data-citry-css-class=\"CSkeleton_491a4a\">@layer citry-ui.theme{:where([data-citry-ui-part=skeleton]){--_cui-skeleton-width:var(--cui-skeleton-width,100%);--_cui-skeleton-height:var(--cui-skeleton-height,6rem);--_cui-skeleton-radius:var(--cui-skeleton-radius,.5rem);--_cui-skeleton-background:var(--cui-skeleton-background,light-dark(#e7e5e4,#44403c));--_cui-skeleton-highlight:var(--cui-skeleton-highlight,#ffffff73);--_cui-skeleton-gap:var(--cui-skeleton-gap,.5em);--_cui-skeleton-duration:var(--cui-skeleton-duration,1.5s);--_cui-skeleton-last-line-width:var(--cui-skeleton-last-line-width,70%);box-sizing:border-box;inline-size:min(100%, var(--_cui-skeleton-width));block-size:var(--_cui-skeleton-height);border-radius:var(--_cui-skeleton-radius);background:var(--_cui-skeleton-background);color:#0000;vertical-align:middle;pointer-events:none;user-select:none;display:inline-block;position:relative;overflow:hidden}:where([data-citry-ui-part=skeleton][data-kind=circle]){--_cui-skeleton-width:var(--cui-skeleton-width,3rem);--_cui-skeleton-height:var(--cui-skeleton-height,3rem);--_cui-skeleton-radius:var(--cui-skeleton-radius,50%);flex:none}:where([data-citry-ui-part=skeleton][data-kind=text]){--_cui-skeleton-height:var(--cui-skeleton-height,.75em);--_cui-skeleton-radius:var(--cui-skeleton-radius,999px);gap:var(--_cui-skeleton-gap);background:0 0;border-radius:0;block-size:auto;display:grid;overflow:visible}:where([data-citry-ui-part=line]){inline-size:100%;block-size:var(--_cui-skeleton-height);border-radius:var(--_cui-skeleton-radius);background:var(--_cui-skeleton-background);display:block;position:relative;overflow:hidden}:where([data-citry-ui-part=line][data-last]){inline-size:var(--_cui-skeleton-last-line-width)}:where([data-citry-ui-part=skeleton][data-animation=pulse]:not([data-kind=text]),[data-citry-ui-part=skeleton][data-animation=pulse] [data-citry-ui-part=line]){animation:cui-skeleton-pulse var(--_cui-skeleton-duration) ease-in-out infinite}:where([data-citry-ui-part=skeleton][data-animation=wave]:not([data-kind=text]),[data-citry-ui-part=skeleton][data-animation=wave] [data-citry-ui-part=line]):after{background:linear-gradient(90deg, transparent, var(--_cui-skeleton-highlight), transparent);content:\"\";animation:cui-skeleton-wave var(--_cui-skeleton-duration) ease-in-out infinite;position:absolute;inset:0;transform:translate(-100%)}@keyframes cui-skeleton-pulse{0%,to{opacity:1}50%{opacity:.45}}@keyframes cui-skeleton-wave{to{transform:translate(100%)}}@media (prefers-reduced-motion:reduce){:where([data-citry-ui-part=skeleton],[data-citry-ui-part=line]),:where([data-citry-ui-part=skeleton],[data-citry-ui-part=line]):after{animation:none!important}}@media (forced-colors:active){:where([data-citry-ui-part=skeleton]:not([data-kind=text]),[data-citry-ui-part=line]){background:canvas;border:1px solid canvastext}}@media print{:where([data-citry-ui-part=skeleton]:not([data-kind=text]),[data-citry-ui-part=line]){background:0 0;border:1px solid}}}\n\u003c/style>\u003cstyle data-citry-css-class=\"CList_34f50c\">@layer citry-ui.theme{:where([data-citry-ui-part=list]){--_cui-list-gap:var(--cui-list-gap,.25rem);--_cui-list-padding:var(--cui-list-padding,.35rem);--_cui-list-item-padding:var(--cui-list-item-padding,.7rem);--_cui-list-radius:var(--cui-list-radius,.65rem);--_cui-list-foreground:var(--cui-list-foreground,CanvasText);--_cui-list-muted:var(--cui-list-muted,light-dark(#57534e,#d6d3d1));--_cui-list-background:var(--cui-list-background,transparent);--_cui-list-hover-background:var(--cui-list-hover-background,light-dark(#f5f5f4,#292524));--_cui-list-current-background:var(--cui-list-current-background,light-dark(#dbeafe,#1e3a5f));--_cui-list-divider-color:var(--cui-list-divider-color,light-dark(#e7e5e4,#44403c));--_cui-list-marker-color:var(--cui-list-marker-color,currentColor);--_cui-list-focus-ring:var(--cui-list-focus-ring,Highlight);gap:var(--_cui-list-gap);padding:var(--_cui-list-padding);color:var(--_cui-list-foreground);background:var(--_cui-list-background);margin:0;list-style-position:outside;display:grid}:where([data-citry-ui-part=list][data-marker=none]){list-style:none}:where([data-citry-ui-part=list][data-marker=disc]){padding-inline-start:calc(var(--_cui-list-padding) + 1.25rem);list-style-type:disc}:where([data-citry-ui-part=list][data-marker=decimal]){padding-inline-start:calc(var(--_cui-list-padding) + 1.75rem);list-style-type:decimal}:where([data-citry-ui-part=list]>[data-citry-ui-part=list-item])::marker{color:var(--_cui-list-marker-color)}:where([data-citry-ui-part=list]>[data-citry-ui-part=list-item]){min-inline-size:0}:where([data-citry-ui-part=list]>[data-citry-ui-part=list-item]>[data-citry-ui-part=surface]){box-sizing:border-box;inline-size:100%;min-inline-size:0;padding:var(--_cui-list-item-padding);border-radius:var(--_cui-list-radius);color:inherit;font:inherit;text-align:start;background:0 0;border:0;grid-template-columns:minmax(0,1fr);align-items:center;gap:.75rem;text-decoration:none;display:grid}:where([data-citry-ui-part=list]>[data-citry-ui-part=list-item]>[data-citry-ui-part=surface][data-cui-list-has-start]){grid-template-columns:auto minmax(0,1fr)}:where([data-citry-ui-part=list]>[data-citry-ui-part=list-item]>[data-citry-ui-part=surface][data-cui-list-has-end]){grid-template-columns:minmax(0,1fr) auto}:where([data-citry-ui-part=list]>[data-citry-ui-part=list-item]>[data-citry-ui-part=surface][data-cui-list-has-start][data-cui-list-has-end]){grid-template-columns:auto minmax(0,1fr) auto}:where([data-citry-ui-part=list][data-variant=surface]>[data-citry-ui-part=list-item]>[data-citry-ui-part=surface]){background:var(--_cui-list-hover-background)}:where([data-citry-ui-part=list]>[data-citry-ui-part=list-item][data-current]>[data-citry-ui-part=surface]){background:var(--_cui-list-current-background);font-weight:650}:where([data-citry-ui-part=list]>[data-citry-ui-part=list-item][data-interactive]>[data-citry-ui-part=surface]:hover){background:var(--_cui-list-hover-background)}:where([data-citry-ui-part=list]>[data-citry-ui-part=list-item][data-disabled]){opacity:.55}:where([data-citry-ui-part=list]>[data-citry-ui-part=list-item]>[data-citry-ui-part=surface]:focus-visible){outline:3px solid var(--_cui-list-focus-ring);outline-offset:2px}:where([data-citry-ui-part=list][data-divided]>[data-citry-ui-part=list-item]+[data-citry-ui-part=list-item]){border-block-start:1px solid var(--_cui-list-divider-color);padding-block-start:var(--_cui-list-gap)}:where([data-citry-ui-part=list][data-density=compact]){--_cui-list-item-padding:.45rem .6rem;--_cui-list-gap:.1rem}:where([data-citry-ui-part=list]>[data-citry-ui-part=list-item]>[data-citry-ui-part=surface]>[data-citry-ui-part=body]){overflow-wrap:anywhere;min-inline-size:0}:where([data-citry-ui-part=list]>[data-citry-ui-part=list-item]>[data-citry-ui-part=surface]>[data-citry-ui-part=body]>[data-citry-ui-part=description]){color:var(--_cui-list-muted);overflow-wrap:anywhere;margin-block-start:.15rem;font-size:.875em;font-weight:400;display:block}:where([data-citry-ui-part=list]>[data-citry-ui-part=list-item]>[data-citry-ui-part=surface]>[data-citry-ui-part=start]),:where([data-citry-ui-part=list]>[data-citry-ui-part=list-item]>[data-citry-ui-part=surface]>[data-citry-ui-part=end]){min-inline-size:0}}\n\u003c/style>\u003cstyle data-citry-css-class=\"CTabs_437718\">@layer citry-ui.theme{:where(.cui-tabs){--_cui-tabs-accent:var(--cui-tabs-accent,LinkText);--_cui-tabs-border-color:var(--cui-tabs-border-color,color-mix(in srgb, currentColor 22%, transparent));--_cui-tabs-muted-color:var(--cui-tabs-muted-color,color-mix(in srgb, currentColor 68%, transparent));--_cui-tabs-list-background:var(--cui-tabs-list-background,transparent);--_cui-tabs-active-background:var(--cui-tabs-active-background,Canvas);--_cui-tabs-hover-background:var(--cui-tabs-hover-background,color-mix(in srgb, currentColor 8%, transparent));--_cui-tabs-focus-color:var(--cui-tabs-focus-color,Highlight);--_cui-tabs-radius:var(--cui-tabs-radius,.5rem);--_cui-tabs-gap:var(--cui-tabs-gap,1rem);--_cui-tabs-tab-inline-padding:var(--cui-tabs-tab-inline-padding,.875rem);--_cui-tabs-tab-block-padding:var(--cui-tabs-tab-block-padding,.625rem);--_cui-tabs-panel-padding:var(--cui-tabs-panel-padding,1rem);gap:var(--_cui-tabs-gap);color:canvastext;min-inline-size:0;font-family:ui-sans-serif,system-ui,sans-serif;display:grid}:where(.cui-tabs[data-orientation=vertical]){grid-template-columns:max-content minmax(0,1fr);align-items:start}:where(.cui-tabs[data-density=comfortable]){--_cui-tabs-tab-inline-padding:var(--cui-tabs-tab-inline-padding,.75rem);--_cui-tabs-tab-block-padding:var(--cui-tabs-tab-block-padding,.5rem)}:where(.cui-tabs[data-density=compact]){--_cui-tabs-tab-inline-padding:var(--cui-tabs-tab-inline-padding,.625rem);--_cui-tabs-tab-block-padding:var(--cui-tabs-tab-block-padding,.375rem)}:where(.cui-tabs[data-variant=pill]){--_cui-tabs-list-background:var(--cui-tabs-list-background,color-mix(in srgb, var(--_cui-tabs-accent) 12%, transparent))}:where(.cui-tab-list){border-block-end:1px solid var(--_cui-tabs-border-color);background:var(--_cui-tabs-list-background);scrollbar-width:thin;justify-content:flex-start;gap:.25rem;min-inline-size:0;display:flex;overflow:auto hidden}:where(.cui-tabs[data-align=center]>.cui-tab-list){justify-content:safe center}:where(.cui-tabs[data-align=end]>.cui-tab-list){justify-content:safe flex-end}:where(.cui-tabs[data-orientation=vertical]>.cui-tab-list){border-block-end:0;border-inline-end:1px solid var(--_cui-tabs-border-color);flex-direction:column;overflow:hidden auto}:where(.cui-tabs[data-grow]>.cui-tab-list>.cui-tab){flex:1 1 0}:where(.cui-tabs[data-variant=pill]>.cui-tab-list){border-radius:var(--_cui-tabs-radius);background:var(--_cui-tabs-list-background);border:0;padding:.25rem}:where(.cui-tab){min-block-size:2.75rem;padding-block:var(--_cui-tabs-tab-block-padding);padding-inline:var(--_cui-tabs-tab-inline-padding);color:var(--_cui-tabs-muted-color);font:inherit;text-align:center;white-space:nowrap;cursor:pointer;background:0 0;border:0;border-block-end:.1875rem solid #0000;font-weight:600;line-height:1.25;transition:background-color .12s,border-color .12s,color .12s;position:relative}:where(.cui-tabs[data-density=comfortable]>.cui-tab-list>.cui-tab){min-block-size:2.5rem}:where(.cui-tabs[data-density=compact]>.cui-tab-list>.cui-tab){min-block-size:2.25rem}:where(.cui-tabs[data-orientation=vertical]>.cui-tab-list>.cui-tab){text-align:start;border-block-end:0;border-inline-end:.1875rem solid #0000}:where(.cui-tab:hover:not(:disabled)){background:var(--_cui-tabs-hover-background);color:canvastext}:where(.cui-tab[data-state=active]){border-color:var(--_cui-tabs-accent);color:var(--_cui-tabs-accent)}:where(.cui-tabs[data-variant=pill]>.cui-tab-list>.cui-tab){border-radius:calc(var(--_cui-tabs-radius) - .125rem);border:0}:where(.cui-tabs[data-variant=pill]>.cui-tab-list>.cui-tab[data-state=active]){background:var(--_cui-tabs-active-background);box-shadow:0 1px 3px color-mix(in srgb, currentColor 22%, transparent)}:where(.cui-tab:focus-visible){z-index:1;outline:.1875rem solid var(--_cui-tabs-focus-color);outline-offset:-.1875rem}:where(.cui-tab:disabled){opacity:.48;cursor:not-allowed}:where(.cui-tab-panel){min-inline-size:0;padding:var(--_cui-tabs-panel-padding)}:where(.cui-tab-panel:focus-visible){outline:.1875rem solid var(--_cui-tabs-focus-color);outline-offset:.125rem}@media (prefers-reduced-motion:reduce){:where(.cui-tab){transition:none}}@media (forced-colors:active){:where(.cui-tab[data-state=active]){color:highlight;forced-color-adjust:none;border-color:highlight}:where(.cui-tabs[data-variant=pill]>.cui-tab-list>.cui-tab[data-state=active]){outline-offset:-2px;outline:2px solid highlight}}}\n\u003c/style>\u003cstyle data-citry-css-class=\"CProgress_dbddf8\">@layer citry-ui.theme{:where([data-citry-ui-part=progress]){--_cui-progress-track-color:var(--cui-progress-track-color,light-dark(#e7e5e4,#44403c));--_cui-progress-range-color:var(--cui-progress-range-color,light-dark(#1d4ed8,#60a5fa));--_cui-progress-height:var(--cui-progress-height,.625rem);--_cui-progress-radius:var(--cui-progress-radius,.25rem);box-sizing:border-box;inline-size:100%;block-size:var(--_cui-progress-height);border-radius:var(--_cui-progress-radius);appearance:none;background:var(--_cui-progress-track-color);color:var(--_cui-progress-range-color);accent-color:var(--_cui-progress-range-color);vertical-align:middle;border:0;display:block;overflow:hidden}:where([data-citry-ui-part=progress][data-intent=neutral]){--_cui-progress-range-color:var(--cui-progress-range-color,light-dark(#57534e,#d6d3d1))}:where([data-citry-ui-part=progress][data-intent=success]){--_cui-progress-range-color:var(--cui-progress-range-color,light-dark(#15803d,#4ade80))}:where([data-citry-ui-part=progress][data-intent=warn]){--_cui-progress-range-color:var(--cui-progress-range-color,light-dark(#b45309,#fbbf24))}:where([data-citry-ui-part=progress][data-intent=danger]){--_cui-progress-range-color:var(--cui-progress-range-color,light-dark(#b91c1c,#f87171))}:where([data-citry-ui-part=progress][data-size=sm]){--_cui-progress-height:var(--cui-progress-height,.375rem)}:where([data-citry-ui-part=progress][data-size=lg]){--_cui-progress-height:var(--cui-progress-height,.875rem)}:where([data-citry-ui-part=progress][data-shape=square]){--_cui-progress-radius:var(--cui-progress-radius,0)}:where([data-citry-ui-part=progress][data-shape=pill]){--_cui-progress-radius:var(--cui-progress-radius,999px)}:where([data-citry-ui-part=progress])::-webkit-progress-bar{border-radius:var(--_cui-progress-radius);background:var(--_cui-progress-track-color)}:where([data-citry-ui-part=progress])::-webkit-progress-value{border-radius:var(--_cui-progress-radius);background:var(--_cui-progress-range-color)}:where([data-citry-ui-part=progress])::-moz-progress-bar{border-radius:var(--_cui-progress-radius);background:var(--_cui-progress-range-color)}:where([data-citry-ui-part=progress]:indeterminate),:where([data-citry-ui-part=progress]:indeterminate)::-webkit-progress-bar{background-color:var(--_cui-progress-track-color);background-image:linear-gradient(100deg, transparent 20%, var(--_cui-progress-range-color) 45%, var(--_cui-progress-range-color) 55%, transparent 80%);background-size:200% 100%;animation:1.4s linear infinite cui-progress-indeterminate}@keyframes cui-progress-indeterminate{0%{background-position:100% 0}to{background-position:-100% 0}}@media (prefers-reduced-motion:reduce){:where([data-citry-ui-part=progress]:indeterminate),:where([data-citry-ui-part=progress]:indeterminate)::-webkit-progress-bar{background-image:repeating-linear-gradient(135deg, transparent 0 .35rem, var(--_cui-progress-range-color) .35rem .7rem);background-size:auto;animation:none}}@media (forced-colors:active){:where([data-citry-ui-part=progress]){--_cui-progress-track-color:var(--cui-progress-track-color,Canvas);--_cui-progress-range-color:var(--cui-progress-range-color,Highlight);forced-color-adjust:auto;border:1px solid canvastext}}@media print{:where([data-citry-ui-part=progress]:indeterminate),:where([data-citry-ui-part=progress]:indeterminate)::-webkit-progress-bar{animation:none}}}\n\u003c/style>\u003ctemplate data-composer-recipe-template=\"button\">\n  \u003cdiv class=\"landing-composer__rendered-recipe\" data-composer-rendered-recipe=\"button\">\n    \n\u003cbutton class=\"cui-button\" type=\"button\" data-variant=\"solid\" data-intent=\"primary\" data-size=\"lg\" data-block data-loading-position=\"center\" style=\"margin: 0.5rem 0; --cui-button-radius: 999px; --cui-button-font-weight: 750; box-shadow: 0 0.7rem 1.5rem color-mix(in srgb, LinkText 24%, transparent);\" data-citry-ui-part=\"button\" data-citry-root=\"\">\n  \u003cspan class=\"cui-button__loading\" aria-hidden=\"true\" hidden data-citry-ui-part=\"loading-indicator\">\n    \n      \u003cspan class=\"cui-button__spinner\">\u003c/span>\n    \n  \u003c/span>\n  \n  \u003cspan class=\"cui-button__content\" data-citry-ui-part=\"content\">\n    \n      Create observation\n    \n  \u003c/span>\n  \n\u003c/button>\n\n  \u003c/div>\n\u003c/template>\n\u003ctemplate data-composer-recipe-template=\"button-group\">\n  \u003cdiv class=\"landing-composer__rendered-recipe\" data-composer-rendered-recipe=\"button-group\">\n    \n\u003cdiv class=\"cui-button-group\" style=\"margin: 0.5rem 0; padding: 1rem; box-sizing: border-box; border: 1px solid color-mix(in srgb, CanvasText 13%, transparent); border-radius: 1rem; background: color-mix(in srgb, LinkText 4%, Canvas); box-shadow: 0 0.65rem 1.8rem color-mix(in srgb, CanvasText 9%, transparent); --cui-button-group-gap: 0.4rem;\" data-citry-ui-part=\"button-group\" data-orientation=\"horizontal\" data-attached data-grow role=\"group\" aria-label=\"Navigation actions\">\n  \n      \n\u003cbutton class=\"cui-button\" type=\"button\" data-variant=\"ghost\" data-intent=\"neutral\" data-size=\"md\" data-loading-position=\"center\" data-citry-ui-part=\"button\" data-citry-root=\"\">\n  \u003cspan class=\"cui-button__loading\" aria-hidden=\"true\" hidden data-citry-ui-part=\"loading-indicator\">\n    \n      \u003cspan class=\"cui-button__spinner\">\u003c/span>\n    \n  \u003c/span>\n  \n  \u003cspan class=\"cui-button__content\" data-citry-ui-part=\"content\">\n    \n        Back\n      \n  \u003c/span>\n  \n\u003c/button>\n\n      \n\u003cbutton class=\"cui-button\" type=\"button\" data-variant=\"outline\" data-intent=\"primary\" data-size=\"md\" data-loading-position=\"center\" data-citry-ui-part=\"button\" data-citry-root=\"\">\n  \u003cspan class=\"cui-button__loading\" aria-hidden=\"true\" hidden data-citry-ui-part=\"loading-indicator\">\n    \n      \u003cspan class=\"cui-button__spinner\">\u003c/span>\n    \n  \u003c/span>\n  \n  \u003cspan class=\"cui-button__content\" data-citry-ui-part=\"content\">\n    \n        Preview\n      \n  \u003c/span>\n  \n\u003c/button>\n\n      \n\u003cbutton class=\"cui-button\" type=\"button\" data-variant=\"solid\" data-intent=\"primary\" data-size=\"md\" data-loading-position=\"center\" data-citry-ui-part=\"button\" data-citry-root=\"\">\n  \u003cspan class=\"cui-button__loading\" aria-hidden=\"true\" hidden data-citry-ui-part=\"loading-indicator\">\n    \n      \u003cspan class=\"cui-button__spinner\">\u003c/span>\n    \n  \u003c/span>\n  \n  \u003cspan class=\"cui-button__content\" data-citry-ui-part=\"content\">\n    \n        Publish\n      \n  \u003c/span>\n  \n\u003c/button>\n\n    \n\u003c/div>\n\n  \u003c/div>\n\u003c/template>\n\u003ctemplate data-composer-recipe-template=\"field-input\">\n  \u003cdiv class=\"landing-composer__rendered-recipe\" data-composer-rendered-recipe=\"field-input\">\n    \n\u003cdiv class=\"cui-field\" id=\"cui-field-c9y4afe55-control-field\" data-orientation=\"vertical\" data-density=\"default\" data-citry-field-root style=\"margin: 0.5rem 0; padding: 1rem; box-sizing: border-box; border: 1px solid color-mix(in srgb, CanvasText 13%, transparent); border-radius: 1rem; background: color-mix(in srgb, LinkText 4%, Canvas); box-shadow: 0 0.65rem 1.8rem color-mix(in srgb, CanvasText 9%, transparent);\" data-citry-ui-part=\"field\" data-citry-root=\"\">\n  \u003clabel id=\"cui-field-c9y4afe55-control-label\" for=\"cui-field-c9y4afe55-control\" data-citry-ui-part=\"label\">\n    \n        Project name\n      \n    \u003cspan class=\"cui-field__required\" aria-hidden=\"true\" hidden data-citry-ui-part=\"required-indicator\">\n      *\n    \u003c/span>\n  \u003c/label>\n  \u003cdiv data-citry-ui-part=\"control\">\n    \n        \n\u003cinput class=\"cui-input\" id=\"cui-field-c9y4afe55-control\" name=\"composer-field-300\" type=\"text\" aria-describedby=\"cui-field-c9y4afe55-control-description\" placeholder=\"Aurora atlas\" data-variant=\"filled\" data-size=\"lg\" data-citry-field-control data-citry-ui-part=\"input\" data-citry-root=\"\"/>\n\n      \n  \u003c/div>\n  \n    \u003cdiv id=\"cui-field-c9y4afe55-control-description\" data-citry-ui-part=\"description\">\n      \n        A clear name helps the whole team find it later.\n      \n    \u003c/div>\n  \n  \u003cdiv id=\"cui-field-c9y4afe55-control-error\" aria-live=\"polite\" hidden data-citry-ui-part=\"error\">\n    \n  \u003c/div>\n\u003c/div>\n\n  \u003c/div>\n\u003c/template>\n\u003ctemplate data-composer-recipe-template=\"checkbox\">\n  \u003cdiv class=\"landing-composer__rendered-recipe\" data-composer-rendered-recipe=\"checkbox\">\n    \n\u003cspan class=\"cui-checkbox\" data-variant=\"solid\" data-size=\"lg\" data-label-pos=\"end\" style=\"margin: 0.5rem 0; padding: 1rem; box-sizing: border-box; border: 1px solid color-mix(in srgb, CanvasText 13%, transparent); border-radius: 1rem; background: color-mix(in srgb, LinkText 4%, Canvas); box-shadow: 0 0.65rem 1.8rem color-mix(in srgb, CanvasText 9%, transparent);\" data-citry-ui-part=\"checkbox\" data-citry-root=\"\">\n  \u003cinput class=\"cui-checkbox__input\" id=\"cui-checkbox-c9y4afe57\" type=\"checkbox\" value=\"on\" checked aria-describedby=\"cui-checkbox-c9y4afe57-description\" data-citry-ui-part=\"input\"/>\n  \n    \u003cspan class=\"cui-checkbox__body\">\n      \n        \u003clabel for=\"cui-checkbox-c9y4afe57\" data-citry-ui-part=\"label\">\n          \n        Include archived observations\n      \n        \u003c/label>\n      \n      \n        \u003cspan id=\"cui-checkbox-c9y4afe57-description\" data-citry-ui-part=\"description\">\n          \n        Search across active and completed projects.\n      \n        \u003c/span>\n      \n    \u003c/span>\n  \n\u003c/span>\n\n  \u003c/div>\n\u003c/template>\n\u003ctemplate data-composer-recipe-template=\"switch\">\n  \u003cdiv class=\"landing-composer__rendered-recipe\" data-composer-rendered-recipe=\"switch\">\n    \n\u003cspan class=\"cui-switch\" data-size=\"lg\" data-label-pos=\"end\" style=\"margin: 0.5rem 0; padding: 1rem; box-sizing: border-box; border: 1px solid color-mix(in srgb, CanvasText 13%, transparent); border-radius: 1rem; background: color-mix(in srgb, LinkText 4%, Canvas); box-shadow: 0 0.65rem 1.8rem color-mix(in srgb, CanvasText 9%, transparent);\" data-citry-ui-part=\"switch\" data-citry-root=\"\">\n  \u003cinput class=\"cui-switch__input\" id=\"cui-switch-c9y4afe58\" type=\"checkbox\" role=\"switch\" value=\"on\" checked aria-describedby=\"cui-switch-c9y4afe58-description\" data-citry-ui-part=\"input\"/>\n  \u003cspan data-citry-ui-part=\"surface\">\n    \u003cspan aria-hidden=\"true\" data-citry-ui-part=\"track\">\n      \u003cspan data-citry-ui-part=\"thumb\">\u003c/span>\n    \u003c/span>\n    \n      \u003cspan data-citry-ui-part=\"body\">\n        \n          \u003clabel for=\"cui-switch-c9y4afe58\" data-citry-ui-part=\"label\">\n            \n        Live updates\n      \n          \u003c/label>\n        \n        \n          \u003cspan id=\"cui-switch-c9y4afe58-description\" data-citry-ui-part=\"description\">\n            \n        Refresh the dashboard as new readings arrive.\n      \n          \u003c/span>\n        \n      \u003c/span>\n    \n  \u003c/span>\n\u003c/span>\n\n  \u003c/div>\n\u003c/template>\n\u003ctemplate data-composer-recipe-template=\"container\">\n  \u003cdiv class=\"landing-composer__rendered-recipe\" data-composer-rendered-recipe=\"container\">\n    \n\u003cdiv class=\"cui-container\" style=\"margin: 0.5rem 0; padding: 1rem; box-sizing: border-box; border: 1px solid color-mix(in srgb, CanvasText 13%, transparent); border-radius: 1rem; background: color-mix(in srgb, LinkText 4%, Canvas); box-shadow: 0 0.65rem 1.8rem color-mix(in srgb, CanvasText 9%, transparent); --cui-container-max-width: 52rem;\" data-citry-ui-part=\"container\" data-size=\"lg\" data-gutter=\"lg\">\n  \n      \n\u003cdiv class=\"cui-alert\" data-intent=\"info\" data-variant=\"soft\" data-size=\"md\" data-announce=\"off\" data-icon data-citry-ui-part=\"alert\" data-citry-root=\"\">\n  \u003cdiv class=\"cui-alert__indicator\" aria-hidden=\"true\" data-citry-ui-part=\"indicator\">\n    \u003csvg class=\"cui-alert__icon\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" focusable=\"false\" aria-hidden=\"true\">\n      \n        \u003cg class=\"cui-alert__glyph\" data-cui-alert-intent=\"info\">\n          \u003ccircle cx=\"12\" cy=\"12\" r=\"10\">\u003c/circle>\u003cpath d=\"M12 16v-4\">\u003c/path>\u003cpath d=\"M12 8h.01\">\u003c/path>\n        \u003c/g>\u003cg class=\"cui-alert__glyph\" data-cui-alert-intent=\"success\" data-cui-alert-hidden>\n          \u003ccircle cx=\"12\" cy=\"12\" r=\"10\">\u003c/circle>\u003cpath d=\"m9 12 2 2 4-4\">\u003c/path>\n        \u003c/g>\u003cg class=\"cui-alert__glyph\" data-cui-alert-intent=\"warn\" data-cui-alert-hidden>\n          \u003cpath d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\">\u003c/path>\u003cpath d=\"M12 9v4\">\u003c/path>\u003cpath d=\"M12 17h.01\">\u003c/path>\n        \u003c/g>\u003cg class=\"cui-alert__glyph\" data-cui-alert-intent=\"error\" data-cui-alert-hidden>\n          \u003ccircle cx=\"12\" cy=\"12\" r=\"10\">\u003c/circle>\u003cpath d=\"m15 9-6 6\">\u003c/path>\u003cpath d=\"m9 9 6 6\">\u003c/path>\n        \u003c/g>\n      \n    \u003c/svg>\n  \u003c/div>\n  \u003cdiv class=\"cui-alert__content\" data-citry-ui-part=\"content\">\n    \n      \u003cdiv class=\"cui-alert__title\" data-citry-ui-part=\"title\">\n        \n          A comfortable content width\n        \n      \u003c/div>\n    \n    \n      \u003cdiv class=\"cui-alert__message\" data-citry-ui-part=\"message\">\n        \n          Container keeps the page readable as the viewport grows.\n        \n      \u003c/div>\n    \n  \u003c/div>\n  \n\u003c/div>\n\n      \u003cbutton type=\"button\" class=\"landing-composer__drop\" data-composer-drop data-composer-accepts=\"layout content action control\" aria-label=\"Choose this spot for another component\">\n        \u003cspan aria-hidden=\"true\">+\u003c/span>\n        \u003cstrong>Drop here\u003c/strong>\n      \u003c/button>\n    \n\u003c/div>\n\n  \u003c/div>\n\u003c/template>\n\u003ctemplate data-composer-recipe-template=\"group\">\n  \u003cdiv class=\"landing-composer__rendered-recipe\" data-composer-rendered-recipe=\"group\">\n    \n\u003cdiv class=\"cui-row\" style=\"margin: 0.5rem 0; padding: 1rem; box-sizing: border-box; border: 1px solid color-mix(in srgb, CanvasText 13%, transparent); border-radius: 1rem; background: color-mix(in srgb, LinkText 4%, Canvas); box-shadow: 0 0.65rem 1.8rem color-mix(in srgb, CanvasText 9%, transparent);\" data-citry-ui-part=\"row\" data-gap=\"md\" data-align=\"center\" data-justify=\"between\" data-wrap>\n  \n      Observatory ready\n      \n\u003cspan class=\"cui-badge\" data-citry-ui-part=\"badge\" data-variant=\"solid\" data-intent=\"success\" data-size=\"md\" data-shape=\"pill\">\n  \n  \u003cspan class=\"cui-badge__label\" data-citry-ui-part=\"label\">\n    \n        All systems online\n      \n  \u003c/span>\n  \n\u003c/span>\n\n      \u003cbutton type=\"button\" class=\"landing-composer__drop\" data-composer-drop data-composer-accepts=\"layout content action control\" aria-label=\"Choose this spot for another component\">\n        \u003cspan aria-hidden=\"true\">+\u003c/span>\n        \u003cstrong>Drop here\u003c/strong>\n      \u003c/button>\n    \n\u003c/div>\n\n  \u003c/div>\n\u003c/template>\n\u003ctemplate data-composer-recipe-template=\"grid\">\n  \u003cdiv class=\"landing-composer__rendered-recipe\" data-composer-rendered-recipe=\"grid\">\n    \n\u003cdiv class=\"cui-grid\" style=\"margin: 0.5rem 0; --_cui-grid-min-column-input: 11rem;\" data-citry-ui-part=\"grid\" data-cols=\"1\" data-intrinsic data-gap=\"lg\">\n  \n      \n\u003cdiv class=\"cui-grid-item\" style=\"--_cui-grid-item-span-base: 1;\" data-citry-ui-part=\"grid-item\" data-span=\"1\">\n  \n        \n\u003cdiv class=\"cui-card\" data-variant=\"elevated\" data-size=\"sm\" style=\"--cui-card-background: color-mix(in srgb, #2563eb 8%, Canvas); --cui-card-border-color: color-mix(in srgb, #2563eb 24%, transparent);\" data-citry-ui-part=\"card\">\n  \n  \n    \u003cdiv class=\"cui-card__header\" data-citry-ui-part=\"header\">\n      \n        \u003cdiv class=\"cui-card__header-content\">\n          \n            \n\u003cdiv class=\"cui-row\" data-citry-ui-part=\"row\" data-gap=\"sm\" data-align=\"center\" data-justify=\"between\" data-wrap>\n  \n              Observation plan\n              \n\u003cspan class=\"cui-badge\" data-citry-ui-part=\"badge\" data-variant=\"soft\" data-intent=\"primary\" data-size=\"md\" data-shape=\"pill\">\n  \n  \u003cspan class=\"cui-badge__label\" data-citry-ui-part=\"label\">\n    \n                Tonight\n              \n  \u003c/span>\n  \n\u003c/span>\n\n            \n\u003c/div>\n\n          \n        \u003c/div>\n      \n      \n    \u003c/div>\n  \n  \n    \u003cdiv class=\"cui-card__body\" data-citry-ui-part=\"body\">\n      \n            Three targets are ready for the evening window.\n          \n    \u003c/div>\n  \n  \n    \u003cdiv class=\"cui-card__footer\" data-citry-ui-part=\"footer\">\n      \n      \n        \u003cdiv class=\"cui-card__actions\" data-citry-ui-part=\"actions\">\n          \n            \u003cbutton type=\"button\" class=\"landing-composer__drop\" data-composer-drop data-composer-accepts=\"layout content action control\" aria-label=\"Choose this spot for another component\">\n              \u003cspan aria-hidden=\"true\">+\u003c/span>\n              \u003cstrong>Drop here\u003c/strong>\n            \u003c/button>\n          \n        \u003c/div>\n      \n    \u003c/div>\n  \n\u003c/div>\n\n      \n\u003c/div>\n\n      \n\u003cdiv class=\"cui-grid-item\" style=\"--_cui-grid-item-span-base: 1;\" data-citry-ui-part=\"grid-item\" data-span=\"1\">\n  \n        \n\u003cdiv class=\"cui-card\" data-variant=\"elevated\" data-size=\"sm\" style=\"--cui-card-background: color-mix(in srgb, #7c3aed 8%, Canvas); --cui-card-border-color: color-mix(in srgb, #7c3aed 24%, transparent);\" data-citry-ui-part=\"card\">\n  \n  \n    \u003cdiv class=\"cui-card__header\" data-citry-ui-part=\"header\">\n      \n        \u003cdiv class=\"cui-card__header-content\">\n          \n            \n\u003cdiv class=\"cui-row\" data-citry-ui-part=\"row\" data-gap=\"sm\" data-align=\"center\" data-justify=\"between\" data-wrap>\n  \n              Team notes\n              \n\u003cspan class=\"cui-badge\" data-citry-ui-part=\"badge\" data-variant=\"soft\" data-intent=\"success\" data-size=\"md\" data-shape=\"pill\">\n  \n  \u003cspan class=\"cui-badge__label\" data-citry-ui-part=\"label\">\n    \n                4 new\n              \n  \u003c/span>\n  \n\u003c/span>\n\n            \n\u003c/div>\n\n          \n        \u003c/div>\n      \n      \n    \u003c/div>\n  \n  \n    \u003cdiv class=\"cui-card__body\" data-citry-ui-part=\"body\">\n      \n            The latest handoff is organized and ready to share.\n          \n    \u003c/div>\n  \n  \n    \u003cdiv class=\"cui-card__footer\" data-citry-ui-part=\"footer\">\n      \n      \n        \u003cdiv class=\"cui-card__actions\" data-citry-ui-part=\"actions\">\n          \n            \u003cbutton type=\"button\" class=\"landing-composer__drop\" data-composer-drop data-composer-accepts=\"layout content action control\" aria-label=\"Choose this spot for another component\">\n              \u003cspan aria-hidden=\"true\">+\u003c/span>\n              \u003cstrong>Drop here\u003c/strong>\n            \u003c/button>\n          \n        \u003c/div>\n      \n    \u003c/div>\n  \n\u003c/div>\n\n      \n\u003c/div>\n\n    \n\u003c/div>\n\n  \u003c/div>\n\u003c/template>\n\u003ctemplate data-composer-recipe-template=\"divider\">\n  \u003cdiv class=\"landing-composer__rendered-recipe\" data-composer-rendered-recipe=\"divider\">\n    \n\n  \u003cdiv class=\"cui-divider cui-divider--labeled\" style=\"margin: 1.1rem 0; --cui-divider-color: color-mix(in srgb, LinkText 58%, transparent); --cui-divider-label-color: LinkText; --cui-divider-label-font-weight: 750;\" data-citry-ui-part=\"divider\" data-orientation=\"horizontal\" data-variant=\"dashed\" data-size=\"md\" data-inset=\"none\" data-label-pos=\"center\" data-labeled data-decorative>\n    \u003chr class=\"cui-divider__line\" data-citry-ui-part=\"line\" aria-hidden=\"true\"/>\n    \u003cspan class=\"cui-divider__label\" data-citry-ui-part=\"label\">\n      \n      Tonight\u0026#x27;s schedule\n    \n    \u003c/span>\n    \u003chr class=\"cui-divider__line\" data-citry-ui-part=\"line\" aria-hidden=\"true\"/>\n  \u003c/div>\n\n\n  \u003c/div>\n\u003c/template>\n\u003ctemplate data-composer-recipe-template=\"card\">\n  \u003cdiv class=\"landing-composer__rendered-recipe\" data-composer-rendered-recipe=\"card\">\n    \n\u003cdiv class=\"cui-card\" data-variant=\"elevated\" data-size=\"md\" style=\"margin: 0.5rem 0;\" data-citry-ui-part=\"card\">\n  \n    \u003cdiv class=\"cui-card__media\" data-citry-ui-part=\"media\">\n      \n        \n\u003cspan class=\"cui-skeleton\" style=\"--cui-skeleton-height: 8rem;\" data-citry-ui-part=\"skeleton\" data-kind=\"rect\" data-animation=\"wave\" aria-hidden=\"true\">\n  \n\u003c/span>\n\n      \n    \u003c/div>\n  \n  \n    \u003cdiv class=\"cui-card__header\" data-citry-ui-part=\"header\">\n      \n        \u003cdiv class=\"cui-card__header-content\">\n          \n        Observation summary\n      \n        \u003c/div>\n      \n      \n    \u003c/div>\n  \n  \n    \u003cdiv class=\"cui-card__body\" data-citry-ui-part=\"body\">\n      \n        Three clear nights are forecast this week, with the best visibility after midnight.\n      \n    \u003c/div>\n  \n  \n    \u003cdiv class=\"cui-card__footer\" data-citry-ui-part=\"footer\">\n      \n      \n        \u003cdiv class=\"cui-card__actions\" data-citry-ui-part=\"actions\">\n          \n        \u003cbutton type=\"button\" class=\"landing-composer__drop\" data-composer-drop data-composer-accepts=\"layout content action control\" aria-label=\"Choose this spot for another component\">\n          \u003cspan aria-hidden=\"true\">+\u003c/span>\n          \u003cstrong>Drop here\u003c/strong>\n        \u003c/button>\n      \n        \u003c/div>\n      \n    \u003c/div>\n  \n\u003c/div>\n\n  \u003c/div>\n\u003c/template>\n\u003ctemplate data-composer-recipe-template=\"badge\">\n  \u003cdiv class=\"landing-composer__rendered-recipe\" data-composer-rendered-recipe=\"badge\">\n    \n\u003cspan class=\"cui-badge\" style=\"margin: 0.5rem 0; box-shadow: 0 0.45rem 1rem color-mix(in srgb, #067647 20%, transparent);\" data-citry-ui-part=\"badge\" data-variant=\"solid\" data-intent=\"success\" data-size=\"lg\" data-shape=\"pill\">\n  \n  \u003cspan class=\"cui-badge__label\" data-citry-ui-part=\"label\">\n    \n      Ready for review\n    \n  \u003c/span>\n  \n\u003c/span>\n\n  \u003c/div>\n\u003c/template>\n\u003ctemplate data-composer-recipe-template=\"list\">\n  \u003cdiv class=\"landing-composer__rendered-recipe\" data-composer-rendered-recipe=\"list\">\n    \n\n  \u003cul class=\"cui-list\" style=\"margin: 0.5rem 0; padding: 1rem; box-sizing: border-box; border: 1px solid color-mix(in srgb, CanvasText 13%, transparent); border-radius: 1rem; background: color-mix(in srgb, LinkText 4%, Canvas); box-shadow: 0 0.65rem 1.8rem color-mix(in srgb, CanvasText 9%, transparent); --cui-list-item-padding: 0.75rem 0.85rem;\" data-citry-ui-part=\"list\" data-marker=\"none\" data-density=\"comfortable\" data-variant=\"surface\" data-divided aria-label=\"Observation queue\">\n      \n\u003cli class=\"cui-list-item\" data-citry-ui-part=\"list-item\">\n  \n    \u003cdiv data-citry-ui-part=\"surface\" data-cui-list-has-end>\n      \n      \u003cdiv data-citry-ui-part=\"body\">\n        \n          Calibrate telescope\n        \n        \n          \u003cdiv data-citry-ui-part=\"description\">\n          Alignment and focus check\n        \u003c/div>\n        \n      \u003c/div>\n      \n        \u003cspan data-citry-ui-part=\"end\">\n          \n\u003cspan class=\"cui-badge\" data-citry-ui-part=\"badge\" data-variant=\"soft\" data-intent=\"success\" data-size=\"md\" data-shape=\"pill\">\n  \n  \u003cspan class=\"cui-badge__label\" data-citry-ui-part=\"label\">\n    \n            Ready\n          \n  \u003c/span>\n  \n\u003c/span>\n\n        \u003c/span>\n      \n    \u003c/div>\n  \n\u003c/li>\n\n      \n\u003cli class=\"cui-list-item\" data-citry-ui-part=\"list-item\">\n  \n    \u003cdiv data-citry-ui-part=\"surface\" data-cui-list-has-end>\n      \n      \u003cdiv data-citry-ui-part=\"body\">\n        \n          Open observatory roof\n        \n        \n          \u003cdiv data-citry-ui-part=\"description\">\n          Scheduled after civil twilight\n        \u003c/div>\n        \n      \u003c/div>\n      \n        \u003cspan data-citry-ui-part=\"end\">\n          20:42\n        \u003c/span>\n      \n    \u003c/div>\n  \n\u003c/li>\n\n      \n\u003cli class=\"cui-list-item\" data-citry-ui-part=\"list-item\">\n  \n    \u003cdiv data-citry-ui-part=\"surface\" data-cui-list-has-end>\n      \n      \u003cdiv data-citry-ui-part=\"body\">\n        \n          Begin exposure\n        \n        \n          \u003cdiv data-citry-ui-part=\"description\">\n          M31 \u00b7 12 frames \u00b7 180 seconds\n        \u003c/div>\n        \n      \u003c/div>\n      \n        \u003cspan data-citry-ui-part=\"end\">\n          \n\u003cspan class=\"cui-badge\" data-citry-ui-part=\"badge\" data-variant=\"soft\" data-intent=\"primary\" data-size=\"md\" data-shape=\"pill\">\n  \n  \u003cspan class=\"cui-badge__label\" data-citry-ui-part=\"label\">\n    \n            Queued\n          \n  \u003c/span>\n  \n\u003c/span>\n\n        \u003c/span>\n      \n    \u003c/div>\n  \n\u003c/li>\n\n    \u003c/ul>\n\n\n  \u003c/div>\n\u003c/template>\n\u003ctemplate data-composer-recipe-template=\"tabs\">\n  \u003cdiv class=\"landing-composer__rendered-recipe\" data-composer-rendered-recipe=\"tabs\">\n    \n\n\n  \n      \n      \n      \n      \n    \n\n\n\n\u003cdiv class=\"cui-tabs\" style=\"margin: 0.5rem 0; padding: 1rem; box-sizing: border-box; border: 1px solid color-mix(in srgb, CanvasText 13%, transparent); border-radius: 1rem; background: color-mix(in srgb, LinkText 4%, Canvas); box-shadow: 0 0.65rem 1.8rem color-mix(in srgb, CanvasText 9%, transparent); --cui-tabs-accent: light-dark(#5946d2, #b4a9ff); --cui-tabs-active-background: color-mix(in srgb, var(--cui-tabs-accent) 15%, Canvas);\" id=\"cui-tabs-c9y4afe66\" data-citry-tabs-root data-value=\"composer-overview-1300\" data-activation=\"automatic\" data-orientation=\"horizontal\" data-loop data-density=\"comfortable\" data-variant=\"pill\" data-align=\"start\" data-grow data-citry-ui-part=\"tabs\" data-citry-root=\"\">\n  \u003cdiv class=\"cui-tab-list\" role=\"tablist\" aria-orientation=\"horizontal\" aria-label=\"Composition sections\" data-citry-tabs-list data-orientation=\"horizontal\" data-citry-ui-part=\"tab-list\">\n    \n      \n\u003cbutton class=\"cui-tab\" id=\"cui-tabs-c9y4afe66-tab-3b3099cdc800\" role=\"tab\" type=\"button\" aria-controls=\"cui-tabs-c9y4afe66-panel-3b3099cdc800\" aria-selected=\"true\" tabindex=\"0\" data-citry-tabs-tab data-value=\"composer-overview-1300\" data-state=\"active\" data-citry-ui-part=\"tab\" data-citry-key=\":composer-overview-1300\">\n  \n        Overview\n      \n\u003c/button>\n\n    \n      \n\u003cbutton class=\"cui-tab\" id=\"cui-tabs-c9y4afe66-tab-8c7d8ccd5599\" role=\"tab\" type=\"button\" aria-controls=\"cui-tabs-c9y4afe66-panel-8c7d8ccd5599\" aria-selected=\"false\" tabindex=\"-1\" data-citry-tabs-tab data-value=\"composer-details-1300\" data-state=\"inactive\" data-citry-ui-part=\"tab\" data-citry-key=\":composer-details-1300\">\n  \n        Details\n      \n\u003c/button>\n\n    \n  \u003c/div>\n  \n    \n\u003cdiv class=\"cui-tab-panel\" id=\"cui-tabs-c9y4afe66-panel-3b3099cdc800\" role=\"tabpanel\" aria-labelledby=\"cui-tabs-c9y4afe66-tab-3b3099cdc800\" tabindex=\"0\" data-citry-tabs-panel data-value=\"composer-overview-1300\" data-state=\"active\" data-citry-ui-part=\"tab-panel\" data-citry-key=\":composer-overview-1300\">\n  \n        \n\u003cdiv class=\"cui-alert\" data-intent=\"info\" data-variant=\"soft\" data-size=\"lg\" data-announce=\"off\" data-icon data-citry-ui-part=\"alert\" data-citry-root=\"\">\n  \u003cdiv class=\"cui-alert__indicator\" aria-hidden=\"true\" data-citry-ui-part=\"indicator\">\n    \u003csvg class=\"cui-alert__icon\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" focusable=\"false\" aria-hidden=\"true\">\n      \n        \u003cg class=\"cui-alert__glyph\" data-cui-alert-intent=\"info\">\n          \u003ccircle cx=\"12\" cy=\"12\" r=\"10\">\u003c/circle>\u003cpath d=\"M12 16v-4\">\u003c/path>\u003cpath d=\"M12 8h.01\">\u003c/path>\n        \u003c/g>\u003cg class=\"cui-alert__glyph\" data-cui-alert-intent=\"success\" data-cui-alert-hidden>\n          \u003ccircle cx=\"12\" cy=\"12\" r=\"10\">\u003c/circle>\u003cpath d=\"m9 12 2 2 4-4\">\u003c/path>\n        \u003c/g>\u003cg class=\"cui-alert__glyph\" data-cui-alert-intent=\"warn\" data-cui-alert-hidden>\n          \u003cpath d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\">\u003c/path>\u003cpath d=\"M12 9v4\">\u003c/path>\u003cpath d=\"M12 17h.01\">\u003c/path>\n        \u003c/g>\u003cg class=\"cui-alert__glyph\" data-cui-alert-intent=\"error\" data-cui-alert-hidden>\n          \u003ccircle cx=\"12\" cy=\"12\" r=\"10\">\u003c/circle>\u003cpath d=\"m15 9-6 6\">\u003c/path>\u003cpath d=\"m9 9 6 6\">\u003c/path>\n        \u003c/g>\n      \n    \u003c/svg>\n  \u003c/div>\n  \u003cdiv class=\"cui-alert__content\" data-citry-ui-part=\"content\">\n    \n      \u003cdiv class=\"cui-alert__title\" data-citry-ui-part=\"title\">\n        \n            A real component tree\n          \n      \u003c/div>\n    \n    \n      \u003cdiv class=\"cui-alert__message\" data-citry-ui-part=\"message\">\n        \n            Components can be nested inside each panel.\n          \n      \u003c/div>\n    \n  \u003c/div>\n  \n\u003c/div>\n\n        \u003cbutton type=\"button\" class=\"landing-composer__drop\" data-composer-drop data-composer-accepts=\"layout content action control\" aria-label=\"Choose this spot for another component\">\n          \u003cspan aria-hidden=\"true\">+\u003c/span>\n          \u003cstrong>Drop here\u003c/strong>\n        \u003c/button>\n      \n\u003c/div>\n\n  \n    \n\u003cdiv class=\"cui-tab-panel\" id=\"cui-tabs-c9y4afe66-panel-8c7d8ccd5599\" role=\"tabpanel\" aria-labelledby=\"cui-tabs-c9y4afe66-tab-8c7d8ccd5599\" tabindex=\"0\" hidden data-citry-tabs-panel data-value=\"composer-details-1300\" data-state=\"inactive\" data-citry-ui-part=\"tab-panel\" data-citry-key=\":composer-details-1300\">\n  \n        \n\u003cdiv class=\"cui-card\" data-variant=\"outline\" data-size=\"md\" data-citry-ui-part=\"card\">\n  \n  \n    \u003cdiv class=\"cui-card__header\" data-citry-ui-part=\"header\">\n      \n        \u003cdiv class=\"cui-card__header-content\">\n          \n            Nested composition\n          \n        \u003c/div>\n      \n      \n    \u003c/div>\n  \n  \n    \u003cdiv class=\"cui-card__body\" data-citry-ui-part=\"body\">\n      \n            This Card lives inside a Tab Panel.\n          \n    \u003c/div>\n  \n  \n    \u003cdiv class=\"cui-card__footer\" data-citry-ui-part=\"footer\">\n      \n      \n        \u003cdiv class=\"cui-card__actions\" data-citry-ui-part=\"actions\">\n          \n            \n\u003cbutton class=\"cui-button\" type=\"button\" data-variant=\"solid\" data-intent=\"primary\" data-size=\"md\" data-loading-position=\"center\" data-citry-ui-part=\"button\" data-citry-root=\"\">\n  \u003cspan class=\"cui-button__loading\" aria-hidden=\"true\" hidden data-citry-ui-part=\"loading-indicator\">\n    \n      \u003cspan class=\"cui-button__spinner\">\u003c/span>\n    \n  \u003c/span>\n  \n  \u003cspan class=\"cui-button__content\" data-citry-ui-part=\"content\">\n    \n              Continue\n            \n  \u003c/span>\n  \n\u003c/button>\n\n          \n        \u003c/div>\n      \n    \u003c/div>\n  \n\u003c/div>\n\n      \n\u003c/div>\n\n  \n\u003c/div>\n\n\n  \u003c/div>\n\u003c/template>\n\u003ctemplate data-composer-recipe-template=\"alert\">\n  \u003cdiv class=\"landing-composer__rendered-recipe\" data-composer-rendered-recipe=\"alert\">\n    \n\u003cdiv class=\"cui-alert\" data-intent=\"success\" data-variant=\"soft\" data-size=\"lg\" data-announce=\"off\" data-icon style=\"margin: 0.5rem 0; box-shadow: 0 0.65rem 1.6rem color-mix(in srgb, #067647 12%, transparent);\" data-citry-ui-part=\"alert\" data-citry-root=\"\">\n  \u003cdiv class=\"cui-alert__indicator\" aria-hidden=\"true\" data-citry-ui-part=\"indicator\">\n    \u003csvg class=\"cui-alert__icon\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" focusable=\"false\" aria-hidden=\"true\">\n      \n        \u003cg class=\"cui-alert__glyph\" data-cui-alert-intent=\"info\" data-cui-alert-hidden>\n          \u003ccircle cx=\"12\" cy=\"12\" r=\"10\">\u003c/circle>\u003cpath d=\"M12 16v-4\">\u003c/path>\u003cpath d=\"M12 8h.01\">\u003c/path>\n        \u003c/g>\u003cg class=\"cui-alert__glyph\" data-cui-alert-intent=\"success\">\n          \u003ccircle cx=\"12\" cy=\"12\" r=\"10\">\u003c/circle>\u003cpath d=\"m9 12 2 2 4-4\">\u003c/path>\n        \u003c/g>\u003cg class=\"cui-alert__glyph\" data-cui-alert-intent=\"warn\" data-cui-alert-hidden>\n          \u003cpath d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\">\u003c/path>\u003cpath d=\"M12 9v4\">\u003c/path>\u003cpath d=\"M12 17h.01\">\u003c/path>\n        \u003c/g>\u003cg class=\"cui-alert__glyph\" data-cui-alert-intent=\"error\" data-cui-alert-hidden>\n          \u003ccircle cx=\"12\" cy=\"12\" r=\"10\">\u003c/circle>\u003cpath d=\"m15 9-6 6\">\u003c/path>\u003cpath d=\"m9 9 6 6\">\u003c/path>\n        \u003c/g>\n      \n    \u003c/svg>\n  \u003c/div>\n  \u003cdiv class=\"cui-alert__content\" data-citry-ui-part=\"content\">\n    \n      \u003cdiv class=\"cui-alert__title\" data-citry-ui-part=\"title\">\n        \n        Import complete\n      \n      \u003c/div>\n    \n    \n      \u003cdiv class=\"cui-alert__message\" data-citry-ui-part=\"message\">\n        \n        Twelve observations are ready to review.\n      \n      \u003c/div>\n    \n  \u003c/div>\n  \n    \u003cdiv class=\"cui-alert__actions\" data-citry-ui-part=\"actions\">\n      \n        \u003cbutton type=\"button\" class=\"landing-composer__drop\" data-composer-drop data-composer-accepts=\"layout content action control\" aria-label=\"Choose this spot for another component\">\n          \u003cspan aria-hidden=\"true\">+\u003c/span>\n          \u003cstrong>Drop here\u003c/strong>\n        \u003c/button>\n      \n    \u003c/div>\n  \n\u003c/div>\n\n  \u003c/div>\n\u003c/template>\n\u003ctemplate data-composer-recipe-template=\"progress\">\n  \u003cdiv class=\"landing-composer__rendered-recipe\" data-composer-rendered-recipe=\"progress\">\n    \n\u003cdiv class=\"cui-card\" data-variant=\"subtle\" data-size=\"md\" style=\"margin: 0.5rem 0; --cui-card-background: color-mix(in srgb, #6d5ce7 7%, Canvas); --cui-card-border-color: color-mix(in srgb, #6d5ce7 20%, transparent);\" data-citry-ui-part=\"card\">\n  \n  \n    \u003cdiv class=\"cui-card__header\" data-citry-ui-part=\"header\">\n      \n        \u003cdiv class=\"cui-card__header-content\">\n          \n        \n\u003cdiv class=\"cui-row\" data-citry-ui-part=\"row\" data-gap=\"sm\" data-align=\"center\" data-justify=\"between\" data-wrap>\n  \n          Importing observations\n          \n\u003cspan class=\"cui-badge\" data-citry-ui-part=\"badge\" data-variant=\"soft\" data-intent=\"primary\" data-size=\"md\" data-shape=\"pill\">\n  \n  \u003cspan class=\"cui-badge__label\" data-citry-ui-part=\"label\">\n    \n            68%\n          \n  \u003c/span>\n  \n\u003c/span>\n\n        \n\u003c/div>\n\n      \n        \u003c/div>\n      \n      \n    \u003c/div>\n  \n  \n    \u003cdiv class=\"cui-card__body\" data-citry-ui-part=\"body\">\n      \n        \n\u003cprogress class=\"cui-progress\" style=\"--cui-progress-range-color: light-dark(#5946d2, #b4a9ff); --cui-progress-track-color: color-mix(in srgb, var(--cui-progress-range-color) 15%, Canvas);\" data-citry-ui-part=\"progress\" data-state=\"determinate\" data-intent=\"primary\" data-size=\"lg\" data-shape=\"pill\" aria-label=\"Importing observations\" value=\"68.0\" max=\"100.0\" data-citry-root=\"\">\u2068Importing observations\u2069: \u206868\u2069 of \u2068100\u2069\u003c/progress>\n\n      \n    \u003c/div>\n  \n  \n\u003c/div>\n\n  \u003c/div>\n\u003c/template>"</script></div></div>

</section>

<section class="landing-section" id="depth" markdown="1">
## Grow without a rewrite.

<p class="landing-section__intro">
A product that works starts running into different problems. None of them need a different framework.
</p>

Read about [caching](/advanced/caching/),
[extensions](/advanced/extensions/),
[internationalization](/i18n/),
[CSRF protection](/security/#protect-event-posts-from-csrf),
[strict CSP](/security/#choose-a-csp-compatibility-mode),
[HTML fragments](/advanced/html-fragments/),
[component libraries](/advanced/component-libraries/), and
[performance](/advanced/performance/).




<div markdown="0"><div class="landing-picker" data-landing-picker><div class="landing-picker__item"><button class="landing-picker__row is-active" type="button" data-picker-case="cache" aria-pressed="true"><span class="landing-picker__number"> 01 </span><span><strong>Caching</strong><br/><span class="landing-picker__blurb"></span></span><svg class="landing-picker__caret" viewBox="0 0 16 16" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 6l4 4 4-4"></path></svg></button><div class="landing-picker__panel" data-picker-panel="cache"><div class="landing-picker__note"><p>Use cache in two ways: Either cache single Component class, or cache a region in the template.</p><p>Connect any backend. Use <code>version</code> to retire old entries on deploy.</p></div><div class="landing-code landing-picker__code"><div class="landing-code__bar"><span class="landing-code__dot"></span><span>product_card.py</span></div><div class="highlight"><pre><span></span><span class="k">class</span><span class="w"> </span><span class="nc">ProductCard</span><span class="p">(</span><span class="n">Component</span><span class="p">):</span>&#10;    <span class="k">class</span><span class="w"> </span><span class="nc">Kwargs</span><span class="p">:</span>&#10;        <span class="n">product_id</span><span class="p">:</span> <span class="nb">int</span>&#10;&#10;    <span class="k">class</span><span class="w"> </span><span class="nc">Slots</span><span class="p">:</span>&#10;        <span class="k">pass</span>&#10;&#10;    <span class="c1"># Cache every time this component is called</span>&#10;    <span class="k">class</span><span class="w"> </span><span class="nc">Cache</span><span class="p">:</span>&#10;        <span class="n">enabled</span> <span class="o">=</span> <span class="kc">True</span>&#10;        <span class="n">ttl</span> <span class="o">=</span> <span class="mi">300</span>&#10;        <span class="n">version</span> <span class="o">=</span> <span class="mi">1</span>&#10;&#10;    <span class="nv">template</span> <span class="o">=</span> <span class="sd">&quot;&quot;&quot;</span>&#10;      <span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span>&#10;        <span class="c">{# Cache only this region #}</span>&#10;        <span class="p">&lt;</span><span class="nt">c-cache</span> <span class="na">key</span><span class="o">=</span><span class="s">&quot;expensive&quot;</span><span class="p">&gt;</span>&#10;          <span class="p">&lt;</span><span class="nt">c-ExpensiveUI</span> <span class="p">/&gt;</span>&#10;        <span class="p">&lt;/</span><span class="nt">c-cache</span><span class="p">&gt;</span>&#10;      <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>&#10;    <span class="sd">&quot;&quot;&quot;</span>&#10;</pre></div></div></div></div><div class="landing-picker__item"><button class="landing-picker__row" type="button" data-picker-case="extensions" aria-pressed="false"><span class="landing-picker__number"> 02 </span><span><strong>Extensions</strong><br/><span class="landing-picker__blurb"></span></span><svg class="landing-picker__caret" viewBox="0 0 16 16" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 6l4 4 4-4"></path></svg></button><div class="landing-picker__panel" data-picker-panel="extensions"><div class="landing-picker__note"><p>Extend all components at once with extensions that hook into Citry's machinery.</p><p>Hooks cover the render lifecycle, components' JS and CSS scripts, and more. Extensions can be configured globally or per-component. Extensions can also add their own URL endpoints and CLI commands.</p></div><div class="landing-code landing-picker__code"><div class="landing-code__bar"><span class="landing-code__dot"></span><span>timing.py</span></div><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">citry</span><span class="w"> </span><span class="kn">import</span> <span class="n">Citry</span><span class="p">,</span> <span class="n">Extension</span>&#10;&#10;<span class="k">class</span><span class="w"> </span><span class="nc">TimingExtension</span><span class="p">(</span><span class="n">Extension</span><span class="p">):</span>&#10;    <span class="n">name</span> <span class="o">=</span> <span class="s2">&quot;timing&quot;</span>&#10;&#10;    <span class="k">def</span><span class="w"> </span><span class="nf">on_component_rendered</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">ctx</span><span class="p">):</span>&#10;        <span class="n">record</span><span class="p">(</span><span class="nb">type</span><span class="p">(</span><span class="n">ctx</span><span class="o">.</span><span class="n">component</span><span class="p">)</span><span class="o">.</span><span class="vm">__name__</span><span class="p">)</span>&#10;        <span class="k">return</span> <span class="kc">None</span>  <span class="c1"># keep the original render</span>&#10;&#10;<span class="n">app</span> <span class="o">=</span> <span class="n">Citry</span><span class="p">(</span><span class="n">extensions</span><span class="o">=</span><span class="p">[</span><span class="n">TimingExtension</span><span class="p">])</span>&#10;</pre></div></div></div></div><div class="landing-picker__item"><button class="landing-picker__row" type="button" data-picker-case="i18n" aria-pressed="false"><span class="landing-picker__number"> 03 </span><span><strong>Internationalization</strong><br/><span class="landing-picker__blurb"></span></span><svg class="landing-picker__caret" viewBox="0 0 16 16" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 6l4 4 4-4"></path></svg></button><div class="landing-picker__panel" data-picker-panel="i18n"><div class="landing-picker__note"><p>Citry has first-class support for internationalisation (i18n) and localisation (l10n). Use <a href="https://github.com/projectfluent/fluent" rel="noreferrer noopener" target="_blank">Fluent</a> messages beside components to define translatable text and its inputs. Put translations in locale catalogs, or install catalogs from third-party packages.</p><p>Also handles locale-aware formatting and parsing of numbers, dates, currencies, direction, and more.</p></div><div class="landing-code landing-picker__code"><div class="landing-code__bar"><span class="landing-code__dot"></span><span>account_card.py</span></div><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">citry</span><span class="w"> </span><span class="kn">import</span> <span class="n">Component</span>&#10;<span class="kn">from</span><span class="w"> </span><span class="nn">citry.ext.i18n</span><span class="w"> </span><span class="kn">import</span> <span class="n">make_context</span>&#10;&#10;<span class="k">class</span><span class="w"> </span><span class="nc">AccountCard</span><span class="p">(</span><span class="n">Component</span><span class="p">):</span>&#10;    <span class="k">class</span><span class="w"> </span><span class="nc">Kwargs</span><span class="p">:</span>&#10;        <span class="n">name</span><span class="p">:</span> <span class="nb">str</span>&#10;&#10;    <span class="nv">template</span> <span class="o">=</span> <span class="sd">&quot;&quot;&quot;</span>&#10;      <span class="p">&lt;</span><span class="nt">h2</span><span class="p">&gt;{{</span> <span class="n">tr</span><span class="p">(</span><span class="s2">&quot;account-greeting&quot;</span><span class="p">,</span> <span class="n">name</span><span class="o">=</span><span class="n">name</span><span class="p">)</span> <span class="p">}}&lt;/</span><span class="nt">h2</span><span class="p">&gt;</span>&#10;    <span class="sd">&quot;&quot;&quot;</span>&#10;&#10;    <span class="nv">messages</span> <span class="o">=</span> <span class="sd">&quot;&quot;&quot;</span>&#10;      <span class="cm"># @param {str} $name - User name.</span>&#10;      <span class="no">account-greeting</span><span class="p"> = </span><span class="l">Welcome, </span><span class="p">{ </span><span class="nv">$name</span><span class="p"> }</span><span class="l">.</span>&#10;    <span class="sd">&quot;&quot;&quot;</span>&#10;&#10;<span class="n">context</span> <span class="o">=</span> <span class="n">make_context</span><span class="p">(</span><span class="n">app</span><span class="p">,</span> <span class="n">locale</span><span class="o">=</span><span class="n">request</span><span class="o">.</span><span class="n">locale</span><span class="p">)</span>&#10;<span class="n">AccountCard</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="n">user</span><span class="o">.</span><span class="n">name</span><span class="p">)</span><span class="o">.</span><span class="n">render</span><span class="p">(</span>&#10;    <span class="n">provides</span><span class="o">=</span><span class="p">{</span><span class="s2">&quot;citry_i18n&quot;</span><span class="p">:</span> <span class="n">context</span><span class="p">},</span>&#10;<span class="p">)</span>&#10;</pre></div></div></div></div><div class="landing-picker__item"><button class="landing-picker__row" type="button" data-picker-case="csrf" aria-pressed="false"><span class="landing-picker__number"> 04 </span><span><strong>CSRF protection</strong><br/><span class="landing-picker__blurb"></span></span><svg class="landing-picker__caret" viewBox="0 0 16 16" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 6l4 4 4-4"></path></svg></button><div class="landing-picker__panel" data-picker-panel="csrf"><div class="landing-picker__note"><p>The web framework of your choice generates and validates the CSRF token:</p><ul><li>Django's middleware protects Citry routes unchanged</li><li>Other hosts can plug in their own token check and browser token source.</li></ul></div><div class="landing-code landing-picker__code"><div class="landing-code__bar"><span class="landing-code__dot"></span><span>security.py</span></div><div class="highlight"><pre><span></span><span class="k">class</span><span class="w"> </span><span class="nc">Profile</span><span class="p">(</span><span class="n">Component</span><span class="p">):</span>&#10;    <span class="k">class</span><span class="w"> </span><span class="nc">Events</span><span class="p">:</span>&#10;        <span class="c1"># Optional for hosts with a custom token scheme.</span>&#10;        <span class="c1"># Django&#39;s CsrfViewMiddleware needs no Citry setup.</span>&#10;        <span class="n">_csrf</span> <span class="o">=</span> <span class="n">check_csrf</span>&#10;&#10;        <span class="k">def</span><span class="w"> </span><span class="nf">save</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">data</span><span class="p">:</span> <span class="n">ProfileIn</span><span class="p">):</span>&#10;            <span class="n">update_profile</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>&#10;</pre></div></div></div></div><div class="landing-picker__item"><button class="landing-picker__row" type="button" data-picker-case="csp" aria-pressed="false"><span class="landing-picker__number"> 05 </span><span><strong>Strict CSP</strong><br/><span class="landing-picker__blurb"></span></span><svg class="landing-picker__caret" viewBox="0 0 16 16" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 6l4 4 4-4"></path></svg></button><div class="landing-picker__panel" data-picker-panel="csp"><div class="landing-picker__note"><p>Citry can automatically pass the CSP nonce to all scripts and styles.</p><p>Citry has 2 CSP modes:</p><ul><li>Strict - Uses Alpine CSP build, raises error on incompatible syntax. Use for production.</li><li>Warning - Uses regular Alpine, prints all incompatibilities, but doesn't block you. Use for development.</li></ul><br/><p>Optionally add verified SHA-384 integrity to the scripts.</p></div><div class="landing-code landing-picker__code"><div class="landing-code__bar"><span class="landing-code__dot"></span><span>csp.py</span></div><div class="highlight"><pre><span></span><span class="n">app</span> <span class="o">=</span> <span class="n">Citry</span><span class="p">(</span>&#10;    <span class="n">security_csp</span><span class="o">=</span><span class="s2">&quot;strict&quot;</span><span class="p">,</span>&#10;    <span class="n">security_script_integrity</span><span class="o">=</span><span class="s2">&quot;citry&quot;</span><span class="p">,</span>&#10;<span class="p">)</span>&#10;&#10;<span class="c1"># Generate a new value for every response.</span>&#10;<span class="n">nonce</span> <span class="o">=</span> <span class="n">new_response_nonce</span><span class="p">()</span>&#10;<span class="n">result</span> <span class="o">=</span> <span class="n">Page</span><span class="p">()</span><span class="o">.</span><span class="n">render</span><span class="p">()</span><span class="o">.</span><span class="n">serialize_result</span><span class="p">(</span><span class="n">csp_nonce</span><span class="o">=</span><span class="n">nonce</span><span class="p">)</span>&#10;&#10;<span class="n">response</span> <span class="o">=</span> <span class="n">HTMLResponse</span><span class="p">(</span><span class="n">result</span><span class="o">.</span><span class="n">html</span><span class="p">)</span>&#10;<span class="n">response</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s2">&quot;Content-Security-Policy&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="p">(</span>&#10;    <span class="sa">f</span><span class="s2">&quot;script-src &#39;self&#39; &#39;nonce-</span><span class="si">{</span><span class="n">nonce</span><span class="si">}</span><span class="s2">&#39;; &quot;</span>&#10;    <span class="sa">f</span><span class="s2">&quot;style-src &#39;self&#39; &#39;nonce-</span><span class="si">{</span><span class="n">nonce</span><span class="si">}</span><span class="s2">&#39;&quot;</span>&#10;<span class="p">)</span>&#10;</pre></div></div></div></div><div class="landing-picker__item"><button class="landing-picker__row" type="button" data-picker-case="fragments" aria-pressed="false"><span class="landing-picker__number"> 06 </span><span><strong>HTML fragments</strong><br/><span class="landing-picker__blurb"></span></span><svg class="landing-picker__caret" viewBox="0 0 16 16" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 6l4 4 4-4"></path></svg></button><div class="landing-picker__panel" data-picker-panel="fragments"><div class="landing-picker__note"><p>Easily integrate with HTMX. Instead of rendering a full page, render only small HTML to update one region.</p><p>Fragments carry their own JS and CSS. Citry loads whatever that region needs. Duplicate assets are never loaded twice.</p></div><div class="landing-code landing-picker__code"><div class="landing-code__bar"><span class="landing-code__dot"></span><span>views.py</span></div><div class="highlight"><pre><span></span><span class="n">card</span> <span class="o">=</span> <span class="n">Card</span><span class="p">(</span><span class="n">title</span><span class="o">=</span><span class="s2">&quot;Welcome&quot;</span><span class="p">)</span>&#10;&#10;<span class="c1"># The browser gets the markup and whatever JS and CSS it still needs</span>&#10;<span class="n">card</span><span class="o">.</span><span class="n">render</span><span class="p">()</span><span class="o">.</span><span class="n">serialize</span><span class="p">(</span><span class="n">deps_strategy</span><span class="o">=</span><span class="s2">&quot;fragment&quot;</span><span class="p">)</span>&#10;</pre></div></div></div></div><div class="landing-picker__item"><button class="landing-picker__row" type="button" data-picker-case="libraries" aria-pressed="false"><span class="landing-picker__number"> 07 </span><span><strong>Component libraries</strong><br/><span class="landing-picker__blurb"></span></span><svg class="landing-picker__caret" viewBox="0 0 16 16" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 6l4 4 4-4"></path></svg></button><div class="landing-picker__panel" data-picker-panel="libraries"><div class="landing-picker__note"><p>Share and publish components across projects as component libraries. Install a library with <code>Citry.register_library()</code></p><p>Ideal for design systems or publishing to registries.</p></div><div class="landing-code landing-picker__code"><div class="landing-code__bar"><span class="landing-code__dot"></span><span>acme_ui/badge.py</span></div><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">citry</span><span class="w"> </span><span class="kn">import</span> <span class="p">(</span>&#10;    <span class="n">ComponentLibrary</span><span class="p">,</span>&#10;    <span class="n">LibraryComponent</span><span class="p">,</span>&#10;    <span class="n">SlotInput</span><span class="p">,</span>&#10;    <span class="n">citry</span><span class="p">,</span>&#10;<span class="p">)</span>&#10;&#10;<span class="c1"># Define library components</span>&#10;<span class="k">class</span><span class="w"> </span><span class="nc">AcmeBadge</span><span class="p">(</span><span class="n">LibraryComponent</span><span class="p">):</span>&#10;    <span class="k">class</span><span class="w"> </span><span class="nc">Kwargs</span><span class="p">:</span>&#10;        <span class="n">tone</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&quot;neutral&quot;</span>&#10;&#10;    <span class="k">class</span><span class="w"> </span><span class="nc">Slots</span><span class="p">:</span>&#10;        <span class="n">default</span><span class="p">:</span> <span class="n">SlotInput</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span>&#10;&#10;<span class="c1"># Create Library</span>&#10;<span class="n">acme_library</span> <span class="o">=</span> <span class="n">ComponentLibrary</span><span class="p">(</span>&#10;    <span class="n">name</span><span class="o">=</span><span class="s2">&quot;acme&quot;</span><span class="p">,</span>&#10;    <span class="n">components</span><span class="o">=</span><span class="p">[</span><span class="n">AcmeBadge</span><span class="p">],</span>&#10;<span class="p">)</span>&#10;&#10;<span class="c1"># Register library with Citry</span>&#10;<span class="n">citry</span><span class="o">.</span><span class="n">register_library</span><span class="p">(</span><span class="n">acme_library</span><span class="p">)</span>&#10;</pre></div></div></div></div><div class="landing-picker__item"><button class="landing-picker__row" type="button" data-picker-case="const" aria-pressed="false"><span class="landing-picker__number"> 08 </span><span><strong>Perf optimization</strong><br/><span class="landing-picker__blurb"></span></span><svg class="landing-picker__caret" viewBox="0 0 16 16" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 6l4 4 4-4"></path></svg></button><div class="landing-picker__panel" data-picker-panel="const"><div class="landing-picker__note"><p>Stop re-rendering what doesn't change. Mark constants and literals with <code>Const</code>. Citry will recognize it and optimize the template graph.</p><p>Marking a value in <code>Const</code> is a promise that the value will not change between renders.</p></div><div class="landing-code landing-picker__code"><div class="landing-code__bar"><span class="landing-code__dot"></span><span>dashboard.py</span></div><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">citry</span><span class="w"> </span><span class="kn">import</span> <span class="n">Const</span>&#10;&#10;<span class="c1"># The parts that never vary are rendered once and reused</span>&#10;<span class="n">Card</span><span class="p">(</span><span class="n">cols</span><span class="o">=</span><span class="n">Const</span><span class="p">(</span><span class="mi">3</span><span class="p">))</span>&#10;</pre></div></div></div></div></div></div>




</section>

<section class="landing-section" id="people" markdown="1">
## Built in public by people who care about Python and the web.

<p class="landing-section__intro">
Citry is the successor to
<a href="https://github.com/django-components/django-components" target="_blank" rel="noopener">django-components</a>
(1.5k stars), distilling years of experience into an elegant and powerful framework.
</p>

<div class="landing-human-grid" markdown="1">

<div markdown="1">

<p style="margin-bottom: 0; margin-top: 0.5rem;">
This project would be nothing without its community. The people below have contributed to Citry or django-components:
</p>



<div markdown="0"><div class="user-list"><div class="user"><div class="avatar-wrapper"><img src="https://avatars.githubusercontent.com/u/7218756?u=b8200f8ba87b4664be41534c2e09ee4e60f153fb&amp;v=4" alt="GitHub avatar of dylanjcastillo"/></div></div><div class="user"><div class="avatar-wrapper"><img src="https://avatars.githubusercontent.com/u/100985243?u=cb5789ce1a4906c3ad5aa068d3848cb2b02f021b&amp;v=4" alt="GitHub avatar of Antoliny0919"/></div></div><div class="user"><div class="avatar-wrapper"><img src="https://avatars.githubusercontent.com/u/2442690?v=4" alt="GitHub avatar of rbeard0330"/></div></div><div class="user"><div class="avatar-wrapper"><img src="https://avatars.githubusercontent.com/u/15968876?u=9d928ac8fed60b9e80b5f820faaadf0eedd72c2f&amp;v=4" alt="GitHub avatar of a3lem"/></div></div><div class="user"><div class="avatar-wrapper"><img src="https://avatars.githubusercontent.com/u/2648874?u=1d54662bc5cd27809d2a8b536267a8b176c8d297&amp;v=4" alt="GitHub avatar of dalito"/></div></div><div class="user"><div class="avatar-wrapper"><img src="https://avatars.githubusercontent.com/u/53351186?u=eea8e215b2e0164fec312eb4e80ac99444e98cc4&amp;v=4" alt="GitHub avatar of hanifbirgani"/></div></div><div class="user"><div class="avatar-wrapper"><img src="https://avatars.githubusercontent.com/u/4050664?u=5516136eb659cc674837d6806cdd6b7f21ac4b17&amp;v=4" alt="GitHub avatar of oliverhaas"/></div></div><div class="user"><div class="avatar-wrapper"><img src="https://avatars.githubusercontent.com/u/1116720?u=4ad25dae2c7885a146ebccfd2d24478c49d5ab7d&amp;v=4" alt="GitHub avatar of GabDug"/></div></div><div class="user"><div class="avatar-wrapper"><img src="https://avatars.githubusercontent.com/u/425099?u=195682b0b11f8b5f0bf5f88a353e7536ab0f54a9&amp;v=4" alt="GitHub avatar of ryanhiebert"/></div></div><div class="user"><div class="avatar-wrapper"><img src="https://avatars.githubusercontent.com/u/54205005?u=a9959d892ab525d6a74eb8a3a3010c17ffb550e9&amp;v=4" alt="GitHub avatar of VojtechPetru"/></div></div><div class="user"><div class="avatar-wrapper"><img src="https://avatars.githubusercontent.com/u/6583221?u=a4b821a792069d41ded4d5201af76e689792cc55&amp;v=4" alt="GitHub avatar of BradleyKirton"/></div></div><div class="user"><div class="avatar-wrapper"><img src="https://avatars.githubusercontent.com/u/249779?u=fa942b72ff80db49a15f75e8126abf830334a030&amp;v=4" alt="GitHub avatar of danjac"/></div></div><div class="user"><div class="avatar-wrapper"><img src="https://avatars.githubusercontent.com/u/2231969?u=b70e881326acc415636fa89b6017141d18eb50ce&amp;v=4" alt="GitHub avatar of Real-Gecko"/></div></div><div class="user"><div class="avatar-wrapper"><img src="https://avatars.githubusercontent.com/u/245021?v=4" alt="GitHub avatar of simkimsia"/></div></div><div class="user"><div class="avatar-wrapper"><img src="https://avatars.githubusercontent.com/u/3911074?v=4" alt="GitHub avatar of spapas"/></div></div><div class="user"><div class="avatar-wrapper"><img src="https://avatars.githubusercontent.com/u/20780097?u=13d747e9e93dec531c5af304a2d125d0e438dbef&amp;v=4" alt="GitHub avatar of ekaj2"/></div></div><div class="user"><div class="avatar-wrapper"><img src="https://avatars.githubusercontent.com/u/32698524?v=4" alt="GitHub avatar of alexandreMartinEcl"/></div></div><div class="user"><div class="avatar-wrapper"><img src="https://avatars.githubusercontent.com/u/43130989?v=4" alt="GitHub avatar of ldurey"/></div></div><div class="user"><div class="avatar-wrapper"><img src="https://avatars.githubusercontent.com/u/10505?u=d4e4574c736ba4de30962798dcbb05ab2949324f&amp;v=4" alt="GitHub avatar of marcfargas"/></div></div><div class="user"><div class="avatar-wrapper"><img src="https://avatars.githubusercontent.com/u/1459574?u=2a4addd99ffc484ba6167643cbd27d0f8423a827&amp;v=4" alt="GitHub avatar of spollard"/></div></div><div class="user"><div class="avatar-wrapper"><img src="https://avatars.githubusercontent.com/u/1136694?u=edec3b76e59723a7df71642f62ebc40f11b89f60&amp;v=4" alt="GitHub avatar of David-Guillot"/></div></div><div class="user"><div class="avatar-wrapper"><img src="https://avatars.githubusercontent.com/u/44384710?u=954224f9184f9cacc2d5057de95fbcd640fb44a7&amp;v=4" alt="GitHub avatar of housUnus"/></div></div><div class="user"><div class="avatar-wrapper"><img src="https://avatars.githubusercontent.com/u/39964466?u=3b24bbe21a009838a3cfdb95d714fdaea48efb10&amp;v=4" alt="GitHub avatar of rafae56038"/></div></div><div class="user"><div class="avatar-wrapper"><img src="https://avatars.githubusercontent.com/u/1010043?u=bb00beecdf6a7c9cf2ce733de3f0405608777d18&amp;v=4" alt="GitHub avatar of mands"/></div></div><div class="user"><div class="avatar-wrapper"><img src="https://avatars.githubusercontent.com/u/2660905?v=4" alt="GitHub avatar of hjalves"/></div></div><div class="user"><div class="avatar-wrapper"><img src="https://avatars.githubusercontent.com/u/1695613?u=1f42d7808b8fb3a5ecbab2891c437af6c199d366&amp;v=4" alt="GitHub avatar of timothyis"/></div></div><div class="user"><div class="avatar-wrapper"><img src="https://avatars.githubusercontent.com/u/351535?u=280d289dc13f333d8e8cc57ebedf142259634678&amp;v=4" alt="GitHub avatar of ar4s"/></div></div><div class="user"><div class="avatar-wrapper"><img src="https://avatars.githubusercontent.com/u/75075?u=7b73005c5bcff5113c13e3ec87907df5a7fa973e&amp;v=4" alt="GitHub avatar of imankulov"/></div></div><div class="user"><div class="avatar-wrapper"><img src="https://avatars.githubusercontent.com/u/446634?u=58fb53e563e7ffa324d564206d6e986d63e091a0&amp;v=4" alt="GitHub avatar of rwblokzijl"/></div></div><div class="user"><div class="avatar-wrapper"><img src="https://avatars.githubusercontent.com/u/54102462?u=740061c7bdc8c7674bb0386a84c441537af6e16f&amp;v=4" alt="GitHub avatar of franciscobmacedo"/></div></div><div class="user"><div class="avatar-wrapper"><img src="https://avatars.githubusercontent.com/u/159043084?v=4" alt="GitHub avatar of tanssinet"/></div></div><div class="user"><div class="avatar-wrapper"><img src="https://avatars.githubusercontent.com/u/79967988?v=4" alt="GitHub avatar of TheSteveBurgess"/></div></div><div class="user"><div class="avatar-wrapper"><img src="https://avatars.githubusercontent.com/u/100217654?u=fbd4bc65c17d90c71733a4553092cc8c40fcc44c&amp;v=4" alt="GitHub avatar of KPCOFGS"/></div></div><div class="user"><div class="avatar-wrapper"><img src="https://avatars.githubusercontent.com/u/37776903?u=c7a33546235920113dd1a556135c84c76ddcc35f&amp;v=4" alt="GitHub avatar of mikucz"/></div></div><div class="user"><div class="avatar-wrapper"><img src="https://avatars.githubusercontent.com/u/24536303?u=02fc27c66233b355ba48d9601b3a081566279376&amp;v=4" alt="GitHub avatar of zachbellay"/></div></div><div class="user"><div class="avatar-wrapper"><img src="https://avatars.githubusercontent.com/u/50702276?u=6459bd912629cf2d4bab426ce2ca5b18cb312bce&amp;v=4" alt="GitHub avatar of daniboygg"/></div></div><div class="user"><div class="avatar-wrapper"><img src="https://avatars.githubusercontent.com/u/213949?u=c11bc9fc9363792c257f84ff5915b4e7bc8a389a&amp;v=4" alt="GitHub avatar of batistadasilva"/></div></div><div class="user"><div class="avatar-wrapper"><img src="https://avatars.githubusercontent.com/u/74967570?u=93f42f6fdf12c0edd9efeb791d799786cd068bad&amp;v=4" alt="GitHub avatar of Yaso2Go"/></div></div><div class="user"><div class="avatar-wrapper"><img src="https://avatars.githubusercontent.com/u/339129?u=061406f20416558a6baa11ff460868ff9107d72c&amp;v=4" alt="GitHub avatar of larsent"/></div></div><div class="user"><div class="avatar-wrapper"><img src="https://avatars.githubusercontent.com/u/84413102?v=4" alt="GitHub avatar of vb8448"/></div></div><div class="user"><div class="avatar-wrapper"><img src="https://avatars.githubusercontent.com/u/7248707?u=232111eeca33c2c3edebf460838c6f6a6269c72e&amp;v=4" alt="GitHub avatar of lhole"/></div></div><div class="user"><div class="avatar-wrapper"><img src="https://avatars.githubusercontent.com/u/236970?u=413ba80d74a12e5b47548b4a9bc758a1ebdfe72a&amp;v=4" alt="GitHub avatar of mlissner"/></div></div><div class="user"><div class="avatar-wrapper"><img src="https://avatars.githubusercontent.com/u/82199014?u=2f6cf9b0d3d090a4cda7d3637c977a47d67d0c41&amp;v=4" alt="GitHub avatar of bfrangi"/></div></div><div class="user"><div class="avatar-wrapper"><img src="https://avatars.githubusercontent.com/u/140476421?u=0256efb59c96d1c4927bfc73882f4a7db46386a1&amp;v=4" alt="GitHub avatar of ralphbibera"/></div></div><div class="user"><div class="avatar-wrapper"><img src="https://avatars.githubusercontent.com/u/30392053?u=e698f6c815e634623853a68ace3e2ff60873f70b&amp;v=4" alt="GitHub avatar of PavelPancocha"/></div></div><div class="user"><div class="avatar-wrapper"><img src="https://avatars.githubusercontent.com/u/488182?u=0cda620fabe211c474acd8ab01147d23e9186be7&amp;v=4" alt="GitHub avatar of KyeRussell"/></div></div><div class="user"><div class="avatar-wrapper"><img src="https://avatars.githubusercontent.com/u/7702200?u=24be56f38126585e1dd5dfb4d49364e519bcd231&amp;v=4" alt="GitHub avatar of joeyjurjens"/></div></div><div class="user"><div class="avatar-wrapper"><img src="https://avatars.githubusercontent.com/u/41957095?u=e7552569ce94efff6997805e319eff49a3f32731&amp;v=4" alt="GitHub avatar of barakharyati"/></div></div><div class="user"><div class="avatar-wrapper"><img src="https://avatars.githubusercontent.com/u/2124818?u=2451088bf3f7434950a62b848a188e1b30eeb3cb&amp;v=4" alt="GitHub avatar of jonathan-s"/></div></div></div></div>



## Fund the work

Citry is built in the open with funding from its sponsors. They get the roadmap
early, a direct line to the maintainer, and a say in what gets built next.

### [Sponsor Citry](https://github.com/sponsors/JuroOravec){: target="_blank" rel="noopener"}

</div>
<div markdown="1">

<div class="landing-maintainer">
<div class="landing-maintainer__portrait">


<div markdown="0"><div class="user-list"><div class="user"><div class="avatar-wrapper"><img src="https://avatars.githubusercontent.com/u/25986782?u=092d1a590439321e2bc0be0a788fc3a8b1afda8f&amp;v=4" alt="GitHub avatar of JuroOravec"/></div></div></div></div>


</div>
<div class="landing-maintainer__identity">
<p class="landing-maintainer__name"><a href="/community/people/">Juro Oravec</a></p>
<p class="landing-maintainer__role">Creator and maintainer of Citry</p>
</div>
</div>

<div class="landing-human-links">
  <a href="/community/people/">Meet the people building Citry</a>
  <a href="/community/help/">Ask a question</a>
  <a href="/community/contributing/">Help improve Citry</a>
</div>
</div>
</div>
</section>

<section class="landing-final" markdown="1">
## Discover frontend that brings joy.

<div class="landing-actions">
  <a class="landing-button landing-button--primary" href="/getting-started/installation/">
    Start the tutorial
    <svg class="landing-button__arrow" viewBox="0 0 16 16" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 8h11M9 4l4 4-4 4"></path></svg>
  </a>
  <a class="landing-button" href="/examples/">Explore examples</a>
</div>

<div class="landing-install">
  <code>pip install citry</code>
  <button class="landing-copy" type="button" data-copy-install>Copy</button>
</div>
</section>