diff --git a/.github/workflows/integration_tests.yml b/.github/workflows/integration_tests.yml index e02b3bf921f..b79fe5c40db 100644 --- a/.github/workflows/integration_tests.yml +++ b/.github/workflows/integration_tests.yml @@ -164,9 +164,9 @@ jobs: which npm && npm -v uv run --active --no-sync bash scripts/integration.sh ./docs/app prod - - name: Validate /docs links against generated sitemap + - name: Validate docs links and published SEO metadata working-directory: ./docs/app - run: uv run --active --no-sync pytest --runxfail tests/test_doc_links.py -v + run: uv run --active --no-sync pytest --runxfail tests/test_doc_links.py tests/test_published_seo.py -v - name: Run docs frontend regression tests working-directory: ./docs/app diff --git a/docs/advanced_onboarding/code_structure.md b/docs/advanced_onboarding/code_structure.md index 2e6f103a60d..e9877955256 100644 --- a/docs/advanced_onboarding/code_structure.md +++ b/docs/advanced_onboarding/code_structure.md @@ -259,7 +259,7 @@ component. ### External Components -Reflex 0.4.3 introduced support for the [`reflex component` CLI commands](/docs/custom-components/overview), which makes it easy +Reflex 0.4.3 introduced support for the [`reflex component` CLI commands](/docs/custom-components/overview/), which makes it easy to bundle up common functionality to publish on PyPI as a standalone Python package that can be installed and used in any Reflex app. diff --git a/docs/advanced_onboarding/configuration.md b/docs/advanced_onboarding/configuration.md index 68f2eb5306a..f8b09d2848b 100644 --- a/docs/advanced_onboarding/configuration.md +++ b/docs/advanced_onboarding/configuration.md @@ -45,7 +45,7 @@ Finally, you can override the configuration file and environment variables by pa uv run reflex run --frontend-port 3001 ``` -See the [CLI reference](/docs/api-reference/cli) for all the arguments available. +See the [CLI reference](/docs/api-reference/cli/) for all the arguments available. ## Loading a .env File diff --git a/docs/advanced_onboarding/how-reflex-works.md b/docs/advanced_onboarding/how-reflex-works.md index 5ebd52537f7..4d9f5f28d19 100644 --- a/docs/advanced_onboarding/how-reflex-works.md +++ b/docs/advanced_onboarding/how-reflex-works.md @@ -117,15 +117,15 @@ Many of our core components are based on [Radix](https://radix-ui.com/), a popul We chose React because it is a popular library with a huge ecosystem. Our goal isn't to recreate the web ecosystem, but to make it accessible to Python developers. -This also lets our users bring their own components if we don't have a component they need. Users can [wrap their own React components](/docs/wrapping-react/overview) and then [publish them](/docs/custom-components/overview) for others to use. Over time we will build out our [third party component ecosystem](/docs/custom-components/overview) so that users can easily find and use components that others have built. +This also lets our users bring their own components if we don't have a component they need. Users can [wrap their own React components](/docs/wrapping-react/overview/) and then [publish them](/docs/custom-components/overview/) for others to use. Over time we will build out our [third party component ecosystem](/docs/custom-components/overview/) so that users can easily find and use components that others have built. ### Styling We wanted to make sure Reflex apps look good out of the box, while still giving developers full control over the appearance of their app. -We have a core [theming system](/docs/styling/theming) that lets you set high level styling options such as dark mode and accent color throughout your app to give it a unified look and feel. +We have a core [theming system](/docs/styling/theming/) that lets you set high level styling options such as dark mode and accent color throughout your app to give it a unified look and feel. -Beyond this, Reflex components can be styled using the full power of CSS. We leverage the [Emotion](https://emotion.sh/docs/introduction) library to allow "CSS-in-Python" styling, so you can pass any CSS prop as a keyword argument to a component. This includes [responsive props](/docs/styling/responsive) by passing a list of values. +Beyond this, Reflex components can be styled using the full power of CSS. We leverage the [Emotion](https://emotion.sh/docs/introduction) library to allow "CSS-in-Python" styling, so you can pass any CSS prop as a keyword argument to a component. This includes [responsive props](/docs/styling/responsive/) by passing a list of values. ## Backend @@ -200,7 +200,7 @@ On the frontend, we maintain an event queue of all pending events. When an event is triggered, it is added to the queue. We have a `processing` flag to make sure only one event is processed at a time. This ensures that the state is always consistent and there aren't any race conditions with two event handlers modifying the state at the same time. ```md alert info -# There are exceptions to this, such as [background events](/docs/events/background-events) which allow you to run events in the background without blocking the UI. +# There are exceptions to this, such as [background events](/docs/events/background-events/) which allow you to run events in the background without blocking the UI. ``` Once the event is ready to be processed, it is sent to the backend through a WebSocket connection. @@ -228,7 +228,7 @@ In our example, the `set_profile` event handler is run on the user's state. This ### State Updates -Every time an event handler returns (or [yields](/docs/events/yield-events)), we save the state in the state manager and send the **state updates** to the frontend to update the UI. +Every time an event handler returns (or [yields](/docs/events/yield-events/)), we save the state in the state manager and send the **state updates** to the frontend to update the UI. To maintain performance as your state grows, internally Reflex keeps track of vars that were updated during the event handler (**dirty vars**). When the event handler is done processing, we find all the dirty vars and create a state update to send to the frontend. diff --git a/docs/ai_builder/figma.md b/docs/ai_builder/figma.md index efb67caf154..76242f3fe0d 100644 --- a/docs/ai_builder/figma.md +++ b/docs/ai_builder/figma.md @@ -1,3 +1,21 @@ +--- +meta_description: Check Figma integration availability in Reflex Build and learn how to use design references and the existing design-system workflow today. +--- + # Figma Integration -Integration Coming Soon! +A direct Figma integration is coming soon. In the meantime, use the design-system workflow below to establish your app's visual direction. + +## Work with design references today + +Use [design systems](/docs/ai/features/design-systems/) to guide the visual style of your Reflex Build app. Describe the layout, spacing, typography, and interactions you want, then review the generated result in the app preview. + +For a consistent result, identify the page you are building, describe its main sections, and make focused changes one at a time. The [best practices guide](/docs/ai/overview/best-practices/) explains how to plan work and iterate with the agent. + +## Choose your next step + +- [Reflex Build overview](/docs/ai/overview/what-is-reflex-build/): understand the integrated app-building workflow. +- [Design systems](/docs/ai/features/design-systems/): configure the visual direction of an app. +- [Build with AI](/docs/ai/): compare Reflex Build with using your own coding agent. + +This page will document setup and supported import behavior when the integration is available. diff --git a/docs/ai_builder/integrations/agent_toolkit.md b/docs/ai_builder/integrations/agent_toolkit.md index e6268b4984e..632818e8c7a 100644 --- a/docs/ai_builder/integrations/agent_toolkit.md +++ b/docs/ai_builder/integrations/agent_toolkit.md @@ -9,14 +9,16 @@ def _resource_card( ) -> rx.Component: return rx.el.a( rx.el.div( - rx.el.h3(title, class_name="text-base font-semibold text-secondary-12"), - rx.el.p(body, class_name="text-sm leading-6 text-secondary-11"), - rx.el.div(action, class_name="text-sm font-semibold text-primary-10"), - class_name="flex h-full flex-col gap-2 rounded-lg border border-secondary-a4 bg-white-1 p-4 transition-colors hover:bg-secondary-2 shadow-xs", + rx.el.h3(title, class_name="text-base font-book text-foreground"), + rx.el.p( + body, class_name="text-sm font-normal leading-6 text-muted-foreground" + ), + rx.el.div(action, class_name="text-sm font-book text-foreground"), + class_name="docs-catalog-card flex h-full flex-col gap-2 rounded-card border border-border-subtle bg-background p-5 transition-colors hover:bg-muted", ), href=href, target=target, - class_name="no-underline", + class_name="no-underline rounded-card focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring", ) diff --git a/docs/ai_builder/integrations/agents_md.md b/docs/ai_builder/integrations/agents_md.md index e5c271d775d..47f1cef8d00 100644 --- a/docs/ai_builder/integrations/agents_md.md +++ b/docs/ai_builder/integrations/agents_md.md @@ -93,4 +93,4 @@ cp AGENTS.md /path/to/your/reflex-project/AGENTS.md - [Reflex Agent Skills](/docs/ai/integrations/skills/) provide reusable workflows that the file references by name. - [Reflex MCP](/docs/ai/integrations/mcp-overview/) provides structured documentation lookup at runtime. -- The [llms.txt index](/llms.txt) gives a broad map of the documentation in one file. +- The [llms.txt index](https://reflex.dev/docs/llms.txt) gives a broad map of the documentation in one file. diff --git a/docs/ai_builder/integrations/skills.md b/docs/ai_builder/integrations/skills.md index 8c1e4ab707f..6bf1561cf34 100644 --- a/docs/ai_builder/integrations/skills.md +++ b/docs/ai_builder/integrations/skills.md @@ -6,10 +6,10 @@ import reflex as rx def _summary_card(kicker: str, title: str, body: str) -> rx.Component: return rx.el.div( - rx.el.div(kicker, class_name="text-xs font-semibold uppercase text-primary-10"), - rx.el.h3(title, class_name="text-base font-semibold text-secondary-12"), - rx.el.p(body, class_name="text-sm leading-6 text-secondary-11"), - class_name="flex flex-col gap-2 rounded-lg border border-secondary-a4 bg-white-1 p-4", + rx.el.div(kicker, class_name="text-xs font-book text-muted-foreground"), + rx.el.h3(title, class_name="text-base font-book text-foreground"), + rx.el.p(body, class_name="text-sm font-normal leading-6 text-muted-foreground"), + class_name="flex flex-col gap-2 rounded-card border border-border-subtle bg-background p-5", ) diff --git a/docs/ai_builder/overview/best_practices.md b/docs/ai_builder/overview/best_practices.md index f902d64e7fa..fbf8b1d59f4 100644 --- a/docs/ai_builder/overview/best_practices.md +++ b/docs/ai_builder/overview/best_practices.md @@ -2,6 +2,14 @@ Reliable results come from clear context, focused prompts, and short review cycles. Start with the smallest useful version of your app, then add one workflow at a time. +```python exec +from reflex_docs.components.ai_guide_graphic import ai_guide_graphic +``` + +```python eval +ai_guide_graphic() +``` + ## Plan the First Version Before generating, write down: diff --git a/docs/api-reference/browser_javascript.md b/docs/api-reference/browser_javascript.md index bd719855b60..f3cd32dcc74 100644 --- a/docs/api-reference/browser_javascript.md +++ b/docs/api-reference/browser_javascript.md @@ -23,7 +23,7 @@ Prefer to use the Python API whenever possible and file an issue if you need add There are four ways to execute custom Javascript code into your Reflex app: -- `rx.script` - Injects the script via `next/script` for efficient loading of inline and external Javascript code. Described further in the [component library](/docs/library/other/script). +- `rx.script` - Injects the script via `next/script` for efficient loading of inline and external Javascript code. Described further in the [component library](/docs/library/other/script/). - These components can be directly included in the body of a page, or they may be passed to `rx.App(head_components=[rx.script(...)])` to be included in the `` tag of all pages. @@ -34,7 +34,7 @@ These previous two methods can work in tandem to load external scripts and then call functions defined within them in response to user events. The following two methods are geared towards wrapping components and are -described with examples in the [Wrapping React](/docs/wrapping-react/overview) +described with examples in the [Wrapping React](/docs/wrapping-react/overview/) section. - `_get_hooks` and `_get_custom_code` in an `rx.Component` subclass diff --git a/docs/api-reference/event_triggers.md b/docs/api-reference/event_triggers.md index 4fc0ad9ed78..16320b1be3a 100644 --- a/docs/api-reference/event_triggers.md +++ b/docs/api-reference/event_triggers.md @@ -73,7 +73,7 @@ def protected_page(): return rx.text("Protected content") ``` -For more details on page load events, see the [page load events documentation](/docs/events/page-load-events). +For more details on page load events, see the [page load events documentation](/docs/events/page-load-events/). ## Event Reference diff --git a/docs/app/README.md b/docs/app/README.md index 95f3d5eac42..cb311a98bca 100644 --- a/docs/app/README.md +++ b/docs/app/README.md @@ -55,10 +55,48 @@ If the build uses a custom `REFLEX_WEB_WORKDIR`, pass that environment variable The `reflex-docs` integration CI jobs run the frontend tests after building the production site, using the installed React and bundler dependencies. -Breadcrumbs and canonical URLs use `deploy_url` and `frontend_path` from the app config. Local runs use the framework's localhost default. Deployment jobs must set `REFLEX_DEPLOY_URL` to the origin serving that build (for example, `https://reflex.dev` in production or the staging origin). +Breadcrumbs, canonicals, sharing URLs, and sitemap locations default to `https://reflex.dev/docs/`, including in a local preview. Staging builds may override `REFLEX_DEPLOY_URL` with their public origin. Supply only the origin (for example, `https://staging.example.com`), without `/docs`; `frontend_path` supplies that prefix. Local serving ports remain independent of the public metadata URL. + +After a production build, run `uv run pytest --runxfail tests/test_published_seo.py` to check every sitemap entry against the generated HTML canonical and sharing URLs. Publish the generated sitemap unchanged; do not prepend `/docs` to its absolute URLs. Recheck the deployed sitemap and HTML after deployment, since local checks cannot validate CDN rewriting or stale cached files. The docs app serves permanent HTTP 301 redirects for its legacy URLs when the Reflex backend serves the frontend. In development or when HTML is hosted separately, requests reach the frontend instead: the redirect pages retain client navigation and prerendered HTML includes an immediate refresh, canonical link, noindex directive, and a usable destination link. That fallback navigates readers but returns HTTP 200. For HTTP 301 semantics on a separate frontend/CDN, configure redirects at that host's edge using the `redirects` list in `reflex_docs/reflex_docs.py`; backend middleware alone cannot redirect requests it never receives. Docs pages intentionally omit the marketing site's pixels and session recording scripts. Search, examples, newsletter signup, and status information remain available. The docs config enables `frontend_lazy_bundled_libraries`. Optional libraries registered for dynamic components load on the first dynamic-component evaluation, while React and the shared runtime stay available immediately. This prevents the full Radix namespace from being imported on every page. The framework default remains `False`; custom scripts that read optional libraries from `window.__reflex` directly should retain that default or await `window.__reflex_load()` first. + +## Editorial theme + +The docs app opts into `SharedSiteStylesPlugin(editorial=True)`, ported from +[marketing PR #102](https://github.com/reflex-dev/marketing/pull/102). Its neutral +palette and semantic roles live in `reflex_site_shared/styles/assets/editorial.css`; +its button uses the marketing variant and size contract (36px compact, 44px +standard, 48px large). The default shared theme remains available to other sites. +Dark mode adapts the neutral ramp; Radix example accent colors remain available. +The header, sidebar, breadcrumbs, and heading anchors share `--docs-header-height`, +which follows the rendered announcement so dismissal leaves no empty strip. + +With a production preview running, verify responsive layout and interactions: + +```bash +REFLEX_DOCS_PREVIEW_URL=http://localhost:3035 uv run pytest tests/test_editorial_browser.py +``` + +## Agent-readable exports + +The deployed app is mounted at `/docs/`. Its indexes are `/docs/llms.txt` and +`/docs/llms-full.txt`; the docs home is `/docs/index.md`. Other canonical pages +use the page URL with the trailing slash removed and `.md` appended. Each page +advertises its exact Markdown destination with a `rel="alternate"` link. + +`AgentFilesPlugin` preserves authored guides and generated API source. After the +production frontend is prerendered, it exports missing programmatic pages and +eval-only catalogs from their main HTML content, then rebuilds both indexes from +the canonical sitemap. This runs before compression. Navigation, decorative +artwork, and footer controls are excluded; headings, destinations, examples, +and tables remain readable. The complete exports require a production build. + +`tests/test_published_seo.py` checks every canonical page's Markdown asset and +flags links that would redirect only to add a trailing slash. Component catalogs +share identical inherited HTML props once per page; overrides and specific props +stay beside their component, and the full API remains in Markdown. diff --git a/docs/app/agent_files/_plugin.py b/docs/app/agent_files/_plugin.py index 4a11adb4c72..4f6360f300c 100644 --- a/docs/app/agent_files/_plugin.py +++ b/docs/app/agent_files/_plugin.py @@ -49,8 +49,9 @@ MARKDOWN_DIRECTIVE = ( "> For AI agents: the complete documentation index is at " - "[llms.txt]({llms_txt_url}). Markdown versions are available by appending " - "`.md` or sending `Accept: text/markdown`." + "[llms.txt]({llms_txt_url}). For a Markdown version, remove the trailing slash " + "from the page URL and append `.md`. The docs home is available at " + "[index.md]({docs_home_markdown_url})." ) PUBLIC_LLMS_TXT_URL = "https://reflex.dev/docs/llms.txt" PUBLIC_EVENT_TRIGGERS_URL = "https://reflex.dev/docs/api-reference/event-triggers/" @@ -156,31 +157,11 @@ def _strip_markdown_directive(source: str) -> str: Returns: The markdown content without the generated directive. """ - directive = _markdown_directive() - if source.startswith(directive): - return source.removeprefix(directive).lstrip() + if source.startswith("> For AI agents: the complete documentation index is at "): + return source.partition("\n")[2].lstrip() return source -def _include_index_entry_in_llms_txt(markdown_file: MarkdownIndexEntry) -> bool: - """Return whether an index entry should appear in llms.txt. - - Args: - markdown_file: The markdown index entry. - - Returns: - Whether the entry should be included in llms.txt. - """ - path = markdown_file.url_path.as_posix() - return ( - path in MCP_DOC_PATHS - or path in AGENT_TOOLKIT_DOC_PATHS - or path in SKILLS_DOC_PATHS - or not path.startswith("ai/") - or path.startswith("ai/overview/") - ) - - def _section_for_path(url_path: Path) -> str: """Return the llms.txt section for a generated markdown asset.""" path = url_path.as_posix() @@ -271,7 +252,10 @@ def _markdown_directive() -> str: Returns: The markdown blockquote directive. """ - return MARKDOWN_DIRECTIVE.format(llms_txt_url=PUBLIC_LLMS_TXT_URL).strip() + return MARKDOWN_DIRECTIVE.format( + llms_txt_url=PUBLIC_LLMS_TXT_URL, + docs_home_markdown_url=public_url("/index.md"), + ).strip() def generate_markdown_file_content(entry: MarkdownFileEntry) -> str: @@ -707,6 +691,19 @@ def generate_dynamic_api_reference_files() -> tuple[tuple[Path, str], ...]: return tuple(files) +def generate_cloud_cli_markdown_files() -> tuple[tuple[Path, str], ...]: + """Export the same command Markdown used to render the Cloud CLI pages.""" + from reflex_docs.pages.docs.cloud_cliref import modules + + return tuple( + ( + Path(f"hosting/cli/{name}.md"), + f"{_markdown_directive()}\n\n# {name.title()} ยท Reflex Cloud CLI\n\n{source.strip()}\n", + ) + for name, source in modules.items() + ) + + def dynamic_api_reference_index_entries( files: Sequence[tuple[Path, str]], ) -> tuple[MarkdownIndexEntry, ...]: @@ -722,7 +719,7 @@ def dynamic_api_reference_index_entries( MarkdownIndexEntry( url_path=path, title=_format_title(path.stem), - section="API Reference", + section=_section_for_path(path), ) for path, _content in files ) @@ -734,8 +731,6 @@ def generate_llms_txt( """Generate an llms.txt index grouped by docs section.""" sections: OrderedDict[str, list[MarkdownIndexEntry]] = OrderedDict() for markdown_file in markdown_files: - if not _include_index_entry_in_llms_txt(markdown_file): - continue sections.setdefault(markdown_file.section, []).append(markdown_file) lines = [ @@ -831,7 +826,13 @@ def generate_agent_files() -> tuple[tuple[Path, str | bytes], ...]: ) for entry in markdown_file_entries ) - dynamic_api_reference_files = generate_dynamic_api_reference_files() + from reflex_docs.pages.docs.cloud import CLOUD_OVERVIEW_MARKDOWN + + dynamic_api_reference_files = ( + *generate_dynamic_api_reference_files(), + *generate_cloud_cli_markdown_files(), + (Path("overview.md"), f"{_markdown_directive()}\n\n{CLOUD_OVERVIEW_MARKDOWN}"), + ) dynamic_api_reference_entries = dynamic_api_reference_index_entries( dynamic_api_reference_files ) @@ -878,6 +879,12 @@ def generate_agent_files() -> tuple[tuple[Path, str | bytes], ...]: class AgentFilesPlugin(Plugin): + def post_build(self, **context): + """Complete agent exports from the final canonical page content.""" + from ._rendered import export_rendered_pages + + export_rendered_pages(context["static_dir"], get_config().frontend_path) + def get_static_assets( self, **context: Unpack[CommonContext] ) -> Sequence[tuple[Path, str | bytes]]: diff --git a/docs/app/agent_files/_rendered.py b/docs/app/agent_files/_rendered.py new file mode 100644 index 00000000000..3c9a81e0819 --- /dev/null +++ b/docs/app/agent_files/_rendered.py @@ -0,0 +1,219 @@ +"""Export navigation and generated page content from prerendered HTML.""" + +from __future__ import annotations + +import re +from dataclasses import dataclass, field +from html.parser import HTMLParser +from pathlib import Path +from urllib.parse import urljoin, urlsplit +from xml.etree import ElementTree + +VOID_TAGS = { + "area", + "base", + "br", + "col", + "embed", + "hr", + "img", + "input", + "link", + "meta", + "param", + "source", + "track", + "wbr", +} +SKIP_TAGS = {"script", "style", "nav", "footer", "button", "svg", "form"} + + +@dataclass +class Element: + """A small HTML tree retaining semantic elements and text.""" + + tag: str + attrs: dict[str, str | None] = field(default_factory=dict) + children: list[Element | str] = field(default_factory=list) + + def find_all(self, tag: str) -> list[Element]: + """Return descendants matching a tag in document order.""" + return ([self] if self.tag == tag else []) + [ + node + for child in self.children + if isinstance(child, Element) + for node in child.find_all(tag) + ] + + def text(self) -> str: + """Return plain text without losing whitespace inside code.""" + return "".join( + child if isinstance(child, str) else child.text() for child in self.children + ) + + +class Document(HTMLParser): + """Parse prerendered HTML without executing scripts or loading resources.""" + + def __init__(self, html: str): + super().__init__(convert_charrefs=True) + self.root = Element("document") + self.stack = [self.root] + self.feed(html) + + def handle_starttag(self, tag, attrs): + """Append an element and track non-void parents.""" + element = Element(tag, dict(attrs)) + self.stack[-1].children.append(element) + if tag not in VOID_TAGS: + self.stack.append(element) + + def handle_endtag(self, tag): + """Close the nearest matching parent.""" + for index in range(len(self.stack) - 1, 0, -1): + if self.stack[index].tag == tag: + del self.stack[index:] + break + + def handle_data(self, data): + """Preserve text for conversion.""" + self.stack[-1].children.append(data) + + +def to_markdown(node: Element | str, page_url: str) -> str: + """Convert semantic HTML to Markdown, retaining code, tables and destinations.""" + if isinstance(node, str): + return re.sub(r"\s+", " ", node) + if ( + node.tag in SKIP_TAGS + or node.attrs.get("aria-hidden") == "true" + or "hidden" in node.attrs + or node.attrs.get("data-slot") == "avatar" + ): + return "" + if "sr-only" in (node.attrs.get("class") or "").split(): + return "" + if node.tag == "pre": + code = node.text().strip("\n") + fence = "`" * max( + 3, max((len(run) + 1 for run in re.findall(r"`+", code)), default=0) + ) + return f"\n\n{fence}\n{code}\n{fence}\n\n" + content = "".join(to_markdown(child, page_url) for child in node.children) + if node.tag in {"h1", "h2", "h3", "h4", "h5", "h6"}: + return f"\n\n{'#' * int(node.tag[1])} {content.strip()}\n\n" + if node.tag == "a" and (href := node.attrs.get("href")): + destination = urljoin(page_url, href) + headings = [ + heading for level in range(1, 7) for heading in node.find_all(f"h{level}") + ] + if headings: + # Keep heading permalink wrappers and linked workflow cards readable. + if destination.split("#")[0] == page_url and "#" in destination: + return content + label = node.attrs.get("aria-label") or headings[0].text().strip() + return f"{content}\n\n[{label}]({destination})\n\n" + label = " ".join(content.split()) or node.attrs.get("aria-label") + link = f"[{label}]({destination})" if label else "" + return f"\n\n{link}\n\n" if "\n" in content else link + " " + if node.tag == "img" and (src := node.attrs.get("src")): + alt = node.attrs.get("alt") or "" + return ( + f"![{alt}]({urljoin(page_url, src)})" + if alt and not alt.startswith("Image preview of") + else "" + ) + if node.tag == "code": + delimiter = "`" * max( + 1, max((len(run) + 1 for run in re.findall(r"`+", content)), default=0) + ) + return f"{delimiter}{content}{delimiter}" + if node.tag == "li": + return f"\n- {content.strip()}\n" + if node.tag == "br": + return "\n" + if node.tag == "table": + rows = [ + [ + " ".join(to_markdown(cell, page_url).split()).replace("|", r"\|") + for cell in row.children + if isinstance(cell, Element) and cell.tag in {"th", "td"} + ] + for row in node.find_all("tr") + ] + rows = [row for row in rows if row] + if not rows: + return "" + width = max(map(len, rows)) + rows = [row + [""] * (width - len(row)) for row in rows] + lines = ["| " + " | ".join(row) + " |" for row in rows] + lines.insert(1, "| " + " | ".join(["---"] * width) + " |") + return "\n\n" + "\n".join(lines) + "\n\n" + if node.tag in {"p", "div", "section", "article", "main", "ul", "ol", "blockquote"}: + return f"\n\n{content.strip()}\n\n" if content.strip() else "" + return content + + +def rendered_content(html: str, page_url: str) -> tuple[str, str]: + """Extract the page answer while excluding shared navigation and controls.""" + document = Document(html).root + roots = document.find_all("article") or document.find_all("main") + if not roots or not (headings := roots[0].find_all("h1")): + raise ValueError(f"Missing main content or heading: {page_url}") + content = to_markdown(roots[0], page_url) + return headings[0].text().strip(), re.sub(r"\n{3,}", "\n\n", content).strip() + "\n" + + +def export_rendered_pages(static_dir: Path, frontend_path: str) -> None: + """Complete canonical-page exports and discovery files after prerendering.""" + from ._plugin import ( + MarkdownIndexEntry, + _extract_markdown_title, + _markdown_directive, + _section_for_path, + generate_llms_full_txt, + generate_llms_txt, + markdown_path_for_trailing_slash_url, + ) + + base = static_dir / frontend_path.strip("/") + sitemap = static_dir / "sitemap.xml" + if not sitemap.exists(): + sitemap = base / "sitemap.xml" + entries = [] + for loc in ElementTree.parse(sitemap).iter(): + if not loc.tag.endswith("loc") or not loc.text: + continue + url = loc.text + relative = urlsplit(url).path.removeprefix(frontend_path).strip("/") + page = base / relative / "index.html" + html = page.read_text() + path = Path(relative + ".md") if relative else Path("index.md") + target = base / path + existing = target.read_text() if target.exists() else "" + # Eval-only source files expose a rendering function, not their answer. + prose = re.sub( + r"^```python (?:exec|eval)[^\n]*\n.*?^```\s*$", + "", + existing, + flags=re.MULTILINE | re.DOTALL, + ) + prose = re.sub(r"^> For AI agents:.*$", "", prose, flags=re.MULTILINE).strip() + title = None + if not prose: + title, content = rendered_content(html, url) + existing = f"{_markdown_directive()}\n\n{content}" + target.parent.mkdir(parents=True, exist_ok=True) + target.write_text(existing) + if title is None: + title = _extract_markdown_title(existing) or relative.rsplit("/", 1)[-1] + twin = base / markdown_path_for_trailing_slash_url(path) + twin.parent.mkdir(parents=True, exist_ok=True) + twin.write_text(existing) + entry = MarkdownIndexEntry(path, title, _section_for_path(path)) + entries.append((entry, existing)) + for name, content in ( + generate_llms_txt([entry for entry, _ in entries]), + generate_llms_full_txt((), entries), + ): + (base / name).write_text(content) diff --git a/docs/app/assets/agent-logos/LICENSE.txt b/docs/app/assets/agent-logos/LICENSE.txt new file mode 100644 index 00000000000..6368063c787 --- /dev/null +++ b/docs/app/assets/agent-logos/LICENSE.txt @@ -0,0 +1,24 @@ +Icons from https://github.com/lobehub/lobe-icons (claudecode-color, codex, cursor). +Brand names and logos belong to their respective owners. + +MIT License + +Copyright (c) 2023 LobeHub + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/docs/app/assets/agent-logos/claude-code.svg b/docs/app/assets/agent-logos/claude-code.svg new file mode 100644 index 00000000000..ed1293c473d --- /dev/null +++ b/docs/app/assets/agent-logos/claude-code.svg @@ -0,0 +1 @@ +Claude Code \ No newline at end of file diff --git a/docs/app/assets/agent-logos/codex.svg b/docs/app/assets/agent-logos/codex.svg new file mode 100644 index 00000000000..4c648aff4a9 --- /dev/null +++ b/docs/app/assets/agent-logos/codex.svg @@ -0,0 +1 @@ +Codex \ No newline at end of file diff --git a/docs/app/assets/agent-logos/cursor.svg b/docs/app/assets/agent-logos/cursor.svg new file mode 100644 index 00000000000..a5b2ee3b1ed --- /dev/null +++ b/docs/app/assets/agent-logos/cursor.svg @@ -0,0 +1 @@ +Cursor \ No newline at end of file diff --git a/docs/app/assets/components_previews/charts/dark/treemap.svg b/docs/app/assets/components_previews/charts/dark/treemap.svg new file mode 100644 index 00000000000..5e9abb9e0cd --- /dev/null +++ b/docs/app/assets/components_previews/charts/dark/treemap.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/docs/app/assets/components_previews/charts/light/treemap.svg b/docs/app/assets/components_previews/charts/light/treemap.svg new file mode 100644 index 00000000000..118cfc3f61a --- /dev/null +++ b/docs/app/assets/components_previews/charts/light/treemap.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/docs/app/assets/favicon.ico b/docs/app/assets/favicon.ico index cf95e07d81b..2545bd24ff0 100644 Binary files a/docs/app/assets/favicon.ico and b/docs/app/assets/favicon.ico differ diff --git a/docs/app/assets/favicon.svg b/docs/app/assets/favicon.svg new file mode 100644 index 00000000000..35f49a90141 --- /dev/null +++ b/docs/app/assets/favicon.svg @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/app/assets/framework-diagrams/blue.avif b/docs/app/assets/framework-diagrams/blue.avif new file mode 100644 index 00000000000..3ad6f1114dd Binary files /dev/null and b/docs/app/assets/framework-diagrams/blue.avif differ diff --git a/docs/app/assets/framework-diagrams/lavender.avif b/docs/app/assets/framework-diagrams/lavender.avif new file mode 100644 index 00000000000..061f99ab18d Binary files /dev/null and b/docs/app/assets/framework-diagrams/lavender.avif differ diff --git a/docs/app/assets/framework-diagrams/mint.avif b/docs/app/assets/framework-diagrams/mint.avif new file mode 100644 index 00000000000..087200e20b4 Binary files /dev/null and b/docs/app/assets/framework-diagrams/mint.avif differ diff --git a/docs/app/assets/framework-diagrams/peach.avif b/docs/app/assets/framework-diagrams/peach.avif new file mode 100644 index 00000000000..b59ee787826 Binary files /dev/null and b/docs/app/assets/framework-diagrams/peach.avif differ diff --git a/docs/app/assets/meta/android-chrome-192x192.png b/docs/app/assets/meta/android-chrome-192x192.png index 6dd5041bb20..e6d3c8cf3e8 100644 Binary files a/docs/app/assets/meta/android-chrome-192x192.png and b/docs/app/assets/meta/android-chrome-192x192.png differ diff --git a/docs/app/assets/meta/android-chrome-512x512.png b/docs/app/assets/meta/android-chrome-512x512.png index 235224e04f7..81b98da53f2 100644 Binary files a/docs/app/assets/meta/android-chrome-512x512.png and b/docs/app/assets/meta/android-chrome-512x512.png differ diff --git a/docs/app/assets/meta/apple-touch-icon.png b/docs/app/assets/meta/apple-touch-icon.png index ab32c4f0926..2db18bd8fc4 100644 Binary files a/docs/app/assets/meta/apple-touch-icon.png and b/docs/app/assets/meta/apple-touch-icon.png differ diff --git a/docs/app/assets/meta/favicon-16x16.png b/docs/app/assets/meta/favicon-16x16.png index 62f8a9eddf7..078d6065879 100644 Binary files a/docs/app/assets/meta/favicon-16x16.png and b/docs/app/assets/meta/favicon-16x16.png differ diff --git a/docs/app/assets/meta/favicon-32x32.png b/docs/app/assets/meta/favicon-32x32.png index 40759914a1b..9ae2cb0fcb5 100644 Binary files a/docs/app/assets/meta/favicon-32x32.png and b/docs/app/assets/meta/favicon-32x32.png differ diff --git a/docs/app/assets/meta/site.webmanifest b/docs/app/assets/meta/site.webmanifest index 464adc4c06c..771e4451309 100644 --- a/docs/app/assets/meta/site.webmanifest +++ b/docs/app/assets/meta/site.webmanifest @@ -1,6 +1,6 @@ { - "name": "", - "short_name": "", + "name": "Reflex", + "short_name": "Reflex", "icons": [ { "src": "/meta/android-chrome-192x192.png", diff --git a/docs/app/news/+docs-discovery.bugfix.md b/docs/app/news/+docs-discovery.bugfix.md new file mode 100644 index 00000000000..efc21bb5661 --- /dev/null +++ b/docs/app/news/+docs-discovery.bugfix.md @@ -0,0 +1 @@ +Fix the agent-guide index link, export Cloud CLI command references as Markdown, replace the Cloud overview placeholder, and reserve preview-image space to prevent catalog layout shifts. Component links now use their canonical trailing-slash paths. diff --git a/docs/app/news/+editorial-docs.docs.md b/docs/app/news/+editorial-docs.docs.md new file mode 100644 index 00000000000..2f919b260d3 --- /dev/null +++ b/docs/app/news/+editorial-docs.docs.md @@ -0,0 +1,37 @@ +Refresh the documentation with Reflex's neutral editorial design across navigation, articles, catalogs, API tables, illustrations, dialogs, footers, and missing pages in light and dark modes. Mobile navigation now matches the desktop docs destinations, while live examples retain their demonstrated styling. + +Fix browser hydration errors in integration cards and the low-level form example. + +Correct the header selection on the docs landing page so Overview is highlighted instead of Framework. + +Restyle the AI Builder cards with matching tinted illustrations, integration logo tiles, and consistent typography in light and dark modes. + +Make the component library easier to scan with baseline-aligned, text-only category headings on a uniform background, fine dividers, and component links arranged in columns with monochrome underlined hover states. + +Match both docs footer social menus to the main site's compact icon styling and remove the forum icon. + +Align documentation fields, navigation, content surfaces, and icon controls with the shared editorial shape system. + +Use a muted gray surface for the Custom Components sidebar card in light and dark modes. + +Remove the empty space beneath the MCP illustration on the docs landing page. + +Refine the docs navbar with understated text navigation, compact search, and consistent editorial controls. + +Explore framework capabilities through accessible tabs and theme-aware product diagrams, with the section heading directly above the tab list. + +Use fine solid connectors with rounded corners and clean junctions in framework diagrams. + +Explain component composition with a Python example mapped to its rendered interface. + +Make the How It Works diagram readable without overlapping windows and let visitors try its Python-backed counter. + +Explore Cloud hosting through a connected diagram centered on one unified hosted application with direct links to deployment, secrets, observability, and configuration guides. + +Integrate the footer newsletter signup with aligned columns, clearer copy, and a quieter inline email form. + +Choose between AI Builder and Python framework guides directly from the documentation introduction. + +Illustrate the AI Builder best practices with a focused prompt, an app preview, and a follow-up refinement. + +Choose Reflex Build or Agent Toolkit from the new Build with AI overview, within the shared documentation sidebar and article layout. diff --git a/docs/app/news/+public-docs-urls.bugfix.md b/docs/app/news/+public-docs-urls.bugfix.md new file mode 100644 index 00000000000..6a65d27a19a --- /dev/null +++ b/docs/app/news/+public-docs-urls.bugfix.md @@ -0,0 +1 @@ +Default documentation canonicals, sharing metadata, and sitemap URLs to the public Reflex domain instead of localhost. Validate published URLs against the generated pages to prevent duplicate docs prefixes and mismatched canonicals. diff --git a/docs/app/news/+specific-page-metadata.bugfix.md b/docs/app/news/+specific-page-metadata.bugfix.md new file mode 100644 index 00000000000..8c4d01632e3 --- /dev/null +++ b/docs/app/news/+specific-page-metadata.bugfix.md @@ -0,0 +1 @@ +Replace generic docs search descriptions with page-specific summaries and bundle the missing light and dark Treemap preview images. diff --git a/docs/app/news/7137.docs.md b/docs/app/news/7137.docs.md new file mode 100644 index 00000000000..683622b35d5 --- /dev/null +++ b/docs/app/news/7137.docs.md @@ -0,0 +1 @@ +Complete Markdown exports and agent indexes for all canonical docs pages, add the React wrapping walkthrough and state guides, and clarify Figma integration availability. Reduce repeated HTML reference content and avoid redirects in documentation links. diff --git a/docs/app/reflex_docs/components/ai_guide_graphic.py b/docs/app/reflex_docs/components/ai_guide_graphic.py new file mode 100644 index 00000000000..aeba1a39d78 --- /dev/null +++ b/docs/app/reflex_docs/components/ai_guide_graphic.py @@ -0,0 +1,116 @@ +"""An illustrated prompt and review cycle for the AI Builder guide.""" + +import reflex as rx + + +def ai_guide_graphic() -> rx.Component: + """Show a focused prompt, its first result, and a specific follow-up.""" + return rx.el.figure( + rx.el.div( + rx.el.div( + rx.el.div( + rx.el.div( + rx.icon("sparkles", size=16), + rx.el.span("Your prompt"), + class_name="flex items-center gap-2 text-xs text-muted-foreground", + ), + rx.el.div( + "Create an employee directory with name, team, and status. Start with sample data.", + class_name="mt-5 text-base font-book leading-7 tracking-tight text-foreground", + ), + class_name="min-w-0 self-center rounded-xl border border-border bg-background p-5 sm:p-6", + ), + rx.el.div( + rx.el.div( + rx.el.div( + *[ + rx.el.span( + class_name="size-1 rounded-full bg-subtle-foreground" + ) + for _ in range(3) + ], + class_name="flex gap-1", + ), + rx.el.span( + "Preview", class_name="text-xs text-muted-foreground" + ), + rx.icon( + "panel-top", size=13, class_name="text-subtle-foreground" + ), + class_name="flex items-center justify-between border-b border-border px-4 py-3", + ), + rx.el.div( + rx.el.div( + "Our team", + class_name="text-lg font-book tracking-tight text-foreground", + ), + rx.el.div( + "People across your organization", + class_name="mt-1 text-xs leading-5 text-muted-foreground", + ), + rx.el.div( + *[ + rx.el.div( + rx.el.span( + initials, + class_name="flex size-8 shrink-0 items-center justify-center rounded-full bg-[var(--fw-tint)] text-[10px] font-book text-[var(--fw-accent)]", + ), + rx.el.div( + rx.el.div( + name, + class_name="text-xs font-book text-foreground", + ), + rx.el.div( + team, + class_name="mt-1 text-[11px] text-muted-foreground", + ), + class_name="min-w-0 flex-1", + ), + rx.el.span( + "Active", + class_name="text-[10px] text-[var(--fw-accent)]", + ), + class_name="flex items-center gap-3 border-t border-border py-3", + ) + for initials, name, team in ( + ("AM", "Alex Morgan", "Engineering"), + ("JC", "Jamie Chen", "Design"), + ("SR", "Sam Rivera", "Operations"), + ) + ], + class_name="mt-5", + ), + class_name="p-4 sm:p-5", + ), + class_name="min-w-0 overflow-hidden rounded-xl border border-border bg-background", + ), + style={ + "grid_template_columns": "repeat(auto-fit, minmax(min(100%, 220px), 1fr))" + }, + class_name="grid w-full items-center gap-5 sm:gap-6", + ), + rx.el.div( + rx.icon( + "corner-up-right", + size=16, + class_name="shrink-0 text-[var(--fw-accent)]", + ), + rx.el.div( + rx.el.span( + "Next prompt", class_name="block text-xs text-muted-foreground" + ), + rx.el.span( + "Keep the layout. Add a filter for each team.", + class_name="mt-1 block text-sm leading-6 text-foreground", + ), + ), + class_name="mt-5 flex w-full items-center gap-3 rounded-xl border border-border bg-background px-5 py-4 sm:mt-6", + ), + aria_hidden=True, + class_name="docs-framework-stage flex-col !p-4 sm:!p-7", + ), + role="img", + aria_label="Build in focused steps: ask for an employee directory with sample data, review the first version, then ask to add a team filter while keeping the layout.", + custom_attrs={"data-tone": "lavender"}, + class_name="docs-ai-guide-graphic docs-framework-panel my-8 min-w-0 overflow-hidden rounded-panel border border-border", + ) diff --git a/docs/app/reflex_docs/components/component_catalog.py b/docs/app/reflex_docs/components/component_catalog.py new file mode 100644 index 00000000000..f6f9768f307 --- /dev/null +++ b/docs/app/reflex_docs/components/component_catalog.py @@ -0,0 +1,54 @@ +"""Shared category rows for the component catalogs.""" + +import reflex as rx + + +def component_category( + title: str, + href: str, + description: str, + links: list[tuple[str, str]], +) -> rx.Component: + """Render an aligned category heading and a responsive list of guide links. + + Args: + title: Category name. + href: Category overview route. + description: Component count or short category description. + links: Display names and documentation routes. + + Returns: + A component catalog section. + """ + return rx.el.section( + rx.link( + rx.box( + rx.el.h2( + title, + class_name="text-lg font-book leading-6 tracking-tight text-foreground transition-colors group-hover:text-muted-foreground", + ), + rx.text( + description, + class_name="text-xs font-normal leading-5 text-muted-foreground", + ), + class_name="flex min-w-0 flex-col gap-2", + ), + href=href, + underline="none", + class_name="group block rounded-compact text-foreground focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-ring", + ), + rx.el.ul( + *[ + rx.el.li( + rx.el.a( + label, + href=link, + class_name="block rounded-compact px-3 py-2 text-sm font-normal leading-6 text-muted-foreground decoration-border-strong underline-offset-4 hover:text-foreground hover:underline transition-colors focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-ring", + ), + ) + for label, link in links + ], + class_name="grid min-w-0 grid-cols-2 gap-x-2 gap-y-1 xl:grid-cols-3 list-none m-0 p-0", + ), + class_name="docs-library-category grid grid-cols-1 items-start gap-5 border-t border-border-subtle -mx-4 px-4 py-7 md:items-baseline md:grid-cols-[minmax(0,1fr)_minmax(0,3fr)] md:gap-8", + ) diff --git a/docs/app/reflex_docs/pages/__init__.py b/docs/app/reflex_docs/pages/__init__.py index 4f3ffac2dae..4418101e605 100644 --- a/docs/app/reflex_docs/pages/__init__.py +++ b/docs/app/reflex_docs/pages/__init__.py @@ -1,8 +1,9 @@ -from reflex_site_shared.pages.page404 import page404 # noqa: F401 from reflex_site_shared.route import Route +from reflex_docs.pages.ai_landing import ai_landing # noqa: F401 from reflex_docs.pages.docs import doc_routes from reflex_docs.pages.docs_landing import docs_landing # noqa: F401 +from reflex_docs.pages.page404 import page404 # noqa: F401 routes = [ *[r for r in locals().values() if isinstance(r, Route) and r.add_as_page], diff --git a/docs/app/reflex_docs/pages/ai_landing.py b/docs/app/reflex_docs/pages/ai_landing.py new file mode 100644 index 00000000000..5dd07734b81 --- /dev/null +++ b/docs/app/reflex_docs/pages/ai_landing.py @@ -0,0 +1,161 @@ +"""Choose a workflow for building Reflex apps with AI.""" + +import reflex as rx + +from reflex_docs.pages.docs import ai_builder +from reflex_docs.pages.docs_landing.views.artwork import artwork +from reflex_docs.templates.docpage import docpage, h1_comp, text_comp_2 + + +def _agent_logos() -> rx.Component: + """Group the coding-agent marks at a consistent size.""" + return rx.el.div( + *[ + rx.image( + src=rx.asset(f"agent-logos/{filename}.svg"), + alt=label, + title=label, + class_name="size-5 shrink-0 object-contain" + + (" dark:invert" if filename != "claude-code" else ""), + ) + for filename, label in ( + ("claude-code", "Claude Code"), + ("codex", "Codex"), + ("cursor", "Cursor"), + ) + ], + class_name="docs-agent-logos flex items-center gap-4", + ) + + +def _workflow_preview(icon: rx.Component, labels: tuple[str, ...]) -> rx.Component: + """Illustrate the tools available in each workflow.""" + return rx.el.div( + rx.el.div( + rx.el.div( + icon, + rx.el.div( + *[ + rx.el.span( + class_name="size-1 rounded-full bg-subtle-foreground" + ) + for _ in range(3) + ], + class_name="flex gap-1", + ), + class_name="flex items-center justify-between border-b border-border px-5 py-4", + ), + rx.el.div( + *[ + rx.el.div( + rx.icon(symbol, size=16, class_name="text-[var(--fw-accent)]"), + rx.el.span(label, class_name="text-sm text-foreground"), + class_name="flex items-center gap-3 rounded-sm bg-[var(--fw-tint)] px-4 py-3", + ) + for symbol, label in zip( + ("message-square", "panels-top-left", "layers"), + labels, + strict=True, + ) + ], + class_name="flex flex-col gap-2 p-4", + ), + class_name="w-full max-w-xs overflow-hidden rounded-xl border border-border bg-background", + ), + aria_hidden=True, + class_name="docs-framework-stage !p-7 sm:!p-9", + ) + + +def _workflow_card( + title: str, + description: str, + action: str, + href: str, + tone: str, + preview: rx.Component, + recommended: bool = False, +) -> rx.Component: + """Present one native link with a visual summary and clear destination.""" + return rx.el.a( + preview, + rx.el.div( + rx.el.div( + rx.el.h2( + title, + class_name="text-2xl font-book tracking-tight text-foreground", + ), + rx.el.span( + "Recommended", + class_name="rounded-sm bg-foreground px-2.5 py-1 text-xs font-book leading-4 text-background", + ) + if recommended + else rx.fragment(), + class_name="flex flex-wrap items-center gap-x-3 gap-y-2", + ), + rx.el.p( + description, class_name="mt-3 text-base leading-7 text-muted-foreground" + ), + rx.el.div( + action, + rx.icon("arrow-right", size=16, aria_hidden=True), + class_name="mt-auto flex items-center gap-2 pt-7 text-sm font-book text-foreground", + ), + class_name="flex flex-1 flex-col border-t border-border bg-background p-6 sm:p-8", + ), + href=href, + aria_label=title, + aria_description="Recommended way to build Reflex apps" + if recommended + else None, + custom_attrs={"data-tone": tone}, + class_name="docs-ai-workflow-card docs-framework-panel flex min-w-0 flex-col overflow-hidden rounded-panel border border-border transition-colors hover:border-border-strong focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-ring", + ) + + +@docpage( + set_path="/ai/", + t="Build with AI", + right_sidebar=False, + description="Build in your browser with Reflex Build, or use Agent Toolkit with your own coding agent.", +) +def ai_landing() -> rx.Component: + """Offer both AI workflows within the shared documentation layout.""" + return rx.el.section( + h1_comp(text="Build with AI"), + text_comp_2( + text="Use Reflex's integrated builder or work with your own coding agent.", + ), + rx.el.div( + _workflow_card( + "Use Reflex Build", + "The most powerful way to build Reflex apps. Bring AI generation, live previews, testing, integrations, and deployment together in one workspace.", + "Explore Reflex Build", + ai_builder.overview.what_is_reflex_build.path, + "lavender", + _workflow_preview( + artwork("reflex_mark", class_name="w-5"), + ( + "Plan and build with AI", + "Preview and test your app", + "Connect data and deploy", + ), + ), + recommended=True, + ), + _workflow_card( + "Bring your own agent", + "Give your preferred coding agent Reflex documentation, skills, and tools with Agent Toolkit.", + "Explore Agent Toolkit", + ai_builder.integrations.agent_toolkit.path, + "mint", + _workflow_preview( + _agent_logos(), + ("Documentation for agents", "Reflex skills", "MCP tools"), + ), + ), + class_name="mt-10 grid grid-cols-1 gap-6 md:grid-cols-2", + ), + aria_label="Choose your AI workflow", + class_name="docs-ai-overview min-w-0 pb-8", + ) diff --git a/docs/app/reflex_docs/pages/docs/cloud.py b/docs/app/reflex_docs/pages/docs/cloud.py index a3d5db0dbfc..bdc9ba0a37e 100644 --- a/docs/app/reflex_docs/pages/docs/cloud.py +++ b/docs/app/reflex_docs/pages/docs/cloud.py @@ -1,17 +1,51 @@ -import reflex as rx - -from reflex_docs.templates.docpage import docpage, h1_comp - -cloud_overview = docpage("overview/", "Cloud Overview")( - lambda: rx.box( - h1_comp(text="Reflex Cloud Overview"), - rx.text("Cloud overview content from markdown"), - ) -) -# Keep the short sidebar/nav label ("Overview"), but emit a descriptive HTML -# for SEO. -cloud_overview.title = "Overview" -cloud_overview.seo_title = "Reflex Cloud Overview ยท Reflex Docs" +"""Overview of deploying and operating Reflex applications.""" + +from reflex_docs.docgen_pipeline import render_markdown +from reflex_docs.templates.docpage import docpage + +CLOUD_OVERVIEW_MARKDOWN = """# Reflex Cloud Overview + +Reflex Cloud hosts your Reflex application so other people can use it on the web. +Start with an app that runs locally, deploy it to a project, and use the hosting +tools to configure and monitor it. + +## Deploy your first app + +Follow the [deployment quick start](/docs/hosting/deploy-quick-start/) to sign in, +find your project's deploy command, and publish your app. The guide covers the +required Python dependencies and the interactive deployment workflow. + +For command options and automation, use the [deploy command reference](/docs/hosting/cli/deploy/) +and the [GitHub Actions deployment guide](/docs/hosting/deploy-with-github-actions/). +## Configure your deployment +- [Secrets and environment variables](/docs/hosting/secrets-environment-vars/): configure API keys and other values used by your app. +- [Custom domains](/docs/hosting/custom-domains/): connect a domain to your deployment. +- [Regions](/docs/hosting/regions/): choose where to run your application. +- [Compute](/docs/hosting/compute/): choose resources for your workload. +- [Cloud configuration](/docs/hosting/config-file/): manage deployment settings in a configuration file. + +## Monitor and manage your app + +Use [application management](/docs/hosting/app-management/) to manage deployments +and [logs](/docs/hosting/logs/) to investigate application behavior. Run a +[security scan](/docs/hosting/security-scan/) to review your app source for +security and logic issues. + +## Choose where to host + +If you need to run in your own environment, compare +[bring your own cloud](/docs/hosting/bring-your-own-cloud/) with +[self-hosting](/docs/hosting/self-hosting/). These guides explain the available +workflows and the setup each requires. +""" + +cloud_overview = docpage( + "/overview/", + "Cloud Overview", + description="Deploy a Reflex app, configure secrets and domains, monitor logs, and compare Reflex Cloud with bring-your-own-cloud and self-hosting options.", +)(lambda: render_markdown(CLOUD_OVERVIEW_MARKDOWN)) +cloud_overview.title = "Overview" +cloud_overview.seo_title = "Reflex Cloud Overview ยท Reflex Docs" pages = [cloud_overview] diff --git a/docs/app/reflex_docs/pages/docs/component.py b/docs/app/reflex_docs/pages/docs/component.py index 9316f47c354..a6364160e00 100644 --- a/docs/app/reflex_docs/pages/docs/component.py +++ b/docs/app/reflex_docs/pages/docs/component.py @@ -14,6 +14,7 @@ from reflex.components.component import Component from reflex.components.radix.primitives.base import RadixPrimitiveComponent from reflex.components.radix.themes.base import RadixThemesComponent +from reflex_components_core.el.elements.base import BaseHTML from reflex_docgen import ( EventHandlerDocumentation, PropDocumentation, @@ -90,16 +91,16 @@ class PropDocsState(rx.State): _PILL_BTN_CLASS = ( - "inline-flex h-7 cursor-pointer items-center justify-center rounded-md " - "border border-secondary-5 bg-secondary-1 px-2.5 text-sm font-medium text-secondary-11 " - "transition-colors hover:border-secondary-6 hover:bg-secondary-2 hover:text-secondary-12 " - "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-secondary-7" + "inline-flex h-7 cursor-pointer items-center justify-center rounded-full " + "border border-border bg-background px-3 text-sm font-book text-muted-foreground " + "transition-colors hover:border-border-strong hover:bg-muted hover:text-foreground " + "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring" ) _PILL_BTN_ACTIVE_CLASS = ( - "inline-flex h-7 cursor-pointer items-center justify-center rounded-md " - "border border-secondary-8 bg-secondary-3 px-2.5 text-sm font-medium text-secondary-12 " - "shadow-[inset_0_0_0_1px_var(--secondary-6)] transition-colors " - "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-secondary-7" + "inline-flex h-7 cursor-pointer items-center justify-center rounded-full " + "border border-foreground bg-foreground px-3 text-sm font-book text-background " + "transition-colors hover:bg-primary-hover " + "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring" ) _PROPS_TABLE_COMPACT_CELL_CLASS = ( "cell-content max-h-[4.25rem] overflow-hidden " @@ -715,6 +716,7 @@ def _render_dynamic_prop(indent: str, p, var) -> rx.Component: interactive_component = rx.el.div( rx.el.div( comp, + data_docs_example=True, class_name=( "flex flex-col items-center justify-center p-6 flex-1 " "bg-secondary-2 border-b lg:border-b-0 lg:border-r " @@ -726,7 +728,7 @@ def _render_dynamic_prop(indent: str, p, var) -> rx.Component: class_name="flex-1 p-4 bg-secondary-1 min-w-0 overflow-x-auto", ), class_name=( - "flex flex-col lg:flex-row w-full rounded-xl border " + "flex flex-col lg:flex-row w-full rounded-card border " "border-secondary-4 overflow-hidden" ), ) @@ -752,8 +754,8 @@ def _render_dynamic_prop(indent: str, p, var) -> rx.Component: for prop, control in interactive_controls ], class_name=( - "mb-4 w-full min-w-0 overflow-hidden rounded-xl border " - "border-secondary-4 bg-secondary-1 shadow-small" + "mb-4 w-full min-w-0 overflow-hidden rounded-card border " + "border-border-subtle bg-background" ), ) @@ -863,8 +865,27 @@ def generate_valid_children(comp: type[Component]) -> rx.Component: ) +def shared_html_props( + components: list[type[Component]], +) -> tuple[PropDocumentation, ...]: + """Find inherited HTML props shared unchanged by at least two components.""" + if len(components) < 2: + return () + props_by_component = [ + {prop.name: prop for prop in generate_documentation(component).props} + for component in components + ] + return tuple( + prop + for prop in generate_documentation(BaseHTML).props + if sum(props.get(prop.name) == prop for props in props_by_component) >= 2 + ) + + def component_docs( - component_tuple: tuple[type[Component], str], previews: dict[str, str] + component_tuple: tuple[type[Component], str], + previews: dict[str, str], + shared_props: tuple[PropDocumentation, ...] = (), ) -> rx.Component: """Generates documentation for a given component.""" component = component_tuple[0] @@ -879,7 +900,13 @@ def component_docs( component_tuple[1], component_tuple[1] ) - props = generate_props(doc.props, component, previews, comp_display_name) + inherited_props = tuple(prop for prop in doc.props if prop in shared_props) + props = generate_props( + tuple(prop for prop in doc.props if prop not in inherited_props), + component, + previews, + comp_display_name, + ) triggers = generate_event_triggers(doc.event_handlers) children = generate_valid_children(component) @@ -887,6 +914,18 @@ def component_docs( h2_comp(text=comp_display_name), rx.box(render_markdown(doc.description or ""), class_name="pb-2"), props, + rx.el.p( + "Also accepts the ", + rx.el.a( + "shared HTML props", + href="#shared-html-props", + class_name="docs-text-link underline underline-offset-4", + ), + ".", + class_name="mb-4 text-sm text-muted-foreground", + ) + if inherited_props + else rx.fragment(), children, triggers, class_name="pb-8 w-full text-left", @@ -905,20 +944,37 @@ def multi_docs( ll_component_list: list | None = None, source: str | None = None, ): + shared = shared_html_props([item[0] for item in component_list[1:]]) components = [ - component_docs(component_tuple, previews) + component_docs(component_tuple, previews, shared) for component_tuple in component_list[1:] ] ll_actual_path = actual_path.replace(".md", "-ll.md") ll_doc_exists = os.path.exists(ll_actual_path) ll_list = ll_component_list if ll_component_list is not None else component_list + ll_shared = shared_html_props([item[0] for item in ll_list[1:]]) ll_components = [ - component_docs(component_tuple, previews) for component_tuple in ll_list[1:] + component_docs(component_tuple, previews, ll_shared) + for component_tuple in ll_list[1:] ] - active_class_name = "font-small bg-secondary-2 p-2 text-secondary-11 rounded-xl shadow-large w-28 cursor-default border border-secondary-4 text-center" + def shared_reference(props): + """Render the common reference once, retaining it in prerendered HTML.""" + if not props: + return rx.fragment() + return rx.box( + h2_comp(text="Shared HTML props"), + rx.el.p( + "Components linked to this section accept these HTML props. " + "Component-specific additions and overrides are listed below.", + class_name="mb-4 text-sm leading-6 text-muted-foreground", + ), + generate_props(props, BaseHTML, {}), + ) + + active_class_name = "text-sm font-book bg-foreground px-4 py-2 text-background rounded-full w-28 cursor-default text-center" - non_active_class_name = "font-small w-28 transition-color hover:text-secondary-12 text-secondary-11 p-2 text-center" + non_active_class_name = "text-sm font-book w-28 rounded-full transition-colors hover:bg-muted hover:text-foreground text-muted-foreground px-4 py-2 text-center" def links(current_page, ll_doc_exists, path): path = str(path).rstrip("/") @@ -935,10 +991,10 @@ def links(current_page, ll_doc_exists, path): rx.box( rx.text("Low Level"), class_name=non_active_class_name ), - href=path + "/low", + href=path + "/low/", underline="none", ), - class_name="bg-secondary-3 rounded-[1.125rem] p-2 gap-2 flex items-center justify-center", + class_name="docs-api-level-switch bg-background border border-border rounded-full p-1 gap-1 flex items-center justify-center [&_a]:rounded-full [&_a]:focus-visible:outline-2 [&_a]:focus-visible:outline-ring", ), class_name="flex mb-2", ) @@ -950,15 +1006,15 @@ def links(current_page, ll_doc_exists, path): rx.box( rx.text("High Level"), class_name=non_active_class_name ), - href=path, + href=path + "/", underline="none", ), rx.link( rx.box(rx.text("Low Level"), class_name=active_class_name), - href=path + "/low", + href=path + "/low/", underline="none", ), - class_name="bg-secondary-3 rounded-[1.125rem] p-2 gap-2 flex items-center justify-center", + class_name="docs-api-level-switch bg-background border border-border rounded-full p-1 gap-1 flex items-center justify-center [&_a]:rounded-full [&_a]:focus-visible:outline-2 [&_a]:focus-visible:outline-ring", ), class_name="flex mb-2", ) @@ -982,11 +1038,14 @@ def out(): # Append API Reference headings for the component list if components: toc.append((1, "API Reference")) + if shared: + toc.append((2, "Shared HTML props")) for component_tuple in component_list[1:]: toc.append((2, component_tuple[1])) api_ref_section = ( [ h2_comp(text="API Reference"), + shared_reference(shared), rx.box(*components, class_name="flex flex-col"), ] if components @@ -1014,7 +1073,7 @@ def out(): ) @docpage( - set_path=path + "low", + set_path=path.rstrip("/") + "/low/", t=title + " (Low Level)", description=ll_description, image=image, @@ -1026,11 +1085,14 @@ def ll(): doc_content = Path(ll_actual_path).read_text(encoding="utf-8") if ll_components: toc.append((1, "API Reference")) + if ll_shared: + toc.append((2, "Shared HTML props")) for component_tuple in ll_list[1:]: toc.append((2, component_tuple[1])) api_ref_section = ( [ h2_comp(text="API Reference"), + shared_reference(ll_shared), rx.box(*ll_components, class_name="flex flex-col"), ] if ll_components diff --git a/docs/app/reflex_docs/pages/docs/custom_components.py b/docs/app/reflex_docs/pages/docs/custom_components.py index 4e4ea054682..244f5397431 100644 --- a/docs/app/reflex_docs/pages/docs/custom_components.py +++ b/docs/app/reflex_docs/pages/docs/custom_components.py @@ -4,9 +4,7 @@ import httpx import reflex as rx from reflex_site_shared.components.icons import get_icon -from reflex_site_shared.styles.colors import c_color -from reflex_site_shared.styles.fonts import base -from reflex_site_shared.styles.shadows import shadows +from reflex_site_shared.components.marketing_button import button from reflex_docs.templates.docpage import docpage, h1_comp, text_comp_2 @@ -123,152 +121,48 @@ def set_selected_filter(self, filter_text: str): self.paginate() # Update paginated data -def filter_item( - icon: str, text: str, border: bool = False, on_click=None -) -> rx.Component: - is_selected = CustomComponentGalleryState.selected_filter == text - return rx.box( - get_icon(icon, class_name="py-[2px]", opacity=rx.cond(is_selected, 0.64, 1)), - rx.text(text, opacity=rx.cond(is_selected, 0.64, 1), class_name="font-small"), - rx.spacer(), - rx.cond( - is_selected, - rx.box( - class_name="size-2 justify-end bg-violet-9 rounded-full", - ), - ), - class_name="flex flex-row gap-[14px] items-center justify-start w-full cursor-pointer hover:bg-secondary-3 transition-bg text-nowrap overflow-hidden p-[8px_14px]", - border_top="1px solid var(--secondary-4)" if border else "none", - border_bottom="1px solid var(--secondary-4)" if border else "none", - on_click=on_click, - ) - - -chips_box_style = { - "width": ["100%", "100%", "auto"], - "box-sizing": "border-box", - "display": "flex", - "flex-direction": "row", - "align_items": "center", - "padding": "6px 12px", - "cursor": "pointer", - "box-shadow": shadows["large"], - "border-radius": "1000px", - "transition": "background 0.075s ease-out, color 0.075s ease-out, border 0.075s ease-out", -} - -# Sorting -sorting_box_style = { - "gap": "12px", - "outline": "none", - "_focus": { - "outline": "none", - }, - **chips_box_style, -} - -menu_item_style = { - "box-sizing": "border-box", - "width": "191px", - "height": "auto", - "overflow": "hidden", - "padding": "0px", - "cursor": "default", - "background_color": "var(--secondary-2)", - "border": "1px solid var(--secondary-4)", - "box-shadow": "0px 2px 4px rgba(0, 0, 0, 0.05)", - "border-radius": "12px", - "color": "var(--secondary-9)", - **base, -} - - -def sorting_filters() -> rx.Component: - return rx.vstack( - filter_item( - "history", - "Recent", - on_click=lambda: CustomComponentGalleryState.set_selected_filter("Recent"), - ), - filter_item( - "arrow_down_big", - "Downloads", - border=True, - on_click=lambda: CustomComponentGalleryState.set_selected_filter( - "Downloads" - ), - ), - gap="0px", - width="100%", - ) - - def sorting_filters_dropdown_menu() -> rx.Component: + """Render the gallery sort menu using the shared neutral controls. + + Returns: + Keyboard-accessible sort menu. + """ condition = CustomComponentGalleryState.selected_filter != "" - conditional_style = { - "background": rx.cond( - condition, - c_color("violet", 9), - c_color("slate", 1), - ), - "color": rx.cond( - condition, - "white", - c_color("slate", 9), - ), - "border": rx.cond( - condition, - f"1px solid {c_color('violet', 9)}", - f"1px solid {c_color('slate', 5)}", - ), - "&[data-state='open']": { - "background": rx.cond( - condition, - c_color("violet", 9), - c_color("slate", 3), - ), - }, - "_hover": { - "background": rx.cond( - condition, - c_color("violet", 9), - c_color("slate", 3), - ), - }, - } return rx.menu.root( rx.menu.trigger( - rx.el.button( - rx.text( + button( + rx.cond( + condition, + "Sort: " + CustomComponentGalleryState.selected_filter, "Sort", - rx.cond( - condition, - rx.text( - f": {CustomComponentGalleryState.selected_filter}", - as_="span", - class_name="text-nowrap", - ), - ), - as_="span", - class_name="font-small", - ), - get_icon( - icon="select", ), - justify_content="space-between", + get_icon("select"), + variant="outline", + size="sm", ), - style=sorting_box_style | conditional_style, + as_child=True, ), rx.menu.content( - rx.menu.item(sorting_filters(), style=menu_item_style), - bg="transparent", - box_shadow="None", - padding="0px", - overflow="visible", - border="none", - align="center", + *[ + rx.menu.item( + get_icon(icon), + label, + rx.cond( + CustomComponentGalleryState.selected_filter == label, + rx.icon("check", size=14), + rx.fragment(), + ), + on_select=CustomComponentGalleryState.set_selected_filter(label), + class_name="flex items-center gap-2 rounded-compact px-3 py-2 text-sm font-book text-foreground data-[highlighted]:bg-muted data-[highlighted]:text-foreground", + ) + for label, icon in ( + ("Recent", "history"), + ("Downloads", "arrow_down_big"), + ) + ], + align="end", + class_name="min-w-44 rounded-panel border border-border bg-background p-1 shadow-medium", ), - width="100%", ) @@ -282,7 +176,7 @@ def download(download_url: str) -> rx.Component: underline="none", href=download_url, is_external=True, - class_name="text-secondary-9 hover:!text-secondary-9 bg-secondary-1 hover:bg-secondary-3 transition-bg cursor-pointer rounded-[6px]", + class_name="text-secondary-9 hover:!text-secondary-9 bg-secondary-1 hover:bg-secondary-3 transition-bg cursor-pointer rounded-compact", title="Documentation", ) @@ -301,15 +195,17 @@ def table_rows(category: dict): rx.table.cell(name), rx.table.cell(updated_at), rx.table.cell( - rx.box( + rx.el.button( rx.text( "pip install " + category["package_name"], as_="p", - class_name="font-small truncate flex-1 min-w-0", + class_name="font-mono text-xs truncate flex-1 min-w-0", ), get_icon(icon="copy", class_name="p-[5px]"), on_click=rx.set_clipboard("pip install " + category["package_name"]), - class_name="flex flex-row gap-1.5 text-secondary-9 w-full items-center overflow-hidden border border-secondary-5 bg-secondary-1 hover:bg-secondary-3 transition-bg cursor-pointer shadow-small rounded-[6px] px-1.5 max-w-[20rem]", + type="button", + aria_label="Copy install command for " + category["package_name"], + class_name="flex flex-row gap-1.5 text-muted-foreground w-full items-center overflow-hidden border border-border bg-background hover:bg-muted transition-colors cursor-pointer rounded-compact px-3 min-h-9 max-w-[20rem] focus-visible:outline-2 focus-visible:outline-ring", ) ), rx.table.cell(download(category["download_url"])), @@ -352,7 +248,9 @@ def component_grid(): def create_pagination(): return rx.hstack( rx.hstack( - rx.text("Rows per page", weight="bold", font_size="12px"), + rx.text( + "Rows per page", class_name="text-xs font-normal text-muted-foreground" + ), rx.select( CustomComponentGalleryState.limits, default_value="50", @@ -365,34 +263,27 @@ def create_pagination(): rx.text( f"Page {CustomComponentGalleryState.current_page} of {CustomComponentGalleryState.total_pages}", width="100px", - weight="bold", - font_size="12px", + class_name="text-xs font-normal text-muted-foreground", ), - rx.button( - rx.icon( - tag="chevron-left", - on_click=CustomComponentGalleryState.previous, - size=25, - cursor="pointer", - ), - color_scheme="gray", - variant="surface", - size="1", - width="32px", - height="32px", + button( + rx.icon("chevron-left", size=16), + on_click=CustomComponentGalleryState.previous, + disabled=CustomComponentGalleryState.offset == 0, + aria_label="Previous page", + variant="outline", + size="icon-sm", ), - rx.button( - rx.icon( - tag="chevron-right", - on_click=CustomComponentGalleryState.next, - size=25, - cursor="pointer", + button( + rx.icon("chevron-right", size=16), + on_click=CustomComponentGalleryState.next, + disabled=( + CustomComponentGalleryState.offset + + CustomComponentGalleryState.current_limit + >= CustomComponentGalleryState.number_of_rows ), - color_scheme="gray", - variant="surface", - size="1", - width="32px", - height="32px", + aria_label="Next page", + variant="outline", + size="icon-sm", ), align_items="center", spacing="1", @@ -413,9 +304,9 @@ def custom_components() -> rx.Component: text="Reflex has a growing ecosystem of custom components that you can use to build your apps. Below is a list of some of the custom components available for Reflex.", ), sorting_filters_dropdown_menu(), - class_name="flex flex-row w-full gap-12 justify-between items-center", + class_name="flex flex-col w-full gap-4 justify-between items-start sm:flex-row sm:items-start [&>p]:mb-0", ), - class_name="flex flex-col w-full", + class_name="flex flex-col w-full gap-6", ), component_grid(), create_pagination(), diff --git a/docs/app/reflex_docs/pages/docs/library.py b/docs/app/reflex_docs/pages/docs/library.py index d76c9b5873b..dd1a7dac423 100644 --- a/docs/app/reflex_docs/pages/docs/library.py +++ b/docs/app/reflex_docs/pages/docs/library.py @@ -1,7 +1,7 @@ import reflex as rx from reflex.utils.format import to_snake_case, to_title_case -from reflex_site_shared.components.icons import get_icon +from reflex_docs.components.component_catalog import component_category from reflex_docs.templates.docpage import docpage, h1_comp, h2_comp, text_comp_2 @@ -44,41 +44,23 @@ def generate_gallery( components, prefix: str = "", ): - sidebar = [ - rx.box( - rx.link( - rx.el.h2( - get_display_name(category), - class_name="font-large text-secondary-12", - ), - get_icon("new_tab", class_name="text-secondary-11 [&>svg]:size-4"), - href=f"/library/{prefix.strip('/') + '/' if prefix.strip('/') else ''}{category.lower()}", - underline="none", - class_name="px-4 py-2 bg-secondary-1 hover:bg-secondary-3 transition-bg flex flex-row justify-between items-center !text-secondary-12", - ), - rx.box( - *[ - rx.link( - get_display_name(c[0]), - href=get_component_link( - category=category, - clist=c, - prefix=prefix, - ), - class_name="font-small text-secondary-11 hover:!text-primary-9 transition-color w-fit", - ) - for c in get_components_for_category( - category, components[category] - ) - ], - class_name="flex flex-col gap-2.5 px-4 py-2 border-t border-secondary-5", - ), - class_name="flex flex-col border border-secondary-5 rounded-xl bg-secondary-2 shadow-large overflow-hidden", + categories = [ + component_category( + title=get_display_name(category), + href=f"/library/{prefix.strip('/') + '/' if prefix.strip('/') else ''}{category.lower()}/", + description=f"{len(components[category])} components", + links=[ + ( + get_display_name(c[0]), + get_component_link(category=category, clist=c, prefix=prefix), + ) + for c in get_components_for_category(category, components[category]) + ], ) for category in components ] - return sidebar + return categories core = generate_gallery( components=component_list, @@ -91,7 +73,7 @@ def generate_gallery( return rx.box( rx.box( *core, - class_name="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-6", + class_name="flex flex-col", ), rx.box( h2_comp( @@ -102,7 +84,7 @@ def generate_gallery( ), rx.box( *graphs, - class_name="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-6", + class_name="flex flex-col", ), class_name="flex flex-col", ), @@ -124,5 +106,15 @@ def library(): text="Components let you split the UI into independent, reusable pieces, and think about each piece in isolation. This page contains a list of all builtin components.", ), component_grid(), + rx.el.p( + "Connect your components to data and events with the ", + rx.el.a( + "state guides", + href="/library/state/", + class_name="docs-text-link underline underline-offset-4", + ), + ".", + class_name="mt-8 text-sm leading-6 text-muted-foreground", + ), class_name="flex flex-col h-full mb-12", ) diff --git a/docs/app/reflex_docs/pages/docs/metadata.py b/docs/app/reflex_docs/pages/docs/metadata.py index 401ab5b1dc3..b4b24310f46 100644 --- a/docs/app/reflex_docs/pages/docs/metadata.py +++ b/docs/app/reflex_docs/pages/docs/metadata.py @@ -1,5 +1,41 @@ """SEO metadata helpers for docs routes.""" +# Summaries for generated catalogs, reference pages, and pages without usable prose. +PAGE_DESCRIPTIONS = { + "library": "Browse Reflex UI components for forms, layouts, data display, charts, and media, with Python examples and prop references.", + "custom-components": "Explore community-built Reflex components and discover reusable Python interfaces for React libraries and custom UI elements.", + "recipes": "Find reusable Reflex recipes for page layouts, content, and authentication, with examples you can adapt for your Python app.", + "api-reference/app": "Configure rx.App, register pages, and customize application behavior with the App class attributes and methods reference.", + "api-reference/component": "Explore the Component API for creating Reflex UI elements, managing props, and customizing component rendering and behavior.", + "api-reference/componentstate": "Use ComponentState to pair reusable Reflex components with their own state. Explore its class attributes and methods.", + "api-reference/event": "Explore the Event API for Reflex event instances, including event names, payloads, and client tokens.", + "api-reference/eventspec": "Explore EventSpec, the representation of a Reflex event handler invocation, including its arguments and event actions.", + "api-reference/eventhandler": "Explore EventHandler attributes and methods for wrapping Python functions as Reflex event handlers.", + "api-reference/statemanager": "Explore StateManager methods for retrieving and managing Reflex state associated with client sessions.", + "api-reference/config": "Reference Reflex configuration fields, defaults, and environment variable overrides for running and deploying your app.", + "api-reference/importvar": "Explore ImportVar fields for describing JavaScript imports used by Reflex components, including aliases and default imports.", + "api-reference/state": "Explore the State class API for managing reactive app data, computed values, event handlers, and state updates in Reflex.", + "api-reference/var": "Explore the Var API for representing frontend expressions and working with typed reactive values in Reflex.", + "api-reference/environment-variables": "Look up Reflex environment variables, their types, and defaults to configure development, builds, and application runtime behavior.", + "hosting/cli/deploy": "Reference the Reflex Cloud deploy command and its options for publishing an application from your terminal.", + "hosting/cli/projects": "Reference Reflex Cloud CLI project commands and options for organizing hosted applications into projects.", + "hosting/cli/scan": "Run a Reflex-aware security review of your app source with the Cloud CLI scan command. Reference its options and output controls.", + "hosting/cli/apps": "Reference Reflex Cloud CLI app commands and options for managing your hosted applications from the terminal.", + "hosting/cli/config": "Reference Reflex Cloud CLI configuration commands and options for managing your local hosting configuration.", + "hosting/cli/secrets": "Reference Reflex Cloud CLI secret commands and options for managing sensitive values used by hosted applications.", + "hosting/cli/regions": "Reference Reflex Cloud CLI region commands to explore available deployment locations for your applications.", + "hosting/cli/providers": "Reference Reflex Cloud CLI provider commands and options for working with supported cloud providers.", + "hosting/cli/login": "Reference Reflex Cloud CLI login options for authenticating your terminal with your hosting account.", + "hosting/cli/vmtypes": "Reference Reflex Cloud CLI VM type commands to explore compute options for hosted applications.", + "overview": "Explore Reflex Cloud hosting and find guides for deploying applications, managing secrets, and monitoring your apps.", + "ai/figma": "Check the status of the planned Figma integration for Reflex Build. This integration is coming soon.", + "ai/integrations/overview": "Browse Reflex Build integrations for databases, AI services, and external tools, and find setup guides for connecting your app.", + "enterprise/components": "Explore enterprise Reflex components for advanced data grids, charts, editors, and interactive workflows, with guides and examples.", + "library/typography/em": "Add semantic emphasis to inline text with rx.text.em. See a Python example of emphasizing words within a Reflex text component.", + "library/typography/quote": "Mark short inline quotations with rx.text.quote. See how to include quoted text within a Reflex text component.", + "wrapping-react/step-by-step": "The Wrapping React Step by Step documentation page is a placeholder; a detailed component-wrapping walkthrough is not yet available.", +} + def truncate_meta_description(description: str, max_len: int = 155) -> str: """Return a search-snippet-safe meta description. @@ -55,8 +91,15 @@ def docs_metadata(path: str, title: str, description: str | None) -> tuple[str, parent for parent in reversed(parents) if parent.lower() != title.lower() ] subject = " ยท ".join([title, *context]) + route = path.strip("/") + if route == "changelog" or route.startswith("changelog/"): + package = route.removeprefix("changelog/") if "/" in route else "reflex" + fallback = f"Read {package} release notes, including new features, bug fixes, and breaking changes, before upgrading your project." + else: + fallback = PAGE_DESCRIPTIONS.get(route) summary = ( description + or fallback or f"{subject}: documentation, examples, and reference for building Python web applications with Reflex." ) return f"{subject} ยท Reflex Docs", truncate_meta_description(summary) diff --git a/docs/app/reflex_docs/pages/docs/recipes_overview.py b/docs/app/reflex_docs/pages/docs/recipes_overview.py index 693b9b2a36b..45f12b5a2e7 100644 --- a/docs/app/reflex_docs/pages/docs/recipes_overview.py +++ b/docs/app/reflex_docs/pages/docs/recipes_overview.py @@ -5,7 +5,7 @@ def get_component_link(category, clist) -> str: file_name_without_extension = clist.split("/")[-1].split(".")[0].replace("_", "-") - return f"/recipes/{category}/{file_name_without_extension}" + return f"/recipes/{category}/{file_name_without_extension}/" def format_titles(path): @@ -32,27 +32,27 @@ def component_grid(): rx.box( rx.el.h2( rx.utils.format.to_title_case(category), - class_name="font-large text-secondary-12", + class_name="text-lg font-book leading-6 tracking-tight text-foreground", ), rx.icon( icons.get(category, "shapes"), size=18, - class_name="!text-secondary-9", + class_name="text-muted-foreground", ), - class_name="px-4 py-2 flex flex-row !text-secondary-12 gap-3 items-center justify-between", + class_name="px-5 py-4 flex flex-row text-foreground gap-3 items-center justify-between", ), rx.box( *[ rx.link( format_titles(c), href=get_component_link(category, c), - class_name="font-small text-secondary-11 hover:!text-primary-9 transition-color w-fit", + class_name="text-sm font-book leading-6 text-muted-foreground hover:text-foreground transition-colors w-fit rounded-sm focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring", ) for c in recipes_list[category] ], - class_name="flex flex-col gap-3 px-4 py-2 border-t border-secondary-5", + class_name="flex flex-col gap-2 px-5 pb-5", ), - class_name="flex flex-col border border-secondary-5 rounded-xl bg-secondary-2 shadow-large overflow-hidden", + class_name="docs-catalog-card flex flex-col border border-border-subtle rounded-card bg-background overflow-hidden", ) ) @@ -65,13 +65,13 @@ def info_card(title, content): return rx.box( rx.el.h2( title, - class_name="font-md-smbold text-secondary-12", + class_name="text-lg font-book leading-6 tracking-tight text-foreground", ), rx.text( content, - class_name="font-small text-secondary-11", + class_name="text-sm font-normal leading-6 text-muted-foreground", ), - class_name="flex flex-col border gap-2 border-secondary-5 rounded-xl bg-secondary-1 shadow-large overflow-hidden px-4 py-2", + class_name="docs-catalog-card flex flex-col gap-2 border border-border-subtle rounded-card bg-background overflow-hidden p-5", ) @@ -95,7 +95,7 @@ def card_section(): ) -@docpage(set_path="/recipes", right_sidebar=False) +@docpage(set_path="/recipes/", right_sidebar=False) def overview(): return rx.box( h1_comp(text="Recipes"), diff --git a/docs/app/reflex_docs/pages/docs/source.py b/docs/app/reflex_docs/pages/docs/source.py index 3178d77d8ad..26166a91231 100644 --- a/docs/app/reflex_docs/pages/docs/source.py +++ b/docs/app/reflex_docs/pages/docs/source.py @@ -12,10 +12,10 @@ from reflex_docs.templates.docpage import h1_comp, h2_comp table_header_class_name = ( - "font-small text-secondary-12 text-normal w-auto justify-start pl-4 font-bold" + "text-sm font-medium text-foreground w-auto justify-start pl-4" ) -description_cell_class_name = "font-small text-secondary-11" +description_cell_class_name = "text-sm font-normal leading-6 text-muted-foreground" def format_field(field: FieldDocumentation) -> rx.Component: diff --git a/docs/app/reflex_docs/pages/docs_landing/__init__.py b/docs/app/reflex_docs/pages/docs_landing/__init__.py index 4dbffcc1fd1..1c642dc134f 100644 --- a/docs/app/reflex_docs/pages/docs_landing/__init__.py +++ b/docs/app/reflex_docs/pages/docs_landing/__init__.py @@ -1,8 +1,7 @@ import reflex as rx from reflex_site_shared.constants import REFLEX_ASSETS_CDN from reflex_site_shared.meta.meta import create_meta_tags -from reflex_site_shared.views.cta_card import cta_card -from reflex_site_shared.views.footer import footer_index +from reflex_site_shared.utils.url import public_url from reflex_docs.pages.docs_landing.views import ( ai_builder_section, @@ -14,7 +13,9 @@ other_section, self_hosting_section, ) +from reflex_docs.pages.docs_landing.views.cta import docs_cta from reflex_docs.views.docs_navbar import docs_navbar +from reflex_docs.views.editorial_footer import editorial_footer @rx.page( @@ -24,12 +25,15 @@ # create_meta_tags list to avoid a favicon-default + preview duplicate. image=f"{REFLEX_ASSETS_CDN}previews/index_preview.webp", meta=[ + rx.el.link(rel="alternate", type="text/markdown", href=public_url("/index.md")) + ] + + [ m for m in create_meta_tags( title="Reflex Documentation - Build Web Apps in Pure Python", description="Reflex documentation: tutorials, API reference, and guides for building full-stack Python web apps. Get started in minutes.", image=f"{REFLEX_ASSETS_CDN}previews/index_preview.webp", - url="https://reflex.dev/docs/", + url=public_url("/"), ) if not (isinstance(m, dict) and m.get("property") == "og:image") ], @@ -47,8 +51,8 @@ def docs_landing() -> rx.Component: hosting_section(), self_hosting_section(), other_section(), - cta_card(), - footer_index(), + docs_cta(), + editorial_footer(), class_name="flex flex-col relative justify-center items-center w-full overflow-hidden", ), class_name="flex flex-col w-full relative h-full justify-center items-center", diff --git a/docs/app/reflex_docs/pages/docs_landing/views/ai_builder.py b/docs/app/reflex_docs/pages/docs_landing/views/ai_builder.py index 6339dded631..8456a74d68c 100644 --- a/docs/app/reflex_docs/pages/docs_landing/views/ai_builder.py +++ b/docs/app/reflex_docs/pages/docs_landing/views/ai_builder.py @@ -4,10 +4,10 @@ import reflex as rx import reflex_components_internal as ui from reflex_site_shared.components.marquee import marquee -from reflex_site_shared.constants import REFLEX_ASSETS_CDN from reflex_site_shared.integrations import get_integration_logo_url from reflex_docs.pages.docs import ai_builder as ai_builder_pages +from reflex_docs.pages.docs_landing.views.artwork import artwork def get_integration_path() -> list: @@ -67,33 +67,49 @@ def get_integration_path() -> list: def card( - title: str, description: str, content: str, href: str, enteprise_only: bool = False + title: str, + description: str, + content: rx.Component, + href: str, + tone: str, + enterprise_only: bool = False, ) -> rx.Component: + """Render a linked guide with a matching editorial illustration panel.""" return rx.el.div( - rx.el.span( - "Enterprise-only", - class_name="text-secondary-12 text-xs font-medium bg-secondary-1 px-2.5 h-7 absolute top-0 right-0 border-b border-l rounded-bl-lg border-secondary-4 flex justify-center items-center", - ) - if enteprise_only - else None, rx.el.div( - rx.el.span( - title, - class_name="text-secondary-12 text-xl font-[575]", + rx.el.div( + rx.el.h3( + title, + class_name="docs-ai-card-title text-xl font-book tracking-tight", + ), + rx.el.span( + "Enterprise-only", + class_name="w-fit rounded-full border border-border bg-muted px-2.5 py-1 text-xs text-muted-foreground", + ) + if enterprise_only + else None, + rx.icon( + "arrow-up-right", + size=18, + aria_hidden=True, + class_name="ml-auto shrink-0", + ), + class_name="flex w-full items-center gap-3 text-foreground", ), - rx.el.span( + rx.el.p( description, - class_name="text-secondary-11 text-sm font-[475]", + class_name="text-sm font-normal leading-6 text-muted-foreground", ), - class_name="flex flex-col gap-2 p-8", + class_name="flex flex-1 flex-col items-start gap-3 p-7 sm:p-8", ), - content, + rx.el.div(content, class_name="docs-ai-card-art", aria_hidden=True), rx.el.a( href=href, aria_label=title, - class_name="absolute inset-0 rounded-xl focus-visible:outline-2 focus-visible:-outline-offset-4 focus-visible:outline-primary-9", + class_name="docs-ai-card-link absolute inset-0 rounded-panel focus-visible:outline-2 focus-visible:-outline-offset-4 focus-visible:outline-foreground", ), - class_name="flex flex-col bg-secondary-1/96 backdrop-blur-[16px] rounded-xl relative cursor-pointer transition-colors overflow-hidden shadow-[0_0_0_1px_rgba(0,0,0,0.04),0_12px_24px_0_rgba(0,0,0,0.08),0_1px_1px_0_rgba(0,0,0,0.01),0_4px_8px_0_rgba(0,0,0,0.03)] dark:shadow-none dark:border dark:border-secondary-4", + data_tone=tone, + class_name="docs-ai-card flex min-w-0 flex-col overflow-hidden rounded-panel border border-border bg-background relative text-left transition-colors hover:border-border-strong", ) @@ -106,7 +122,7 @@ def integration_icon_marquee(integration_name: str) -> rx.Component: ), alt=f"{integration_name} logo", unstyled=True, - class_name="size-full", + class_name="size-6 object-contain", ), ui.avatar.fallback( integration_name[0], @@ -114,7 +130,7 @@ def integration_icon_marquee(integration_name: str) -> rx.Component: unstyled=True, ), unstyled=True, - class_name="size-6.5 flex items-center justify-center mx-3", + class_name="docs-ai-integration flex size-14 shrink-0 items-center justify-center rounded-xl border border-border bg-background mx-2", ) @@ -127,22 +143,20 @@ def integrations_marquee() -> rx.Component: marquee( *[integration_icon_marquee(name) for name in reversed(integration_names)], direction="left", - gradient_color="light-dark(rgba(255, 255, 255, 0.96), var(--secondary-1))", + gradient=False, class_name="h-auto w-full overflow-hidden", - gradient_width=65, speed=25, - pause_on_hover=False, + pause_on_hover=True, ), marquee( *[integration_icon_marquee(name) for name in integration_names], direction="right", - gradient_color="light-dark(rgba(255, 255, 255, 0.96), var(--secondary-1))", + gradient=False, class_name="h-auto w-full overflow-hidden", - gradient_width=65, speed=25, - pause_on_hover=False, + pause_on_hover=True, ), - class_name="flex flex-col gap-6.5 px-8 max-lg:pb-6", + class_name="docs-ai-integrations flex w-full flex-col gap-4", ) @@ -152,11 +166,11 @@ def ai_builder_section() -> rx.Component: rx.el.div( rx.el.h2( "AI Builder", - class_name="text-secondary-12 text-3xl font-[575]", + class_name="text-secondary-12 text-3xl font-book tracking-tight", ), rx.el.p( "Learn how to build applications with Reflex AI.", - class_name="text-secondary-11 text-sm font-[475]", + class_name="text-secondary-11 text-sm font-normal", ), class_name="flex flex-col gap-4", ), @@ -164,33 +178,34 @@ def ai_builder_section() -> rx.Component: card( title="Getting Started", description="A comprehensive guide to working effectively with AI Builder. The key to success is clarity, structure, and iteration.", - content=rx.image( - src=f"{REFLEX_ASSETS_CDN}docs/{rx.color_mode_cond('light', 'dark')}/getting_started_1.svg", - alt="AI Builder getting started guide", - class_name="w-full h-auto pb-8", + content=artwork( + "ai_getting_started", + "w-full", ), href=ai_builder_pages.overview.best_practices.path, + tone="blue", ), card( title="Integrations", description="Easily connect with the tools your team already uses or extend your app with any Python SDK, library, or API.", content=integrations_marquee(), href=ai_builder_pages.integrations.overview.path, + tone="peach", ), card( title="MCP", description="The Reflex Model Context Protocol (MCP) provides AI assistants and coding tools with structured access to Reflex documentation and component information.", - content=rx.image( - src=f"{REFLEX_ASSETS_CDN}docs/{rx.color_mode_cond('light', 'dark')}/mcp_1.svg", - alt="Reflex MCP integration illustration", - class_name="w-full h-auto -mt-4", + content=artwork( + "ai_mcp", + "w-full", ), href=ai_builder_pages.integrations.mcp_overview.path, - enteprise_only=True, + tone="mint", + enterprise_only=True, ), - class_name="grid grid-cols-1 lg:grid-cols-3 gap-12", + class_name="grid grid-cols-1 lg:grid-cols-3 gap-6", ), class_name="flex flex-col gap-10 max-lg:text-center relative max-w-(--landing-layout-max-width) mx-auto", ), - class_name="bg-gradient-to-b from-secondary-2 to-secondary-1 w-full lg:pt-24 lg:pb-24 pb-10 max-xl:px-6 max-lg:pt-10", + class_name="bg-muted w-full lg:pt-24 lg:pb-24 pb-10 max-xl:px-6 max-lg:pt-10", ) diff --git a/docs/app/reflex_docs/pages/docs_landing/views/ai_getting_started.svg b/docs/app/reflex_docs/pages/docs_landing/views/ai_getting_started.svg new file mode 100644 index 00000000000..b11c3368494 --- /dev/null +++ b/docs/app/reflex_docs/pages/docs_landing/views/ai_getting_started.svg @@ -0,0 +1,17 @@ +<svg width="360" height="220" viewBox="0 0 360 220" fill="none" xmlns="http://www.w3.org/2000/svg" font-family="Instrument Sans, sans-serif"> +<path d="M180 85V114" stroke="var(--ai-accent)" stroke-width="1.25"/> +<rect x="32" y="27" width="296" height="58" rx="12" fill="var(--background)" stroke="var(--border)"/> +<text x="49" y="61" fill="var(--foreground)" font-size="12">Build a customer dashboard</text> +<circle cx="301" cy="56" r="12" fill="var(--foreground)"/> +<path d="M297 56H305M302 52L306 56L302 60" stroke="var(--background)" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/> +<rect x="56" y="106" width="248" height="145" rx="12" fill="var(--background)" stroke="var(--border)"/> +<path d="M56 134H304" stroke="var(--border)"/> +<circle cx="71" cy="120" r="1.5" fill="var(--muted-foreground)"/><circle cx="77" cy="120" r="1.5" fill="var(--muted-foreground)"/><circle cx="83" cy="120" r="1.5" fill="var(--muted-foreground)"/> +<text x="98" y="123" fill="var(--muted-foreground)" font-size="9">Customers</text> +<rect x="71" y="148" width="90" height="48" rx="6" fill="var(--ai-tint)"/> +<text x="81" y="164" fill="var(--ai-accent)" font-size="9">Active customers</text> +<text x="81" y="184" fill="var(--foreground)" font-size="17">1,284</text> +<path d="M176 188H287" stroke="var(--border)"/> +<path d="M181 178L199 172L218 178L237 157L255 162L281 147" stroke="var(--ai-accent)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> +<path d="M72 209H288" stroke="var(--border)"/> +</svg> \ No newline at end of file diff --git a/docs/app/reflex_docs/pages/docs_landing/views/ai_mcp.svg b/docs/app/reflex_docs/pages/docs_landing/views/ai_mcp.svg new file mode 100644 index 00000000000..8f4e33e0d47 --- /dev/null +++ b/docs/app/reflex_docs/pages/docs_landing/views/ai_mcp.svg @@ -0,0 +1,15 @@ +<svg width="360" height="220" viewBox="0 0 360 220" fill="none" xmlns="http://www.w3.org/2000/svg" font-family="Instrument Sans, sans-serif"> +<path d="M113 110H148M212 110H234Q244 110 244 100V65Q244 55 254 55H270M244 100V155Q244 165 254 165H270" stroke="var(--ai-accent)" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/> +<rect x="22" y="77" width="91" height="66" rx="10" fill="var(--background)" stroke="var(--border)"/> +<path d="M61 91L63 97L69 99L63 101L61 107L59 101L53 99L59 97Z" stroke="var(--ai-accent)" stroke-width="1.2" stroke-linejoin="round"/> +<text x="38" y="127" fill="var(--foreground)" font-size="11">AI assistant</text> +<rect x="148" y="78" width="64" height="64" rx="14" fill="var(--background)" stroke="var(--border)"/> +<rect x="155" y="85" width="50" height="50" rx="9" fill="var(--ai-tint)"/> +<text x="164" y="114" fill="var(--ai-accent)" font-size="13">MCP</text> +<rect x="269" y="27" width="68" height="62" rx="10" fill="var(--background)" stroke="var(--border)"/> +<path d="M295 40H307L311 44V58H295V40ZM307 40V45H311M299 49H307M299 53H307" stroke="var(--ai-accent)" stroke-width="1.2" stroke-linejoin="round"/> +<text x="291" y="75" fill="var(--foreground)" font-size="10">Docs</text> +<rect x="259" y="136" width="88" height="62" rx="10" fill="var(--background)" stroke="var(--border)"/> +<path d="M293 151H301V159H293ZM305 151H313V159H305ZM293 163H301V171H293ZM305 163H313V171H305Z" stroke="var(--ai-accent)" stroke-width="1.2" stroke-linejoin="round"/> +<text x="273" y="186" fill="var(--foreground)" font-size="10">Components</text> +</svg> \ No newline at end of file diff --git a/docs/app/reflex_docs/pages/docs_landing/views/artwork.py b/docs/app/reflex_docs/pages/docs_landing/views/artwork.py new file mode 100644 index 00000000000..75abbb8e544 --- /dev/null +++ b/docs/app/reflex_docs/pages/docs_landing/views/artwork.py @@ -0,0 +1,14 @@ +"""Theme-aware versions of the documentation illustrations.""" + +from pathlib import Path + +import reflex as rx + + +def artwork(name: str, class_name: str = "") -> rx.Component: + """Inline a trusted local SVG so its colors follow the page theme.""" + return rx.html( + Path(__file__).with_name(f"{name}.svg").read_text(encoding="utf-8"), + aria_hidden=True, + class_name=f"pointer-events-none [&_svg]:h-auto [&_svg]:w-full {class_name}", + ) diff --git a/docs/app/reflex_docs/pages/docs_landing/views/cta.py b/docs/app/reflex_docs/pages/docs_landing/views/cta.py new file mode 100644 index 00000000000..2e6b3353f31 --- /dev/null +++ b/docs/app/reflex_docs/pages/docs_landing/views/cta.py @@ -0,0 +1,58 @@ +"""A quiet closing invitation using the editorial theme.""" + +import reflex as rx +import reflex_components_internal as ui +from reflex_components_internal.blocks.demo_form import demo_form_open_cs +from reflex_site_shared.components.marketing_button import button +from reflex_site_shared.constants import REFLEX_BUILD_URL + +from reflex_docs.pages.docs_landing.views.artwork import artwork + + +def docs_cta() -> rx.Component: + """Render the closing actions alongside the original product illustration.""" + return rx.el.section( + rx.el.div( + rx.el.div( + rx.el.h2( + "The platform to build and scale enterprise apps.", + id="docs-cta-title", + class_name="max-w-xl text-3xl sm:text-4xl font-book leading-[1.15] tracking-[-0.03em] text-foreground text-balance", + ), + rx.el.p( + "Describe your idea, and let AI transform it into a complete, " + "production-ready Python web application.", + class_name="max-w-lg text-base leading-7 font-normal text-muted-foreground", + ), + rx.el.div( + rx.el.elements.a( + button( + "Try for free", + ui.icon("ArrowRight01Icon", aria_hidden=True), + variant="primary", + native_button=False, + ), + href=REFLEX_BUILD_URL, + target="_blank", + rel="noopener noreferrer", + class_name="rounded-full focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-ring", + ), + button( + "Book a Demo", + variant="ghost", + aria_haspopup="dialog", + on_click=rx.call_function(demo_form_open_cs.set_value(True)), + ), + class_name="flex flex-wrap items-center gap-x-5 gap-y-3", + ), + class_name="relative z-10 flex flex-col items-start gap-6 p-6 sm:p-10 lg:py-14 lg:pl-12 lg:pr-0", + ), + artwork( + "cta_illustration", + "docs-cta-art self-center w-full max-lg:hidden", + ), + class_name="grid lg:grid-cols-[1.1fr_1fr] items-center overflow-hidden rounded-panel border border-border-subtle bg-muted", + ), + aria_labelledby="docs-cta-title", + class_name="docs-cta mx-auto w-full max-w-(--landing-layout-max-width) px-6 xl:px-0 py-16 lg:py-24", + ) diff --git a/docs/app/reflex_docs/pages/docs_landing/views/cta_illustration.svg b/docs/app/reflex_docs/pages/docs_landing/views/cta_illustration.svg new file mode 100644 index 00000000000..3f9abdf4eed --- /dev/null +++ b/docs/app/reflex_docs/pages/docs_landing/views/cta_illustration.svg @@ -0,0 +1,224 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="596" height="380" viewBox="0 0 596 380" fill="none" aria-hidden="true" focusable="false"> + <g clip-path="url(#docs-cta_illustration-clip0_25523_22528)"> + <mask id="docs-cta_illustration-mask0_25523_22528" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="596" height="380"> + <rect width="596" height="380" fill="url(#docs-cta_illustration-paint0_linear_25523_22528)" /> + </mask> + <g mask="url(#docs-cta_illustration-mask0_25523_22528)"> + <g clip-path="url(#docs-cta_illustration-clip1_25523_22528)"> + <mask id="docs-cta_illustration-mask1_25523_22528" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="334" height="380"> + <rect width="334" height="380" fill="url(#docs-cta_illustration-paint1_linear_25523_22528)" /> + </mask> + <g mask="url(#docs-cta_illustration-mask1_25523_22528)"> + <line x1="56.5" y1="-2.18557e-08" x2="56.5" y2="380" stroke="var(--border)" stroke-dasharray="1 3" /> + <g> + <rect x="32" y="108" width="320" height="32" rx="10" fill="var(--background)" /> + <path d="M126.772 128.12C126.236 128.12 125.768 128.016 125.368 127.808C124.968 127.592 124.66 127.292 124.444 126.908C124.228 126.516 124.12 126.06 124.12 125.54V121.7C124.12 121.172 124.228 120.716 124.444 120.332C124.66 119.948 124.968 119.652 125.368 119.444C125.768 119.228 126.236 119.12 126.772 119.12C127.316 119.12 127.784 119.228 128.176 119.444C128.576 119.652 128.884 119.948 129.1 120.332C129.316 120.716 129.424 121.172 129.424 121.7V125.54C129.424 126.06 129.316 126.516 129.1 126.908C128.884 127.292 128.576 127.592 128.176 127.808C127.776 128.016 127.308 128.12 126.772 128.12ZM126.772 127.172C127.244 127.172 127.624 127.02 127.912 126.716C128.208 126.412 128.356 126.02 128.356 125.54V121.7C128.356 121.22 128.208 120.828 127.912 120.524C127.624 120.22 127.244 120.068 126.772 120.068C126.3 120.068 125.916 120.22 125.62 120.524C125.332 120.828 125.188 121.22 125.188 121.7V125.54C125.188 126.02 125.332 126.412 125.62 126.716C125.916 127.02 126.3 127.172 126.772 127.172ZM126.772 124.328C126.556 124.328 126.38 124.26 126.244 124.124C126.116 123.988 126.052 123.808 126.052 123.584C126.052 123.368 126.116 123.196 126.244 123.068C126.38 122.932 126.556 122.864 126.772 122.864C126.988 122.864 127.16 122.932 127.288 123.068C127.424 123.196 127.492 123.368 127.492 123.584C127.492 123.808 127.424 123.988 127.288 124.124C127.16 124.26 126.988 124.328 126.772 124.328Z" fill="var(--foreground)" /> + <path d="M119.576 128.12C119.272 128.12 119.028 128.032 118.844 127.856C118.66 127.68 118.568 127.44 118.568 127.136C118.568 126.824 118.66 126.576 118.844 126.392C119.028 126.2 119.272 126.104 119.576 126.104C119.88 126.104 120.124 126.2 120.308 126.392C120.492 126.576 120.584 126.824 120.584 127.136C120.584 127.44 120.492 127.68 120.308 127.856C120.124 128.032 119.88 128.12 119.576 128.12Z" fill="var(--foreground)" /> + <path d="M109.861 128V126.992H112.225V120.176L109.849 121.94V120.716L111.841 119.24H113.341V126.992H115.273V128H109.861Z" fill="var(--foreground)" /> + <path d="M95.4824 128V119.24H98.1944C98.7224 119.24 99.1824 119.348 99.5744 119.564C99.9664 119.772 100.27 120.064 100.486 120.44C100.71 120.816 100.822 121.26 100.822 121.772C100.822 122.356 100.666 122.864 100.354 123.296C100.05 123.72 99.6384 124.012 99.1184 124.172L100.942 128H99.6344L97.9664 124.304H96.5864V128H95.4824ZM96.5864 123.296H98.1944C98.6424 123.296 98.9984 123.156 99.2624 122.876C99.5344 122.596 99.6704 122.228 99.6704 121.772C99.6704 121.3 99.5344 120.928 99.2624 120.656C98.9984 120.376 98.6424 120.236 98.1944 120.236H96.5864V123.296Z" fill="var(--foreground)" /> + <path d="M88.3833 128V119.24H93.4353V120.248H89.4873V122.924H93.0153V123.92H89.4873V126.992H93.4353V128H88.3833Z" fill="var(--foreground)" /> + <path d="M82.8439 128L80.5879 119.24H81.7399L83.2159 125.168C83.3119 125.576 83.3959 125.956 83.4679 126.308C83.5399 126.66 83.5919 126.932 83.6239 127.124C83.6559 126.932 83.7039 126.66 83.7679 126.308C83.8399 125.956 83.9279 125.576 84.0319 125.168L85.4839 119.24H86.6119L84.3559 128H82.8439Z" fill="var(--foreground)" /> + <path d="M142.6 124.88C142.296 124.88 142.052 124.792 141.868 124.616C141.684 124.44 141.592 124.2 141.592 123.896C141.592 123.584 141.684 123.336 141.868 123.152C142.052 122.96 142.296 122.864 142.6 122.864C142.904 122.864 143.148 122.96 143.332 123.152C143.516 123.336 143.608 123.584 143.608 123.896C143.608 124.2 143.516 124.44 143.332 124.616C143.148 124.792 142.904 124.88 142.6 124.88Z" fill="var(--subtle-foreground)" /> + <path d="M199.264 127.999V126.871L201.94 124.027C202.396 123.547 202.724 123.123 202.924 122.755C203.132 122.379 203.236 122.015 203.236 121.663C203.236 121.175 203.104 120.795 202.84 120.523C202.576 120.243 202.216 120.103 201.76 120.103C201.256 120.103 200.86 120.247 200.572 120.535C200.284 120.823 200.14 121.215 200.14 121.711H199.024C199.04 121.183 199.16 120.723 199.384 120.331C199.616 119.939 199.936 119.639 200.344 119.431C200.752 119.223 201.224 119.119 201.76 119.119C202.288 119.119 202.748 119.223 203.14 119.431C203.532 119.631 203.832 119.919 204.04 120.295C204.248 120.671 204.352 121.119 204.352 121.639C204.352 122.135 204.22 122.631 203.956 123.127C203.692 123.615 203.26 124.167 202.66 124.783L200.524 126.991H204.496V127.999H199.264Z" fill="var(--foreground)" /> + <path d="M192.068 127.999V119.239H193.172V122.983H194.456L196.376 119.239H197.588L195.428 123.451L197.708 127.999H196.436L194.42 123.955H193.172V127.999H192.068Z" fill="var(--foreground)" /> + <path d="M187.249 128.119C186.721 128.119 186.257 128.015 185.857 127.807C185.457 127.591 185.149 127.295 184.933 126.919C184.717 126.543 184.609 126.103 184.609 125.599H185.725C185.725 126.079 185.861 126.455 186.133 126.727C186.405 126.999 186.781 127.135 187.261 127.135C187.757 127.135 188.137 126.995 188.401 126.715C188.673 126.427 188.809 126.055 188.809 125.599V124.975C188.809 124.487 188.673 124.111 188.401 123.847C188.137 123.575 187.757 123.439 187.261 123.439H186.361V122.335L188.509 120.247H184.969V119.239H189.637V120.343L187.285 122.611V122.455C188.093 122.455 188.733 122.683 189.205 123.139C189.677 123.595 189.913 124.207 189.913 124.975V125.599C189.913 126.103 189.801 126.543 189.577 126.919C189.361 127.295 189.053 127.591 188.653 127.807C188.253 128.015 187.785 128.119 187.249 128.119Z" fill="var(--foreground)" /> + <path d="M178.782 127.999L181.914 120.259H178.638V121.543H177.522V119.239H183.018V120.319L179.982 127.999H178.782Z" fill="var(--foreground)" /> + <path d="M172.991 128.119C172.455 128.119 171.987 128.027 171.587 127.843C171.187 127.651 170.879 127.379 170.663 127.027C170.447 126.675 170.339 126.263 170.339 125.791C170.339 125.271 170.479 124.823 170.759 124.447C171.047 124.071 171.499 123.735 172.115 123.439L173.291 122.875C173.635 122.707 173.903 122.491 174.095 122.227C174.287 121.955 174.383 121.655 174.383 121.327C174.383 120.951 174.255 120.651 173.999 120.427C173.751 120.203 173.415 120.091 172.991 120.091C172.567 120.091 172.227 120.207 171.971 120.439C171.723 120.663 171.599 120.963 171.599 121.339C171.599 121.659 171.691 121.955 171.875 122.227C172.067 122.491 172.323 122.703 172.643 122.863L173.819 123.415C174.459 123.711 174.923 124.051 175.211 124.435C175.499 124.811 175.643 125.263 175.643 125.791C175.643 126.503 175.403 127.071 174.923 127.495C174.451 127.911 173.807 128.119 172.991 128.119ZM172.991 127.135C173.471 127.135 173.851 127.011 174.131 126.763C174.419 126.515 174.563 126.179 174.563 125.755C174.563 125.419 174.471 125.111 174.287 124.831C174.103 124.551 173.839 124.331 173.495 124.171L172.331 123.595C171.691 123.291 171.227 122.959 170.939 122.599C170.659 122.239 170.519 121.807 170.519 121.303C170.519 120.855 170.619 120.467 170.819 120.139C171.019 119.803 171.303 119.547 171.671 119.371C172.047 119.195 172.487 119.107 172.991 119.107C173.495 119.107 173.931 119.195 174.299 119.371C174.675 119.547 174.963 119.803 175.163 120.139C175.363 120.467 175.463 120.855 175.463 121.303C175.463 121.807 175.319 122.239 175.031 122.599C174.751 122.951 174.283 123.287 173.627 123.607L172.451 124.183C172.123 124.335 171.867 124.551 171.683 124.831C171.507 125.103 171.419 125.403 171.419 125.731C171.419 126.163 171.559 126.507 171.839 126.763C172.127 127.011 172.511 127.135 172.991 127.135Z" fill="var(--foreground)" /> + <path d="M165.843 128.119C165.315 128.119 164.855 128.019 164.463 127.819C164.071 127.619 163.767 127.331 163.551 126.955C163.343 126.571 163.239 126.119 163.239 125.599V121.639C163.239 121.111 163.343 120.659 163.551 120.283C163.767 119.907 164.071 119.619 164.463 119.419C164.855 119.219 165.315 119.119 165.843 119.119C166.371 119.119 166.827 119.223 167.211 119.431C167.603 119.631 167.903 119.919 168.111 120.295C168.319 120.671 168.423 121.119 168.423 121.639H167.319C167.319 121.151 167.187 120.775 166.923 120.511C166.667 120.247 166.307 120.115 165.843 120.115C165.379 120.115 165.011 120.247 164.739 120.511C164.475 120.767 164.343 121.139 164.343 121.627V125.599C164.343 126.087 164.475 126.463 164.739 126.727C165.011 126.991 165.379 127.123 165.843 127.123C166.307 127.123 166.667 126.991 166.923 126.727C167.187 126.463 167.319 126.087 167.319 125.599H168.423C168.423 126.111 168.319 126.559 168.111 126.943C167.903 127.319 167.603 127.611 167.211 127.819C166.827 128.019 166.371 128.119 165.843 128.119Z" fill="var(--foreground)" /> + <path d="M156.092 127.999V126.871L158.768 124.027C159.224 123.547 159.552 123.123 159.752 122.755C159.96 122.379 160.064 122.015 160.064 121.663C160.064 121.175 159.932 120.795 159.668 120.523C159.404 120.243 159.044 120.103 158.588 120.103C158.084 120.103 157.688 120.247 157.4 120.535C157.112 120.823 156.968 121.215 156.968 121.711H155.852C155.868 121.183 155.988 120.723 156.212 120.331C156.444 119.939 156.764 119.639 157.172 119.431C157.58 119.223 158.052 119.119 158.588 119.119C159.116 119.119 159.576 119.223 159.968 119.431C160.36 119.631 160.66 119.919 160.868 120.295C161.076 120.671 161.18 121.119 161.18 121.639C161.18 122.135 161.048 122.631 160.784 123.127C160.52 123.615 160.088 124.167 159.488 124.783L157.352 126.991H161.324V127.999H156.092Z" fill="var(--foreground)" /> + <circle cx="56.5" cy="123.5" r="2.5" fill="var(--subtle-foreground)" /> + </g> + <path d="M126.772 84.1201C126.236 84.1201 125.768 84.0161 125.368 83.8081C124.968 83.5921 124.66 83.2921 124.444 82.9081C124.228 82.5161 124.12 82.0601 124.12 81.5401V77.7001C124.12 77.1721 124.228 76.7161 124.444 76.3321C124.66 75.9481 124.968 75.6521 125.368 75.4441C125.768 75.2281 126.236 75.1201 126.772 75.1201C127.316 75.1201 127.784 75.2281 128.176 75.4441C128.576 75.6521 128.884 75.9481 129.1 76.3321C129.316 76.7161 129.424 77.1721 129.424 77.7001V81.5401C129.424 82.0601 129.316 82.5161 129.1 82.9081C128.884 83.2921 128.576 83.5921 128.176 83.8081C127.776 84.0161 127.308 84.1201 126.772 84.1201ZM126.772 83.1721C127.244 83.1721 127.624 83.0201 127.912 82.7161C128.208 82.4121 128.356 82.0201 128.356 81.5401V77.7001C128.356 77.2201 128.208 76.8281 127.912 76.5241C127.624 76.2201 127.244 76.0681 126.772 76.0681C126.3 76.0681 125.916 76.2201 125.62 76.5241C125.332 76.8281 125.188 77.2201 125.188 77.7001V81.5401C125.188 82.0201 125.332 82.4121 125.62 82.7161C125.916 83.0201 126.3 83.1721 126.772 83.1721ZM126.772 80.3281C126.556 80.3281 126.38 80.2601 126.244 80.1241C126.116 79.9881 126.052 79.8081 126.052 79.5841C126.052 79.3681 126.116 79.1961 126.244 79.0681C126.38 78.9321 126.556 78.8641 126.772 78.8641C126.988 78.8641 127.16 78.9321 127.288 79.0681C127.424 79.1961 127.492 79.3681 127.492 79.5841C127.492 79.8081 127.424 79.9881 127.288 80.1241C127.16 80.2601 126.988 80.3281 126.772 80.3281Z" fill="var(--subtle-foreground)" /> + <path d="M119.576 84.1195C119.272 84.1195 119.028 84.0315 118.844 83.8555C118.66 83.6795 118.568 83.4395 118.568 83.1355C118.568 82.8235 118.66 82.5755 118.844 82.3915C119.028 82.1995 119.272 82.1035 119.576 82.1035C119.88 82.1035 120.124 82.1995 120.308 82.3915C120.492 82.5755 120.584 82.8235 120.584 83.1355C120.584 83.4395 120.492 83.6795 120.308 83.8555C120.124 84.0315 119.88 84.1195 119.576 84.1195Z" fill="var(--subtle-foreground)" /> + <path d="M112.381 84.1201C111.845 84.1201 111.377 84.0161 110.977 83.8081C110.577 83.5921 110.269 83.2921 110.053 82.9081C109.837 82.5161 109.729 82.0601 109.729 81.5401V77.7001C109.729 77.1721 109.837 76.7161 110.053 76.3321C110.269 75.9481 110.577 75.6521 110.977 75.4441C111.377 75.2281 111.845 75.1201 112.381 75.1201C112.925 75.1201 113.393 75.2281 113.785 75.4441C114.185 75.6521 114.493 75.9481 114.709 76.3321C114.925 76.7161 115.033 77.1721 115.033 77.7001V81.5401C115.033 82.0601 114.925 82.5161 114.709 82.9081C114.493 83.2921 114.185 83.5921 113.785 83.8081C113.385 84.0161 112.917 84.1201 112.381 84.1201ZM112.381 83.1721C112.853 83.1721 113.233 83.0201 113.521 82.7161C113.817 82.4121 113.965 82.0201 113.965 81.5401V77.7001C113.965 77.2201 113.817 76.8281 113.521 76.5241C113.233 76.2201 112.853 76.0681 112.381 76.0681C111.909 76.0681 111.525 76.2201 111.229 76.5241C110.941 76.8281 110.797 77.2201 110.797 77.7001V81.5401C110.797 82.0201 110.941 82.4121 111.229 82.7161C111.525 83.0201 111.909 83.1721 112.381 83.1721ZM112.381 80.3281C112.165 80.3281 111.989 80.2601 111.853 80.1241C111.725 79.9881 111.661 79.8081 111.661 79.5841C111.661 79.3681 111.725 79.1961 111.853 79.0681C111.989 78.9321 112.165 78.8641 112.381 78.8641C112.597 78.8641 112.769 78.9321 112.897 79.0681C113.033 79.1961 113.101 79.3681 113.101 79.5841C113.101 79.8081 113.033 79.9881 112.897 80.1241C112.769 80.2601 112.597 80.3281 112.381 80.3281Z" fill="var(--subtle-foreground)" /> + <path d="M95.4824 84.0002V75.2402H98.1944C98.7224 75.2402 99.1824 75.3482 99.5744 75.5642C99.9664 75.7722 100.27 76.0642 100.486 76.4402C100.71 76.8162 100.822 77.2602 100.822 77.7722C100.822 78.3562 100.666 78.8642 100.354 79.2962C100.05 79.7202 99.6384 80.0122 99.1184 80.1722L100.942 84.0002H99.6344L97.9664 80.3042H96.5864V84.0002H95.4824ZM96.5864 79.2962H98.1944C98.6424 79.2962 98.9984 79.1562 99.2624 78.8762C99.5344 78.5962 99.6704 78.2282 99.6704 77.7722C99.6704 77.3002 99.5344 76.9282 99.2624 76.6562C98.9984 76.3762 98.6424 76.2362 98.1944 76.2362H96.5864V79.2962Z" fill="var(--subtle-foreground)" /> + <path d="M88.3833 84.0002V75.2402H93.4353V76.2482H89.4873V78.9242H93.0153V79.9202H89.4873V82.9922H93.4353V84.0002H88.3833Z" fill="var(--subtle-foreground)" /> + <path d="M82.8439 84.0002L80.5879 75.2402H81.7399L83.2159 81.1682C83.3119 81.5762 83.3959 81.9562 83.4679 82.3082C83.5399 82.6602 83.5919 82.9322 83.6239 83.1242C83.6559 82.9322 83.7039 82.6602 83.7679 82.3082C83.8399 81.9562 83.9279 81.5762 84.0319 81.1682L85.4839 75.2402H86.6119L84.3559 84.0002H82.8439Z" fill="var(--subtle-foreground)" /> + <path d="M142.6 80.8803C142.296 80.8803 142.052 80.7923 141.868 80.6163C141.684 80.4403 141.592 80.2003 141.592 79.8963C141.592 79.5843 141.684 79.3363 141.868 79.1523C142.052 78.9603 142.296 78.8643 142.6 78.8643C142.904 78.8643 143.148 78.9603 143.332 79.1523C143.516 79.3363 143.608 79.5843 143.608 79.8963C143.608 80.2003 143.516 80.4403 143.332 80.6163C143.148 80.7923 142.904 80.8803 142.6 80.8803Z" fill="var(--subtle-foreground)" /> + <path d="M201.784 84.1202C201.224 84.1202 200.732 84.0042 200.308 83.7722C199.884 83.5322 199.556 83.2002 199.324 82.7762C199.092 82.3442 198.976 81.8482 198.976 81.2882C198.976 80.8482 199.036 80.4162 199.156 79.9922C199.284 79.5682 199.472 79.1602 199.72 78.7682L201.904 75.2402H203.14L200.656 79.2362L200.704 79.2722C200.856 79.0402 201.064 78.8642 201.328 78.7442C201.592 78.6242 201.888 78.5642 202.216 78.5642C202.696 78.5642 203.112 78.6762 203.464 78.9002C203.816 79.1242 204.088 79.4402 204.28 79.8482C204.472 80.2562 204.568 80.7402 204.568 81.3002C204.568 81.8602 204.452 82.3522 204.22 82.7762C203.988 83.2002 203.664 83.5322 203.248 83.7722C202.832 84.0042 202.344 84.1202 201.784 84.1202ZM201.772 83.1362C202.276 83.1362 202.684 82.9682 202.996 82.6322C203.308 82.2962 203.464 81.8522 203.464 81.3002C203.464 80.7482 203.308 80.3042 202.996 79.9682C202.684 79.6322 202.276 79.4642 201.772 79.4642C201.268 79.4642 200.86 79.6322 200.548 79.9682C200.236 80.3042 200.08 80.7482 200.08 81.3002C200.08 81.8522 200.236 82.2962 200.548 82.6322C200.86 82.9682 201.268 83.1362 201.772 83.1362Z" fill="var(--subtle-foreground)" /> + <path d="M192.081 84.0002V75.2402H194.577C195.385 75.2402 196.013 75.4402 196.461 75.8402C196.917 76.2402 197.145 76.7962 197.145 77.5082C197.145 77.9082 197.061 78.2562 196.893 78.5522C196.725 78.8482 196.485 79.0762 196.173 79.2362C195.869 79.3962 195.505 79.4722 195.081 79.4642V79.2962C195.537 79.2882 195.933 79.3762 196.269 79.5602C196.613 79.7362 196.877 79.9962 197.061 80.3402C197.253 80.6842 197.349 81.0922 197.349 81.5642C197.349 82.0602 197.245 82.4922 197.037 82.8602C196.829 83.2282 196.529 83.5122 196.137 83.7122C195.753 83.9042 195.293 84.0002 194.757 84.0002H192.081ZM193.161 83.0042H194.637C195.133 83.0042 195.525 82.8722 195.813 82.6082C196.109 82.3442 196.257 81.9802 196.257 81.5162C196.257 81.0442 196.109 80.6602 195.813 80.3642C195.525 80.0682 195.133 79.9202 194.637 79.9202H193.161V83.0042ZM193.161 78.9602H194.565C195.021 78.9602 195.381 78.8402 195.645 78.6002C195.917 78.3522 196.053 78.0162 196.053 77.5922C196.053 77.1762 195.921 76.8482 195.657 76.6082C195.393 76.3602 195.033 76.2362 194.577 76.2362H193.161V78.9602Z" fill="var(--subtle-foreground)" /> + <path d="M187.429 84.1201C186.901 84.1201 186.441 84.0201 186.049 83.8201C185.657 83.6201 185.353 83.3321 185.137 82.9561C184.929 82.5721 184.825 82.1201 184.825 81.6001V77.6401C184.825 77.1121 184.929 76.6601 185.137 76.2841C185.353 75.9081 185.657 75.6201 186.049 75.4201C186.441 75.2201 186.901 75.1201 187.429 75.1201C187.957 75.1201 188.413 75.2241 188.797 75.4321C189.189 75.6321 189.489 75.9201 189.697 76.2961C189.905 76.6721 190.009 77.1201 190.009 77.6401H188.905C188.905 77.1521 188.773 76.7761 188.509 76.5121C188.253 76.2481 187.893 76.1161 187.429 76.1161C186.965 76.1161 186.597 76.2481 186.325 76.5121C186.061 76.7681 185.929 77.1401 185.929 77.6281V81.6001C185.929 82.0881 186.061 82.4641 186.325 82.7281C186.597 82.9921 186.965 83.1241 187.429 83.1241C187.893 83.1241 188.253 82.9921 188.509 82.7281C188.773 82.4641 188.905 82.0881 188.905 81.6001H190.009C190.009 82.1121 189.905 82.5601 189.697 82.9441C189.489 83.3201 189.189 83.6121 188.797 83.8201C188.413 84.0201 187.957 84.1201 187.429 84.1201Z" fill="var(--subtle-foreground)" /> + <path d="M178.782 84.0002L181.914 76.2602H178.638V77.5442H177.522V75.2402H183.018V76.3202L179.982 84.0002H178.782Z" fill="var(--subtle-foreground)" /> + <path d="M172.966 84.1202C172.246 84.1202 171.658 83.9362 171.202 83.5682C170.746 83.1922 170.478 82.6922 170.398 82.0682H171.514C171.546 82.4042 171.69 82.6682 171.946 82.8602C172.202 83.0442 172.546 83.1362 172.978 83.1362C173.474 83.1362 173.846 83.0042 174.094 82.7402C174.342 82.4682 174.466 82.0882 174.466 81.6002V80.7482C174.466 80.2602 174.342 79.8842 174.094 79.6202C173.846 79.3482 173.478 79.2122 172.99 79.2122C172.678 79.2122 172.406 79.2922 172.174 79.4522C171.95 79.6122 171.786 79.8242 171.682 80.0882H170.578L170.638 75.2402H175.27V76.2482H171.706L171.67 79.1402H172.018L171.67 79.4522C171.67 79.0762 171.818 78.7802 172.114 78.5642C172.418 78.3482 172.822 78.2402 173.326 78.2402C174.038 78.2322 174.59 78.4522 174.982 78.9002C175.382 79.3402 175.582 79.9562 175.582 80.7482V81.6002C175.582 82.3762 175.35 82.9922 174.886 83.4482C174.43 83.8962 173.79 84.1202 172.966 84.1202Z" fill="var(--subtle-foreground)" /> + <path d="M162.783 84.0002L165.051 75.2402H166.539L168.807 84.0002H167.679L167.115 81.6842H164.487L163.923 84.0002H162.783ZM164.703 80.7482H166.887L166.227 78.0842C166.099 77.5802 165.999 77.1562 165.927 76.8122C165.855 76.4682 165.811 76.2442 165.795 76.1402C165.779 76.2442 165.735 76.4682 165.663 76.8122C165.591 77.1562 165.491 77.5762 165.363 78.0722L164.703 80.7482Z" fill="var(--subtle-foreground)" /> + <path d="M159.896 84.0002V82.0922H155.948V80.3642L159.404 75.2402H160.616L157.052 80.6042V81.0842H159.896V78.9602H161.012V84.0002H159.896Z" fill="var(--subtle-foreground)" /> + <rect x="56" y="74" width="1" height="11" fill="var(--subtle-foreground)" /> + <circle cx="56.5" cy="79.5" r="1.5" fill="var(--subtle-foreground)" /> + <path d="M126.772 172.12C126.236 172.12 125.768 172.016 125.368 171.808C124.968 171.592 124.66 171.292 124.444 170.908C124.228 170.516 124.12 170.06 124.12 169.54V165.7C124.12 165.172 124.228 164.716 124.444 164.332C124.66 163.948 124.968 163.652 125.368 163.444C125.768 163.228 126.236 163.12 126.772 163.12C127.316 163.12 127.784 163.228 128.176 163.444C128.576 163.652 128.884 163.948 129.1 164.332C129.316 164.716 129.424 165.172 129.424 165.7V169.54C129.424 170.06 129.316 170.516 129.1 170.908C128.884 171.292 128.576 171.592 128.176 171.808C127.776 172.016 127.308 172.12 126.772 172.12ZM126.772 171.172C127.244 171.172 127.624 171.02 127.912 170.716C128.208 170.412 128.356 170.02 128.356 169.54V165.7C128.356 165.22 128.208 164.828 127.912 164.524C127.624 164.22 127.244 164.068 126.772 164.068C126.3 164.068 125.916 164.22 125.62 164.524C125.332 164.828 125.188 165.22 125.188 165.7V169.54C125.188 170.02 125.332 170.412 125.62 170.716C125.916 171.02 126.3 171.172 126.772 171.172ZM126.772 168.328C126.556 168.328 126.38 168.26 126.244 168.124C126.116 167.988 126.052 167.808 126.052 167.584C126.052 167.368 126.116 167.196 126.244 167.068C126.38 166.932 126.556 166.864 126.772 166.864C126.988 166.864 127.16 166.932 127.288 167.068C127.424 167.196 127.492 167.368 127.492 167.584C127.492 167.808 127.424 167.988 127.288 168.124C127.16 168.26 126.988 168.328 126.772 168.328Z" fill="var(--subtle-foreground)" /> + <path d="M119.576 172.12C119.272 172.12 119.028 172.032 118.844 171.856C118.66 171.68 118.568 171.44 118.568 171.136C118.568 170.824 118.66 170.576 118.844 170.392C119.028 170.2 119.272 170.104 119.576 170.104C119.88 170.104 120.124 170.2 120.308 170.392C120.492 170.576 120.584 170.824 120.584 171.136C120.584 171.44 120.492 171.68 120.308 171.856C120.124 172.032 119.88 172.12 119.576 172.12Z" fill="var(--subtle-foreground)" /> + <path d="M109.873 172V170.872L112.549 168.028C113.005 167.548 113.333 167.124 113.533 166.756C113.741 166.38 113.845 166.016 113.845 165.664C113.845 165.176 113.713 164.796 113.449 164.524C113.185 164.244 112.825 164.104 112.369 164.104C111.865 164.104 111.469 164.248 111.181 164.536C110.893 164.824 110.749 165.216 110.749 165.712H109.633C109.649 165.184 109.769 164.724 109.993 164.332C110.225 163.94 110.545 163.64 110.953 163.432C111.361 163.224 111.833 163.12 112.369 163.12C112.897 163.12 113.357 163.224 113.749 163.432C114.141 163.632 114.441 163.92 114.649 164.296C114.857 164.672 114.961 165.12 114.961 165.64C114.961 166.136 114.829 166.632 114.565 167.128C114.301 167.616 113.869 168.168 113.269 168.784L111.133 170.992H115.105V172H109.873Z" fill="var(--subtle-foreground)" /> + <path d="M95.4824 172V163.24H98.1944C98.7224 163.24 99.1824 163.348 99.5744 163.564C99.9664 163.772 100.27 164.064 100.486 164.44C100.71 164.816 100.822 165.26 100.822 165.772C100.822 166.356 100.666 166.864 100.354 167.296C100.05 167.72 99.6384 168.012 99.1184 168.172L100.942 172H99.6344L97.9664 168.304H96.5864V172H95.4824ZM96.5864 167.296H98.1944C98.6424 167.296 98.9984 167.156 99.2624 166.876C99.5344 166.596 99.6704 166.228 99.6704 165.772C99.6704 165.3 99.5344 164.928 99.2624 164.656C98.9984 164.376 98.6424 164.236 98.1944 164.236H96.5864V167.296Z" fill="var(--subtle-foreground)" /> + <path d="M88.3833 172V163.24H93.4353V164.248H89.4873V166.924H93.0153V167.92H89.4873V170.992H93.4353V172H88.3833Z" fill="var(--subtle-foreground)" /> + <path d="M82.8439 172L80.5879 163.24H81.7399L83.2159 169.168C83.3119 169.576 83.3959 169.956 83.4679 170.308C83.5399 170.66 83.5919 170.932 83.6239 171.124C83.6559 170.932 83.7039 170.66 83.7679 170.308C83.8399 169.956 83.9279 169.576 84.0319 169.168L85.4839 163.24H86.6119L84.3559 172H82.8439Z" fill="var(--subtle-foreground)" /> + <path d="M142.6 168.88C142.296 168.88 142.052 168.792 141.868 168.616C141.684 168.44 141.592 168.2 141.592 167.896C141.592 167.584 141.684 167.336 141.868 167.152C142.052 166.96 142.296 166.864 142.6 166.864C142.904 166.864 143.148 166.96 143.332 167.152C143.516 167.336 143.608 167.584 143.608 167.896C143.608 168.2 143.516 168.44 143.332 168.616C143.148 168.792 142.904 168.88 142.6 168.88Z" fill="var(--subtle-foreground)" /> + <path d="M199.264 171.999V170.871L201.94 168.027C202.396 167.547 202.724 167.123 202.924 166.755C203.132 166.379 203.236 166.015 203.236 165.663C203.236 165.175 203.104 164.795 202.84 164.523C202.576 164.243 202.216 164.103 201.76 164.103C201.256 164.103 200.86 164.247 200.572 164.535C200.284 164.823 200.14 165.215 200.14 165.711H199.024C199.04 165.183 199.16 164.723 199.384 164.331C199.616 163.939 199.936 163.639 200.344 163.431C200.752 163.223 201.224 163.119 201.76 163.119C202.288 163.119 202.748 163.223 203.14 163.431C203.532 163.631 203.832 163.919 204.04 164.295C204.248 164.671 204.352 165.119 204.352 165.639C204.352 166.135 204.22 166.631 203.956 167.127C203.692 167.615 203.26 168.167 202.66 168.783L200.524 170.991H204.496V171.999H199.264Z" fill="var(--subtle-foreground)" /> + <path d="M194.625 172.119C194.049 172.119 193.553 172.023 193.137 171.831C192.721 171.639 192.401 171.363 192.177 171.003C191.953 170.643 191.837 170.215 191.829 169.719H192.945C192.945 170.151 193.093 170.495 193.389 170.751C193.685 170.999 194.097 171.123 194.625 171.123C195.121 171.123 195.509 171.003 195.789 170.763C196.069 170.515 196.209 170.175 196.209 169.743C196.209 169.391 196.117 169.087 195.933 168.831C195.749 168.567 195.481 168.383 195.129 168.279L193.953 167.919C193.345 167.735 192.877 167.427 192.549 166.995C192.221 166.555 192.057 166.043 192.057 165.459C192.057 164.987 192.165 164.575 192.381 164.223C192.597 163.871 192.897 163.599 193.281 163.407C193.665 163.207 194.121 163.107 194.649 163.107C195.417 163.107 196.033 163.323 196.497 163.755C196.969 164.179 197.209 164.747 197.217 165.459H196.101C196.101 165.035 195.973 164.703 195.717 164.463C195.461 164.223 195.101 164.103 194.637 164.103C194.173 164.103 193.813 164.215 193.557 164.439C193.301 164.655 193.173 164.963 193.173 165.363C193.173 165.715 193.269 166.023 193.461 166.287C193.653 166.543 193.925 166.727 194.277 166.839L195.465 167.211C196.057 167.395 196.513 167.711 196.833 168.159C197.161 168.599 197.325 169.115 197.325 169.707C197.325 170.187 197.213 170.611 196.989 170.979C196.765 171.339 196.449 171.619 196.041 171.819C195.633 172.019 195.161 172.119 194.625 172.119Z" fill="var(--subtle-foreground)" /> + <path d="M184.873 171.999V163.239H185.977V166.983H187.261L189.181 163.239H190.393L188.233 167.451L190.513 171.999H189.241L187.225 167.955H185.977V171.999H184.873Z" fill="var(--subtle-foreground)" /> + <path d="M177.174 171.999L179.442 163.239H180.93L183.198 171.999H182.07L181.506 169.683H178.878L178.314 171.999H177.174ZM179.094 168.747H181.278L180.618 166.083C180.49 165.579 180.39 165.155 180.318 164.811C180.246 164.467 180.202 164.243 180.186 164.139C180.17 164.243 180.126 164.467 180.054 164.811C179.982 165.155 179.882 165.575 179.754 166.071L179.094 168.747Z" fill="var(--subtle-foreground)" /> + <path d="M170.483 171.999V170.871L173.159 168.027C173.615 167.547 173.943 167.123 174.143 166.755C174.351 166.379 174.455 166.015 174.455 165.663C174.455 165.175 174.323 164.795 174.059 164.523C173.795 164.243 173.435 164.103 172.979 164.103C172.475 164.103 172.079 164.247 171.791 164.535C171.503 164.823 171.359 165.215 171.359 165.711H170.243C170.259 165.183 170.379 164.723 170.603 164.331C170.835 163.939 171.155 163.639 171.563 163.431C171.971 163.223 172.443 163.119 172.979 163.119C173.507 163.119 173.967 163.223 174.359 163.431C174.751 163.631 175.051 163.919 175.259 164.295C175.467 164.671 175.571 165.119 175.571 165.639C175.571 166.135 175.439 166.631 175.175 167.127C174.911 167.615 174.479 168.167 173.879 168.783L171.743 170.991H175.715V171.999H170.483Z" fill="var(--subtle-foreground)" /> + <path d="M165.796 172.119C165.26 172.119 164.792 172.027 164.392 171.843C163.992 171.651 163.684 171.379 163.468 171.027C163.252 170.675 163.144 170.263 163.144 169.791C163.144 169.271 163.284 168.823 163.564 168.447C163.852 168.071 164.304 167.735 164.92 167.439L166.096 166.875C166.44 166.707 166.708 166.491 166.9 166.227C167.092 165.955 167.188 165.655 167.188 165.327C167.188 164.951 167.06 164.651 166.804 164.427C166.556 164.203 166.22 164.091 165.796 164.091C165.372 164.091 165.032 164.207 164.776 164.439C164.528 164.663 164.404 164.963 164.404 165.339C164.404 165.659 164.496 165.955 164.68 166.227C164.872 166.491 165.128 166.703 165.448 166.863L166.624 167.415C167.264 167.711 167.728 168.051 168.016 168.435C168.304 168.811 168.448 169.263 168.448 169.791C168.448 170.503 168.208 171.071 167.728 171.495C167.256 171.911 166.612 172.119 165.796 172.119ZM165.796 171.135C166.276 171.135 166.656 171.011 166.936 170.763C167.224 170.515 167.368 170.179 167.368 169.755C167.368 169.419 167.276 169.111 167.092 168.831C166.908 168.551 166.644 168.331 166.3 168.171L165.136 167.595C164.496 167.291 164.032 166.959 163.744 166.599C163.464 166.239 163.324 165.807 163.324 165.303C163.324 164.855 163.424 164.467 163.624 164.139C163.824 163.803 164.108 163.547 164.476 163.371C164.852 163.195 165.292 163.107 165.796 163.107C166.3 163.107 166.736 163.195 167.104 163.371C167.48 163.547 167.768 163.803 167.968 164.139C168.168 164.467 168.268 164.855 168.268 165.303C168.268 165.807 168.124 166.239 167.836 166.599C167.556 166.951 167.088 167.287 166.432 167.607L165.256 168.183C164.928 168.335 164.672 168.551 164.488 168.831C164.312 169.103 164.224 169.403 164.224 169.731C164.224 170.163 164.364 170.507 164.644 170.763C164.932 171.011 165.316 171.135 165.796 171.135Z" fill="var(--subtle-foreground)" /> + <path d="M155.468 171.999L157.976 167.559L155.6 163.239H156.884L158.216 165.795C158.288 165.939 158.36 166.091 158.432 166.251C158.512 166.403 158.576 166.519 158.624 166.599C158.664 166.519 158.72 166.403 158.792 166.251C158.872 166.091 158.948 165.939 159.02 165.795L160.376 163.239H161.6L159.224 167.487L161.732 171.999H160.46L158.984 169.239C158.912 169.103 158.836 168.959 158.756 168.807C158.684 168.647 158.628 168.519 158.588 168.423C158.548 168.519 158.488 168.643 158.408 168.795C158.336 168.947 158.264 169.091 158.192 169.227L156.704 171.999H155.468Z" fill="var(--subtle-foreground)" /> + <rect x="56" y="162" width="1" height="11" fill="var(--subtle-foreground)" /> + <circle cx="56.5" cy="167.5" r="1.5" fill="var(--subtle-foreground)" /> + <g> + <rect x="32" y="220" width="320" height="96" rx="10" fill="var(--background)" /> + <path d="M289.323 249.14C288.613 249.14 287.988 248.981 287.447 248.664C286.905 248.337 286.481 247.894 286.173 247.334C285.874 246.774 285.725 246.13 285.725 245.402C285.725 244.674 285.874 244.034 286.173 243.484C286.481 242.924 286.905 242.49 287.447 242.182C287.988 241.874 288.613 241.72 289.323 241.72C290.004 241.72 290.597 241.864 291.101 242.154C291.605 242.443 291.992 242.854 292.263 243.386C292.543 243.908 292.683 244.524 292.683 245.234C292.683 245.346 292.678 245.448 292.669 245.542C292.659 245.635 292.645 245.733 292.627 245.836H286.663V244.786H291.689L291.325 245.234C291.325 244.45 291.147 243.852 290.793 243.442C290.447 243.022 289.957 242.812 289.323 242.812C288.651 242.812 288.114 243.045 287.713 243.512C287.311 243.969 287.111 244.599 287.111 245.402C287.111 246.223 287.311 246.872 287.713 247.348C288.114 247.814 288.665 248.048 289.365 248.048C289.785 248.048 290.153 247.959 290.471 247.782C290.797 247.604 291.035 247.343 291.185 246.998H292.473C292.239 247.66 291.847 248.183 291.297 248.566C290.746 248.948 290.088 249.14 289.323 249.14Z" fill="var(--subtle-foreground)" /> + <path d="M281.832 249.14C280.917 249.14 280.185 248.934 279.634 248.524C279.093 248.113 278.785 247.534 278.71 246.788H279.998C280.054 247.217 280.241 247.544 280.558 247.768C280.885 247.982 281.319 248.09 281.86 248.09C282.336 248.09 282.705 248.006 282.966 247.838C283.227 247.67 283.358 247.427 283.358 247.11C283.358 246.876 283.283 246.68 283.134 246.522C282.985 246.363 282.695 246.228 282.266 246.116L281.118 245.836C280.418 245.668 279.895 245.411 279.55 245.066C279.205 244.711 279.032 244.277 279.032 243.764C279.032 243.129 279.27 242.63 279.746 242.266C280.222 241.902 280.871 241.72 281.692 241.72C282.513 241.72 283.176 241.906 283.68 242.28C284.184 242.653 284.464 243.171 284.52 243.834H283.246C283.19 243.488 283.022 243.222 282.742 243.036C282.471 242.849 282.107 242.756 281.65 242.756C281.211 242.756 280.875 242.835 280.642 242.994C280.409 243.152 280.292 243.376 280.292 243.666C280.292 243.89 280.376 244.081 280.544 244.24C280.721 244.389 281.015 244.52 281.426 244.632L282.588 244.926C283.269 245.103 283.778 245.378 284.114 245.752C284.459 246.125 284.632 246.573 284.632 247.096C284.632 247.74 284.385 248.244 283.89 248.608C283.405 248.962 282.719 249.14 281.832 249.14Z" fill="var(--subtle-foreground)" /> + <path d="M276.175 249C276.138 248.832 276.11 248.64 276.091 248.426C276.072 248.211 276.063 247.945 276.063 247.628H275.993V244.226C275.993 243.75 275.876 243.395 275.643 243.162C275.41 242.928 275.041 242.812 274.537 242.812C274.042 242.812 273.646 242.91 273.347 243.106C273.058 243.302 272.89 243.577 272.843 243.932H271.541C271.597 243.25 271.896 242.714 272.437 242.322C272.978 241.92 273.697 241.72 274.593 241.72C275.508 241.72 276.194 241.934 276.651 242.364C277.108 242.793 277.337 243.428 277.337 244.268V247.628C277.337 247.842 277.351 248.062 277.379 248.286C277.416 248.51 277.458 248.748 277.505 249H276.175ZM273.711 249.14C272.983 249.14 272.404 248.962 271.975 248.608C271.546 248.244 271.331 247.754 271.331 247.138C271.331 246.503 271.555 246.004 272.003 245.64C272.451 245.276 273.095 245.028 273.935 244.898L276.315 244.506V245.5L274.145 245.85C273.66 245.924 273.291 246.055 273.039 246.242C272.796 246.419 272.675 246.68 272.675 247.026C272.675 247.362 272.787 247.614 273.011 247.782C273.244 247.95 273.576 248.034 274.005 248.034C274.584 248.034 275.06 247.898 275.433 247.628C275.806 247.357 275.993 247.012 275.993 246.592L276.189 247.614C276.021 248.099 275.722 248.477 275.293 248.748C274.864 249.009 274.336 249.14 273.711 249.14Z" fill="var(--subtle-foreground)" /> + <path d="M266.917 249.126C266.245 249.126 265.68 248.967 265.223 248.65C264.775 248.323 264.481 247.866 264.341 247.278L264.481 247.25V249H263.179V238.92H264.551V243.498L264.397 243.47C264.555 242.919 264.868 242.49 265.335 242.182C265.801 241.874 266.361 241.72 267.015 241.72C267.649 241.72 268.2 241.874 268.667 242.182C269.143 242.49 269.511 242.919 269.773 243.47C270.034 244.021 270.165 244.665 270.165 245.402C270.165 246.149 270.029 246.802 269.759 247.362C269.488 247.922 269.11 248.356 268.625 248.664C268.139 248.972 267.57 249.126 266.917 249.126ZM266.637 247.978C267.299 247.978 267.817 247.749 268.191 247.292C268.573 246.835 268.765 246.205 268.765 245.402C268.765 244.609 268.573 243.988 268.191 243.54C267.808 243.092 267.29 242.868 266.637 242.868C265.983 242.868 265.461 243.097 265.069 243.554C264.686 244.011 264.495 244.641 264.495 245.444C264.495 246.228 264.686 246.849 265.069 247.306C265.461 247.754 265.983 247.978 266.637 247.978Z" fill="var(--subtle-foreground)" /> + <path d="M259.988 249C259.95 248.832 259.922 248.64 259.904 248.426C259.885 248.211 259.876 247.945 259.876 247.628H259.806V244.226C259.806 243.75 259.689 243.395 259.456 243.162C259.222 242.928 258.854 242.812 258.35 242.812C257.855 242.812 257.458 242.91 257.16 243.106C256.87 243.302 256.702 243.577 256.656 243.932H255.354C255.41 243.25 255.708 242.714 256.25 242.322C256.791 241.92 257.51 241.72 258.406 241.72C259.32 241.72 260.006 241.934 260.464 242.364C260.921 242.793 261.15 243.428 261.15 244.268V247.628C261.15 247.842 261.164 248.062 261.192 248.286C261.229 248.51 261.271 248.748 261.318 249H259.988ZM257.524 249.14C256.796 249.14 256.217 248.962 255.788 248.608C255.358 248.244 255.144 247.754 255.144 247.138C255.144 246.503 255.368 246.004 255.816 245.64C256.264 245.276 256.908 245.028 257.748 244.898L260.128 244.506V245.5L257.958 245.85C257.472 245.924 257.104 246.055 256.852 246.242C256.609 246.419 256.488 246.68 256.488 247.026C256.488 247.362 256.6 247.614 256.824 247.782C257.057 247.95 257.388 248.034 257.818 248.034C258.396 248.034 258.872 247.898 259.246 247.628C259.619 247.357 259.806 247.012 259.806 246.592L260.002 247.614C259.834 248.099 259.535 248.477 259.106 248.748C258.676 249.009 258.149 249.14 257.524 249.14Z" fill="var(--subtle-foreground)" /> + <path d="M253.187 249.125C252.356 249.125 251.749 248.938 251.367 248.565C250.993 248.182 250.807 247.622 250.807 246.885V240.207L252.165 239.703V246.885C252.165 247.268 252.263 247.552 252.459 247.739C252.664 247.916 252.986 248.005 253.425 248.005C253.602 248.005 253.756 247.991 253.887 247.963C254.017 247.935 254.129 247.902 254.223 247.865V248.985C254.129 249.032 253.989 249.064 253.803 249.083C253.616 249.111 253.411 249.125 253.187 249.125ZM249.365 242.965V241.859H254.223V242.965H249.365Z" fill="var(--subtle-foreground)" /> + <path d="M247.259 249C247.222 248.832 247.194 248.64 247.175 248.426C247.156 248.211 247.147 247.945 247.147 247.628H247.077V244.226C247.077 243.75 246.96 243.395 246.727 243.162C246.494 242.928 246.125 242.812 245.621 242.812C245.126 242.812 244.73 242.91 244.431 243.106C244.142 243.302 243.974 243.577 243.927 243.932H242.625C242.681 243.25 242.98 242.714 243.521 242.322C244.062 241.92 244.781 241.72 245.677 241.72C246.592 241.72 247.278 241.934 247.735 242.364C248.192 242.793 248.421 243.428 248.421 244.268V247.628C248.421 247.842 248.435 248.062 248.463 248.286C248.5 248.51 248.542 248.748 248.589 249H247.259ZM244.795 249.14C244.067 249.14 243.488 248.962 243.059 248.608C242.63 248.244 242.415 247.754 242.415 247.138C242.415 246.503 242.639 246.004 243.087 245.64C243.535 245.276 244.179 245.028 245.019 244.898L247.399 244.506V245.5L245.229 245.85C244.744 245.924 244.375 246.055 244.123 246.242C243.88 246.419 243.759 246.68 243.759 247.026C243.759 247.362 243.871 247.614 244.095 247.782C244.328 247.95 244.66 248.034 245.089 248.034C245.668 248.034 246.144 247.898 246.517 247.628C246.89 247.357 247.077 247.012 247.077 246.592L247.273 247.614C247.105 248.099 246.806 248.477 246.377 248.748C245.948 249.009 245.42 249.14 244.795 249.14Z" fill="var(--subtle-foreground)" /> + <path d="M239.443 249V247.348L239.583 247.39C239.424 247.922 239.107 248.347 238.631 248.664C238.164 248.981 237.609 249.14 236.965 249.14C236.311 249.14 235.742 248.986 235.257 248.678C234.781 248.37 234.412 247.941 234.151 247.39C233.889 246.839 233.759 246.195 233.759 245.458C233.759 244.711 233.894 244.058 234.165 243.498C234.435 242.938 234.813 242.504 235.299 242.196C235.784 241.888 236.353 241.734 237.007 241.734C237.679 241.734 238.239 241.897 238.687 242.224C239.135 242.541 239.433 242.994 239.583 243.582L239.373 243.61V238.92H240.745V249H239.443ZM237.287 247.978C237.949 247.978 238.472 247.754 238.855 247.306C239.237 246.849 239.429 246.219 239.429 245.416C239.429 244.632 239.233 244.016 238.841 243.568C238.458 243.111 237.935 242.882 237.273 242.882C236.619 242.882 236.101 243.111 235.719 243.568C235.345 244.016 235.159 244.646 235.159 245.458C235.159 246.251 235.345 246.872 235.719 247.32C236.101 247.759 236.624 247.978 237.287 247.978Z" fill="var(--subtle-foreground)" /> + <path d="M223.052 249V238.92H224.424V249H223.052ZM228.134 249V244.38C228.134 243.876 228.003 243.498 227.742 243.246C227.49 242.994 227.116 242.868 226.622 242.868C226.192 242.868 225.81 242.966 225.474 243.162C225.147 243.349 224.89 243.615 224.704 243.96C224.517 244.296 224.424 244.688 224.424 245.136L224.242 243.442C224.475 242.91 224.834 242.49 225.32 242.182C225.814 241.874 226.384 241.72 227.028 241.72C227.784 241.72 228.381 241.935 228.82 242.364C229.268 242.793 229.492 243.358 229.492 244.058V249H228.134Z" fill="var(--subtle-foreground)" /> + <path d="M220.62 249.125C219.79 249.125 219.183 248.938 218.8 248.565C218.427 248.182 218.24 247.622 218.24 246.885V240.207L219.598 239.703V246.885C219.598 247.268 219.696 247.552 219.892 247.739C220.098 247.916 220.42 248.005 220.858 248.005C221.036 248.005 221.19 247.991 221.32 247.963C221.451 247.935 221.563 247.902 221.656 247.865V248.985C221.563 249.032 221.423 249.064 221.236 249.083C221.05 249.111 220.844 249.125 220.62 249.125ZM216.798 242.965V241.859H221.656V242.965H216.798Z" fill="var(--subtle-foreground)" /> + <path d="M214.206 249V241.86H215.578V249H214.206ZM214.122 240.404V238.85H215.648V240.404H214.122Z" fill="var(--subtle-foreground)" /> + <path d="M204.821 248.999L202.637 241.859H204.037L205.563 247.697H205.339L207.201 241.859H208.377L210.239 247.697H210.015L211.541 241.859H212.885L210.701 248.999H209.511L207.649 243.273H207.887L205.997 248.999H204.821Z" fill="var(--subtle-foreground)" /> + <path d="M197.169 249V247.348L197.309 247.39C197.151 247.922 196.833 248.347 196.357 248.664C195.891 248.981 195.335 249.14 194.691 249.14C194.038 249.14 193.469 248.986 192.983 248.678C192.507 248.37 192.139 247.941 191.877 247.39C191.616 246.839 191.485 246.195 191.485 245.458C191.485 244.711 191.621 244.058 191.891 243.498C192.162 242.938 192.54 242.504 193.025 242.196C193.511 241.888 194.08 241.734 194.733 241.734C195.405 241.734 195.965 241.897 196.413 242.224C196.861 242.541 197.16 242.994 197.309 243.582L197.099 243.61V238.92H198.471V249H197.169ZM195.013 247.978C195.676 247.978 196.199 247.754 196.581 247.306C196.964 246.849 197.155 246.219 197.155 245.416C197.155 244.632 196.959 244.016 196.567 243.568C196.185 243.111 195.662 242.882 194.999 242.882C194.346 242.882 193.828 243.111 193.445 243.568C193.072 244.016 192.885 244.646 192.885 245.458C192.885 246.251 193.072 246.872 193.445 247.32C193.828 247.759 194.351 247.978 195.013 247.978Z" fill="var(--subtle-foreground)" /> + <path d="M186.876 249V241.86H188.178V243.638H188.248V249H186.876ZM188.248 245.276L188.066 243.582C188.234 242.975 188.523 242.513 188.934 242.196C189.354 241.878 189.844 241.72 190.404 241.72C190.628 241.72 190.782 241.738 190.866 241.776V243.078C190.819 243.059 190.754 243.05 190.67 243.05C190.586 243.04 190.483 243.036 190.362 243.036C189.662 243.036 189.135 243.227 188.78 243.61C188.425 243.983 188.248 244.538 188.248 245.276Z" fill="var(--subtle-foreground)" /> + <path d="M183.685 249C183.647 248.832 183.619 248.64 183.601 248.426C183.582 248.211 183.573 247.945 183.573 247.628H183.503V244.226C183.503 243.75 183.386 243.395 183.153 243.162C182.919 242.928 182.551 242.812 182.047 242.812C181.552 242.812 181.155 242.91 180.857 243.106C180.567 243.302 180.399 243.577 180.353 243.932H179.051C179.107 243.25 179.405 242.714 179.947 242.322C180.488 241.92 181.207 241.72 182.103 241.72C183.017 241.72 183.703 241.934 184.161 242.364C184.618 242.793 184.847 243.428 184.847 244.268V247.628C184.847 247.842 184.861 248.062 184.889 248.286C184.926 248.51 184.968 248.748 185.015 249H183.685ZM181.221 249.14C180.493 249.14 179.914 248.962 179.485 248.608C179.055 248.244 178.841 247.754 178.841 247.138C178.841 246.503 179.065 246.004 179.513 245.64C179.961 245.276 180.605 245.028 181.445 244.898L183.825 244.506V245.5L181.655 245.85C181.169 245.924 180.801 246.055 180.549 246.242C180.306 246.419 180.185 246.68 180.185 247.026C180.185 247.362 180.297 247.614 180.521 247.782C180.754 247.95 181.085 248.034 181.515 248.034C182.093 248.034 182.569 247.898 182.943 247.628C183.316 247.357 183.503 247.012 183.503 246.592L183.699 247.614C183.531 248.099 183.232 248.477 182.803 248.748C182.373 249.009 181.846 249.14 181.221 249.14Z" fill="var(--subtle-foreground)" /> + <path d="M174.124 249.14C173.396 249.14 172.757 248.981 172.206 248.664C171.665 248.337 171.24 247.894 170.932 247.334C170.634 246.764 170.484 246.12 170.484 245.402C170.484 244.674 170.638 244.034 170.946 243.484C171.254 242.933 171.679 242.504 172.22 242.196C172.762 241.878 173.396 241.72 174.124 241.72C174.852 241.72 175.487 241.878 176.028 242.196C176.57 242.504 176.99 242.933 177.288 243.484C177.596 244.034 177.75 244.674 177.75 245.402C177.75 246.12 177.596 246.764 177.288 247.334C176.99 247.894 176.57 248.337 176.028 248.664C175.487 248.981 174.852 249.14 174.124 249.14ZM174.124 248.006C174.563 248.006 174.95 247.898 175.286 247.684C175.622 247.46 175.884 247.152 176.07 246.76C176.257 246.368 176.35 245.91 176.35 245.388C176.35 244.613 176.145 243.997 175.734 243.54C175.324 243.073 174.787 242.84 174.124 242.84C173.462 242.84 172.925 243.073 172.514 243.54C172.104 243.997 171.898 244.613 171.898 245.388C171.898 245.91 171.992 246.368 172.178 246.76C172.365 247.152 172.626 247.46 172.962 247.684C173.298 247.898 173.686 248.006 174.124 248.006Z" fill="var(--subtle-foreground)" /> + <path d="M166.169 249.126C165.497 249.126 164.932 248.967 164.475 248.65C164.027 248.323 163.733 247.866 163.593 247.278L163.733 247.25V249H162.431V238.92H163.803V243.498L163.649 243.47C163.807 242.919 164.12 242.49 164.587 242.182C165.053 241.874 165.613 241.72 166.267 241.72C166.901 241.72 167.452 241.874 167.919 242.182C168.395 242.49 168.763 242.919 169.025 243.47C169.286 244.021 169.417 244.665 169.417 245.402C169.417 246.149 169.281 246.802 169.011 247.362C168.74 247.922 168.362 248.356 167.877 248.664C167.391 248.972 166.822 249.126 166.169 249.126ZM165.889 247.978C166.551 247.978 167.069 247.749 167.443 247.292C167.825 246.835 168.017 246.205 168.017 245.402C168.017 244.609 167.825 243.988 167.443 243.54C167.06 243.092 166.542 242.868 165.889 242.868C165.235 242.868 164.713 243.097 164.321 243.554C163.938 244.011 163.747 244.641 163.747 245.444C163.747 246.228 163.938 246.849 164.321 247.306C164.713 247.754 165.235 247.978 165.889 247.978Z" fill="var(--subtle-foreground)" /> + <path d="M153.981 249V238.92H155.353V249H153.981ZM159.063 249V244.38C159.063 243.876 158.933 243.498 158.671 243.246C158.419 242.994 158.046 242.868 157.551 242.868C157.122 242.868 156.739 242.966 156.403 243.162C156.077 243.349 155.82 243.615 155.633 243.96C155.447 244.296 155.353 244.688 155.353 245.136L155.171 243.442C155.405 242.91 155.764 242.49 156.249 242.182C156.744 241.874 157.313 241.72 157.957 241.72C158.713 241.72 159.311 241.935 159.749 242.364C160.197 242.793 160.421 243.358 160.421 244.058V249H159.063Z" fill="var(--subtle-foreground)" /> + <path d="M149.775 249.14C148.861 249.14 148.128 248.934 147.577 248.524C147.036 248.113 146.728 247.534 146.653 246.788H147.941C147.997 247.217 148.184 247.544 148.501 247.768C148.828 247.982 149.262 248.09 149.803 248.09C150.279 248.09 150.648 248.006 150.909 247.838C151.171 247.67 151.301 247.427 151.301 247.11C151.301 246.876 151.227 246.68 151.077 246.522C150.928 246.363 150.639 246.228 150.209 246.116L149.061 245.836C148.361 245.668 147.839 245.411 147.493 245.066C147.148 244.711 146.975 244.277 146.975 243.764C146.975 243.129 147.213 242.63 147.689 242.266C148.165 241.902 148.814 241.72 149.635 241.72C150.457 241.72 151.119 241.906 151.623 242.28C152.127 242.653 152.407 243.171 152.463 243.834H151.189C151.133 243.488 150.965 243.222 150.685 243.036C150.415 242.849 150.051 242.756 149.593 242.756C149.155 242.756 148.819 242.835 148.585 242.994C148.352 243.152 148.235 243.376 148.235 243.666C148.235 243.89 148.319 244.081 148.487 244.24C148.665 244.389 148.959 244.52 149.369 244.632L150.531 244.926C151.213 245.103 151.721 245.378 152.057 245.752C152.403 246.125 152.575 246.573 152.575 247.096C152.575 247.74 152.328 248.244 151.833 248.608C151.348 248.962 150.662 249.14 149.775 249.14Z" fill="var(--subtle-foreground)" /> + <path d="M144.118 249C144.081 248.832 144.053 248.64 144.034 248.426C144.016 248.211 144.006 247.945 144.006 247.628H143.936V244.226C143.936 243.75 143.82 243.395 143.586 243.162C143.353 242.928 142.984 242.812 142.48 242.812C141.986 242.812 141.589 242.91 141.29 243.106C141.001 243.302 140.833 243.577 140.786 243.932H139.484C139.54 243.25 139.839 242.714 140.38 242.322C140.922 241.92 141.64 241.72 142.536 241.72C143.451 241.72 144.137 241.934 144.594 242.364C145.052 242.793 145.28 243.428 145.28 244.268V247.628C145.28 247.842 145.294 248.062 145.322 248.286C145.36 248.51 145.402 248.748 145.448 249H144.118ZM141.654 249.14C140.926 249.14 140.348 248.962 139.918 248.608C139.489 248.244 139.274 247.754 139.274 247.138C139.274 246.503 139.498 246.004 139.946 245.64C140.394 245.276 141.038 245.028 141.878 244.898L144.258 244.506V245.5L142.088 245.85C141.603 245.924 141.234 246.055 140.982 246.242C140.74 246.419 140.618 246.68 140.618 247.026C140.618 247.362 140.73 247.614 140.954 247.782C141.188 247.95 141.519 248.034 141.948 248.034C142.527 248.034 143.003 247.898 143.376 247.628C143.75 247.357 143.936 247.012 143.936 246.592L144.132 247.614C143.964 248.099 143.666 248.477 143.236 248.748C142.807 249.009 142.28 249.14 141.654 249.14Z" fill="var(--subtle-foreground)" /> + <path d="M136.302 249V247.348L136.442 247.39C136.284 247.922 135.966 248.347 135.49 248.664C135.023 248.981 134.468 249.14 133.824 249.14C133.171 249.14 132.601 248.986 132.116 248.678C131.64 248.37 131.272 247.941 131.01 247.39C130.749 246.839 130.618 246.195 130.618 245.458C130.618 244.711 130.754 244.058 131.024 243.498C131.295 242.938 131.673 242.504 132.158 242.196C132.643 241.888 133.213 241.734 133.866 241.734C134.538 241.734 135.098 241.897 135.546 242.224C135.994 242.541 136.293 242.994 136.442 243.582L136.232 243.61V238.92H137.604V249H136.302ZM134.146 247.978C134.809 247.978 135.331 247.754 135.714 247.306C136.097 246.849 136.288 246.219 136.288 245.416C136.288 244.632 136.092 244.016 135.7 243.568C135.317 243.111 134.795 242.882 134.132 242.882C133.479 242.882 132.961 243.111 132.578 243.568C132.205 244.016 132.018 244.646 132.018 245.458C132.018 246.251 132.205 246.872 132.578 247.32C132.961 247.759 133.483 247.978 134.146 247.978Z" fill="var(--subtle-foreground)" /> + <path d="M121.524 251.869L123.204 247.991L123.484 247.543L125.71 241.859H127.124L122.938 251.869H121.524ZM123.19 249.013L120.096 241.859H121.594L124.226 248.299L123.19 249.013Z" fill="var(--subtle-foreground)" /> + <path d="M108.263 249V241.86H109.565V243.54H109.635V249H108.263ZM113.079 249V244.31C113.079 243.815 112.962 243.451 112.729 243.218C112.505 242.984 112.178 242.868 111.749 242.868C111.347 242.868 110.983 242.966 110.657 243.162C110.339 243.348 110.087 243.61 109.901 243.946C109.723 244.272 109.635 244.66 109.635 245.108L109.453 243.442C109.686 242.91 110.041 242.49 110.517 242.182C110.993 241.874 111.525 241.72 112.113 241.72C112.813 241.72 113.373 241.925 113.793 242.336C114.222 242.746 114.437 243.288 114.437 243.96V249H113.079ZM117.895 249V244.31C117.895 243.815 117.778 243.451 117.545 243.218C117.311 242.984 116.98 242.868 116.551 242.868C116.159 242.868 115.799 242.966 115.473 243.162C115.155 243.348 114.903 243.61 114.717 243.946C114.53 244.272 114.437 244.66 114.437 245.108L114.157 243.442C114.39 242.91 114.754 242.49 115.249 242.182C115.743 241.874 116.289 241.72 116.887 241.72C117.596 241.72 118.165 241.925 118.595 242.336C119.033 242.746 119.253 243.302 119.253 244.002V249H117.895Z" fill="var(--subtle-foreground)" /> + <path d="M103.056 249.125C102.225 249.125 101.619 248.938 101.236 248.565C100.863 248.182 100.676 247.622 100.676 246.885V240.207L102.034 239.703V246.885C102.034 247.268 102.132 247.552 102.328 247.739C102.533 247.916 102.855 248.005 103.294 248.005C103.471 248.005 103.625 247.991 103.756 247.963C103.887 247.935 103.999 247.902 104.092 247.865V248.985C103.999 249.032 103.859 249.064 103.672 249.083C103.485 249.111 103.28 249.125 103.056 249.125ZM99.2339 242.965V241.859H104.092V242.965H99.2339Z" fill="var(--subtle-foreground)" /> + <path d="M95.4665 249.14C94.7945 249.14 94.1971 248.981 93.6745 248.664C93.1518 248.337 92.7365 247.894 92.4285 247.334C92.1298 246.764 91.9805 246.12 91.9805 245.402C91.9805 244.683 92.1298 244.048 92.4285 243.498C92.7365 242.947 93.1518 242.513 93.6745 242.196C94.1971 241.878 94.7945 241.72 95.4665 241.72C96.0171 241.72 96.5118 241.827 96.9505 242.042C97.3985 242.247 97.7671 242.541 98.0565 242.924C98.3458 243.306 98.5278 243.754 98.6025 244.268H97.3145C97.2211 243.838 97.0158 243.493 96.6985 243.232C96.3811 242.97 95.9798 242.84 95.4945 242.84C95.0651 242.84 94.6918 242.947 94.3745 243.162C94.0665 243.367 93.8238 243.666 93.6465 244.058C93.4785 244.44 93.3945 244.888 93.3945 245.402C93.3945 245.924 93.4785 246.382 93.6465 246.774C93.8238 247.166 94.0711 247.469 94.3885 247.684C94.7058 247.898 95.0745 248.006 95.4945 248.006C95.9705 248.006 96.3718 247.88 96.6985 247.628C97.0251 247.366 97.2351 247.021 97.3285 246.592H98.6165C98.5325 247.096 98.3411 247.544 98.0425 247.936C97.7531 248.318 97.3845 248.617 96.9365 248.832C96.4978 249.037 96.0078 249.14 95.4665 249.14Z" fill="var(--subtle-foreground)" /> + <path d="M87.5941 249.14C86.8848 249.14 86.2594 248.981 85.7181 248.664C85.1768 248.337 84.7521 247.894 84.4441 247.334C84.1454 246.774 83.9961 246.13 83.9961 245.402C83.9961 244.674 84.1454 244.034 84.4441 243.484C84.7521 242.924 85.1768 242.49 85.7181 242.182C86.2594 241.874 86.8848 241.72 87.5941 241.72C88.2754 241.72 88.8681 241.864 89.3721 242.154C89.8761 242.443 90.2634 242.854 90.5341 243.386C90.8141 243.908 90.9541 244.524 90.9541 245.234C90.9541 245.346 90.9494 245.448 90.9401 245.542C90.9308 245.635 90.9168 245.733 90.8981 245.836H84.9341V244.786H89.9601L89.5961 245.234C89.5961 244.45 89.4188 243.852 89.0641 243.442C88.7188 243.022 88.2288 242.812 87.5941 242.812C86.9221 242.812 86.3854 243.045 85.9841 243.512C85.5828 243.969 85.3821 244.599 85.3821 245.402C85.3821 246.223 85.5828 246.872 85.9841 247.348C86.3854 247.814 86.9361 248.048 87.6361 248.048C88.0561 248.048 88.4248 247.959 88.7421 247.782C89.0688 247.604 89.3068 247.343 89.4561 246.998H90.7441C90.5108 247.66 90.1188 248.183 89.5681 248.566C89.0174 248.948 88.3594 249.14 87.5941 249.14Z" fill="var(--subtle-foreground)" /> + <path d="M76.1064 249V241.86H77.4084V243.54H77.4784V249H76.1064ZM81.1884 249V244.38C81.1884 243.876 81.0578 243.498 80.7964 243.246C80.5444 242.994 80.1711 242.868 79.6764 242.868C79.2471 242.868 78.8644 242.966 78.5284 243.162C78.2018 243.348 77.9451 243.614 77.7584 243.96C77.5718 244.296 77.4784 244.688 77.4784 245.136L77.2964 243.442C77.5298 242.91 77.8891 242.49 78.3744 242.182C78.8691 241.874 79.4384 241.72 80.0824 241.72C80.8384 241.72 81.4358 241.934 81.8744 242.364C82.3224 242.793 82.5464 243.358 82.5464 244.058V249H81.1884Z" fill="var(--subtle-foreground)" /> + <path d="M67.6572 249V241.86H68.9592V243.54H69.0292V249H67.6572ZM72.7392 249V244.38C72.7392 243.876 72.6086 243.498 72.3472 243.246C72.0952 242.994 71.7219 242.868 71.2272 242.868C70.7979 242.868 70.4152 242.966 70.0792 243.162C69.7526 243.348 69.4959 243.614 69.3092 243.96C69.1226 244.296 69.0292 244.688 69.0292 245.136L68.8472 243.442C69.0806 242.91 69.4399 242.49 69.9252 242.182C70.4199 241.874 70.9892 241.72 71.6332 241.72C72.3892 241.72 72.9866 241.934 73.4252 242.364C73.8732 242.793 74.0972 243.358 74.0972 244.058V249H72.7392Z" fill="var(--subtle-foreground)" /> + <path d="M62.4935 249.14C61.7655 249.14 61.1262 248.981 60.5755 248.664C60.0342 248.337 59.6095 247.894 59.3015 247.334C59.0028 246.764 58.8535 246.12 58.8535 245.402C58.8535 244.674 59.0075 244.034 59.3155 243.484C59.6235 242.933 60.0482 242.504 60.5895 242.196C61.1308 241.878 61.7655 241.72 62.4935 241.72C63.2215 241.72 63.8562 241.878 64.3975 242.196C64.9388 242.504 65.3588 242.933 65.6575 243.484C65.9655 244.034 66.1195 244.674 66.1195 245.402C66.1195 246.12 65.9655 246.764 65.6575 247.334C65.3588 247.894 64.9388 248.337 64.3975 248.664C63.8562 248.981 63.2215 249.14 62.4935 249.14ZM62.4935 248.006C62.9322 248.006 63.3195 247.898 63.6555 247.684C63.9915 247.46 64.2528 247.152 64.4395 246.76C64.6262 246.368 64.7195 245.91 64.7195 245.388C64.7195 244.613 64.5142 243.997 64.1035 243.54C63.6928 243.073 63.1562 242.84 62.4935 242.84C61.8308 242.84 61.2942 243.073 60.8835 243.54C60.4728 243.997 60.2675 244.613 60.2675 245.388C60.2675 245.91 60.3608 246.368 60.5475 246.76C60.7342 247.152 60.9955 247.46 61.3315 247.684C61.6675 247.898 62.0548 248.006 62.4935 248.006Z" fill="var(--subtle-foreground)" /> + <path d="M53.4739 249.139C52.7739 249.139 52.1252 249.013 51.5279 248.761C50.9399 248.5 50.4265 248.141 49.9879 247.683C49.5585 247.217 49.2225 246.666 48.9799 246.031C48.7465 245.397 48.6299 244.701 48.6299 243.945C48.6299 243.189 48.7465 242.499 48.9799 241.873C49.2225 241.239 49.5585 240.693 49.9879 240.235C50.4172 239.769 50.9259 239.409 51.5139 239.157C52.1112 238.905 52.7599 238.779 53.4599 238.779C54.2345 238.779 54.9299 238.938 55.5459 239.255C56.1712 239.563 56.6799 240.002 57.0719 240.571C57.4732 241.131 57.7205 241.789 57.8139 242.545H56.4279C56.3159 241.743 55.9939 241.117 55.4619 240.669C54.9392 240.212 54.2765 239.983 53.4739 239.983C52.7925 239.983 52.1952 240.151 51.6819 240.487C51.1685 240.814 50.7672 241.276 50.4779 241.873C50.1979 242.461 50.0579 243.152 50.0579 243.945C50.0579 244.739 50.1979 245.434 50.4779 246.031C50.7672 246.629 51.1685 247.095 51.6819 247.431C52.2045 247.767 52.8019 247.935 53.4739 247.935C54.2765 247.935 54.9439 247.707 55.4759 247.249C56.0079 246.792 56.3299 246.167 56.4419 245.373H57.8419C57.7392 246.129 57.4872 246.792 57.0859 247.361C56.6939 247.921 56.1899 248.36 55.5739 248.677C54.9579 248.985 54.2579 249.139 53.4739 249.139Z" fill="var(--subtle-foreground)" /> + <rect x="48.5" y="276.5" width="115" height="23" rx="11.5" stroke="var(--subtle-foreground)" stroke-dasharray="1 3" /> + <path d="M62.499 285.563C62.6591 285.522 62.827 285.5 63 285.5C64.1046 285.5 65 286.395 65 287.5C65 288.605 64.1046 289.5 63 289.5C62.8225 289.5 62.6505 289.477 62.4867 289.433M62.499 285.563C62.4996 285.542 62.5 285.521 62.5 285.5C62.5 284.395 61.6046 283.5 60.5 283.5C59.5412 283.5 58.74 284.175 58.5452 285.075M62.499 285.563C62.4884 285.904 62.3927 286.223 62.2324 286.5M62.4867 289.433C62.4955 289.374 62.5 289.312 62.5 289.25C62.5 288.645 62.0706 288.141 61.5 288.025M62.4867 289.433C62.3979 290.037 61.878 290.5 61.25 290.5H61C59.8954 290.5 59 291.395 59 292.5M59.7325 286C59.4749 285.555 59.0508 285.218 58.5452 285.075C58.3719 285.026 58.189 285 58 285C56.8954 285 56 285.895 56 287C56 287.194 56.0277 287.382 56.0794 287.56C56.1246 287.715 56.1882 287.863 56.2676 288M56.0794 287.56C55.4557 287.742 55 288.318 55 289C55 289.828 55.6716 290.5 56.5 290.5C57.1531 290.5 57.7087 290.083 57.9146 289.5" stroke="var(--subtle-foreground)" stroke-linecap="round" stroke-linejoin="round" /> + <path d="M59.919 287.872C59.6638 288.118 59.2275 288.16 58.8545 287.95C58.4816 287.741 58.2911 287.346 58.3686 287" stroke="var(--subtle-foreground)" stroke-linecap="round" /> + <path d="M151.764 294.579C151.004 294.579 150.376 294.407 149.88 294.063C149.384 293.719 149.088 293.259 148.992 292.683H150.096C150.176 292.963 150.356 293.183 150.636 293.343C150.924 293.503 151.308 293.583 151.788 293.583C152.396 293.583 152.848 293.427 153.144 293.115C153.448 292.811 153.6 292.359 153.6 291.759V290.439L153.72 290.463C153.592 290.927 153.328 291.295 152.928 291.567C152.528 291.839 152.048 291.975 151.488 291.975C150.944 291.975 150.468 291.847 150.06 291.591C149.66 291.327 149.348 290.967 149.124 290.511C148.9 290.047 148.788 289.507 148.788 288.891C148.788 288.267 148.904 287.719 149.136 287.247C149.368 286.775 149.692 286.411 150.108 286.155C150.532 285.899 151.024 285.771 151.584 285.771C152.16 285.771 152.636 285.911 153.012 286.191C153.396 286.463 153.644 286.855 153.756 287.367L153.66 287.379V285.879H154.776V291.747C154.776 292.627 154.508 293.319 153.972 293.823C153.436 294.327 152.7 294.579 151.764 294.579ZM151.824 291.003C152.376 291.003 152.816 290.811 153.144 290.427C153.48 290.043 153.648 289.515 153.648 288.843C153.648 288.187 153.48 287.671 153.144 287.295C152.816 286.919 152.372 286.731 151.812 286.731C151.252 286.731 150.808 286.923 150.48 287.307C150.152 287.683 149.988 288.207 149.988 288.879C149.988 289.535 150.152 290.055 150.48 290.439C150.816 290.815 151.264 291.003 151.824 291.003Z" fill="var(--subtle-foreground)" /> + <path d="M142.001 292V285.88H143.117V287.32H143.177V292H142.001ZM146.357 292V288.04C146.357 287.608 146.245 287.284 146.021 287.068C145.805 286.852 145.485 286.744 145.061 286.744C144.693 286.744 144.365 286.828 144.077 286.996C143.797 287.156 143.577 287.384 143.417 287.68C143.257 287.968 143.177 288.304 143.177 288.688L143.021 287.236C143.221 286.78 143.529 286.42 143.945 286.156C144.369 285.892 144.857 285.76 145.409 285.76C146.057 285.76 146.569 285.944 146.945 286.312C147.329 286.68 147.521 287.164 147.521 287.764V292H146.357Z" fill="var(--subtle-foreground)" /> + <path d="M139.025 292V285.88H140.201V292H139.025ZM138.953 284.632V283.3H140.261V284.632H138.953Z" fill="var(--subtle-foreground)" /> + <path d="M132.568 292V283.18H133.744V292H132.568ZM136.552 292L133.468 288.82L136.444 285.88H137.956L134.584 289.096L134.656 288.532L138.076 292H136.552Z" fill="var(--subtle-foreground)" /> + <path d="M125.326 292V285.88H126.442V287.32H126.502V292H125.326ZM129.682 292V288.04C129.682 287.608 129.57 287.284 129.346 287.068C129.13 286.852 128.81 286.744 128.386 286.744C128.018 286.744 127.69 286.828 127.402 286.996C127.122 287.156 126.902 287.384 126.742 287.68C126.582 287.968 126.502 288.304 126.502 288.688L126.346 287.236C126.546 286.78 126.854 286.42 127.27 286.156C127.694 285.892 128.182 285.76 128.734 285.76C129.382 285.76 129.894 285.944 130.27 286.312C130.654 286.68 130.846 287.164 130.846 287.764V292H129.682Z" fill="var(--subtle-foreground)" /> + <path d="M122.349 292V285.88H123.525V292H122.349ZM122.277 284.632V283.3H123.585V284.632H122.277Z" fill="var(--subtle-foreground)" /> + <path d="M115.13 291.999V283.359H116.306V291.999H115.13ZM119.486 291.999V288.039C119.486 287.607 119.374 287.283 119.15 287.067C118.934 286.851 118.614 286.743 118.19 286.743C117.822 286.743 117.494 286.827 117.206 286.995C116.926 287.155 116.706 287.383 116.546 287.679C116.386 287.967 116.306 288.303 116.306 288.687L116.15 287.235C116.35 286.779 116.658 286.419 117.074 286.155C117.498 285.891 117.986 285.759 118.538 285.759C119.186 285.759 119.698 285.943 120.074 286.311C120.458 286.679 120.65 287.163 120.65 287.763V291.999H119.486Z" fill="var(--subtle-foreground)" /> + <path d="M109.703 291.999V284.355H106.775V283.359H113.831V284.355H110.903V291.999H109.703Z" fill="var(--subtle-foreground)" /> + <path d="M100.756 292.108C100.18 292.108 99.7002 291.972 99.3162 291.7C98.9322 291.42 98.6802 291.028 98.5602 290.524L98.7282 290.5V294.46H97.5522V285.88H98.6682V287.284L98.5482 287.26C98.6842 286.788 98.9602 286.42 99.3762 286.156C99.7922 285.892 100.28 285.76 100.84 285.76C101.384 285.76 101.856 285.892 102.256 286.156C102.664 286.42 102.98 286.788 103.204 287.26C103.428 287.732 103.54 288.284 103.54 288.916C103.54 289.556 103.424 290.116 103.192 290.596C102.96 291.076 102.636 291.448 102.22 291.712C101.804 291.976 101.316 292.108 100.756 292.108ZM100.516 291.124C101.084 291.124 101.528 290.928 101.848 290.536C102.176 290.144 102.34 289.604 102.34 288.916C102.34 288.236 102.176 287.704 101.848 287.32C101.52 286.936 101.076 286.744 100.516 286.744C99.9562 286.744 99.5082 286.94 99.1722 287.332C98.8442 287.724 98.6802 288.264 98.6802 288.952C98.6802 289.624 98.8442 290.156 99.1722 290.548C99.5082 290.932 99.9562 291.124 100.516 291.124Z" fill="var(--subtle-foreground)" /> + <path d="M93.3477 292.12C92.7397 292.12 92.2037 291.984 91.7397 291.712C91.2757 291.432 90.9117 291.052 90.6477 290.572C90.3917 290.092 90.2637 289.54 90.2637 288.916C90.2637 288.292 90.3917 287.744 90.6477 287.272C90.9117 286.792 91.2757 286.42 91.7397 286.156C92.2037 285.892 92.7397 285.76 93.3477 285.76C93.9317 285.76 94.4397 285.884 94.8717 286.132C95.3037 286.38 95.6357 286.732 95.8677 287.188C96.1077 287.636 96.2277 288.164 96.2277 288.772C96.2277 288.868 96.2237 288.956 96.2157 289.036C96.2077 289.116 96.1957 289.2 96.1797 289.288H91.0677V288.388H95.3757L95.0637 288.772C95.0637 288.1 94.9117 287.588 94.6077 287.236C94.3117 286.876 93.8917 286.696 93.3477 286.696C92.7717 286.696 92.3117 286.896 91.9677 287.296C91.6237 287.688 91.4517 288.228 91.4517 288.916C91.4517 289.62 91.6237 290.176 91.9677 290.584C92.3117 290.984 92.7837 291.184 93.3837 291.184C93.7437 291.184 94.0597 291.108 94.3317 290.956C94.6117 290.804 94.8157 290.58 94.9437 290.284H96.0477C95.8477 290.852 95.5117 291.3 95.0397 291.628C94.5677 291.956 94.0037 292.12 93.3477 292.12Z" fill="var(--subtle-foreground)" /> + <path d="M86.5039 292.12C85.8959 292.12 85.3599 291.984 84.8959 291.712C84.4319 291.432 84.0679 291.052 83.8039 290.572C83.5479 290.092 83.4199 289.54 83.4199 288.916C83.4199 288.292 83.5479 287.744 83.8039 287.272C84.0679 286.792 84.4319 286.42 84.8959 286.156C85.3599 285.892 85.8959 285.76 86.5039 285.76C87.0879 285.76 87.5959 285.884 88.0279 286.132C88.4599 286.38 88.7919 286.732 89.0239 287.188C89.2639 287.636 89.3839 288.164 89.3839 288.772C89.3839 288.868 89.3799 288.956 89.3719 289.036C89.3639 289.116 89.3519 289.2 89.3359 289.288H84.2239V288.388H88.5319L88.2199 288.772C88.2199 288.1 88.0679 287.588 87.7639 287.236C87.4679 286.876 87.0479 286.696 86.5039 286.696C85.9279 286.696 85.4679 286.896 85.1239 287.296C84.7799 287.688 84.6079 288.228 84.6079 288.916C84.6079 289.62 84.7799 290.176 85.1239 290.584C85.4679 290.984 85.9399 291.184 86.5399 291.184C86.8999 291.184 87.2159 291.108 87.4879 290.956C87.7679 290.804 87.9719 290.58 88.0999 290.284H89.2039C89.0039 290.852 88.6679 291.3 88.1959 291.628C87.7239 291.956 87.1599 292.12 86.5039 292.12Z" fill="var(--subtle-foreground)" /> + <path d="M75.6438 291.999V290.991H77.9358C78.6318 290.991 79.2278 290.851 79.7238 290.571C80.2198 290.283 80.5998 289.887 80.8638 289.383C81.1358 288.879 81.2718 288.295 81.2718 287.631C81.2718 286.975 81.1398 286.403 80.8758 285.915C80.6118 285.419 80.2318 285.039 79.7358 284.775C79.2398 284.503 78.6398 284.367 77.9358 284.367H75.6558V283.359H77.9358C78.8558 283.359 79.6558 283.535 80.3358 283.887C81.0238 284.239 81.5558 284.739 81.9318 285.387C82.3078 286.027 82.4958 286.783 82.4958 287.655C82.4958 288.519 82.3038 289.279 81.9198 289.935C81.5438 290.583 81.0158 291.091 80.3358 291.459C79.6558 291.819 78.8598 291.999 77.9478 291.999H75.6438ZM74.9238 291.999V283.359H76.1238V291.999H74.9238Z" fill="var(--subtle-foreground)" /> + </g> + </g> + </g> + <mask id="docs-cta_illustration-mask2_25523_22528" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="596" height="380"> + <rect width="596" height="380" fill="url(#docs-cta_illustration-paint2_linear_25523_22528)" /> + </mask> + <g mask="url(#docs-cta_illustration-mask2_25523_22528)"> + <g> + <g clip-path="url(#docs-cta_illustration-clip2_25523_22528)"> + <rect width="262" height="380" transform="translate(334)" fill="var(--background)" /> + <rect x="358.5" y="200.5" width="213" height="55" rx="7.5" fill="var(--background)" /> + <rect x="358.5" y="200.5" width="213" height="55" rx="7.5" stroke="var(--border-strong)" stroke-dasharray="1 3" /> + <path d="M382.385 230.321C381.442 230.882 378.969 232.029 380.475 233.464C381.211 234.165 382.03 234.666 383.061 234.666H388.939C389.97 234.666 390.789 234.165 391.525 233.464C393.031 232.029 390.558 230.882 389.615 230.321C387.403 229.004 384.597 229.004 382.385 230.321Z" fill="var(--background)" /> + <path d="M389 224.333C389 225.99 387.657 227.333 386 227.333C384.343 227.333 383 225.99 383 224.333C383 222.676 384.343 221.333 386 221.333C387.657 221.333 389 222.676 389 224.333Z" fill="var(--background)" /> + <path d="M382.573 233.608C382.726 233.646 382.888 233.666 383.061 233.666V234.666C382.807 234.666 382.566 234.635 382.336 234.579L382.573 233.608ZM383.551 233.666V234.666H383.061V233.666H383.551ZM385.51 233.666V234.666H384.53V233.666H385.51ZM387.47 233.666V234.666H386.49V233.666H387.47ZM388.939 233.666V234.666H388.449V233.666H388.939ZM389.663 234.579C389.492 234.621 389.314 234.649 389.13 234.66L388.939 234.666V233.666C389.112 233.666 389.274 233.646 389.427 233.608L389.663 234.579ZM381.009 232.553C381.026 232.585 381.069 232.649 381.165 232.74C381.322 232.89 381.476 233.022 381.63 233.137L381.032 233.938C380.84 233.794 380.655 233.635 380.476 233.464C380.32 233.315 380.208 233.169 380.131 233.027L380.272 232.951L381.009 232.553ZM391.868 233.027C391.791 233.169 391.68 233.316 391.524 233.464C391.344 233.635 391.159 233.794 390.967 233.938L390.37 233.137C390.524 233.022 390.678 232.89 390.835 232.74C390.931 232.649 390.974 232.585 390.991 232.553L391.868 233.027ZM380.96 231.19L381.551 231.997C381.27 232.203 381.105 232.373 381.03 232.495L380.178 231.973C380.31 231.757 380.505 231.556 380.73 231.369L380.96 231.19ZM391.039 231.19C391.359 231.424 391.646 231.687 391.821 231.973L391.396 232.234L391.396 232.233L390.97 232.495C390.895 232.373 390.73 232.203 390.449 231.997L390.758 231.576L391.039 231.19ZM382.801 230.093L382.922 230.333L383.252 230.984C383.132 231.045 383.013 231.11 382.896 231.18C382.751 231.266 382.564 231.371 382.397 231.466L381.902 230.598C382.084 230.494 382.25 230.401 382.385 230.32C382.521 230.239 382.659 230.163 382.8 230.092L382.801 230.093ZM389.199 230.092C389.34 230.163 389.479 230.239 389.615 230.32C389.75 230.401 389.915 230.494 390.097 230.598L389.603 231.466C389.436 231.371 389.249 231.266 389.104 231.18C388.987 231.11 388.868 231.045 388.748 230.984L389.199 230.092ZM384.637 229.719L384.784 230.454C384.519 230.508 384.257 230.579 384 230.668L383.824 230.162L383.673 229.723C383.972 229.619 384.278 229.536 384.587 229.474L384.637 229.719ZM387.412 229.474C387.721 229.536 388.027 229.619 388.326 229.723L388.176 230.162L388 230.668C387.743 230.579 387.481 230.508 387.216 230.454L387.412 229.474ZM385.526 229.349C385.841 229.328 386.158 229.328 386.473 229.349L386.407 230.347C386.136 230.329 385.864 230.329 385.593 230.347L385.526 229.349ZM385.608 226.295C385.734 226.32 385.865 226.333 386 226.333V227.333C385.799 227.333 385.604 227.313 385.414 227.275L385.608 226.295ZM386.585 227.275C386.396 227.313 386.2 227.333 386 227.333V226.333C386.135 226.333 386.266 226.32 386.392 226.295L386.585 227.275ZM384.336 225.443C384.482 225.662 384.671 225.851 384.89 225.997L384.333 226.827C384.006 226.608 383.724 226.326 383.505 225.999L384.336 225.443ZM388.494 225.999C388.275 226.326 387.993 226.608 387.666 226.827L387.11 225.997C387.329 225.851 387.518 225.662 387.664 225.443L388.494 225.999ZM383 224.333C383 224.133 383.019 223.937 383.057 223.747L384.038 223.941C384.013 224.067 384 224.198 384 224.333C384 224.468 384.013 224.599 384.038 224.725L383.057 224.918C383.019 224.729 383 224.533 383 224.333ZM389 224.333C389 224.533 388.98 224.729 388.942 224.918L387.962 224.725C387.987 224.599 388 224.468 388 224.333C388 224.198 387.987 224.067 387.962 223.941L388.942 223.747C388.98 223.937 389 224.132 389 224.333ZM384.89 222.669C384.671 222.815 384.482 223.004 384.336 223.223L383.505 222.666C383.724 222.339 384.006 222.057 384.333 221.838L384.89 222.669ZM387.666 221.838C387.993 222.057 388.275 222.339 388.494 222.666L387.664 223.223C387.518 223.004 387.329 222.815 387.11 222.669L387.666 221.838ZM386 221.333C386.2 221.333 386.396 221.352 386.585 221.39L386.392 222.371C386.266 222.346 386.135 222.333 386 222.333C385.865 222.333 385.734 222.346 385.608 222.371L385.414 221.39C385.604 221.352 385.8 221.333 386 221.333Z" fill="var(--subtle-foreground)" /> + <path d="M386 243V244C385.825 244 385.65 243.996 385.476 243.99L385.491 243.492L385.508 242.992C385.671 242.997 385.835 243 386 243ZM386.509 243.492L386.523 243.99C386.35 243.996 386.175 244 386 244V243C386.165 243 386.329 242.997 386.492 242.992L386.509 243.492ZM382.592 242.611C382.909 242.685 383.23 242.749 383.555 242.802L383.393 243.787C383.046 243.73 382.704 243.663 382.365 243.584L382.592 242.611ZM389.634 243.584C389.295 243.663 388.953 243.73 388.606 243.787L388.445 242.802C388.77 242.749 389.091 242.685 389.408 242.611L389.634 243.584ZM379.809 241.667C380.106 241.802 380.408 241.927 380.715 242.043L380.362 242.978C380.034 242.854 379.711 242.721 379.394 242.577L379.809 241.667ZM392.605 242.577C392.288 242.721 391.965 242.854 391.637 242.978L391.538 242.714L391.285 242.043C391.592 241.927 391.894 241.802 392.191 241.667L392.605 242.577ZM377.263 240.194C377.528 240.385 377.801 240.567 378.079 240.74L377.55 241.588C377.253 241.403 376.963 241.208 376.68 241.005L377.263 240.194ZM395.319 241.005C395.036 241.208 394.746 241.403 394.449 241.588L393.921 240.74C394.199 240.567 394.472 240.385 394.737 240.194L395.319 241.005ZM375.052 238.254C375.276 238.493 375.507 238.724 375.746 238.948L375.403 239.312L375.404 239.313L375.062 239.677C374.808 239.438 374.561 239.191 374.322 238.937L375.052 238.254ZM397.312 238.596L397.677 238.937C397.438 239.191 397.191 239.438 396.937 239.677L396.596 239.313L396.254 238.948C396.493 238.724 396.724 238.493 396.948 238.254L397.312 238.596ZM373.26 235.921C373.433 236.199 373.615 236.472 373.806 236.737L372.994 237.319C372.791 237.036 372.596 236.746 372.411 236.449L373.26 235.921ZM398.74 235.921L399.588 236.449C399.403 236.746 399.208 237.036 399.005 237.319L398.194 236.737C398.385 236.472 398.567 236.199 398.74 235.921ZM371.957 233.285C372.073 233.592 372.198 233.894 372.333 234.191L371.422 234.605C371.278 234.288 371.145 233.965 371.021 233.637L371.957 233.285ZM400.978 233.637C400.854 233.965 400.721 234.288 400.577 234.605L399.667 234.191C399.802 233.894 399.927 233.592 400.043 233.285L400.978 233.637ZM371.198 230.445C371.251 230.77 371.315 231.091 371.389 231.408L370.415 231.634C370.336 231.295 370.269 230.953 370.212 230.606L371.198 230.445ZM401.787 230.606C401.73 230.953 401.663 231.295 401.584 231.634L400.611 231.408C400.685 231.091 400.749 230.77 400.802 230.445L401.787 230.606ZM370 228C370 227.862 370.001 227.724 370.005 227.587L370.009 227.476L370.508 227.491L371.008 227.508C371.003 227.671 371 227.835 371 228C371 228.165 371.003 228.329 371.008 228.492L370.507 228.508L370.508 228.509L370.009 228.523C370.003 228.35 370 228.175 370 228ZM401.99 227.476C401.996 227.65 402 227.825 402 228C402 228.175 401.996 228.35 401.99 228.523L401.492 228.509L400.992 228.492C400.997 228.329 401 228.165 401 228C401 227.835 400.997 227.671 400.992 227.508L401.99 227.476ZM370.415 224.365L371.389 224.592C371.315 224.909 371.251 225.23 371.198 225.555L370.212 225.393C370.267 225.054 370.334 224.718 370.41 224.387L370.415 224.365ZM401.584 224.365C401.663 224.704 401.73 225.046 401.787 225.393L400.802 225.555C400.749 225.23 400.685 224.909 400.611 224.592L401.584 224.365ZM371.423 221.395L372.333 221.809C372.198 222.106 372.073 222.408 371.957 222.715L371.021 222.362C371.145 222.034 371.278 221.711 371.422 221.394L371.423 221.395ZM400.577 221.394C400.721 221.711 400.854 222.034 400.978 222.362L400.043 222.715C399.927 222.408 399.802 222.106 399.667 221.809L400.577 221.394ZM373.806 219.263C373.615 219.528 373.433 219.801 373.26 220.079L372.835 219.813L372.411 219.55C372.596 219.253 372.791 218.963 372.994 218.68L373.806 219.263ZM399.005 218.68C399.208 218.963 399.403 219.253 399.588 219.55L399.165 219.814L399.164 219.813L398.74 220.079C398.567 219.801 398.385 219.528 398.194 219.263L399.005 218.68ZM375.746 217.052C375.507 217.276 375.276 217.507 375.052 217.746L374.675 217.394L374.322 217.062C374.561 216.808 374.808 216.561 375.062 216.322L375.746 217.052ZM396.937 216.322C397.191 216.561 397.438 216.808 397.677 217.062L397.325 217.394L396.948 217.746C396.724 217.507 396.493 217.276 396.254 217.052L396.937 216.322ZM378.079 215.26C377.801 215.433 377.528 215.615 377.263 215.806L376.68 214.994C376.963 214.791 377.253 214.596 377.55 214.411L378.079 215.26ZM394.449 214.411C394.746 214.596 395.036 214.791 395.319 214.994L394.737 215.806C394.472 215.615 394.199 215.433 393.921 215.26L394.449 214.411ZM380.715 213.957C380.408 214.073 380.106 214.198 379.809 214.333L379.395 213.423L379.394 213.422C379.711 213.278 380.034 213.145 380.362 213.021L380.715 213.957ZM391.637 213.021C391.965 213.145 392.288 213.278 392.605 213.422L392.191 214.333C391.894 214.198 391.592 214.073 391.285 213.957L391.637 213.021ZM383.555 213.198C383.23 213.251 382.909 213.315 382.592 213.389L382.365 212.415C382.704 212.336 383.046 212.269 383.393 212.212L383.555 213.198ZM388.606 212.212C388.953 212.269 389.295 212.336 389.634 212.415L389.408 213.389C389.091 213.315 388.77 213.251 388.445 213.198L388.606 212.212ZM386 212C386.175 212 386.35 212.003 386.523 212.009L386.519 212.211L386.492 213.008C386.329 213.003 386.165 213 386 213C385.835 213 385.671 213.003 385.508 213.008L385.481 212.211L385.476 212.009C385.65 212.003 385.825 212 386 212Z" fill="var(--subtle-foreground)" /> + <path d="M410 221C410 219.343 411.343 218 413 218H439C440.657 218 442 219.343 442 221C442 222.657 440.657 224 439 224H413C411.343 224 410 222.657 410 221Z" fill="var(--background)" /> + <path d="M412.737 222.983C412.823 222.995 412.911 223 413 223V224C412.867 224 412.737 223.99 412.608 223.974L412.737 222.983ZM413.481 223V224H413V223H413.481ZM416.37 223V224H415.407V223H416.37ZM419.259 223V224H418.296V223H419.259ZM422.148 223V224H421.186V223H422.148ZM425.037 223V224H424.074V223H425.037ZM427.926 223V224H426.963V223H427.926ZM430.814 223V224H429.852V223H430.814ZM433.704 223V224H432.741V223H433.704ZM436.593 223V224H435.63V223H436.593ZM439 223V224H438.519V223H439ZM439.327 223.479L439.377 223.865L439.391 223.974C439.263 223.99 439.132 224 439 224V223C439.089 223 439.177 222.995 439.263 222.983L439.327 223.479ZM411.413 222.217C411.52 222.356 411.644 222.48 411.783 222.587L411.175 223.377C410.967 223.217 410.782 223.032 410.622 222.824L411.413 222.217ZM441.377 222.824C441.217 223.032 441.032 223.217 440.824 223.377L440.217 222.587C440.356 222.48 440.48 222.356 440.587 222.217L441.377 222.824ZM410 221C410 220.867 410.009 220.737 410.025 220.608L410.521 220.673H410.521L411.017 220.737C411.005 220.823 411 220.911 411 221C411 221.089 411.005 221.177 411.017 221.263L410.025 221.391C410.009 221.263 410 221.132 410 221ZM442 221C442 221.132 441.99 221.263 441.974 221.391L440.983 221.263C440.995 221.177 441 221.089 441 221C441 220.911 440.995 220.823 440.983 220.737L441.974 220.608C441.99 220.737 442 220.867 442 221ZM411.783 219.413C411.644 219.52 411.52 219.644 411.413 219.783L410.622 219.175C410.782 218.967 410.967 218.782 411.175 218.622L411.783 219.413ZM440.824 218.622C441.032 218.782 441.217 218.967 441.377 219.175L440.587 219.783C440.48 219.644 440.356 219.52 440.217 219.413L440.824 218.622ZM413.481 218V219H413C412.911 219 412.823 219.005 412.737 219.017L412.627 218.172L412.608 218.025C412.737 218.009 412.867 218 413 218H413.481ZM439 218C439.132 218 439.263 218.009 439.391 218.025L439.373 218.172L439.263 219.017C439.177 219.005 439.089 219 439 219H438.519V218H439ZM416.37 218V219H415.407V218H416.37ZM419.259 218V219H418.296V218H419.259ZM422.148 218V219H421.186V218H422.148ZM425.037 218V219H424.074V218H425.037ZM427.926 218V219H426.963V218H427.926ZM430.814 218V219H429.852V218H430.814ZM433.704 218V219H432.741V218H433.704ZM436.593 218V219H435.63V218H436.593Z" fill="var(--subtle-foreground)" /> + <path d="M410 235C410 233.343 411.343 232 413 232H479C480.657 232 482 233.343 482 235C482 236.657 480.657 238 479 238H413C411.343 238 410 236.657 410 235Z" fill="var(--background)" /> + <path d="M412.737 236.983C412.823 236.995 412.911 237 413 237V238C412.867 238 412.737 237.99 412.608 237.974L412.737 236.983ZM413.5 237V238H413V237H413.5ZM416.5 237V238H415.5V237H416.5ZM419.5 237V238H418.5V237H419.5ZM422.5 237V238H421.5V237H422.5ZM425.5 237V238H424.5V237H425.5ZM428.5 237V238H427.5V237H428.5ZM431.5 237V238H430.5V237H431.5ZM434.5 237V238H433.5V237H434.5ZM437.5 237V238H436.5V237H437.5ZM440.5 237V238H439.5V237H440.5ZM443.5 237V238H442.5V237H443.5ZM446.5 237V238H445.5V237H446.5ZM449.5 237V238H448.5V237H449.5ZM452.5 237V238H451.5V237H452.5ZM455.5 237V238H454.5V237H455.5ZM458.5 237V238H457.5V237H458.5ZM461.5 237V238H460.5V237H461.5ZM464.5 237V238H463.5V237H464.5ZM467.5 237V238H466.5V237H467.5ZM470.5 237V238H469.5V237H470.5ZM473.5 237V238H472.5V237H473.5ZM476.5 237V238H475.5V237H476.5ZM479 237V238H478.5V237H479ZM479.327 237.479L479.377 237.865L479.391 237.974C479.313 237.984 479.234 237.992 479.154 237.996L479 238V237C479.089 237 479.177 236.995 479.263 236.983L479.327 237.479ZM411.413 236.217C411.52 236.356 411.644 236.48 411.783 236.587L411.175 237.377C410.967 237.217 410.782 237.032 410.622 236.824L411.413 236.217ZM481.377 236.824C481.217 237.032 481.032 237.217 480.824 237.377L480.217 236.587C480.356 236.48 480.48 236.356 480.587 236.217L481.377 236.824ZM410 235C410 234.867 410.009 234.737 410.025 234.608L410.521 234.673H410.521L411.017 234.737C411.005 234.823 411 234.911 411 235C411 235.089 411.005 235.177 411.017 235.263L410.025 235.391C410.009 235.263 410 235.132 410 235ZM482 235C482 235.132 481.99 235.263 481.974 235.391L480.983 235.263C480.995 235.177 481 235.089 481 235C481 234.911 480.995 234.823 480.983 234.737L481.974 234.608C481.99 234.737 482 234.867 482 235ZM411.783 233.413C411.644 233.52 411.52 233.644 411.413 233.783L410.622 233.175C410.782 232.967 410.967 232.782 411.175 232.622L411.783 233.413ZM480.824 232.622C481.032 232.782 481.217 232.967 481.377 233.175L480.587 233.783C480.48 233.644 480.356 233.52 480.217 233.413L480.824 232.622ZM413.5 232V233H413C412.911 233 412.823 233.005 412.737 233.017L412.627 232.172L412.608 232.025C412.737 232.009 412.867 232 413 232H413.5ZM479.154 232.004C479.234 232.008 479.313 232.015 479.391 232.025L479.373 232.172L479.263 233.017C479.177 233.005 479.089 233 479 233H478.5V232H479L479.154 232.004ZM416.5 232V233H415.5V232H416.5ZM419.5 232V233H418.5V232H419.5ZM422.5 232V233H421.5V232H422.5ZM425.5 232V233H424.5V232H425.5ZM428.5 232V233H427.5V232H428.5ZM431.5 232V233H430.5V232H431.5ZM434.5 232V233H433.5V232H434.5ZM437.5 232V233H436.5V232H437.5ZM440.5 232V233H439.5V232H440.5ZM443.5 232V233H442.5V232H443.5ZM446.5 232V233H445.5V232H446.5ZM449.5 232V233H448.5V232H449.5ZM452.5 232V233H451.5V232H452.5ZM455.5 232V233H454.5V232H455.5ZM458.5 232V233H457.5V232H458.5ZM461.5 232V233H460.5V232H461.5ZM464.5 232V233H463.5V232H464.5ZM467.5 232V233H466.5V232H467.5ZM470.5 232V233H469.5V232H470.5ZM473.5 232V233H472.5V232H473.5ZM476.5 232V233H475.5V232H476.5Z" fill="var(--subtle-foreground)" /> + <path d="M536 227V228C535.824 228 535.65 227.992 535.477 227.98L535.541 226.985C535.693 226.995 535.846 227 536 227ZM536.533 227V228H536V227H536.533ZM539.733 227V228H538.667V227H539.733ZM542.934 227V228H541.866V227H542.934ZM546.134 227V228H545.066V227H546.134ZM549.333 227V228H548.267V227H549.333ZM552 227V228H551.467V227H552ZM552.522 227.98C552.35 227.992 552.176 228 552 228V227C552.154 227 552.307 226.995 552.459 226.985L552.522 227.98ZM532.904 226.28C533.176 226.415 533.459 226.532 533.749 226.631L533.428 227.575C533.095 227.462 532.772 227.329 532.461 227.175L532.904 226.28ZM555.538 227.175C555.227 227.329 554.904 227.462 554.571 227.575L554.251 226.631C554.541 226.532 554.824 226.415 555.096 226.28L555.538 227.175ZM530.736 224.615C530.938 224.845 531.155 225.062 531.385 225.264L530.727 226.013C530.464 225.782 530.217 225.535 529.986 225.272L530.736 224.615ZM558.013 225.272C557.782 225.535 557.535 225.782 557.272 226.013L556.615 225.264C556.845 225.062 557.062 224.845 557.264 224.615L558.013 225.272ZM529.369 222.251C529.468 222.541 529.585 222.824 529.72 223.096L528.824 223.538C528.67 223.227 528.537 222.904 528.424 222.571L529.369 222.251ZM559.575 222.571C559.462 222.904 559.329 223.227 559.175 223.538L558.28 223.096C558.415 222.824 558.532 222.541 558.631 222.251L559.575 222.571ZM528 220C528 219.824 528.007 219.65 528.019 219.477L529.015 219.541C529.005 219.693 529 219.846 529 220C529 220.154 529.005 220.307 529.015 220.459L528.019 220.522C528.007 220.35 528 220.176 528 220ZM560 220C560 220.176 559.992 220.35 559.98 220.522L558.985 220.459C558.995 220.307 559 220.154 559 220C559 219.846 558.995 219.693 558.985 219.541L559.98 219.477C559.992 219.65 560 219.824 560 220ZM529.72 216.904C529.585 217.176 529.468 217.459 529.369 217.749L528.424 217.428C528.537 217.095 528.67 216.772 528.824 216.461L529.72 216.904ZM559.175 216.461C559.329 216.772 559.462 217.095 559.575 217.428L558.631 217.749C558.532 217.459 558.415 217.176 558.28 216.904L559.175 216.461ZM531.385 214.736C531.155 214.938 530.938 215.155 530.736 215.385L529.986 214.727C530.217 214.464 530.464 214.217 530.727 213.986L531.385 214.736ZM557.272 213.986C557.535 214.217 557.782 214.464 558.013 214.727L557.264 215.385C557.062 215.155 556.845 214.938 556.615 214.736L557.272 213.986ZM533.749 213.369C533.459 213.468 533.176 213.585 532.904 213.72L532.461 212.824C532.772 212.67 533.095 212.537 533.428 212.424L533.749 213.369ZM554.571 212.424C554.904 212.537 555.227 212.67 555.538 212.824L555.096 213.72C554.824 213.585 554.541 213.468 554.251 213.369L554.571 212.424ZM536.533 212V213H536C535.846 213 535.693 213.005 535.541 213.015L535.477 212.019C535.65 212.007 535.824 212 536 212H536.533ZM552.412 212.011C552.449 212.013 552.486 212.016 552.522 212.019L552.459 213.015C552.307 213.005 552.154 213 552 213H551.467V212H552L552.412 212.011ZM539.733 212V213H538.667V212H539.733ZM542.934 212V213H541.866V212H542.934ZM546.134 212V213H545.066V212H546.134ZM549.333 212V213H548.267V212H549.333Z" fill="var(--subtle-foreground)" /> + <circle cx="552" cy="220" r="4.5" stroke="var(--subtle-foreground)" stroke-dasharray="1 1" /> + <rect x="358.5" y="272.5" width="213" height="55" rx="7.5" fill="var(--background)" /> + <rect x="358.5" y="272.5" width="213" height="55" rx="7.5" stroke="var(--border-strong)" stroke-dasharray="1 3" /> + <path d="M382.385 302.321C381.442 302.882 378.969 304.029 380.475 305.464C381.211 306.165 382.03 306.666 383.061 306.666H388.939C389.97 306.666 390.789 306.165 391.525 305.464C393.031 304.029 390.558 302.882 389.615 302.321C387.403 301.004 384.597 301.004 382.385 302.321Z" fill="var(--background)" /> + <path d="M389 296.333C389 297.99 387.657 299.333 386 299.333C384.343 299.333 383 297.99 383 296.333C383 294.676 384.343 293.333 386 293.333C387.657 293.333 389 294.676 389 296.333Z" fill="var(--background)" /> + <path d="M382.573 305.608C382.726 305.646 382.888 305.666 383.061 305.666V306.666C382.807 306.666 382.566 306.635 382.336 306.579L382.573 305.608ZM383.551 305.666V306.666H383.061V305.666H383.551ZM385.51 305.666V306.666H384.53V305.666H385.51ZM387.47 305.666V306.666H386.49V305.666H387.47ZM388.939 305.666V306.666H388.449V305.666H388.939ZM389.663 306.579C389.492 306.621 389.314 306.649 389.13 306.66L388.939 306.666V305.666C389.112 305.666 389.274 305.646 389.427 305.608L389.663 306.579ZM381.009 304.553C381.026 304.585 381.069 304.649 381.165 304.74C381.322 304.89 381.476 305.022 381.63 305.137L381.032 305.938C380.84 305.794 380.655 305.635 380.476 305.464C380.32 305.315 380.208 305.169 380.131 305.027L380.272 304.951L381.009 304.553ZM391.868 305.027C391.791 305.169 391.68 305.316 391.524 305.464C391.344 305.635 391.159 305.794 390.967 305.938L390.37 305.137C390.524 305.022 390.678 304.89 390.835 304.74C390.931 304.649 390.974 304.585 390.991 304.553L391.868 305.027ZM380.96 303.19L381.551 303.997C381.27 304.203 381.105 304.373 381.03 304.495L380.178 303.973C380.31 303.757 380.505 303.556 380.73 303.369L380.96 303.19ZM391.039 303.19C391.359 303.424 391.646 303.687 391.821 303.973L391.396 304.234L391.396 304.233L390.97 304.495C390.895 304.373 390.73 304.203 390.449 303.997L390.758 303.576L391.039 303.19ZM382.801 302.093L382.922 302.333L383.252 302.984C383.132 303.045 383.013 303.11 382.896 303.18C382.751 303.266 382.564 303.371 382.397 303.466L381.902 302.598C382.084 302.494 382.25 302.401 382.385 302.32C382.521 302.239 382.659 302.163 382.8 302.092L382.801 302.093ZM389.199 302.092C389.34 302.163 389.479 302.239 389.615 302.32C389.75 302.401 389.915 302.494 390.097 302.598L389.603 303.466C389.436 303.371 389.249 303.266 389.104 303.18C388.987 303.11 388.868 303.045 388.748 302.984L389.199 302.092ZM384.637 301.719L384.784 302.454C384.519 302.508 384.257 302.579 384 302.668L383.824 302.162L383.673 301.723C383.972 301.619 384.278 301.536 384.587 301.474L384.637 301.719ZM387.412 301.474C387.721 301.536 388.027 301.619 388.326 301.723L388.176 302.162L388 302.668C387.743 302.579 387.481 302.508 387.216 302.454L387.412 301.474ZM385.526 301.349C385.841 301.328 386.158 301.328 386.473 301.349L386.407 302.347C386.136 302.329 385.864 302.329 385.593 302.347L385.526 301.349ZM385.608 298.295C385.734 298.32 385.865 298.333 386 298.333V299.333C385.799 299.333 385.604 299.313 385.414 299.275L385.608 298.295ZM386.585 299.275C386.396 299.313 386.2 299.333 386 299.333V298.333C386.135 298.333 386.266 298.32 386.392 298.295L386.585 299.275ZM384.336 297.443C384.482 297.662 384.671 297.851 384.89 297.997L384.333 298.827C384.006 298.608 383.724 298.326 383.505 297.999L384.336 297.443ZM388.494 297.999C388.275 298.326 387.993 298.608 387.666 298.827L387.11 297.997C387.329 297.851 387.518 297.662 387.664 297.443L388.494 297.999ZM383 296.333C383 296.133 383.019 295.937 383.057 295.747L384.038 295.941C384.013 296.067 384 296.198 384 296.333C384 296.468 384.013 296.599 384.038 296.725L383.057 296.918C383.019 296.729 383 296.533 383 296.333ZM389 296.333C389 296.533 388.98 296.729 388.942 296.918L387.962 296.725C387.987 296.599 388 296.468 388 296.333C388 296.198 387.987 296.067 387.962 295.941L388.942 295.747C388.98 295.937 389 296.132 389 296.333ZM384.89 294.669C384.671 294.815 384.482 295.004 384.336 295.223L383.505 294.666C383.724 294.339 384.006 294.057 384.333 293.838L384.89 294.669ZM387.666 293.838C387.993 294.057 388.275 294.339 388.494 294.666L387.664 295.223C387.518 295.004 387.329 294.815 387.11 294.669L387.666 293.838ZM386 293.333C386.2 293.333 386.396 293.352 386.585 293.39L386.392 294.371C386.266 294.346 386.135 294.333 386 294.333C385.865 294.333 385.734 294.346 385.608 294.371L385.414 293.39C385.604 293.352 385.8 293.333 386 293.333Z" fill="var(--subtle-foreground)" /> + <path d="M386 315V316C385.825 316 385.65 315.996 385.476 315.99L385.491 315.492L385.508 314.992C385.671 314.997 385.835 315 386 315ZM386.509 315.492L386.523 315.99C386.35 315.996 386.175 316 386 316V315C386.165 315 386.329 314.997 386.492 314.992L386.509 315.492ZM382.592 314.611C382.909 314.685 383.23 314.749 383.555 314.802L383.393 315.787C383.046 315.73 382.704 315.663 382.365 315.584L382.592 314.611ZM389.634 315.584C389.295 315.663 388.953 315.73 388.606 315.787L388.445 314.802C388.77 314.749 389.091 314.685 389.408 314.611L389.634 315.584ZM379.809 313.667C380.106 313.802 380.408 313.927 380.715 314.043L380.362 314.978C380.034 314.854 379.711 314.721 379.394 314.577L379.809 313.667ZM392.605 314.577C392.288 314.721 391.965 314.854 391.637 314.978L391.538 314.714L391.285 314.043C391.592 313.927 391.894 313.802 392.191 313.667L392.605 314.577ZM377.263 312.194C377.528 312.385 377.801 312.567 378.079 312.74L377.55 313.588C377.253 313.403 376.963 313.208 376.68 313.005L377.263 312.194ZM395.319 313.005C395.036 313.208 394.746 313.403 394.449 313.588L393.921 312.74C394.199 312.567 394.472 312.385 394.737 312.194L395.319 313.005ZM375.052 310.254C375.276 310.493 375.507 310.724 375.746 310.948L375.403 311.312L375.404 311.313L375.062 311.677C374.808 311.438 374.561 311.191 374.322 310.937L375.052 310.254ZM397.312 310.596L397.677 310.937C397.438 311.191 397.191 311.438 396.937 311.677L396.596 311.313L396.254 310.948C396.493 310.724 396.724 310.493 396.948 310.254L397.312 310.596ZM373.26 307.921C373.433 308.199 373.615 308.472 373.806 308.737L372.994 309.319C372.791 309.036 372.596 308.746 372.411 308.449L373.26 307.921ZM398.74 307.921L399.588 308.449C399.403 308.746 399.208 309.036 399.005 309.319L398.194 308.737C398.385 308.472 398.567 308.199 398.74 307.921ZM371.957 305.285C372.073 305.592 372.198 305.894 372.333 306.191L371.422 306.605C371.278 306.288 371.145 305.965 371.021 305.637L371.957 305.285ZM400.978 305.637C400.854 305.965 400.721 306.288 400.577 306.605L399.667 306.191C399.802 305.894 399.927 305.592 400.043 305.285L400.978 305.637ZM371.198 302.445C371.251 302.77 371.315 303.091 371.389 303.408L370.415 303.634C370.336 303.295 370.269 302.953 370.212 302.606L371.198 302.445ZM401.787 302.606C401.73 302.953 401.663 303.295 401.584 303.634L400.611 303.408C400.685 303.091 400.749 302.77 400.802 302.445L401.787 302.606ZM370 300C370 299.862 370.001 299.724 370.005 299.587L370.009 299.476L370.508 299.491L371.008 299.508C371.003 299.671 371 299.835 371 300C371 300.165 371.003 300.329 371.008 300.492L370.507 300.508L370.508 300.509L370.009 300.523C370.003 300.35 370 300.175 370 300ZM401.99 299.476C401.996 299.65 402 299.825 402 300C402 300.175 401.996 300.35 401.99 300.523L401.492 300.509L400.992 300.492C400.997 300.329 401 300.165 401 300C401 299.835 400.997 299.671 400.992 299.508L401.99 299.476ZM370.415 296.365L371.389 296.592C371.315 296.909 371.251 297.23 371.198 297.555L370.212 297.393C370.267 297.054 370.334 296.718 370.41 296.387L370.415 296.365ZM401.584 296.365C401.663 296.704 401.73 297.046 401.787 297.393L400.802 297.555C400.749 297.23 400.685 296.909 400.611 296.592L401.584 296.365ZM371.423 293.395L372.333 293.809C372.198 294.106 372.073 294.408 371.957 294.715L371.021 294.362C371.145 294.034 371.278 293.711 371.422 293.394L371.423 293.395ZM400.577 293.394C400.721 293.711 400.854 294.034 400.978 294.362L400.043 294.715C399.927 294.408 399.802 294.106 399.667 293.809L400.577 293.394ZM373.806 291.263C373.615 291.528 373.433 291.801 373.26 292.079L372.835 291.813L372.411 291.55C372.596 291.253 372.791 290.963 372.994 290.68L373.806 291.263ZM399.005 290.68C399.208 290.963 399.403 291.253 399.588 291.55L399.165 291.814L399.164 291.813L398.74 292.079C398.567 291.801 398.385 291.528 398.194 291.263L399.005 290.68ZM375.746 289.052C375.507 289.276 375.276 289.507 375.052 289.746L374.675 289.394L374.322 289.062C374.561 288.808 374.808 288.561 375.062 288.322L375.746 289.052ZM396.937 288.322C397.191 288.561 397.438 288.808 397.677 289.062L397.325 289.394L396.948 289.746C396.724 289.507 396.493 289.276 396.254 289.052L396.937 288.322ZM378.079 287.26C377.801 287.433 377.528 287.615 377.263 287.806L376.68 286.994C376.963 286.791 377.253 286.596 377.55 286.411L378.079 287.26ZM394.449 286.411C394.746 286.596 395.036 286.791 395.319 286.994L394.737 287.806C394.472 287.615 394.199 287.433 393.921 287.26L394.449 286.411ZM380.715 285.957C380.408 286.073 380.106 286.198 379.809 286.333L379.395 285.423L379.394 285.422C379.711 285.278 380.034 285.145 380.362 285.021L380.715 285.957ZM391.637 285.021C391.965 285.145 392.288 285.278 392.605 285.422L392.191 286.333C391.894 286.198 391.592 286.073 391.285 285.957L391.637 285.021ZM383.555 285.198C383.23 285.251 382.909 285.315 382.592 285.389L382.365 284.415C382.704 284.336 383.046 284.269 383.393 284.212L383.555 285.198ZM388.606 284.212C388.953 284.269 389.295 284.336 389.634 284.415L389.408 285.389C389.091 285.315 388.77 285.251 388.445 285.198L388.606 284.212ZM386 284C386.175 284 386.35 284.003 386.523 284.009L386.519 284.211L386.492 285.008C386.329 285.003 386.165 285 386 285C385.835 285 385.671 285.003 385.508 285.008L385.481 284.211L385.476 284.009C385.65 284.003 385.825 284 386 284Z" fill="var(--subtle-foreground)" /> + <path d="M410 293C410 291.343 411.343 290 413 290H439C440.657 290 442 291.343 442 293C442 294.657 440.657 296 439 296H413C411.343 296 410 294.657 410 293Z" fill="var(--background)" /> + <path d="M412.737 294.983C412.823 294.995 412.911 295 413 295V296C412.867 296 412.737 295.99 412.608 295.974L412.737 294.983ZM413.481 295V296H413V295H413.481ZM416.37 295V296H415.407V295H416.37ZM419.259 295V296H418.296V295H419.259ZM422.148 295V296H421.186V295H422.148ZM425.037 295V296H424.074V295H425.037ZM427.926 295V296H426.963V295H427.926ZM430.814 295V296H429.852V295H430.814ZM433.704 295V296H432.741V295H433.704ZM436.593 295V296H435.63V295H436.593ZM439 295V296H438.519V295H439ZM439.327 295.479L439.377 295.865L439.391 295.974C439.263 295.99 439.132 296 439 296V295C439.089 295 439.177 294.995 439.263 294.983L439.327 295.479ZM411.413 294.217C411.52 294.356 411.644 294.48 411.783 294.587L411.175 295.377C410.967 295.217 410.782 295.032 410.622 294.824L411.413 294.217ZM441.377 294.824C441.217 295.032 441.032 295.217 440.824 295.377L440.217 294.587C440.356 294.48 440.48 294.356 440.587 294.217L441.377 294.824ZM410 293C410 292.867 410.009 292.737 410.025 292.608L410.521 292.673H410.521L411.017 292.737C411.005 292.823 411 292.911 411 293C411 293.089 411.005 293.177 411.017 293.263L410.025 293.391C410.009 293.263 410 293.132 410 293ZM442 293C442 293.132 441.99 293.263 441.974 293.391L440.983 293.263C440.995 293.177 441 293.089 441 293C441 292.911 440.995 292.823 440.983 292.737L441.974 292.608C441.99 292.737 442 292.867 442 293ZM411.783 291.413C411.644 291.52 411.52 291.644 411.413 291.783L410.622 291.175C410.782 290.967 410.967 290.782 411.175 290.622L411.783 291.413ZM440.824 290.622C441.032 290.782 441.217 290.967 441.377 291.175L440.587 291.783C440.48 291.644 440.356 291.52 440.217 291.413L440.824 290.622ZM413.481 290V291H413C412.911 291 412.823 291.005 412.737 291.017L412.627 290.172L412.608 290.025C412.737 290.009 412.867 290 413 290H413.481ZM439 290C439.132 290 439.263 290.009 439.391 290.025L439.373 290.172L439.263 291.017C439.177 291.005 439.089 291 439 291H438.519V290H439ZM416.37 290V291H415.407V290H416.37ZM419.259 290V291H418.296V290H419.259ZM422.148 290V291H421.186V290H422.148ZM425.037 290V291H424.074V290H425.037ZM427.926 290V291H426.963V290H427.926ZM430.814 290V291H429.852V290H430.814ZM433.704 290V291H432.741V290H433.704ZM436.593 290V291H435.63V290H436.593Z" fill="var(--subtle-foreground)" /> + <path d="M410 307C410 305.343 411.343 304 413 304H479C480.657 304 482 305.343 482 307C482 308.657 480.657 310 479 310H413C411.343 310 410 308.657 410 307Z" fill="var(--background)" /> + <path d="M412.737 308.983C412.823 308.995 412.911 309 413 309V310C412.867 310 412.737 309.99 412.608 309.974L412.737 308.983ZM413.5 309V310H413V309H413.5ZM416.5 309V310H415.5V309H416.5ZM419.5 309V310H418.5V309H419.5ZM422.5 309V310H421.5V309H422.5ZM425.5 309V310H424.5V309H425.5ZM428.5 309V310H427.5V309H428.5ZM431.5 309V310H430.5V309H431.5ZM434.5 309V310H433.5V309H434.5ZM437.5 309V310H436.5V309H437.5ZM440.5 309V310H439.5V309H440.5ZM443.5 309V310H442.5V309H443.5ZM446.5 309V310H445.5V309H446.5ZM449.5 309V310H448.5V309H449.5ZM452.5 309V310H451.5V309H452.5ZM455.5 309V310H454.5V309H455.5ZM458.5 309V310H457.5V309H458.5ZM461.5 309V310H460.5V309H461.5ZM464.5 309V310H463.5V309H464.5ZM467.5 309V310H466.5V309H467.5ZM470.5 309V310H469.5V309H470.5ZM473.5 309V310H472.5V309H473.5ZM476.5 309V310H475.5V309H476.5ZM479 309V310H478.5V309H479ZM479.327 309.479L479.377 309.865L479.391 309.974C479.313 309.984 479.234 309.992 479.154 309.996L479 310V309C479.089 309 479.177 308.995 479.263 308.983L479.327 309.479ZM411.413 308.217C411.52 308.356 411.644 308.48 411.783 308.587L411.175 309.377C410.967 309.217 410.782 309.032 410.622 308.824L411.413 308.217ZM481.377 308.824C481.217 309.032 481.032 309.217 480.824 309.377L480.217 308.587C480.356 308.48 480.48 308.356 480.587 308.217L481.377 308.824ZM410 307C410 306.867 410.009 306.737 410.025 306.608L410.521 306.673H410.521L411.017 306.737C411.005 306.823 411 306.911 411 307C411 307.089 411.005 307.177 411.017 307.263L410.025 307.391C410.009 307.263 410 307.132 410 307ZM482 307C482 307.132 481.99 307.263 481.974 307.391L480.983 307.263C480.995 307.177 481 307.089 481 307C481 306.911 480.995 306.823 480.983 306.737L481.974 306.608C481.99 306.737 482 306.867 482 307ZM411.783 305.413C411.644 305.52 411.52 305.644 411.413 305.783L410.622 305.175C410.782 304.967 410.967 304.782 411.175 304.622L411.783 305.413ZM480.824 304.622C481.032 304.782 481.217 304.967 481.377 305.175L480.587 305.783C480.48 305.644 480.356 305.52 480.217 305.413L480.824 304.622ZM413.5 304V305H413C412.911 305 412.823 305.005 412.737 305.017L412.627 304.172L412.608 304.025C412.737 304.009 412.867 304 413 304H413.5ZM479.154 304.004C479.234 304.008 479.313 304.015 479.391 304.025L479.373 304.172L479.263 305.017C479.177 305.005 479.089 305 479 305H478.5V304H479L479.154 304.004ZM416.5 304V305H415.5V304H416.5ZM419.5 304V305H418.5V304H419.5ZM422.5 304V305H421.5V304H422.5ZM425.5 304V305H424.5V304H425.5ZM428.5 304V305H427.5V304H428.5ZM431.5 304V305H430.5V304H431.5ZM434.5 304V305H433.5V304H434.5ZM437.5 304V305H436.5V304H437.5ZM440.5 304V305H439.5V304H440.5ZM443.5 304V305H442.5V304H443.5ZM446.5 304V305H445.5V304H446.5ZM449.5 304V305H448.5V304H449.5ZM452.5 304V305H451.5V304H452.5ZM455.5 304V305H454.5V304H455.5ZM458.5 304V305H457.5V304H458.5ZM461.5 304V305H460.5V304H461.5ZM464.5 304V305H463.5V304H464.5ZM467.5 304V305H466.5V304H467.5ZM470.5 304V305H469.5V304H470.5ZM473.5 304V305H472.5V304H473.5ZM476.5 304V305H475.5V304H476.5Z" fill="var(--subtle-foreground)" /> + <path d="M536 299V300C535.824 300 535.65 299.992 535.477 299.98L535.541 298.985C535.693 298.995 535.846 299 536 299ZM536.533 299V300H536V299H536.533ZM539.733 299V300H538.667V299H539.733ZM542.934 299V300H541.866V299H542.934ZM546.134 299V300H545.066V299H546.134ZM549.333 299V300H548.267V299H549.333ZM552 299V300H551.467V299H552ZM552.522 299.98C552.35 299.992 552.176 300 552 300V299C552.154 299 552.307 298.995 552.459 298.985L552.522 299.98ZM532.904 298.28C533.176 298.415 533.459 298.532 533.749 298.631L533.428 299.575C533.095 299.462 532.772 299.329 532.461 299.175L532.904 298.28ZM555.538 299.175C555.227 299.329 554.904 299.462 554.571 299.575L554.251 298.631C554.541 298.532 554.824 298.415 555.096 298.28L555.538 299.175ZM530.736 296.615C530.938 296.845 531.155 297.062 531.385 297.264L530.727 298.013C530.464 297.782 530.217 297.535 529.986 297.272L530.736 296.615ZM558.013 297.272C557.782 297.535 557.535 297.782 557.272 298.013L556.615 297.264C556.845 297.062 557.062 296.845 557.264 296.615L558.013 297.272ZM529.369 294.251C529.468 294.541 529.585 294.824 529.72 295.096L528.824 295.538C528.67 295.227 528.537 294.904 528.424 294.571L529.369 294.251ZM559.575 294.571C559.462 294.904 559.329 295.227 559.175 295.538L558.28 295.096C558.415 294.824 558.532 294.541 558.631 294.251L559.575 294.571ZM528 292C528 291.824 528.007 291.65 528.019 291.477L529.015 291.541C529.005 291.693 529 291.846 529 292C529 292.154 529.005 292.307 529.015 292.459L528.019 292.522C528.007 292.35 528 292.176 528 292ZM560 292C560 292.176 559.992 292.35 559.98 292.522L558.985 292.459C558.995 292.307 559 292.154 559 292C559 291.846 558.995 291.693 558.985 291.541L559.98 291.477C559.992 291.65 560 291.824 560 292ZM529.72 288.904C529.585 289.176 529.468 289.459 529.369 289.749L528.424 289.428C528.537 289.095 528.67 288.772 528.824 288.461L529.72 288.904ZM559.175 288.461C559.329 288.772 559.462 289.095 559.575 289.428L558.631 289.749C558.532 289.459 558.415 289.176 558.28 288.904L559.175 288.461ZM531.385 286.736C531.155 286.938 530.938 287.155 530.736 287.385L529.986 286.727C530.217 286.464 530.464 286.217 530.727 285.986L531.385 286.736ZM557.272 285.986C557.535 286.217 557.782 286.464 558.013 286.727L557.264 287.385C557.062 287.155 556.845 286.938 556.615 286.736L557.272 285.986ZM533.749 285.369C533.459 285.468 533.176 285.585 532.904 285.72L532.461 284.824C532.772 284.67 533.095 284.537 533.428 284.424L533.749 285.369ZM554.571 284.424C554.904 284.537 555.227 284.67 555.538 284.824L555.096 285.72C554.824 285.585 554.541 285.468 554.251 285.369L554.571 284.424ZM536.533 284V285H536C535.846 285 535.693 285.005 535.541 285.015L535.477 284.019C535.65 284.007 535.824 284 536 284H536.533ZM552.412 284.011C552.449 284.013 552.486 284.016 552.522 284.019L552.459 285.015C552.307 285.005 552.154 285 552 285H551.467V284H552L552.412 284.011ZM539.733 284V285H538.667V284H539.733ZM542.934 284V285H541.866V284H542.934ZM546.134 284V285H545.066V284H546.134ZM549.333 284V285H548.267V284H549.333Z" fill="var(--subtle-foreground)" /> + <circle cx="552" cy="292" r="4.5" stroke="var(--subtle-foreground)" stroke-dasharray="1 1" /> + <rect x="358.5" y="344.5" width="213" height="55" rx="7.5" fill="var(--background)" /> + <rect x="358.5" y="344.5" width="213" height="55" rx="7.5" stroke="var(--border-strong)" stroke-dasharray="1 3" /> + <path d="M382.385 374.321C381.442 374.882 378.969 376.029 380.475 377.464C381.211 378.165 382.03 378.666 383.061 378.666H388.939C389.97 378.666 390.789 378.165 391.525 377.464C393.031 376.029 390.558 374.882 389.615 374.321C387.403 373.004 384.597 373.004 382.385 374.321Z" fill="var(--background)" /> + <path d="M389 368.333C389 369.99 387.657 371.333 386 371.333C384.343 371.333 383 369.99 383 368.333C383 366.676 384.343 365.333 386 365.333C387.657 365.333 389 366.676 389 368.333Z" fill="var(--background)" /> + <path d="M382.573 377.608C382.726 377.646 382.888 377.666 383.061 377.666V378.666C382.807 378.666 382.566 378.635 382.336 378.579L382.573 377.608ZM383.551 377.666V378.666H383.061V377.666H383.551ZM385.51 377.666V378.666H384.53V377.666H385.51ZM387.47 377.666V378.666H386.49V377.666H387.47ZM388.939 377.666V378.666H388.449V377.666H388.939ZM389.663 378.579C389.492 378.621 389.314 378.649 389.13 378.66L388.939 378.666V377.666C389.112 377.666 389.274 377.646 389.427 377.608L389.663 378.579ZM381.009 376.553C381.026 376.585 381.069 376.649 381.165 376.74C381.322 376.89 381.476 377.022 381.63 377.137L381.032 377.938C380.84 377.794 380.655 377.635 380.476 377.464C380.32 377.315 380.208 377.169 380.131 377.027L380.272 376.951L381.009 376.553ZM391.868 377.027C391.791 377.169 391.68 377.316 391.524 377.464C391.344 377.635 391.159 377.794 390.967 377.938L390.37 377.137C390.524 377.022 390.678 376.89 390.835 376.74C390.931 376.649 390.974 376.585 390.991 376.553L391.868 377.027ZM380.96 375.19L381.551 375.997C381.27 376.203 381.105 376.373 381.03 376.495L380.178 375.973C380.31 375.757 380.505 375.556 380.73 375.369L380.96 375.19ZM391.039 375.19C391.359 375.424 391.646 375.687 391.821 375.973L391.396 376.234L391.396 376.233L390.97 376.495C390.895 376.373 390.73 376.203 390.449 375.997L390.758 375.576L391.039 375.19ZM382.801 374.093L382.922 374.333L383.252 374.984C383.132 375.045 383.013 375.11 382.896 375.18C382.751 375.266 382.564 375.371 382.397 375.466L381.902 374.598C382.084 374.494 382.25 374.401 382.385 374.32C382.521 374.239 382.659 374.163 382.8 374.092L382.801 374.093ZM389.199 374.092C389.34 374.163 389.479 374.239 389.615 374.32C389.75 374.401 389.915 374.494 390.097 374.598L389.603 375.466C389.436 375.371 389.249 375.266 389.104 375.18C388.987 375.11 388.868 375.045 388.748 374.984L389.199 374.092ZM384.637 373.719L384.784 374.454C384.519 374.508 384.257 374.579 384 374.668L383.824 374.162L383.673 373.723C383.972 373.619 384.278 373.536 384.587 373.474L384.637 373.719ZM387.412 373.474C387.721 373.536 388.027 373.619 388.326 373.723L388.176 374.162L388 374.668C387.743 374.579 387.481 374.508 387.216 374.454L387.412 373.474ZM385.526 373.349C385.841 373.328 386.158 373.328 386.473 373.349L386.407 374.347C386.136 374.329 385.864 374.329 385.593 374.347L385.526 373.349ZM385.608 370.295C385.734 370.32 385.865 370.333 386 370.333V371.333C385.799 371.333 385.604 371.313 385.414 371.275L385.608 370.295ZM386.585 371.275C386.396 371.313 386.2 371.333 386 371.333V370.333C386.135 370.333 386.266 370.32 386.392 370.295L386.585 371.275ZM384.336 369.443C384.482 369.662 384.671 369.851 384.89 369.997L384.333 370.827C384.006 370.608 383.724 370.326 383.505 369.999L384.336 369.443ZM388.494 369.999C388.275 370.326 387.993 370.608 387.666 370.827L387.11 369.997C387.329 369.851 387.518 369.662 387.664 369.443L388.494 369.999ZM383 368.333C383 368.133 383.019 367.937 383.057 367.747L384.038 367.941C384.013 368.067 384 368.198 384 368.333C384 368.468 384.013 368.599 384.038 368.725L383.057 368.918C383.019 368.729 383 368.533 383 368.333ZM389 368.333C389 368.533 388.98 368.729 388.942 368.918L387.962 368.725C387.987 368.599 388 368.468 388 368.333C388 368.198 387.987 368.067 387.962 367.941L388.942 367.747C388.98 367.937 389 368.132 389 368.333ZM384.89 366.669C384.671 366.815 384.482 367.004 384.336 367.223L383.505 366.666C383.724 366.339 384.006 366.057 384.333 365.838L384.89 366.669ZM387.666 365.838C387.993 366.057 388.275 366.339 388.494 366.666L387.664 367.223C387.518 367.004 387.329 366.815 387.11 366.669L387.666 365.838ZM386 365.333C386.2 365.333 386.396 365.352 386.585 365.39L386.392 366.371C386.266 366.346 386.135 366.333 386 366.333C385.865 366.333 385.734 366.346 385.608 366.371L385.414 365.39C385.604 365.352 385.8 365.333 386 365.333Z" fill="var(--subtle-foreground)" /> + <path d="M386 387V388C385.825 388 385.65 387.996 385.476 387.99L385.491 387.492L385.508 386.992C385.671 386.997 385.835 387 386 387ZM386.509 387.492L386.523 387.99C386.35 387.996 386.175 388 386 388V387C386.165 387 386.329 386.997 386.492 386.992L386.509 387.492ZM382.592 386.611C382.909 386.685 383.23 386.749 383.555 386.802L383.393 387.787C383.046 387.73 382.704 387.663 382.365 387.584L382.592 386.611ZM389.634 387.584C389.295 387.663 388.953 387.73 388.606 387.787L388.445 386.802C388.77 386.749 389.091 386.685 389.408 386.611L389.634 387.584ZM379.809 385.667C380.106 385.802 380.408 385.927 380.715 386.043L380.362 386.978C380.034 386.854 379.711 386.721 379.394 386.577L379.809 385.667ZM392.605 386.577C392.288 386.721 391.965 386.854 391.637 386.978L391.538 386.714L391.285 386.043C391.592 385.927 391.894 385.802 392.191 385.667L392.605 386.577ZM377.263 384.194C377.528 384.385 377.801 384.567 378.079 384.74L377.55 385.588C377.253 385.403 376.963 385.208 376.68 385.005L377.263 384.194ZM395.319 385.005C395.036 385.208 394.746 385.403 394.449 385.588L393.921 384.74C394.199 384.567 394.472 384.385 394.737 384.194L395.319 385.005ZM375.052 382.254C375.276 382.493 375.507 382.724 375.746 382.948L375.403 383.312L375.404 383.313L375.062 383.677C374.808 383.438 374.561 383.191 374.322 382.937L375.052 382.254ZM397.312 382.596L397.677 382.937C397.438 383.191 397.191 383.438 396.937 383.677L396.596 383.313L396.254 382.948C396.493 382.724 396.724 382.493 396.948 382.254L397.312 382.596ZM373.26 379.921C373.433 380.199 373.615 380.472 373.806 380.737L372.994 381.319C372.791 381.036 372.596 380.746 372.411 380.449L373.26 379.921ZM398.74 379.921L399.588 380.449C399.403 380.746 399.208 381.036 399.005 381.319L398.194 380.737C398.385 380.472 398.567 380.199 398.74 379.921ZM371.957 377.285C372.073 377.592 372.198 377.894 372.333 378.191L371.422 378.605C371.278 378.288 371.145 377.965 371.021 377.637L371.957 377.285ZM400.978 377.637C400.854 377.965 400.721 378.288 400.577 378.605L399.667 378.191C399.802 377.894 399.927 377.592 400.043 377.285L400.978 377.637ZM371.198 374.445C371.251 374.77 371.315 375.091 371.389 375.408L370.415 375.634C370.336 375.295 370.269 374.953 370.212 374.606L371.198 374.445ZM401.787 374.606C401.73 374.953 401.663 375.295 401.584 375.634L400.611 375.408C400.685 375.091 400.749 374.77 400.802 374.445L401.787 374.606ZM370 372C370 371.862 370.001 371.724 370.005 371.587L370.009 371.476L370.508 371.491L371.008 371.508C371.003 371.671 371 371.835 371 372C371 372.165 371.003 372.329 371.008 372.492L370.507 372.508L370.508 372.509L370.009 372.523C370.003 372.35 370 372.175 370 372ZM401.99 371.476C401.996 371.65 402 371.825 402 372C402 372.175 401.996 372.35 401.99 372.523L401.492 372.509L400.992 372.492C400.997 372.329 401 372.165 401 372C401 371.835 400.997 371.671 400.992 371.508L401.99 371.476ZM370.415 368.365L371.389 368.592C371.315 368.909 371.251 369.23 371.198 369.555L370.212 369.393C370.267 369.054 370.334 368.718 370.41 368.387L370.415 368.365ZM401.584 368.365C401.663 368.704 401.73 369.046 401.787 369.393L400.802 369.555C400.749 369.23 400.685 368.909 400.611 368.592L401.584 368.365ZM371.423 365.395L372.333 365.809C372.198 366.106 372.073 366.408 371.957 366.715L371.021 366.362C371.145 366.034 371.278 365.711 371.422 365.394L371.423 365.395ZM400.577 365.394C400.721 365.711 400.854 366.034 400.978 366.362L400.043 366.715C399.927 366.408 399.802 366.106 399.667 365.809L400.577 365.394ZM373.806 363.263C373.615 363.528 373.433 363.801 373.26 364.079L372.835 363.813L372.411 363.55C372.596 363.253 372.791 362.963 372.994 362.68L373.806 363.263ZM399.005 362.68C399.208 362.963 399.403 363.253 399.588 363.55L399.165 363.814L399.164 363.813L398.74 364.079C398.567 363.801 398.385 363.528 398.194 363.263L399.005 362.68ZM375.746 361.052C375.507 361.276 375.276 361.507 375.052 361.746L374.675 361.394L374.322 361.062C374.561 360.808 374.808 360.561 375.062 360.322L375.746 361.052ZM396.937 360.322C397.191 360.561 397.438 360.808 397.677 361.062L397.325 361.394L396.948 361.746C396.724 361.507 396.493 361.276 396.254 361.052L396.937 360.322ZM378.079 359.26C377.801 359.433 377.528 359.615 377.263 359.806L376.68 358.994C376.963 358.791 377.253 358.596 377.55 358.411L378.079 359.26ZM394.449 358.411C394.746 358.596 395.036 358.791 395.319 358.994L394.737 359.806C394.472 359.615 394.199 359.433 393.921 359.26L394.449 358.411ZM380.715 357.957C380.408 358.073 380.106 358.198 379.809 358.333L379.395 357.423L379.394 357.422C379.711 357.278 380.034 357.145 380.362 357.021L380.715 357.957ZM391.637 357.021C391.965 357.145 392.288 357.278 392.605 357.422L392.191 358.333C391.894 358.198 391.592 358.073 391.285 357.957L391.637 357.021ZM383.555 357.198C383.23 357.251 382.909 357.315 382.592 357.389L382.365 356.415C382.704 356.336 383.046 356.269 383.393 356.212L383.555 357.198ZM388.606 356.212C388.953 356.269 389.295 356.336 389.634 356.415L389.408 357.389C389.091 357.315 388.77 357.251 388.445 357.198L388.606 356.212ZM386 356C386.175 356 386.35 356.003 386.523 356.009L386.519 356.211L386.492 357.008C386.329 357.003 386.165 357 386 357C385.835 357 385.671 357.003 385.508 357.008L385.481 356.211L385.476 356.009C385.65 356.003 385.825 356 386 356Z" fill="var(--subtle-foreground)" /> + <path d="M410 365C410 363.343 411.343 362 413 362H439C440.657 362 442 363.343 442 365C442 366.657 440.657 368 439 368H413C411.343 368 410 366.657 410 365Z" fill="var(--background)" /> + <path d="M412.737 366.983C412.823 366.995 412.911 367 413 367V368C412.867 368 412.737 367.99 412.608 367.974L412.737 366.983ZM413.481 367V368H413V367H413.481ZM416.37 367V368H415.407V367H416.37ZM419.259 367V368H418.296V367H419.259ZM422.148 367V368H421.186V367H422.148ZM425.037 367V368H424.074V367H425.037ZM427.926 367V368H426.963V367H427.926ZM430.814 367V368H429.852V367H430.814ZM433.704 367V368H432.741V367H433.704ZM436.593 367V368H435.63V367H436.593ZM439 367V368H438.519V367H439ZM439.327 367.479L439.377 367.865L439.391 367.974C439.263 367.99 439.132 368 439 368V367C439.089 367 439.177 366.995 439.263 366.983L439.327 367.479ZM411.413 366.217C411.52 366.356 411.644 366.48 411.783 366.587L411.175 367.377C410.967 367.217 410.782 367.032 410.622 366.824L411.413 366.217ZM441.377 366.824C441.217 367.032 441.032 367.217 440.824 367.377L440.217 366.587C440.356 366.48 440.48 366.356 440.587 366.217L441.377 366.824ZM410 365C410 364.867 410.009 364.737 410.025 364.608L410.521 364.673H410.521L411.017 364.737C411.005 364.823 411 364.911 411 365C411 365.089 411.005 365.177 411.017 365.263L410.025 365.391C410.009 365.263 410 365.132 410 365ZM442 365C442 365.132 441.99 365.263 441.974 365.391L440.983 365.263C440.995 365.177 441 365.089 441 365C441 364.911 440.995 364.823 440.983 364.737L441.974 364.608C441.99 364.737 442 364.867 442 365ZM411.783 363.413C411.644 363.52 411.52 363.644 411.413 363.783L410.622 363.175C410.782 362.967 410.967 362.782 411.175 362.622L411.783 363.413ZM440.824 362.622C441.032 362.782 441.217 362.967 441.377 363.175L440.587 363.783C440.48 363.644 440.356 363.52 440.217 363.413L440.824 362.622ZM413.481 362V363H413C412.911 363 412.823 363.005 412.737 363.017L412.627 362.172L412.608 362.025C412.737 362.009 412.867 362 413 362H413.481ZM439 362C439.132 362 439.263 362.009 439.391 362.025L439.373 362.172L439.263 363.017C439.177 363.005 439.089 363 439 363H438.519V362H439ZM416.37 362V363H415.407V362H416.37ZM419.259 362V363H418.296V362H419.259ZM422.148 362V363H421.186V362H422.148ZM425.037 362V363H424.074V362H425.037ZM427.926 362V363H426.963V362H427.926ZM430.814 362V363H429.852V362H430.814ZM433.704 362V363H432.741V362H433.704ZM436.593 362V363H435.63V362H436.593Z" fill="var(--subtle-foreground)" /> + <path d="M410 379C410 377.343 411.343 376 413 376H479C480.657 376 482 377.343 482 379C482 380.657 480.657 382 479 382H413C411.343 382 410 380.657 410 379Z" fill="var(--background)" /> + <path d="M412.737 380.983C412.823 380.995 412.911 381 413 381V382C412.867 382 412.737 381.99 412.608 381.974L412.737 380.983ZM413.5 381V382H413V381H413.5ZM416.5 381V382H415.5V381H416.5ZM419.5 381V382H418.5V381H419.5ZM422.5 381V382H421.5V381H422.5ZM425.5 381V382H424.5V381H425.5ZM428.5 381V382H427.5V381H428.5ZM431.5 381V382H430.5V381H431.5ZM434.5 381V382H433.5V381H434.5ZM437.5 381V382H436.5V381H437.5ZM440.5 381V382H439.5V381H440.5ZM443.5 381V382H442.5V381H443.5ZM446.5 381V382H445.5V381H446.5ZM449.5 381V382H448.5V381H449.5ZM452.5 381V382H451.5V381H452.5ZM455.5 381V382H454.5V381H455.5ZM458.5 381V382H457.5V381H458.5ZM461.5 381V382H460.5V381H461.5ZM464.5 381V382H463.5V381H464.5ZM467.5 381V382H466.5V381H467.5ZM470.5 381V382H469.5V381H470.5ZM473.5 381V382H472.5V381H473.5ZM476.5 381V382H475.5V381H476.5ZM479 381V382H478.5V381H479ZM479.327 381.479L479.377 381.865L479.391 381.974C479.313 381.984 479.234 381.992 479.154 381.996L479 382V381C479.089 381 479.177 380.995 479.263 380.983L479.327 381.479ZM411.413 380.217C411.52 380.356 411.644 380.48 411.783 380.587L411.175 381.377C410.967 381.217 410.782 381.032 410.622 380.824L411.413 380.217ZM481.377 380.824C481.217 381.032 481.032 381.217 480.824 381.377L480.217 380.587C480.356 380.48 480.48 380.356 480.587 380.217L481.377 380.824ZM410 379C410 378.867 410.009 378.737 410.025 378.608L410.521 378.673H410.521L411.017 378.737C411.005 378.823 411 378.911 411 379C411 379.089 411.005 379.177 411.017 379.263L410.025 379.391C410.009 379.263 410 379.132 410 379ZM482 379C482 379.132 481.99 379.263 481.974 379.391L480.983 379.263C480.995 379.177 481 379.089 481 379C481 378.911 480.995 378.823 480.983 378.737L481.974 378.608C481.99 378.737 482 378.867 482 379ZM411.783 377.413C411.644 377.52 411.52 377.644 411.413 377.783L410.622 377.175C410.782 376.967 410.967 376.782 411.175 376.622L411.783 377.413ZM480.824 376.622C481.032 376.782 481.217 376.967 481.377 377.175L480.587 377.783C480.48 377.644 480.356 377.52 480.217 377.413L480.824 376.622ZM413.5 376V377H413C412.911 377 412.823 377.005 412.737 377.017L412.627 376.172L412.608 376.025C412.737 376.009 412.867 376 413 376H413.5ZM479.154 376.004C479.234 376.008 479.313 376.015 479.391 376.025L479.373 376.172L479.263 377.017C479.177 377.005 479.089 377 479 377H478.5V376H479L479.154 376.004ZM416.5 376V377H415.5V376H416.5ZM419.5 376V377H418.5V376H419.5ZM422.5 376V377H421.5V376H422.5ZM425.5 376V377H424.5V376H425.5ZM428.5 376V377H427.5V376H428.5ZM431.5 376V377H430.5V376H431.5ZM434.5 376V377H433.5V376H434.5ZM437.5 376V377H436.5V376H437.5ZM440.5 376V377H439.5V376H440.5ZM443.5 376V377H442.5V376H443.5ZM446.5 376V377H445.5V376H446.5ZM449.5 376V377H448.5V376H449.5ZM452.5 376V377H451.5V376H452.5ZM455.5 376V377H454.5V376H455.5ZM458.5 376V377H457.5V376H458.5ZM461.5 376V377H460.5V376H461.5ZM464.5 376V377H463.5V376H464.5ZM467.5 376V377H466.5V376H467.5ZM470.5 376V377H469.5V376H470.5ZM473.5 376V377H472.5V376H473.5ZM476.5 376V377H475.5V376H476.5Z" fill="var(--subtle-foreground)" /> + <path d="M536 371V372C535.824 372 535.65 371.992 535.477 371.98L535.541 370.985C535.693 370.995 535.846 371 536 371ZM536.533 371V372H536V371H536.533ZM539.733 371V372H538.667V371H539.733ZM542.934 371V372H541.866V371H542.934ZM546.134 371V372H545.066V371H546.134ZM549.333 371V372H548.267V371H549.333ZM552 371V372H551.467V371H552ZM552.522 371.98C552.35 371.992 552.176 372 552 372V371C552.154 371 552.307 370.995 552.459 370.985L552.522 371.98ZM532.904 370.28C533.176 370.415 533.459 370.532 533.749 370.631L533.428 371.575C533.095 371.462 532.772 371.329 532.461 371.175L532.904 370.28ZM555.538 371.175C555.227 371.329 554.904 371.462 554.571 371.575L554.251 370.631C554.541 370.532 554.824 370.415 555.096 370.28L555.538 371.175ZM530.736 368.615C530.938 368.845 531.155 369.062 531.385 369.264L530.727 370.013C530.464 369.782 530.217 369.535 529.986 369.272L530.736 368.615ZM558.013 369.272C557.782 369.535 557.535 369.782 557.272 370.013L556.615 369.264C556.845 369.062 557.062 368.845 557.264 368.615L558.013 369.272ZM529.369 366.251C529.468 366.541 529.585 366.824 529.72 367.096L528.824 367.538C528.67 367.227 528.537 366.904 528.424 366.571L529.369 366.251ZM559.575 366.571C559.462 366.904 559.329 367.227 559.175 367.538L558.28 367.096C558.415 366.824 558.532 366.541 558.631 366.251L559.575 366.571ZM528 364C528 363.824 528.007 363.65 528.019 363.477L529.015 363.541C529.005 363.693 529 363.846 529 364C529 364.154 529.005 364.307 529.015 364.459L528.019 364.522C528.007 364.35 528 364.176 528 364ZM560 364C560 364.176 559.992 364.35 559.98 364.522L558.985 364.459C558.995 364.307 559 364.154 559 364C559 363.846 558.995 363.693 558.985 363.541L559.98 363.477C559.992 363.65 560 363.824 560 364ZM529.72 360.904C529.585 361.176 529.468 361.459 529.369 361.749L528.424 361.428C528.537 361.095 528.67 360.772 528.824 360.461L529.72 360.904ZM559.175 360.461C559.329 360.772 559.462 361.095 559.575 361.428L558.631 361.749C558.532 361.459 558.415 361.176 558.28 360.904L559.175 360.461ZM531.385 358.736C531.155 358.938 530.938 359.155 530.736 359.385L529.986 358.727C530.217 358.464 530.464 358.217 530.727 357.986L531.385 358.736ZM557.272 357.986C557.535 358.217 557.782 358.464 558.013 358.727L557.264 359.385C557.062 359.155 556.845 358.938 556.615 358.736L557.272 357.986ZM533.749 357.369C533.459 357.468 533.176 357.585 532.904 357.72L532.461 356.824C532.772 356.67 533.095 356.537 533.428 356.424L533.749 357.369ZM554.571 356.424C554.904 356.537 555.227 356.67 555.538 356.824L555.096 357.72C554.824 357.585 554.541 357.468 554.251 357.369L554.571 356.424ZM536.533 356V357H536C535.846 357 535.693 357.005 535.541 357.015L535.477 356.019C535.65 356.007 535.824 356 536 356H536.533ZM552.412 356.011C552.449 356.013 552.486 356.016 552.522 356.019L552.459 357.015C552.307 357.005 552.154 357 552 357H551.467V356H552L552.412 356.011ZM539.733 356V357H538.667V356H539.733ZM542.934 356V357H541.866V356H542.934ZM546.134 356V357H545.066V356H546.134ZM549.333 356V357H548.267V356H549.333Z" fill="var(--subtle-foreground)" /> + <circle cx="552" cy="364" r="4.5" stroke="var(--subtle-foreground)" stroke-dasharray="1 1" /> + <rect x="438.5" y="120.5" width="133" height="63" rx="7.5" fill="var(--background)" /> + <rect x="438.5" y="120.5" width="133" height="63" rx="7.5" stroke="var(--border-strong)" stroke-dasharray="1 3" /> + <path d="M450.491 135.5V136.5H450V135.5H450.491ZM454.42 135.5V136.5H453.438V135.5H454.42ZM458.349 135.5V136.5H457.366V135.5H458.349ZM462.276 135.5V136.5H461.295V135.5H462.276ZM466.205 135.5V136.5H465.224V135.5H466.205ZM470.134 135.5V136.5H469.151V135.5H470.134ZM474.062 135.5V136.5H473.08V135.5H474.062ZM477.991 135.5V136.5H477.009V135.5H477.991ZM481.92 135.5V136.5H480.938V135.5H481.92ZM485.849 135.5V136.5H484.866V135.5H485.849ZM489.776 135.5V136.5H488.795V135.5H489.776ZM493.705 135.5V136.5H492.724V135.5H493.705ZM497.634 135.5V136.5H496.651V135.5H497.634ZM501.562 135.5V136.5H500.58V135.5H501.562ZM505.491 135.5V136.5H504.509V135.5H505.491ZM509.42 135.5V136.5H508.438V135.5H509.42ZM513.349 135.5V136.5H512.366V135.5H513.349ZM517.276 135.5V136.5H516.295V135.5H517.276ZM521.205 135.5V136.5H520.224V135.5H521.205ZM525.134 135.5V136.5H524.151V135.5H525.134ZM529.062 135.5V136.5H528.08V135.5H529.062ZM532.991 135.5V136.5H532.009V135.5H532.991ZM536.92 135.5V136.5H535.938V135.5H536.92ZM540.849 135.5V136.5H539.866V135.5H540.849ZM544.776 135.5V136.5H543.795V135.5H544.776ZM548.705 135.5V136.5H547.724V135.5H548.705ZM552.634 135.5V136.5H551.651V135.5H552.634ZM556.562 135.5V136.5H555.58V135.5H556.562ZM560 135.5V136.5H559.509V135.5H560Z" fill="var(--subtle-foreground)" /> + <path d="M450.491 151.5V152.5H450V151.5H450.491ZM454.42 151.5V152.5H453.438V151.5H454.42ZM458.349 151.5V152.5H457.366V151.5H458.349ZM462.276 151.5V152.5H461.295V151.5H462.276ZM466.205 151.5V152.5H465.224V151.5H466.205ZM470.134 151.5V152.5H469.151V151.5H470.134ZM474.062 151.5V152.5H473.08V151.5H474.062ZM477.991 151.5V152.5H477.009V151.5H477.991ZM481.92 151.5V152.5H480.938V151.5H481.92ZM485.849 151.5V152.5H484.866V151.5H485.849ZM489.776 151.5V152.5H488.795V151.5H489.776ZM493.705 151.5V152.5H492.724V151.5H493.705ZM497.634 151.5V152.5H496.651V151.5H497.634ZM501.562 151.5V152.5H500.58V151.5H501.562ZM505.491 151.5V152.5H504.509V151.5H505.491ZM509.42 151.5V152.5H508.438V151.5H509.42ZM513.349 151.5V152.5H512.366V151.5H513.349ZM517.276 151.5V152.5H516.295V151.5H517.276ZM521.205 151.5V152.5H520.224V151.5H521.205ZM525.134 151.5V152.5H524.151V151.5H525.134ZM529.062 151.5V152.5H528.08V151.5H529.062ZM532.991 151.5V152.5H532.009V151.5H532.991ZM536.92 151.5V152.5H535.938V151.5H536.92ZM540.849 151.5V152.5H539.866V151.5H540.849ZM544.776 151.5V152.5H543.795V151.5H544.776ZM548.705 151.5V152.5H547.724V151.5H548.705ZM552.634 151.5V152.5H551.651V151.5H552.634ZM556.562 151.5V152.5H555.58V151.5H556.562ZM560 151.5V152.5H559.509V151.5H560Z" fill="var(--subtle-foreground)" /> + <path d="M450.491 167.5V168.5H450V167.5H450.491ZM454.42 167.5V168.5H453.438V167.5H454.42ZM458.349 167.5V168.5H457.366V167.5H458.349ZM462.276 167.5V168.5H461.295V167.5H462.276ZM466.205 167.5V168.5H465.224V167.5H466.205ZM470.134 167.5V168.5H469.151V167.5H470.134ZM474.062 167.5V168.5H473.08V167.5H474.062ZM477.991 167.5V168.5H477.009V167.5H477.991ZM481.92 167.5V168.5H480.938V167.5H481.92ZM485.849 167.5V168.5H484.866V167.5H485.849ZM489.776 167.5V168.5H488.795V167.5H489.776ZM493.705 167.5V168.5H492.724V167.5H493.705ZM497.634 167.5V168.5H496.651V167.5H497.634ZM501.562 167.5V168.5H500.58V167.5H501.562ZM505.491 167.5V168.5H504.509V167.5H505.491ZM509.42 167.5V168.5H508.438V167.5H509.42ZM513.349 167.5V168.5H512.366V167.5H513.349ZM517.276 167.5V168.5H516.295V167.5H517.276ZM521.205 167.5V168.5H520.224V167.5H521.205ZM525.134 167.5V168.5H524.151V167.5H525.134ZM529.062 167.5V168.5H528.08V167.5H529.062ZM532.991 167.5V168.5H532.009V167.5H532.991ZM536.92 167.5V168.5H535.938V167.5H536.92ZM540.849 167.5V168.5H539.866V167.5H540.849ZM544.776 167.5V168.5H543.795V167.5H544.776ZM548.705 167.5V168.5H547.724V167.5H548.705ZM552.634 167.5V168.5H551.651V167.5H552.634ZM556.562 167.5V168.5H555.58V167.5H556.562ZM560 167.5V168.5H559.509V167.5H560Z" fill="var(--subtle-foreground)" /> + <path d="M558.553 136C559.352 136 560 136.551 560 137.231C560 137.911 559.352 138.462 558.553 138.462C556.724 138.462 555.411 139.194 554.293 140.495C553.126 141.851 552.271 143.717 551.435 145.758C550.633 147.716 549.831 149.889 548.777 151.528C547.721 153.171 546.135 154.705 543.567 154.706C541.291 154.706 539.464 153.698 538.038 152.95C536.495 152.14 535.305 151.56 533.878 151.56C532.784 151.56 531.717 151.875 530.578 152.418C529.412 152.974 528.308 153.696 527.047 154.468C524.681 155.915 521.697 157.606 518.009 156.673C516.765 156.358 515.689 155.763 514.763 155.161C514.296 154.858 513.844 154.538 513.418 154.236C512.985 153.928 512.581 153.639 512.176 153.373C510.544 152.297 509.343 151.864 507.845 152.388C506.743 152.774 505.729 153.753 504.661 155.266C504.143 155.999 503.649 156.803 503.14 157.643C502.638 158.471 502.117 159.341 501.571 160.154C500.512 161.73 499.187 163.369 497.311 164.167C495.245 165.046 492.917 164.764 490.35 163.303C487.445 161.65 483.427 159.568 479.452 158.356C475.388 157.116 471.874 156.937 469.525 158.422C468.13 159.304 466.849 160.309 465.561 161.353C464.292 162.382 462.99 163.473 461.637 164.44C458.896 166.399 455.741 168 451.447 168C450.648 168 450 167.449 450 166.769C450 166.089 450.648 165.538 451.447 165.538C454.777 165.538 457.287 164.334 459.775 162.555C461.038 161.653 462.242 160.642 463.562 159.572C464.862 158.518 466.251 157.424 467.798 156.446C471.396 154.171 476.2 154.748 480.43 156.038C484.749 157.356 489.005 159.576 491.96 161.257C493.976 162.404 495.175 162.323 496.016 161.965C497.047 161.527 498.01 160.493 499.058 158.934C499.565 158.179 500.052 157.365 500.567 156.516C501.075 155.678 501.607 154.809 502.179 153.999C503.291 152.423 504.714 150.823 506.743 150.113C509.77 149.053 512.144 150.238 513.95 151.428C514.405 151.728 514.853 152.047 515.274 152.346C515.704 152.651 516.11 152.939 516.52 153.204C517.345 153.74 518.09 154.125 518.835 154.314C521.018 154.866 522.895 153.975 525.356 152.469C526.509 151.764 527.811 150.916 529.163 150.272C530.541 149.615 532.106 149.099 533.878 149.099C536.243 149.099 538.102 150.091 539.557 150.855C541.13 151.68 542.253 152.244 543.567 152.244C544.503 152.244 545.335 151.749 546.237 150.346C547.142 148.939 547.839 147.052 548.701 144.948C549.528 142.929 550.502 140.732 551.956 139.042C553.457 137.297 555.555 136 558.553 136Z" fill="var(--background)" /> + <path d="M451.099 167.962C451.21 167.985 451.327 168 451.447 168C451.601 168 451.753 167.996 451.904 167.992L451.905 167.994L451.933 168.993C451.772 168.997 451.61 169 451.447 169C451.258 169 451.072 168.98 450.893 168.942L451.099 167.964V167.962ZM454.631 167.687L454.83 168.666C454.516 168.73 454.196 168.786 453.869 168.832L453.729 167.842L453.728 167.841C454.035 167.797 454.335 167.744 454.63 167.685L454.631 167.687ZM457.259 166.901L457.628 167.831C457.33 167.95 457.025 168.061 456.714 168.164L456.399 167.215L456.398 167.214C456.691 167.117 456.977 167.01 457.258 166.898L457.259 166.901ZM450.008 166.896C450.017 166.97 450.035 167.043 450.059 167.113L450.058 167.114L449.585 167.276L449.111 167.438C449.039 167.228 449 167.002 449 166.77C449 166.537 449.039 166.312 449.111 166.101L450.058 166.425C450.02 166.534 450 166.65 450 166.77L450.008 166.896ZM459.721 165.69L460.227 166.554C459.952 166.714 459.672 166.87 459.386 167.021L459.14 166.553L458.921 166.135C459.192 165.992 459.457 165.842 459.719 165.688L459.721 165.69ZM493.147 164.452C493.554 164.55 493.953 164.613 494.345 164.643V164.646L494.312 165.085L494.268 165.643C493.819 165.608 493.367 165.535 492.912 165.425L493.029 164.938L493.147 164.452ZM451.947 165.526C451.783 165.532 451.616 165.538 451.447 165.538C451.327 165.538 451.21 165.552 451.099 165.575L451.089 165.529L450.893 164.596C451.072 164.558 451.258 164.538 451.447 164.538C451.604 164.538 451.759 164.536 451.911 164.53L451.947 165.526ZM461.974 164.196L462.267 164.596L462.565 165.002C462.45 165.086 462.335 165.171 462.219 165.254C462.091 165.345 461.962 165.435 461.832 165.525L461.263 164.703L461.262 164.701C461.387 164.614 461.513 164.528 461.637 164.439C461.749 164.359 461.861 164.276 461.973 164.194L461.974 164.196ZM497.758 163.956L497.977 164.378L498.219 164.844C498.051 164.931 497.879 165.012 497.702 165.087C497.486 165.179 497.266 165.261 497.043 165.331L496.742 164.378L496.74 164.374C496.932 164.313 497.123 164.247 497.311 164.167C497.463 164.102 497.611 164.029 497.756 163.954L497.758 163.956ZM454.904 165.058C454.587 165.149 454.264 165.227 453.933 165.294L453.736 164.316C454.04 164.255 454.336 164.183 454.627 164.099L454.904 165.058ZM490.35 163.303C490.529 163.405 490.707 163.5 490.884 163.591L490.66 164.03L490.429 164.481C490.238 164.384 490.047 164.281 489.855 164.172C489.715 164.092 489.571 164.011 489.426 163.929L489.914 163.059C490.062 163.141 490.207 163.222 490.35 163.303ZM457.426 163.46L457.664 163.896C457.371 164.057 457.074 164.208 456.773 164.349L456.352 163.445C456.632 163.315 456.909 163.173 457.185 163.021L457.426 163.46ZM463.946 162.666L464.185 162.962L464.574 163.444C464.357 163.62 464.137 163.795 463.916 163.97L463.296 163.186L463.294 163.183C463.512 163.011 463.729 162.838 463.944 162.664L463.946 162.666ZM500.066 162.115L500.807 162.787C500.566 163.051 500.312 163.31 500.042 163.556L499.705 163.185L499.369 162.815L499.367 162.813C499.611 162.592 499.844 162.358 500.065 162.114L500.066 162.115ZM487.275 161.636C487.576 161.79 487.871 161.946 488.16 162.1L487.692 162.982C487.407 162.831 487.115 162.679 486.818 162.526L487.275 161.636ZM460.1 162.318C459.992 162.398 459.884 162.478 459.775 162.556C459.639 162.653 459.501 162.748 459.364 162.842L458.799 162.019C458.93 161.928 459.063 161.835 459.194 161.741C459.299 161.666 459.404 161.59 459.509 161.514L460.1 162.318ZM466.194 161.484L466.508 161.874C466.402 161.959 466.296 162.044 466.19 162.13C466.084 162.216 465.977 162.304 465.869 162.392L465.237 161.616L465.236 161.615C465.344 161.527 465.453 161.441 465.561 161.354C465.667 161.267 465.774 161.182 465.881 161.096L466.194 161.484ZM496.649 161.614C496.44 161.756 496.23 161.874 496.017 161.965C495.812 162.052 495.586 162.121 495.333 162.162L495.176 161.176C495.339 161.149 495.487 161.104 495.625 161.045L495.853 160.934C495.93 160.891 496.009 160.843 496.09 160.788L496.649 161.614ZM484.577 160.329C484.882 160.468 485.185 160.608 485.483 160.75L485.306 161.127L485.055 161.653C484.761 161.514 484.464 161.375 484.164 161.239L484.577 160.329ZM492.454 160.388C492.656 160.502 492.846 160.603 493.025 160.689L492.59 161.588C492.389 161.491 492.179 161.382 491.96 161.257C491.81 161.172 491.655 161.087 491.499 160.999L491.5 160.998L491.99 160.126C492.148 160.215 492.303 160.302 492.454 160.388ZM462.002 160.838C461.792 161.008 461.583 161.175 461.375 161.34L460.756 160.559C460.961 160.396 461.167 160.231 461.375 160.062L462.002 160.838ZM501.843 159.742L502.682 160.286C502.589 160.429 502.496 160.571 502.401 160.712C502.307 160.852 502.21 160.994 502.11 161.136L501.291 160.562L501.289 160.561C501.384 160.424 501.48 160.29 501.571 160.154C501.663 160.018 501.751 159.879 501.841 159.74L501.843 159.742ZM468.422 160.39C468.207 160.547 467.993 160.708 467.78 160.871L467.174 160.08C467.392 159.913 467.609 159.746 467.83 159.584L468.422 160.39ZM481.812 159.171C482.124 159.29 482.434 159.412 482.742 159.538L482.365 160.463C482.064 160.34 481.761 160.219 481.457 160.104L481.66 159.572L481.812 159.171ZM490.113 159.101L489.879 159.542L489.646 159.984L489.645 159.985C489.339 159.824 489.027 159.66 488.707 159.496L489.164 158.606C489.488 158.772 489.805 158.937 490.113 159.101ZM463.903 159.296L463.904 159.297C463.79 159.389 463.675 159.48 463.562 159.572C463.457 159.657 463.353 159.741 463.25 159.825L462.619 159.05C462.722 158.966 462.827 158.881 462.932 158.796C463.045 158.704 463.161 158.611 463.276 158.518L463.903 159.296ZM499.27 158.505L499.317 158.536C499.231 158.67 499.145 158.803 499.058 158.934C498.918 159.141 498.779 159.339 498.643 159.528L497.834 158.942C497.963 158.764 498.094 158.575 498.228 158.376C498.312 158.251 498.396 158.123 498.48 157.993L499.27 158.505ZM470.47 159.035C470.328 159.107 470.191 159.185 470.06 159.268C469.948 159.338 469.838 159.409 469.728 159.481L469.444 159.05L469.18 158.646C469.294 158.57 469.409 158.495 469.525 158.422C469.685 158.321 469.85 158.228 470.02 158.143L470.47 159.035ZM479.452 158.355C479.611 158.404 479.769 158.456 479.928 158.507L479.814 158.861L479.622 159.457C479.468 159.407 479.314 159.359 479.16 159.312C478.983 159.259 478.807 159.207 478.633 159.157L478.77 158.676L478.906 158.196C479.087 158.248 479.269 158.3 479.452 158.355ZM475.576 157.492C475.942 157.539 476.316 157.598 476.697 157.67L476.514 158.65C476.151 158.582 475.796 158.525 475.451 158.481L475.521 157.938L475.576 157.492ZM504.238 157.762C504.158 157.893 504.077 158.027 503.995 158.161C503.91 158.301 503.826 158.44 503.739 158.583L502.884 158.065L502.882 158.064C502.968 157.923 503.055 157.782 503.14 157.643C503.221 157.508 503.305 157.375 503.386 157.242L504.238 157.762ZM487.24 157.657L486.812 158.562C486.497 158.412 486.176 158.264 485.853 158.117L486.266 157.206C486.595 157.355 486.92 157.506 487.24 157.657ZM473.348 158.374C472.993 158.387 472.654 158.418 472.331 158.467L472.235 157.841L472.181 157.479C472.544 157.423 472.922 157.389 473.312 157.375L473.348 158.374ZM465.986 157.685C465.749 157.859 465.514 158.035 465.283 158.211L464.677 157.415C464.912 157.235 465.153 157.056 465.396 156.878L465.986 157.685ZM521.708 156.797L521.798 157.288L521.887 157.78C521.505 157.85 521.113 157.898 520.711 157.923L520.65 156.925C521.011 156.903 521.363 156.857 521.707 156.795L521.708 156.797ZM517.435 156.501C517.622 156.563 517.814 156.623 518.01 156.673C518.185 156.717 518.358 156.754 518.53 156.787L518.343 157.771C518.151 157.734 517.959 157.692 517.765 157.643C517.544 157.587 517.328 157.523 517.118 157.453L517.434 156.505L517.435 156.501ZM484.288 156.356L483.912 157.283L483.911 157.284C483.585 157.152 483.257 157.022 482.927 156.896L483.283 155.962C483.62 156.09 483.956 156.222 484.288 156.356ZM524.725 155.77L525.161 156.67C524.826 156.832 524.478 156.987 524.119 157.13L523.75 156.2L523.749 156.197C524.082 156.065 524.408 155.922 524.724 155.769L524.725 155.77ZM500.398 155.829L500.826 156.09L500.828 156.091C500.741 156.233 500.653 156.374 500.567 156.516C500.485 156.652 500.401 156.786 500.319 156.921L499.466 156.404C499.547 156.271 499.629 156.134 499.712 155.997C499.798 155.856 499.885 155.713 499.973 155.569L500.398 155.829ZM468.266 156.17L468.267 156.171C468.109 156.258 467.952 156.349 467.798 156.446C467.674 156.525 467.551 156.605 467.429 156.685L467.319 156.516L466.882 155.847C467.008 155.764 467.135 155.682 467.264 155.601C467.435 155.493 467.608 155.39 467.783 155.294L468.266 156.17ZM514.763 155.161C514.927 155.268 515.097 155.373 515.271 155.478V155.479L515.014 155.907L514.755 156.336C514.57 156.224 514.39 156.112 514.218 156C514.138 155.948 514.059 155.896 513.98 155.844L514.535 155.012L514.536 155.009C514.612 155.059 514.686 155.112 514.763 155.161ZM505.884 155.292C505.75 155.465 505.615 155.648 505.478 155.843C505.392 155.963 505.307 156.086 505.223 156.212L504.395 155.654C504.483 155.523 504.571 155.393 504.661 155.266C504.806 155.061 504.949 154.866 505.092 154.681L505.884 155.292ZM480.722 155.082C480.895 155.135 481.068 155.189 481.24 155.244L480.935 156.196L480.934 156.198C480.766 156.144 480.598 156.089 480.43 156.038C480.257 155.985 480.083 155.936 479.908 155.886L479.909 155.884L480.186 154.923C480.365 154.974 480.544 155.028 480.722 155.082ZM544.1 154.683L544.142 155.167L544.187 155.679C543.985 155.696 543.778 155.705 543.567 155.705C543.381 155.705 543.197 155.7 543.016 155.688L543.078 154.69V154.688C543.101 154.69 543.123 154.693 543.146 154.694L543.567 154.705C543.749 154.705 543.926 154.696 544.099 154.681L544.1 154.683ZM528.152 154.961C527.962 155.079 527.767 155.199 527.568 155.32C527.418 155.412 527.264 155.507 527.106 155.602L526.592 154.744L526.59 154.74C526.744 154.648 526.898 154.559 527.047 154.468C527.244 154.347 527.437 154.228 527.627 154.11L528.152 154.961ZM476.895 154.175C477.267 154.235 477.637 154.304 478.006 154.381L477.803 155.359L477.802 155.362C477.447 155.288 477.09 155.22 476.733 155.162L476.895 154.175ZM471.153 154.124L471.33 155.107V155.109C470.972 155.174 470.619 155.253 470.272 155.352L470.136 154.871L470 154.39C470.38 154.282 470.765 154.194 471.153 154.124ZM540.237 154.011C540.533 154.129 540.84 154.239 541.159 154.335V154.337L541.029 154.771L540.871 155.295C540.52 155.189 540.185 155.068 539.865 154.94L540.237 154.013V154.011ZM513.419 154.235C513.492 154.287 513.566 154.339 513.641 154.392L513.062 155.209C512.987 155.156 512.912 155.103 512.839 155.051C512.769 155.001 512.701 154.953 512.634 154.905L513.213 154.09C513.281 154.138 513.35 154.186 513.419 154.235ZM547.027 153.52L547.646 154.306C547.325 154.558 546.976 154.788 546.596 154.985L546.135 154.098L546.133 154.095C546.453 153.928 546.751 153.735 547.026 153.519L547.027 153.52ZM473.476 153.906C473.859 153.901 474.244 153.909 474.628 153.929L474.576 154.927L474.575 154.928C474.212 154.909 473.85 154.9 473.49 154.905L473.483 154.406L473.476 153.906ZM519.08 153.344C519.239 153.384 519.394 153.415 519.546 153.437L519.405 154.426C519.217 154.399 519.027 154.362 518.835 154.313C518.698 154.279 518.561 154.235 518.423 154.188L518.751 153.245C518.863 153.284 518.973 153.317 519.08 153.344ZM502.477 153.588L502.479 153.589C502.377 153.726 502.276 153.862 502.179 153.999C502.083 154.134 501.99 154.272 501.896 154.41L501.896 154.409L501.067 153.849C501.164 153.706 501.263 153.563 501.362 153.422C501.463 153.279 501.567 153.135 501.674 152.991L502.477 153.588ZM511.851 153.164C511.957 153.231 512.066 153.3 512.176 153.373C512.245 153.419 512.314 153.467 512.384 153.515L511.825 154.341C511.758 154.296 511.692 154.251 511.626 154.208C511.522 154.139 511.419 154.074 511.32 154.012L511.851 153.164ZM522.739 153.712L522.8 153.853C522.43 154.013 522.067 154.146 521.707 154.247L521.437 153.287C521.746 153.2 522.066 153.083 522.403 152.937L522.739 153.712ZM537.381 152.61C537.593 152.718 537.812 152.831 538.038 152.949C538.178 153.023 538.323 153.098 538.471 153.176L538.239 153.62L538.238 153.619L538.007 154.062C537.859 153.985 537.71 153.907 537.573 153.835C537.349 153.717 537.136 153.606 536.93 153.502L537.182 153.006L537.381 152.61ZM531.488 153.105C531.331 153.172 531.171 153.244 531.008 153.321C530.818 153.412 530.629 153.507 530.438 153.608L530.202 153.164L529.97 152.726C530.171 152.619 530.374 152.515 530.578 152.418C530.754 152.334 530.928 152.257 531.101 152.185L531.488 153.105ZM508.46 153.246L508.176 153.332C508.018 153.387 507.856 153.462 507.689 153.559L507.188 152.694C507.337 152.608 507.487 152.532 507.64 152.468L507.846 152.389C507.971 152.345 508.094 152.309 508.215 152.278L508.46 153.246ZM517.064 152.365C517.18 152.44 517.294 152.513 517.404 152.58L516.883 153.433C516.763 153.36 516.642 153.284 516.52 153.204C516.449 153.158 516.38 153.11 516.31 153.062L516.864 152.234C516.931 152.279 516.998 152.322 517.064 152.365ZM509.744 152.232C509.988 152.28 510.233 152.353 510.483 152.45L510.31 152.9L510.121 153.383C509.918 153.304 509.731 153.248 509.553 153.213L509.655 152.691L509.744 152.232ZM525.979 152.083C525.767 152.214 525.56 152.344 525.356 152.469C525.188 152.572 525.02 152.669 524.856 152.766L524.603 152.34L524.348 151.91C524.507 151.816 524.669 151.718 524.835 151.616C525.036 151.493 525.241 151.365 525.453 151.233L525.979 152.083ZM534.144 151.566C534.302 151.575 534.459 151.591 534.613 151.613L534.474 152.603C534.279 152.575 534.081 152.561 533.878 152.561C533.72 152.561 533.563 152.568 533.404 152.584L533.306 151.591C533.496 151.572 533.686 151.561 533.878 151.561L534.144 151.566ZM549.054 151.076L549.917 151.58C549.82 151.746 549.721 151.909 549.618 152.068C549.515 152.229 549.406 152.391 549.291 152.551L548.479 151.969L548.477 151.967C548.581 151.822 548.682 151.675 548.777 151.527C548.871 151.381 548.962 151.229 549.052 151.075L549.054 151.076ZM515.854 151.531C515.922 151.58 515.99 151.628 516.058 151.676L515.769 152.084L515.479 152.49C515.412 152.442 515.343 152.395 515.274 152.346C515.202 152.294 515.129 152.243 515.055 152.19L515.114 152.107L515.634 151.375C515.708 151.428 515.781 151.48 515.854 151.531ZM543.01 151.2C543.194 151.229 543.378 151.244 543.567 151.244V152.244L543.323 152.237C543.165 152.229 543.01 152.212 542.855 152.188L543.01 151.2ZM544.133 152.176C543.948 152.221 543.76 152.244 543.567 152.244V151.244C543.686 151.244 543.794 151.231 543.895 151.206L544.133 152.176ZM503.917 150.585L504.546 151.361L504.547 151.362C504.283 151.576 504.031 151.804 503.793 152.042L503.086 151.335C503.346 151.075 503.623 150.823 503.917 150.585ZM514.5 150.593C514.578 150.644 514.656 150.696 514.732 150.748L514.174 151.577L514.172 151.578C514.098 151.528 514.025 151.477 513.95 151.428C513.834 151.351 513.714 151.274 513.593 151.198L514.125 150.352C514.253 150.432 514.378 150.513 514.5 150.593ZM540.021 149.969C540.241 150.084 540.448 150.192 540.647 150.294L540.419 150.738L540.191 151.184C539.988 151.079 539.777 150.97 539.558 150.854C539.413 150.779 539.265 150.701 539.112 150.622L539.574 149.734C539.729 149.814 539.879 149.894 540.021 149.969ZM546.493 149.923C546.409 150.068 546.325 150.209 546.237 150.346C546.128 150.516 546.02 150.672 545.912 150.816L545.512 150.518L545.111 150.218C545.203 150.095 545.299 149.958 545.396 149.806C545.475 149.683 545.553 149.555 545.63 149.422L546.493 149.923ZM529.533 150.101V150.103C529.409 150.158 529.285 150.213 529.163 150.271C528.943 150.376 528.725 150.487 528.508 150.602L528.042 149.716C528.269 149.596 528.499 149.48 528.732 149.369C528.862 149.307 528.994 149.247 529.127 149.188L529.533 150.101ZM511.636 149.132C511.94 149.234 512.234 149.351 512.515 149.478L512.105 150.39C511.851 150.275 511.587 150.172 511.315 150.081L511.316 150.08L511.324 150.057L511.636 149.132ZM506.868 149.023L507.147 149.983C507.014 150.022 506.879 150.066 506.743 150.113L506.369 150.257C506.336 150.271 506.303 150.286 506.271 150.301V150.299L506.069 149.841L505.869 149.383C506.046 149.306 506.227 149.234 506.412 149.169C506.565 149.115 506.717 149.067 506.868 149.023ZM536.625 148.497C536.986 148.601 537.331 148.72 537.659 148.848L537.297 149.78C536.993 149.662 536.676 149.553 536.348 149.458L536.625 148.497ZM508.775 148.707C509.109 148.694 509.434 148.703 509.75 148.73L509.662 149.727C509.387 149.702 509.104 149.695 508.815 149.707L508.812 149.605L508.795 149.207L508.775 148.707ZM550.436 148.213L551.357 148.601C551.217 148.934 551.073 149.267 550.925 149.597L550.469 149.391L550.013 149.186L550.011 149.185C550.155 148.865 550.296 148.54 550.434 148.212L550.436 148.213ZM531.853 149.321C531.583 149.38 531.32 149.449 531.062 149.526L530.918 149.049L530.774 148.569C531.055 148.485 531.343 148.409 531.639 148.345L531.853 149.321ZM533.878 148.099C534.068 148.099 534.255 148.104 534.438 148.115L534.379 149.113L534.378 149.114C534.357 149.113 534.336 149.111 534.315 149.11L533.878 149.099C533.741 149.099 533.605 149.102 533.47 149.108L533.425 148.108C533.574 148.102 533.726 148.099 533.878 148.099ZM547.768 147.241C547.638 147.554 547.506 147.857 547.376 148.151L546.464 147.747C546.59 147.462 546.717 147.165 546.845 146.857L547.768 147.241ZM552.549 145.681C552.486 145.832 552.422 145.984 552.359 146.138C552.294 146.298 552.228 146.46 552.162 146.624L551.698 146.436L551.235 146.249C551.302 146.084 551.368 145.921 551.435 145.759C551.498 145.603 551.563 145.448 551.627 145.295L552.549 145.681ZM548.917 144.427C548.844 144.602 548.772 144.776 548.701 144.948C548.638 145.102 548.576 145.256 548.515 145.407L547.587 145.031C547.649 144.879 547.712 144.725 547.775 144.569C547.847 144.395 547.92 144.219 547.994 144.041L548.917 144.427ZM552.898 142.572L553.338 142.811L553.776 143.05C553.629 143.322 553.484 143.605 553.342 143.899L552.892 143.681L552.443 143.464C552.541 143.262 552.639 143.063 552.74 142.869L552.898 142.572ZM550.365 141.367C550.182 141.696 550.011 142.032 549.845 142.369L549.842 142.368L548.945 141.926C549.118 141.576 549.3 141.226 549.492 140.881L550.365 141.367ZM555.29 140.883C555.21 140.967 555.13 141.055 555.051 141.147C554.956 141.258 554.862 141.373 554.77 141.493L553.978 140.885C554.011 140.841 554.043 140.797 554.077 140.755L554.293 140.495C554.383 140.391 554.474 140.29 554.566 140.193L555.29 140.883ZM556.921 139.745C556.719 139.824 556.525 139.918 556.338 140.029L555.827 139.171C556.06 139.032 556.304 138.913 556.559 138.814L556.921 139.745ZM552.234 138.731C552.139 138.833 552.047 138.937 551.956 139.042C551.834 139.184 551.715 139.331 551.6 139.48L551.599 139.479L551.204 139.173L550.81 138.865C550.935 138.704 551.065 138.545 551.198 138.39C551.297 138.275 551.399 138.161 551.503 138.049L552.234 138.731ZM558.901 138.426L559.107 139.404C558.928 139.442 558.742 139.462 558.553 139.462C558.434 139.462 558.319 139.465 558.208 139.472L558.146 138.475C558.169 138.473 558.192 138.471 558.216 138.47L558.553 138.462C558.673 138.462 558.789 138.447 558.9 138.424L558.901 138.426ZM560 137.23C560 137.111 559.978 136.995 559.94 136.886H559.942L560.889 136.562C560.961 136.772 561 136.998 561 137.23C561 137.463 560.961 137.688 560.889 137.899L560.415 137.736L559.942 137.575L559.94 137.574C559.965 137.504 559.983 137.431 559.992 137.356L560 137.23ZM554.168 137.154L554.169 137.155C553.928 137.299 553.697 137.454 553.476 137.618L553.475 137.617L553.176 137.216L552.878 136.814C553.126 136.631 553.385 136.457 553.656 136.295L554.168 137.154ZM556.259 135.252L556.461 136.131L556.482 136.227C556.205 136.29 555.938 136.369 555.68 136.457V136.455L555.357 135.508C555.648 135.409 555.949 135.323 556.259 135.252ZM558.553 135C558.742 135 558.928 135.02 559.107 135.058L558.901 136.036L558.9 136.037C558.835 136.023 558.769 136.013 558.7 136.007L558.553 136C558.411 136 558.272 136.003 558.135 136.009L558.094 135.01C558.245 135.004 558.398 135 558.553 135Z" fill="var(--subtle-foreground)" /> + <rect x="358.5" y="120.5" width="63" height="63" rx="7.5" fill="var(--background)" /> + <rect x="358.5" y="120.5" width="63" height="63" rx="7.5" stroke="var(--border-strong)" stroke-dasharray="1 3" /> + <path d="M370 150C370 148.895 370.895 148 372 148H374C375.105 148 377 148.895 377 150V170C377 171.105 375.105 172 374 172H372C370.895 172 370 171.105 370 170V150Z" fill="var(--background)" /> + <path d="M381 140C381 138.895 381.895 138 383 138H386C387.105 138 388 138.895 388 140V170C388 171.105 387.105 172 386 172H383C381.895 172 381 171.105 381 170V140Z" fill="var(--background)" /> + <path d="M392 156C392 154.895 392.895 154 394 154H397C398.105 154 399 154.895 399 156V170C399 171.105 398.105 172 397 172H394C392.895 172 392 171.105 392 170V156Z" fill="var(--background)" /> + <path d="M403 134C403 132.895 403.895 132 405 132H408C409.105 132 410 132.895 410 134V170C410 171.105 409.105 172 408 172H405C403.895 172 403 171.105 403 170V134Z" fill="var(--background)" /> + <path d="M371.74 170.966C371.822 170.988 371.909 171 372 171V172C371.821 172 371.648 171.974 371.482 171.93L371.74 170.966ZM372.333 171V172H372V171H372.333ZM382.74 170.966C382.822 170.988 382.909 171 383 171V172C382.821 172 382.648 171.974 382.482 171.93L382.74 170.966ZM383.5 171V172H383V171H383.5ZM393.74 170.966C393.822 170.988 393.909 171 394 171V172C393.821 172 393.648 171.974 393.482 171.93L393.74 170.966ZM394.5 171V172H394V171H394.5ZM404.74 170.966C404.822 170.988 404.909 171 405 171V172C404.821 172 404.648 171.974 404.482 171.93L404.74 170.966ZM405.5 171V172H405V171H405.5ZM408 171V172H407.5V171H408ZM408.517 171.93C408.352 171.974 408.179 172 408 172V171C408.091 171 408.178 170.988 408.26 170.966L408.517 171.93ZM397 171V172H396.5V171H397ZM397.517 171.93C397.352 171.974 397.179 172 397 172V171C397.091 171 397.178 170.988 397.26 170.966L397.517 171.93ZM386 171V172H385.5V171H386ZM386.517 171.93C386.352 171.974 386.179 172 386 172V171C386.091 171 386.178 170.988 386.26 170.966L386.517 171.93ZM374 171V172H373.667V171H374ZM374.64 171.919C374.412 171.97 374.194 172 374 172V171C374.104 171 374.247 170.984 374.421 170.945L374.64 171.919ZM377 170C377 170.213 376.928 170.416 376.807 170.608L375.963 170.076C375.985 170.042 375.994 170.018 375.998 170.007C376.001 169.996 376 169.995 376 170V169.523H377V170ZM370 170V169.523H371V170C371 170.091 371.012 170.178 371.034 170.26L370.069 170.517C370.025 170.352 370 170.179 370 170ZM381 170V169.5H382V170C382 170.091 382.012 170.178 382.034 170.26L381.069 170.517C381.025 170.352 381 170.179 381 170ZM388 170C388 170.179 387.974 170.352 387.93 170.517L386.966 170.26C386.988 170.178 387 170.091 387 170V169.5H388V170ZM392 170V169.533H393V170C393 170.091 393.012 170.178 393.034 170.26L392.069 170.517C392.025 170.352 392 170.179 392 170ZM399 170C399 170.179 398.974 170.352 398.93 170.517L397.966 170.26C397.988 170.178 398 170.091 398 170V169.533H399V170ZM403 170V169.5H404V170C404 170.091 404.012 170.178 404.034 170.26L403.069 170.517C403.025 170.352 403 170.179 403 170ZM410 170C410 170.179 409.974 170.352 409.93 170.517L408.966 170.26C408.988 170.178 409 170.091 409 170V169.5H410V170ZM393 166.733V167.667H392V166.733H393ZM399 166.733V167.667H398V166.733H399ZM371 166.667V167.619H370V166.667H371ZM377 166.667V167.619H376V166.667H377ZM382 166.5V167.5H381V166.5H382ZM404 166.5V167.5H403V166.5H404ZM410 166.5V167.5H409V166.5H410ZM388 166.5V167.5H387V166.5H388ZM393 163.934V164.866H392V163.934H393ZM399 163.934V164.866H398V163.934H399ZM371 163.81V164.762H370V163.81H371ZM377 163.81V164.762H376V163.81H377ZM382 163.5V164.5H381V163.5H382ZM404 163.5V164.5H403V163.5H404ZM410 163.5V164.5H409V163.5H410ZM388 163.5V164.5H387V163.5H388ZM393 161.134V162.066H392V161.134H393ZM399 161.134V162.066H398V161.134H399ZM371 160.952V161.904H370V160.952H371ZM377 160.952V161.904H376V160.952H377ZM382 160.5V161.5H381V160.5H382ZM404 160.5V161.5H403V160.5H404ZM410 160.5V161.5H409V160.5H410ZM388 160.5V161.5H387V160.5H388ZM393 158.333V159.267H392V158.333H393ZM399 158.333V159.267H398V158.333H399ZM371 158.096V159.048H370V158.096H371ZM377 158.096V159.048H376V158.096H377ZM382 157.5V158.5H381V157.5H382ZM404 157.5V158.5H403V157.5H404ZM410 157.5V158.5H409V157.5H410ZM388 157.5V158.5H387V157.5H388ZM392 156C392 155.821 392.025 155.648 392.069 155.482L393.034 155.74C393.012 155.822 393 155.909 393 156V156.467H392V156ZM399 156.467H398V156C398 155.909 397.988 155.822 397.966 155.74L398.93 155.482C398.974 155.648 399 155.821 399 156V156.467ZM371 155.238V156.19H370V155.238H371ZM377 155.238V156.19H376V155.238H377ZM382 154.5V155.5H381V154.5H382ZM404 154.5V155.5H403V154.5H404ZM410 154.5V155.5H409V154.5H410ZM388 154.5V155.5H387V154.5H388ZM394.5 154V155H394C393.909 155 393.822 155.012 393.74 155.034L393.482 154.069C393.648 154.025 393.821 154 394 154H394.5ZM397 154C397.179 154 397.352 154.025 397.517 154.069L397.26 155.034C397.178 155.012 397.091 155 397 155H396.5V154H397ZM371 152.381V153.333H370V152.381H371ZM377 152.381V153.333H376V152.381H377ZM382 151.5V152.5H381V151.5H382ZM404 151.5V152.5H403V151.5H404ZM410 151.5V152.5H409V151.5H410ZM388 151.5V152.5H387V151.5H388ZM370 150C370 149.821 370.025 149.648 370.069 149.482L371.034 149.74C371.012 149.822 371 149.909 371 150V150.477H370V150ZM377 150.477H376V150C376 150.005 376.001 150.004 375.998 149.993C375.994 149.982 375.985 149.958 375.963 149.924L376.807 149.391C376.928 149.583 377 149.787 377 150V150.477ZM382 148.5V149.5H381V148.5H382ZM404 148.5V149.5H403V148.5H404ZM410 148.5V149.5H409V148.5H410ZM388 148.5V149.5H387V148.5H388ZM374 148C374.194 148 374.412 148.029 374.64 148.08L374.421 149.055C374.247 149.016 374.104 149 374 149H373.667V148H374ZM372.333 148V149H372C371.909 149 371.822 149.012 371.74 149.034L371.482 148.069C371.648 148.025 371.821 148 372 148H372.333ZM382 145.5V146.5H381V145.5H382ZM404 145.5V146.5H403V145.5H404ZM410 145.5V146.5H409V145.5H410ZM388 145.5V146.5H387V145.5H388ZM382 142.5V143.5H381V142.5H382ZM404 142.5V143.5H403V142.5H404ZM410 142.5V143.5H409V142.5H410ZM388 142.5V143.5H387V142.5H388ZM381 140C381 139.821 381.025 139.648 381.069 139.482L382.034 139.74C382.012 139.822 382 139.909 382 140V140.5H381V140ZM404 139.5V140.5H403V139.5H404ZM410 139.5V140.5H409V139.5H410ZM388 140.5H387V140C387 139.909 386.988 139.822 386.966 139.74L387.93 139.482C387.974 139.648 388 139.821 388 140V140.5ZM383.5 138V139H383C382.909 139 382.822 139.012 382.74 139.034L382.482 138.069C382.648 138.025 382.821 138 383 138H383.5ZM386 138C386.179 138 386.352 138.025 386.517 138.069L386.26 139.034C386.178 139.012 386.091 139 386 139H385.5V138H386ZM404 136.5V137.5H403V136.5H404ZM410 136.5V137.5H409V136.5H410ZM403 134C403 133.821 403.025 133.648 403.069 133.482L404.034 133.74C404.012 133.822 404 133.909 404 134V134.5H403V134ZM410 134.5H409V134C409 133.909 408.988 133.822 408.966 133.74L409.472 133.605L409.93 133.482C409.974 133.648 410 133.821 410 134V134.5ZM405.5 132V133H405C404.909 133 404.822 133.012 404.74 133.034L404.482 132.069C404.648 132.025 404.821 132 405 132H405.5ZM408 132C408.179 132 408.352 132.025 408.517 132.069L408.26 133.034C408.178 133.012 408.091 133 408 133H407.5V132H408Z" fill="var(--subtle-foreground)" /> + <g> + <rect width="262" height="96" transform="translate(334)" fill="var(--background)" /> + <mask id="docs-cta_illustration-path-138-inside-1_25523_22528" fill="white"> + <path d="M334 64H596V96H334V64Z" /> + </mask> + <path d="M334 64H596V96H334V64Z" fill="var(--background)" /> + <path d="M596 64V63.5H595.504V64V64.5H596V64ZM592.527 64V63.5H591.534V64V64.5H592.527V64ZM588.557 64V63.5H587.564V64V64.5H588.557V64ZM584.587 64V63.5H583.595V64V64.5H584.587V64ZM580.617 64V63.5H579.625V64V64.5H580.617V64ZM576.648 64V63.5H575.655V64V64.5H576.648V64ZM572.678 64V63.5H571.686V64V64.5H572.678V64ZM568.708 64V63.5H567.716V64V64.5H568.708V64ZM564.739 64V63.5H563.746V64V64.5H564.739V64ZM560.769 64V63.5H559.777V64V64.5H560.769V64ZM556.799 64V63.5H555.807V64V64.5H556.799V64ZM552.83 64V63.5H551.837V64V64.5H552.83V64ZM548.86 64V63.5H547.867V64V64.5H548.86V64ZM544.89 64V63.5H543.898V64V64.5H544.89V64ZM540.92 64V63.5H539.928V64V64.5H540.92V64ZM536.951 64V63.5H535.958V64V64.5H536.951V64ZM532.981 64V63.5H531.989V64V64.5H532.981V64ZM529.011 64V63.5H528.019V64V64.5H529.011V64ZM525.042 64V63.5H524.049V64V64.5H525.042V64ZM521.072 64V63.5H520.08V64V64.5H521.072V64ZM517.102 64V63.5H516.11V64V64.5H517.102V64ZM513.133 64V63.5H512.14V64V64.5H513.133V64ZM509.163 64V63.5H508.17V64V64.5H509.163V64ZM505.193 64V63.5H504.201V64V64.5H505.193V64ZM501.224 64V63.5H500.231V64V64.5H501.224V64ZM497.254 64V63.5H496.261V64V64.5H497.254V64ZM493.284 64V63.5H492.292V64V64.5H493.284V64ZM489.314 64V63.5H488.322V64V64.5H489.314V64ZM485.345 64V63.5H484.352V64V64.5H485.345V64ZM481.375 64V63.5H480.383V64V64.5H481.375V64ZM477.405 64V63.5H476.413V64V64.5H477.405V64ZM473.436 64V63.5H472.443V64V64.5H473.436V64ZM469.466 64V63.5H468.474V64V64.5H469.466V64ZM465.496 64V63.5H464.504V64V64.5H465.496V64ZM461.527 64V63.5H460.534V64V64.5H461.527V64ZM457.557 64V63.5H456.564V64V64.5H457.557V64ZM453.587 64V63.5H452.595V64V64.5H453.587V64ZM449.617 64V63.5H448.625V64V64.5H449.617V64ZM445.648 64V63.5H444.655V64V64.5H445.648V64ZM441.678 64V63.5H440.686V64V64.5H441.678V64ZM437.708 64V63.5H436.716V64V64.5H437.708V64ZM433.739 64V63.5H432.746V64V64.5H433.739V64ZM429.769 64V63.5H428.776V64V64.5H429.769V64ZM425.799 64V63.5H424.807V64V64.5H425.799V64ZM421.829 64V63.5H420.837V64V64.5H421.829V64ZM417.86 64V63.5H416.867V64V64.5H417.86V64ZM413.89 64V63.5H412.898V64V64.5H413.89V64ZM409.92 64V63.5H408.928V64V64.5H409.92V64ZM405.951 64V63.5H404.958V64V64.5H405.951V64ZM401.981 64V63.5H400.988V64V64.5H401.981V64ZM398.011 64V63.5H397.019V64V64.5H398.011V64ZM394.041 64V63.5H393.049V64V64.5H394.041V64ZM390.072 64V63.5H389.079V64V64.5H390.072V64ZM386.102 64V63.5H385.11V64V64.5H386.102V64ZM382.132 64V63.5H381.14V64V64.5H382.132V64ZM378.163 64V63.5H377.17V64V64.5H378.163V64ZM374.193 64V63.5H373.2V64V64.5H374.193V64ZM370.223 64V63.5H369.231V64V64.5H370.223V64ZM366.253 64V63.5H365.261V64V64.5H366.253V64ZM362.284 64V63.5H361.291V64V64.5H362.284V64ZM358.314 64V63.5H357.322V64V64.5H358.314V64ZM354.344 64V63.5H353.352V64V64.5H354.344V64ZM350.375 64V63.5H349.382V64V64.5H350.375V64ZM346.405 64V63.5H345.412V64V64.5H346.405V64ZM342.435 64V63.5H341.443V64V64.5H342.435V64ZM338.465 64V63.5H337.473V64V64.5H338.465V64ZM334.496 64V63.5H334V64V64.5H334.496V64ZM596 64V63H595.504V64V65H596V64ZM592.527 64V63H591.534V64V65H592.527V64ZM588.557 64V63H587.564V64V65H588.557V64ZM584.587 64V63H583.595V64V65H584.587V64ZM580.617 64V63H579.625V64V65H580.617V64ZM576.648 64V63H575.655V64V65H576.648V64ZM572.678 64V63H571.686V64V65H572.678V64ZM568.708 64V63H567.716V64V65H568.708V64ZM564.739 64V63H563.746V64V65H564.739V64ZM560.769 64V63H559.777V64V65H560.769V64ZM556.799 64V63H555.807V64V65H556.799V64ZM552.83 64V63H551.837V64V65H552.83V64ZM548.86 64V63H547.867V64V65H548.86V64ZM544.89 64V63H543.898V64V65H544.89V64ZM540.92 64V63H539.928V64V65H540.92V64ZM536.951 64V63H535.958V64V65H536.951V64ZM532.981 64V63H531.989V64V65H532.981V64ZM529.011 64V63H528.019V64V65H529.011V64ZM525.042 64V63H524.049V64V65H525.042V64ZM521.072 64V63H520.08V64V65H521.072V64ZM517.102 64V63H516.11V64V65H517.102V64ZM513.133 64V63H512.14V64V65H513.133V64ZM509.163 64V63H508.17V64V65H509.163V64ZM505.193 64V63H504.201V64V65H505.193V64ZM501.224 64V63H500.231V64V65H501.224V64ZM497.254 64V63H496.261V64V65H497.254V64ZM493.284 64V63H492.292V64V65H493.284V64ZM489.314 64V63H488.322V64V65H489.314V64ZM485.345 64V63H484.352V64V65H485.345V64ZM481.375 64V63H480.383V64V65H481.375V64ZM477.405 64V63H476.413V64V65H477.405V64ZM473.436 64V63H472.443V64V65H473.436V64ZM469.466 64V63H468.474V64V65H469.466V64ZM465.496 64V63H464.504V64V65H465.496V64ZM461.527 64V63H460.534V64V65H461.527V64ZM457.557 64V63H456.564V64V65H457.557V64ZM453.587 64V63H452.595V64V65H453.587V64ZM449.617 64V63H448.625V64V65H449.617V64ZM445.648 64V63H444.655V64V65H445.648V64ZM441.678 64V63H440.686V64V65H441.678V64ZM437.708 64V63H436.716V64V65H437.708V64ZM433.739 64V63H432.746V64V65H433.739V64ZM429.769 64V63H428.776V64V65H429.769V64ZM425.799 64V63H424.807V64V65H425.799V64ZM421.829 64V63H420.837V64V65H421.829V64ZM417.86 64V63H416.867V64V65H417.86V64ZM413.89 64V63H412.898V64V65H413.89V64ZM409.92 64V63H408.928V64V65H409.92V64ZM405.951 64V63H404.958V64V65H405.951V64ZM401.981 64V63H400.988V64V65H401.981V64ZM398.011 64V63H397.019V64V65H398.011V64ZM394.041 64V63H393.049V64V65H394.041V64ZM390.072 64V63H389.079V64V65H390.072V64ZM386.102 64V63H385.11V64V65H386.102V64ZM382.132 64V63H381.14V64V65H382.132V64ZM378.163 64V63H377.17V64V65H378.163V64ZM374.193 64V63H373.2V64V65H374.193V64ZM370.223 64V63H369.231V64V65H370.223V64ZM366.253 64V63H365.261V64V65H366.253V64ZM362.284 64V63H361.291V64V65H362.284V64ZM358.314 64V63H357.322V64V65H358.314V64ZM354.344 64V63H353.352V64V65H354.344V64ZM350.375 64V63H349.382V64V65H350.375V64ZM346.405 64V63H345.412V64V65H346.405V64ZM342.435 64V63H341.443V64V65H342.435V64ZM338.465 64V63H337.473V64V65H338.465V64ZM334.496 64V63H334V64V65H334.496V64Z" fill="var(--border-strong)" mask="url(#docs-cta_illustration-path-138-inside-1_25523_22528)" /> + <path d="M365.583 74.5L365.882 75.0589C366.084 75.4367 366.185 75.6257 366.119 75.7066C366.052 75.7875 365.832 75.7221 365.392 75.5914C364.951 75.4608 364.484 75.3905 364 75.3905C361.377 75.3905 359.25 77.4542 359.25 80C359.25 80.8396 359.481 81.6268 359.885 82.3048M362.417 85.5L362.118 84.9411C361.916 84.5633 361.814 84.3743 361.881 84.2934C361.948 84.2125 362.168 84.2779 362.608 84.4086C363.049 84.5392 363.516 84.6095 364 84.6095C366.623 84.6095 368.75 82.5458 368.75 80C368.75 79.1604 368.519 78.3732 368.115 77.6952" stroke="var(--subtle-foreground)" stroke-linecap="round" stroke-linejoin="round" /> + <path d="M494.941 83.9994V82.5834L495.061 82.6194C494.925 83.0754 494.653 83.4394 494.245 83.7114C493.845 83.9834 493.369 84.1194 492.817 84.1194C492.257 84.1194 491.769 83.9874 491.353 83.7234C490.945 83.4594 490.629 83.0914 490.405 82.6194C490.181 82.1474 490.069 81.5954 490.069 80.9634C490.069 80.3234 490.185 79.7634 490.417 79.2834C490.649 78.8034 490.973 78.4314 491.389 78.1674C491.805 77.9034 492.293 77.7714 492.853 77.7714C493.429 77.7714 493.909 77.9114 494.293 78.1914C494.677 78.4634 494.933 78.8514 495.061 79.3554L494.881 79.3794V75.3594H496.057V83.9994H494.941ZM493.093 83.1234C493.661 83.1234 494.109 82.9314 494.437 82.5474C494.765 82.1554 494.929 81.6154 494.929 80.9274C494.929 80.2554 494.761 79.7274 494.425 79.3434C494.097 78.9514 493.649 78.7554 493.081 78.7554C492.521 78.7554 492.077 78.9514 491.749 79.3434C491.429 79.7274 491.269 80.2674 491.269 80.9634C491.269 81.6434 491.429 82.1754 491.749 82.5594C492.077 82.9354 492.525 83.1234 493.093 83.1234Z" fill="var(--subtle-foreground)" /> + <path d="M486.119 83.9998V77.8798H487.235V79.4038H487.295V83.9998H486.119ZM487.295 80.8078L487.139 79.3558C487.283 78.8358 487.531 78.4398 487.883 78.1678C488.243 77.8958 488.663 77.7598 489.143 77.7598C489.335 77.7598 489.467 77.7758 489.539 77.8078V78.9238C489.499 78.9078 489.443 78.8998 489.371 78.8998C489.299 78.8918 489.211 78.8878 489.107 78.8878C488.507 78.8878 488.055 79.0518 487.751 79.3798C487.447 79.6998 487.295 80.1758 487.295 80.8078Z" fill="var(--subtle-foreground)" /> + <path d="M483.383 83.9998C483.351 83.8558 483.327 83.6918 483.311 83.5078C483.295 83.3238 483.287 83.0958 483.287 82.8238H483.227V79.9078C483.227 79.4998 483.127 79.1958 482.927 78.9958C482.727 78.7958 482.411 78.6958 481.979 78.6958C481.555 78.6958 481.215 78.7798 480.959 78.9478C480.711 79.1158 480.567 79.3518 480.527 79.6558H479.411C479.459 79.0718 479.715 78.6118 480.179 78.2758C480.643 77.9318 481.259 77.7598 482.027 77.7598C482.811 77.7598 483.399 77.9438 483.791 78.3118C484.183 78.6798 484.379 79.2238 484.379 79.9438V82.8238C484.379 83.0078 484.391 83.1958 484.415 83.3878C484.447 83.5798 484.483 83.7838 484.523 83.9998H483.383ZM481.271 84.1198C480.647 84.1198 480.151 83.9678 479.783 83.6638C479.415 83.3518 479.231 82.9318 479.231 82.4038C479.231 81.8598 479.423 81.4318 479.807 81.1198C480.191 80.8078 480.743 80.5958 481.463 80.4838L483.503 80.1478V80.9998L481.643 81.2998C481.227 81.3638 480.911 81.4758 480.695 81.6358C480.487 81.7878 480.383 82.0118 480.383 82.3078C480.383 82.5958 480.479 82.8118 480.671 82.9558C480.871 83.0998 481.155 83.1718 481.523 83.1718C482.019 83.1718 482.427 83.0558 482.747 82.8238C483.067 82.5918 483.227 82.2958 483.227 81.9358L483.395 82.8118C483.251 83.2278 482.995 83.5518 482.627 83.7838C482.259 84.0078 481.807 84.1198 481.271 84.1198Z" fill="var(--subtle-foreground)" /> + <path d="M475.188 84.1198C474.564 84.1198 474.016 83.9838 473.544 83.7118C473.08 83.4318 472.716 83.0518 472.452 82.5718C472.196 82.0838 472.068 81.5318 472.068 80.9158C472.068 80.2918 472.2 79.7438 472.464 79.2718C472.728 78.7998 473.092 78.4318 473.556 78.1678C474.02 77.8958 474.564 77.7598 475.188 77.7598C475.812 77.7598 476.356 77.8958 476.82 78.1678C477.284 78.4318 477.644 78.7998 477.9 79.2718C478.164 79.7438 478.296 80.2918 478.296 80.9158C478.296 81.5318 478.164 82.0838 477.9 82.5718C477.644 83.0518 477.284 83.4318 476.82 83.7118C476.356 83.9838 475.812 84.1198 475.188 84.1198ZM475.188 83.1478C475.564 83.1478 475.896 83.0558 476.184 82.8718C476.472 82.6798 476.696 82.4158 476.856 82.0798C477.016 81.7438 477.096 81.3518 477.096 80.9038C477.096 80.2398 476.92 79.7118 476.568 79.3198C476.216 78.9198 475.756 78.7198 475.188 78.7198C474.62 78.7198 474.16 78.9198 473.808 79.3198C473.456 79.7118 473.28 80.2398 473.28 80.9038C473.28 81.3518 473.36 81.7438 473.52 82.0798C473.68 82.4158 473.904 82.6798 474.192 82.8718C474.48 83.0558 474.812 83.1478 475.188 83.1478Z" fill="var(--subtle-foreground)" /> + <path d="M468.37 84.1074C467.794 84.1074 467.31 83.9714 466.918 83.6994C466.534 83.4194 466.282 83.0274 466.162 82.5234L466.282 82.4994V83.9994H465.166V75.3594H466.342V79.2834L466.21 79.2594C466.346 78.7874 466.614 78.4194 467.014 78.1554C467.414 77.8914 467.894 77.7594 468.454 77.7594C468.998 77.7594 469.47 77.8914 469.87 78.1554C470.278 78.4194 470.594 78.7874 470.818 79.2594C471.042 79.7314 471.154 80.2834 471.154 80.9154C471.154 81.5554 471.038 82.1154 470.806 82.5954C470.574 83.0754 470.25 83.4474 469.834 83.7114C469.418 83.9754 468.93 84.1074 468.37 84.1074ZM468.13 83.1234C468.698 83.1234 469.142 82.9274 469.462 82.5354C469.79 82.1434 469.954 81.6034 469.954 80.9154C469.954 80.2354 469.79 79.7034 469.462 79.3194C469.134 78.9354 468.69 78.7434 468.13 78.7434C467.57 78.7434 467.122 78.9394 466.786 79.3314C466.458 79.7234 466.294 80.2634 466.294 80.9514C466.294 81.6234 466.458 82.1554 466.786 82.5474C467.122 82.9314 467.57 83.1234 468.13 83.1234Z" fill="var(--subtle-foreground)" /> + <path d="M457.923 83.9994V75.3594H459.099V83.9994H457.923ZM462.279 83.9994V80.0394C462.279 79.6074 462.167 79.2834 461.943 79.0674C461.727 78.8514 461.407 78.7434 460.983 78.7434C460.615 78.7434 460.287 78.8274 459.999 78.9954C459.719 79.1554 459.499 79.3834 459.339 79.6794C459.179 79.9674 459.099 80.3034 459.099 80.6874L458.943 79.2354C459.143 78.7794 459.451 78.4194 459.867 78.1554C460.291 77.8914 460.779 77.7594 461.331 77.7594C461.979 77.7594 462.491 77.9434 462.867 78.3114C463.251 78.6794 463.443 79.1634 463.443 79.7634V83.9994H462.279Z" fill="var(--subtle-foreground)" /> + <path d="M454.318 84.1198C453.534 84.1198 452.906 83.9438 452.434 83.5918C451.97 83.2398 451.706 82.7438 451.642 82.1038H452.746C452.794 82.4718 452.954 82.7518 453.226 82.9438C453.506 83.1278 453.878 83.2198 454.342 83.2198C454.75 83.2198 455.066 83.1478 455.29 83.0038C455.514 82.8598 455.626 82.6518 455.626 82.3798C455.626 82.1798 455.562 82.0118 455.434 81.8758C455.306 81.7398 455.058 81.6238 454.69 81.5278L453.706 81.2878C453.106 81.1438 452.658 80.9238 452.362 80.6278C452.066 80.3238 451.918 79.9518 451.918 79.5118C451.918 78.9678 452.122 78.5398 452.53 78.2278C452.938 77.9158 453.494 77.7598 454.198 77.7598C454.902 77.7598 455.47 77.9198 455.902 78.2398C456.334 78.5598 456.574 79.0038 456.622 79.5718H455.53C455.482 79.2758 455.338 79.0478 455.098 78.8878C454.866 78.7278 454.554 78.6478 454.162 78.6478C453.786 78.6478 453.498 78.7158 453.298 78.8518C453.098 78.9878 452.998 79.1798 452.998 79.4278C452.998 79.6198 453.07 79.7838 453.214 79.9198C453.366 80.0478 453.618 80.1598 453.97 80.2558L454.966 80.5078C455.55 80.6598 455.986 80.8958 456.274 81.2158C456.57 81.5358 456.718 81.9198 456.718 82.3678C456.718 82.9198 456.506 83.3518 456.082 83.6638C455.666 83.9678 455.078 84.1198 454.318 84.1198Z" fill="var(--subtle-foreground)" /> + <path d="M449.469 83.9998C449.437 83.8558 449.413 83.6918 449.397 83.5078C449.381 83.3238 449.373 83.0958 449.373 82.8238H449.313V79.9078C449.313 79.4998 449.213 79.1958 449.013 78.9958C448.813 78.7958 448.497 78.6958 448.065 78.6958C447.641 78.6958 447.301 78.7798 447.045 78.9478C446.797 79.1158 446.653 79.3518 446.613 79.6558H445.497C445.545 79.0718 445.801 78.6118 446.265 78.2758C446.729 77.9318 447.345 77.7598 448.113 77.7598C448.897 77.7598 449.485 77.9438 449.877 78.3118C450.269 78.6798 450.465 79.2238 450.465 79.9438V82.8238C450.465 83.0078 450.477 83.1958 450.501 83.3878C450.533 83.5798 450.569 83.7838 450.609 83.9998H449.469ZM447.357 84.1198C446.733 84.1198 446.237 83.9678 445.869 83.6638C445.501 83.3518 445.317 82.9318 445.317 82.4038C445.317 81.8598 445.509 81.4318 445.893 81.1198C446.277 80.8078 446.829 80.5958 447.549 80.4838L449.589 80.1478V80.9998L447.729 81.2998C447.313 81.3638 446.997 81.4758 446.781 81.6358C446.573 81.7878 446.469 82.0118 446.469 82.3078C446.469 82.5958 446.565 82.8118 446.757 82.9558C446.957 83.0998 447.241 83.1718 447.609 83.1718C448.105 83.1718 448.513 83.0558 448.833 82.8238C449.153 82.5918 449.313 82.2958 449.313 81.9358L449.481 82.8118C449.337 83.2278 449.081 83.5518 448.713 83.7838C448.345 84.0078 447.893 84.1198 447.357 84.1198Z" fill="var(--subtle-foreground)" /> + <path d="M442.769 83.9994V82.5834L442.889 82.6194C442.753 83.0754 442.481 83.4394 442.073 83.7114C441.673 83.9834 441.197 84.1194 440.645 84.1194C440.085 84.1194 439.597 83.9874 439.181 83.7234C438.773 83.4594 438.457 83.0914 438.233 82.6194C438.009 82.1474 437.897 81.5954 437.897 80.9634C437.897 80.3234 438.013 79.7634 438.245 79.2834C438.477 78.8034 438.801 78.4314 439.217 78.1674C439.633 77.9034 440.121 77.7714 440.681 77.7714C441.257 77.7714 441.737 77.9114 442.121 78.1914C442.505 78.4634 442.761 78.8514 442.889 79.3554L442.709 79.3794V75.3594H443.885V83.9994H442.769ZM440.921 83.1234C441.489 83.1234 441.937 82.9314 442.265 82.5474C442.593 82.1554 442.757 81.6154 442.757 80.9274C442.757 80.2554 442.589 79.7274 442.253 79.3434C441.925 78.9514 441.477 78.7554 440.909 78.7554C440.349 78.7554 439.905 78.9514 439.577 79.3434C439.257 79.7274 439.097 80.2674 439.097 80.9634C439.097 81.6434 439.257 82.1754 439.577 82.5594C439.905 82.9354 440.353 83.1234 440.921 83.1234Z" fill="var(--subtle-foreground)" /> + <path d="M433.612 84.7795L436.684 74.9395H437.704L434.632 84.7795H433.612Z" fill="var(--subtle-foreground)" /> + <path d="M565.549 75.5C563.725 75.5033 562.769 75.5481 562.159 76.1588C561.5 76.8178 561.5 77.8785 561.5 79.9998C561.5 82.1211 561.5 83.1818 562.159 83.8408C562.818 84.4999 563.878 84.4999 565.999 84.4999C568.12 84.4999 569.181 84.4999 569.84 83.8408C570.45 83.2302 570.495 82.2746 570.498 80.4498M570.278 75.748L565.524 80.5292M570.497 78.0135C570.502 77.6613 570.525 75.9953 570.278 75.748C570.031 75.5007 568.367 75.5237 568.015 75.5288" stroke="var(--subtle-foreground)" stroke-linecap="round" stroke-linejoin="round" /> + </g> + </g> + </g> + </g> + </g> + </g> + <defs> + <linearGradient id="docs-cta_illustration-paint0_linear_25523_22528" x1="298" y1="0" x2="298" y2="380" gradientUnits="userSpaceOnUse"> + <stop stop-opacity="0" /> + <stop offset="0.2" /> + <stop offset="0.8" /> + <stop offset="1" stop-opacity="0" /> + </linearGradient> + <linearGradient id="docs-cta_illustration-paint1_linear_25523_22528" x1="0" y1="190" x2="334" y2="190" gradientUnits="userSpaceOnUse"> + <stop stop-opacity="0" /> + <stop offset="0.2" /> + <stop offset="0.8" /> + <stop offset="1" stop-opacity="0" /> + </linearGradient> + <linearGradient id="docs-cta_illustration-paint2_linear_25523_22528" x1="0" y1="190" x2="596" y2="190" gradientUnits="userSpaceOnUse"> + <stop stop-opacity="0" /> + <stop offset="0.2" /> + <stop offset="0.8" /> + <stop offset="1" stop-opacity="0" /> + </linearGradient> + <clipPath id="docs-cta_illustration-clip0_25523_22528"> + <rect width="596" height="380" fill="white" /> + </clipPath> + <clipPath id="docs-cta_illustration-clip1_25523_22528"> + <rect width="334" height="380" fill="white" /> + </clipPath> + <clipPath id="docs-cta_illustration-clip2_25523_22528"> + <rect width="262" height="380" fill="white" transform="translate(334)" /> + </clipPath> + </defs> +</svg> diff --git a/docs/app/reflex_docs/pages/docs_landing/views/enterprise.py b/docs/app/reflex_docs/pages/docs_landing/views/enterprise.py index 3ed024c832b..9d99c82f13c 100644 --- a/docs/app/reflex_docs/pages/docs_landing/views/enterprise.py +++ b/docs/app/reflex_docs/pages/docs_landing/views/enterprise.py @@ -9,11 +9,11 @@ def enterprise_section() -> rx.Component: rx.el.div( rx.el.h2( "Enterprise", - class_name="text-secondary-12 text-3xl font-[575]", + class_name="text-secondary-12 text-3xl font-book tracking-tight", ), rx.el.p( "Learn how to build enterprise-ready applications with Reflex.", - class_name="text-secondary-11 text-sm font-[475]", + class_name="text-secondary-11 text-sm font-normal", ), class_name="flex flex-col gap-4", ), diff --git a/docs/app/reflex_docs/pages/docs_landing/views/framework.py b/docs/app/reflex_docs/pages/docs_landing/views/framework.py index 81f1159fa07..d634ab45f6d 100644 --- a/docs/app/reflex_docs/pages/docs_landing/views/framework.py +++ b/docs/app/reflex_docs/pages/docs_landing/views/framework.py @@ -1,191 +1,215 @@ +"""Interactive framework overview with editorial product diagrams.""" + import reflex as rx import reflex_components_internal as ui -from reflex_site_shared.components.marketing_button import button -from reflex_site_shared.constants import REFLEX_ASSETS_CDN from reflex_docs.pages.docs import database, enterprise, getting_started from reflex_docs.pages.docs.library import library -from reflex_docs.pages.library_previews import core_components_dict +from reflex_docs.pages.docs_landing.views.artwork import artwork +CAPABILITIES = ( + ( + "how", + "How It Works", + "Define your interface and state in Python. Reflex connects them into a reactive web application.", + "From Python to a live interface", + "Read the introduction", + getting_started.introduction.path, + "blue", + ), + ( + "components", + "Components", + "Each component creates a part of your interface. Nest headings, inputs, and buttons inside layout components to build a page.", + "Python components. A complete interface.", + "Browse all components", + library.path, + "lavender", + ), + ( + "auth", + "Auth", + "Connect your identity provider and control who can access your application with enterprise authentication.", + "Your identity. Your access rules.", + "Explore authentication", + enterprise.auth.overview.path, + "peach", + ), + ( + "database", + "Database", + "Define typed models, query your database, and bring live data into your application with Python.", + "One model, from database to interface", + "Explore databases", + database.overview.path, + "mint", + ), +) -def docs_item( - icon: str, title: str, description: str, href: str, enterprise_only: bool = False -) -> rx.Component: - return rx.el.div( - rx.el.div( - ui.icon( - icon, - class_name="size-6 group-hover:text-primary-10 group-hover:dark:text-secondary-11", - stroke_width=1.5, - ), - rx.el.span( - title, - class_name="text-secondary-12 text-xl font-[575] group-hover:text-primary-10 group-hover:dark:text-secondary-11", - ), - rx.el.div( - "Enterprise-only", - class_name="text-secondary-12 text-xs font-medium bg-secondary-1 px-2.5 h-7 border-b border rounded-lg border-secondary-4 flex justify-center items-center ml-1", - ) - if enterprise_only - else None, - ui.icon( - "ArrowRight01Icon", - class_name="size-4 ml-auto group-hover:text-primary-10 group-hover:dark:text-secondary-11", - ), - class_name="flex row items-center gap-3 h-8", - ), - rx.el.p( - description, - class_name="text-secondary-11 text-sm font-[475]", - ), - rx.el.a(to=href, class_name="absolute inset-0"), - class_name="flex flex-col gap-2 py-8 pr-8 relative group lg:max-w-[21rem] w-full max-lg:text-start hover:bg-[linear-gradient(243deg,var(--secondary-2)_0%,var(--secondary-1)_100%)]", - ) +class FrameworkCounterState(rx.State): + """Keep the landing-page counter in real server-side Python state.""" -def links_section() -> rx.Component: - return rx.el.div( - docs_item( - "SourceCodeSquareIcon", - "How It Works", - "Learn the basics of how Reflex works behind the scenes and how its architecture enables flexible, advanced usage.", - getting_started.introduction.path, - ), - docs_item( - "ShieldUserIcon", - "Auth", - "Implement secure authentication for your apps using Reflexโ€™s built-in features and extensible architecture.", - enterprise.auth.overview.path, - enterprise_only=True, - ), - docs_item( - "DatabaseIcon", - "Database", - "Manage and interact with your data seamlessly using Reflexโ€™s straightforward models and querying approach.", - database.overview.path, - ), - class_name="flex flex-col border-r border-y border-secondary-4 divide-y divide-secondary-4", - ) + count: int = 0 + @rx.event + def increment(self): + """Increase the displayed count by one.""" + self.count += 1 -def component_link(name: str, href: str) -> rx.Component: - return rx.el.a( - button( - name, - ui.icon("ArrowRight01Icon", class_name="ml-auto"), - variant="ghost", - size="xs", - class_name="font-[525] w-full text-secondary-12 px-0", - ), - to=f"/library/{href.strip('/')}", - class_name="w-full", + +def example_window_header(title: str) -> rx.Component: + """Render the shared chrome of the code and preview windows.""" + return rx.el.div( + rx.el.span("โ€ขโ€ขโ€ข", aria_hidden=True, class_name="tracking-[3px]"), + rx.el.span(title), + class_name="flex items-center gap-5 border-b border-border px-5 py-3 text-xs text-muted-foreground", ) -def components_section() -> rx.Component: +def live_counter_diagram() -> rx.Component: + """Connect a readable Python example to a working counter interface.""" return rx.el.div( rx.el.div( - rx.el.div( - ui.icon("MenuSquareIcon", class_name="size-6", stroke_width=1.5), - rx.el.span( - "Component Library", - class_name="text-secondary-12 text-xl font-[575] group-hover:text-primary-10", + example_window_header("app.py"), + rx.el.pre( + rx.el.code( + "import reflex as rx\n\n" + "class State(rx.State):\n" + " count: int = 0\n\n" + " @rx.event\n" + " def increment(self):\n" + " self.count += 1", ), - class_name="flex row items-center gap-3 h-8", + class_name="m-0 px-5 py-5 font-mono text-[11px] leading-6 text-foreground sm:text-[13px]", ), - rx.el.p( - "Build your app with our comprehensive collection of UI components and features.", - class_name="text-secondary-11 text-sm font-[475] max-w-[16.5rem]", - ), - rx.el.a( - button( - "Browse All Components", - variant="outline", - native_button=False, - class_name="font-[525] w-fit text-secondary-12", - ), - to=library.path, - class_name="w-fit mt-4", - ), - class_name="flex flex-col gap-2 max-lg:text-start", + class_name="docs-framework-live-code w-full overflow-hidden rounded-xl border border-border bg-background", ), + rx.el.div(class_name="docs-framework-live-connector", aria_hidden=True), rx.el.div( + example_window_header("Live preview"), rx.el.div( - component_link( - "Data Display", core_components_dict["data-display"]["path"] - ), - component_link( - "Disclosure", core_components_dict["disclosure"]["path"] + rx.el.h3( + "Your first Reflex app", + class_name="text-xl font-book tracking-tight text-foreground", ), - component_link( - "Dynamic Rendering", - core_components_dict["dynamic_rendering"]["path"], + rx.el.div( + rx.el.output( + FrameworkCounterState.count, + aria_label="Counter value", + aria_live="polite", + aria_atomic=True, + class_name="docs-framework-counter-value flex min-h-16 min-w-20 items-center justify-center rounded-lg px-4 text-3xl tabular-nums", + ), + rx.el.button( + "Increment", + rx.icon("plus", size=16, aria_hidden=True), + type="button", + on_click=FrameworkCounterState.increment, + class_name="inline-flex min-h-11 items-center justify-center gap-2 rounded-full bg-foreground px-5 text-sm text-background transition-opacity hover:opacity-80 focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-ring", + ), + class_name="flex flex-wrap items-center justify-between gap-4", ), - component_link("Forms", core_components_dict["forms"]["path"]), - component_link("Layout", core_components_dict["layout"]["path"]), - component_link("Media", core_components_dict["media"]["path"]), - class_name="flex flex-col gap-2", + class_name="flex flex-col gap-5 p-5 sm:p-6", ), - rx.el.div( - component_link("Other", core_components_dict["other"]["path"]), - component_link("Overlays", core_components_dict["overlays"]["path"]), - component_link( - "Tables And Data Grids Rendering", - core_components_dict["tables_and_data_grids"]["path"], - ), - component_link( - "Typography", core_components_dict["typography"]["path"] - ), - class_name="flex flex-col gap-2", - ), - class_name="grid grid-cols-1 lg:grid-cols-2 lg:gap-28 gap-10 mt-auto", + class_name="docs-framework-live-app w-full overflow-hidden rounded-xl border border-border bg-background", ), - class_name="flex flex-col gap-4 lg:px-8 pt-8 max-lg:pr-8 pb-6 h-auto w-full flex-1 border-r border-secondary-4 border-y", + class_name="docs-framework-live mx-auto flex w-full max-w-[30rem] flex-col items-center py-4", ) -def squares_divider() -> rx.Component: - return rx.el.div( - rx.image( - src=f"{REFLEX_ASSETS_CDN}common/{rx.color_mode_cond('light', 'dark')}/squares_vertical_docs.svg", - alt="Squares Vertical Docs", - loading="lazy", - class_name="pointer-events-none w-auto h-full", +def framework_tab(value: str, title: str, description: str) -> rx.Component: + """Select a diagram with a keyboard-accessible vertical tab.""" + return ui.tabs.tab( + rx.el.span( + rx.el.span( + title, class_name="text-xl font-book tracking-tight sm:text-2xl" + ), + rx.el.span( + "Enterprise-only", + class_name="rounded-full border border-border bg-muted px-2 py-0.5 text-xs font-normal tracking-normal text-muted-foreground", + ) + if value == "auth" + else None, + class_name="flex flex-wrap items-center gap-3", ), - class_name="flex p-4.5 h-auto border-r border-y border-secondary-4 max-lg:hidden", + rx.el.span(description, class_name="docs-framework-tab-description"), + value=value, + aria_label=title, + unstyled=True, + class_name="docs-framework-tab w-full text-left text-foreground focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-ring", ) -def framework() -> rx.Component: - return rx.el.section( +def framework_panel( + value: str, caption: str, link: str, href: str, tone: str +) -> rx.Component: + """Pair an interactive example or illustration with its documentation link.""" + return ui.tabs.panel( rx.el.div( - rx.el.h2( - "Framework", - class_name="text-secondary-12 text-3xl font-[575]", - ), - rx.el.p( - "Learn how to build applications with Reflex Framework.", - class_name="text-secondary-11 text-sm font-[475]", - ), - class_name="flex flex-col gap-4", + live_counter_diagram() + if value == "how" + else artwork(f"framework_{value}", class_name="docs-framework-diagram"), + class_name="docs-framework-stage", ), rx.el.div( - rx.el.div( - class_name="absolute bottom-0 -left-24 w-24 h-px bg-gradient-to-r from-transparent to-current text-secondary-4" - ), - rx.el.div( - class_name="absolute top-0 -left-24 w-24 h-px bg-gradient-to-r from-transparent to-current text-secondary-4" + rx.el.p(caption, class_name="text-base font-book text-foreground"), + rx.el.a( + link, + rx.icon("arrow-right", class_name="size-4", aria_hidden=True), + href=href, + class_name="inline-flex min-h-10 w-fit items-center gap-2 rounded-full border border-border bg-background px-4 text-sm font-book text-foreground transition-colors hover:border-border-strong focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-ring", ), + class_name="docs-framework-caption flex flex-wrap items-center justify-between gap-x-6 gap-y-2 border-t border-border bg-muted px-6 py-5 sm:px-8", + ), + value=value, + unstyled=True, + custom_attrs={"data-tone": tone}, + class_name="docs-framework-panel min-w-0 overflow-hidden rounded-panel border border-border-subtle", + ) + + +def framework() -> rx.Component: + """Explore framework capabilities through tabs and product diagrams.""" + return rx.el.section( + ui.tabs.root( rx.el.div( - class_name="absolute bottom-0 -right-24 w-24 h-px bg-gradient-to-l from-transparent to-current text-secondary-4" + rx.el.div( + rx.el.h2( + "Framework", + id="docs-framework-title", + class_name="text-foreground text-3xl font-book tracking-tight", + ), + rx.el.p( + "Learn how to build applications with Reflex Framework.", + class_name="text-muted-foreground text-sm font-normal", + ), + class_name="flex flex-col gap-4", + ), + ui.tabs.list( + *[ + framework_tab(value, title, description) + for value, title, description, *_ in CAPABILITIES + ], + activate_on_focus=True, + aria_label="Framework capabilities", + unstyled=True, + class_name="docs-framework-tabs flex min-w-0 flex-col w-full", + ), + class_name="docs-framework-navigation flex min-w-0 flex-col gap-6", ), rx.el.div( - class_name="absolute right-0 -top-24 h-24 w-px bg-gradient-to-b from-transparent to-current text-secondary-4" + *[ + framework_panel(value, caption, link, href, tone) + for value, _, _, caption, link, href, tone in CAPABILITIES + ], + class_name="min-w-0", ), - links_section(), - squares_divider(), - components_section(), - class_name="flex flex-col lg:flex-row relative", + default_value="how", + orientation="vertical", + unstyled=True, + class_name="grid grid-cols-1 items-center gap-8 lg:grid-cols-[minmax(0,1fr)_minmax(0,2fr)] lg:gap-12", ), - class_name="flex flex-col gap-10 max-lg:text-center relative max-w-(--landing-layout-max-width) mx-auto w-full justify-start lg:mb-24 mb-10 max-xl:px-6 overflow-hidden", + aria_labelledby="docs-framework-title", + class_name="docs-framework-section flex flex-col gap-10 max-w-(--landing-layout-max-width) mx-auto w-full lg:pt-24 pt-10 lg:mb-24 mb-10 max-xl:px-6", ) diff --git a/docs/app/reflex_docs/pages/docs_landing/views/framework_auth.svg b/docs/app/reflex_docs/pages/docs_landing/views/framework_auth.svg new file mode 100644 index 00000000000..7eb44930289 --- /dev/null +++ b/docs/app/reflex_docs/pages/docs_landing/views/framework_auth.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 480" fill="none" font-family="Instrument Sans, sans-serif"><rect x="38" y="92" width="207" height="297" rx="12" fill="var(--fw-surface)" stroke="var(--fw-line)"/><text x="56" y="119" font-size="9" fill="var(--fw-muted)" >โ— โ— โ—</text><text x="114" y="119" font-size="12" fill="var(--fw-muted)" >Identity</text><path d="M38 136h207" stroke="var(--fw-line)"/><text x="56" y="167" font-size="18" fill="var(--fw-ink)" >Sign in to continue</text><rect x="56" y="192" width="171" height="39" rx="8" fill="var(--fw-surface)" stroke="var(--fw-line)"/><text x="72" y="217" font-size="12" fill="var(--fw-ink)" >Workspace SSO</text><text x="205" y="217" font-size="14" fill="var(--fw-muted)" >โ†’</text><rect x="56" y="245" width="171" height="39" rx="8" fill="var(--fw-surface)" stroke="var(--fw-line)"/><text x="72" y="270" font-size="12" fill="var(--fw-ink)" >GitHub</text><text x="205" y="270" font-size="14" fill="var(--fw-muted)" >โ†’</text><rect x="56" y="298" width="171" height="39" rx="8" fill="var(--fw-surface)" stroke="var(--fw-line)"/><text x="72" y="323" font-size="12" fill="var(--fw-ink)" >Google</text><text x="205" y="323" font-size="14" fill="var(--fw-muted)" >โ†’</text><path d="M245 240h60" fill="none" stroke="var(--fw-accent)" stroke-width="1.5" class="fw-flow"/><path d="M377 240h39" fill="none" stroke="var(--fw-accent)" stroke-width="1.5" class="fw-flow"/><circle cx="341" cy="240" r="37" fill="var(--fw-tint)" stroke="var(--fw-line)"/><path d="M341 217l17 7v17c0 11-17 21-17 21s-17-10-17-21v-17l17-7Z" stroke="var(--fw-accent)" stroke-width="1.6"/><path d="m332 239 6 6 12-13" stroke="var(--fw-accent)" stroke-width="1.8"/><text x="306" y="303" font-size="12" fill="var(--fw-accent)" >Verified</text><rect x="416" y="138" width="186" height="220" rx="12" fill="var(--fw-surface)" stroke="var(--fw-line)"/><text x="434" y="165" font-size="9" fill="var(--fw-muted)" >โ— โ— โ—</text><text x="492" y="165" font-size="12" fill="var(--fw-muted)" >Workspace</text><path d="M416 182h186" stroke="var(--fw-line)"/><text x="434" y="211" font-size="17" fill="var(--fw-ink)" >Access granted</text><text x="434" y="242" font-size="12" fill="var(--fw-muted)" >alex@acme.com</text><rect x="434" y="266" width="84" height="27" rx="13" fill="var(--fw-tint)" stroke="var(--fw-tint)"/><text x="446" y="284" font-size="11" fill="var(--fw-accent)" >Admin</text><rect x="434" y="305" width="139" height="27" rx="13" fill="var(--fw-tint)" stroke="var(--fw-tint)"/><text x="446" y="323" font-size="11" fill="var(--fw-accent)" >Session protected</text></svg> diff --git a/docs/app/reflex_docs/pages/docs_landing/views/framework_components.svg b/docs/app/reflex_docs/pages/docs_landing/views/framework_components.svg new file mode 100644 index 00000000000..6f4ab302fa5 --- /dev/null +++ b/docs/app/reflex_docs/pages/docs_landing/views/framework_components.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 480" fill="none" font-family="Instrument Sans, sans-serif"><text x="28" y="66" font-size="14" fill="var(--fw-ink)" >Python components</text><text x="370" y="66" font-size="14" fill="var(--fw-ink)" >Rendered interface</text><rect x="28" y="98" width="312" height="298" rx="12" fill="var(--fw-surface)" stroke="var(--fw-line)"/><text x="47" y="126" font-size="9" fill="var(--fw-muted)" >โ— โ— โ—</text><text x="111" y="126" font-size="12" fill="var(--fw-muted)" >components.py</text><path d="M28 143h312" stroke="var(--fw-line)"/><text x="48" y="172" font-size="12" fill="var(--fw-muted)" font-family="JetBrains Mono, monospace" xml:space="preserve">rx.vstack(</text><text x="67" y="198" font-size="12" fill="var(--fw-accent)" font-family="JetBrains Mono, monospace" xml:space="preserve">rx.heading("Get updates"),</text><circle cx="47" cy="194" r="9" fill="var(--fw-tint)"/><text x="47" y="197.5" font-size="10" fill="var(--fw-accent)" text-anchor="middle">1</text><text x="67" y="224" font-size="12" fill="var(--fw-accent)" font-family="JetBrains Mono, monospace" xml:space="preserve">rx.input(</text><circle cx="47" cy="220" r="9" fill="var(--fw-tint)"/><text x="47" y="223.5" font-size="10" fill="var(--fw-accent)" text-anchor="middle">2</text><text x="82" y="250" font-size="12" fill="var(--fw-muted)" font-family="JetBrains Mono, monospace" xml:space="preserve">placeholder="Email",</text><text x="67" y="276" font-size="12" fill="var(--fw-muted)" font-family="JetBrains Mono, monospace" xml:space="preserve">),</text><text x="67" y="302" font-size="12" fill="var(--fw-accent)" font-family="JetBrains Mono, monospace" xml:space="preserve">rx.button("Subscribe"),</text><circle cx="47" cy="298" r="9" fill="var(--fw-tint)"/><text x="47" y="301.5" font-size="10" fill="var(--fw-accent)" text-anchor="middle">3</text><text x="67" y="328" font-size="12" fill="var(--fw-muted)" font-family="JetBrains Mono, monospace" xml:space="preserve">align="stretch",</text><text x="48" y="354" font-size="12" fill="var(--fw-muted)" font-family="JetBrains Mono, monospace" xml:space="preserve">)</text><path d="M340 248h25m-5-5 5 5-5 5" class="fw-flow" fill="none" stroke="var(--fw-accent)"/><text x="370" y="110" font-size="12" fill="var(--fw-accent)" font-family="JetBrains Mono, monospace">rx.vstack</text><rect x="370" y="128" width="242" height="242" rx="12" fill="var(--fw-surface)" stroke="var(--fw-accent)"/><circle cx="391" cy="174" r="9" fill="var(--fw-tint)"/><text x="391" y="177.5" font-size="10" fill="var(--fw-accent)" text-anchor="middle">1</text><text x="410" y="181" font-size="21" fill="var(--fw-ink)" >Get updates</text><circle cx="391" cy="227" r="9" fill="var(--fw-tint)"/><text x="391" y="230.5" font-size="10" fill="var(--fw-accent)" text-anchor="middle">2</text><rect x="410" y="206" width="182" height="42" rx="8" fill="var(--fw-surface)" stroke="var(--fw-line)"/><text x="424" y="232" font-size="14" fill="var(--fw-muted)" >Email</text><circle cx="391" cy="297" r="9" fill="var(--fw-tint)"/><text x="391" y="300.5" font-size="10" fill="var(--fw-accent)" text-anchor="middle">3</text><rect x="410" y="276" width="182" height="42" rx="8" fill="var(--fw-ink)" stroke="var(--fw-ink)"/><text x="501" y="302" font-size="14" fill="var(--fw-surface)" text-anchor="middle">Subscribe</text></svg> diff --git a/docs/app/reflex_docs/pages/docs_landing/views/framework_database.svg b/docs/app/reflex_docs/pages/docs_landing/views/framework_database.svg new file mode 100644 index 00000000000..1b0c6b3463a --- /dev/null +++ b/docs/app/reflex_docs/pages/docs_landing/views/framework_database.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 480" fill="none" font-family="Instrument Sans, sans-serif"><rect x="93" y="38" width="454" height="148" rx="12" fill="var(--fw-surface)" stroke="var(--fw-line)"/><text x="111" y="65" font-size="9" fill="var(--fw-muted)" >โ— โ— โ—</text><text x="169" y="65" font-size="12" fill="var(--fw-muted)" >models.py</text><path d="M93 82h454" stroke="var(--fw-line)"/><text x="114" y="112" font-size="14" fill="var(--fw-accent)" font-family="JetBrains Mono, monospace">class Customer(SQLModel, table=True):</text><text x="114" y="143" font-size="13" fill="var(--fw-muted)" font-family="JetBrains Mono, monospace"> name: str email: str</text><path d="M320 186v18" fill="none" stroke="var(--fw-accent)" stroke-width="1.5" class="fw-flow"/><path d="M267 217.5h-78q-16 0-16 16v30.5" fill="none" stroke="var(--fw-accent)" stroke-width="1.5" class="fw-flow"/><path d="M373 217.5h78q16 0 16 16v30.5" fill="none" stroke="var(--fw-accent)" stroke-width="1.5" class="fw-flow"/><rect x="267" y="204" width="106" height="27" rx="13" fill="var(--fw-tint)" stroke="var(--fw-tint)"/><text x="320" y="217.5" text-anchor="middle" dominant-baseline="central" font-size="11" fill="var(--fw-accent)">Typed models</text><rect x="40" y="264" width="250" height="171" rx="12" fill="var(--fw-surface)" stroke="var(--fw-line)"/><text x="58" y="291" font-size="9" fill="var(--fw-muted)" >โ— โ— โ—</text><text x="116" y="291" font-size="12" fill="var(--fw-muted)" >Database / customers</text><path d="M40 308h250" stroke="var(--fw-line)"/><text x="56" y="335" font-size="13" fill="var(--fw-ink)" >id</text><text x="123" y="335" font-size="11" fill="var(--fw-muted)" >integer ยท primary key</text><path d="M56 347h217" stroke="var(--fw-line)"/><text x="56" y="367" font-size="13" fill="var(--fw-ink)" >name</text><text x="123" y="367" font-size="11" fill="var(--fw-muted)" >text</text><path d="M56 379h217" stroke="var(--fw-line)"/><text x="56" y="399" font-size="13" fill="var(--fw-ink)" >email</text><text x="123" y="399" font-size="11" fill="var(--fw-muted)" >text</text><rect x="340" y="264" width="260" height="171" rx="12" fill="var(--fw-surface)" stroke="var(--fw-line)"/><text x="358" y="291" font-size="9" fill="var(--fw-muted)" >โ— โ— โ—</text><text x="416" y="291" font-size="12" fill="var(--fw-muted)" >Application data</text><path d="M340 308h260" stroke="var(--fw-line)"/><text x="356" y="335" font-size="12" fill="var(--fw-ink)" >Alex Morgan</text><text x="531" y="335" font-size="11" fill="var(--fw-accent)" >Active</text><path d="M356 347h227" stroke="var(--fw-line)"/><text x="356" y="367" font-size="12" fill="var(--fw-ink)" >Jamie Chen</text><text x="531" y="367" font-size="11" fill="var(--fw-accent)" >Active</text><path d="M356 379h227" stroke="var(--fw-line)"/><text x="356" y="399" font-size="12" fill="var(--fw-ink)" >Sam Rivera</text><text x="531" y="399" font-size="11" fill="var(--fw-accent)" >Invited</text></svg> diff --git a/docs/app/reflex_docs/pages/docs_landing/views/getting_started_illustration.svg b/docs/app/reflex_docs/pages/docs_landing/views/getting_started_illustration.svg new file mode 100644 index 00000000000..e9663e3cac8 --- /dev/null +++ b/docs/app/reflex_docs/pages/docs_landing/views/getting_started_illustration.svg @@ -0,0 +1,339 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="336" height="96" viewBox="0 0 336 96" fill="none" aria-hidden="true" focusable="false"> + <mask id="docs-getting_started_illustration-mask0_20497_6965" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="336" height="96"> + <rect width="336" height="96" transform="matrix(1 0 0 -1 0 96)" fill="url(#docs-getting_started_illustration-paint0_linear_20497_6965)" /> + </mask> + <g mask="url(#docs-getting_started_illustration-mask0_20497_6965)"> + <g clip-path="url(#docs-getting_started_illustration-clip0_20497_6965)"> + <mask id="docs-getting_started_illustration-path-2-inside-1_20497_6965" fill="white"> + <path d="M0 0H336V32H0V0Z" /> + </mask> + <rect width="72" height="32" fill="var(--background)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 3 12)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 3 24)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 6 6)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 6 12)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 6 15)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 6 18)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 9 6)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 9 21)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 12 15)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 12 24)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 15 9)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 15 24)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 18 15)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 18 24)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 21 12)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 21 15)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 21 18)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 24 9)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 24 12)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 24 15)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 27 6)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 27 15)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 30 24)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 33 9)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 33 12)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 33 18)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 36 6)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 36 9)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 36 15)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 36 24)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 39 6)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 39 18)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 39 24)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 42 6)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 42 15)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 42 21)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 42 24)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 45 24)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 48 9)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 48 21)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 51 6)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 51 9)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 51 18)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 51 24)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 54 24)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 57 12)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 57 15)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 57 18)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 57 24)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 60 9)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 60 12)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 60 21)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 63 9)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 63 15)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 63 21)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 66 12)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 66 15)" fill="var(--subtle-foreground)" /> + <mask id="docs-getting_started_illustration-path-61-inside-2_20497_6965" fill="white"> + <path d="M72 0H104V32H72V0Z" /> + </mask> + <path d="M72 0H104V32H72V0Z" fill="var(--background)" /> + <path d="M72 32H73V0H72H71V32H72Z" fill="var(--border-subtle)" mask="url(#docs-getting_started_illustration-path-61-inside-2_20497_6965)" /> + <path d="M85.4823 20V18.992H87.8463V12.176L85.4703 13.94V12.716L87.4623 11.24H88.9623V18.992H90.8943V20H85.4823Z" fill="var(--subtle-foreground)" /> + <g> + <rect width="232" height="32" transform="translate(104)" fill="var(--background)" /> + <path d="M115.092 20V11.24H117.912C118.464 11.24 118.94 11.348 119.34 11.564C119.748 11.772 120.06 12.072 120.276 12.464C120.5 12.848 120.612 13.304 120.612 13.832C120.612 14.352 120.5 14.808 120.276 15.2C120.052 15.584 119.74 15.884 119.34 16.1C118.94 16.316 118.464 16.424 117.912 16.424H116.208V20H115.092ZM116.208 15.416H117.912C118.384 15.416 118.76 15.272 119.04 14.984C119.32 14.696 119.46 14.312 119.46 13.832C119.46 13.344 119.32 12.956 119.04 12.668C118.76 12.38 118.384 12.236 117.912 12.236H116.208V15.416ZM122.563 20V11.24H123.679V18.98H127.615V20H122.563ZM128.979 20L131.247 11.24H132.735L135.003 20H133.875L133.311 17.684H130.683L130.119 20H128.979ZM130.899 16.748H133.083L132.423 14.084C132.295 13.58 132.195 13.156 132.123 12.812C132.051 12.468 132.007 12.244 131.991 12.14C131.975 12.244 131.931 12.468 131.859 12.812C131.787 13.156 131.687 13.576 131.559 14.072L130.899 16.748ZM136.654 20V11.24H138.118L140.782 18.704C140.766 18.504 140.746 18.256 140.722 17.96C140.698 17.664 140.678 17.352 140.662 17.024C140.654 16.696 140.65 16.388 140.65 16.1V11.24H141.718V20H140.254L137.602 12.536C137.618 12.728 137.634 12.972 137.65 13.268C137.674 13.564 137.69 13.876 137.698 14.204C137.714 14.524 137.722 14.832 137.722 15.128V20H136.654ZM151.069 20V11.24H153.889C154.441 11.24 154.917 11.348 155.317 11.564C155.725 11.772 156.037 12.072 156.253 12.464C156.477 12.848 156.589 13.304 156.589 13.832C156.589 14.352 156.477 14.808 156.253 15.2C156.029 15.584 155.717 15.884 155.317 16.1C154.917 16.316 154.441 16.424 153.889 16.424H152.185V20H151.069ZM152.185 15.416H153.889C154.361 15.416 154.737 15.272 155.017 14.984C155.297 14.696 155.437 14.312 155.437 13.832C155.437 13.344 155.297 12.956 155.017 12.668C154.737 12.38 154.361 12.236 153.889 12.236H152.185V15.416ZM158.264 20V11.24H160.976C161.504 11.24 161.964 11.348 162.356 11.564C162.748 11.772 163.052 12.064 163.268 12.44C163.492 12.816 163.604 13.26 163.604 13.772C163.604 14.356 163.448 14.864 163.136 15.296C162.832 15.72 162.42 16.012 161.9 16.172L163.724 20H162.416L160.748 16.304H159.368V20H158.264ZM159.368 15.296H160.976C161.424 15.296 161.78 15.156 162.044 14.876C162.316 14.596 162.452 14.228 162.452 13.772C162.452 13.3 162.316 12.928 162.044 12.656C161.78 12.376 161.424 12.236 160.976 12.236H159.368V15.296ZM167.967 20.12C167.439 20.12 166.983 20.02 166.599 19.82C166.215 19.62 165.919 19.332 165.711 18.956C165.511 18.572 165.411 18.12 165.411 17.6V13.64C165.411 13.112 165.511 12.66 165.711 12.284C165.919 11.908 166.215 11.62 166.599 11.42C166.983 11.22 167.439 11.12 167.967 11.12C168.495 11.12 168.951 11.22 169.335 11.42C169.719 11.62 170.011 11.908 170.211 12.284C170.419 12.66 170.523 13.108 170.523 13.628V17.6C170.523 18.12 170.419 18.572 170.211 18.956C170.011 19.332 169.719 19.62 169.335 19.82C168.951 20.02 168.495 20.12 167.967 20.12ZM167.967 19.124C168.431 19.124 168.787 18.992 169.035 18.728C169.291 18.464 169.419 18.088 169.419 17.6V13.64C169.419 13.152 169.291 12.776 169.035 12.512C168.787 12.248 168.431 12.116 167.967 12.116C167.511 12.116 167.155 12.248 166.899 12.512C166.643 12.776 166.515 13.152 166.515 13.64V17.6C166.515 18.088 166.643 18.464 166.899 18.728C167.155 18.992 167.511 19.124 167.967 19.124ZM174.803 20.12C173.963 20.12 173.299 19.884 172.811 19.412C172.331 18.94 172.091 18.296 172.091 17.48H173.207C173.207 18.008 173.347 18.416 173.627 18.704C173.907 18.992 174.299 19.136 174.803 19.136C175.307 19.136 175.699 18.992 175.979 18.704C176.259 18.416 176.399 18.008 176.399 17.48V12.26H174.311V11.24H177.515V17.48C177.515 18.296 177.271 18.94 176.783 19.412C176.303 19.884 175.643 20.12 174.803 20.12ZM179.946 20V11.24H184.998V12.248H181.05V14.924H184.578V15.92H181.05V18.992H184.998V20H179.946ZM189.601 20.12C189.073 20.12 188.613 20.02 188.221 19.82C187.829 19.62 187.525 19.332 187.309 18.956C187.101 18.572 186.997 18.12 186.997 17.6V13.64C186.997 13.112 187.101 12.66 187.309 12.284C187.525 11.908 187.829 11.62 188.221 11.42C188.613 11.22 189.073 11.12 189.601 11.12C190.129 11.12 190.585 11.224 190.969 11.432C191.361 11.632 191.661 11.92 191.869 12.296C192.077 12.672 192.181 13.12 192.181 13.64H191.077C191.077 13.152 190.945 12.776 190.681 12.512C190.425 12.248 190.065 12.116 189.601 12.116C189.137 12.116 188.769 12.248 188.497 12.512C188.233 12.768 188.101 13.14 188.101 13.628V17.6C188.101 18.088 188.233 18.464 188.497 18.728C188.769 18.992 189.137 19.124 189.601 19.124C190.065 19.124 190.425 18.992 190.681 18.728C190.945 18.464 191.077 18.088 191.077 17.6H192.181C192.181 18.112 192.077 18.56 191.869 18.944C191.661 19.32 191.361 19.612 190.969 19.82C190.585 20.02 190.129 20.12 189.601 20.12ZM196.196 20V12.248H193.796V11.228H199.7V12.248H197.3V20H196.196Z" fill="var(--foreground)" /> + </g> + </g> + <path d="M0 0V1H336V0V-1H0V0Z" fill="var(--border-subtle)" mask="url(#docs-getting_started_illustration-path-2-inside-1_20497_6965)" /> + <g clip-path="url(#docs-getting_started_illustration-clip1_20497_6965)"> + <mask id="docs-getting_started_illustration-path-65-inside-3_20497_6965" fill="white"> + <path d="M0 32H336V64H0V32Z" /> + </mask> + <rect width="104" height="32" transform="translate(0 32)" fill="var(--background)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 20 38)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 20 41)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 20 44)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 20 56)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 23 44)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 23 56)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 26 44)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 26 47)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 26 53)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 26 56)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 29 41)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 29 47)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 29 50)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 29 53)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 32 41)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 32 53)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 32 56)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 35 44)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 35 56)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 38 38)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 38 44)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 38 47)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 38 50)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 41 38)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 41 53)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 44 47)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 44 56)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 47 41)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 47 56)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 50 47)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 50 56)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 53 44)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 53 47)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 53 50)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 56 41)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 56 44)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 56 47)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 59 38)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 59 47)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 62 56)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 65 41)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 65 44)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 65 50)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 68 38)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 68 41)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 68 47)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 68 56)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 71 38)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 71 50)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 71 56)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 74 38)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 74 47)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 74 53)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 74 56)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 77 56)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 80 41)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 80 53)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 83 38)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 83 41)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 83 50)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 83 56)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 86 56)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 89 44)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 89 47)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 89 50)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 89 56)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 92 41)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 92 44)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 92 53)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 95 41)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 95 47)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 95 53)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 98 44)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 98 47)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(1 0 0 -1 16 48)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(1 0 0 -1 16 39)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(1 0 0 -1 13 51)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(1 0 0 -1 13 48)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(1 0 0 -1 13 45)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(1 0 0 -1 10 54)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(1 0 0 -1 10 51)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(1 0 0 -1 10 48)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(1 0 0 -1 7 57)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(1 0 0 -1 7 48)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(1 0 0 -1 4 39)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(1 0 0 -1 1 54)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(1 0 0 -1 1 51)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(1 0 0 -1 1 45)" fill="var(--subtle-foreground)" /> + <mask id="docs-getting_started_illustration-path-155-inside-4_20497_6965" fill="white"> + <path d="M104 32H136V64H104V32Z" /> + </mask> + <path d="M104 32H136V64H104V32Z" fill="var(--background)" /> + <path d="M104 64H105V32H104H103V64H104Z" fill="var(--border-subtle)" mask="url(#docs-getting_started_illustration-path-155-inside-4_20497_6965)" /> + <path d="M117.494 52V50.872L120.17 48.028C120.626 47.548 120.954 47.124 121.154 46.756C121.362 46.38 121.466 46.016 121.466 45.664C121.466 45.176 121.334 44.796 121.07 44.524C120.806 44.244 120.446 44.104 119.99 44.104C119.486 44.104 119.09 44.248 118.802 44.536C118.514 44.824 118.37 45.216 118.37 45.712H117.254C117.27 45.184 117.39 44.724 117.614 44.332C117.846 43.94 118.166 43.64 118.574 43.432C118.982 43.224 119.454 43.12 119.99 43.12C120.518 43.12 120.978 43.224 121.37 43.432C121.762 43.632 122.062 43.92 122.27 44.296C122.478 44.672 122.582 45.12 122.582 45.64C122.582 46.136 122.45 46.632 122.186 47.128C121.922 47.616 121.49 48.168 120.89 48.784L118.754 50.992H122.726V52H117.494Z" fill="var(--subtle-foreground)" /> + <g> + <rect width="200" height="32" transform="translate(136 32)" fill="var(--background)" /> + <path d="M149.048 52V44.248H146.648V43.228H152.552V44.248H150.152V52H149.048ZM154.299 52V43.24H155.403V46.972H158.187V43.24H159.291V52H158.187V47.98H155.403V52H154.299ZM161.639 52V50.992H163.427V44.248H161.639V43.24H166.343V44.248H164.555V50.992H166.343V52H161.639ZM168.654 52V43.24H170.118L172.782 50.704C172.766 50.504 172.746 50.256 172.722 49.96C172.698 49.664 172.678 49.352 172.662 49.024C172.654 48.696 172.65 48.388 172.65 48.1V43.24H173.718V52H172.254L169.602 44.536C169.618 44.728 169.634 44.972 169.65 45.268C169.674 45.564 169.69 45.876 169.698 46.204C169.714 46.524 169.722 46.832 169.722 47.128V52H168.654ZM175.873 52V43.24H176.977V46.984H178.261L180.181 43.24H181.393L179.233 47.452L181.513 52H180.241L178.225 47.956H176.977V52H175.873ZM190.06 52V43.24H191.512L192.412 46.072C192.5 46.328 192.58 46.584 192.652 46.84C192.724 47.088 192.776 47.276 192.808 47.404C192.848 47.276 192.9 47.088 192.964 46.84C193.028 46.584 193.1 46.324 193.18 46.06L194.032 43.24H195.484V52H194.392V48.856C194.392 48.496 194.404 48.092 194.428 47.644C194.452 47.196 194.484 46.74 194.524 46.276C194.564 45.804 194.604 45.356 194.644 44.932C194.692 44.5 194.732 44.124 194.764 43.804L193.372 48.16H192.208L190.804 43.804C190.844 44.116 190.884 44.48 190.924 44.896C190.964 45.304 191 45.74 191.032 46.204C191.064 46.66 191.092 47.12 191.116 47.584C191.14 48.04 191.152 48.464 191.152 48.856V52H190.06ZM199.967 52.12C199.439 52.12 198.983 52.02 198.599 51.82C198.215 51.62 197.919 51.332 197.711 50.956C197.511 50.572 197.411 50.12 197.411 49.6V45.64C197.411 45.112 197.511 44.66 197.711 44.284C197.919 43.908 198.215 43.62 198.599 43.42C198.983 43.22 199.439 43.12 199.967 43.12C200.495 43.12 200.951 43.22 201.335 43.42C201.719 43.62 202.011 43.908 202.211 44.284C202.419 44.66 202.523 45.108 202.523 45.628V49.6C202.523 50.12 202.419 50.572 202.211 50.956C202.011 51.332 201.719 51.62 201.335 51.82C200.951 52.02 200.495 52.12 199.967 52.12ZM199.967 51.124C200.431 51.124 200.787 50.992 201.035 50.728C201.291 50.464 201.419 50.088 201.419 49.6V45.64C201.419 45.152 201.291 44.776 201.035 44.512C200.787 44.248 200.431 44.116 199.967 44.116C199.511 44.116 199.155 44.248 198.899 44.512C198.643 44.776 198.515 45.152 198.515 45.64V49.6C198.515 50.088 198.643 50.464 198.899 50.728C199.155 50.992 199.511 51.124 199.967 51.124ZM204.655 52V43.24H206.947C207.515 43.24 208.007 43.348 208.423 43.564C208.839 43.78 209.159 44.084 209.383 44.476C209.615 44.868 209.73 45.336 209.73 45.88V49.348C209.73 49.884 209.615 50.352 209.383 50.752C209.159 51.152 208.839 51.46 208.423 51.676C208.007 51.892 207.515 52 206.947 52H204.655ZM205.759 51.004H206.947C207.467 51.004 207.875 50.86 208.171 50.572C208.467 50.276 208.615 49.868 208.615 49.348V45.88C208.615 45.368 208.467 44.968 208.171 44.68C207.875 44.384 207.467 44.236 206.947 44.236H205.759V51.004ZM214.358 52.12C213.542 52.12 212.914 51.896 212.474 51.448C212.042 50.992 211.826 50.372 211.826 49.588V43.24H212.93V49.588C212.93 50.068 213.046 50.448 213.278 50.728C213.518 51 213.878 51.136 214.358 51.136C214.83 51.136 215.186 51 215.426 50.728C215.666 50.448 215.786 50.068 215.786 49.588V43.24H216.89V49.588C216.89 50.38 216.674 51 216.242 51.448C215.81 51.896 215.182 52.12 214.358 52.12ZM219.321 52V43.24H220.437V50.98H224.373V52H219.321ZM225.736 52L228.004 43.24H229.492L231.76 52H230.632L230.068 49.684H227.44L226.876 52H225.736ZM227.656 48.748H229.84L229.18 46.084C229.052 45.58 228.952 45.156 228.88 44.812C228.808 44.468 228.764 44.244 228.748 44.14C228.732 44.244 228.688 44.468 228.616 44.812C228.544 45.156 228.444 45.576 228.316 46.072L227.656 48.748ZM233.436 52V43.24H236.148C236.676 43.24 237.136 43.348 237.528 43.564C237.92 43.772 238.224 44.064 238.44 44.44C238.664 44.816 238.776 45.26 238.776 45.772C238.776 46.356 238.62 46.864 238.308 47.296C238.004 47.72 237.592 48.012 237.072 48.172L238.896 52H237.588L235.92 48.304H234.54V52H233.436ZM234.54 47.296H236.148C236.596 47.296 236.952 47.156 237.216 46.876C237.488 46.596 237.624 46.228 237.624 45.772C237.624 45.3 237.488 44.928 237.216 44.656C236.952 44.376 236.596 44.236 236.148 44.236H234.54V47.296Z" fill="var(--foreground)" /> + </g> + </g> + <path d="M0 32V33H336V32V31H0V32Z" fill="var(--border-subtle)" mask="url(#docs-getting_started_illustration-path-65-inside-3_20497_6965)" /> + <g clip-path="url(#docs-getting_started_illustration-clip2_20497_6965)"> + <mask id="docs-getting_started_illustration-path-159-inside-5_20497_6965" fill="white"> + <path d="M0 64H336V96H0V64Z" /> + </mask> + <rect width="136" height="32" transform="translate(0 64)" fill="var(--background)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 52 70)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 52 73)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 52 76)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 52 88)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 55 76)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 55 88)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 58 76)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 58 79)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 58 85)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 58 88)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 61 73)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 61 79)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 61 82)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 61 85)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 64 73)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 64 85)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 64 88)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 67 76)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 67 88)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 70 70)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 70 76)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 70 79)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 70 82)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 73 70)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 73 85)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 76 79)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 76 88)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 79 73)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 79 88)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 82 79)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 82 88)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 85 76)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 85 79)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 85 82)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 88 73)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 88 76)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 88 79)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 91 70)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 91 79)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 94 88)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 97 73)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 97 76)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 97 82)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 100 70)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 100 73)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 100 79)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 100 88)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 103 70)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 103 82)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 103 88)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 106 70)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 106 79)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 106 85)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 106 88)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 109 88)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 112 73)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 112 85)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 115 70)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 115 73)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 115 82)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 115 88)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 118 88)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 121 76)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 121 79)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 121 82)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 121 88)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 124 73)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 124 76)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 124 85)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 127 73)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 127 79)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 127 85)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 130 76)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(-1 0 0 1 130 79)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(1 0 0 -1 48 80)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(1 0 0 -1 48 71)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(1 0 0 -1 45 83)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(1 0 0 -1 45 80)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(1 0 0 -1 45 77)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(1 0 0 -1 42 86)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(1 0 0 -1 42 83)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(1 0 0 -1 42 80)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(1 0 0 -1 39 89)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(1 0 0 -1 39 80)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(1 0 0 -1 36 71)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(1 0 0 -1 33 86)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(1 0 0 -1 33 83)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(1 0 0 -1 33 77)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(1 0 0 -1 30 89)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(1 0 0 -1 30 86)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(1 0 0 -1 30 80)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(1 0 0 -1 30 71)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(1 0 0 -1 27 89)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(1 0 0 -1 27 77)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(1 0 0 -1 27 71)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(1 0 0 -1 24 89)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(1 0 0 -1 24 80)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(1 0 0 -1 24 74)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(1 0 0 -1 24 71)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(1 0 0 -1 21 71)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(1 0 0 -1 18 86)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(1 0 0 -1 18 74)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(1 0 0 -1 15 89)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(1 0 0 -1 15 86)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(1 0 0 -1 15 77)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(1 0 0 -1 15 71)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(1 0 0 -1 12 71)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(1 0 0 -1 9 83)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(1 0 0 -1 9 80)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(1 0 0 -1 9 77)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(1 0 0 -1 9 71)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(1 0 0 -1 6 86)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(1 0 0 -1 6 83)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(1 0 0 -1 6 74)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(1 0 0 -1 3 86)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(1 0 0 -1 3 80)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(1 0 0 -1 3 74)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(1 0 0 -1 0 83)" fill="var(--subtle-foreground)" /> + <rect width="1" height="1" transform="matrix(1 0 0 -1 0 80)" fill="var(--subtle-foreground)" /> + <mask id="docs-getting_started_illustration-path-280-inside-6_20497_6965" fill="white"> + <path d="M136 64H168V96H136V64Z" /> + </mask> + <path d="M136 64H168V96H136V64Z" fill="var(--background)" /> + <path d="M136 96H137V64H136H135V96H136Z" fill="var(--border-subtle)" mask="url(#docs-getting_started_illustration-path-280-inside-6_20497_6965)" /> + <path d="M151.87 84.12C151.342 84.12 150.878 84.016 150.478 83.808C150.078 83.592 149.77 83.296 149.554 82.92C149.338 82.544 149.23 82.104 149.23 81.6H150.346C150.346 82.08 150.482 82.456 150.754 82.728C151.026 83 151.402 83.136 151.882 83.136C152.378 83.136 152.758 82.996 153.022 82.716C153.294 82.428 153.43 82.056 153.43 81.6V80.976C153.43 80.488 153.294 80.112 153.022 79.848C152.758 79.576 152.378 79.44 151.882 79.44H150.982V78.336L153.13 76.248H149.59V75.24H154.258V76.344L151.906 78.612V78.456C152.714 78.456 153.354 78.684 153.826 79.14C154.298 79.596 154.534 80.208 154.534 80.976V81.6C154.534 82.104 154.422 82.544 154.198 82.92C153.982 83.296 153.674 83.592 153.274 83.808C152.874 84.016 152.406 84.12 151.87 84.12Z" fill="var(--subtle-foreground)" /> + <g> + <rect width="168" height="32" transform="translate(168 64)" fill="var(--background)" /> + <path d="M179.092 84V75.24H181.912C182.464 75.24 182.94 75.348 183.34 75.564C183.748 75.772 184.06 76.072 184.276 76.464C184.5 76.848 184.612 77.304 184.612 77.832C184.612 78.352 184.5 78.808 184.276 79.2C184.052 79.584 183.74 79.884 183.34 80.1C182.94 80.316 182.464 80.424 181.912 80.424H180.208V84H179.092ZM180.208 79.416H181.912C182.384 79.416 182.76 79.272 183.04 78.984C183.32 78.696 183.46 78.312 183.46 77.832C183.46 77.344 183.32 76.956 183.04 76.668C182.76 76.38 182.384 76.236 181.912 76.236H180.208V79.416ZM186.287 84V75.24H188.999C189.527 75.24 189.987 75.348 190.379 75.564C190.771 75.772 191.075 76.064 191.291 76.44C191.515 76.816 191.627 77.26 191.627 77.772C191.627 78.356 191.471 78.864 191.159 79.296C190.855 79.72 190.443 80.012 189.923 80.172L191.747 84H190.439L188.771 80.304H187.391V84H186.287ZM187.391 79.296H188.999C189.447 79.296 189.803 79.156 190.067 78.876C190.339 78.596 190.475 78.228 190.475 77.772C190.475 77.3 190.339 76.928 190.067 76.656C189.803 76.376 189.447 76.236 188.999 76.236H187.391V79.296ZM195.991 84.12C195.463 84.12 195.007 84.02 194.623 83.82C194.239 83.62 193.943 83.332 193.735 82.956C193.535 82.572 193.435 82.12 193.435 81.6V77.64C193.435 77.112 193.535 76.66 193.735 76.284C193.943 75.908 194.239 75.62 194.623 75.42C195.007 75.22 195.463 75.12 195.991 75.12C196.519 75.12 196.975 75.22 197.359 75.42C197.743 75.62 198.035 75.908 198.235 76.284C198.443 76.66 198.547 77.108 198.547 77.628V81.6C198.547 82.12 198.443 82.572 198.235 82.956C198.035 83.332 197.743 83.62 197.359 83.82C196.975 84.02 196.519 84.12 195.991 84.12ZM195.991 83.124C196.455 83.124 196.811 82.992 197.059 82.728C197.315 82.464 197.443 82.088 197.443 81.6V77.64C197.443 77.152 197.315 76.776 197.059 76.512C196.811 76.248 196.455 76.116 195.991 76.116C195.535 76.116 195.179 76.248 194.923 76.512C194.667 76.776 194.539 77.152 194.539 77.64V81.6C194.539 82.088 194.667 82.464 194.923 82.728C195.179 82.992 195.535 83.124 195.991 83.124ZM200.474 84V75.24H201.926L202.826 78.072C202.914 78.328 202.994 78.584 203.066 78.84C203.138 79.088 203.19 79.276 203.222 79.404C203.262 79.276 203.314 79.088 203.378 78.84C203.442 78.584 203.514 78.324 203.594 78.06L204.446 75.24H205.898V84H204.806V80.856C204.806 80.496 204.818 80.092 204.842 79.644C204.866 79.196 204.898 78.74 204.938 78.276C204.978 77.804 205.018 77.356 205.058 76.932C205.106 76.5 205.146 76.124 205.178 75.804L203.786 80.16H202.622L201.218 75.804C201.258 76.116 201.298 76.48 201.338 76.896C201.378 77.304 201.414 77.74 201.446 78.204C201.478 78.66 201.506 79.12 201.53 79.584C201.554 80.04 201.566 80.464 201.566 80.856V84H200.474ZM207.873 84V75.24H210.693C211.245 75.24 211.721 75.348 212.121 75.564C212.529 75.772 212.841 76.072 213.057 76.464C213.281 76.848 213.393 77.304 213.393 77.832C213.393 78.352 213.281 78.808 213.057 79.2C212.833 79.584 212.521 79.884 212.121 80.1C211.721 80.316 211.245 80.424 210.693 80.424H208.989V84H207.873ZM208.989 79.416H210.693C211.165 79.416 211.541 79.272 211.821 78.984C212.101 78.696 212.241 78.312 212.241 77.832C212.241 77.344 212.101 76.956 211.821 76.668C211.541 76.38 211.165 76.236 210.693 76.236H208.989V79.416ZM217.025 84V76.248H214.625V75.228H220.529V76.248H218.129V84H217.025ZM232.015 84.12C231.487 84.12 231.027 84.02 230.635 83.82C230.243 83.62 229.939 83.332 229.723 82.956C229.515 82.572 229.411 82.12 229.411 81.6V77.64C229.411 77.112 229.515 76.66 229.723 76.284C229.939 75.908 230.243 75.62 230.635 75.42C231.027 75.22 231.487 75.12 232.015 75.12C232.543 75.12 232.999 75.224 233.383 75.432C233.775 75.632 234.075 75.92 234.283 76.296C234.491 76.672 234.595 77.12 234.595 77.64H233.491C233.491 77.152 233.359 76.776 233.095 76.512C232.839 76.248 232.479 76.116 232.015 76.116C231.551 76.116 231.183 76.248 230.911 76.512C230.647 76.768 230.515 77.14 230.515 77.628V81.6C230.515 82.088 230.647 82.464 230.911 82.728C231.183 82.992 231.551 83.124 232.015 83.124C232.479 83.124 232.839 82.992 233.095 82.728C233.359 82.464 233.491 82.088 233.491 81.6H234.595C234.595 82.112 234.491 82.56 234.283 82.944C234.075 83.32 233.775 83.612 233.383 83.82C232.999 84.02 232.543 84.12 232.015 84.12ZM236.931 84V75.24H238.047V82.98H241.983V84H236.931ZM243.946 84V75.24H248.998V76.248H245.05V78.924H248.578V79.92H245.05V82.992H248.998V84H243.946ZM250.541 84L252.809 75.24H254.297L256.565 84H255.437L254.873 81.684H252.245L251.681 84H250.541ZM252.461 80.748H254.645L253.985 78.084C253.857 77.58 253.757 77.156 253.685 76.812C253.613 76.468 253.569 76.244 253.553 76.14C253.537 76.244 253.493 76.468 253.421 76.812C253.349 77.156 253.249 77.576 253.121 78.072L252.461 80.748ZM258.24 84V75.24H260.952C261.48 75.24 261.94 75.348 262.332 75.564C262.724 75.772 263.028 76.064 263.244 76.44C263.468 76.816 263.58 77.26 263.58 77.772C263.58 78.356 263.424 78.864 263.112 79.296C262.808 79.72 262.396 80.012 261.876 80.172L263.7 84H262.392L260.724 80.304H259.344V84H258.24ZM259.344 79.296H260.952C261.4 79.296 261.756 79.156 262.02 78.876C262.292 78.596 262.428 78.228 262.428 77.772C262.428 77.3 262.292 76.928 262.02 76.656C261.756 76.376 261.4 76.236 260.952 76.236H259.344V79.296Z" fill="var(--foreground)" /> + </g> + </g> + <path d="M0 64V65H336V64V63H0V64ZM336 96V95H0V96V97H336V96Z" fill="var(--border-subtle)" mask="url(#docs-getting_started_illustration-path-159-inside-5_20497_6965)" /> + </g> + <defs> + <linearGradient id="docs-getting_started_illustration-paint0_linear_20497_6965" x1="0" y1="48" x2="336" y2="48" gradientUnits="userSpaceOnUse"> + <stop stop-opacity="0" /> + <stop offset="0.32" /> + <stop offset="0.68" /> + <stop offset="1" stop-opacity="0" /> + </linearGradient> + <clipPath id="docs-getting_started_illustration-clip0_20497_6965"> + <path d="M0 0H336V32H0V0Z" fill="white" /> + </clipPath> + <clipPath id="docs-getting_started_illustration-clip1_20497_6965"> + <path d="M0 32H336V64H0V32Z" fill="white" /> + </clipPath> + <clipPath id="docs-getting_started_illustration-clip2_20497_6965"> + <path d="M0 64H336V96H0V64Z" fill="white" /> + </clipPath> + </defs> +</svg> diff --git a/docs/app/reflex_docs/pages/docs_landing/views/hero.py b/docs/app/reflex_docs/pages/docs_landing/views/hero.py index bde3723225d..7fcb16108b4 100644 --- a/docs/app/reflex_docs/pages/docs_landing/views/hero.py +++ b/docs/app/reflex_docs/pages/docs_landing/views/hero.py @@ -1,66 +1,56 @@ +"""Editorial introduction to the documentation.""" + import reflex as rx import reflex_components_internal as ui from reflex_site_shared.components.marketing_button import button -from reflex_site_shared.constants import REFLEX_ASSETS_CDN -from reflex_site_shared.views.hosting_banner import HostingBannerState from reflex_docs.pages.docs import getting_started +from reflex_docs.pages.docs_landing.views.artwork import artwork def hero() -> rx.Component: + """Render the docs introduction using the marketing typography and actions.""" return rx.el.section( rx.el.div( - rx.el.p( - "About Reflex", - class_name="text-sm font-[525] text-primary-10 dark:text-secondary-11", - ), rx.el.h1( "Reflex Documentation", - class_name="text-secondary-12 lg:text-5xl text-3xl font-[575] lg:text-nowrap", + class_name="text-foreground text-4xl sm:text-5xl lg:text-6xl font-book tracking-[-0.03em] leading-[1.06] text-balance", ), rx.el.p( - "Get up and running with Reflex in minutes. A complete set ", - rx.el.br(class_name="max-lg:hidden"), - " of resources to build, deploy, and scale your application. ", - class_name="text-base text-secondary-11 font-[475]", - ), - rx.el.a( - button( - "Get Started", - ui.icon("ArrowRight01Icon"), - variant="primary", - size="md", - native_button=False, - class_name="w-fit", - ), - to=getting_started.introduction.path, + "Get up and running with Reflex in minutes. A complete set of resources " + "to build, deploy, and scale your application.", + class_name="max-w-2xl text-muted-foreground text-base sm:text-lg leading-7 font-normal text-balance", ), - class_name=ui.cn( - "flex flex-col gap-6 max-lg:text-center relative just-start lg:pb-24", - rx.cond( - HostingBannerState.is_banner_visible, - "lg:pt-[14.5rem] pt-[12.5rem]", - "lg:pt-[10.5rem] pt-[7.5rem]", + rx.el.div( + rx.el.a( + button( + "Build with AI", + ui.icon("ArrowRight01Icon"), + variant="primary", + size="lg", + native_button=False, + ), + href="/ai/", + class_name="rounded-full focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-ring", ), - ), - ), - rx.el.div( - rx.image( - alt="Squares Docs Logo", - custom_attrs={"fetchPriority": "high"}, - src=f"{REFLEX_ASSETS_CDN}common/{rx.color_mode_cond('light', 'dark')}/squares_docs_logo_1.svg", - class_name="pointer-events-none h-auto w-auto lg:absolute max-lg:hidden", - ), - class_name=ui.cn( - "flex", - rx.cond( - HostingBannerState.is_banner_visible, - "lg:pt-[8.5rem] pt-0", - "lg:pt-[4.5rem] pt-0", + rx.el.a( + button( + "Explore Framework", + ui.icon("ArrowRight01Icon"), + variant="outline", + size="lg", + native_button=False, + ), + href=getting_started.introduction.path, + class_name="rounded-full focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-ring", ), + class_name="flex flex-wrap items-center gap-3", ), + class_name="relative z-10 flex flex-col items-start gap-6 lg:max-w-[55%]", ), - class_name=ui.cn( - "flex lg:flex-row flex-col max-w-(--landing-layout-max-width) mx-auto w-full max-lg:pb-10 max-xl:px-6", + artwork( + "squares_docs_logo", + class_name="docs-hero-art absolute left-1/2 w-1/2 max-lg:hidden", ), + class_name="docs-hero relative max-w-(--landing-layout-max-width) mx-auto w-full px-6 xl:px-0 pb-16 lg:pb-24", ) diff --git a/docs/app/reflex_docs/pages/docs_landing/views/hosting.py b/docs/app/reflex_docs/pages/docs_landing/views/hosting.py index 8fafce7a12a..006c9c46e4d 100644 --- a/docs/app/reflex_docs/pages/docs_landing/views/hosting.py +++ b/docs/app/reflex_docs/pages/docs_landing/views/hosting.py @@ -1,51 +1,175 @@ +"""A connected overview of the Reflex Cloud hosting guides.""" + import reflex as rx +from reflex_site_shared.constants import REFLEX_ASSETS_CDN from reflex_docs.pages.docs import hosting as hosting_page -from reflex_docs.pages.docs_landing.views.link_item import faded_borders, link_item +from reflex_docs.pages.docs_landing.views.artwork import artwork + + +def hosting_guide(icon: str, title: str, description: str, href: str) -> rx.Component: + """Link a hosting capability into the shared application diagram.""" + return rx.el.a( + rx.el.div( + rx.icon(icon, size=20, aria_hidden=True, class_name="docs-cloud-icon"), + rx.icon("arrow-up-right", size=16, aria_hidden=True), + class_name="flex items-center justify-between text-muted-foreground", + ), + rx.el.h3( + title, class_name="text-base font-book tracking-tight text-foreground" + ), + rx.el.p(description, class_name="text-sm leading-6 text-muted-foreground"), + href=href, + aria_label=title, + class_name="docs-cloud-guide relative flex min-w-0 flex-1 flex-col gap-3 rounded-xl border border-border bg-background p-6 text-left transition-colors hover:border-border-strong focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-ring", + ) + + +def application_preview() -> rx.Component: + """Illustrate one complete hosted application in a single window.""" + return rx.el.div( + rx.el.div( + rx.el.span("Overview", class_name="text-xs text-foreground"), + rx.el.span("โ€ขโ€ขโ€ข", class_name="text-muted-foreground tracking-[2px]"), + class_name="flex items-center justify-between border-b border-border px-4 py-2", + ), + rx.el.div( + rx.el.div( + *[rx.icon(icon, size=14) for icon in ("layout-grid", "files", "users")], + class_name="flex w-10 shrink-0 flex-col items-center gap-5 border-r border-border bg-muted py-4 text-muted-foreground", + ), + rx.el.div( + rx.el.div( + *[ + rx.el.div( + rx.el.span( + value, class_name="text-lg font-book text-foreground" + ), + rx.el.span( + label, class_name="text-[10px] text-muted-foreground" + ), + class_name="flex flex-1 flex-col rounded-md border border-border px-3 py-2", + ) + for value, label in (("12", "Projects"), ("24", "Members")) + ], + class_name="flex gap-2", + ), + rx.el.div( + *[ + rx.el.div( + style={"height": f"{height}%"}, + class_name="docs-cloud-chart-bar min-w-0 flex-1 rounded-t-[2px]", + ) + for height in (28, 44, 36, 61, 52, 73, 65, 92) + ], + class_name="flex h-16 items-end gap-2 border-b border-border pt-2", + ), + class_name="flex min-w-0 flex-1 flex-col gap-4 p-3", + ), + class_name="flex", + ), + aria_hidden=True, + class_name="overflow-hidden rounded-lg border border-border bg-background text-left", + ) + + +def hosting_application() -> rx.Component: + """Place one unified hosted application at the center of the diagram.""" + return rx.el.div( + rx.el.div( + rx.el.span( + "Deploy anywhere", + class_name="whitespace-nowrap text-sm text-foreground", + ), + rx.el.div( + rx.el.div( + artwork("reflex_mark", class_name="w-4 text-foreground sm:w-5"), + role="img", + aria_label="Reflex", + title="Reflex", + class_name="shrink-0", + ), + *[ + rx.el.img( + src=rx.color_mode_cond( + f"{REFLEX_ASSETS_CDN}landing/integrations/light/{provider}.svg", + f"{REFLEX_ASSETS_CDN}landing/integrations/dark/{provider}.svg", + ), + alt=label, + title=label, + class_name="size-4 object-contain sm:size-5", + ) + for provider, label in ( + ("aws", "AWS"), + ("azure", "Microsoft Azure"), + ("gcp", "Google Cloud"), + ) + ], + class_name="flex shrink-0 items-center gap-1.5", + ), + class_name="flex items-center justify-between gap-2 border-b border-border px-4 py-4 sm:px-6", + ), + rx.el.div( + rx.el.h3( + "Your application", + class_name="text-xl font-book tracking-tight text-foreground", + ), + application_preview(), + class_name="flex flex-col gap-6 p-6 text-center", + ), + class_name="docs-cloud-hub relative min-w-0 self-center rounded-panel border border-border bg-background", + ) def hosting_section() -> rx.Component: + """Explore the connected deployment, security, and operations guides.""" return rx.el.section( rx.el.div( rx.el.h2( "Cloud", - class_name="text-secondary-12 text-3xl font-[575]", + id="docs-cloud-title", + class_name="text-foreground text-3xl font-book tracking-tight", ), rx.el.p( "Learn how to host your applications with Reflex Hosting.", - class_name="text-secondary-11 text-sm font-[475]", + class_name="text-muted-foreground text-sm font-normal", ), class_name="flex flex-col gap-4", ), rx.el.div( - faded_borders(), - link_item( - "CloudServerIcon", - "Deployment", - "Step-by-step instructions to deploy your Reflex application to the cloud, including configuration and setup guides.", - hosting_page.deploy_quick_start.path, - ), - link_item( - "LockKeyIcon", - "Secret Management", - "How to securely manage sensitive environment variables, API keys, and secrets in Reflex Hosting.", - hosting_page.secrets_environment_vars.path, - has_padding_left=True, - ), - link_item( - "EyeIcon", - "Observability", - "Monitor your application's health, view logs, and gain insights using Reflex Hosting's integrated observability tools.", - hosting_page.logs.path, - ), - link_item( - "CodeIcon", - "Custom Headers and Advanced Options", - "Configure custom HTTP headers, set caching policies, and explore advanced hosting settings for your Reflex app.", - hosting_page.deploy_quick_start.path, - has_padding_left=True, - ), - class_name="grid grid-cols-1 lg:grid-cols-2 border-t border-secondary-4 relative", - ), - class_name="flex flex-col gap-10 max-lg:text-center relative max-w-(--landing-layout-max-width) mx-auto w-full justify-start max-xl:px-6 lg:mb-24 overflow-hidden", + rx.el.div( + hosting_guide( + "cloud-upload", + "Deployment", + "Take your Python app from local development to the cloud.", + hosting_page.deploy_quick_start.path, + ), + hosting_guide( + "key-round", + "Secret Management", + "Securely provide API keys and environment variables to your app.", + hosting_page.secrets_environment_vars.path, + ), + class_name="docs-cloud-guides docs-cloud-inputs", + ), + hosting_application(), + rx.el.div( + hosting_guide( + "activity", + "Observability", + "View logs and monitor your application's health.", + hosting_page.logs.path, + ), + hosting_guide( + "settings-2", + "Custom Headers and Advanced Options", + "Set HTTP headers, caching policies, and hosting configuration.", + hosting_page.deploy_quick_start.path, + ), + class_name="docs-cloud-guides docs-cloud-outputs", + ), + class_name="docs-cloud-diagram rounded-panel border border-border", + ), + aria_labelledby="docs-cloud-title", + class_name="flex flex-col gap-10 max-lg:text-center relative max-w-(--landing-layout-max-width) mx-auto w-full justify-start max-xl:px-6 lg:mb-24", ) diff --git a/docs/app/reflex_docs/pages/docs_landing/views/link_item.py b/docs/app/reflex_docs/pages/docs_landing/views/link_item.py index 41fbfb6adfd..7714026796c 100644 --- a/docs/app/reflex_docs/pages/docs_landing/views/link_item.py +++ b/docs/app/reflex_docs/pages/docs_landing/views/link_item.py @@ -3,20 +3,7 @@ def faded_borders() -> rx.Component: - return rx.fragment( - rx.el.div( - class_name="absolute bottom-0 -left-24 w-24 h-px bg-gradient-to-r from-transparent to-current text-secondary-4" - ), - rx.el.div( - class_name="absolute -top-px -left-24 w-24 h-px bg-gradient-to-r from-transparent to-current text-secondary-4" - ), - rx.el.div( - class_name="absolute bottom-0 -right-24 w-24 h-px bg-gradient-to-l from-transparent to-current text-secondary-4" - ), - rx.el.div( - class_name="absolute right-0 -top-24 h-24 w-px bg-gradient-to-b from-transparent to-current text-secondary-4" - ), - ) + return rx.fragment() def link_item( @@ -31,7 +18,7 @@ def link_item( ), rx.el.span( title, - class_name="text-secondary-12 text-xl font-[575] group-hover:text-primary-10 group-hover:dark:text-secondary-11", + class_name="text-secondary-12 text-xl font-book tracking-tight group-hover:text-primary-10 group-hover:dark:text-secondary-11", ), ui.icon( "ArrowRight01Icon", @@ -41,15 +28,15 @@ def link_item( ), rx.el.span( description, - class_name="text-secondary-11 text-sm font-[475] text-start", + class_name="text-secondary-11 text-sm font-normal text-start", ), rx.el.a( to=href, aria_label=title, - class_name="absolute inset-0 rounded-xl focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-primary-9", + class_name="absolute inset-0 rounded-compact focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-primary-9", ), class_name=ui.cn( - "flex flex-col gap-2 pr-8 py-8 group border-r border-b border-secondary-4 relative max-lg:p-6 hover:bg-[linear-gradient(243deg,var(--secondary-2)_0%,var(--secondary-1)_100%)]", + "flex flex-col gap-2 pr-8 py-8 group border-b border-border-subtle relative max-lg:p-6 hover:bg-accent transition-colors motion-reduce:transition-none", "lg:pl-8 pl-6" if has_padding_left else "", ), ) diff --git a/docs/app/reflex_docs/pages/docs_landing/views/mcp_illustration.svg b/docs/app/reflex_docs/pages/docs_landing/views/mcp_illustration.svg new file mode 100644 index 00000000000..e330d958f5b --- /dev/null +++ b/docs/app/reflex_docs/pages/docs_landing/views/mcp_illustration.svg @@ -0,0 +1,26 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="336" height="91" fill="none" viewBox="0 0 336 91" aria-hidden="true" focusable="false"> + <mask id="docs-mcp_illustration-b" width="336" height="96" x="0" y="0" maskUnits="userSpaceOnUse" style="mask-type:alpha"> + <path fill="url(#docs-mcp_illustration-a)" d="M0 0h336v96H0z" transform="matrix(1 0 0 -1 0 96)" /> + </mask> + <g mask="url(#docs-mcp_illustration-b)"> + <mask id="docs-mcp_illustration-d" width="336" height="96" x="0" y="0" maskUnits="userSpaceOnUse" style="mask-type:alpha"> + <path fill="url(#docs-mcp_illustration-c)" d="M0 0h336v96H0z" transform="matrix(1 0 0 -1 0 96)" /> + </mask> + <g fill="var(--subtle-foreground)" mask="url(#docs-mcp_illustration-d)"> + <path d="M5 24h1v1H5zm0 3h1v1H5zm0 3h1v1H5zM5 9h1v1H5zm0 24h1v1H5zm0 3h1v1H5zm0-21h1v1H5zm0 24h1v1H5zm0 12h1v1H5zm0 9h1v1H5zm0 6h1v1H5zm0 12h1v1H5zm0 3h1v1H5zm3-57h1v1H8zm0 3h1v1H8zm0 6h1v1H8zm0 6h1v1H8zm0 3h1v1H8zm0 3h1v1H8zm0 3h1v1H8zm0 18h1v1H8zm0 6h1v1H8zm0 6h1v1H8zm0 6h1v1H8zm0 3h1v1H8zm3-66h1v1h-1zm0-18h1v1h-1zm0 33h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 12h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 12h1v1h-1zm0 3h1v1h-1zm3-63h1v1h-1zm0 21h1v1h-1zm0 12h1v1h-1zm0 9h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm3-60h1v1h-1zm0 9h1v1h-1zm0 3h1v1h-1zm0 15h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm3-51h1v1h-1zm0 3h1v1h-1zm0 15h1v1h-1zm0 3h1v1h-1zm0 12h1v1h-1zm0 6h1v1h-1zm0 9h1v1h-1zm0 3h1v1h-1zm3-54h1v1h-1zm0-21h1v1h-1zm0 27h1v1h-1zm0 15h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm0 21h1v1h-1zm3-69h1v1h-1zm0 3h1v1h-1zm0 15h1v1h-1zm0 6h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm0 9h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 9h1v1h-1zm3-81h1v1h-1zm0 24h1v1h-1zm0-21h1v1h-1zm0 39h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm0 27h1v1h-1zm0 3h1v1h-1zm3-66h1v1h-1zm0-21h1v1h-1zm0 30h1v1h-1zm0-21h1v1h-1zm0 27h1v1h-1zm0-21h1v1h-1zm0 30h1v1h-1zm0 3h1v1h-1zm0 12h1v1h-1zm0 6h1v1h-1zm0 6h1v1h-1zm0 6h1v1h-1zm0 12h1v1h-1zm3-72h1v1h-1zm0 15h1v1h-1zm0 12h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 9h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 9h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm3-72h1v1h-1zm0 12h1v1h-1zm0-21h1v1h-1zm0 24h1v1h-1zm0 12h1v1h-1zm0 12h1v1h-1zm0 3h1v1h-1zm0 12h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm0 6h1v1h-1zm36-69h1v1h-1zm0-15h1v1h-1zm0 24h1v1h-1zm0-21h1v1h-1zm0 30h1v1h-1zm0 12h1v1h-1zm0 6h1v1h-1zm0 9h1v1h-1zm0 3h1v1h-1zm0 15h1v1h-1zm0 3h1v1h-1zm36-66h1v1h-1zm0 3h1v1h-1zm0 30h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm36-78h1v1h-1zm0 24h1v1h-1zm0 12h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 12h1v1h-1zm0 15h1v1h-1zm0 3h1v1h-1zm36-63h1v1h-1zm0-21h1v1h-1zm0 24h1v1h-1zm0 18h1v1h-1zm0 9h1v1h-1zm0 3h1v1h-1zm0 12h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 9h1v1h-1zm36-87h1v1h-1zm0 3h1v1h-1zm0 24h1v1h-1zm0 6h1v1h-1zm0-21h1v1h-1zm0 27h1v1h-1zm0 6h1v1h-1zm0 9h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm0 12h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm36-63h1v1h-1zm0 6h1v1h-1zm0 36h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm0 9h1v1h-1zm0 9h1v1h-1zm36-54h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm0 9h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm36-54h1v1h-1zm0-21h1v1h-1zm0 27h1v1h-1zm0 9h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 9h1v1h-1zm0 3h1v1h-1zm0 9h1v1h-1zm0 24h1v1h-1zm0 3h1v1h-1zM41 24h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm0-21h1v1h-1zm0 30h1v1h-1zm0 12h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 9h1v1h-1zm0 6h1v1h-1zm0 9h1v1h-1zm36-66h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0-21h1v1h-1zm0 3h1v1h-1zm0 24h1v1h-1zm0 6h1v1h-1zm0 9h1v1h-1zm0 18h1v1h-1zm0 9h1v1h-1zm0 12h1v1h-1zm0 3h1v1h-1zm36-69h1v1h-1zm0-21h1v1h-1zm0 30h1v1h-1zm0-21h1v1h-1zm0 27h1v1h-1zm0-21h1v1h-1zm0 27h1v1h-1zm0 24h1v1h-1zm0 3h1v1h-1zm0 12h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm36-87h1v1h-1zm0 36h1v1h-1zm0 12h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 9h1v1h-1zm0 3h1v1h-1zm0 9h1v1h-1zm36-60h1v1h-1zm0 6h1v1h-1zm0-21h1v1h-1zm0 27h1v1h-1zm0-21h1v1h-1zm0 39h1v1h-1zm0 15h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm0 12h1v1h-1zm36-72h1v1h-1zm0 12h1v1h-1zm0 9h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 12h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 9h1v1h-1zm0 3h1v1h-1zm0 12h1v1h-1zm36-57h1v1h-1zm0 9h1v1h-1zm0 6h1v1h-1zm0 9h1v1h-1zm0 12h1v1h-1zm0 12h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm36-63h1v1h-1zm0-21h1v1h-1zm0 27h1v1h-1zm0-21h1v1h-1zm0 33h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 15h1v1h-1zm0 12h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm36-69h1v1h-1zm0 3h1v1h-1zm0-21h1v1h-1zm0 36h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 18h1v1h-1zm0 12h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zM44 21h1v1h-1zm0 3h1v1h-1zm0-21h1v1h-1zm0 39h1v1h-1zm0 3h1v1h-1zm0 12h1v1h-1zm0 6h1v1h-1zm0 9h1v1h-1zm0 9h1v1h-1zm0 6h1v1h-1zm36-66h1v1h-1zm0 9h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm0 9h1v1h-1zm0 18h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm36-69h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0-21h1v1h-1zm0 27h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 9h1v1h-1zm0 9h1v1h-1zm0 9h1v1h-1zm0 3h1v1h-1zm0 12h1v1h-1zm36-69h1v1h-1zm0-21h1v1h-1zm0 30h1v1h-1zm0 6h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm0 12h1v1h-1zm0 3h1v1h-1zm0 9h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm36-66h1v1h-1zm0 3h1v1h-1zm0-21h1v1h-1zm0 24h1v1h-1zm0 6h1v1h-1zm0 9h1v1h-1zm0 9h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 15h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm36-60h1v1h-1zm0-21h1v1h-1zm0 36h1v1h-1zm0 9h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 9h1v1h-1zm0 9h1v1h-1zm0 15h1v1h-1zm36-66h1v1h-1zm0-21h1v1h-1zm0 42h1v1h-1zm0 3h1v1h-1zm0 21h1v1h-1zm0 6h1v1h-1zm0 9h1v1h-1zm0 3h1v1h-1zm36-60h1v1h-1zm0 9h1v1h-1zm0-21h1v1h-1zm0 36h1v1h-1zm0 9h1v1h-1zm0 12h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zM47 21h1v1h-1zm0 3h1v1h-1zm0-21h1v1h-1zm0 24h1v1h-1zm0 18h1v1h-1zm0 3h1v1h-1zm0 12h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 9h1v1h-1zm0 6h1v1h-1zm36-63h1v1h-1zm0 9h1v1h-1zm0-21h1v1h-1zm0 33h1v1h-1zm0 6h1v1h-1zm0 6h1v1h-1zm0 6h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 9h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm36-60h1v1h-1zm0-21h1v1h-1zm0 24h1v1h-1zm0-21h1v1h-1zm0 24h1v1h-1zm0-21h1v1h-1zm0 27h1v1h-1zm0-21h1v1h-1zm0 24h1v1h-1zm0 12h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 18h1v1h-1zm36-72h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0-21h1v1h-1zm0 24h1v1h-1zm0-21h1v1h-1zm0 36h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm0 6h1v1h-1zm0 12h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm36-51h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 9h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm0 6h1v1h-1zm0 9h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm36-72h1v1h-1zm0 9h1v1h-1zm0 3h1v1h-1zm0-21h1v1h-1zm0 27h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 9h1v1h-1zm0 6h1v1h-1zm0 6h1v1h-1zm0 6h1v1h-1zm0 6h1v1h-1zm0 15h1v1h-1zm36-63h1v1h-1zm0 6h1v1h-1zm0-21h1v1h-1zm0 30h1v1h-1zm0 21h1v1h-1zm0 15h1v1h-1zm0 6h1v1h-1zm36-63h1v1h-1zm0 6h1v1h-1zm0 21h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 9h1v1h-1zM50 21h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm0 6h1v1h-1zm0-21h1v1h-1zm0 30h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm0 18h1v1h-1zm36-69h1v1h-1zm0 18h1v1h-1zm0 9h1v1h-1zm0 6h1v1h-1zm0 6h1v1h-1zm0 6h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm36-66h1v1h-1zm0 9h1v1h-1zm0-18h1v1h-1zm0 24h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm0 6h1v1h-1zm0 9h1v1h-1zm0 6h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm36-66h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm0 9h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm0 6h1v1h-1zm0 6h1v1h-1zm0 6h1v1h-1zm36-66h1v1h-1zm0-21h1v1h-1zm0 24h1v1h-1zm0-21h1v1h-1zm0 33h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 12h1v1h-1zm36-54h1v1h-1zm0 12h1v1h-1zm0 3h1v1h-1zm0 12h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm36-66h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm0 15h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 9h1v1h-1zm0 9h1v1h-1zm0 3h1v1h-1zm36-69h1v1h-1zm0-21h1v1h-1zm0 36h1v1h-1zm0-21h1v1h-1zm0 24h1v1h-1zm0 3h1v1h-1zm0 18h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 12h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zM53 24h1v1h-1zm0 3h1v1h-1zm0-21h1v1h-1zm0 24h1v1h-1zm0-21h1v1h-1zm0 27h1v1h-1zm0 18h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 15h1v1h-1zm0 3h1v1h-1zm36-66h1v1h-1zm0-18h1v1h-1zm0 30h1v1h-1zm0 9h1v1h-1zm0 9h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm0 9h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm36-72h1v1h-1zm0 6h1v1h-1zm0-21h1v1h-1zm0 24h1v1h-1zm0-15h1v1h-1zm0 24h1v1h-1zm0-21h1v1h-1zm0 30h1v1h-1zm0 15h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 9h1v1h-1zm0 3h1v1h-1zm36-87h1v1h-1zm0 6h1v1h-1zm0 24h1v1h-1zm0-21h1v1h-1zm0 24h1v1h-1zm0 12h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 9h1v1h-1zm0 9h1v1h-1zm36-42h1v1h-1zm0-21h1v1h-1zm0 24h1v1h-1zm0-21h1v1h-1zm0 30h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm0 6h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm36-69h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 18h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 12h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 9h1v1h-1zm36-63h1v1h-1zm0 6h1v1h-1zm0 9h1v1h-1zm0 9h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 9h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 9h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm36-54h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 9h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 9h1v1h-1zm0 3h1v1h-1zM56 24h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0-21h1v1h-1zm0 33h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 18h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm36-60h1v1h-1zm0-21h1v1h-1zm0 27h1v1h-1zm0 9h1v1h-1zm0 3h1v1h-1zm0 9h1v1h-1zm0 3h1v1h-1zm0 12h1v1h-1zm0 3h1v1h-1zm36-54h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 9h1v1h-1zm0 3h1v1h-1zm0 9h1v1h-1zm0 3h1v1h-1zm0 12h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm36-81h1v1h-1zm0 24h1v1h-1zm0-21h1v1h-1zm0 24h1v1h-1zm0-21h1v1h-1zm0 30h1v1h-1zm0 6h1v1h-1zm0 6h1v1h-1zm0 9h1v1h-1zm0 12h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm36-81h1v1h-1zm0 24h1v1h-1zm0 6h1v1h-1zm0 6h1v1h-1zm0 9h1v1h-1zm0 6h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm36-57h1v1h-1zm0 21h1v1h-1zm0 3h1v1h-1zm0 9h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 9h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm36-54h1v1h-1zm0 6h1v1h-1zm0 6h1v1h-1zm0 9h1v1h-1zm0 9h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm36-63h1v1h-1zm0 15h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm0 12h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm0 9h1v1h-1zM59 21h1v1h-1zm0-21h1v1h-1zm0 24h1v1h-1zm0 9h1v1h-1zm0-21h1v1h-1zm0 36h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 12h1v1h-1zm0 3h1v1h-1zm0 9h1v1h-1zm36-51h1v1h-1zm0 12h1v1h-1zm0 6h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm0 6h1v1h-1zm0 6h1v1h-1zm0 9h1v1h-1zm0 6h1v1h-1zm36-66h1v1h-1zm0 15h1v1h-1zm0-21h1v1h-1zm0 33h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 15h1v1h-1zm36-66h1v1h-1zm0 3h1v1h-1zm0-18h1v1h-1zm0 30h1v1h-1zm0-21h1v1h-1zm0 30h1v1h-1zm0 15h1v1h-1zm0 12h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm36-66h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 9h1v1h-1zm0-21h1v1h-1zm0 24h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 12h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm0 12h1v1h-1zm36-63h1v1h-1zm0-9h1v1h-1zm0 54h1v1h-1zm0 9h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm36-84h1v1h-1zm0 6h1v1h-1zm0 27h1v1h-1zm0 6h1v1h-1zm0 12h1v1h-1zm0 6h1v1h-1zm0 9h1v1h-1zm0 12h1v1h-1zm36-63h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm0 12h1v1h-1zm0 6h1v1h-1zm0 15h1v1h-1zm0 6h1v1h-1zm0 6h1v1h-1zm0 15h1v1h-1zM62 21h1v1h-1zm0 6h1v1h-1zm0 6h1v1h-1zm0-21h1v1h-1zm0 30h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 15h1v1h-1zm0 6h1v1h-1zm0 6h1v1h-1zm0 6h1v1h-1zm36-69h1v1h-1zm0 3h1v1h-1zm0-21h1v1h-1zm0 27h1v1h-1zm0-18h1v1h-1zm0 24h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm0 9h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm36-48h1v1h-1zm0-21h1v1h-1zm0 30h1v1h-1zm0-21h1v1h-1zm0 24h1v1h-1zm0-21h1v1h-1zm0 24h1v1h-1zm0-21h1v1h-1zm0 36h1v1h-1zm0 9h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 9h1v1h-1zm36-63h1v1h-1zm0-21h1v1h-1zm0 6h1v1h-1zm0 27h1v1h-1zm0 9h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm36-57h1v1h-1zm0 18h1v1h-1zm0 6h1v1h-1zm0 12h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 9h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm36-51h1v1h-1zm0 15h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm0 9h1v1h-1zm0 3h1v1h-1zm0 12h1v1h-1zm36-60h1v1h-1zm0-21h1v1h-1zm0 30h1v1h-1zm0-18h1v1h-1zm0 24h1v1h-1zm0 9h1v1h-1zm0 6h1v1h-1zm0 6h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 9h1v1h-1zm36-39h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 9h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 9h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zM65 21h1v1h-1zm0 6h1v1h-1zm0-21h1v1h-1zm0 36h1v1h-1zm0 3h1v1h-1zm0 30h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm36-63h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0-21h1v1h-1zm0 45h1v1h-1zm0 15h1v1h-1zm0 9h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm36-69h1v1h-1zm0-21h1v1h-1zm0 30h1v1h-1zm0-21h1v1h-1zm0 27h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 12h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm0 9h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm36-66h1v1h-1zm0-21h1v1h-1zm0 24h1v1h-1zm0 6h1v1h-1zm0-21h1v1h-1zm0 3h1v1h-1zm0 24h1v1h-1zm0-21h1v1h-1zm0 27h1v1h-1zm0 9h1v1h-1zm0 9h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 9h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm36-69h1v1h-1zm0-21h1v1h-1zm0 27h1v1h-1zm0-21h1v1h-1zm0 27h1v1h-1zm0-21h1v1h-1zm0 36h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 9h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm36-69h1v1h-1zm0-21h1v1h-1zm0 42h1v1h-1zm0 6h1v1h-1zm0 18h1v1h-1zm0 12h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm36-63h1v1h-1zm0 3h1v1h-1zm0-21h1v1h-1zm0 39h1v1h-1zm0 3h1v1h-1zm0 9h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm36-57h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 12h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 12h1v1h-1zM68 24h1v1h-1zm0 6h1v1h-1zm0 6h1v1h-1zm0 9h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm0 12h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm36-42h1v1h-1zm0 6h1v1h-1zm0 12h1v1h-1zm0 9h1v1h-1zm0 6h1v1h-1zm0 9h1v1h-1zm36-57h1v1h-1zm0 21h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 9h1v1h-1zm0 9h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm36-63h1v1h-1zm0 3h1v1h-1zm0 9h1v1h-1zm0 3h1v1h-1zm0-21h1v1h-1zm0 24h1v1h-1zm0 12h1v1h-1zm0 12h1v1h-1zm0 12h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm36-72h1v1h-1zm0 3h1v1h-1zm0-21h1v1h-1zm0 30h1v1h-1zm0-21h1v1h-1zm0 24h1v1h-1zm0-21h1v1h-1zm0 33h1v1h-1zm0 12h1v1h-1zm0 6h1v1h-1zm0 6h1v1h-1zm0 6h1v1h-1zm0 9h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm36-69h1v1h-1zm0-21h1v1h-1zm0 30h1v1h-1zm0-21h1v1h-1zm0 27h1v1h-1zm0-21h1v1h-1zm0 24h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 9h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 9h1v1h-1zm0 3h1v1h-1zm36-54h1v1h-1zm0-21h1v1h-1zm0 24h1v1h-1zm0-21h1v1h-1zm0 24h1v1h-1zm0 3h1v1h-1zm0-21h1v1h-1zm0 24h1v1h-1zm0 15h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 9h1v1h-1zm36-60h1v1h-1zm0 3h1v1h-1zm0-21h1v1h-1zm0 24h1v1h-1zm0-21h1v1h-1zm0 30h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm0 6h1v1h-1zm0 9h1v1h-1zm0 6h1v1h-1zm0 6h1v1h-1zm0 6h1v1h-1zM71 30h1v1h-1zm0 12h1v1h-1zm0 3h1v1h-1zm0 9h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm0 12h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm36-69h1v1h-1zm0 12h1v1h-1zm0 15h1v1h-1zm0 6h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 15h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm36-69h1v1h-1zm0-21h1v1h-1zm0 24h1v1h-1zm0 3h1v1h-1zm0-21h1v1h-1zm0 24h1v1h-1zm0 6h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 12h1v1h-1zm0 12h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 12h1v1h-1zm36-69h1v1h-1zm0 12h1v1h-1zm0-21h1v1h-1zm0 27h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 9h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 9h1v1h-1zm0 9h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm36-63h1v1h-1zm0-21h1v1h-1zm0 24h1v1h-1zm0 6h1v1h-1zm0 6h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 9h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm0 12h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm36-69h1v1h-1zm0 6h1v1h-1zm0-21h1v1h-1zm0 24h1v1h-1zm0-21h1v1h-1zm0 27h1v1h-1zm0-21h1v1h-1zm0 27h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm36-66h1v1h-1zm0-21h1v1h-1zm0 27h1v1h-1zm0 21h1v1h-1zm0 3h1v1h-1zm0 9h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 9h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm36-87h1v1h-1zm0 33h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm0 6h1v1h-1zm0 3h1v1h-1zm0 6h1v1h-1zm0 15h1v1h-1zm0 6h1v1h-1z" /> + </g> + </g> + <defs> + <linearGradient id="docs-mcp_illustration-a" x1="168" x2="168" y1="96" y2="0" gradientUnits="userSpaceOnUse"> + <stop stop-opacity="0" /> + <stop offset=".72" /> + <stop offset="1" stop-opacity="0" /> + </linearGradient> + <linearGradient id="docs-mcp_illustration-c" x1="0" x2="336" y1="48" y2="48" gradientUnits="userSpaceOnUse"> + <stop stop-opacity="0" /> + <stop offset=".101" /> + <stop offset=".899" /> + <stop offset="1" stop-opacity="0" /> + </linearGradient> + </defs> +</svg> diff --git a/docs/app/reflex_docs/pages/docs_landing/views/other.py b/docs/app/reflex_docs/pages/docs_landing/views/other.py index f3945d82c7d..b91c5d5a618 100644 --- a/docs/app/reflex_docs/pages/docs_landing/views/other.py +++ b/docs/app/reflex_docs/pages/docs_landing/views/other.py @@ -10,11 +10,11 @@ def other_section() -> rx.Component: rx.el.div( rx.el.h2( "Other", - class_name="text-secondary-12 text-3xl font-[575]", + class_name="text-secondary-12 text-3xl font-book tracking-tight", ), rx.el.p( "Learn about other features and tools that Reflex offers.", - class_name="text-secondary-11 text-sm font-[475]", + class_name="text-secondary-11 text-sm font-normal", ), class_name="flex flex-col gap-4", ), diff --git a/docs/app/reflex_docs/pages/docs_landing/views/reflex_mark.svg b/docs/app/reflex_docs/pages/docs_landing/views/reflex_mark.svg new file mode 100644 index 00000000000..cbecfd7ce7b --- /dev/null +++ b/docs/app/reflex_docs/pages/docs_landing/views/reflex_mark.svg @@ -0,0 +1,11 @@ +<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> + <rect x="1" y="1" width="14" height="14" rx="3.5" fill="currentColor" /> + <path + d="M9.75 9.16675V11.9376C9.75 12.0181 9.81529 12.0834 9.89583 12.0834H11.3542C11.4347 12.0834 11.5 12.0181 11.5 11.9376V9.31258C11.5 9.23204 11.4347 9.16675 11.3542 9.16675H9.75Z" + fill="var(--background)" + /> + <path + d="M4.64583 3.91675C4.56529 3.91675 4.5 3.98204 4.5 4.06258V11.9376C4.5 12.0181 4.56529 12.0834 4.64583 12.0834H6.10417C6.18471 12.0834 6.25 12.0181 6.25 11.9376V9.31258C6.25 9.23204 6.31529 9.16675 6.39583 9.16675H9.75V7.41675H6.39583C6.31529 7.41675 6.25 7.35146 6.25 7.27091V5.81258C6.25 5.73204 6.31529 5.66675 6.39583 5.66675H9.60417C9.68471 5.66675 9.75 5.73204 9.75 5.81258V7.41675H11.3542C11.4347 7.41675 11.5 7.35146 11.5 7.27091V4.06258C11.5 3.98204 11.4347 3.91675 11.3542 3.91675H4.64583Z" + fill="var(--background)" + /> + </svg> diff --git a/docs/app/reflex_docs/pages/docs_landing/views/self_hosting.py b/docs/app/reflex_docs/pages/docs_landing/views/self_hosting.py index 6f94f974f6e..e6b64ad180e 100644 --- a/docs/app/reflex_docs/pages/docs_landing/views/self_hosting.py +++ b/docs/app/reflex_docs/pages/docs_landing/views/self_hosting.py @@ -9,11 +9,11 @@ def self_hosting_section() -> rx.Component: rx.el.div( rx.el.h2( "Self-Hosting", - class_name="text-secondary-12 text-3xl font-[575]", + class_name="text-secondary-12 text-3xl font-book tracking-tight", ), rx.el.p( "Learn how to self-host your applications with Reflex.", - class_name="text-secondary-11 text-sm font-[475]", + class_name="text-secondary-11 text-sm font-normal", ), class_name="flex flex-col gap-4", ), diff --git a/docs/app/reflex_docs/pages/docs_landing/views/squares_docs_logo.svg b/docs/app/reflex_docs/pages/docs_landing/views/squares_docs_logo.svg new file mode 100644 index 00000000000..551cbce90c3 --- /dev/null +++ b/docs/app/reflex_docs/pages/docs_landing/views/squares_docs_logo.svg @@ -0,0 +1,343 @@ +<svg aria-hidden="true" focusable="false" width="721" height="433" viewBox="0 0 721 433" fill="none" xmlns="http://www.w3.org/2000/svg"> +<mask id="mask0_20497_6755" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="721" height="433"> +<rect width="721" height="433" fill="url(#paint0_linear_20497_6755)"/> +</mask> +<g mask="url(#mask0_20497_6755)"> +<mask id="mask1_20497_6755" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="721" height="433"> +<rect width="721" height="433" fill="url(#paint1_linear_20497_6755)"/> +</mask> +<g mask="url(#mask1_20497_6755)"> +<rect x="0.5" y="0.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="48.5" y="0.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="96.5" y="0.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="144.5" y="0.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="192.5" y="0.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="240.5" y="0.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="288.5" y="0.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="336.5" y="0.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="384.5" y="0.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="432.5" y="0.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="480.5" y="0.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="528.5" y="0.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="576.5" y="0.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="624.5" y="0.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="672.5" y="0.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="0.5" y="48.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="48.5" y="48.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="96.5" y="48.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="144.5" y="48.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="192.5" y="48.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="240.5" y="48.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="288.5" y="48.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="336.5" y="48.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="384.5" y="48.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="432.5" y="48.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="480.5" y="48.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="528.5" y="48.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="576.5" y="48.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="624.5" y="48.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="672.5" y="48.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="0.5" y="96.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="48.5" y="96.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="96.5" y="96.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="144.5" y="96.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="192.5" y="96.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="240.5" y="96.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="288.5" y="96.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="336.5" y="96.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="384.5" y="96.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="432.5" y="96.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="480.5" y="96.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="528.5" y="96.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="576.5" y="96.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="624.5" y="96.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="672.5" y="96.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="0.5" y="144.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="48.5" y="144.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="96.5" y="144.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="144.5" y="144.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="192.5" y="144.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="240.5" y="144.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="288.5" y="144.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="336.5" y="144.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="384.5" y="144.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="432.5" y="144.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="480.5" y="144.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="528.5" y="144.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="576.5" y="144.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="624.5" y="144.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="672.5" y="144.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="0.5" y="192.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="48.5" y="192.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="96.5" y="192.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="144.5" y="192.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="192.5" y="192.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="240.5" y="192.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="288.5" y="192.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="336.5" y="192.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="384.5" y="192.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="432.5" y="192.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="480.5" y="192.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="528.5" y="192.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="576.5" y="192.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="624.5" y="192.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="672.5" y="192.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="0.5" y="240.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="48.5" y="240.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="96.5" y="240.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="144.5" y="240.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="192.5" y="240.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="240.5" y="240.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="288.5" y="240.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="336.5" y="240.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="384.5" y="240.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="432.5" y="240.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="480.5" y="240.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="528.5" y="240.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="576.5" y="240.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="624.5" y="240.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="672.5" y="240.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="0.5" y="288.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="48.5" y="288.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="96.5" y="288.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="144.5" y="288.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="192.5" y="288.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="240.5" y="288.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="288.5" y="288.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="336.5" y="288.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="384.5" y="288.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="432.5" y="288.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="480.5" y="288.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="528.5" y="288.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="576.5" y="288.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="624.5" y="288.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="672.5" y="288.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="0.5" y="336.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="48.5" y="336.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="96.5" y="336.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="144.5" y="336.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="192.5" y="336.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="240.5" y="336.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="288.5" y="336.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="336.5" y="336.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="384.5" y="336.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="432.5" y="336.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="480.5" y="336.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="528.5" y="336.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="576.5" y="336.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="624.5" y="336.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="672.5" y="336.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="0.5" y="384.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="48.5" y="384.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="96.5" y="384.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="144.5" y="384.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="192.5" y="384.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="240.5" y="384.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="288.5" y="384.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="336.5" y="384.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="384.5" y="384.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="432.5" y="384.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="480.5" y="384.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="528.5" y="384.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="576.5" y="384.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="624.5" y="384.5" width="48" height="48" stroke="var(--border-subtle)"/> +<rect x="672.5" y="384.5" width="48" height="48" stroke="var(--border-subtle)"/> +<g filter="url(#filter0_i_20497_6755)"> +<rect x="528.5" y="144.5" width="48" height="48" fill="var(--muted)"/> +<rect x="528.5" y="144.5" width="48" height="48" stroke="var(--border)"/> +</g> +<g filter="url(#filter1_i_20497_6755)"> +<rect x="576.5" y="192.5" width="48" height="48" fill="var(--muted)"/> +<rect x="576.5" y="192.5" width="48" height="48" stroke="var(--border)"/> +</g> +<g filter="url(#filter2_i_20497_6755)"> +<rect x="528.5" y="240.5" width="48" height="48" fill="var(--muted)"/> +<rect x="528.5" y="240.5" width="48" height="48" stroke="var(--border)"/> +</g> +<g filter="url(#filter3_i_20497_6755)"> +<rect x="192.5" y="96.5" width="48" height="48" fill="var(--muted)"/> +<rect x="192.5" y="96.5" width="48" height="48" stroke="var(--border)"/> +</g> +<g filter="url(#filter4_i_20497_6755)"> +<rect x="96.5" y="192.5" width="48" height="48" fill="var(--muted)"/> +<rect x="96.5" y="192.5" width="48" height="48" stroke="var(--border)"/> +</g> +<g filter="url(#filter5_i_20497_6755)"> +<rect x="144.5" y="144.5" width="48" height="48" fill="var(--muted)"/> +<rect x="144.5" y="144.5" width="48" height="48" stroke="var(--border)"/> +</g> +<g filter="url(#filter6_i_20497_6755)"> +<rect x="144.5" y="240.5" width="48" height="48" fill="var(--muted)"/> +<rect x="144.5" y="240.5" width="48" height="48" stroke="var(--border)"/> +</g> +<g filter="url(#filter7_i_20497_6755)"> +<rect x="288.5" y="240.5" width="48" height="48" fill="var(--muted)"/> +<rect x="288.5" y="240.5" width="48" height="48" stroke="var(--border)"/> +</g> +<g filter="url(#filter8_i_20497_6755)"> +<rect x="192.5" y="288.5" width="48" height="48" fill="var(--muted)"/> +<rect x="192.5" y="288.5" width="48" height="48" stroke="var(--border)"/> +</g> +<g filter="url(#filter9_i_20497_6755)"> +<rect x="336.5" y="192.5" width="48" height="48" fill="var(--muted)"/> +<rect x="336.5" y="192.5" width="48" height="48" stroke="var(--border)"/> +</g> +<g filter="url(#filter10_i_20497_6755)"> +<rect x="384.5" y="144.5" width="48" height="48" fill="var(--muted)"/> +<rect x="384.5" y="144.5" width="48" height="48" stroke="var(--border)"/> +</g> +<g filter="url(#filter11_i_20497_6755)"> +<rect x="480.5" y="96.5" width="48" height="48" fill="var(--muted)"/> +<rect x="480.5" y="96.5" width="48" height="48" stroke="var(--border)"/> +</g> +<g filter="url(#filter12_i_20497_6755)"> +<rect x="480.5" y="288.5" width="48" height="48" fill="var(--muted)"/> +<rect x="480.5" y="288.5" width="48" height="48" stroke="var(--border)"/> +</g> +</g> +</g> +<defs> +<filter id="filter0_i_20497_6755" x="528" y="144" width="49" height="55" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"> +<feFlood flood-opacity="0" result="BackgroundImageFix"/> +<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/> +<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> +<feOffset dy="6"/> +<feGaussianBlur stdDeviation="8"/> +<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/> +<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.04 0"/> +<feBlend mode="normal" in2="shape" result="effect1_innerShadow_20497_6755"/> +</filter> +<filter id="filter1_i_20497_6755" x="576" y="192" width="49" height="55" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"> +<feFlood flood-opacity="0" result="BackgroundImageFix"/> +<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/> +<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> +<feOffset dy="6"/> +<feGaussianBlur stdDeviation="8"/> +<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/> +<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.04 0"/> +<feBlend mode="normal" in2="shape" result="effect1_innerShadow_20497_6755"/> +</filter> +<filter id="filter2_i_20497_6755" x="528" y="240" width="49" height="55" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"> +<feFlood flood-opacity="0" result="BackgroundImageFix"/> +<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/> +<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> +<feOffset dy="6"/> +<feGaussianBlur stdDeviation="8"/> +<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/> +<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.04 0"/> +<feBlend mode="normal" in2="shape" result="effect1_innerShadow_20497_6755"/> +</filter> +<filter id="filter3_i_20497_6755" x="192" y="96" width="49" height="55" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"> +<feFlood flood-opacity="0" result="BackgroundImageFix"/> +<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/> +<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> +<feOffset dy="6"/> +<feGaussianBlur stdDeviation="8"/> +<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/> +<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.04 0"/> +<feBlend mode="normal" in2="shape" result="effect1_innerShadow_20497_6755"/> +</filter> +<filter id="filter4_i_20497_6755" x="96" y="192" width="49" height="55" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"> +<feFlood flood-opacity="0" result="BackgroundImageFix"/> +<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/> +<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> +<feOffset dy="6"/> +<feGaussianBlur stdDeviation="8"/> +<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/> +<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.04 0"/> +<feBlend mode="normal" in2="shape" result="effect1_innerShadow_20497_6755"/> +</filter> +<filter id="filter5_i_20497_6755" x="144" y="144" width="49" height="55" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"> +<feFlood flood-opacity="0" result="BackgroundImageFix"/> +<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/> +<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> +<feOffset dy="6"/> +<feGaussianBlur stdDeviation="8"/> +<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/> +<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.04 0"/> +<feBlend mode="normal" in2="shape" result="effect1_innerShadow_20497_6755"/> +</filter> +<filter id="filter6_i_20497_6755" x="144" y="240" width="49" height="55" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"> +<feFlood flood-opacity="0" result="BackgroundImageFix"/> +<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/> +<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> +<feOffset dy="6"/> +<feGaussianBlur stdDeviation="8"/> +<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/> +<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.04 0"/> +<feBlend mode="normal" in2="shape" result="effect1_innerShadow_20497_6755"/> +</filter> +<filter id="filter7_i_20497_6755" x="288" y="240" width="49" height="55" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"> +<feFlood flood-opacity="0" result="BackgroundImageFix"/> +<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/> +<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> +<feOffset dy="6"/> +<feGaussianBlur stdDeviation="8"/> +<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/> +<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.04 0"/> +<feBlend mode="normal" in2="shape" result="effect1_innerShadow_20497_6755"/> +</filter> +<filter id="filter8_i_20497_6755" x="192" y="288" width="49" height="55" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"> +<feFlood flood-opacity="0" result="BackgroundImageFix"/> +<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/> +<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> +<feOffset dy="6"/> +<feGaussianBlur stdDeviation="8"/> +<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/> +<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.04 0"/> +<feBlend mode="normal" in2="shape" result="effect1_innerShadow_20497_6755"/> +</filter> +<filter id="filter9_i_20497_6755" x="336" y="192" width="49" height="55" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"> +<feFlood flood-opacity="0" result="BackgroundImageFix"/> +<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/> +<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> +<feOffset dy="6"/> +<feGaussianBlur stdDeviation="8"/> +<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/> +<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.04 0"/> +<feBlend mode="normal" in2="shape" result="effect1_innerShadow_20497_6755"/> +</filter> +<filter id="filter10_i_20497_6755" x="384" y="144" width="49" height="55" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"> +<feFlood flood-opacity="0" result="BackgroundImageFix"/> +<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/> +<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> +<feOffset dy="6"/> +<feGaussianBlur stdDeviation="8"/> +<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/> +<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.04 0"/> +<feBlend mode="normal" in2="shape" result="effect1_innerShadow_20497_6755"/> +</filter> +<filter id="filter11_i_20497_6755" x="480" y="96" width="49" height="55" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"> +<feFlood flood-opacity="0" result="BackgroundImageFix"/> +<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/> +<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> +<feOffset dy="6"/> +<feGaussianBlur stdDeviation="8"/> +<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/> +<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.04 0"/> +<feBlend mode="normal" in2="shape" result="effect1_innerShadow_20497_6755"/> +</filter> +<filter id="filter12_i_20497_6755" x="480" y="288" width="49" height="55" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"> +<feFlood flood-opacity="0" result="BackgroundImageFix"/> +<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/> +<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/> +<feOffset dy="6"/> +<feGaussianBlur stdDeviation="8"/> +<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/> +<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.04 0"/> +<feBlend mode="normal" in2="shape" result="effect1_innerShadow_20497_6755"/> +</filter> +<linearGradient id="paint0_linear_20497_6755" x1="721" y1="216.5" x2="0" y2="216.5" gradientUnits="userSpaceOnUse"> +<stop stop-opacity="0"/> +<stop offset="0.102273"/> +<stop offset="0.901515"/> +<stop offset="1" stop-opacity="0"/> +</linearGradient> +<linearGradient id="paint1_linear_20497_6755" x1="360.5" y1="0" x2="360.5" y2="433" gradientUnits="userSpaceOnUse"> +<stop stop-opacity="0"/> +<stop offset="0.204167"/> +<stop offset="0.8"/> +<stop offset="1" stop-opacity="0"/> +</linearGradient> +</defs> +</svg> diff --git a/docs/app/reflex_docs/pages/integrations/integration_gallery.py b/docs/app/reflex_docs/pages/integrations/integration_gallery.py index 8bf4c41c2c6..e2732f8b2d5 100644 --- a/docs/app/reflex_docs/pages/integrations/integration_gallery.py +++ b/docs/app/reflex_docs/pages/integrations/integration_gallery.py @@ -1,6 +1,7 @@ import reflex as rx import reflex_components_internal as ui from reflex.experimental import ClientStateVar +from reflex_site_shared.components.marketing_button import button from reflex_site_shared.integrations import get_integration_logo_url from .integration_list import get_integration_path @@ -19,16 +20,13 @@ def integration_filter_button(data: dict): - active_pill = ( - "border border-primary-8 bg-primary-3 hover:bg-primary-3 !text-primary-10" - ) - - return ui.button( + active = selected_filter.value == data["name"] + return button( ui.icon(icon=data["icon"]), - rx.el.p(data["name"], class_name="text-sm"), - variant="outline", - class_name="flex flex-row items-center " - + rx.cond(selected_filter.value == data["name"], active_pill, "").to(str), + data["name"], + variant=rx.cond(active, "primary", "outline"), + size="sm", + aria_pressed=active, on_click=selected_filter.set_value(data["name"]), ) @@ -37,9 +35,9 @@ def integration_filters(): return rx.el.div( rx.el.div( *[integration_filter_button(data) for data in FilterOptions], - class_name="flex flex-row gap-3 items-center justify-center flex-wrap", + class_name="flex flex-row gap-2 items-center flex-wrap", ), - class_name="w-full max-w-[64.19rem] pb-12", + class_name="w-full pb-8", ) @@ -66,30 +64,25 @@ def integration_gallery_cards(data): unstyled=True, class_name="size-8 flex items-center justify-center", ), - ui.link( - render_=ui.button( - "Learn More", - variant="outline", - class_name="group-hover:bg-secondary-2 hover:bg-transparent", - ), - to=data["path"], + rx.el.span( + "Learn more", + rx.icon("arrow-up-right", size=14, aria_hidden=True), + class_name="flex items-center gap-1 text-sm font-book text-muted-foreground group-hover:text-foreground", ), class_name="w-full flex flex-row items-center justify-between", ), rx.el.div( - rx.el.p( - data["title"], class_name="text-lg font-semibold text-secondary-12" - ), + rx.el.p(data["title"], class_name="text-lg font-book text-foreground"), rx.el.p( data["description"], - class_name="font-medium text-secondary-11 leading-[1.35]", + class_name="text-sm font-normal text-muted-foreground leading-6", ), class_name="flex flex-col gap-y-1", ), - class_name="flex flex-col gap-y-6 rounded-ui-xl border border-secondary-a4 bg-secondary-1 shadow-small p-6 h-[13rem] justify-between hover:bg-secondary-2", + class_name="flex flex-col gap-6 rounded-card border border-border-subtle bg-background p-5 min-h-48 h-full justify-between group-hover:bg-muted", ), href=data["path"], - class_name="group text-inherit hover:!text-inherit decoration-none no-underline " + class_name="docs-integration-card group rounded-card text-inherit hover:!text-inherit no-underline focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-ring " + rx.cond( (selected_filter.value == data["tags"]) | (selected_filter.value == "All"), "flex", @@ -113,10 +106,6 @@ def integration_gallery(): def integration_request_form(): return rx.el.div( rx.el.p("Missing an integration?"), - rx.el.p( - rx.fragment( - "Click ", request_integration_dialog(), " to tell us what you need." - ) - ), - class_name="w-full max-w-[64.19rem] flex flex-col gap-y-1 text-md font-semibold py-10 items-center justify-center", + request_integration_dialog(), + class_name="w-full flex flex-wrap items-center justify-between gap-4 border-t border-border-subtle py-8 mt-10 text-sm font-normal text-muted-foreground", ) diff --git a/docs/app/reflex_docs/pages/integrations/integration_header.py b/docs/app/reflex_docs/pages/integrations/integration_header.py index c4aa997effb..dbf7dc067ed 100644 --- a/docs/app/reflex_docs/pages/integrations/integration_header.py +++ b/docs/app/reflex_docs/pages/integrations/integration_header.py @@ -5,12 +5,12 @@ def integration_header() -> rx.Component: return rx.el.section( rx.el.h1( "Integrations", - class_name="max-w-full inline-block bg-clip-text bg-gradient-to-r from-secondary-12 to-secondary-11 w-full font-xx-large text-center text-transparent text-balance mx-auto break-words", + class_name="w-full text-4xl sm:text-5xl font-book tracking-tight text-foreground text-balance", ), - rx.el.h2( + rx.el.p( """Easily connect with the tools your team already uses or extend your app with any Python SDK, library, or API.""", - class_name="max-w-full w-full font-semibold text-md text-center text-secondary-11 -mt-2 md:text-2xl mx-auto text-balance word-wrap break-words md:whitespace-pre", + class_name="max-w-2xl text-base font-normal leading-7 text-muted-foreground text-balance", ), - class_name="flex flex-col justify-center items-center gap-4 mx-auto w-full max-w-[64.19rem] pb-[2.5rem] pt-12", + class_name="flex flex-col items-start gap-4 w-full pb-8", ) diff --git a/docs/app/reflex_docs/pages/integrations/integration_list.py b/docs/app/reflex_docs/pages/integrations/integration_list.py index 128dfc7297b..b5237e8ca4c 100644 --- a/docs/app/reflex_docs/pages/integrations/integration_list.py +++ b/docs/app/reflex_docs/pages/integrations/integration_list.py @@ -49,7 +49,7 @@ def get_integration_path() -> list: result.append({ key: { - "path": f"{web_path_prefix}/{slug}", + "path": f"{web_path_prefix}/{slug}/", "tags": tag, "description": description, "name": key, diff --git a/docs/app/reflex_docs/pages/integrations/integration_request.py b/docs/app/reflex_docs/pages/integrations/integration_request.py index 53e1cc52027..edacef3d027 100644 --- a/docs/app/reflex_docs/pages/integrations/integration_request.py +++ b/docs/app/reflex_docs/pages/integrations/integration_request.py @@ -1,5 +1,6 @@ import reflex as rx import reflex_components_internal as ui +from reflex_site_shared.components.marketing_button import button from reflex_docs.templates.docpage.feedback_state import FeedbackState @@ -8,9 +9,11 @@ def request_integration_dialog() -> rx.Component: return ui.dialog( title="Request Integration", description="Let us know what integration you'd like to see added.", - trigger=rx.el.strong( - rx.el.u("here"), - class_name="cursor-pointer text-primary-11 decoration-primary-9", + trigger=button( + "Request integration", + variant="outline", + size="sm", + native_button=False, ), content=rx.el.form( ui.textarea( @@ -21,10 +24,11 @@ def request_integration_dialog() -> rx.Component: max_length=2000, class_name="h-[6rem]", ), - ui.button( + button( "Submit", variant="primary", size="md", + type="submit", ), on_submit=[ rx.run_script( @@ -35,4 +39,5 @@ def request_integration_dialog() -> rx.Component: class_name="flex flex-col gap-4 w-full", reset_on_submit=True, ), + class_name="docs-integration-dialog rounded-panel border-border [&_[data-slot=dialog-title]]:font-book [&_[data-slot=dialog-title]]:tracking-tight", ) diff --git a/docs/app/reflex_docs/pages/library_previews.py b/docs/app/reflex_docs/pages/library_previews.py index ed8fae173cf..b6efb3d958b 100644 --- a/docs/app/reflex_docs/pages/library_previews.py +++ b/docs/app/reflex_docs/pages/library_previews.py @@ -2,6 +2,7 @@ from reflex.utils.format import to_snake_case, to_title_case from reflex_site_shared.constants import REFLEX_ASSETS_CDN +from reflex_docs.docgen_pipeline import render_markdown from reflex_docs.pages.docs.metadata import truncate_meta_description from reflex_docs.templates.docpage import docpage, h1_comp, text_comp_2 @@ -55,16 +56,24 @@ def get_preview_asset(name: str, section: str) -> tuple[str, str]: def component_card(name: str, link: str, section: str) -> rx.Component: preview_section, preview_name = get_preview_asset(name, section) + + def preview_src(mode: str) -> str: + """Resolve the bundled Treemap preview or an existing CDN preview.""" + path = f"components_previews/{preview_section}/{mode}/{preview_name}.svg" + if (preview_section, preview_name) == ("charts", "treemap"): + return rx.asset(path) + return f"{REFLEX_ASSETS_CDN}{path}" + return rx.link( rx.box( rx.image( - src=f"{REFLEX_ASSETS_CDN}components_previews/{preview_section}/light/{preview_name}.svg", + src=preview_src("light"), loading="lazy", alt=f"Image preview of {name}", class_name="object-contain object-center h-full w-full dark:hidden", ), rx.image( - src=f"{REFLEX_ASSETS_CDN}components_previews/{preview_section}/dark/{preview_name}.svg", + src=preview_src("dark"), loading="lazy", alt=f"Image preview of {name}", class_name="object-contain object-center h-full w-full dark:block hidden", @@ -72,14 +81,15 @@ def component_card(name: str, link: str, section: str) -> rx.Component: rx.box( rx.text( get_display_name(name), - class_name="truncate font-base text-secondary-12", + class_name="truncate text-sm font-book text-foreground", ), rx.icon("chevron-right", size=14, class_name="!text-secondary-9"), class_name="bottom-0 absolute flex flex-row justify-between w-full px-4 py-2 items-center", ), - class_name="rounded-xl border overflow-hidden relative box-border shadow-large bg-secondary-2 hover:bg-secondary-3 transition-bg border-secondary-5", + class_name="aspect-[320/232] rounded-card border overflow-hidden relative box-border bg-background group-hover:bg-muted transition-colors border-border-subtle", ), href=link, + class_name="docs-preview-card group rounded-card focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-ring", ) @@ -115,6 +125,31 @@ def page() -> rx.Component: ) component_list = get_component_list(type) + if component_category == "State": + return rx.box( + h1_comp(text="State"), + render_markdown( + "Reflex state connects your components to application data. " + "Store values in an `rx.State` class, pass its vars to component " + "props, and update those values from event handlers.\n\n" + "## Start with state and events\n\n" + "- [State overview](/docs/state/overview/): define application " + "state and connect it to your interface.\n" + "- [Base vars](/docs/vars/base-vars/): store typed values that " + "components can display.\n" + "- [Computed vars](/docs/vars/computed-vars/): derive values " + "from existing state.\n" + "- [Events](/docs/events/events-overview/): update state in " + "response to user interactions.\n\n" + "## Build reusable interactive components\n\n" + "Use [component state](/docs/state-structure/component-state/) " + "when each instance needs its own data. Use " + "[conditional rendering](/docs/components/conditional-rendering/) " + "and [rendering iterables](/docs/components/rendering-iterables/) " + "to make your interface respond to state changes." + ), + class_name="flex flex-col w-full mb-10", + ) return rx.box( rx.box( h1_comp(text=get_display_name(component_category)), @@ -209,7 +244,7 @@ def page() -> rx.Component: }, "state": { "path": "state", - "description": "Components that help with state, such as state variables, state hooks, and state management. These are useful for creating responsive and interactive user interfaces.", + "description": "Connect Reflex components to Python state, typed vars, computed values, and event handlers. Find guides for reusable interactive components and dynamic rendering.", "component_category": "State", }, } diff --git a/docs/app/reflex_docs/pages/page404.py b/docs/app/reflex_docs/pages/page404.py new file mode 100644 index 00000000000..7810a7dece4 --- /dev/null +++ b/docs/app/reflex_docs/pages/page404.py @@ -0,0 +1,54 @@ +"""Missing-page view using the same shell as the documentation landing page.""" + +import reflex as rx +from reflex_site_shared.components.marketing_button import button +from reflex_site_shared.route import Route + + +def not_found() -> rx.Component: + """Render a recovery path inside the current documentation design. + + Returns: + Docs navigation, missing-page message, and shared landing footer. + """ + # These views depend on the page registry, so load them after registration. + from reflex_docs.views.docs_navbar import docs_navbar + from reflex_docs.views.editorial_footer import editorial_footer + + return rx.el.div( + docs_navbar(), + rx.el.main( + rx.el.div( + rx.el.p("404", class_name="text-sm font-book text-muted-foreground"), + rx.el.h1( + "Page not found", + class_name="text-4xl sm:text-5xl font-book tracking-tight text-foreground", + ), + rx.el.p( + "We couldn't find that documentation page. Browse the docs or use search to find what you need.", + class_name="max-w-md text-base leading-7 text-muted-foreground", + ), + rx.el.elements.a( + button( + "Back to docs", + variant="primary", + size="md", + native_button=False, + ), + href="/docs/", + class_name="rounded-full focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-ring", + ), + class_name="flex min-h-[65vh] flex-col items-start justify-center gap-6 mx-auto max-w-(--landing-layout-max-width) px-6 pb-16 pt-[calc(var(--docs-header-height)+4rem)]", + ), + editorial_footer(), + ), + class_name="min-h-screen bg-background", + ) + + +page404 = Route( + path="/404", + title="Page Not Found ยท Reflex Docs", + component=not_found, + add_as_page=False, +) diff --git a/docs/app/reflex_docs/reflex_docs.py b/docs/app/reflex_docs/reflex_docs.py index b58f3adac72..58d8fb5f2f9 100644 --- a/docs/app/reflex_docs/reflex_docs.py +++ b/docs/app/reflex_docs/reflex_docs.py @@ -35,9 +35,11 @@ def _llms_txt_directive() -> rx.Component: rx.el.span("For AI agents: the complete documentation index is at "), rx.el.a("llms.txt", href=LLMS_TXT_PATH), rx.el.span( - ". Markdown versions are available by appending .md or sending " - "Accept: text/markdown." + ". Remove the trailing slash from a page URL and append .md " + "to read its Markdown version. For the docs home, use " ), + rx.el.a("index.md", href="/index.md"), + ".", class_name="sr-only", ) @@ -52,7 +54,10 @@ def _llms_txt_directive() -> rx.Component: radius="large", accent_color="violet", ), - head_components=favicons_links(), + head_components=[ + *favicons_links(), + rx.el.link(rel="icon", type="image/svg+xml", href=rx.asset("favicon.svg")), + ], ) app.register_lifespan_task(monitor_checkly_status) @@ -140,6 +145,15 @@ def _canonical_url(path: str) -> str: canonical = None meta = list(route.meta) if route.meta is not None else [] if canonical is not None: + meta.append( + rx.el.link( + rel="alternate", + type="text/markdown", + href=canonical + "index.md" + if route.path.strip("/") == "" + else canonical.rstrip("/") + ".md", + ) + ) meta.append( rx.el.script( json.dumps( @@ -235,7 +249,7 @@ def _redirect_page(target: str): ], ) -app.add_page(page404.component, route=page404.path) +app.add_page(page404.component, route=page404.path, title=page404.title) # HTTP 301 applies when page requests reach this backend. Separate frontend diff --git a/docs/app/reflex_docs/templates/docpage/docpage.py b/docs/app/reflex_docs/templates/docpage/docpage.py index 4de61d1528e..56c805f20fe 100644 --- a/docs/app/reflex_docs/templates/docpage/docpage.py +++ b/docs/app/reflex_docs/templates/docpage/docpage.py @@ -250,7 +250,7 @@ def breadcrumb(path: str, nav_sidebar: rx.Component, doc_content: str | None = N label = to_title_case(to_snake_case(segment), sep=" ") label = _BREADCRUMB_LABEL_OVERRIDES.get(label, label) base_class = ui.cn( - "min-h-8 flex items-center text-sm font-[525] text-secondary-12 last:text-secondary-11", + "min-h-8 flex items-center text-sm font-book text-foreground last:text-muted-foreground", "truncate" if i == len(segments) - 1 else "", ) @@ -265,7 +265,7 @@ def breadcrumb(path: str, nav_sidebar: rx.Component, doc_content: str | None = N label, class_name=ui.cn( base_class, - "hover:text-primary-10 dark:hover:text-primary-9", + "hover:text-muted-foreground rounded-sm focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring", ), underline="none", href=href, @@ -318,7 +318,7 @@ def breadcrumb(path: str, nav_sidebar: rx.Component, doc_content: str | None = N class_name="flex flex-row items-center gap-2 lg:p-0 p-[0.563rem]", ), class_name=ui.cn( - "relative z-10 flex flex-row justify-between items-center gap-4 lg:gap-0 border-secondary-4 mt-[139px] lg:p-0 border-b lg:border-none w-full max-lg:py-2", + "docs-breadcrumb relative z-10 flex flex-row justify-between items-center gap-4 lg:gap-0 border-secondary-4 mt-[139px] lg:p-0 border-b lg:border-none w-full max-lg:py-2", rx.cond( HostingBannerState.is_banner_visible, "lg:mt-[139px]", @@ -418,10 +418,11 @@ def wrapper(*args, **kwargs) -> rx.Component: ), underline="none", href=prev.link, - class_name="py-0.5 lg:py-0 rounded-lg lg:w-auto font-small text-secondary-9 hover:!text-secondary-11 transition-color", + class_name="py-0.5 rounded-sm text-sm font-normal text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-ring", ), rx.text( - next_prev_name, class_name="font-smbold text-secondary-12" + next_prev_name, + class_name="text-base font-book tracking-tight text-foreground", ), class_name="flex flex-col justify-start gap-1", ) @@ -442,10 +443,11 @@ def wrapper(*args, **kwargs) -> rx.Component: ), underline="none", href=next.link, - class_name="py-0.5 lg:py-0 rounded-lg lg:w-auto font-small text-secondary-9 hover:!text-secondary-11 transition-color", + class_name="py-0.5 rounded-sm text-sm font-normal text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-ring", ), rx.text( - next_prev_name, class_name="font-smbold text-secondary-12" + next_prev_name, + class_name="text-base font-book tracking-tight text-foreground", ), class_name="flex flex-col justify-start gap-1 items-end", ) @@ -492,10 +494,11 @@ def wrapper(*args, **kwargs) -> rx.Component: ), ), rx.box( - rx.el.article(comp, class_name="[&>div]:!p-0"), + rx.el.article(comp, class_name="docs-prose [&>div]:!p-0"), rx.el.nav( *links, - class_name="flex flex-row gap-2 mt-8 lg:mt-10 mb-6 lg:mb-12", + aria_label="Previous and next pages", + class_name="docs-page-navigation flex flex-row gap-2 mt-8 lg:mt-10 mb-8", ), docpage_footer(path=path.rstrip("/"), edit_href=edit_href), class_name="lg:mt-0 h-auto", @@ -554,10 +557,10 @@ def hover_item(component: rx.Component, component_str: str) -> rx.Component: get_icon(icon="copy", class_name="p-[5px]"), rx.text( component_str, - class_name="flex-1 font-small truncate", + class_name="flex-1 text-sm font-book truncate", ), on_click=rx.set_clipboard(component_str), - class_name="flex flex-row items-center gap-1.5 border-secondary-5 bg-secondary-1 hover:bg-secondary-3 shadow-small pr-1.5 border rounded-md w-full max-w-[300px] text-secondary-11 transition-bg cursor-pointer", + class_name="flex flex-row items-center gap-1.5 border-border bg-background hover:bg-muted pr-3 border rounded-compact min-h-9 w-full max-w-[300px] text-muted-foreground transition-colors cursor-pointer focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring", ), ), ) @@ -780,5 +783,6 @@ def style_grid( ), ), ), - class_name="flex flex-col justify-center items-center gap-6 border-secondary-4 bg-secondary-2 mb-4 p-6 border rounded-xl", + class_name="flex flex-col justify-center items-center gap-6 border-border-subtle bg-muted mb-4 p-6 border rounded-card", + data_docs_example=True, ) diff --git a/docs/app/reflex_docs/templates/docpage/sidebar/sidebar.py b/docs/app/reflex_docs/templates/docpage/sidebar/sidebar.py index 22f9459de8d..37cf44bad3e 100644 --- a/docs/app/reflex_docs/templates/docpage/sidebar/sidebar.py +++ b/docs/app/reflex_docs/templates/docpage/sidebar/sidebar.py @@ -31,7 +31,6 @@ from .state import SideBarBase, SideBarItem SIDEBAR_ICON_MAP = { - "Getting Started": "rocket", "Tutorials": "graduation-cap", "Advanced Onboarding": "newspaper", "Components": "layers", @@ -347,6 +346,7 @@ def sidebar_comp( is_docs_hosting = url.startswith("/hosting/") is_docs_ai_builder = url.startswith("/ai/") + is_ai_overview = url == "/ai/" is_ai_mcp_or_skills = ( url.startswith("/ai/integrations/agent-toolkit/") | url.startswith("/ai/integrations/skills/") @@ -381,10 +381,16 @@ def sidebar_comp( ai_builder_categories = rx.el.ul( sidebar_category( - "AI Builder", - ai_builder_pages.overview.best_practices.path, + "Overview", + "/ai/", + "sparkles", + is_ai_overview, + ), + sidebar_category( + "Reflex Build", + ai_builder_pages.overview.what_is_reflex_build.path, "bot", - ~is_ai_mcp_or_skills, + ~is_ai_mcp_or_skills & ~is_ai_overview, ), sidebar_category( "Agent Toolkit", @@ -422,7 +428,7 @@ def sidebar_comp( ) ai_builder_overview_content = rx.el.ul( create_sidebar_section( - "Overview", + "Guides", ai_builder_pages.overview.best_practices.path, ai_builder_overview_items, ai_builder_overview_index, @@ -498,19 +504,19 @@ def sidebar_comp( rx.icon("atom", size=16), # pyright: ignore [reportCallIssue] rx.el.h5( "Custom Components", - class_name="font-smbold text-[0.875rem] text-secondary-12 leading-5 tracking-[-0.01313rem] transition-color", + class_name="text-sm font-book leading-5 tracking-tight text-foreground transition-colors", ), class_name="flex flex-row items-center gap-3 text-secondary-12", ), rx.text( # pyright: ignore [reportCallIssue] "See what components people have made with Reflex!", - class_name="font-small text-secondary-11", + class_name="text-sm font-normal leading-5 text-muted-foreground", ), - class_name="flex flex-col gap-2 border-secondary-5 bg-secondary-1 hover:bg-secondary-3 shadow-large px-3.5 py-2 border rounded-xl transition-bg", + class_name="flex flex-col gap-2 border-border-subtle bg-muted hover:border-border-strong px-3.5 py-3 border rounded-card transition-colors", ), underline="none", href=custom_components.path, - class_name="w-fit lg:ml-[2.5rem]", + class_name="w-fit rounded-card lg:ml-[2.5rem] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring", ), class_name="m-0 p-0 flex flex-col items-start gap-8 w-full list-none list-style-none", ) diff --git a/docs/app/reflex_docs/templates/docpage/sidebar/sidebar_items/ai.py b/docs/app/reflex_docs/templates/docpage/sidebar/sidebar_items/ai.py index 049bf502293..904713f5271 100644 --- a/docs/app/reflex_docs/templates/docpage/sidebar/sidebar_items/ai.py +++ b/docs/app/reflex_docs/templates/docpage/sidebar/sidebar_items/ai.py @@ -7,7 +7,7 @@ def get_sidebar_items_ai_builder_overview(): return [ create_item( - "Overview", + "Getting Started", children=[ ai_builder.overview.best_practices, ai_builder.overview.what_is_reflex_build, diff --git a/docs/app/reflex_docs/templates/docpage/sidebar/sidebar_items/component_lib.py b/docs/app/reflex_docs/templates/docpage/sidebar/sidebar_items/component_lib.py index ba4090e25a6..526e789c2d6 100644 --- a/docs/app/reflex_docs/templates/docpage/sidebar/sidebar_items/component_lib.py +++ b/docs/app/reflex_docs/templates/docpage/sidebar/sidebar_items/component_lib.py @@ -40,7 +40,7 @@ def sort_html_components(components: list) -> list: def get_component_link(category, clist, prefix="") -> str: component_name = rx.utils.format.to_kebab_case(clist[0]) # construct the component link. The component name points to the name of the md file. - return f"/library/{prefix.strip('/') + '/' if prefix.strip('/') else ''}{category.lower().replace(' ', '-')}/{component_name.lower()}" + return f"/library/{prefix.strip('/') + '/' if prefix.strip('/') else ''}{category.lower().replace(' ', '-')}/{component_name.lower()}/" def get_category_children(category, category_list, prefix=""): diff --git a/docs/app/reflex_docs/views/docs_navbar.py b/docs/app/reflex_docs/views/docs_navbar.py index aea1fe95652..9c3fb7be981 100644 --- a/docs/app/reflex_docs/views/docs_navbar.py +++ b/docs/app/reflex_docs/views/docs_navbar.py @@ -1,6 +1,9 @@ import reflex as rx import reflex_components_internal as ui -from reflex_components_internal.blocks.demo_form import demo_form_dialog +from reflex_components_internal.blocks.demo_form import ( + demo_form_dialog, + demo_form_open_cs, +) from reflex_site_shared.components.docs_shell import docs_navbar_frame from reflex_site_shared.components.icons import get_icon from reflex_site_shared.components.marketing_button import button @@ -8,34 +11,34 @@ GITHUB_STARS, GITHUB_URL, REFLEX_ASSETS_CDN, - REFLEX_URL, + XY_GITHUB_STARS, +) +from reflex_site_shared.views.hosting_banner import ( + AGENT_TOOLKIT_EARLY_ACCESS_URL, + HostingBannerState, ) -from reflex_docs.components.docpage.navbar.buttons.sidebar import navbar_sidebar_button -from reflex_docs.pages.docs import ai_builder, getting_started, hosting +from reflex_docs.pages.docs import getting_started, hosting from reflex_docs.views.search import search_bar def github_button() -> rx.Component: - label = f"View Reflex on GitHub - {GITHUB_STARS // 1000}K stars" + stars = f"{(GITHUB_STARS + XY_GITHUB_STARS) / 1000:.0f}K" + label = f"View Reflex on GitHub - {stars} combined stars for Reflex and Reflex XY" return rx.el.elements.a( - button( - get_icon(icon="github_navbar", class_name="shrink-0"), - f"{GITHUB_STARS // 1000}K", - custom_attrs={"aria-label": label}, - size="sm", - variant="ghost", - native_button=False, - ), + get_icon(icon="github_navbar", class_name="size-4 shrink-0"), + stars, href=GITHUB_URL, target="_blank", rel="noopener noreferrer", - custom_attrs={"aria-label": label}, + aria_label=label, + title=f"Reflex + Reflex XY: {stars} combined GitHub stars", + class_name="inline-flex h-9 items-center gap-2 rounded-full text-sm font-book text-foreground transition-colors hover:text-muted-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring", ) def logo() -> rx.Component: - return rx.el.elements.a( + return rx.el.a( rx.el.div( rx.image( src=f"{REFLEX_ASSETS_CDN}logos/light/reflex.svg", @@ -60,8 +63,9 @@ def logo() -> rx.Component: class_name="shrink-0 hidden dark:block", ), ), - href=REFLEX_URL, - class_name="flex flex-row gap-2.5 items-center shrink-0 mr-10", + href="/", + aria_label="Docs overview", + class_name="flex flex-row gap-2.5 items-center shrink-0 rounded-compact focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-ring", ) @@ -69,117 +73,198 @@ def menu_item( text: str, href: str, active_str: str = "", external: bool = False ) -> rx.Component: router_path = rx.State.router.page.path - active_cn = "shadow-[inset_0_-1px_0_0_var(--primary-10)] [&_button]:text-primary-10 [&_div]:text-primary-10" + # Router paths include the deployment prefix; navigation describes docs routes. + router_path = rx.cond( + (router_path == "/docs") | router_path.startswith("/docs/"), + router_path[5:], + router_path, + ) + is_overview = (router_path == "") | (router_path == "/") | (router_path == "/index") - # For paths starting with "/" (like Start), use exact match - # For "framework", it's the default - active when in /docs but not matching other sections - # For other segments (like "ai"), use contains if active_str.startswith("/"): - if active_str == "/": - active = (router_path == "/") | (router_path == "/index") - else: - active = router_path == active_str + active = is_overview if active_str == "/" else router_path == active_str elif active_str == "framework": - is_overview = (router_path == "/") | (router_path == "/index") - is_ai_builder = router_path.startswith("/ai/") | router_path.startswith( - "/docs/ai/" - ) - is_hosting = router_path.contains("hosting") - is_xy = router_path.startswith("/xy/") | router_path.startswith("/docs/xy/") + is_ai_builder = (router_path == "/ai") | router_path.startswith("/ai/") + is_hosting = router_path.startswith("/hosting/") + is_xy = router_path.startswith("/xy/") active = ~is_overview & ~is_ai_builder & ~is_hosting & ~is_xy - elif active_str == "ai": - active = router_path.startswith("/ai/") | router_path.startswith("/docs/ai/") - elif active_str == "xy": - active = router_path.startswith("/xy/") | router_path.startswith("/docs/xy/") else: - active = router_path.contains(active_str) + active = (router_path == f"/{active_str}") | router_path.startswith( + f"/{active_str}/" + ) anchor = rx.el.elements.a if external else rx.el.a - return ui.navigation_menu.item( + return rx.el.li( anchor( + text, + href=href, + aria_current=rx.cond(active, "page", "false"), + class_name="docs-navbar-link inline-flex h-9 items-center justify-center rounded-full px-3 text-sm font-book text-muted-foreground transition-colors hover:text-foreground aria-[current=page]:text-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring", + ), + class_name="flex items-center", + ) + + +def mobile_navigation() -> rx.Component: + """Show the same docs destinations as the desktop header. + + Returns: + Mobile navigation drawer with native links and the shared booking action. + """ + return rx.drawer.root( + rx.drawer.trigger( button( - text, - size="sm", + rx.icon("menu", size=18), variant="ghost", - native_button=False, + size="icon-sm", + aria_label="Open sidebar", ), - href=href, + as_child=True, ), - class_name=ui.cn( - "md:flex hidden h-full items-center justify-center", - rx.cond(active, active_cn, ""), + rx.drawer.portal( + rx.drawer.content( + rx.el.div( + rx.drawer.title( + "Documentation", + class_name="text-xs font-book text-muted-foreground", + font_size="0.75rem", + font_weight="450", + line_height="1.5", + letter_spacing="normal", + ), + rx.drawer.close( + button( + rx.icon("x", size=18), + variant="ghost", + size="icon-sm", + aria_label="Close navigation", + ), + as_child=True, + ), + class_name="flex items-center justify-between gap-4", + ), + rx.el.nav( + *[ + rx.drawer.close( + (rx.el.elements.a if external else rx.el.a)( + label, + rx.icon( + "arrow-up-right" if external else "arrow-right", + size=16, + aria_hidden=True, + ), + href=href, + class_name="flex min-h-14 items-center justify-between gap-4 border-b border-border-subtle text-base font-book text-foreground focus-visible:outline-2 focus-visible:outline-ring", + ), + as_child=True, + ) + for label, href, external in ( + ("Overview", "/", False), + ("Build with AI", "/ai/", False), + ("Framework", getting_started.introduction.path, False), + ("Cloud", hosting.deploy_quick_start.path, False), + ("XY", "/docs/xy/", True), + ) + ], + aria_label="Documentation navigation", + class_name="flex flex-col", + ), + rx.drawer.close( + button( + "Book a Demo", + variant="primary", + size="md", + on_click=rx.call_function(demo_form_open_cs.set_value(True)), + class_name="w-full mt-6", + ), + as_child=True, + ), + class_name="docs-mobile-menu fixed inset-x-0 bottom-0 top-(--docs-header-height) flex flex-col gap-4 overflow-y-auto border-t border-border-subtle bg-background p-6 outline-none", + top="var(--docs-header-height)", + z_index=10000, + ), ), - custom_attrs={"role": "menuitem"}, + direction="bottom", ) def navigation_menu() -> rx.Component: - return ui.navigation_menu.root( - ui.navigation_menu.list( - menu_item("Overview", "/", "/"), - menu_item("Build with AI", ai_builder.overview.best_practices.path, "ai"), - menu_item("Framework", getting_started.introduction.path, "framework"), - menu_item("Cloud", hosting.deploy_quick_start.path, "hosting"), - menu_item("XY", "/docs/xy/", "xy", external=True), - class_name="flex flex-row items-center gap-2 m-0 h-full list-none", - custom_attrs={"role": "menubar"}, - ), - ui.navigation_menu.list( - ui.navigation_menu.item( - github_button(), - unstyled=True, - class_name="md:flex hidden", - custom_attrs={"role": "menuitem"}, + """Pair quiet documentation links with compact marketing-style actions.""" + return rx.el.div( + rx.el.nav( + rx.el.ul( + menu_item("Overview", "/", "/"), + menu_item("Build with AI", "/ai/", "ai"), + menu_item("Framework", getting_started.introduction.path, "framework"), + menu_item("Cloud", hosting.deploy_quick_start.path, "hosting"), + menu_item("XY", "/docs/xy/", "xy", external=True), + class_name="m-0 flex h-full list-none items-center p-0", ), - ui.navigation_menu.item( + aria_label="Documentation navigation", + class_name="hidden h-full lg:flex", + ), + rx.el.div( + rx.el.div( + button( + ui.icon("Search01Icon", size=16, aria_hidden=True), + variant="ghost", + size="icon-sm", + aria_label="Search (loading)", + aria_disabled=True, + aria_busy=True, + tab_index=-1, + class_name="group-has-[.ReflexSearch-root]/docs-search:hidden", + ), search_bar(), - unstyled=True, - custom_attrs={"role": "menuitem"}, + class_name="group/docs-search docs-navbar-search flex h-9 w-9 lg:w-40 shrink-0 items-center justify-center", ), - ui.navigation_menu.item( + rx.el.div(github_button(), class_name="hidden xl:flex"), + rx.el.div( demo_form_dialog( + id_prefix="docs-booking", trigger=button( "Book a Demo", size="sm", variant="primary", - class_name=" whitespace-nowrap max-xl:hidden", + class_name="whitespace-nowrap text-sm", native_button=False, ), ), - unstyled=True, - class_name="xl:flex hidden", - custom_attrs={"role": "menuitem"}, + class_name="hidden xl:flex", ), - ui.navigation_menu.item( - navbar_sidebar_button(), - class_name="md:hidden flex", - unstyled=True, - custom_attrs={"role": "menuitem"}, - ), - class_name="flex flex-row lg:gap-4 gap-2 m-0 h-full list-none items-center", - custom_attrs={"role": "menubar"}, + rx.el.div(mobile_navigation(), class_name="flex lg:hidden"), + class_name="ml-auto flex items-center gap-2 sm:gap-6", ), - ui.navigation_menu.portal( - ui.navigation_menu.positioner( - ui.navigation_menu.popup( - ui.navigation_menu.viewport(), - unstyled=True, - class_name="relative h-[var(--popup-height)] w-max origin-[var(--transform-origin)] transition-[opacity,transform,width,height,scale,translate] duration-[0.35s] ease-[cubic-bezier(0.22,1,0.36,1)] data-[ending-style]:scale-90 data-[ending-style]:opacity-0 data-[ending-style]:duration-150 data-[starting-style]:scale-90 data-[starting-style]:opacity-0 min-[500px]:w-[var(--popup-width)] xs:w-[var(--popup-width)] rounded-xl bg-secondary-1 overflow-hidden", - style={ - "box-shadow": "0 0 0 1px rgba(0, 0, 0, 0.03), 0 -1px 1px 0 rgba(0, 0, 0, 0.04), 0 16px 32px 0 rgba(0, 0, 0, 0.08), 0 1px 1px 0 rgba(0, 0, 0, 0.08), 0 4px 8px 0 rgba(0, 0, 0, 0.03);", - }, - ), - side_offset=30, - align="start", - align_offset=-20, - ), - ), - unstyled=True, - class_name="relative flex w-full items-center h-full justify-between gap-6 mx-auto flex-row", + class_name="flex h-full min-w-0 flex-1 items-center justify-between gap-2", ) @rx.memo def docs_navbar() -> rx.Component: - return docs_navbar_frame(logo(), navigation_menu()) + """Render the editorial header with a dismissible announcement.""" + return rx.fragment( + rx.cond( + HostingBannerState.is_banner_visible, + rx.el.aside( + rx.el.elements.a( + "Reflex Agent Toolkit is launching. Get early access", + href=AGENT_TOOLKIT_EARLY_ACCESS_URL, + class_name="text-center text-xs sm:text-sm font-medium rounded-sm focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#ffffff]", + ), + rx.el.button( + ui.icon("MultiplicationSignIcon", size=16), + type="button", + aria_label="Close banner", + on_click=HostingBannerState.hide_banner, + class_name="absolute right-2 top-1/2 -translate-y-1/2 flex size-8 items-center justify-center rounded-full hover:bg-white/15 focus-visible:outline-2 focus-visible:outline-[#ffffff]", + ), + custom_attrs={"data-docs-announcement": ""}, + class_name="fixed top-0 z-[10000] flex h-14 sm:h-10 w-full items-center justify-center bg-[#181818] text-[#ffffff] px-12", + ), + ), + rx.el.div( + docs_navbar_frame(logo(), navigation_menu(), show_banner=False), + class_name="[&_.docs-navbar]:top-[calc(var(--docs-header-height)-4rem)]", + ), + ) diff --git a/docs/app/reflex_docs/views/editorial_footer.py b/docs/app/reflex_docs/views/editorial_footer.py new file mode 100644 index 00000000000..91a2b6c3380 --- /dev/null +++ b/docs/app/reflex_docs/views/editorial_footer.py @@ -0,0 +1,244 @@ +"""Open footer layout for the documentation landing page.""" + +from datetime import datetime +from typing import Literal + +import reflex as rx +from reflex.style import color_mode, set_color_mode +from reflex_site_shared.backend.signup import IndexState +from reflex_site_shared.backend.status import StatusState +from reflex_site_shared.components.icons import get_icon +from reflex_site_shared.components.server_status import server_status +from reflex_site_shared.constants import ( + CHANGELOG_URL, + DISCORD_URL, + GITHUB_ORG_URL, + LINKEDIN_URL, + REFLEX_ASSETS_CDN, + REFLEX_BUILD_URL, + REFLEX_URL, + TWITTER_URL, +) + +_FOOTER_LINKS = ( + ( + "Product", + ( + ("AI Builder", REFLEX_BUILD_URL), + ("Agent Toolkit", "/docs/ai/integrations/agent-toolkit/"), + ("Enterprise", "/docs/enterprise/overview/"), + ("App Management", "/hosting/"), + ("Pricing", "/pricing/"), + ), + ), + ( + "Solutions", + ( + ("Enterprise", "/use-cases/"), + ("Finance", "/use-cases/finance/"), + ("Healthcare", "/use-cases/healthcare/"), + ("Consulting", "/use-cases/consulting/"), + ("Government", "/use-cases/government/"), + ), + ), + ( + "Resources", + ( + ("Blog", "/blog/"), + ("Templates", "/templates/"), + ("Integrations", "/docs/ai/integrations/overview/"), + ("FAQ", "/faq/"), + ), + ), + ( + "Developers", + ( + ("Documentation", "/docs/"), + ("Changelog", CHANGELOG_URL), + ("Common Errors", "/errors/"), + ), + ), + ( + "Migration", + ( + ("From No-Code", "/migration/no-code/"), + ("From Low-Code", "/migration/low-code/"), + ("From Other Frameworks", "/migration/other-frameworks/"), + ("From Other AI Tools", "/migration/other-ai-tools/"), + ), + ), + ( + "Company", + ( + ("About", "/about/"), + ("Careers", "https://www.ycombinator.com/companies/reflex/jobs"), + ("Privacy Policy", "https://build.reflex.dev/privacy-policy"), + ("Terms of Service", "https://build.reflex.dev/terms-of-use"), + ), + ), +) + +_FOCUS = ( + "focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-ring" +) + + +def _newsletter() -> rx.Component: + """Integrate a quiet, labeled signup into the footer's column grid.""" + return rx.el.div( + rx.el.h2( + "Updates from Reflex", + class_name="text-base font-book tracking-tight text-foreground", + ), + rx.cond( + IndexState.signed_up, + rx.el.div( + rx.el.p("Thanks for subscribing!", role="status"), + rx.el.button( + "Use another email", + type="button", + on_click=IndexState.signup_for_another_user, + class_name=f"w-fit text-sm underline underline-offset-4 {_FOCUS}", + ), + class_name="mt-2 flex flex-col items-start gap-3 text-sm text-muted-foreground", + ), + rx.el.div( + rx.el.p( + "Product updates and new guides, in your inbox.", + id="docs-newsletter-description", + class_name="mt-2 text-sm leading-6 text-muted-foreground", + ), + rx.el.form( + rx.el.label( + "Email address", + html_for="docs-newsletter-email", + class_name="sr-only", + ), + rx.el.input( + id="docs-newsletter-email", + name="input_email", + type="email", + auto_complete="email", + aria_describedby="docs-newsletter-description", + placeholder="Your email address", + required=True, + class_name="h-11 w-0 min-w-0 flex-1 border-0 bg-transparent p-0 text-sm text-foreground outline-none placeholder:text-subtle-foreground focus-visible:outline-none", + ), + rx.el.button( + "Subscribe", + rx.icon("arrow-right", size=14, aria_hidden=True), + type="submit", + class_name=f"inline-flex shrink-0 items-center gap-2 rounded-sm py-3 text-sm font-book text-foreground transition-colors hover:text-muted-foreground {_FOCUS}", + ), + on_submit=IndexState.signup, + class_name="mt-3 flex items-center gap-4 border-b border-border transition-colors focus-within:border-foreground", + ), + ), + ), + class_name="docs-footer-newsletter w-full min-w-0 lg:col-span-2", + ) + + +def _theme_toggle() -> rx.Component: + """Offer the three existing color modes in a flat segmented control.""" + modes: tuple[tuple[Literal["system", "light", "dark"], str], ...] = ( + ("system", "computer_footer"), + ("light", "sun_footer"), + ("dark", "moon_footer"), + ) + return rx.el.div( + *[ + rx.el.button( + get_icon(icon, class_name="size-3.5"), + on_click=set_color_mode(mode), + type="button", + aria_label=f"Toggle {mode} color mode", + aria_pressed=color_mode == mode, + class_name=f"flex size-7 items-center justify-center rounded-compact transition-colors {_FOCUS}", + ) + for mode, icon in modes + ], + role="group", + aria_label="Color mode", + class_name="docs-theme-toggle flex w-fit items-center gap-0.5", + ) + + +def _link_column(heading: str, links: tuple[tuple[str, str], ...]) -> rx.Component: + """Keep root-site destinations outside the docs router basename.""" + return rx.el.nav( + rx.el.h2(heading, class_name="mb-3 text-xs font-book text-muted-foreground"), + *[ + rx.el.elements.a( + text, + href=href, + target="_blank" if not href.startswith("/") else None, + rel="noopener noreferrer" if not href.startswith("/") else None, + class_name=f"w-fit rounded-sm text-sm font-book leading-6 text-foreground transition-colors hover:text-muted-foreground {_FOCUS}", + ) + for text, href in links + ], + aria_label=f"{heading} footer links", + class_name="flex min-w-0 flex-col gap-2", + ) + + +def editorial_footer() -> rx.Component: + """Render the newsletter, open link directory, and compact utility row.""" + return rx.el.footer( + rx.el.div( + rx.el.elements.a( + rx.image( + src=f"{REFLEX_ASSETS_CDN}logos/light/reflex.svg", + alt="Reflex home", + class_name="block h-5 w-auto dark:hidden", + ), + rx.image( + src=f"{REFLEX_ASSETS_CDN}logos/dark/reflex.svg", + alt="Reflex home", + class_name="hidden h-5 w-auto dark:block", + ), + href=REFLEX_URL, + class_name=f"w-fit rounded-sm lg:col-span-4 {_FOCUS}", + ), + _newsletter(), + class_name="grid grid-cols-1 items-start gap-8 pb-12 sm:grid-cols-2 lg:grid-cols-6", + ), + rx.el.div( + *[_link_column(heading, links) for heading, links in _FOOTER_LINKS], + class_name="grid grid-cols-2 gap-x-8 gap-y-10 md:grid-cols-3 lg:grid-cols-6", + ), + rx.el.div( + rx.el.div( + *[ + rx.el.elements.a( + get_icon(icon, class_name="size-4 shrink-0"), + href=url, + aria_label=f"Social link for {name}", + title=f"{name} (opens in a new tab)", + target="_blank", + rel="noopener noreferrer", + class_name="docs-footer-social-link flex size-7 items-center justify-center rounded-full text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-foreground", + ) + for icon, url, name in ( + ("twitter_footer", TWITTER_URL, "Twitter"), + ("github_navbar", GITHUB_ORG_URL, "GitHub"), + ("linkedin_footer", LINKEDIN_URL, "LinkedIn"), + ("discord_navbar", DISCORD_URL, "Discord"), + ) + ], + class_name="flex flex-wrap items-center gap-3", + ), + rx.el.span( + f"Reflex ยฉ {datetime.now().year} Pynecone, Inc.", + class_name="text-xs font-normal text-muted-foreground", + ), + rx.el.div( + server_status(StatusState.status), + _theme_toggle(), + class_name="flex flex-wrap items-center gap-4 [&>a]:rounded-full [&>a]:focus-visible:outline-2 [&>a]:focus-visible:outline-ring [&>a>div]:text-xs [&>a>div]:font-normal", + ), + class_name="flex flex-col items-start justify-between gap-6 pt-16 lg:flex-row lg:flex-wrap lg:items-center", + ), + class_name="docs-footer mx-auto w-full max-w-(--landing-layout-max-width) border-t border-border-subtle px-6 pb-10 pt-12 text-foreground sm:pt-16 xl:px-0", + ) diff --git a/docs/app/rxconfig.py b/docs/app/rxconfig.py index f5c884863bb..893de890942 100644 --- a/docs/app/rxconfig.py +++ b/docs/app/rxconfig.py @@ -5,6 +5,7 @@ config = rx.Config( app_name="reflex_docs", + deploy_url="https://reflex.dev", frontend_path="/docs", show_built_with_reflex=False, frontend_lazy_bundled_libraries=True, @@ -17,7 +18,7 @@ telemetry_enabled=False, plugins=[ rx.plugins.TailwindV4Plugin(), - SharedSiteStylesPlugin(), + SharedSiteStylesPlugin(editorial=True), rx.plugins.SitemapPlugin(trailing_slash="always"), AgentFilesPlugin(), ], diff --git a/docs/app/tests/frontend_quality.test.mjs b/docs/app/tests/frontend_quality.test.mjs index 4d6ecbbd01b..cbeacd169a0 100644 --- a/docs/app/tests/frontend_quality.test.mjs +++ b/docs/app/tests/frontend_quality.test.mjs @@ -196,3 +196,36 @@ with RegistrationContext(), patch('reflex.compiler.compiler.get_config', return_ else globalThis.window = previousWindow; } }); + +test("Editorial actions preserve native semantics and caller events", async () => { + const { createElement } = await import(resolve("react")); + const { renderToStaticMarkup } = await import(resolve("react-dom/server")); + const { transform } = await import(resolve("rolldown/utils")); + const filename = path.join(web, "public/components/GradientButton.tsx"); + const source = await readFile(filename, "utf8"); + const compiled = await transform(filename, source, {jsx: {runtime: "automatic"}}); + const { GradientButton } = await import(moduleUrl(compiled.code + .replaceAll('from "react/jsx-runtime"', `from "${resolve("react/jsx-runtime")}"`) + .replaceAll('from "clsx-for-tailwind"', `from "${resolve("clsx-for-tailwind")}"`))); + const html = renderToStaticMarkup(createElement(GradientButton, { + type: "submit", disabled: true, name: "action", value: "send", form: "feedback", + }, "Send")); + assert.match(html, /^<button/); + for (const prop of ['type="submit"', 'disabled=""', 'name="action"', 'value="send"', 'form="feedback"']) { + assert.ok(html.includes(prop), prop); + } + assert.match(html, /rounded-full/); + assert.match(html, /focus-visible:outline-2/); + assert.equal(GradientButton({children: "Link", nativeButton: false}).type, "div"); + assert.equal(GradientButton({children: "Demo", nativeButton: false, "aria-haspopup": "dialog"}).type, "button"); + let moves = 0; + const element = GradientButton({children: "Send", onMouseMove: () => moves++}); + const properties = new Map(); + element.props.onMouseMove({clientX: 12, clientY: 24, currentTarget: { + getBoundingClientRect: () => ({left: 2, top: 4}), + style: {setProperty: (key, value) => properties.set(key, value)}, + }}); + assert.equal(moves, 1); + assert.equal(properties.get("--glow-x"), "10px"); + assert.equal(properties.get("--glow-y"), "20px"); +}); diff --git a/docs/app/tests/test_agent_files.py b/docs/app/tests/test_agent_files.py index c223060eeba..cb02926377f 100644 --- a/docs/app/tests/test_agent_files.py +++ b/docs/app/tests/test_agent_files.py @@ -107,8 +107,8 @@ def test_generate_llms_txt_groups_docs_at_public_root(monkeypatch): "- [Reflex Build: Best Practices](https://reflex.dev/docs/ai/overview/best-practices.md)" in content ) - assert "Resend Integration" not in content - assert "Code and Review" not in content + assert "Resend Integration" in content + assert "Code and Review" in content assert "### Agent Toolkit\n\n" in content assert ( "- [Agent Toolkit](https://reflex.dev/docs/ai/integrations/agent-toolkit.md)" @@ -151,8 +151,9 @@ def test_generate_markdown_file_content_adds_agent_directive(monkeypatch, tmp_pa assert content.startswith( "> For AI agents: the complete documentation index is at " - "[llms.txt](https://reflex.dev/docs/llms.txt). Markdown versions are " - "available by appending `.md` or sending `Accept: text/markdown`.\n\n" + "[llms.txt](https://reflex.dev/docs/llms.txt). For a Markdown version, " + "remove the trailing slash from the page URL and append `.md`. " + "The docs home is available at [index.md](http://localhost:3000/docs/index.md).\n\n" "# Overview" ) @@ -219,8 +220,9 @@ def test_generate_dynamic_api_reference_files(monkeypatch): assert Path("api-reference/var.md") in files assert files[Path("api-reference/var.md")].startswith( "> For AI agents: the complete documentation index is at " - "[llms.txt](https://reflex.dev/docs/llms.txt). Markdown versions are " - "available by appending `.md` or sending `Accept: text/markdown`.\n\n" + "[llms.txt](https://reflex.dev/docs/llms.txt). For a Markdown version, " + "remove the trailing slash from the page URL and append `.md`. " + "The docs home is available at [index.md](https://reflex.dev/docs/index.md).\n\n" "# Var\n\n" ) assert "## Methods" in files[Path("api-reference/var.md")] @@ -231,8 +233,9 @@ def test_generate_dynamic_api_reference_files(monkeypatch): assert Path("api-reference/eventhandler.md") in files assert files[Path("api-reference/eventhandler.md")].startswith( "> For AI agents: the complete documentation index is at " - "[llms.txt](https://reflex.dev/docs/llms.txt). Markdown versions are " - "available by appending `.md` or sending `Accept: text/markdown`.\n\n" + "[llms.txt](https://reflex.dev/docs/llms.txt). For a Markdown version, " + "remove the trailing slash from the page URL and append `.md`. " + "The docs home is available at [index.md](https://reflex.dev/docs/index.md).\n\n" "# Eventhandler\n\n" ) assert Path("api-reference/event-handler.md") not in files @@ -243,8 +246,9 @@ def test_generate_dynamic_api_reference_files(monkeypatch): env_vars = files[Path("api-reference/environment-variables.md")] assert env_vars.startswith( "> For AI agents: the complete documentation index is at " - "[llms.txt](https://reflex.dev/docs/llms.txt). Markdown versions are " - "available by appending `.md` or sending `Accept: text/markdown`.\n\n" + "[llms.txt](https://reflex.dev/docs/llms.txt). For a Markdown version, " + "remove the trailing slash from the page URL and append `.md`. " + "The docs home is available at [index.md](https://reflex.dev/docs/index.md).\n\n" "# Environment Variables\n\n" ) assert "`reflex.config.EnvironmentVariables`" in env_vars @@ -304,8 +308,9 @@ def test_generate_llms_full_txt_stitches_markdown_docs(monkeypatch, tmp_path): section="API Reference", ), "> For AI agents: the complete documentation index is at " - "[llms.txt](https://reflex.dev/docs/llms.txt). Markdown versions are " - "available by appending `.md` or sending `Accept: text/markdown`.\n\n" + "[llms.txt](https://reflex.dev/docs/llms.txt). For a Markdown version, " + "remove the trailing slash from the page URL and append `.md`. " + "The docs home is available at [index.md](https://reflex.dev/docs/index.md).\n\n" "# Eventhandler\n\n" "`reflex_base.event.EventHandler`\n", ) @@ -364,3 +369,24 @@ def test_generate_agent_files_emits_trailing_slash_variants(): twin = markdown_path_for_trailing_slash_url(path) assert twin in files, f"missing trailing-slash twin for {path}" assert files[twin] == files[path], f"content mismatch for {twin}" + + +def test_agent_directive_only_advertises_static_markdown(): + """Static builds must not promise unsupported content negotiation.""" + from agent_files._plugin import MARKDOWN_DIRECTIVE + + assert "Accept:" not in MARKDOWN_DIRECTIVE + assert "trailing slash" in MARKDOWN_DIRECTIVE + + +def test_cloud_cli_markdown_matches_published_commands(): + """CLI Markdown and the agent index must include the rendered command source.""" + from agent_files._plugin import generate_agent_files + from reflex_docs.pages.docs.cloud_cliref import modules + + files = dict(generate_agent_files()) + for name, source in modules.items(): + path = Path(f"hosting/cli/{name}.md") + assert path in files + assert source.strip() in files[path] + assert f"/docs/{path}" in files[Path("llms.txt")] diff --git a/docs/app/tests/test_config.py b/docs/app/tests/test_config.py index 304fea3388b..b70166da922 100644 --- a/docs/app/tests/test_config.py +++ b/docs/app/tests/test_config.py @@ -9,14 +9,14 @@ @pytest.mark.parametrize( "deploy_url,expected", [ - (None, "http://localhost:3000"), + (None, "https://reflex.dev"), ("https://staging.example.com", "https://staging.example.com"), ], ) def test_docs_deployment_origin_comes_from_environment( monkeypatch, deploy_url, expected ): - """Local runs keep the framework default; deployment jobs supply their origin.""" + """Builds default to the public origin; staging may explicitly override it.""" monkeypatch.delenv("REFLEX_DEPLOY_URL", raising=False) monkeypatch.delenv("REFLEX_FRONTEND_PORT", raising=False) if deploy_url is not None: diff --git a/docs/app/tests/test_docs_navbar.py b/docs/app/tests/test_docs_navbar.py index b33630de7b2..b4c63b70ac3 100644 --- a/docs/app/tests/test_docs_navbar.py +++ b/docs/app/tests/test_docs_navbar.py @@ -74,7 +74,7 @@ def test_external_menu_items_use_plain_anchors(navbar): def test_navigation_menu_routes_in_app_destinations(navbar): """Every in-app navbar destination compiles to a router link.""" - from reflex_docs.pages.docs import ai_builder, getting_started, hosting + from reflex_docs.pages.docs import getting_started, hosting router_targets = { dest @@ -84,7 +84,7 @@ def test_navigation_menu_routes_in_app_destinations(navbar): for path in ( "/", - ai_builder.overview.best_practices.path, + "/ai/", getting_started.introduction.path, hosting.deploy_quick_start.path, ): @@ -109,10 +109,25 @@ def test_navigation_menu_keeps_cross_app_destinations_raw(navbar): def test_external_links_bypass_the_router(navbar): """Absolute off-site URLs render as raw anchors, not router links.""" - from reflex_site_shared.constants import GITHUB_URL, REFLEX_URL + from reflex_site_shared.constants import GITHUB_URL assert _collect_links(navbar.github_button()) == [("anchor", GITHUB_URL)] - assert _collect_links(navbar.logo()) == [("anchor", REFLEX_URL)] + + +def test_docs_logo_returns_to_internal_overview(navbar): + """The Docs logo resolves its overview destination through the docs basename.""" + assert _collect_links(navbar.logo()) == [("router", "/")] + + +def test_github_badge_combines_both_projects(navbar, monkeypatch): + """The displayed star count sums both projects and identifies the total.""" + monkeypatch.setattr(navbar, "GITHUB_STARS", 12500) + monkeypatch.setattr(navbar, "XY_GITHUB_STARS", 2500) + button = navbar.github_button() + assert "15K combined stars for Reflex and Reflex XY" in str( + button.custom_attrs["aria-label"] + ) + assert "15K" in str(button.children[-1]) def test_reflex_el_a_and_elements_a_are_not_interchangeable(): diff --git a/docs/app/tests/test_editorial_browser.py b/docs/app/tests/test_editorial_browser.py new file mode 100644 index 00000000000..9e50bd35e6d --- /dev/null +++ b/docs/app/tests/test_editorial_browser.py @@ -0,0 +1,592 @@ +"""Production-browser checks: set REFLEX_DOCS_PREVIEW_URL to the app origin.""" + +import os +from typing import Literal + +import pytest +from playwright.sync_api import Page, expect + +PREVIEW_URL = os.environ.get("REFLEX_DOCS_PREVIEW_URL", "") +pytestmark = pytest.mark.skipif( + not PREVIEW_URL, reason="Requires a running docs preview" +) + + +@pytest.mark.parametrize("width", [320, 375, 768, 1440]) +@pytest.mark.parametrize("route", ["/docs/", "/docs/getting-started/introduction/"]) +def test_editorial_header_stays_in_viewport(page: Page, width: int, route: str): + """Centered page containers must not offset or clip the fixed navigation.""" + page.set_viewport_size({"width": width, "height": 1000}) + response = page.goto(f"{PREVIEW_URL}{route}", wait_until="networkidle") + assert response.status == 200 + header = page.locator("header") + expect(header).to_be_visible() + bounds = header.bounding_box() + assert bounds["x"] == 0 + assert bounds["width"] == width + assert page.evaluate("document.documentElement.scrollWidth <= innerWidth") + expect(page.get_by_role("heading", level=1).first).to_be_visible() + + +def test_editorial_banner_dismissal_search_and_navigation(page: Page): + """Dismissal reclaims header space; keyboard search and docs links still work.""" + page.set_viewport_size({"width": 1440, "height": 1000}) + page.goto(f"{PREVIEW_URL}/docs/", wait_until="networkidle") + page.get_by_role("button", name="Close banner").click() + expect(page.locator("[data-docs-announcement]")).to_have_count(0) + assert page.locator("header").bounding_box()["y"] == 0 + page.get_by_role("button", name="Search", exact=False).first.click() + expect(page.get_by_role("dialog").first).to_be_visible() + page.keyboard.press("Escape") + expect(page.get_by_role("dialog")).to_have_count(0) + hero = page.locator(".docs-hero") + hero.get_by_role("link", name="Build with AI", exact=True).click() + expect(page).to_have_url(f"{PREVIEW_URL}/docs/ai/") + page.go_back(wait_until="networkidle") + hero.get_by_role("link", name="Explore Framework", exact=True).click() + expect(page).to_have_url(f"{PREVIEW_URL}/docs/getting-started/introduction/") + expect(page.locator("header")).to_be_visible() + assert page.locator("header").bounding_box()["y"] == 0 + + +@pytest.mark.parametrize("width", [375, 1440]) +def test_docs_logo_returns_to_overview(page: Page, width: int): + """The shared logo returns readers to the docs overview from an article.""" + page.set_viewport_size({"width": width, "height": 1000}) + page.goto( + f"{PREVIEW_URL}/docs/ai/overview/what-is-reflex-build/", + wait_until="networkidle", + ) + logo = page.locator("header a").filter(has=page.locator('img[alt="Docs Logo"]')) + expect(logo).to_have_attribute("href", "/docs/") + logo.focus() + page.keyboard.press("Enter") + expect(page).to_have_url(f"{PREVIEW_URL}/docs/") + expect(page.locator(".docs-hero")).to_be_visible() + + +def test_header_selection_matches_the_docs_route(page: Page): + """The docs base path selects Overview and client navigation updates selection.""" + page.set_viewport_size({"width": 1440, "height": 1000}) + page.goto(f"{PREVIEW_URL}/docs/", wait_until="networkidle") + header = page.locator("header") + overview = header.get_by_role("link", name="Overview", exact=True) + expect(overview).to_have_css("font-weight", "450") + expect(overview.locator("..")).to_have_css("box-shadow", "none") + expect(header.locator('[aria-current="page"]')).to_have_text("Overview") + for label, path in [ + ("Framework", "/docs/getting-started/introduction/"), + ("Build with AI", "/docs/ai/"), + ("Cloud", "/docs/hosting/deploy-quick-start/"), + ("Overview", "/docs/"), + ]: + header.get_by_role("link", name=label, exact=True).click() + expect(page).to_have_url(f"{PREVIEW_URL}{path}") + expect(header.locator('[aria-current="page"]')).to_have_text(label) + + +@pytest.mark.parametrize("width", [375, 1024, 1440]) +def test_navbar_search_position_and_shortcuts(page: Page, width: int): + """Search precedes GitHub and advertises the keyboard shortcut on desktop.""" + page.set_viewport_size({"width": width, "height": 1000}) + page.goto(f"{PREVIEW_URL}/docs/enterprise/components/", wait_until="networkidle") + search = page.get_by_role("button", name="Search Reflex", exact=True) + expect(search).to_be_visible() + shortcut = search.locator("kbd") + if width >= 1024: + expect(shortcut).to_be_visible() + expect(shortcut).to_contain_text("K") + else: + expect(shortcut).not_to_be_visible() + if width >= 1280: + github = page.get_by_role("link", name="View Reflex on GitHub", exact=False) + assert ( + search.bounding_box()["x"] + search.bounding_box()["width"] + < github.bounding_box()["x"] + ) + for key in ("Meta+k", "Control+k"): + page.keyboard.press(key) + expect(page.get_by_role("dialog")).to_be_visible() + page.keyboard.press("Escape") + expect(page.get_by_role("dialog")).to_have_count(0) + search.click() + expect(page.get_by_role("dialog")).to_be_visible() + assert page.evaluate("document.documentElement.scrollWidth <= innerWidth") + + +@pytest.mark.parametrize("width", [375, 1440]) +def test_ai_overview_offers_build_and_agent_paths(page: Page, width: int): + """The AI choice page keeps both workflows readable and links to their guides.""" + page.set_viewport_size({"width": width, "height": 1000}) + for label, destination in ( + ("Use Reflex Build", "/docs/ai/overview/what-is-reflex-build/"), + ("Bring your own agent", "/docs/ai/integrations/agent-toolkit/"), + ): + response = page.goto(f"{PREVIEW_URL}/docs/ai/", wait_until="networkidle") + assert response.status == 200 + expect( + page.get_by_role("heading", level=1, name="Build with AI", exact=True) + ).to_be_visible() + assert page.evaluate("document.documentElement.scrollWidth <= innerWidth") + if width < 1024: + page.get_by_role("button", name="Open documentation navigation").click() + navigation = page.get_by_role("dialog") + else: + navigation = page.locator(".docs-left-sidebar") + expect(navigation).to_be_visible() + expect( + navigation.get_by_role("link", name="Navigate to Overview", exact=True) + ).to_have_attribute("aria-current", "true") + expect( + navigation.get_by_role("link", name="Navigate to Reflex Build", exact=True) + ).to_have_attribute("href", "/docs/ai/overview/what-is-reflex-build/") + expect( + navigation.get_by_role("link", name="Navigate to Agent Toolkit", exact=True) + ).to_have_attribute("href", "/docs/ai/integrations/agent-toolkit/") + if width < 1024: + page.keyboard.press("Escape") + expect(navigation).not_to_be_visible() + choice = page.get_by_role("link", name=label, exact=True) + expect(choice).to_have_attribute("href", destination) + choice.focus() + page.keyboard.press("Enter") + expect(page).to_have_url(f"{PREVIEW_URL}{destination}") + expect(page.get_by_role("heading", level=1)).to_be_visible() + + +def test_editorial_dark_mode_keeps_readable_surfaces(browser): + """System dark mode switches both neutral text and surface colors.""" + context = browser.new_context(color_scheme="dark") + page = context.new_page() + try: + page.goto(f"{PREVIEW_URL}/docs/", wait_until="networkidle") + expect(page.locator("[data-docs-announcement]")).to_have_css( + "color", "rgb(255, 255, 255)" + ) + expect(page.locator("header")).to_have_css( + "background-color", "rgb(24, 24, 24)" + ) + expect(page.get_by_role("heading", level=1)).to_have_css( + "color", "rgb(245, 245, 245)" + ) + finally: + context.close() + + +def test_mobile_menu_meets_the_header(page: Page): + """The open drawer must follow the rendered header without a content gap.""" + page.set_viewport_size({"width": 375, "height": 1000}) + page.goto(f"{PREVIEW_URL}/docs/", wait_until="networkidle") + page.get_by_role("button", name="Open sidebar").click() + expect(page.get_by_role("dialog")).to_be_visible() + page.wait_for_function( + "Math.abs(document.querySelector('[role=dialog]').getBoundingClientRect().y - document.querySelector('header').getBoundingClientRect().bottom) < 1", + timeout=5000, + ) + page.keyboard.press("Escape") + expect(page.get_by_role("dialog")).to_have_count(0) + + +@pytest.mark.parametrize("width", [375, 1440]) +def test_editorial_footer_preserves_links_and_email_validation(page: Page, width: int): + """Footer links keep their destinations and empty signup stays in the browser.""" + page.set_viewport_size({"width": width, "height": 1000}) + page.goto(f"{PREVIEW_URL}/docs/", wait_until="networkidle") + footer = page.locator(".docs-footer") + expect(footer.get_by_role("link", name="Blog", exact=True)).to_have_attribute( + "href", "/blog/" + ) + expect( + footer.get_by_role("link", name="Documentation", exact=True) + ).to_have_attribute("href", "/docs/") + email = footer.get_by_label("Email address", exact=True) + expect(email).to_have_attribute("name", "input_email") + footer.get_by_role("button", name="Subscribe", exact=True).click() + assert email.evaluate("input => input.validity.valueMissing") + expect(email).to_be_focused() + expect(footer.get_by_role("status")).to_have_count(0) + assert page.evaluate("document.documentElement.scrollWidth <= innerWidth") + + footer.get_by_role("button", name="Toggle dark color mode", exact=True).click() + expect( + footer.get_by_role("button", name="Toggle dark color mode") + ).to_have_attribute("aria-pressed", "true") + expect(page.locator("header")).to_have_css("background-color", "rgb(24, 24, 24)") + expect(page.locator(".docs-cta-art [fill='var(--background)']").first).to_have_css( + "fill", "rgb(24, 24, 24)" + ) + + +def test_editorial_closing_actions_keep_native_navigation_and_dialog(page: Page): + """The closing CTA supports its link and booking dialog without nested buttons.""" + page.goto(f"{PREVIEW_URL}/docs/", wait_until="networkidle") + cta = page.locator(".docs-cta") + trial = cta.get_by_role("link", name="Try for free", exact=True) + expect(trial).to_have_attribute("href", "https://build.reflex.dev/") + expect(trial.locator("button")).to_have_count(0) + cta.get_by_role("button", name="Book a Demo", exact=True).click() + expect(page.get_by_role("dialog", include_hidden=True)).to_have_count(1) + expect(page.get_by_role("dialog")).to_be_visible() + page.keyboard.press("Escape") + expect(page.get_by_role("dialog")).to_have_count(0) + + +@pytest.mark.parametrize("width", [375, 1440]) +def test_article_footer_feedback_and_theme_controls(page: Page, width: int): + """The article footer keeps its links, feedback, and mode controls usable.""" + page.set_viewport_size({"width": width, "height": 1000}) + page.goto( + f"{PREVIEW_URL}/docs/getting-started/introduction/", wait_until="networkidle" + ) + footer = page.locator(".docs-page-footer") + expect(footer.get_by_role("link", name="Blog", exact=True)).to_have_attribute( + "href", "/blog/" + ) + expect(footer.get_by_role("link", name="Edit this page")).to_have_attribute( + "href", + "https://github.com/reflex-dev/reflex/edit/main/docs/getting_started/introduction.md", + ) + expect(footer.get_by_role("link", name="Raise an issue")).to_be_visible() + footer.get_by_role("button", name="Yes", exact=True).click() + expect(page.get_by_placeholder("Write a commentโ€ฆ")).to_be_visible() + expect(footer.get_by_role("button", name="Yes", exact=True)).to_have_attribute( + "aria-pressed", "true" + ) + page.keyboard.press("Escape") + expect(page.get_by_placeholder("Write a commentโ€ฆ")).to_have_count(0) + dark = footer.get_by_role("button", name="Toggle dark color mode") + dark.click() + expect(dark).to_have_attribute("aria-pressed", "true") + expect(dark).to_have_css("background-color", "rgb(32, 32, 32)") + expect(footer.get_by_role("link", name="Introduction", exact=True)).to_have_css( + "color", "rgb(245, 245, 245)" + ) + footer.get_by_role("button", name="Toggle light color mode").click() + expect(dark).to_have_attribute("aria-pressed", "false") + assert page.evaluate("document.documentElement.scrollWidth <= innerWidth") + + +@pytest.mark.parametrize("width", [375, 1440]) +def test_sidebar_groups_and_current_page_navigation(page: Page, width: int): + """Styled sidebar rows preserve disclosure, keyboard access, and selection.""" + page.set_viewport_size({"width": width, "height": 1000}) + page.goto( + f"{PREVIEW_URL}/docs/getting-started/installation/", wait_until="networkidle" + ) + if width < 1024: + page.get_by_role("button", name="Open documentation navigation").click() + sidebar = page.get_by_role("dialog") + else: + sidebar = page.locator(".docs-left-sidebar") + + current = sidebar.locator('.docs-sidebar-leaf[aria-current="page"]') + expect(current).to_have_text("Installation") + group = sidebar.locator("summary").filter(has_text="Getting Started") + group.click() + expect(current).not_to_be_visible() + group.press("Enter") + expect(current).to_be_visible() + group.press("Tab") + expect(current).to_be_focused() + sidebar.get_by_role("link", name="Basics", exact=True).click() + expect(page).to_have_url(f"{PREVIEW_URL}/docs/getting-started/basics/") + + if width < 1024: + expect(page.get_by_role("dialog")).to_have_count(0) + page.get_by_role("button", name="Open documentation navigation").click() + expect(sidebar.locator('.docs-sidebar-leaf[aria-current="page"]')).to_have_text( + "Basics" + ) + assert page.evaluate("document.documentElement.scrollWidth <= innerWidth") + + +@pytest.mark.parametrize( + "route", + [ + "/docs/library/", + "/docs/api-reference/app/", + "/docs/getting-started/installation/", + ], +) +def test_nested_article_headings_share_the_editorial_type(page: Page, route: str): + """Cards, API content, and nested tab panels use the article heading scale.""" + page.goto(f"{PREVIEW_URL}{route}", wait_until="networkidle") + headings = page.locator( + ".docs-prose :is(h1,h2,h3,h4):not([data-docs-example] *):visible" + ) + assert headings.count() > 1 + for heading in headings.all(): + expect(heading).to_have_css("font-weight", "450") + + +def test_article_theme_preserves_live_example_styles_and_events(page: Page): + """Editorial type must not overwrite the rendered counter's own heading style.""" + page.goto( + f"{PREVIEW_URL}/docs/getting-started/introduction/", wait_until="networkidle" + ) + demo = ( + page + .locator("[data-docs-example]") + .filter(has=page.get_by_role("button", name="Increment", exact=True)) + .first + ) + value = demo.get_by_role("heading") + expect(value).to_have_css("font-weight", "700") + for label, accent in [("Increment", "grass"), ("Decrement", "ruby")]: + expect(demo.get_by_role("button", name=label, exact=True)).to_have_attribute( + "data-accent-color", accent + ) + before = int(value.inner_text()) + demo.get_by_role("button", name="Increment", exact=True).click() + expect(value).to_have_text(str(before + 1)) + + +def test_mobile_header_links_and_booking_form(page: Page): + """Mobile docs destinations match desktop; opening booking closes the drawer.""" + page.set_viewport_size({"width": 375, "height": 667}) + page.goto(f"{PREVIEW_URL}/docs/", wait_until="networkidle") + page.get_by_role("button", name="Open sidebar").click() + menu = page.get_by_role("navigation", name="Documentation navigation") + expect(menu).to_be_visible() + for label, href in { + "Overview": "/docs/", + "Build with AI": "/docs/ai/", + "Framework": "/docs/getting-started/introduction/", + "Cloud": "/docs/hosting/deploy-quick-start/", + "XY": "/docs/xy/", + }.items(): + expect(menu.get_by_role("link", name=label, exact=True)).to_have_attribute( + "href", href + ) + page.get_by_role("dialog").get_by_role("button", name="Book a Demo").click() + booking = page.locator('[data-slot="dialog-popup"]:has(#docs-booking_user_email)') + expect(booking).to_be_visible() + expect(page.get_by_role("dialog", include_hidden=True)).to_have_count(1) + expect(booking.get_by_role("heading", name="Book a Demo")).to_have_css( + "font-weight", "450" + ) + email = booking.locator("#docs-booking_user_email") + email.fill("preview@example.com") + expect(email).to_have_value("preview@example.com") + page.wait_for_function( + """() => { + const box = document.querySelector('[data-slot="dialog-popup"]').getBoundingClientRect(); + return box.top >= 0 && box.bottom <= innerHeight && box.right <= innerWidth; + }""" + ) + page.keyboard.press("Escape") + expect(page.get_by_role("dialog")).to_have_count(0) + + +def test_gallery_sort_menu_supports_keyboard_selection(page: Page): + """The gallery sort trigger remains usable after adopting shared controls.""" + page.goto(f"{PREVIEW_URL}/docs/custom-components/", wait_until="networkidle") + trigger = page.get_by_role("button", name="Sort", exact=True) + trigger.focus() + trigger.press("Enter") + recent = page.get_by_role("menuitem", name="Recent") + expect(recent).to_be_visible() + recent.focus() + recent.press("Enter") + expect(page.get_by_role("button", name="Sort: Recent", exact=True)).to_be_visible() + expect(page.get_by_role("menuitem", name="Recent")).to_have_count(0) + + +def test_missing_page_uses_docs_shell_and_recovery_link(page: Page): + """A missing route keeps docs navigation and a working route back to overview.""" + page.goto(f"{PREVIEW_URL}/docs/404/", wait_until="networkidle") + expect(page.get_by_role("heading", name="Page not found")).to_be_visible() + expect(page.locator(".docs-navbar")).to_be_visible() + expect(page.locator(".docs-footer")).to_be_visible() + page.get_by_role("link", name="Back to docs", exact=True).click() + expect(page).to_have_url(f"{PREVIEW_URL}/docs/") + + +def test_integration_catalog_hydrates_and_filters(page: Page): + """Integration cards keep valid link markup and accessible category filters.""" + errors = [] + page.on("pageerror", lambda error: errors.append(str(error))) + page.goto(f"{PREVIEW_URL}/docs/ai/integrations/overview/", wait_until="networkidle") + expect(page.get_by_role("heading", name="Integrations", level=1)).to_be_visible() + assert not errors + cards = page.locator(".docs-integration-card:visible") + all_count = cards.count() + page.get_by_role("button", name="Authentication", exact=True).click() + expect(page.get_by_role("button", name="Authentication")).to_have_attribute( + "aria-pressed", "true" + ) + assert 0 < cards.count() < all_count + expect(cards.locator("a, button")).to_have_count(0) + page.get_by_role("button", name="Request integration", exact=True).click() + expect(page.get_by_placeholder("Requested integration...")).to_be_visible() + page.keyboard.press("Escape") + expect(page.get_by_role("dialog")).to_have_count(0) + + +def test_low_level_form_example_has_valid_inline_result_markup(page: Page): + """Inline submitted values must not cause the browser to repair nested paragraphs.""" + errors = [] + page.on("pageerror", lambda error: errors.append(str(error))) + page.goto(f"{PREVIEW_URL}/docs/library/forms/form/low/", wait_until="networkidle") + expect(page.get_by_text("Username submitted:", exact=False)).to_be_visible() + assert not errors + + +@pytest.mark.parametrize("width", [375, 1440]) +def test_framework_tabs_switch_diagrams_and_keep_docs_links(page: Page, width: int): + """Every framework tab reveals one illustration and its matching docs link.""" + page.set_viewport_size({"width": width, "height": 1000}) + page.goto(f"{PREVIEW_URL}/docs/", wait_until="networkidle") + section = page.get_by_role("region", name="Framework", exact=True) + for title, link, href in [ + ( + "How It Works", + "Read the introduction", + "/docs/getting-started/introduction/", + ), + ("Components", "Browse all components", "/docs/library/"), + ("Auth", "Explore authentication", "/docs/enterprise/auth/overview/"), + ("Database", "Explore databases", "/docs/database/overview/"), + ]: + tab = section.get_by_role("tab", name=title, exact=True) + tab.click() + expect(tab).to_have_attribute("aria-selected", "true") + panel = section.get_by_role("tabpanel") + expect(panel).to_have_count(1) + expect(panel.locator("svg").first).to_be_visible() + expect(panel.get_by_role("link", name=link)).to_have_attribute("href", href) + assert page.evaluate("document.documentElement.scrollWidth <= innerWidth") + section.get_by_role("tab", name="How It Works", exact=True).focus() + page.keyboard.press("ArrowDown") + expect(section.get_by_role("tab", name="Components", exact=True)).to_have_attribute( + "aria-selected", "true" + ) + page.keyboard.press("Tab") + expect( + section.get_by_role("tabpanel", name="Components", exact=True) + ).to_be_focused() + page.keyboard.press("Tab") + browse = section.get_by_role("link", name="Browse all components") + expect(browse).to_be_focused() + page.keyboard.press("Enter") + expect(page).to_have_url(f"{PREVIEW_URL}/docs/library/") + + +@pytest.mark.parametrize("width", [375, 1440]) +@pytest.mark.parametrize("color_scheme", ["light", "dark"]) +def test_framework_counter_is_live_and_windows_do_not_overlap( + page: Page, width: int, color_scheme: Literal["light", "dark"] +): + """The diagram exposes a real counter without covering its Python example.""" + page.set_viewport_size({"width": width, "height": 1000}) + page.emulate_media(color_scheme=color_scheme) + page.goto(f"{PREVIEW_URL}/docs/", wait_until="networkidle") + section = page.get_by_role("region", name="Framework", exact=True) + panel = section.get_by_role("tabpanel", name="How It Works", exact=True) + button = panel.get_by_role("button", name="Increment", exact=False) + expect(button).to_be_visible() + count = panel.get_by_role("status", name="Counter value") + expect(count).to_have_text("0") + button.click() + expect(count).to_have_text("1") + button.click() + expect(count).to_have_text("2") + button.focus() + page.keyboard.press("Space") + expect(count).to_have_text("3") + source = panel.locator(".docs-framework-live-code").bounding_box() + app = panel.locator(".docs-framework-live-app").bounding_box() + assert source["y"] + source["height"] < app["y"] + assert panel.locator("pre").evaluate("el => el.scrollWidth <= el.clientWidth") + assert page.evaluate("document.documentElement.scrollWidth <= innerWidth") + section.get_by_role("tab", name="Components", exact=True).click() + section.get_by_role("tab", name="How It Works", exact=True).click() + expect(count).to_have_text("3") + + +@pytest.mark.parametrize("width", [320, 375, 1440]) +@pytest.mark.parametrize("color_scheme", ["light", "dark"]) +def test_cloud_diagram_preserves_guides_without_overlapping( + page: Page, width: int, color_scheme: Literal["light", "dark"] +): + """The hosting diagram keeps every guide accessible at both layout sizes.""" + page.set_viewport_size({"width": width, "height": 1000}) + page.emulate_media(color_scheme=color_scheme) + page.goto(f"{PREVIEW_URL}/docs/", wait_until="networkidle") + section = page.get_by_role("region", name="Cloud", exact=True) + expect(section.get_by_role("heading", name="Your application")).to_be_visible() + assert section.locator(".docs-cloud-hub").evaluate( + "el => [...el.querySelectorAll('*')].every(node => node.scrollWidth <= node.clientWidth + 1)" + ) + for title, href in [ + ("Deployment", "/docs/hosting/deploy-quick-start/"), + ("Secret Management", "/docs/hosting/secrets-environment-vars/"), + ("Observability", "/docs/hosting/logs/"), + ("Custom Headers and Advanced Options", "/docs/hosting/deploy-quick-start/"), + ]: + link = section.get_by_role("link", name=title, exact=True) + expect(link).to_have_attribute("href", href) + link.focus() + expect(link).to_be_focused() + bounds = link.bounding_box() + hub = section.locator(".docs-cloud-hub").bounding_box() + assert ( + bounds["x"] + bounds["width"] <= hub["x"] + or bounds["x"] >= hub["x"] + hub["width"] + or bounds["y"] + bounds["height"] <= hub["y"] + or bounds["y"] >= hub["y"] + hub["height"] + ) + assert page.evaluate("document.documentElement.scrollWidth <= innerWidth") + section.get_by_role("link", name="Deployment", exact=True).click() + expect(page).to_have_url(f"{PREVIEW_URL}/docs/hosting/deploy-quick-start/") + + +def test_agent_file_links_respect_docs_mount(page: Page): + """The router adds the docs prefix exactly once to agent-file links.""" + page.goto(f"{PREVIEW_URL}/docs/getting-started/introduction/") + expect(page.locator('a[href="/docs/llms.txt"]').first).to_have_count(1) + page.get_by_role("button", name="Copy page options").click() + link = page.get_by_role("link", name="llms-full.txt", exact=False) + expect(link).to_have_attribute("href", "/docs/llms-full.txt") + response = page.request.get(f"{PREVIEW_URL}/docs/llms-full.txt") + assert response.status == 200 + assert "# Reflex Documentation" in response.text() + + +def test_wrapping_walkthrough_color_event_updates_state(page: Page): + """The documented React wrapper sends color changes to Python state.""" + page.goto( + f"{PREVIEW_URL}/docs/wrapping-react/step-by-step/", wait_until="networkidle" + ) + label = page.locator("p").filter(has_text="Selected color:").first + expect(label).to_contain_text("#6750a4") + picker = page.locator(".react-colorful__saturation") + picker.click(position={"x": 20, "y": 20}) + expect(label).not_to_contain_text("#6750a4") + + +def test_markdown_alternate_updates_with_client_navigation(page: Page): + """Discovery points to emitted files at the root and after router navigation.""" + page.goto(f"{PREVIEW_URL}/docs/", wait_until="networkidle") + alternate = page.locator('head link[rel="alternate"][type="text/markdown"]') + expect(alternate).to_have_attribute("href", "https://reflex.dev/docs/index.md") + page.locator("header").get_by_role("link", name="Build with AI", exact=True).click() + expect(page).to_have_url(f"{PREVIEW_URL}/docs/ai/") + expect(alternate).to_have_attribute("href", "https://reflex.dev/docs/ai.md") + + +def test_reference_table_keeps_columns_readable_on_mobile(page: Page): + """Narrow screens scroll the reference table instead of overlapping columns.""" + page.set_viewport_size({"width": 390, "height": 950}) + page.goto(f"{PREVIEW_URL}/docs/library/html/layout/", wait_until="networkidle") + table = page.locator(".docs-table").first + table.scroll_into_view_if_needed() + assert table.locator("table").bounding_box()["width"] >= 640 + assert table.evaluate("element => element.scrollWidth > element.clientWidth") + assert not page.evaluate("document.documentElement.scrollWidth > innerWidth") + name = table.locator("td code").filter(has_text="content_editable").first + assert name.evaluate( + "element => element.getBoundingClientRect().right <= element.closest('td').getBoundingClientRect().right" + ) + table.evaluate("element => { element.scrollLeft = 100; }") + assert table.evaluate("element => element.scrollLeft") == 100 diff --git a/docs/app/tests/test_published_seo.py b/docs/app/tests/test_published_seo.py new file mode 100644 index 00000000000..fd50d1a7e0a --- /dev/null +++ b/docs/app/tests/test_published_seo.py @@ -0,0 +1,111 @@ +"""Validate URL metadata in the actual production build, not only its inputs.""" + +import runpy +from html.parser import HTMLParser +from pathlib import Path +from urllib.parse import urljoin, urlsplit +from xml.etree import ElementTree + +import pytest +from reflex_base.environment import environment + +APP = Path(__file__).parents[1] +WEB = APP / environment.REFLEX_WEB_WORKDIR.get() +SITEMAP = WEB / "public/sitemap.xml" + + +class PageURLs(HTMLParser): + """Collect canonical and social URLs from generated HTML.""" + + def __init__(self, html: str): + super().__init__() + self.canonical = [] + self.social = [] + self.markdown = [] + self.links = [] + self.feed(html) + + def handle_starttag(self, tag, attrs): + """Collect URL-bearing metadata tags.""" + attrs = dict(attrs) + if tag == "link" and attrs.get("rel") == "canonical": + self.canonical.append(attrs.get("href")) + if ( + tag == "link" + and attrs.get("rel") == "alternate" + and attrs.get("type") == "text/markdown" + ): + self.markdown.append(attrs.get("href")) + if tag == "a" and attrs.get("href"): + self.links.append(attrs["href"]) + if tag == "meta" and ( + attrs.get("property") in {"og:url", "twitter:url"} + or attrs.get("name") == "twitter:url" + ): + self.social.append(attrs.get("content")) + + +@pytest.mark.xfail( + not SITEMAP.is_file(), + reason="Build the docs before validating published SEO metadata.", + run=False, +) +def test_generated_sitemap_and_page_urls_share_public_origin(): + """Every sitemap location and generated canonical uses one public docs prefix.""" + config = runpy.run_path(str(APP / "rxconfig.py"))["config"] + base = config.deploy_url.rstrip("/") + config.frontend_path.rstrip("/") + urls = [ + element.text + for element in ElementTree.parse(SITEMAP).iter() + if element.tag.endswith("loc") + ] + assert urls + assert len(urls) == len(set(urls)) + canonical_paths = {urlsplit(url).path for url in urls} + redirected = set() + for url in urls: + assert url.startswith(base + "/"), url + assert url.endswith("/"), url + path = urlsplit(url).path + assert "/docs/docs/" not in path, url + page = WEB / "build/client" / path.lstrip("/") / "index.html" + assert page.is_file(), page + metadata = PageURLs(page.read_text()) + assert metadata.canonical == [url], (url, metadata.canonical) + assert len(metadata.social) == 2, (url, metadata.social) + assert all(value == url for value in metadata.social), (url, metadata.social) + markdown_url = ( + url + "index.md" if url == base + "/" else url.rstrip("/") + ".md" + ) + assert metadata.markdown == [markdown_url], (url, metadata.markdown) + markdown = WEB / "build/client" / urlsplit(markdown_url).path.lstrip("/") + assert markdown.is_file(), markdown + assert len(markdown.read_text().split()) > 20, markdown + for href in metadata.links: + target = urlsplit(urljoin(url, href)) + if ( + target.netloc == urlsplit(base).netloc + and target.path + "/" in canonical_paths + ): + redirected.add((path, href)) + assert not redirected, sorted(redirected) + + +@pytest.mark.xfail( + not SITEMAP.is_file(), + reason="Build the docs before checking reference payloads.", + run=False, +) +@pytest.mark.parametrize( + ("route", "budget"), + [ + ("library/html/layout", 350_000), + ("library/html/text", 300_000), + ("library/html/media", 350_000), + ("library/tables-and-data-grids/table", 500_000), + ], +) +def test_reference_html_stays_within_payload_budget(route, budget): + """Catch accidental reintroduction of repeated inherited API tables.""" + page = WEB / "build/client/docs" / route / "index.html" + assert page.stat().st_size < budget, (route, page.stat().st_size, budget) diff --git a/docs/app/tests/test_rendered_markdown.py b/docs/app/tests/test_rendered_markdown.py new file mode 100644 index 00000000000..16ac0532b2d --- /dev/null +++ b/docs/app/tests/test_rendered_markdown.py @@ -0,0 +1,97 @@ +"""Regression coverage for canonical-page Markdown exports.""" + +from types import SimpleNamespace + +from agent_files import AgentFilesPlugin + + +def test_post_build_exports_missing_pages_and_preserves_authored_markdown( + tmp_path, monkeypatch +): + """Catalog links and answers survive export without replacing authored code.""" + monkeypatch.setattr( + "agent_files._plugin.get_config", + lambda: SimpleNamespace(frontend_path="/docs", deploy_url="https://reflex.dev"), + ) + monkeypatch.setattr( + "reflex_site_shared.utils.url.get_config", + lambda: SimpleNamespace(frontend_path="/docs", deploy_url="https://reflex.dev"), + ) + root = tmp_path / "docs" + for route, body in { + "": '<h1>Reflex Docs</h1><p>Choose a workflow.</p><a href="/docs/ai/">Build with AI</a>', + "ai/": '<h1>Build with AI</h1><h2>Use Reflex Build</h2><p>Recommended workflow.</p><a href="/docs/ai/guide/">Read the guide</a>', + "ai/guide/": "<h1>Guide</h1><p>Rendered example.</p>", + }.items(): + page = root / route / "index.html" + page.parent.mkdir(parents=True, exist_ok=True) + page.write_text( + f"<html><head><title>Docs
{body}
" + ) + authored = '# Guide\n\n```python\nprint("keep original code")\n```\n' + (root / "ai/guide.md").write_text(authored) + (root / "ai.md").write_text( + "> For AI agents: old directive\n\n```python exec\nfrom example import page\n```\n\n```python eval\npage()\n```\n" + ) + (tmp_path / "sitemap.xml").write_text( + "" + + "".join( + f"https://reflex.dev/docs/{route}" + for route in ("", "ai/", "ai/guide/") + ) + + "" + ) + plugin = AgentFilesPlugin() + plugin.post_build(static_dir=tmp_path) + assert (root / "index.md").is_file() + generated = (root / "ai.md").read_text() + assert "# Build with AI" in generated + assert "Recommended workflow." in generated + assert "https://reflex.dev/docs/ai/guide/" in generated + assert "Global navigation" not in generated and "Copyright" not in generated + assert "python eval" not in generated and "from example" not in generated + assert (root / "ai/guide.md").read_text() == authored + assert "https://reflex.dev/docs/index.md" in (root / "llms.txt").read_text() + assert "https://reflex.dev/docs/ai.md" in (root / "llms.txt").read_text() + combined = (root / "llms-full.txt").read_text() + assert ( + "Recommended workflow." in combined + and 'print("keep original code")' in combined + ) + plugin.post_build(static_dir=tmp_path) + assert (root / "llms-full.txt").read_text() == combined + + +def test_rendered_markdown_retains_code_tables_and_links(): + """Fallback exports preserve the answer and handle Markdown delimiters.""" + from agent_files._rendered import rendered_content + + title, content = rendered_content( + '

Reference

def example():\n    return "```"
' + "" + "
PropType
colorstr | None
" + '

Guide

' + '
', + "https://reflex.dev/docs/reference/", + ) + assert title == "Reference" + assert content.startswith("# Reference\n") + assert '````\ndef example():\n return "```"\n````' in content + assert "| `color` | str \\| None |" in content + assert "https://reflex.dev/docs/guide/?a=1&b=2#example" in content + assert "Copy" not in content and "Decoration" not in content + + +def test_linked_cards_keep_their_content_and_destination(): + """Heading wrappers and empty overlay links must not discard navigation.""" + from agent_files._rendered import rendered_content + + _, content = rendered_content( + '

Build with AI

' + "

Use Build

Recommended workflow.

" + '
', + "https://reflex.dev/docs/ai/", + ) + assert "## Use Build" in content and "Recommended workflow." in content + assert "[Use Build](https://reflex.dev/docs/build/)" in content + assert "[Agent Toolkit](https://reflex.dev/docs/agents/)" in content diff --git a/docs/app/tests/test_routes.py b/docs/app/tests/test_routes.py index 2fbc0eac99b..f26c045723b 100644 --- a/docs/app/tests/test_routes.py +++ b/docs/app/tests/test_routes.py @@ -352,3 +352,14 @@ def test_docs_titles_and_descriptions_are_unique(routes_fixture): if value and count > 1 } assert duplicates == {}, (attr, duplicates) + + +def test_routes_have_specific_descriptions(routes_fixture): + """Published pages must not fall back to the generic documentation snippet.""" + generic = ( + "documentation, examples, and reference for building Python web applications" + ) + failures = [ + route.path for route in routes_fixture if generic in (route.description or "") + ] + assert not failures, failures diff --git a/docs/app/tests/test_site_quality.py b/docs/app/tests/test_site_quality.py index dd3f232c29b..098bb3c5f3c 100644 --- a/docs/app/tests/test_site_quality.py +++ b/docs/app/tests/test_site_quality.py @@ -22,7 +22,9 @@ def test_landing_card_link_has_an_accessible_name(): def test_ai_card_focus_outline_is_inside_the_clipped_card(): """The overlay's focus indicator must fit within the rounded clipping box.""" - rendered = str(card("AI Builder", "Build an app", "Preview", "/ai/")) + rendered = str( + card("AI Builder", "Build an app", rx.text("Preview"), "/ai/", "blue") + ) assert "focus-visible:-outline-offset-4" in rendered assert "focus-visible:outline-offset-4" not in rendered @@ -53,3 +55,75 @@ def test_deferred_demo_preserves_code_and_defers_only_the_preview(): assert "DeferredDemo" in rendered assert "Preview" in rendered assert "rx.text" in rendered + + +def test_treemap_previews_are_bundled(): + """Both theme previews exist locally instead of pointing at missing CDN files.""" + from pathlib import Path + + from reflex_docs.pages.library_previews import component_card + + rendered = str( + component_card("treemap", "/library/graphing/charts/treemap/", "charts") + ) + for mode in ("light", "dark"): + path = f"components_previews/charts/{mode}/treemap.svg" + assert (Path(__file__).parents[1] / "assets" / path).is_file() + assert path in rendered + assert "web.reflex-assets.dev/components_previews/charts" not in rendered + + +def test_component_destinations_use_canonical_trailing_slash(): + """Component links avoid a redirect before loading the documentation.""" + from reflex_docs.templates.docpage.sidebar.sidebar_items.component_lib import ( + get_component_link, + ) + + assert ( + get_component_link("data-display", ["avatar"]) + == "/library/data-display/avatar/" + ) + + +def test_preview_cards_reserve_space_before_images_load(): + """Lazy preview images must not shift the catalog when they arrive.""" + from reflex_docs.pages.library_previews import component_card + + rendered = str( + component_card("avatar", "/library/data-display/avatar/", "data-display") + ) + assert "aspect-[320/232]" in rendered + + +def test_multi_component_reference_shares_only_identical_html_props(): + """Common HTML props appear once, while element-specific props stay local.""" + from reflex_docs.pages.docs.component import component_docs, shared_html_props + + components = [type(rx.el.div()), type(rx.el.input()), type(rx.el.portal())] + shared = shared_html_props(components) + assert "title" in {prop.name for prop in shared} + assert "value" not in {prop.name for prop in shared} + assert shared_html_props(components[:1]) == () + rendered = str(component_docs((components[1], "rx.el.input"), {}, shared)) + assert "#shared-html-props" in rendered + assert '"value"' in rendered + assert '"access_key"' not in rendered + + class OverrideTitle(type(rx.el.div())): + title: rx.Var[int] + + override = str(component_docs((OverrideTitle, "OverrideTitle"), {}, shared)) + assert '"title"' in override + assert '"access_key"' not in override + + +def test_state_catalog_offers_real_guides_instead_of_an_empty_grid(): + """The state catalog provides useful routes and can be found in the library.""" + from reflex_docs.pages.docs.library import library + from reflex_docs.pages.library_previews import library_previews + + route = next(route for route in library_previews if route.path == "/library/state/") + rendered = str(route.component()) + assert 'href:"/docs/state/overview/"' in rendered + assert 'href:"/docs/events/events-overview/"' in rendered + assert 'to:"/library/state/"' in str(library.component()) diff --git a/docs/assets/upload_and_download_files.md b/docs/assets/upload_and_download_files.md index 19b3559ce33..5ffccaaf2e5 100644 --- a/docs/assets/upload_and_download_files.md +++ b/docs/assets/upload_and_download_files.md @@ -174,7 +174,7 @@ def download_random_data_button(): The `data` arg accepts `str` or `bytes` data, a `data:` URI, `PIL.Image`, or any state Var. If the Var is not already a string, it will be converted to a string using `JSON.stringify`. This allows complex state structures to be offered as JSON downloads. -Reference page for `rx.download` [here](/docs/api-reference/special-events#rx.download). +Reference page for `rx.download` [here](/docs/api-reference/special-events/#rx.download). ## Upload @@ -192,4 +192,4 @@ def index(): ) ``` -For detailed information, see the reference page of the component [here](/docs/library/forms/upload). +For detailed information, see the reference page of the component [here](/docs/library/forms/upload/). diff --git a/docs/components/conditional_rendering.md b/docs/components/conditional_rendering.md index 16554735519..2431556c1c7 100644 --- a/docs/components/conditional_rendering.md +++ b/docs/components/conditional_rendering.md @@ -4,14 +4,14 @@ import reflex as rx # Conditional Rendering -Recall from the [basics](/docs/getting-started/basics) that we cannot use Python `if/else` statements when referencing state vars in Reflex. Instead, use the `rx.cond` component to conditionally render components or set props based on the value of a state var. +Recall from the [basics](/docs/getting-started/basics/) that we cannot use Python `if/else` statements when referencing state vars in Reflex. Instead, use the `rx.cond` component to conditionally render components or set props based on the value of a state var. ```md video https://youtube.com/embed/ITOZkzjtjUA?start=6040&end=6463 # Video: Conditional Rendering ``` ```md alert -# Check out the API reference for [cond docs](/docs/library/dynamic-rendering/cond). +# Check out the API reference for [cond docs](/docs/library/dynamic-rendering/cond/). ``` ```python eval @@ -66,11 +66,11 @@ def cond_prop(): ## Var Operations -You can use [var operations](/docs/vars/var-operations) with the `cond` component for more complex conditions. See the full [cond reference](/docs/library/dynamic-rendering/cond) for more details. +You can use [var operations](/docs/vars/var-operations/) with the `cond` component for more complex conditions. See the full [cond reference](/docs/library/dynamic-rendering/cond/) for more details. ## Multiple Conditional Statements -The [`rx.match`](/docs/library/dynamic-rendering/match) component in Reflex provides a powerful alternative to`rx.cond` for handling multiple conditional statements and structural pattern matching. This component allows you to handle multiple conditions and their associated components in a cleaner and more readable way compared to nested `rx.cond` structures. +The [`rx.match`](/docs/library/dynamic-rendering/match/) component in Reflex provides a powerful alternative to`rx.cond` for handling multiple conditional statements and structural pattern matching. This component allows you to handle multiple conditions and their associated components in a cleaner and more readable way compared to nested `rx.cond` structures. ```python demo exec from typing import List diff --git a/docs/components/props.md b/docs/components/props.md index e53330284b3..b3df4960713 100644 --- a/docs/components/props.md +++ b/docs/components/props.md @@ -19,7 +19,7 @@ rx.image( ) ``` -Check the docs for the component you are using to see what props are available and how they affect the component (see the `rx.image` [reference](/docs/library/media/image#api-reference) page for example). +Check the docs for the component you are using to see what props are available and how they affect the component (see the `rx.image` [reference](/docs/library/media/image/#api-reference) page for example). ## Common Props @@ -54,15 +54,15 @@ rx.button( ) ``` -See the [styling docs](/docs/styling/overview) to learn more about customizing the appearance of your app. +See the [styling docs](/docs/styling/overview/) to learn more about customizing the appearance of your app. ## Binding Props to State ```md alert warning -# Optional: Learn all about [State](/docs/state/overview) first. +# Optional: Learn all about [State](/docs/state/overview/) first. ``` -Reflex apps define [State](/docs/state/overview) classes that hold variables that can change over time. +Reflex apps define [State](/docs/state/overview/) classes that hold variables that can change over time. State may be modified in response to things like user input like clicking a button, or in response to events like loading a page. diff --git a/docs/components/rendering_iterables.md b/docs/components/rendering_iterables.md index bf9a3783674..9b28faf4ddb 100644 --- a/docs/components/rendering_iterables.md +++ b/docs/components/rendering_iterables.md @@ -4,9 +4,9 @@ import reflex as rx # Rendering Iterables -Recall again from the [basics](/docs/getting-started/basics) that we cannot use Python `for` loops when referencing state vars in Reflex. Instead, use the `rx.foreach` component to render components from a collection of data. +Recall again from the [basics](/docs/getting-started/basics/) that we cannot use Python `for` loops when referencing state vars in Reflex. Instead, use the `rx.foreach` component to render components from a collection of data. -For dynamic content that should automatically scroll to show the newest items, consider using the [auto scroll](/docs/library/dynamic-rendering/auto-scroll) component together with `rx.foreach`. +For dynamic content that should automatically scroll to show the newest items, consider using the [auto scroll](/docs/library/dynamic-rendering/auto-scroll/) component together with `rx.foreach`. ```python demo exec class IterState(rx.State): @@ -231,7 +231,7 @@ def projects_example() -> rx.Component: return rx.box(rx.foreach(NestedStateFE.projects, project_item)) ``` -If you want an example where not all of the values in the dict are the same type then check out the example on [var operations using foreach](/docs/vars/var-operations). +If you want an example where not all of the values in the dict are the same type then check out the example on [var operations using foreach](/docs/vars/var-operations/). Here is a further example of how to use `foreach` with a nested data structure. diff --git a/docs/custom-components/command-reference.md b/docs/custom-components/command-reference.md index 989312feb64..4288ef2bd18 100644 --- a/docs/custom-components/command-reference.md +++ b/docs/custom-components/command-reference.md @@ -82,7 +82,7 @@ The `custom_components` folder is where the actual implementation is. Do not wor `reflex_google_auth` is the top folder for importable code. The `reflex_google_auth/__init__.py` imports everything from the `reflex_google_auth/google_auth.py`. For the user of the package, the import looks like `from reflex_google_auth import ABC, XYZ`. -`reflex_google_auth/google_auth.py` is prefilled with code example and instructions from the [wrapping react guide](/docs/wrapping-react/overview). +`reflex_google_auth/google_auth.py` is prefilled with code example and instructions from the [wrapping react guide](/docs/wrapping-react/overview/). ### Demo App Folder diff --git a/docs/custom-components/overview.md b/docs/custom-components/overview.md index fa23d59d471..cfbc45f9017 100644 --- a/docs/custom-components/overview.md +++ b/docs/custom-components/overview.md @@ -8,7 +8,7 @@ Reflex users create many components of their own: ready to use high level compon Release **0.4.3** introduces a series of `reflex component` commands that help developers wrap react components, test, and publish them as python packages. As shown in the image below, there are already a few custom components published on PyPI, such as `reflex-spline`, `reflex-webcam`. -Check out the custom components gallery [here](/docs/custom-components/overview). +Check out the custom components gallery [here](/docs/custom-components/overview/). ```python eval rx.center( @@ -24,7 +24,7 @@ rx.center( ## Prerequisites for Publishing -In order to publish a Python package, an account is required with a python package index, for example, PyPI. The documentation to create accounts and generate API tokens can be found on their websites. For a quick reference, check out our [Prerequisites for Publishing](/docs/custom-components/prerequisites-for-publishing) page. +In order to publish a Python package, an account is required with a python package index, for example, PyPI. The documentation to create accounts and generate API tokens can be found on their websites. For a quick reference, check out our [Prerequisites for Publishing](/docs/custom-components/prerequisites-for-publishing/) page. ## Steps to Publishing diff --git a/docs/database/queries.md b/docs/database/queries.md index 8fdc8e343fd..fb20ab10d52 100644 --- a/docs/database/queries.md +++ b/docs/database/queries.md @@ -307,7 +307,7 @@ with rx.session() as session: ``` If the related objects are linked with foreign keys, the -[relationship loading techniques](/docs/database/relationships) can also fetch +[relationship loading techniques](/docs/database/relationships/) can also fetch linked objects without extra queries. ### Return Only What the UI Needs @@ -315,7 +315,7 @@ linked objects without extra queries. - Select only the columns the UI displays when tables are wide, instead of whole rows. - Give every query that returns detail rows a `.limit()`, and use - offset-based [pagination](/docs/library/tables-and-data-grids/table) when + offset-based [pagination](/docs/library/tables-and-data-grids/table/) when the user needs more rows. - Load static data (dropdown options, date bounds) once in an `on_load` event handler. When a filter changes, re-run only the filtered queries โ€” not the diff --git a/docs/enterprise/components.md b/docs/enterprise/components.md index 9c7dc58e9a7..fe2e77197aa 100644 --- a/docs/enterprise/components.md +++ b/docs/enterprise/components.md @@ -5,6 +5,9 @@ title: Enterprise Components ```python exec import reflex as rx +from reflex_docs.components.component_catalog import component_category +from reflex_docs.templates.docpage import h1_comp, text_comp_2 + def enterprise_component_grid(): sections = [ @@ -66,42 +69,17 @@ def enterprise_component_grid(): }, ] - cards = [] - for section in sections: - cards.append( - rx.box( - rx.link( - rx.el.h2( - section["title"], - class_name="font-large text-secondary-12", - ), - rx.icon("arrow_up_right", size=16, class_name="text-secondary-11"), - href=section["link"], - underline="none", - class_name="px-4 py-2 bg-secondary-1 hover:bg-secondary-3 transition-bg flex flex-row justify-between items-center !text-secondary-12", - ), - rx.text( - section["description"], - class_name="px-4 py-2 font-small text-secondary-9 border-t border-secondary-5", - ), - rx.box( - *[ - rx.link( - comp[0], - href=comp[1], - class_name="font-small text-secondary-11 hover:!text-primary-9 transition-color w-fit", - ) - for comp in section["components"] - ], - class_name="flex flex-col gap-2.5 px-4 py-2 border-t border-secondary-5", - ), - class_name="flex flex-col border border-secondary-5 rounded-xl bg-secondary-2 shadow-large overflow-hidden", - ) - ) - return rx.box( - *cards, - class_name="grid grid-cols-1 lg:grid-cols-2 gap-6 mt-8", + *[ + component_category( + title=section["title"], + href=section["link"], + description=section["description"], + links=section["components"], + ) + for section in sections + ], + class_name="docs-enterprise-catalog flex flex-col mt-8 mb-12", ) @@ -109,19 +87,15 @@ component_grid = enterprise_component_grid() ``` ```python eval -rx.el.h1( - "Enterprise Components", - class_name="lg:text-5xl text-3xl font-[525] scroll-mt-[113px] my-4 text-secondary-12", -) +h1_comp(text="Enterprise Components") ``` ```python eval -rx.el.span( - "Advanced UI components and features to enhance your Reflex applications. Available for free with the 'Built with Reflex' badge, or without the badge with an enterprise license.", - class_name="font-[475] text-secondary-11 max-w-[80%] text-sm", +text_comp_2( + text="Advanced UI components and features to enhance your Reflex applications. Available for free with the 'Built with Reflex' badge, or without the badge with an enterprise license.", ) ``` ```python eval component_grid -``` \ No newline at end of file +``` diff --git a/docs/events/events_overview.md b/docs/events/events_overview.md index 2b02ef75b54..fadeba60054 100644 --- a/docs/events/events_overview.md +++ b/docs/events/events_overview.md @@ -9,7 +9,7 @@ Events are composed of two parts: Event Triggers and Event Handlers. - **Events Handlers** are how the State of a Reflex application is updated. They are triggered by user interactions with the UI, such as clicking a button or hovering over an element. Events can also be triggered by the page loading or by other events. - **Event triggers** are component props that create an event to be sent to an event handler. - Each component supports a set of events triggers. They are described in each [component's documentation](/docs/library) in the event trigger section. + Each component supports a set of events triggers. They are described in each [component's documentation](/docs/library/) in the event trigger section. ## Example diff --git a/docs/events/special_events.md b/docs/events/special_events.md index 77c4c767df3..3df06d482c8 100644 --- a/docs/events/special_events.md +++ b/docs/events/special_events.md @@ -4,7 +4,7 @@ import reflex as rx # Special Events -Reflex also has built-in special events can be found in the [reference](/docs/api-reference/special-events). +Reflex also has built-in special events can be found in the [reference](/docs/api-reference/special-events/). For example, an event handler can trigger an alert on the browser. diff --git a/docs/getting_started/basics.md b/docs/getting_started/basics.md index 703be5d7bd8..96edac91915 100644 --- a/docs/getting_started/basics.md +++ b/docs/getting_started/basics.md @@ -18,7 +18,7 @@ import reflex as rx - Create pages and navigate between them ``` -If you haven't yet, [install Reflex](/docs/getting-started/installation) before continuing. Every example below imports the library as `rx`: +If you haven't yet, [install Reflex](/docs/getting-started/installation/) before continuing. Every example below imports the library as `rx`: ```python import reflex as rx @@ -26,7 +26,7 @@ import reflex as rx ## Creating and nesting components -[Components](/docs/ui/overview) are the building blocks for your app's user interface (UI). They are the visual elements that make up your app, like buttons, text, and images. Reflex has a wide selection of [built-in components](/docs/library) to get you started quickly. +[Components](/docs/ui/overview/) are the building blocks for your app's user interface (UI). They are the visual elements that make up your app, like buttons, text, and images. Reflex has a wide selection of [built-in components](/docs/library/) to get you started quickly. Components are created using functions that return a component object. @@ -57,11 +57,11 @@ def my_div(): ) ``` -If you need a component not provided by Reflex, you can check the [3rd party ecosystem](/docs/custom-components) or [wrap your own React component](/docs/wrapping-react/library-and-tags). +If you need a component not provided by Reflex, you can check the [3rd party ecosystem](/docs/custom-components/) or [wrap your own React component](/docs/wrapping-react/library-and-tags/). ## Customizing and styling components -Components can be customized using [props](/docs/components/props), which are passed in as keyword arguments to the component function. +Components can be customized using [props](/docs/components/props/), which are passed in as keyword arguments to the component function. Each component has props that are specific to that component. Check the docs for the component you are using to see what props are available. @@ -81,7 +81,7 @@ def round_button(): Use the `snake_case` version of the CSS property name as the prop name. ``` -See the [styling guide](/docs/styling/overview) for more information on how to style components +See the [styling guide](/docs/styling/overview/) for more information on how to style components In summary, components are made up of children and props. @@ -99,9 +99,9 @@ In summary, components are made up of children and props. ## Displaying data that changes over time -Apps need to store and display data that changes over time. Reflex handles this through [State](/docs/state/overview), which is a Python class that stores variables that can change when the app is running, as well as the functions that can change those variables. +Apps need to store and display data that changes over time. Reflex handles this through [State](/docs/state/overview/), which is a Python class that stores variables that can change when the app is running, as well as the functions that can change those variables. -To define a state class, subclass `rx.State` and define fields that store the state of your app. The state variables ([vars](/docs/vars/base-vars)) should have a type annotation, and can be initialized with a default value. +To define a state class, subclass `rx.State` and define fields that store the state of your app. The state variables ([vars](/docs/vars/base-vars/)) should have a type annotation, and can be initialized with a default value. ```python class MyState(rx.State): @@ -133,7 +133,7 @@ Vars can be referenced in multiple components, and will automatically update whe ## Responding to events and updating the screen -So far, we've defined state vars but we haven't shown how to change them. All state changes are handled through functions in the state class, called [event handlers](/docs/events/events-overview). +So far, we've defined state vars but we haven't shown how to change them. All state changes are handled through functions in the state class, called [event handlers](/docs/events/events-overview/). ```md alert Event handlers are the **only** way to change state in Reflex. @@ -309,7 +309,7 @@ In the next sections, we will show how to handle these cases. ## Conditional rendering -As mentioned above, you cannot use Python `if/else` statements with state vars in components. Instead, use the [rx.cond](/docs/components/conditional-rendering) function to conditionally render components. +As mentioned above, you cannot use Python `if/else` statements with state vars in components. Instead, use the [rx.cond](/docs/components/conditional-rendering/) function to conditionally render components. ```python demo exec class LoginState(rx.State): @@ -333,7 +333,7 @@ def show_login(): ## Rendering lists -To iterate over a var that is a list, use the [rx.foreach](/docs/components/rendering-iterables) function to render a list of components. +To iterate over a var that is a list, use the [rx.foreach](/docs/components/rendering-iterables/) function to render a list of components. Pass the list var and a function that returns a component as arguments to `rx.foreach`. @@ -358,7 +358,7 @@ The function that renders each item takes in a `Var`, since this will get compil ## Var Operations -You can't use arbitrary Python operations on state vars in components, but Reflex has [var operations](/docs/vars/var-operations) that you can use to manipulate state vars. +You can't use arbitrary Python operations on state vars in components, but Reflex has [var operations](/docs/vars/var-operations/) that you can use to manipulate state vars. For example, to check if a var is even, you can use the `%` and `==` var operations. @@ -411,7 +411,7 @@ You've got the core pieces โ€” components, state, events, compile-time vs. runti ```md alert info # Go deeper -- [Vars](/docs/vars/base-vars) and [var operations](/docs/vars/var-operations) โ€” the full API. -- [Events](/docs/events/events-overview) and [pages](/docs/pages/overview) โ€” routing, triggers, handlers. +- [Vars](/docs/vars/base-vars/) and [var operations](/docs/vars/var-operations/) โ€” the full API. +- [Events](/docs/events/events-overview/) and [pages](/docs/pages/overview) โ€” routing, triggers, handlers. - [How Reflex works](/docs/advanced-onboarding/how-reflex-works) โ€” what runs where, and why. ``` diff --git a/docs/getting_started/chatapp_tutorial.md b/docs/getting_started/chatapp_tutorial.md index c4edd7c9029..6b687f278ba 100644 --- a/docs/getting_started/chatapp_tutorial.md +++ b/docs/getting_started/chatapp_tutorial.md @@ -114,7 +114,7 @@ In this tutorial you'll learn how to: # Video: Example of Setting up the Chat App ``` -We will start by creating a new project and setting up our development environment. If you haven't installed [uv](https://docs.astral.sh/uv/) yet, see the [installation guide](/docs/getting-started/installation). Then create a new project directory and scaffold a Reflex app: +We will start by creating a new project and setting up our development environment. If you haven't installed [uv](https://docs.astral.sh/uv/) yet, see the [installation guide](/docs/getting-started/installation/). Then create a new project directory and scaffold a Reflex app: ```bash mkdir chatapp @@ -142,7 +142,7 @@ Now that we have our project set up, in the next section we will start building ## Basic Frontend -Let's start with defining the frontend for our chat app. In Reflex, the frontend can be broken down into independent, reusable components. See the [components docs](/docs/components/props) for more information. +Let's start with defining the frontend for our chat app. In Reflex, the frontend can be broken down into independent, reusable components. See the [components docs](/docs/components/props/) for more information. ### Display Q&A @@ -191,7 +191,7 @@ app.add_page(index) Components can be nested inside each other to create complex layouts. Here we create a parent container that contains two boxes for the question and answer. -We also add some basic styling to the components. Components take in keyword arguments, called [props](/docs/components/props), that modify the appearance and functionality of the component. We use the `text_align` prop to align the text to the left and right. +We also add some basic styling to the components. Components take in keyword arguments, called [props](/docs/components/props/), that modify the appearance and functionality of the component. We use the `text_align` prop to align the text to the left and right. ### Reusing Components @@ -256,7 +256,7 @@ def index() -> rx.Component: ### Chat Input -Now we want a way for the user to input a question. For this, we will use the [input](/docs/library/forms/input) component to have the user add text and a [button](/docs/library/forms/button) component to submit the question. +Now we want a way for the user to input a question. For this, we will use the [input](/docs/library/forms/input/) component to have the user add text and a [button](/docs/library/forms/button/) component to submit the question. ```python exec def action_bar() -> rx.Component: @@ -290,7 +290,7 @@ def index() -> rx.Component: ### Styling -Let's add some styling to the app. More information on styling can be found in the [styling docs](/docs/styling/overview). To keep our code clean, we will move the styling to a separate file `chatapp/style.py`. +Let's add some styling to the app. More information on styling can be found in the [styling docs](/docs/styling/overview/). To keep our code clean, we will move the styling to a separate file `chatapp/style.py`. ```python # style.py @@ -415,7 +415,7 @@ The app is looking good, but it's not very useful yet! In the next section, we w ## State -Now letโ€™s make the chat app interactive by adding state. The state is where we define all the variables that can change in the app and all the functions that can modify them. You can learn more about state in the [state docs](/docs/state/overview). +Now letโ€™s make the chat app interactive by adding state. The state is where we define all the variables that can change in the app and all the functions that can modify them. You can learn more about state in the [state docs](/docs/state/overview/). ### Defining State @@ -521,7 +521,7 @@ def action_bar() -> rx.Component: ) ``` -Normal Python `for` loops don't work for iterating over state vars because these values can change and aren't known at compile time. Instead, we use the [foreach](/docs/library/dynamic-rendering/foreach) component to iterate over the chat history. +Normal Python `for` loops don't work for iterating over state vars because these values can change and aren't known at compile time. Instead, we use the [foreach](/docs/library/dynamic-rendering/foreach/) component to iterate over the chat history. We also bind the input's `on_change` event to the `set_question` event handler, which will update the `question` state var while the user types in the input. We bind the button's `on_click` event to the `answer` event handler, which will process the question and add the answer to the chat history. @@ -601,7 +601,7 @@ def answer(self): ### Streaming Text -Normally state updates are sent to the frontend when an event handler returns. However, we want to stream the text from the chatbot as it is generated. We can do this by yielding from the event handler. See the [yield events docs](/docs/events/yield-events) for more info. +Normally state updates are sent to the frontend when an event handler returns. However, we want to stream the text from the chatbot as it is generated. We can do this by yielding from the event handler. See the [yield events docs](/docs/events/yield-events/) for more info. ```python exec import asyncio diff --git a/docs/getting_started/dashboard_tutorial.md b/docs/getting_started/dashboard_tutorial.md index 191c9caeccc..8a59249ce1f 100644 --- a/docs/getting_started/dashboard_tutorial.md +++ b/docs/getting_started/dashboard_tutorial.md @@ -6,7 +6,7 @@ import reflex as rx **~20 min hands-on** ยท Build a small data dashboard where users can input data that renders in a table and a graph. -This tutorial does not assume any existing Reflex knowledge, but we do recommend checking out the quick [Basics Guide](/docs/getting-started/basics) first. The techniques you'll learn are fundamental to any Reflex app. +This tutorial does not assume any existing Reflex knowledge, but we do recommend checking out the quick [Basics Guide](/docs/getting-started/basics/) first. The techniques you'll learn are fundamental to any Reflex app. This tutorial is divided into several sections: @@ -222,7 +222,7 @@ rx.box( ## Setup -1. [Install Reflex](/docs/getting-started/installation) if you haven't already. +1. [Install Reflex](/docs/getting-started/installation/) if you haven't already. 2. Create a folder called `dashboard_tutorial` and `cd` into it. 3. Run `uv init` and `uv add reflex`. 4. Run `uv run reflex init` and choose template `0` (the blank template). @@ -232,7 +232,7 @@ rx.box( ### Starter code -The `reflex init` command scaffolds an `rxconfig.py` (app [config](/docs/advanced-onboarding/configuration)), an `assets/` folder for static files, and a `dashboard_tutorial/dashboard_tutorial.py` module containing your app. Open that module and replace its contents โ€” we'll build the app up from scratch. +The `reflex init` command scaffolds an `rxconfig.py` (app [config](/docs/advanced-onboarding/configuration/)), an `assets/` folder for static files, and a `dashboard_tutorial/dashboard_tutorial.py` module containing your app. Open that module and replace its contents โ€” we'll build the app up from scratch. A minimal Reflex page is just a component function plus an app that registers it: @@ -316,7 +316,7 @@ def index() -> rx.Component: ## Dynamic data with State -The table above is static โ€” the rows are hardcoded. To make it dynamic, we move the data onto **state**: a Python class whose fields ([state vars](/docs/state/overview)) hold the app's data and whose methods ([event handlers](/docs/events/events-overview)) mutate them. +The table above is static โ€” the rows are hardcoded. To make it dynamic, we move the data onto **state**: a Python class whose fields ([state vars](/docs/state/overview/)) hold the app's data and whose methods ([event handlers](/docs/events/events-overview/)) mutate them. We'll model each row as a `User` dataclass so we can access fields by name (`user.name`) instead of by index: @@ -338,7 +338,7 @@ class State(rx.State): ] ``` -To iterate a list state var, use [`rx.foreach`](/docs/components/rendering-iterables) โ€” it takes an iterable and a function that renders each item. Here `show_user` receives a `User` and returns a `table.row`: +To iterate a list state var, use [`rx.foreach`](/docs/components/rendering-iterables/) โ€” it takes an iterable and a function that renders each item. Here `show_user` receives a `User` and returns a `table.row`: ```python def show_user(user: User) -> rx.Component: @@ -369,7 +369,7 @@ def index() -> rx.Component: ```md alert info # Why not a `for` loop? -A regular `for` loop runs at compile time, but state vars change at runtime โ€” so the rendered rows wouldn't update. `rx.foreach` tells the compiler to re-render when the state var changes. See [compile-time vs runtime](/docs/getting-started/basics#compile-time-vs.-runtime). +A regular `for` loop runs at compile time, but state vars change at runtime โ€” so the rendered rows wouldn't update. `rx.foreach` tells the compiler to re-render when the state var changes. See [compile-time vs runtime](/docs/getting-started/basics/#compile-time-vs.-runtime). ``` ```python exec @@ -427,7 +427,7 @@ The table looks the same, but the rows now come from state โ€” next we'll add a ## Add data with a form -We build a form using `rx.form`, which takes several components such as `rx.input` and `rx.select`, which represent the form fields that allow you to add information to submit with the form. Check out the [form](/docs/library/forms/form) docs for more information on form components. +We build a form using `rx.form`, which takes several components such as `rx.input` and `rx.select`, which represent the form fields that allow you to add information to submit with the form. Check out the [form](/docs/library/forms/form/) docs for more information on form components. The `rx.input` component takes in several props. The `placeholder` prop is the text that is displayed in the input field when it is empty. The `name` prop is the name of the input field, which gets passed through in the dictionary when the form is submitted. The `required` prop is a boolean that determines if the input field is required. @@ -448,7 +448,7 @@ rx.form( ) ``` -This form is all very compact as you can see from the example, so we need to add some styling to make it look better. We can do this by adding a `vstack` component around the form fields. The `vstack` component stacks the form fields vertically. Check out the [layout](/docs/styling/layout) docs for more information on how to layout your app. +This form is all very compact as you can see from the example, so we need to add some styling to make it look better. We can do this by adding a `vstack` component around the form fields. The `vstack` component stacks the form fields vertically. Check out the [layout](/docs/styling/layout/) docs for more information on how to layout your app. ```python demo rx.form( @@ -469,7 +469,7 @@ rx.form( Now you have probably realised that we have all the form fields, but we have no way to submit the form. We can add a submit button to the form by adding a `rx.button` component to the `vstack` component. The `rx.button` component takes in the text that is displayed on the button and the `type` prop which is the type of button. The `type` prop is set to `submit` so that the form is submitted when the button is clicked. -In addition to this we need a way to update the `users` state variable when the form is submitted. All state changes are handled through functions in the state class, called [event handlers](/docs/events/events-overview). +In addition to this we need a way to update the `users` state variable when the form is submitted. All state changes are handled through functions in the state class, called [event handlers](/docs/events/events-overview/). Components have special props called event triggers, such as `on_submit`, that can be used to make components interactive. Event triggers connect components to event handlers, which update the state. Different event triggers expect the event handler that you hook them up to, to take in different arguments (and some do not take in any arguments). @@ -1309,9 +1309,9 @@ The most important one is `theme` which allows you to customize the look and fee The `radius` prop sets the global radius value for the app that is inherited by all components that have a `radius` prop. It can be overwritten locally for a specific component by manually setting the `radius` prop. -The `accent_color` prop sets the accent color of the app. See the [theme docs](/docs/library/other/theme) for the full list of options. +The `accent_color` prop sets the accent color of the app. See the [theme docs](/docs/library/other/theme/) for the full list of options. -To see other props that can be set at the app level check out this [documentation](/docs/styling/theming) +To see other props that can be set at the app level check out this [documentation](/docs/styling/theming/) ```python app = rx.App( diff --git a/docs/getting_started/introduction.md b/docs/getting_started/introduction.md index 9afea4242bd..45b85175c8e 100644 --- a/docs/getting_started/introduction.md +++ b/docs/getting_started/introduction.md @@ -125,14 +125,12 @@ rx.hstack( rx.button( "Decrement", color_scheme="ruby", - high_contrast=True, on_click=CounterExampleState.decrement, ), rx.heading(CounterExampleState.count, as_="h2", font_size="2em"), rx.button( "Increment", color_scheme="grass", - high_contrast=True, on_click=CounterExampleState.increment, ), spacing="4", @@ -169,14 +167,12 @@ rx.box( rx.button( "Decrement", color_scheme="ruby", - high_contrast=True, on_click=State.decrement, ), rx.heading(State.count, font_size="2em"), rx.button( "Increment", color_scheme="grass", - high_contrast=True, on_click=State.increment, ), spacing="4", @@ -215,7 +211,7 @@ class State(rx.State): count: int = 0 ``` -State holds the app's mutable data. Variables declared here are called **[vars](/docs/vars/base-vars)**. Our counter has one: `count`, starting at `0`. +State holds the app's mutable data. Variables declared here are called **[vars](/docs/vars/base-vars/)**. Our counter has one: `count`, starting at `0`. ### Event Handlers @@ -240,21 +236,19 @@ def index(): rx.button( "Decrement", color_scheme="ruby", - high_contrast=True, on_click=State.decrement, ), rx.heading(State.count, as_="h2", font_size="2em"), rx.button( "Increment", color_scheme="grass", - high_contrast=True, on_click=State.increment, ), spacing="4", ) ``` -The UI is built from components (`rx.hstack`, `rx.button`, `rx.heading`) that can be nested and styled with CSS or [Tailwind](/docs/styling/tailwind). Reflex ships with [50+ built-in components](/docs/library), and you can [wrap any React component](/docs/wrapping-react/overview). +The UI is built from components (`rx.hstack`, `rx.button`, `rx.heading`) that can be nested and styled with CSS or [Tailwind](/docs/styling/tailwind/). Reflex ships with [50+ built-in components](/docs/library/), and you can [wrap any React component](/docs/wrapping-react/overview/). Components reference state vars (`rx.heading(State.count, โ€ฆ)`) and reactively re-render when state changes. Event triggers (`on_click=State.decrement`) wire UI to handlers. diff --git a/docs/getting_started/project-structure.md b/docs/getting_started/project-structure.md index efc7afd6a41..7891798d453 100644 --- a/docs/getting_started/project-structure.md +++ b/docs/getting_started/project-structure.md @@ -81,4 +81,4 @@ config = rx.Config( ) ``` -We will discuss project structure and configuration in more detail in the [advanced project structure](/docs/advanced-onboarding/code-structure) documentation. +We will discuss project structure and configuration in more detail in the [advanced project structure](/docs/advanced-onboarding/code-structure/) documentation. diff --git a/docs/hosting/config_file.md b/docs/hosting/config_file.md index d017990e418..2d7c81e6b3b 100644 --- a/docs/hosting/config_file.md +++ b/docs/hosting/config_file.md @@ -93,14 +93,14 @@ rx.table.root( "object", "sjc: 1", "Region deployment mapping", - "/hosting/regions", + "/hosting/regions/", ), ( "vmtype", "string", "c1m1", "Virtual machine specifications", - "/hosting/machine-types", + "/hosting/machine-types/", ), ("hostname", "string", "null", "Custom subdomain", None), ( @@ -108,7 +108,7 @@ rx.table.root( "string", ".env", "Environment variables file path", - "/hosting/secrets-environment-vars", + "/hosting/secrets-environment-vars/", ), ("project", "uuid", "null", "Project uuid", None), ("projectname", "string", "null", "Project name", None), diff --git a/docs/hosting/databricks.md b/docs/hosting/databricks.md index 43f3bd03992..d21caeb1ffc 100644 --- a/docs/hosting/databricks.md +++ b/docs/hosting/databricks.md @@ -54,7 +54,7 @@ env: ### Obtain Required Tokens 1. **Reflex Access Token** - - Visit the [Reflex Cloud Tokens docs](/docs/hosting/tokens) + - Visit the [Reflex Cloud Tokens docs](/docs/hosting/tokens/) - Navigate to Account Settings โ†’ Tokens - Create a new token and copy the value - Replace `your-token-here` in the configuration diff --git a/docs/hosting/deploy-quick-start.md b/docs/hosting/deploy-quick-start.md index 3b119d417a0..a1e857a10a1 100644 --- a/docs/hosting/deploy-quick-start.md +++ b/docs/hosting/deploy-quick-start.md @@ -8,6 +8,8 @@ So far, we have been running our apps locally on our own machines. But what if we want to share our apps with the world? This is where the hosting service comes in. +For hosting options and configuration guides, see the [Reflex Cloud overview](/docs/overview/). + ## Quick Start Reflexโ€™s hosting service makes it easy to deploy your apps without worrying about configuring the infrastructure. @@ -64,7 +66,7 @@ The command is by default interactive. It asks you a few questions for informati Thatโ€™s it! You should receive some feedback on the progress of your deployment and in a few minutes your app should be up. ๐ŸŽ‰ -For detailed information about the deploy command and its options, see the [Deploy API Reference](/docs/hosting/deploy-quick-start/) and the [CLI Reference](https://reflex.dev/docs/api-reference/cli/). +For detailed information about the deploy command and its options, see the [Deploy Command Reference](/docs/hosting/cli/deploy/) and the [CLI Reference](https://reflex.dev/docs/api-reference/cli/). ```md alert info diff --git a/docs/library/data-display/callout.md b/docs/library/data-display/callout.md index ef84cf58dd4..345fdcae21d 100644 --- a/docs/library/data-display/callout.md +++ b/docs/library/data-display/callout.md @@ -28,7 +28,7 @@ rx.callout( ) ``` -The `icon` prop allows an icon to be passed to the `callout` component. See the [**icon** component for all icons that are available.](/docs/library/data-display/icon) +The `icon` prop allows an icon to be passed to the `callout` component. See the [**icon** component for all icons that are available.](/docs/library/data-display/icon/) ## As alert diff --git a/docs/library/forms/form-ll.md b/docs/library/forms/form-ll.md index 5506e3014a7..6e77f923522 100644 --- a/docs/library/forms/form-ll.md +++ b/docs/library/forms/form-ll.md @@ -452,6 +452,7 @@ def radix_form_example(): "Username submitted: ", rx.text( RadixFormState.username, + as_="span", weight="bold", color="var(--accent-11)", ), @@ -460,6 +461,7 @@ def radix_form_example(): "Email submitted: ", rx.text( RadixFormState.email, + as_="span", weight="bold", color="var(--accent-11)", ), diff --git a/docs/library/forms/input.md b/docs/library/forms/input.md index 08026236fae..72c6c7dfba3 100644 --- a/docs/library/forms/input.md +++ b/docs/library/forms/input.md @@ -150,7 +150,7 @@ def form_input1(): ) ``` -To learn more about how to use forms in the [Form](/docs/library/forms/form) docs. +To learn more about how to use forms in the [Form](/docs/library/forms/form/) docs. ## Setting a value without using a State var diff --git a/docs/library/forms/select.md b/docs/library/forms/select.md index 8bc430cb619..e33f311ae2e 100644 --- a/docs/library/forms/select.md +++ b/docs/library/forms/select.md @@ -81,10 +81,10 @@ Use `rx.select` when: Consider alternatives when: -- You have fewer than five options โ†’ use [Radio Group](/docs/library/forms/radio-group) for better visibility -- Users need to select multiple values โ†’ use [Checkbox](/docs/library/forms/checkbox) groups or a multi-select pattern -- Users should type a value rather than choose one โ†’ use [Input](/docs/library/forms/input) -- You need hierarchical or searchable options โ†’ use the [low-level Select API](/docs/library/forms/select/low) with custom content +- You have fewer than five options โ†’ use [Radio Group](/docs/library/forms/radio-group/) for better visibility +- Users need to select multiple values โ†’ use [Checkbox](/docs/library/forms/checkbox/) groups or a multi-select pattern +- Users should type a value rather than choose one โ†’ use [Input](/docs/library/forms/input/) +- You need hierarchical or searchable options โ†’ use the [low-level Select API](/docs/library/forms/select/low/) with custom content ## Basic Usage @@ -98,7 +98,7 @@ The user can click the trigger button to open the dropdown and choose a differen ## Tracking the Selected Value -In most real apps, you need to react when the user selects a value, to filter data, update a chart, save to a database, or trigger another event. Bind the select to a [State](/docs/state/overview) var using the `value` prop and an `on_change` event handler. +In most real apps, you need to react when the user selects a value, to filter data, update a chart, save to a database, or trigger another event. Bind the select to a [State](/docs/state/overview/) var using the `value` prop and an `on_change` event handler. ```python demo exec class SelectState(rx.State): @@ -200,7 +200,7 @@ rx.select( ) ``` -To disable individual items rather than the whole select, use the [low-level API](/docs/library/forms/select/low) and set `disabled=True` on specific `rx.select.item` components. +To disable individual items rather than the whole select, use the [low-level API](/docs/library/forms/select/low/) and set `disabled=True` on specific `rx.select.item` components. ## Using Select in a Form @@ -254,11 +254,11 @@ def select_form(): ) ``` -For full details on building forms, validation, and submission handling, see the [Form documentation](/docs/library/forms/form). +For full details on building forms, validation, and submission handling, see the [Form documentation](/docs/library/forms/form/). ## Mapping Display Labels to Underlying Values -When your options have separate display labels and underlying values (e.g., a user ID for the value, a name for the label), use a [computed var](/docs/vars/computed-vars) to map between them. +When your options have separate display labels and underlying values (e.g., a user ID for the value, a name for the label), use a [computed var](/docs/vars/computed-vars/) to map between them. ```python demo exec class SelectDictState(rx.State): @@ -298,11 +298,11 @@ def select_dict_example(): ) ``` -For native label/value separation in the dropdown itself, use the [low-level Select API](/docs/library/forms/select/low) and pass `value=` and a display label child to each `rx.select.item`. +For native label/value separation in the dropdown itself, use the [low-level Select API](/docs/library/forms/select/low/) and pass `value=` and a display label child to each `rx.select.item`. ## Using Select Inside a Dialog -When placing a select inside a [Dialog](/docs/library/overlay/dialog) or other portal-based container, set `position="popper"` on the select so the dropdown menu positions itself correctly above the overlay content. +When placing a select inside a [Dialog](/docs/library/overlay/dialog/) or other portal-based container, set `position="popper"` on the select so the dropdown menu positions itself correctly above the overlay content. ```python demo rx.dialog.root( @@ -326,7 +326,7 @@ rx.dialog.root( Beyond `on_change`, the `on_open_change` event fires when the dropdown opens or closes. Use this to trigger analytics, prefetch data, or animate related UI. -The example below uses [rx.cond](/docs/library/dynamic-rendering/cond) to swap between two badges based on whether the dropdown is open. +The example below uses [rx.cond](/docs/library/dynamic-rendering/cond/) to swap between two badges based on whether the dropdown is open. ```python demo exec class SelectOpenState(rx.State): @@ -497,15 +497,15 @@ A dropdown (or single-select) lets the user pick one option. A multiselect lets ## Related Components -- [Radio Group](/docs/library/forms/radio-group) - inline single-selection for fewer than 5 options -- [Checkbox](/docs/library/forms/checkbox) - single or multi-selection with visible state -- [Form](/docs/library/forms/form) - grouping selects with other inputs for submission -- [Dialog](/docs/library/overlay/dialog) - modal dialogs that can contain selects -- [Low-level Select API](/docs/library/forms/select/low) - fine-grained control over trigger, content, and items +- [Radio Group](/docs/library/forms/radio-group/) - inline single-selection for fewer than 5 options +- [Checkbox](/docs/library/forms/checkbox/) - single or multi-selection with visible state +- [Form](/docs/library/forms/form/) - grouping selects with other inputs for submission +- [Dialog](/docs/library/overlay/dialog/) - modal dialogs that can contain selects +- [Low-level Select API](/docs/library/forms/select/low/) - fine-grained control over trigger, content, and items ## Related Concepts -- [State Management](/docs/state/overview) - how Reflex components track and update values -- [Event Handlers](/docs/events/events-overview) - understanding `on_change`, `on_open_change`, and related triggers -- [Forms and Validation](/docs/library/forms/form) - building complete forms with typed, validated inputs -- [Computed Vars](/docs/vars/computed-vars) - deriving values from state for dynamic options and lookups +- [State Management](/docs/state/overview/) - how Reflex components track and update values +- [Event Handlers](/docs/events/events-overview/) - understanding `on_change`, `on_open_change`, and related triggers +- [Forms and Validation](/docs/library/forms/form/) - building complete forms with typed, validated inputs +- [Computed Vars](/docs/vars/computed-vars/) - deriving values from state for dynamic options and lookups diff --git a/docs/library/graphing/charts/areachart.md b/docs/library/graphing/charts/areachart.md index cb2bf02c60e..96fc70b146c 100644 --- a/docs/library/graphing/charts/areachart.md +++ b/docs/library/graphing/charts/areachart.md @@ -323,6 +323,6 @@ def area_stateful(): Explore more chart types you can build with Reflex and Recharts in pure Python: -- [Line Chart](/docs/library/graphing/charts/linechart) -- [Bar Chart](/docs/library/graphing/charts/barchart) -- [Composed Chart](/docs/library/graphing/charts/composedchart) +- [Line Chart](/docs/library/graphing/charts/linechart/) +- [Bar Chart](/docs/library/graphing/charts/barchart/) +- [Composed Chart](/docs/library/graphing/charts/composedchart/) diff --git a/docs/library/graphing/charts/barchart.md b/docs/library/graphing/charts/barchart.md index 2d5be2e6be5..1f6470d22af 100644 --- a/docs/library/graphing/charts/barchart.md +++ b/docs/library/graphing/charts/barchart.md @@ -423,12 +423,12 @@ def bar_vertical(): ) ``` -To learn how to use the `sync_id`, `stack_id`,`x_axis_id` and `y_axis_id` props check out the of the area chart [documentation](/docs/library/graphing/charts/areachart), where these props are all described with examples. +To learn how to use the `sync_id`, `stack_id`,`x_axis_id` and `y_axis_id` props check out the of the area chart [documentation](/docs/library/graphing/charts/areachart/), where these props are all described with examples. ## Related Charts Explore more chart types you can build with Reflex and Recharts in pure Python: -- [Line Chart](/docs/library/graphing/charts/linechart) -- [Area Chart](/docs/library/graphing/charts/areachart) -- [Composed Chart](/docs/library/graphing/charts/composedchart) +- [Line Chart](/docs/library/graphing/charts/linechart/) +- [Area Chart](/docs/library/graphing/charts/areachart/) +- [Composed Chart](/docs/library/graphing/charts/composedchart/) diff --git a/docs/library/graphing/charts/composedchart.md b/docs/library/graphing/charts/composedchart.md index 8dc6f92d46b..c4abc9e03c7 100644 --- a/docs/library/graphing/charts/composedchart.md +++ b/docs/library/graphing/charts/composedchart.md @@ -14,7 +14,7 @@ import reflex as rx Composed charts in Reflex are built on [Recharts](https://recharts.org/), a React charting library, and created in pure Python. A `composed_chart` (also called a combo chart) is a higher-level component chart that is composed of multiple charts, where other charts are the children of the `composed_chart`. The charts are placed on top of each other in the order they are provided in the `composed_chart` function. ```md alert info -# To learn more about individual charts, checkout: **[area_chart](/docs/library/graphing/charts/areachart)**, **[line_chart](/docs/library/graphing/charts/linechart)**, or **[bar_chart](/docs/library/graphing/charts/barchart)**. +# To learn more about individual charts, checkout: **[area_chart](/docs/library/graphing/charts/areachart/)**, **[line_chart](/docs/library/graphing/charts/linechart/)**, or **[bar_chart](/docs/library/graphing/charts/barchart/)**. ``` ```python demo graphing @@ -94,6 +94,6 @@ def composed_dual_axis(): Explore more chart types you can build with Reflex and Recharts in pure Python: -- [Bar Chart](/docs/library/graphing/charts/barchart) -- [Line Chart](/docs/library/graphing/charts/linechart) -- [Area Chart](/docs/library/graphing/charts/areachart) +- [Bar Chart](/docs/library/graphing/charts/barchart/) +- [Line Chart](/docs/library/graphing/charts/linechart/) +- [Area Chart](/docs/library/graphing/charts/areachart/) diff --git a/docs/library/graphing/charts/errorbar.md b/docs/library/graphing/charts/errorbar.md index d4097aa6663..bd77827a406 100644 --- a/docs/library/graphing/charts/errorbar.md +++ b/docs/library/graphing/charts/errorbar.md @@ -89,6 +89,6 @@ def line_error(): Explore more chart types you can build with Reflex and Recharts in pure Python: -- [Scatter Chart](/docs/library/graphing/charts/scatterchart) -- [Line Chart](/docs/library/graphing/charts/linechart) -- [Bar Chart](/docs/library/graphing/charts/barchart) +- [Scatter Chart](/docs/library/graphing/charts/scatterchart/) +- [Line Chart](/docs/library/graphing/charts/linechart/) +- [Bar Chart](/docs/library/graphing/charts/barchart/) diff --git a/docs/library/graphing/charts/funnelchart.md b/docs/library/graphing/charts/funnelchart.md index 67bbba48250..4575ab7eedc 100644 --- a/docs/library/graphing/charts/funnelchart.md +++ b/docs/library/graphing/charts/funnelchart.md @@ -157,6 +157,6 @@ def funnel_animation(): Explore more chart types you can build with Reflex and Recharts in pure Python: -- [Pie Chart](/docs/library/graphing/charts/piechart) -- [Bar Chart](/docs/library/graphing/charts/barchart) -- [Composed Chart](/docs/library/graphing/charts/composedchart) +- [Pie Chart](/docs/library/graphing/charts/piechart/) +- [Bar Chart](/docs/library/graphing/charts/barchart/) +- [Composed Chart](/docs/library/graphing/charts/composedchart/) diff --git a/docs/library/graphing/charts/linechart.md b/docs/library/graphing/charts/linechart.md index 47a32dc7a64..92ba7240415 100644 --- a/docs/library/graphing/charts/linechart.md +++ b/docs/library/graphing/charts/linechart.md @@ -244,12 +244,12 @@ def line_dynamic(): ) ``` -To learn how to use the `sync_id`, `x_axis_id` and `y_axis_id` props check out the of the area chart [documentation](/docs/library/graphing/charts/areachart), where these props are all described with examples. +To learn how to use the `sync_id`, `x_axis_id` and `y_axis_id` props check out the of the area chart [documentation](/docs/library/graphing/charts/areachart/), where these props are all described with examples. ## Related Charts Explore more chart types you can build with Reflex and Recharts in pure Python: -- [Area Chart](/docs/library/graphing/charts/areachart) -- [Bar Chart](/docs/library/graphing/charts/barchart) -- [Scatter Chart](/docs/library/graphing/charts/scatterchart) +- [Area Chart](/docs/library/graphing/charts/areachart/) +- [Bar Chart](/docs/library/graphing/charts/barchart/) +- [Scatter Chart](/docs/library/graphing/charts/scatterchart/) diff --git a/docs/library/graphing/charts/piechart.md b/docs/library/graphing/charts/piechart.md index 8f329d16cc9..d6536644513 100644 --- a/docs/library/graphing/charts/piechart.md +++ b/docs/library/graphing/charts/piechart.md @@ -345,6 +345,6 @@ def pie_hover(): Explore more chart types you can build with Reflex and Recharts in pure Python: -- [Radial Bar Chart](/docs/library/graphing/charts/radialbarchart) -- [Funnel Chart](/docs/library/graphing/charts/funnelchart) -- [Bar Chart](/docs/library/graphing/charts/barchart) +- [Radial Bar Chart](/docs/library/graphing/charts/radialbarchart/) +- [Funnel Chart](/docs/library/graphing/charts/funnelchart/) +- [Bar Chart](/docs/library/graphing/charts/barchart/) diff --git a/docs/library/graphing/charts/radarchart.md b/docs/library/graphing/charts/radarchart.md index 09c7fd978c7..e398a12ba6d 100644 --- a/docs/library/graphing/charts/radarchart.md +++ b/docs/library/graphing/charts/radarchart.md @@ -201,6 +201,6 @@ def radar_dynamic(): Explore more chart types you can build with Reflex and Recharts in pure Python: -- [Radial Bar Chart](/docs/library/graphing/charts/radialbarchart) -- [Pie Chart](/docs/library/graphing/charts/piechart) -- [Line Chart](/docs/library/graphing/charts/linechart) +- [Radial Bar Chart](/docs/library/graphing/charts/radialbarchart/) +- [Pie Chart](/docs/library/graphing/charts/piechart/) +- [Line Chart](/docs/library/graphing/charts/linechart/) diff --git a/docs/library/graphing/charts/radialbarchart.md b/docs/library/graphing/charts/radialbarchart.md index 5d6320bc4f7..25164e76754 100644 --- a/docs/library/graphing/charts/radialbarchart.md +++ b/docs/library/graphing/charts/radialbarchart.md @@ -81,12 +81,12 @@ def radial_bar_advanced(): ## When to Use a Radial Bar Chart -A radial bar chart is a compact, eye-catching alternative to a standard bar chart, best suited to comparing a small number of categories or showing progress toward a goal. Because the bars wrap around a circle, it works well for dashboards where space is limited. The key props for shaping the chart are `inner_radius` and `outer_radius` (how far the bars sit from the center), `start_angle` and `end_angle` (the arc the bars span โ€” use `180` to `0` for a half-circle gauge), `min_angle` (the minimum bar length), and `background` (a track drawn behind each bar). For many categories or precise value comparisons, a standard [Bar Chart](/docs/library/graphing/charts/barchart) is usually easier to read. +A radial bar chart is a compact, eye-catching alternative to a standard bar chart, best suited to comparing a small number of categories or showing progress toward a goal. Because the bars wrap around a circle, it works well for dashboards where space is limited. The key props for shaping the chart are `inner_radius` and `outer_radius` (how far the bars sit from the center), `start_angle` and `end_angle` (the arc the bars span โ€” use `180` to `0` for a half-circle gauge), `min_angle` (the minimum bar length), and `background` (a track drawn behind each bar). For many categories or precise value comparisons, a standard [Bar Chart](/docs/library/graphing/charts/barchart/) is usually easier to read. ## Related Charts Explore more chart types you can build with Reflex and Recharts in pure Python: -- [Radar Chart](/docs/library/graphing/charts/radarchart) -- [Pie Chart](/docs/library/graphing/charts/piechart) -- [Bar Chart](/docs/library/graphing/charts/barchart) +- [Radar Chart](/docs/library/graphing/charts/radarchart/) +- [Pie Chart](/docs/library/graphing/charts/piechart/) +- [Bar Chart](/docs/library/graphing/charts/barchart/) diff --git a/docs/library/graphing/charts/scatterchart.md b/docs/library/graphing/charts/scatterchart.md index 6085341a8e1..6b84ecf58fd 100644 --- a/docs/library/graphing/charts/scatterchart.md +++ b/docs/library/graphing/charts/scatterchart.md @@ -81,7 +81,7 @@ def scatter_double(): ) ``` -To learn how to use the `x_axis_id` and `y_axis_id` props, check out the Multiple Axis section of the area chart [documentation](/docs/library/graphing/charts/areachart). +To learn how to use the `x_axis_id` and `y_axis_id` props, check out the Multiple Axis section of the area chart [documentation](/docs/library/graphing/charts/areachart/). ## Dynamic Data @@ -292,6 +292,6 @@ def bubble_chart(): Explore more chart types you can build with Reflex and Recharts in pure Python: -- [Line Chart](/docs/library/graphing/charts/linechart) -- [Bar Chart](/docs/library/graphing/charts/barchart) -- [Error Bar](/docs/library/graphing/charts/errorbar) +- [Line Chart](/docs/library/graphing/charts/linechart/) +- [Bar Chart](/docs/library/graphing/charts/barchart/) +- [Error Bar](/docs/library/graphing/charts/errorbar/) diff --git a/docs/library/graphing/charts/treemap.md b/docs/library/graphing/charts/treemap.md index 7346ef8b9c3..95c21ae3090 100644 --- a/docs/library/graphing/charts/treemap.md +++ b/docs/library/graphing/charts/treemap.md @@ -67,6 +67,6 @@ def treemap_aspect(): Explore more chart types you can build with Reflex and Recharts in pure Python: -- [Pie Chart](/docs/library/graphing/charts/piechart) -- [Bar Chart](/docs/library/graphing/charts/barchart) -- [Funnel Chart](/docs/library/graphing/charts/funnelchart) +- [Pie Chart](/docs/library/graphing/charts/piechart/) +- [Bar Chart](/docs/library/graphing/charts/barchart/) +- [Funnel Chart](/docs/library/graphing/charts/funnelchart/) diff --git a/docs/library/html/html.md b/docs/library/html/html.md index b95278e9499..c8997f8e60d 100644 --- a/docs/library/html/html.md +++ b/docs/library/html/html.md @@ -14,9 +14,9 @@ Reflex also provides a set of HTML elements that can be used to create web pages The most commonly used HTML elements are listed below. The full API reference is split into smaller pages: -- [Text elements](/docs/library/html/text) -- [Document and layout elements](/docs/library/html/layout) -- [Form elements](/docs/library/html/forms) -- [Media and embedded elements](/docs/library/html/media) -- [Table elements](/docs/library/html/tables) -- [SVG elements](/docs/library/html/svg) +- [Text elements](/docs/library/html/text/) +- [Document and layout elements](/docs/library/html/layout/) +- [Form elements](/docs/library/html/forms/) +- [Media and embedded elements](/docs/library/html/media/) +- [Table elements](/docs/library/html/tables/) +- [SVG elements](/docs/library/html/svg/) diff --git a/docs/library/media/audio.md b/docs/library/media/audio.md index cd7b02f12f3..c4aa85296bd 100644 --- a/docs/library/media/audio.md +++ b/docs/library/media/audio.md @@ -24,5 +24,5 @@ If we had a local file in the `assets` folder named `test.mp3` we could set `src ```md alert info # How to let your user upload an audio file -To let a user upload an audio file to your app check out the [upload docs](/docs/library/forms/upload). +To let a user upload an audio file to your app check out the [upload docs](/docs/library/forms/upload/). ``` diff --git a/docs/library/media/image.md b/docs/library/media/image.md index 77108d51070..42966339859 100644 --- a/docs/library/media/image.md +++ b/docs/library/media/image.md @@ -63,5 +63,5 @@ A cv2 image must be converted to a PIL image to be passed directly to `rx.image` ```md alert info # How to let your user upload an image -To let a user upload an image to your app check out the [upload docs](/docs/library/forms/upload). +To let a user upload an image to your app check out the [upload docs](/docs/library/forms/upload/). ``` diff --git a/docs/library/media/video.md b/docs/library/media/video.md index 1579cf724e8..d4f687d9912 100644 --- a/docs/library/media/video.md +++ b/docs/library/media/video.md @@ -20,5 +20,5 @@ If we had a local file in the `assets` folder named `test.mp4` we could set `url ```md alert info # How to let your user upload a video -To let a user upload a video to your app check out the [upload docs](/docs/library/forms/upload). +To let a user upload a video to your app check out the [upload docs](/docs/library/forms/upload/). ``` diff --git a/docs/library/other/html_embed.md b/docs/library/other/html_embed.md index 3593f2ad1d5..5b8b04cd3de 100644 --- a/docs/library/other/html_embed.md +++ b/docs/library/other/html_embed.md @@ -27,7 +27,7 @@ rx.vstack( # Missing Styles? Reflex uses Radix-UI and tailwind for styling, both of which reset default styles for headings. -If you are using the html component and want pretty default styles, consider setting `class_name='prose'`, adding `@tailwindcss/typography` package to `frontend_packages` and enabling it via `tailwind` config in `rxconfig.py`. See the [Tailwind docs](/docs/styling/overview) for an example of adding this plugin. +If you are using the html component and want pretty default styles, consider setting `class_name='prose'`, adding `@tailwindcss/typography` package to `frontend_packages` and enabling it via `tailwind` config in `rxconfig.py`. See the [Tailwind docs](/docs/styling/overview/) for an example of adding this plugin. ``` In this example, we render an image. diff --git a/docs/library/overlay/dialog.md b/docs/library/overlay/dialog.md index 8c5d092a734..08b965b7619 100644 --- a/docs/library/overlay/dialog.md +++ b/docs/library/overlay/dialog.md @@ -192,7 +192,7 @@ def dialog_example(): ) ``` -Check out the [menu docs](/docs/library/overlay/dropdown-menu) for an example of opening a dialog from within a dropdown menu. +Check out the [menu docs](/docs/library/overlay/dropdown-menu/) for an example of opening a dialog from within a dropdown menu. ## Form Submission to a Database from a Dialog diff --git a/docs/library/tables-and-data-grids/data_editor.md b/docs/library/tables-and-data-grids/data_editor.md index b589d0bd6ff..e57281dd491 100644 --- a/docs/library/tables-and-data-grids/data_editor.md +++ b/docs/library/tables-and-data-grids/data_editor.md @@ -36,7 +36,7 @@ data: list[list[Any]] = [ ] ``` -This component is introduced as an alternative to the [datatable](/docs/library/tables-and-data-grids/data-table) to support editing the displayed data. +This component is introduced as an alternative to the [datatable](/docs/library/tables-and-data-grids/data-table/) to support editing the displayed data. ## Columns @@ -459,6 +459,6 @@ rx.data_editor( Explore the other ways to work with tabular data in Reflex, all in pure Python: -- [Data Table](/docs/library/tables-and-data-grids/data-table) -- [Table](/docs/library/tables-and-data-grids/table) +- [Data Table](/docs/library/tables-and-data-grids/data-table/) +- [Table](/docs/library/tables-and-data-grids/table/) - [Tables and Data Grids](/docs/library/tables-and-data-grids/) diff --git a/docs/library/tables-and-data-grids/data_table.md b/docs/library/tables-and-data-grids/data_table.md index 96c23bf0b58..c181c6ca677 100644 --- a/docs/library/tables-and-data-grids/data_table.md +++ b/docs/library/tables-and-data-grids/data_table.md @@ -17,7 +17,7 @@ In this example we will read data from a csv file, convert it to a pandas datafr We will also add a search, pagination, sorting to the data_table to make it more accessible. -If you want to [add, edit or remove data](/docs/library/tables-and-data-grids/table) in your app or deal with anything but static data then the [`rx.table`](/docs/library/tables-and-data-grids/table) might be a better fit for your use case. +If you want to [add, edit or remove data](/docs/library/tables-and-data-grids/table/) in your app or deal with anything but static data then the [`rx.table`](/docs/library/tables-and-data-grids/table/) might be a better fit for your use case. ```python demo box rx.data_table( @@ -74,6 +74,6 @@ def index(): Explore the other ways to work with tabular data in Reflex, all in pure Python: -- [Table](/docs/library/tables-and-data-grids/table) -- [Data Editor](/docs/library/tables-and-data-grids/data-editor) +- [Table](/docs/library/tables-and-data-grids/table/) +- [Data Editor](/docs/library/tables-and-data-grids/data-editor/) - [Tables and Data Grids](/docs/library/tables-and-data-grids/) diff --git a/docs/library/tables-and-data-grids/table.md b/docs/library/tables-and-data-grids/table.md index 7155c9ea8ce..82df0adb071 100644 --- a/docs/library/tables-and-data-grids/table.md +++ b/docs/library/tables-and-data-grids/table.md @@ -152,7 +152,7 @@ class Customer(rx.Model, table=True): The Reflex table component (`rx.table`) is a semantic, composable table for presenting tabular data in pure Python. It renders a standard React/HTML table with headers, rows, and cells that you compose yourself. -If you just want to [represent static data](/docs/library/tables-and-data-grids/data-table) then the [`rx.data_table`](/docs/library/tables-and-data-grids/data-table) might be a better fit for your use case as it comes with in-built pagination, search and sorting. +If you just want to [represent static data](/docs/library/tables-and-data-grids/data-table/) then the [`rx.data_table`](/docs/library/tables-and-data-grids/data-table/) might be a better fit for your use case as it comes with in-built pagination, search and sorting. ## Basic Example @@ -195,7 +195,7 @@ If the table has too many columns to fit, wrap the `rx.table.root` in a containe Many times there is a need for the data we represent in our table to be dynamic. Dynamic data must be in `State`. Later we will show an example of how to access data from a database and how to load data from a source file. -In this example there is a `people` data structure in `State` that is [iterated through using `rx.foreach`](/docs/components/rendering-iterables). +In this example there is a `people` data structure in `State` that is [iterated through using `rx.foreach`](/docs/components/rendering-iterables/). ```python demo exec class TableForEachState(rx.State): @@ -474,9 +474,9 @@ The code below shows how to load data from a database and place it in an `rx.tab ## Loading data into table -A `Customer` [model](/docs/database/tables) is defined that inherits from `rx.Model`. +A `Customer` [model](/docs/database/tables/) is defined that inherits from `rx.Model`. -The `load_entries` event handler executes a [query](/docs/database/queries) that is used to request information from a database table. This `load_entries` event handler is called on the `on_mount` event trigger of the `rx.table.root`. +The `load_entries` event handler executes a [query](/docs/database/queries/) that is used to request information from a database table. This `load_entries` event handler is called on the `on_mount` event trigger of the `rx.table.root`. If you want to load the data when the page in the app loads you can set `on_load` in `app.add_page()` to equal this event handler, like `app.add_page(page_name, on_load=State.load_entries)`. @@ -1322,6 +1322,6 @@ rx.flex( Explore the other ways to work with tabular data in Reflex, all in pure Python: -- [Data Table](/docs/library/tables-and-data-grids/data-table) -- [Data Editor](/docs/library/tables-and-data-grids/data-editor) +- [Data Table](/docs/library/tables-and-data-grids/data-table/) +- [Data Editor](/docs/library/tables-and-data-grids/data-editor/) - [Tables and Data Grids](/docs/library/tables-and-data-grids/) diff --git a/docs/library/typography/link.md b/docs/library/typography/link.md index 4aa7c4fb6eb..0c8923c26d9 100644 --- a/docs/library/typography/link.md +++ b/docs/library/typography/link.md @@ -20,7 +20,7 @@ You can also provide local links to other pages in your project without writing ```python demo rx.link( "Example", - href="/library", + href="/library/", ) ``` @@ -39,13 +39,13 @@ rx.box("Example", id="example") To reference an anchor, you can use the `href` prop of the `link` component. The `href` should be in the format of the page you want to link to followed by a # and the id of the anchor. ```python demo -rx.link("Example", href="/library/typography/link#example") +rx.link("Example", href="/library/typography/link/#example") ``` ```md alert info # Redirecting the user using State -It is also possible to redirect the user to a new path within the application, using `rx.redirect()`. Check out the docs [here](/docs/api-reference/special-events). +It is also possible to redirect the user to a new path within the application, using `rx.redirect()`. Check out the docs [here](/docs/api-reference/special-events/). ``` ## Style diff --git a/docs/pages/overview.md b/docs/pages/overview.md index a12eca86884..c88f10084c5 100644 --- a/docs/pages/overview.md +++ b/docs/pages/overview.md @@ -69,7 +69,7 @@ You can directly import the module or import another module that imports the dec ### Links -[Links](/docs/library/typography/link) are accessible elements used primarily for navigation. Use the `href` prop to specify the location for the link to navigate to. +[Links](/docs/library/typography/link/) are accessible elements used primarily for navigation. Use the `href` prop to specify the location for the link to navigate to. ```python demo rx.link("Reflex Home Page.", href="https://reflex.dev/") @@ -78,7 +78,7 @@ rx.link("Reflex Home Page.", href="https://reflex.dev/") You can also provide local links to other pages in your project without writing the full url. ```python demo -rx.link("Example", href="/library") +rx.link("Example", href="/library/") ``` To open the link in a new tab, set the `is_external` prop to `True`. @@ -87,7 +87,7 @@ To open the link in a new tab, set the `is_external` prop to `True`. rx.link("Open in new tab", href="https://reflex.dev/", is_external=True) ``` -Check out the [link docs](/docs/library/typography/link) to learn more. +Check out the [link docs](/docs/library/typography/link/) to learn more. ```md video https://youtube.com/embed/ITOZkzjtjUA?start=4083&end=4423 # Video: Link-based Navigation @@ -200,7 +200,7 @@ app = rx.App() ## Getting the Current Page -You can access the current page from the `router` attribute in any state. See the [router docs](/docs/utility-methods/router-attributes) for all available attributes. +You can access the current page from the `router` attribute in any state. See the [router docs](/docs/utility-methods/router-attributes/) for all available attributes. ```python class State(rx.State): @@ -211,7 +211,7 @@ class State(rx.State): ``` The `router.route_id` attribute allows you to obtain the route pattern matched for the current page, -for [dynamic pages](/docs/pages/dynamic-routing) this will contain the slug rather than the actual value used to load the page. +for [dynamic pages](/docs/pages/dynamic-routing/) this will contain the slug rather than the actual value used to load the page. To get the actual URL path displayed in the browser, use `router.url.path`. For query parameters and the URL fragment, use `router.url.query_parameters` and diff --git a/docs/recipes/content/forms.md b/docs/recipes/content/forms.md index 09c038b66bb..19574b7f98a 100644 --- a/docs/recipes/content/forms.md +++ b/docs/recipes/content/forms.md @@ -6,7 +6,7 @@ import reflex as rx Forms are a common way to gather information from users. Below are some examples. -For more details, see the [form docs page](/docs/library/forms/form). +For more details, see the [form docs page](/docs/library/forms/form/). ## Event creation diff --git a/docs/recipes/content/grid.md b/docs/recipes/content/grid.md index 9dd83ce3c30..71bb637cbf7 100644 --- a/docs/recipes/content/grid.md +++ b/docs/recipes/content/grid.md @@ -6,7 +6,7 @@ import reflex as rx A simple responsive grid layout. We specify the number of columns to the `grid_template_columns` property as a list. The grid will automatically adjust the number of columns based on the screen size. -For details, see the [responsive docs page](/docs/styling/responsive). +For details, see the [responsive docs page](/docs/styling/responsive/). ## Cards diff --git a/docs/recipes/content/multi_column_row.md b/docs/recipes/content/multi_column_row.md index a6b0f78a3a6..65d2e514924 100644 --- a/docs/recipes/content/multi_column_row.md +++ b/docs/recipes/content/multi_column_row.md @@ -6,7 +6,7 @@ import reflex as rx A simple responsive multi-column and row layout. We specify the number of columns/rows to the `flex_direction` property as a list. The layout will automatically adjust the number of columns/rows based on the screen size. -For details, see the [responsive docs page](/docs/styling/responsive). +For details, see the [responsive docs page](/docs/styling/responsive/). ## Column diff --git a/docs/state_structure/component_state.md b/docs/state_structure/component_state.md index 56332e5849f..9fcf2d8fe2e 100644 --- a/docs/state_structure/component_state.md +++ b/docs/state_structure/component_state.md @@ -8,8 +8,8 @@ _New in version 0.4.6_. Defining a subclass of `rx.ComponentState` creates a special type of state that is tied to an instance of a component, rather than existing globally in the app. A Component State combines -[UI code](/docs/ui/overview) with state [Vars](/docs/vars/base-vars) and -[Event Handlers](/docs/events/events-overview), +[UI code](/docs/ui/overview/) with state [Vars](/docs/vars/base-vars/) and +[Event Handlers](/docs/events/events-overview/), and is useful for creating reusable components which operate independently of each other. ```md alert warning diff --git a/docs/styling/custom-stylesheets.md b/docs/styling/custom-stylesheets.md index 6b050933d2e..4c43af84291 100644 --- a/docs/styling/custom-stylesheets.md +++ b/docs/styling/custom-stylesheets.md @@ -16,7 +16,7 @@ app = rx.App( ## Local Stylesheets -You can also add local stylesheets. Just put the stylesheet under [`assets/`](/docs/assets/upload-and-download-files) and pass the path to the stylesheet to `rx.App`: +You can also add local stylesheets. Just put the stylesheet under [`assets/`](/docs/assets/upload-and-download-files/) and pass the path to the stylesheet to `rx.App`: ```python app = rx.App( diff --git a/docs/styling/overview.md b/docs/styling/overview.md index cd5eb45edbc..b6308b4ce00 100644 --- a/docs/styling/overview.md +++ b/docs/styling/overview.md @@ -172,7 +172,7 @@ The style dictionaries are applied in the order they are passed in. This means t ## Theming -As of Reflex 'v0.4.0', you can now theme your Reflex web apps. To learn more checkout the [Theme docs](/docs/styling/theming). +As of Reflex 'v0.4.0', you can now theme your Reflex web apps. To learn more checkout the [Theme docs](/docs/styling/theming/). The `Theme` component is used to change the theme of the application. The `Theme` can be set directly in your rx.App. @@ -184,7 +184,7 @@ app = rx.App( ) ``` -Additionally you can modify the theme of your app through using the `Theme Panel` component which can be found in the [Theme Panel docs](/docs/library/other/theme). +Additionally you can modify the theme of your app through using the `Theme Panel` component which can be found in the [Theme Panel docs](/docs/library/other/theme/). ## Special Styles diff --git a/docs/styling/theming.md b/docs/styling/theming.md index d21aaa9de86..ddb11f876cd 100644 --- a/docs/styling/theming.md +++ b/docs/styling/theming.md @@ -252,7 +252,7 @@ rx.table.root( ) ``` -Additionally you can modify the theme of your app through using the `Theme Panel` component which can be found in the [Theme Panel docs](/docs/library/other/theme). +Additionally you can modify the theme of your app through using the `Theme Panel` component which can be found in the [Theme Panel docs](/docs/library/other/theme/). ## Colors diff --git a/docs/ui/overview.md b/docs/ui/overview.md index d22e9c4c94e..af72f2e6870 100644 --- a/docs/ui/overview.md +++ b/docs/ui/overview.md @@ -51,7 +51,7 @@ Some props are specific to a component. For example, the `header` and `content` Styling props like `color` are shared across many components. ```md alert info -# You can find all the props for a component by checking its documentation page in the [component library](/docs/library). +# You can find all the props for a component by checking its documentation page in the [component library](/docs/library/). ``` ## Pages diff --git a/docs/utility_methods/lifespan_tasks.md b/docs/utility_methods/lifespan_tasks.md index 0fcd2222a4b..bf9ac97515b 100644 --- a/docs/utility_methods/lifespan_tasks.md +++ b/docs/utility_methods/lifespan_tasks.md @@ -23,7 +23,7 @@ Choose based on what starts the work and what it operates on: runs independent of any user or session โ€” it starts when the app starts and is not tied to UI state. Examples: polling an external API on an interval, monitoring a service, refreshing a shared cache, scheduled maintenance. -- Use a [background event](/docs/events/background-events) +- Use a [background event](/docs/events/background-events/) (`@rx.event(background=True)`) for user-triggered, session-bound work that reads or updates that user's UI state. Examples: processing a file after a user clicks submit, calling an API on demand, showing progress during a diff --git a/docs/utility_methods/router_attributes.md b/docs/utility_methods/router_attributes.md index fd36a69968f..afd827aea1b 100644 --- a/docs/utility_methods/router_attributes.md +++ b/docs/utility_methods/router_attributes.md @@ -107,7 +107,7 @@ about the current page, session, or state. The `self.router` attribute has several sub-attributes that provide various information: - `router.url`: the URL of the current page, parsed into its components (see [URL Attributes](#url-attributes) below). -- `router.route_id`: the route pattern that matched the current request (e.g. `/posts/[id]`). For [dynamic pages](/docs/pages/dynamic-routing) this contains the slug rather than the actual value used to load the page. +- `router.route_id`: the route pattern that matched the current request (e.g. `/posts/[id]`). For [dynamic pages](/docs/pages/dynamic-routing/) this contains the slug rather than the actual value used to load the page. - `router.session`: data about the current session - `client_token`: UUID associated with the current tab's token. Each tab has a unique token. @@ -173,7 +173,7 @@ class State(rx.State): # ... load the appropriate data for that page ... ``` -For dynamic path segments such as `[id]` or `[[...splat]]`, see [Dynamic Routes](/docs/pages/dynamic-routing) โ€” those values are exposed as state vars on the root state (e.g. `rx.State.id`, `rx.State.splat`), not through `router.url`. +For dynamic path segments such as `[id]` or `[[...splat]]`, see [Dynamic Routes](/docs/pages/dynamic-routing/) โ€” those values are exposed as state vars on the root state (e.g. `rx.State.id`, `rx.State.splat`), not through `router.url`. ## Migrating from `router.page` diff --git a/docs/vars/custom_vars.md b/docs/vars/custom_vars.md index 5222c139af0..3461576be4b 100644 --- a/docs/vars/custom_vars.md +++ b/docs/vars/custom_vars.md @@ -6,7 +6,7 @@ from typing import TypedDict # Custom Vars -As mentioned in the [vars page](/docs/vars/base-vars), Reflex vars must be JSON serializable. +As mentioned in the [vars page](/docs/vars/base-vars/), Reflex vars must be JSON serializable. This means we can support any Python primitive types, as well as lists, dicts, and tuples. However, you can also create more complex var types using dataclasses (recommended), TypedDict, or Pydantic models. diff --git a/docs/vars/hybrid_properties.md b/docs/vars/hybrid_properties.md index b45a8d1bc14..83c586564d2 100644 --- a/docs/vars/hybrid_properties.md +++ b/docs/vars/hybrid_properties.md @@ -51,7 +51,7 @@ event handler, `self.full_name` returns the actual combined string. ## Hybrid Properties vs. Computed Vars -[Computed vars](/docs/vars/computed-vars) and hybrid properties both derive a value from +[Computed vars](/docs/vars/computed-vars/) and hybrid properties both derive a value from other state vars, but they work very differently: | | Computed var (`@rx.var`) | Hybrid property (`hybrid_property`) | @@ -74,7 +74,7 @@ value can only be produced on the server. By default a hybrid property reuses the **same code** on the frontend and backend. When the two should differ, register a frontend-only implementation with `@.var`. The -function receives the state class and returns a [Var](/docs/vars/base-vars); declaring it +function receives the state class and returns a [Var](/docs/vars/base-vars/); declaring it a `classmethod` types that first parameter as the class: ```python demo exec id=hybrid_greeting diff --git a/docs/wrapping-react/more-wrapping-examples.md b/docs/wrapping-react/more-wrapping-examples.md index ce1a8364b6d..814b0b65340 100644 --- a/docs/wrapping-react/more-wrapping-examples.md +++ b/docs/wrapping-react/more-wrapping-examples.md @@ -105,7 +105,7 @@ app.add_page(index) In this example we are wrapping the React Leaflet library from the NPM package [react-leaflet](https://www.npmjs.com/package/react-leaflet). -On line `1` we import the `dynamic` function from Next.js and on line `21` we set `ssr: false`. Lines `4` and `6` use the `dynamic` function to import the `MapContainer` and `TileLayer` components from the `react-leaflet` library. This is used to dynamically import the `MapContainer` and `TileLayer` components from the `react-leaflet` library. This is done in Reflex by using the `NoSSRComponent` class when defining the component. There is more information of when this is needed on the `Dynamic Imports` section of this [page](/docs/wrapping-react/library-and-tags). +On line `1` we import the `dynamic` function from Next.js and on line `21` we set `ssr: false`. Lines `4` and `6` use the `dynamic` function to import the `MapContainer` and `TileLayer` components from the `react-leaflet` library. This is used to dynamically import the `MapContainer` and `TileLayer` components from the `react-leaflet` library. This is done in Reflex by using the `NoSSRComponent` class when defining the component. There is more information of when this is needed on the `Dynamic Imports` section of this [page](/docs/wrapping-react/library-and-tags/). It mentions in the documentation that it is necessary to include the Leaflet CSS file, which is added on line `2` in the React code below. This can be done in Reflex by using the `add_imports` method in the `MapContainer` component. We can add a relative path from within the React library or a full URL to the CSS file. @@ -207,7 +207,7 @@ app.add_page(index) In this example we are wrapping the React renderer for creating PDF files on the browser and server from the NPM package [@react-pdf/renderer](https://www.npmjs.com/package/@react-pdf/renderer). -This example is similar to the previous examples, and again Dynamic Imports are required for this library. This is done in Reflex by using the `NoSSRComponent` class when defining the component. There is more information on why this is needed on the `Dynamic Imports` section of this [page](/docs/wrapping-react/library-and-tags). +This example is similar to the previous examples, and again Dynamic Imports are required for this library. This is done in Reflex by using the `NoSSRComponent` class when defining the component. There is more information on why this is needed on the `Dynamic Imports` section of this [page](/docs/wrapping-react/library-and-tags/). The main difference with this example is that the `style` prop, used on lines `20`, `21` and `24` in React code, is a reserved name in Reflex so can not be wrapped. A different name must be used when wrapping this prop and then this name must be changed back to the original with the `rename_props` method. In this example we name the prop `theme` in our Reflex code and then change it back to `style` with the `rename_props` method in both the `Page` and `View` components. diff --git a/docs/wrapping-react/overview.md b/docs/wrapping-react/overview.md index 453c38d47fe..d602207f9bb 100644 --- a/docs/wrapping-react/overview.md +++ b/docs/wrapping-react/overview.md @@ -9,7 +9,9 @@ One of Reflex's most powerful features is the ability to wrap React components a If you want a specific component for your app but Reflex doesn't provide it, there's a good chance it's available as a React component. Search for it on [npm](https://www.npmjs.com/), and if it's there, you can use it in your Reflex app. You can also create your own local React components and wrap them in Reflex. -Once you wrap your component, you [publish it](/docs/custom-components/overview) to the Reflex library so that others can use it. +Once you wrap your component, you [publish it](/docs/custom-components/overview/) to the Reflex library so that others can use it. + +For a complete example with typed props, Python state, and an event handler, follow [Wrapping React Step by Step](/docs/wrapping-react/step-by-step/). ## Simple Example diff --git a/docs/wrapping-react/step-by-step.md b/docs/wrapping-react/step-by-step.md index 5bab69ef458..7be671ee19f 100644 --- a/docs/wrapping-react/step-by-step.md +++ b/docs/wrapping-react/step-by-step.md @@ -1,2 +1,88 @@ +--- +meta_description: Wrap a React color picker in Reflex, declare typed props and events, connect Python state, and troubleshoot imports and browser-only rendering. +--- # Wrapping React Step by Step + +This guide wraps a React color picker as a Reflex component. You will pass a Python state value to a React prop, receive a color-change event, and update the page from Python. + +The example uses [react-colorful](https://github.com/omgovich/react-colorful), which is also used in the [wrapping overview](/docs/wrapping-react/overview/). Start with a working Reflex project from the [installation guide](/docs/getting-started/installation/). + +## 1. Identify the package and export + +The npm package is `react-colorful`, and the named React export is `HexColorPicker`. Reflex needs both names: `library` identifies the package to install, while `tag` identifies the component to import. + +This example pins the library to `react-colorful@5.7.0`. Reflex installs this frontend dependency during compilation; you do not install it with a Python package manager. + +Use `is_default = True` only for a default export. `HexColorPicker` is a named export, so the example keeps the default value of `False`. + +## 2. Declare the component's props + +Subclass `NoSSRComponent` for this browser-rendered picker. Declare `color: rx.Var[str]` so a literal string or a reactive Reflex value can be passed as the React `color` prop. + +A wrapper's Python field names use snake case. Reflex translates `on_change` to React's `onChange` prop. The event declaration `rx.EventHandler[lambda color: [color]]` extracts the string emitted by the picker and passes it to your Python handler. + +Different React components emit different arguments. Check the library's documented callback signature instead of assuming every change event contains a browser event object. + +## 3. Connect the event to Python state + +The state stores the current hex color. The `set_color` event handler receives the emitted string and updates that state. Passing the state back as the picker's `color` prop makes this a controlled component: the picker and text readout share one value. + +## 4. Run the complete example + +Move the picker below to update the color value. To use this code in an app, copy the imports, component class, state class, and `wrapped_color_example` function into your app module, then register the function with `app.add_page(wrapped_color_example)`. + +```python demo exec +import reflex as rx +from reflex.components.component import NoSSRComponent + + +class TutorialColorPicker(NoSSRComponent): + library = "react-colorful@5.7.0" + tag = "HexColorPicker" + color: rx.Var[str] + on_change: rx.EventHandler[lambda color: [color]] + + +class WrappedColorState(rx.State): + color: str = "#6750a4" + + @rx.event + def set_color(self, color: str): + self.color = color + + +def wrapped_color_example(): + return rx.vstack( + TutorialColorPicker.create( + color=WrappedColorState.color, + on_change=WrappedColorState.set_color, + ), + rx.text("Selected color: ", WrappedColorState.color), + spacing="4", + align="center", + ) +``` + +For a standalone app, add this after the example: + +```python +app = rx.App() +app.add_page(wrapped_color_example) +``` + +Run your app with `reflex run`, open the page, and drag the picker. The text should change along with the picker. A plain `rx.Component` is appropriate for wrappers that support server rendering; use `NoSSRComponent` when a package needs browser APIs during rendering. + +## Troubleshooting + +| Symptom | What to check | +| --- | --- | +| The package or export cannot be found | Check the exact npm package name, version, exported tag, and whether it is a named or default export. | +| The picker moves but Python state does not change | Check that the prop is named `on_change`, that its event specification extracts the callback argument, and that the handler accepts that argument. | +| The text changes but the picker resets | Pass the state value as the `color` prop as well as connecting `on_change`. | +| A package raises `window is not defined` during rendering | Check whether it requires browser-only rendering and whether `NoSSRComponent` is appropriate. | +| A prop type fails to compile | Match the React library's documented type. Use typed `rx.Var` declarations for values that can be reactive. | + +## Next steps + +Add only the props and callbacks you need, and test them against the React library's public API. For reusable packaging, see [custom components](/docs/custom-components/overview/). For state and event behavior, see [events](/docs/events/events-overview/) and the [state overview](/docs/state/overview/). diff --git a/packages/reflex-site-shared/README.md b/packages/reflex-site-shared/README.md index a2ad14309e2..068d6709433 100644 --- a/packages/reflex-site-shared/README.md +++ b/packages/reflex-site-shared/README.md @@ -63,6 +63,12 @@ config = rx.Config( Existing sites that provide their own font CSS can use `SharedSiteStylesPlugin(include_fonts=False)`. +Use `SharedSiteStylesPlugin(editorial=True)` for the neutral editorial palette +and capsule button treatment from the marketing site. It includes semantic +surface, text, border, and focus tokens, and adapts to `.light` / `.dark` themes. +The default remains the original violet theme; either option supports custom +font loading through `include_fonts=False`. + `DocsMarkdownPlugin` serves each discovered page as Markdown using the same URL convention as the official Reflex docs. For example, `/guide/installation/` is also available at `/guide/installation.md` and diff --git a/packages/reflex-site-shared/news/+editorial-theme.feature.md b/packages/reflex-site-shared/news/+editorial-theme.feature.md new file mode 100644 index 00000000000..5174fb82ede --- /dev/null +++ b/packages/reflex-site-shared/news/+editorial-theme.feature.md @@ -0,0 +1,11 @@ +Add an opt-in editorial theme to `SharedSiteStylesPlugin` with neutral colors, consistent documentation typography and surfaces, accessible capsule buttons, and dark-mode support. Live documentation examples retain their own heading and table styles. + +Match documentation footer social links to the marketing footer's compact icon controls and remove the forum icon. + +Align documentation fields, navigation, content surfaces, and icon controls with the shared editorial shape system. + +Refine the docs navbar with understated text navigation, compact search, and consistent editorial controls. + +Explore framework capabilities through accessible tabs and theme-aware product diagrams. + +Use fine solid connectors with rounded corners and clean junctions in framework diagrams. diff --git a/packages/reflex-site-shared/news/7137.bugfix.md b/packages/reflex-site-shared/news/7137.bugfix.md new file mode 100644 index 00000000000..76eb4e56cc1 --- /dev/null +++ b/packages/reflex-site-shared/news/7137.bugfix.md @@ -0,0 +1 @@ +Use canonical trailing-slash destinations for documentation Markdown links while preserving asset URLs, query strings, and fragments. Keep reference-table columns readable on narrow screens with horizontal scrolling. diff --git a/packages/reflex-site-shared/src/reflex_site_shared/components/blocks/collapsible.py b/packages/reflex-site-shared/src/reflex_site_shared/components/blocks/collapsible.py index fb2dc4485d1..ce33479eda5 100644 --- a/packages/reflex-site-shared/src/reflex_site_shared/components/blocks/collapsible.py +++ b/packages/reflex-site-shared/src/reflex_site_shared/components/blocks/collapsible.py @@ -67,4 +67,5 @@ def collapsible_box( margin_bottom="16px", margin_top="16px", overflow="hidden", + class_name="docs-callout", ) diff --git a/packages/reflex-site-shared/src/reflex_site_shared/components/blocks/demo.py b/packages/reflex-site-shared/src/reflex_site_shared/components/blocks/demo.py index c33abe4bb6c..257134188f8 100644 --- a/packages/reflex-site-shared/src/reflex_site_shared/components/blocks/demo.py +++ b/packages/reflex-site-shared/src/reflex_site_shared/components/blocks/demo.py @@ -71,7 +71,7 @@ def _reflex_build_action() -> rx.Component: rel="noopener noreferrer", class_name=( f"{ui.button.class_names.for_button('ghost-highlight', 'sm')} " - "mb-1.5 no-underline pl-0" + "docs-demo-build-action mb-1.5 no-underline pl-0" ), ) @@ -107,6 +107,7 @@ def docdemobox(*children, **props) -> rx.Component: """ return rx.box( *children, + data_docs_example=True, **props, class_name="flex flex-col p-6 rounded-xl overflow-x-auto border border-secondary-4 bg-secondary-2 items-center justify-center w-full", ) diff --git a/packages/reflex-site-shared/src/reflex_site_shared/components/blocks/flexdown.py b/packages/reflex-site-shared/src/reflex_site_shared/components/blocks/flexdown.py index 5b52091bf83..1e91deedaec 100644 --- a/packages/reflex-site-shared/src/reflex_site_shared/components/blocks/flexdown.py +++ b/packages/reflex-site-shared/src/reflex_site_shared/components/blocks/flexdown.py @@ -53,7 +53,7 @@ def _markdown_table(*children, **props) -> rx.Component: class_name="w-full border-collapse text-sm border border-secondary-4 rounded-lg overflow-hidden bg-white-1 ", **props, ), - class_name="w-full rounded-xl border border-secondary-a4 my-6 max-w-full overflow-hidden", + class_name="docs-table w-full rounded-xl border border-secondary-a4 my-6 max-w-full overflow-hidden", ) diff --git a/packages/reflex-site-shared/src/reflex_site_shared/components/blocks/typography.py b/packages/reflex-site-shared/src/reflex_site_shared/components/blocks/typography.py index 52d11557d77..f87e1cf7048 100644 --- a/packages/reflex-site-shared/src/reflex_site_shared/components/blocks/typography.py +++ b/packages/reflex-site-shared/src/reflex_site_shared/components/blocks/typography.py @@ -1,5 +1,8 @@ """Typography blocks for doc pages.""" +from pathlib import PurePosixPath +from urllib.parse import urlsplit + import reflex as rx from reflex_site_shared.styles import fonts @@ -33,6 +36,7 @@ def definition(title: str, *children) -> rx.Component: "background_color": rx.color("mauve", 3), }, align_items="start", + class_name="docs-definition", ) @@ -43,7 +47,9 @@ def text_comp(text: rx.Var[str]) -> rx.Component: Returns: The component. """ - return rx.text(text, class_name="font-normal text-secondary-11 mb-4 leading-7") + return rx.text( + text, class_name="docs-body-text font-normal text-secondary-11 mb-4 leading-7" + ) @rx.memo @@ -55,7 +61,7 @@ def text_comp_2(text: rx.Var[str]) -> rx.Component: """ return rx.text( text, - class_name="font-normal text-secondary-11 max-w-[80%] mb-10", + class_name="docs-introduction docs-body-text font-normal text-secondary-11 max-w-[80%] mb-10", ) @@ -66,7 +72,9 @@ def list_comp(text: rx.Var[str]) -> rx.Component: Returns: The component. """ - return rx.list_item(text, class_name="font-normal text-secondary-11 mb-4") + return rx.list_item( + text, class_name="docs-body-text font-normal text-secondary-11 mb-4" + ) @rx.memo @@ -99,6 +107,29 @@ def code_comp(text: rx.Var[str]) -> rx.Component: return rx.code(text, class_name="code-style") +def _canonical_docs_href(href: str | rx.Var[str] | None): + """Add the docs page slash while preserving assets, queries and fragments. + + Args: + href: Literal or reactive link destination. + + Returns: + The canonical docs destination, or the original non-docs link. + """ + if not isinstance(href, str): + return href + url = urlsplit(href) + if ( + url.netloc in {"", "reflex.dev", "www.reflex.dev"} + and url.scheme in {"", "http", "https"} + and (url.path == "/docs" or url.path.startswith("/docs/")) + and not url.path.endswith("/") + and not PurePosixPath(url.path).suffix + ): + return url._replace(path=url.path + "/").geturl() + return href + + def doclink(text: str, href: str, **props) -> rx.Component: """Create a styled link for doc pages. @@ -112,9 +143,9 @@ def doclink(text: str, href: str, **props) -> rx.Component: """ return rx.el.elements.a( text, - href=href, + href=_canonical_docs_href(href), **props, - class_name="text-secondary-12 decoration-secondary-12 underline", + class_name="docs-text-link text-secondary-12 decoration-secondary-12 underline", ) @@ -129,9 +160,11 @@ def doclink2(text: str, **props) -> rx.Component: Returns: The styled link. """ + if "href" in props: + props["href"] = _canonical_docs_href(props["href"]) return rx.el.elements.a( text, **props, style=fonts.base, - class_name="text-secondary-12 decoration-secondary-12 underline", + class_name="docs-text-link text-secondary-12 decoration-secondary-12 underline", ) diff --git a/packages/reflex-site-shared/src/reflex_site_shared/components/docs_api.py b/packages/reflex-site-shared/src/reflex_site_shared/components/docs_api.py index 42ffba62048..93164ffcb42 100644 --- a/packages/reflex-site-shared/src/reflex_site_shared/components/docs_api.py +++ b/packages/reflex-site-shared/src/reflex_site_shared/components/docs_api.py @@ -51,10 +51,13 @@ def docs_api_table( class_name="border-b border-secondary-4 bg-secondary-2", ), rx.el.tbody(*rows, class_name="bg-secondary-1"), - class_name="w-full table-fixed border-collapse text-left", + class_name=( + "w-full min-w-[40rem] table-fixed border-collapse text-left " + "[&_td_code]:min-w-0 [&_td_code]:whitespace-normal [&_td_code]:break-words" + ), ), class_name=( - "mb-4 w-full min-w-0 overflow-hidden rounded-xl border " + "docs-table mb-4 w-full min-w-0 overflow-x-auto rounded-xl border " "border-secondary-4 bg-secondary-1 shadow-small" ), ) diff --git a/packages/reflex-site-shared/src/reflex_site_shared/components/docs_shell.py b/packages/reflex-site-shared/src/reflex_site_shared/components/docs_shell.py index 2bc83de793c..456c5199644 100644 --- a/packages/reflex-site-shared/src/reflex_site_shared/components/docs_shell.py +++ b/packages/reflex-site-shared/src/reflex_site_shared/components/docs_shell.py @@ -76,7 +76,7 @@ def docs_navbar_frame( ), class_name="mx-auto flex h-[4.5rem] w-full max-w-full flex-row items-center bg-gradient-to-b from-secondary-2 to-secondary-1 px-6 shadow-[0_-2px_2px_1px_rgba(0,0,0,0.02),0_1px_1px_0_rgba(0,0,0,0.08),0_4px_8px_0_rgba(0,0,0,0.03),0_0_0_1px_#FFF_inset] backdrop-blur-[16px] dark:border-b dark:border-secondary-4 dark:shadow-none 3xl:px-16", ), - class_name="fixed top-0 z-[9999] flex w-full flex-col self-center", + class_name="docs-navbar fixed top-0 z-[9999] flex w-full flex-col self-center", ) @@ -97,7 +97,7 @@ def docs_left_sidebar( return rx.box( content, class_name=ui.cn( - "sticky left-0 z-10 hidden w-[19.5rem] shrink-0 border-r border-secondary-4 before:absolute before:bottom-0 before:right-0 before:top-0 before:-z-10 before:w-[100vw] before:bg-white-1 lg:block", + "docs-left-sidebar sticky left-0 z-10 hidden w-[19.5rem] shrink-0 border-r border-secondary-4 before:absolute before:bottom-0 before:right-0 before:top-0 before:-z-10 before:w-[100vw] before:bg-white-1 lg:block", ( rx.cond( HostingBannerState.is_banner_visible, @@ -131,42 +131,17 @@ def docs_sidebar_leaf( """ return rx.el.li( rx.link( - rx.cond( - active, - rx.el.div( - class_name="absolute left-0 top-1/2 -translate-y-1/2 w-full h-8 rounded-lg bg-secondary-3 z-[-1]", - ), - rx.fragment(), - ), rx.flex( - rx.cond( - active, - rx.el.div( - class_name="pointer-events-none absolute -bottom-1 -top-1 left-0 w-px bg-primary-10", - ), - rx.fragment(), - ), rx.text( title, - class_name=rx.cond( - active, - "m-0 pl-4 text-sm font-[525] text-primary-10 transition-color", - "m-0 w-full text-sm font-[525] text-secondary-11 transition-color hover:text-secondary-12", - ), - ), - class_name=rx.cond( - active, - f"relative {guide_margin_class} flex h-8 max-w-[14rem] items-center", - "relative flex h-8 items-center pl-4", + class_name="m-0 w-full text-sm", ), + class_name="relative flex items-center px-3", ), href=href, underline="none", - class_name=rx.cond( - active, - "relative block w-full", - f"block w-full {guide_margin_class}", - ), + aria_current=rx.cond(active, "page", "false"), + class_name=f"docs-sidebar-leaf relative block {guide_margin_class}", ), class_name="relative m-0 w-full list-none p-0 !overflow-visible", ) @@ -197,7 +172,7 @@ def docs_sidebar_section( ), underline="none", href=href, - class_name="mb-2 ml-[2.5rem] flex h-8 items-center justify-start", + class_name="docs-sidebar-section-label mb-2 ml-[2.5rem] flex h-8 items-center justify-start", ), rx.el.ul( *( @@ -241,7 +216,7 @@ def docs_sidebar_category( rx.cond( active, rx.el.div( - class_name="absolute left-0 top-1/2 -translate-y-1/2 w-full h-8 rounded-lg bg-secondary-3 z-[-1]", + class_name="docs-sidebar-selection absolute left-0 top-1/2 -translate-y-1/2 w-full h-8 rounded-lg bg-secondary-3 z-[-1]", ), rx.fragment(), ), @@ -255,7 +230,8 @@ def docs_sidebar_category( ), href=href, underline="none", - class_name="block w-full relative no-underline", + class_name="docs-sidebar-category block w-full relative no-underline", + aria_current=rx.cond(active, "true", "false"), custom_attrs={"aria-label": f"Navigate to {name}"}, ), class_name="m-0 p-0 w-full relative list-none", @@ -279,28 +255,23 @@ def docs_sidebar_group( Returns: Collapsible documentation group row. """ - has_icon = icon is not None - guide_left = "left-[3rem]" if has_icon else "left-[2.5rem]" return rx.el.li( rx.el.details( rx.el.summary( - rx.icon(tag=icon, size=16, class_name="mr-4") + rx.icon(tag=icon, size=14, class_name="mr-2.5 shrink-0") if icon is not None else rx.fragment(), rx.text(title, class_name="m-0 text-sm font-[525]"), rx.box(class_name="flex-grow"), ui.icon( "ArrowDown01Icon", - class_name="size-4 group-open/details:rotate-180 transition-transform", + class_name="docs-sidebar-chevron size-3 shrink-0 group-open/details:rotate-180 transition-transform", ), - class_name="!px-0 m-0 flex items-center justify-start !ml-[2.5rem] !bg-transparent !hover:bg-transparent !py-1 !pr-0 w-[calc(100%-2.5rem)] !text-secondary-11 hover:!text-secondary-12 transition-color group xl:max-w-[14rem] cursor-pointer list-none [&::-webkit-details-marker]:hidden [&::marker]:hidden", + class_name="docs-sidebar-group-trigger m-0 flex items-center justify-start cursor-pointer list-none [&::-webkit-details-marker]:hidden [&::marker]:hidden", ), rx.el.ul( - rx.el.li( - class_name=f"m-0 p-0 absolute {guide_left} top-0 bottom-0 w-px bg-secondary-4 z-[-1] pointer-events-none !rounded-none list-none", - ), *children, - class_name="!my-1 p-0 flex flex-col items-start gap-1 list-none !bg-transparent !rounded-none !shadow-none relative", + class_name="docs-sidebar-group-items my-1 p-0 flex flex-col items-start gap-0 list-none !bg-transparent !rounded-none !shadow-none relative", ), open=open_, class_name="group/details m-0 p-0 w-full !bg-transparent border-none", @@ -343,6 +314,7 @@ def _feedback_choice_button( ), ), aria_label=label, + aria_pressed=active, on_click=DocsFeedbackState.set_score(score), ) @@ -363,6 +335,7 @@ def _feedback_thumb_card(score: int, icon: str, label: str) -> rx.Component: label, type="button", on_click=DocsFeedbackState.set_score(score), + aria_pressed=DocsFeedbackState.score == score, class_name=rx.cond( DocsFeedbackState.score == score, "flex h-9 items-center justify-center gap-2 rounded-md border border-primary-6 bg-primary-3 px-3 text-sm font-medium text-primary-11 transition-colors", @@ -415,7 +388,7 @@ def _feedback_content() -> rx.Component: ), class_name="flex flex-col gap-4 w-full", ), - class_name="p-2", + class_name="docs-feedback-form p-2", ) @@ -425,7 +398,7 @@ def docs_feedback_button() -> rx.Component: Returns: Feedback popover trigger. """ - shared_class = "flex w-full cursor-pointer flex-row items-center justify-center gap-2 whitespace-nowrap border px-3 py-0.5 font-small transition-colors" + shared_class = "docs-feedback-choice flex w-full cursor-pointer flex-row items-center justify-center gap-2 whitespace-nowrap border px-3 py-0.5 font-small transition-colors" return ui.popover.root( rx.el.div( ui.popover.trigger( @@ -499,7 +472,7 @@ def docs_right_sidebar( rx.el.p( rx.icon("align-left", size=14, class_name="text-secondary-12"), "On This Page", - class_name="flex h-8 items-center justify-start gap-1.5 text-sm font-[525] text-secondary-12", + class_name="docs-toc-label flex h-8 items-center justify-start gap-1.5 text-sm font-[525] text-secondary-12", ), rx.el.ul( *( @@ -537,7 +510,7 @@ def docs_right_sidebar( ), ), ), - class_name="sticky top-0 hidden h-screen w-[240px] shrink-0 2xl:block", + class_name="docs-right-sidebar sticky top-0 hidden h-screen w-[240px] shrink-0 2xl:block", ) @@ -564,15 +537,15 @@ def docs_footer_shell( rx.box( feedback, actions, - class_name="flex w-full flex-row items-center justify-center border-y-0 border-secondary-4 pb-6 pt-0 lg:justify-between lg:border-y lg:pb-8 lg:pt-8", + class_name="docs-footer-feedback-row flex w-full flex-row items-center justify-center border-y-0 border-secondary-4 pb-6 pt-0 lg:justify-between lg:border-y lg:pb-8 lg:pt-8", ), rx.box( link_columns, controls, copyright_status, - class_name="flex w-full flex-col justify-between gap-10 py-6 lg:py-8", + class_name="docs-footer-directory flex w-full flex-col justify-between gap-10 py-6 lg:py-8", ), - class_name="flex w-full max-w-full flex-col lg:max-w-none", + class_name="docs-page-footer flex w-full max-w-full flex-col lg:max-w-none", ) @@ -593,7 +566,7 @@ def _docs_footer_link( Styled footer link. """ class_name = ( - "font-small text-secondary-9 hover:!text-secondary-11 " + "docs-footer-link font-small text-secondary-9 hover:!text-secondary-11 " "transition-color no-underline" ) if root_site: @@ -620,7 +593,7 @@ def _docs_footer_link_column( class_name="font-semibold text-secondary-12 text-sm tracking-[-0.01313rem]", ), *links, - class_name="flex flex-col gap-4", + class_name="docs-footer-link-column flex flex-col gap-4", ) @@ -636,16 +609,13 @@ def _docs_social_menu_item(icon: str, url: str, name: str) -> rx.Component: Social icon link. """ return rx.el.elements.a( - button( - get_icon(icon, class_name="shrink-0"), - variant="ghost", - size="icon-sm", - class_name="text-secondary-11", - native_button=False, - ), + get_icon(icon, class_name="size-4 shrink-0"), href=url, custom_attrs={"aria-label": f"Social link for {name}"}, + title=f"{name} (opens in a new tab)", target="_blank", + rel="noopener noreferrer", + class_name="docs-footer-social-link flex size-7 items-center justify-center rounded-full text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-foreground", ) @@ -657,11 +627,10 @@ def _docs_social_menu() -> rx.Component: """ return rx.box( _docs_social_menu_item("twitter_footer", TWITTER_URL, "Twitter"), - _docs_social_menu_item("github_navbar", GITHUB_URL, "Github"), - _docs_social_menu_item("discord_navbar", DISCORD_URL, "Discord"), + _docs_social_menu_item("github_navbar", GITHUB_URL, "GitHub"), _docs_social_menu_item("linkedin_footer", LINKEDIN_URL, "LinkedIn"), - _docs_social_menu_item("forum_footer", FORUM_URL, "Forum"), - class_name="flex flex-row items-center gap-2", + _docs_social_menu_item("discord_navbar", DISCORD_URL, "Discord"), + class_name="flex flex-wrap items-center gap-3", ) @@ -682,7 +651,7 @@ def _docs_footer_action( text, href=href, underline="none", - class_name="lg:flex hidden flex-row justify-center items-center gap-2 lg:border-secondary-5 bg-secondary-3 lg:bg-secondary-1 hover:bg-secondary-3 shadow-none lg:shadow-large px-3 py-0.5 lg:border lg:border-solid border-none rounded-lg lg:rounded-full w-auto font-small font-small text-secondary-9 !hover:text-secondary-11 hover:!text-secondary-9 truncate whitespace-nowrap transition-bg transition-color cursor-pointer", + class_name="docs-footer-action lg:flex hidden flex-row justify-center items-center gap-2 lg:border-secondary-5 bg-secondary-3 lg:bg-secondary-1 hover:bg-secondary-3 shadow-none lg:shadow-large px-3 py-0.5 lg:border lg:border-solid border-none rounded-lg lg:rounded-full w-auto font-small text-secondary-9 !hover:text-secondary-11 hover:!text-secondary-9 truncate whitespace-nowrap transition-bg transition-color cursor-pointer", ) @@ -708,12 +677,12 @@ def _docs_page_footer_content( class_name="whitespace-nowrap font-small text-secondary-11 lg:text-secondary-9", ), docs_feedback_button(), - class_name="flex w-full flex-col items-center gap-3 rounded-lg bg-secondary-3 p-4 lg:w-auto lg:flex-row lg:gap-4 lg:bg-transparent lg:p-0", + class_name="docs-footer-feedback flex w-full flex-col items-center gap-3 rounded-lg bg-secondary-3 p-4 lg:w-auto lg:flex-row lg:gap-4 lg:bg-transparent lg:p-0", ) actions = rx.box( _docs_footer_action("Raise an issue", issue_href), _docs_footer_action("Edit this page", edit_href), - class_name="hidden w-auto flex-row items-center gap-2 lg:flex", + class_name="docs-footer-actions hidden w-auto flex-row items-center gap-2 lg:flex", ) docs_prefix = "https://reflex.dev/docs" if external_docs_links else "" root_prefix = "https://reflex.dev" if external_docs_links else "" @@ -761,12 +730,12 @@ def _docs_page_footer_content( _docs_footer_link("Roadmap", ROADMAP_URL), _docs_footer_link("Forum", FORUM_URL), ), - class_name="flex w-full flex-wrap justify-between gap-12", + class_name="docs-footer-links flex w-full flex-wrap justify-between gap-12", ) controls = rx.box( rx.box(dark_mode_toggle(), class_name="[&>div]:!ml-0"), _docs_social_menu(), - class_name="flex w-full flex-row items-end justify-between gap-6", + class_name="docs-footer-controls flex w-full flex-row items-end justify-between gap-6", ) copyright_status = rx.el.div( rx.text( @@ -774,7 +743,7 @@ def _docs_page_footer_content( class_name="font-small text-secondary-9", ), server_status(StatusState.status), - class_name="flex w-full flex-row items-center justify-between gap-4", + class_name="docs-footer-copyright flex w-full flex-row items-center justify-between gap-4", ) return docs_footer_shell( feedback, diff --git a/packages/reflex-site-shared/src/reflex_site_shared/constants.py b/packages/reflex-site-shared/src/reflex_site_shared/constants.py index 273d4da0d52..b31cab8bfe0 100644 --- a/packages/reflex-site-shared/src/reflex_site_shared/constants.py +++ b/packages/reflex-site-shared/src/reflex_site_shared/constants.py @@ -5,10 +5,10 @@ CHANGELOG_URL = "https://reflex.dev/docs/changelog/" CONTRIBUTING_URL = "https://github.com/reflex-dev/reflex/blob/main/CONTRIBUTING.md" DISCUSSIONS_URL = "https://github.com/orgs/reflex-dev/discussions" -GITHUB_STARS = 28000 +GITHUB_STARS = 28885 GITHUB_URL = "https://github.com/reflex-dev/reflex" GITHUB_ORG_URL = "https://github.com/reflex-dev" -XY_GITHUB_STARS = 1400 +XY_GITHUB_STARS = 1841 XY_GITHUB_URL = "https://github.com/reflex-dev/xy" JOBS_BOARD_URL = "https://www.ycombinator.com/companies/reflex/jobs" REFLEX_ASSETS_CDN = "https://web.reflex-assets.dev/" diff --git a/packages/reflex-site-shared/src/reflex_site_shared/docs/markdown.py b/packages/reflex-site-shared/src/reflex_site_shared/docs/markdown.py index 95bebe3918a..13b8d6b0d98 100644 --- a/packages/reflex-site-shared/src/reflex_site_shared/docs/markdown.py +++ b/packages/reflex-site-shared/src/reflex_site_shared/docs/markdown.py @@ -272,7 +272,7 @@ def text_block(self, block: TextBlock) -> rx.Component: return text_comp(text=children[0]) return rx.text( *children, - class_name="font-[475] text-secondary-11 mb-4 leading-7", + class_name="docs-body-text font-[475] text-secondary-11 mb-4 leading-7", ) def code_block(self, block: CodeBlock) -> rx.Component: @@ -331,7 +331,7 @@ def _list_item_from_spans(spans: tuple[Span, ...]) -> rx.Component: return list_comp(text=_spans_to_plaintext(spans)) return rx.list_item( *_render_spans(spans), - class_name="font-[475] text-secondary-11 mb-4", + class_name="docs-body-text font-[475] text-secondary-11 mb-4", ) def transform_list_item(self, item: ListItem) -> rx.Component: @@ -377,7 +377,7 @@ def table(self, block: TableBlock) -> rx.Component: rx.table.body(*rows), variant="surface", size="1", - class_name="w-full border border-secondary-4 mb-4", + class_name="docs-table w-full border border-secondary-4 mb-4", ) def transform_table_row(self, row: TableRow) -> rx.Component: @@ -500,7 +500,7 @@ def _render_demo_only(self, content: str, flags: set[str]) -> rx.Component: ) raise - return rx.box(comp, margin_bottom="1em", id=comp_id) + return rx.box(comp, margin_bottom="1em", id=comp_id, data_docs_example=True) def _render_children(self, blocks: tuple[Block, ...]) -> rx.Component: """Render a sequence of parsed blocks into a single component.""" @@ -567,7 +567,7 @@ def foreground_color() -> str: def title_comp() -> rx.Component: return rx.box( *_render_spans(title_spans), - class_name="font-[475]", + class_name="docs-callout-title font-[475]", color=foreground_color(), ) @@ -610,7 +610,7 @@ def title_comp() -> rx.Component: trigger.append( rx.box( *spans, - class_name="font-[475]", + class_name="docs-callout-title font-[475]", color=foreground_color(), ), ) @@ -628,6 +628,7 @@ def title_comp() -> rx.Component: margin_bottom="16px", margin_top="16px", width="100%", + class_name="docs-callout", ) def _render_video(self, block: DirectiveBlock) -> rx.Component: diff --git a/packages/reflex-site-shared/src/reflex_site_shared/plugins.py b/packages/reflex-site-shared/src/reflex_site_shared/plugins.py index 5c57ce5a259..701d80d3744 100644 --- a/packages/reflex-site-shared/src/reflex_site_shared/plugins.py +++ b/packages/reflex-site-shared/src/reflex_site_shared/plugins.py @@ -120,9 +120,12 @@ class SharedSiteStylesPlugin(Plugin): Consumers install the referenced Fontsource packages through ``rx.Config.frontend_packages``. Existing sites with their own font CSS can disable this while retaining the shared theme CSS. + editorial: Use the neutral marketing palette and capsule buttons, + with an adaptive dark palette for documentation sites. """ include_fonts: bool = True + editorial: bool = False def get_static_assets(self, **context: Any) -> tuple[tuple[Path, str], ...]: """Emit package-owned CSS into the generated web styles directory. @@ -136,6 +139,7 @@ def get_static_assets(self, **context: Any) -> tuple[tuple[Path, str], ...]: stylesheets = ( *_BASE_STYLESHEETS, *((_FONT_STYLESHEET,) if self.include_fonts else ()), + *(("editorial.css",) if self.editorial else ()), ) stylesheet_assets = tuple( ( @@ -147,7 +151,11 @@ def get_static_assets(self, **context: Any) -> tuple[tuple[Path, str], ...]: public_assets = tuple( ( Path("public") / asset, - (_SOURCE_DIR / asset).read_text(encoding="utf-8"), + ( + _SOURCE_DIR / "editorial" / "GradientButton.tsx" + if self.editorial and asset == "components/GradientButton.tsx" + else _SOURCE_DIR / asset + ).read_text(encoding="utf-8"), ) for asset in _PUBLIC_ASSETS ) @@ -165,6 +173,7 @@ def get_stylesheet_paths(self, **context: Any) -> tuple[str, ...]: shared_stylesheets = ( *_BASE_STYLESHEETS, *((_FONT_STYLESHEET,) if self.include_fonts else ()), + *(("editorial.css",) if self.editorial else ()), ) return tuple(f"./reflex-site-shared/{name}" for name in shared_stylesheets) diff --git a/packages/reflex-site-shared/src/reflex_site_shared/styles/assets/components/AlgoliaSearch.tsx b/packages/reflex-site-shared/src/reflex_site_shared/styles/assets/components/AlgoliaSearch.tsx index f26fb9e1720..b56cd003ad6 100644 --- a/packages/reflex-site-shared/src/reflex_site_shared/styles/assets/components/AlgoliaSearch.tsx +++ b/packages/reflex-site-shared/src/reflex_site_shared/styles/assets/components/AlgoliaSearch.tsx @@ -540,6 +540,8 @@ export function AlgoliaSearch() { aria-expanded={isOpen} aria-haspopup="dialog" aria-label="Search Reflex" + aria-keyshortcuts="Meta+K Control+K" + title={`Search (${modifierKey}K)`} className="ReflexSearch-button" onClick={openSearch} ref={buttonRef} diff --git a/packages/reflex-site-shared/src/reflex_site_shared/styles/assets/editorial.css b/packages/reflex-site-shared/src/reflex_site_shared/styles/assets/editorial.css new file mode 100644 index 00000000000..3f602e976b4 --- /dev/null +++ b/packages/reflex-site-shared/src/reflex_site_shared/styles/assets/editorial.css @@ -0,0 +1,770 @@ +/* The public marketing site uses one neutral color and spacing system. */ +:root, .light, .light-theme, .dark, .dark-theme { + --c-slate-1: #ffffff; + --c-slate-2: #f6f6f6; + --c-slate-3: #eeeeee; + --c-slate-4: #e8e8e8; + --c-slate-5: #e2e2e2; + --c-slate-6: #d9d9d9; + --c-slate-7: #c7c7c7; + --c-slate-8: #b3b3b3; + --c-slate-9: #8c8c8c; + --c-slate-10: #767676; + --c-slate-11: #595959; + --c-slate-12: #181818; + --slate-1: var(--c-slate-1); + --slate-2: var(--c-slate-2); + --slate-3: var(--c-slate-3); + --slate-4: var(--c-slate-4); + --slate-5: var(--c-slate-5); + --slate-6: var(--c-slate-6); + --slate-7: var(--c-slate-7); + --slate-8: var(--c-slate-8); + --slate-9: var(--c-slate-9); + --slate-10: var(--c-slate-10); + --slate-11: var(--c-slate-11); + --slate-12: var(--c-slate-12); + --secondary-1: var(--c-slate-1); + --secondary-2: var(--c-slate-2); + --secondary-3: var(--c-slate-3); + --secondary-4: var(--c-slate-4); + --secondary-5: var(--c-slate-5); + --secondary-6: var(--c-slate-6); + --secondary-7: var(--c-slate-7); + --secondary-8: var(--c-slate-8); + --secondary-9: var(--c-slate-9); + --secondary-10: var(--c-slate-10); + --secondary-11: var(--c-slate-11); + --secondary-12: var(--c-slate-12); + --secondary-a1: color-mix(in srgb, var(--foreground) 2%, transparent); + --secondary-a2: color-mix(in srgb, var(--foreground) 4%, transparent); + --secondary-a3: color-mix(in srgb, var(--foreground) 6%, transparent); + --secondary-a4: color-mix(in srgb, var(--foreground) 9%, transparent); + --secondary-a5: color-mix(in srgb, var(--foreground) 12%, transparent); + --secondary-a6: color-mix(in srgb, var(--foreground) 16%, transparent); + --secondary-a7: color-mix(in srgb, var(--foreground) 22%, transparent); + --secondary-a8: color-mix(in srgb, var(--foreground) 32%, transparent); + --secondary-a9: color-mix(in srgb, var(--foreground) 48%, transparent); + --secondary-a10: color-mix(in srgb, var(--foreground) 56%, transparent); + --secondary-a11: color-mix(in srgb, var(--foreground) 72%, transparent); + --secondary-a12: color-mix(in srgb, var(--foreground) 92%, transparent); + --primary-1: var(--c-slate-1); + --primary-2: var(--c-slate-2); + --primary-3: var(--c-slate-3); + --primary-4: var(--c-slate-4); + --primary-5: var(--c-slate-5); + --primary-6: var(--c-slate-6); + /* Internal inputs retain their flush focus treatment, using neutral ink. */ + --primary-a6: var(--secondary-a6); + --primary-7: var(--c-slate-7); + --primary-8: var(--c-slate-8); + --primary-9: var(--c-slate-12); + --primary-10: var(--primary-hover); + --primary-11: var(--c-slate-12); + --primary-12: var(--c-slate-12); + --primary-contrast: #ffffff; + --color-gray-soft: var(--c-slate-2); + --color-gray-light: var(--c-slate-5); + --color-gray-accent: var(--c-slate-9); + --color-gray-strong: var(--c-slate-11); + --color-gray-black: var(--c-slate-12); +} + + +:root, .light, .light-theme { + --editorial-primary-hover: #303030; + --editorial-border-subtle: #ebebeb; + --c-white-1: #ffffff; +} +.dark, .dark-theme { + --c-slate-1: #181818; + --c-slate-2: #202020; + --c-slate-3: #292929; + --c-slate-4: #333333; + --c-slate-5: #3e3e3e; + --c-slate-6: #4a4a4a; + --c-slate-7: #5c5c5c; + --c-slate-8: #707070; + --c-slate-9: #8c8c8c; + --c-slate-10: #a3a3a3; + --c-slate-11: #bdbdbd; + --c-slate-12: #f5f5f5; + --c-white-1: #181818; + --primary-contrast: #181818; + --editorial-primary-hover: #dedede; + --editorial-border-subtle: #303030; +} +/* Semantic theme. Use surface / foreground pairs in components. + Primitive color ramps remain available for artwork and data visualization. */ +:root, .light, .light-theme, .dark, .dark-theme { + --background: var(--c-slate-1); + --foreground: var(--c-slate-12); + --card: var(--c-slate-1); + --card-foreground: var(--c-slate-12); + --primary: var(--c-slate-12); + --primary-foreground: var(--c-slate-1); + --primary-hover: var(--editorial-primary-hover); + --muted: var(--c-slate-2); + --muted-foreground: var(--c-slate-11); + --subtle: #fafafa; + --subtle-foreground: var(--c-slate-10); + --inset: var(--c-slate-3); + --accent: var(--c-slate-2); + --accent-hover: var(--border-subtle); + /* Default boundaries, quiet separators, and hover/emphasis respectively. + Focus has its own visible outline; a hover border is not a focus indicator. */ + --border: var(--c-slate-5); + --border-subtle: var(--editorial-border-subtle); + --border-strong: var(--c-slate-7); + --ring: var(--c-slate-10); + --destructive: #ba2623; + --success: #f4fbf7; + --success-foreground: #208368; + --warning: #fffbeb; + --warning-foreground: #95611f; +} + +@theme inline { + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + --color-primary-hover: var(--primary-hover); + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + --color-subtle: var(--subtle); + --color-subtle-foreground: var(--subtle-foreground); + --color-inset: var(--inset); + --color-accent: var(--accent); + --color-accent-hover: var(--accent-hover); + --color-border: var(--border); + --color-border-subtle: var(--border-subtle); + --color-border-strong: var(--border-strong); + --color-ring: var(--ring); + --color-destructive: var(--destructive); + /* Shape scale at a 16px root: 4, 6, 8, 10, 12, 14, 16, 24px. + Role tokens prevent equivalent controls from drifting between pages. + See /design.md before adding a new radius or overriding a role. */ + --radius-xs: 0.25rem; + --radius-sm: 0.375rem; + --radius-md: var(--radius); + --radius-lg: 0.625rem; + --radius-xl: 0.75rem; + --radius-2xl: 0.875rem; + --radius-3xl: 1rem; + --radius-4xl: 1.5rem; + --radius-compact: var(--radius-xs); + --radius-control: 9999px; + --radius-card: var(--radius-2xl); + --radius-panel: var(--radius-3xl); +} + + +@theme { + --color-gray-white: #ffffff; + --color-red-strong: #de282f; + --color-red-deep: #ba2623; + --font-weight-normal: 425; + --font-weight-book: 450; +} +:root { + --docs-header-height: 4rem; +} +:root:has([data-docs-announcement]) { + --docs-header-height: 6.5rem; +} +.docs-navbar { left: 0; } +.docs-navbar header { + height: 4rem; + background: var(--background); + border-bottom: 1px solid var(--border-subtle); + box-shadow: none; +} +.docs-left-sidebar { + top: var(--docs-header-height); + height: calc(100vh - var(--docs-header-height)); +} +.docs-breadcrumb { + margin-top: calc(var(--docs-header-height) + 2rem); +} +.docs-hero { + padding-top: calc(var(--docs-header-height) + clamp(3rem, 6vw, 6rem)); +} +.docs-hero-art { + top: calc(var(--docs-header-height) + 1rem); +} +@media (min-width: 1024px) { + .docs-hero { min-height: calc(var(--docs-header-height) + 433px); } +} +.docs-prose :is(h1, h2, h3, h4, h5, h6):not([data-docs-example] *) { + font-family: var(--font-instrument-sans); + font-weight: 450; + letter-spacing: -0.03em; +} +.docs-prose :is(strong, b):not([data-docs-example] *) { font-weight: 600; } +.docs-prose .docs-body-text:not([data-docs-example] *), +.docs-prose .docs-text-link:not([data-docs-example] *) { font-weight: 425; } +.docs-prose .docs-introduction { + max-width: 44rem; + line-height: 1.75rem; +} +/* These are decorative SVG thumbnails, separate from live component examples. */ +.docs-prose .docs-preview-card img { filter: grayscale(1); } +.docs-prose .font-small:not([data-docs-example] *) { + font-size: 0.875rem; + font-weight: 425; + line-height: 1.5; + letter-spacing: normal; +} +.docs-prose .docs-text-link:not([data-docs-example] *) { + color: var(--foreground); + text-decoration-color: var(--border-strong); + text-underline-offset: 3px; +} +.docs-prose .docs-text-link:hover { text-decoration-color: var(--foreground); } +.docs-prose .code-style:not([data-docs-example] *) { + color: var(--foreground); + background: var(--muted); + border-color: var(--border-subtle); +} +.docs-prose .docs-definition:not([data-docs-example] *) { + color: var(--muted-foreground); + background: var(--muted); + border-color: var(--border-subtle); + border-radius: var(--radius-card); +} +.docs-prose .docs-definition h3:not([data-docs-example] *) { color: var(--foreground); } +.docs-prose .docs-callout:not([data-docs-example] *) { border-radius: var(--radius-card); } +.docs-prose .docs-callout-title:not([data-docs-example] *) { font-weight: 450; } +.docs-prose .docs-table:not([data-docs-example] *) { + border: 1px solid var(--border-subtle); + border-radius: var(--radius-card); + background: var(--background); + box-shadow: none; + overflow-x: auto; +} +.docs-prose table:not([data-docs-example] *) { + --table-row-background-color: transparent; + --table-row-box-shadow: inset 0 -1px var(--border-subtle); + border-color: var(--border-subtle); + font-size: 0.875rem; + font-weight: 425; +} +.docs-prose table :is(th, td):not([data-docs-example] *) { + border-color: var(--border-subtle); + font-size: 0.875rem; + font-weight: 425; + line-height: 1.5; + letter-spacing: normal; +} +.docs-prose table th:not([data-docs-example] *) { font-weight: 500; } +.docs-prose table :is(thead, tbody):not([data-docs-example] *) { + background: var(--background); +} +.docs-prose table thead:not([data-docs-example] *) { background: var(--muted); } +.docs-prose table tr:not([data-docs-example] *) { color: var(--foreground); } +.docs-prose .docs-demo-build-action { + color: var(--foreground); + font-weight: 450; + border-radius: var(--radius-control); +} +.docs-prose .docs-demo-build-action > svg:first-child path { fill: var(--background); } +.docs-prose :is(.docs-text-link, .docs-demo-build-action):focus-visible { + outline: 2px solid var(--ring); + outline-offset: 3px; +} +@layer components { + .docs-prose .pill-tab-list { + border: 1px solid var(--border) !important; + border-radius: var(--radius-control) !important; + background: var(--background) !important; + padding: 3px !important; + } + .docs-prose .pill-tab { + font-size: 0.875rem !important; + font-weight: 450 !important; + border-radius: var(--radius-control) !important; + color: var(--muted-foreground) !important; + } + .docs-prose .pill-tab[data-state='active'] { + background: var(--foreground) !important; + color: var(--background) !important; + box-shadow: none !important; + } + .docs-prose .pill-tab[data-state='inactive']:hover { + background: var(--muted) !important; + color: var(--foreground) !important; + } + .docs-prose :is(.pill-tab, .doc-demo-tab):focus-visible { + outline: 2px solid var(--ring) !important; + outline-offset: -2px !important; + } +} +.docs-prose :is(h1, h2, h3, h4)[id] { + scroll-margin-top: calc(var(--docs-header-height) + 2rem); +} +@media (max-width: 639px) { + :root:has([data-docs-announcement]) { --docs-header-height: 7.5rem; } + .docs-navbar header { padding-inline: 1rem; } +} + +.docs-navbar header > div { gap: 2rem; } +:root .docs-navbar .ReflexSearch-root { width: auto; } +:root .docs-navbar .ReflexSearch-button { + width: 2.25rem; + min-width: 0; + height: 2.25rem; + padding: 0; + justify-content: center; + border-radius: var(--radius-control); + border: 0; + background: transparent; + color: var(--muted-foreground); + box-shadow: none; +} +:root .docs-navbar .ReflexSearch-button:hover { + background: var(--muted); + color: var(--foreground); +} +:root .docs-navbar .ReflexSearch-button:focus-visible { + outline: 2px solid var(--ring); + outline-offset: 2px; +} +:root .docs-navbar .ReflexSearch-button > svg { + width: 1rem; + height: 1rem; + margin: 0; +} +:root .docs-navbar :is(.ReflexSearch-buttonText, .ReflexSearch-shortcut) { display: none; } +@media (min-width: 1024px) { + :root .docs-navbar .ReflexSearch-root { width: 100%; } + :root .docs-navbar .ReflexSearch-button { + width: 100%; + justify-content: flex-start; + gap: 0.5rem; + padding-inline: 0.625rem; + border: 1px solid var(--border-subtle); + border-radius: var(--radius-compact); + } + :root .docs-navbar .ReflexSearch-buttonText { + display: inline; + font-size: 0.875rem; + font-weight: 425; + } + :root .docs-navbar .ReflexSearch-shortcut { + display: inline-flex; + background: var(--muted); + color: var(--muted-foreground); + font-size: 0.75rem; + font-weight: 450; + } +} +:root .ReflexSearch-overlay .ReflexSearch-dialog { + border-radius: var(--radius-panel); + border-color: var(--border); + background: var(--background); +} +/* Portaled forms need the same theme while component examples stay independent. */ +[data-slot="dialog-popup"]:has(#docs-booking_user_email), +[data-slot="popover-popup"]:has(.docs-feedback-form) { + border-color: var(--border); + border-radius: var(--radius-panel); + color: var(--foreground); + font-family: var(--font-instrument-sans); +} +[data-slot="dialog-popup"]:has(#docs-booking_user_email) { + max-width: min(28rem, calc(100vw - 2rem)); + max-height: calc(100dvh - 3rem); + overflow-y: auto; + margin-top: 0; +} +[data-slot="dialog-popup"]:has(#docs-booking_user_email) h1 { + font-weight: 450; + letter-spacing: -0.03em; +} +[data-slot="dialog-popup"]:has(#docs-booking_user_email) button, +.docs-feedback-form button { + border-radius: var(--radius-control); + font-weight: 450; +} +.docs-feedback-form button[aria-pressed="true"] { + background: var(--foreground); + color: var(--background); + border-color: var(--foreground); +} +.docs-right-sidebar > nav { + margin-top: calc(var(--docs-header-height) + 2rem); +} + +.docs-sidebar-section-label h2, +.docs-toc-label { + font-family: var(--font-instrument-sans); + font-size: 0.75rem; + font-weight: 450; + line-height: 1rem; + letter-spacing: normal; + text-transform: none; + color: var(--muted-foreground); +} +.docs-sidebar-category h3, +.docs-sidebar-group-trigger > p, +.docs-sidebar-leaf p, +.docs-right-sidebar #toc-navigation a { + font-family: var(--font-instrument-sans); + font-size: 0.875rem; + font-weight: 425; + line-height: 1.25rem; + letter-spacing: normal; + color: var(--muted-foreground); +} +.docs-sidebar-category h3, +.docs-sidebar-group-trigger > p { font-weight: 450; } +.docs-sidebar-leaf { + width: auto; + margin-right: 0.5rem; + border-radius: var(--radius-compact); + transition: background-color 150ms ease, color 150ms ease; +} +.docs-sidebar-leaf > .flex { + min-height: 2rem; + height: auto; + padding-block: 0.25rem; +} +.docs-sidebar-group-trigger { + min-height: 2rem; + width: calc(100% - 2.5rem); + margin-left: 2rem; + padding: 0.375rem 0.5rem; + gap: 0.25rem; + border-radius: var(--radius-compact); + background: transparent; + transition: background-color 150ms ease, color 150ms ease; +} +.docs-sidebar-group-trigger:hover, +.docs-sidebar-leaf:hover, +.docs-sidebar-leaf[aria-current="page"] { + background: var(--muted); +} +.docs-sidebar-group-trigger > .docs-sidebar-chevron { + width: 0.75rem; + height: 0.75rem; + opacity: 0.65; +} +details[open] > .docs-sidebar-group-trigger > p { + color: var(--foreground); +} +.docs-sidebar-selection { + top: 0; + bottom: 0; + height: auto; + translate: none; + border-radius: var(--radius-compact); + background: var(--muted); +} +.docs-sidebar-category[aria-current="true"] h3, +.docs-sidebar-leaf[aria-current="page"] p, +.docs-right-sidebar #toc-navigation a.text-primary-9 { + color: var(--foreground); + font-weight: 450; +} +.docs-sidebar-category:hover h3, +.docs-sidebar-leaf:hover p, +.docs-sidebar-group-trigger:hover > p, +.docs-sidebar-section-label:hover h2, +.docs-right-sidebar #toc-navigation a:hover { color: var(--foreground); } +.docs-sidebar-category svg, +.docs-sidebar-group-trigger svg, +.docs-toc-label svg { + color: var(--muted-foreground); + stroke-width: 1.5; +} +.docs-sidebar-category[aria-current="true"] svg { color: var(--foreground); } +:is(.docs-sidebar-category, .docs-sidebar-leaf, .docs-sidebar-section-label, + .docs-sidebar-group-trigger, .docs-right-sidebar a):focus-visible { + outline: 2px solid var(--ring); + outline-offset: -2px; + border-radius: var(--radius-compact); +} + +/* Article footers share the landing page's type, surfaces, and controls. */ +.docs-page-footer { + font-family: var(--font-instrument-sans); + color: var(--foreground); +} +.docs-footer-feedback-row { + flex-wrap: wrap; + justify-content: space-between; + gap: 1.25rem; + border-block: 1px solid var(--border-subtle); + padding-block: 1.5rem; +} +.docs-footer-feedback { + width: auto; + flex-direction: row; + flex-wrap: wrap; + gap: 1rem; + padding: 0; + background: transparent; +} +.docs-footer-feedback > p { + color: var(--muted-foreground); + font-size: 0.875rem; + font-weight: 425; + line-height: 1.5; + letter-spacing: normal; +} +.docs-footer-feedback > div { width: auto; } +.docs-footer-actions { + display: flex; + flex-wrap: wrap; +} +.docs-page-footer :is(.docs-feedback-choice, .docs-footer-action) { + display: flex; + height: 2.25rem; + padding-inline: 0.875rem; + border: 1px solid var(--border); + background: var(--background); + color: var(--muted-foreground); + box-shadow: none; + font-size: 0.875rem; + font-weight: 450; + line-height: 1.5; + letter-spacing: normal; +} +.docs-page-footer .docs-footer-action { border-radius: var(--radius-control); } +.docs-page-footer .docs-feedback-choice:first-child { border-right: 0; } +.docs-page-footer .docs-feedback-choice svg { width: 1rem; height: 1rem; } +.docs-page-footer :is(.docs-feedback-choice, .docs-footer-action):hover { + background: var(--accent); + color: var(--foreground) !important; +} +.docs-page-footer .docs-feedback-choice[aria-pressed="true"] { + background: var(--foreground); + color: var(--background) !important; +} +.docs-footer-directory { + gap: 2rem; + padding-block: 2rem; +} +.docs-footer-links { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 2rem; +} +.docs-footer-link-column { gap: 0.5rem; } +.docs-footer-link-column h4 { + margin-bottom: 0.75rem; + color: var(--muted-foreground); + font-size: 0.75rem; + font-weight: 450; + line-height: 1rem; + letter-spacing: normal; +} +.docs-page-footer .docs-footer-link { + width: fit-content; + color: var(--foreground); + font-size: 0.875rem; + font-weight: 450; + line-height: 1.5rem; + letter-spacing: normal; +} +.docs-page-footer .docs-footer-link:hover { color: var(--muted-foreground) !important; } +.docs-footer-controls, +.docs-footer-copyright { + flex-wrap: wrap; + align-items: center; +} +.docs-footer-controls { padding-top: 1rem; } +.docs-footer-copyright > p, +.docs-footer-copyright > a > div { + font-size: 0.75rem; + font-weight: 425; + line-height: 1.5; + letter-spacing: normal; +} +.docs-footer-copyright > p { color: var(--muted-foreground); } +.docs-footer-copyright > a > div { padding-inline: 0; } +:is(.docs-theme-toggle, .docs-page-footer .site-color-mode-toggle) { + padding: 2px; + border: 1px solid var(--border-subtle); + border-radius: 6px; + background: var(--background); + box-shadow: none; +} +:is(.docs-theme-toggle, .docs-page-footer .site-color-mode-toggle) button { + width: 1.75rem; + height: 1.5rem; + border-radius: var(--radius-compact); + color: var(--muted-foreground); + background: transparent; + box-shadow: none; +} +:is(.docs-theme-toggle, .docs-page-footer .site-color-mode-toggle) button:hover { + background: var(--accent); + color: var(--foreground); +} +:is(.docs-theme-toggle, .docs-page-footer .site-color-mode-toggle) button[aria-pressed="true"] { + background: var(--muted); + color: var(--foreground); +} +:is(.docs-theme-toggle, .docs-page-footer .site-color-mode-toggle) svg { + width: 0.875rem; + height: 0.875rem; +} +.docs-page-footer :is(a, button):focus-visible { + outline: 2px solid var(--ring); + outline-offset: 4px; +} +.docs-page-footer .docs-footer-social-link:focus-visible { outline-offset: 2px; } +@media (max-width: 639px) { + .docs-footer-links { grid-template-columns: repeat(2, minmax(0, 1fr)); } +} + +.docs-framework-tab { + display: flex; + flex-direction: column; + padding: 1.25rem 0; + border-bottom: 1px solid var(--border); + cursor: pointer; + background: transparent; +} +.docs-framework-tab:hover { color: var(--muted-foreground); } +.docs-framework-tab[data-active] { border-bottom: 2px solid var(--foreground); } +.docs-framework-tab-description { display: none; } +.docs-framework-tab[data-active] .docs-framework-tab-description { + display: block; + margin-top: 1rem; + padding-bottom: 0.5rem; + color: var(--muted-foreground); + font-size: 0.875rem; + font-weight: 425; + line-height: 1.75; +} +.docs-framework-panel { + --fw-ink: var(--foreground); + --fw-muted: var(--muted-foreground); + --fw-surface: var(--background); + --fw-line: var(--border); + --fw-accent: #547994; + --fw-tint: #edf3f7; + --fw-cloud: url('/docs/framework-diagrams/blue.avif'); + animation: docs-framework-enter 240ms ease-out; +} +.docs-framework-panel[data-ending-style] { display: none; animation: none; } +.docs-framework-panel[data-tone="lavender"] { --fw-accent: #7b6e9d; --fw-tint: #f0edf7; --fw-cloud: url('/docs/framework-diagrams/lavender.avif'); } +.docs-framework-panel[data-tone="peach"] { --fw-accent: #98754f; --fw-tint: #f7f0e7; --fw-cloud: url('/docs/framework-diagrams/peach.avif'); } +.docs-framework-panel[data-tone="mint"] { --fw-accent: #527e72; --fw-tint: #eaf3f0; --fw-cloud: url('/docs/framework-diagrams/mint.avif'); } +.docs-framework-stage { + display: flex; + align-items: center; + justify-content: center; + padding: clamp(0.5rem, 2vw, 2rem); + background: linear-gradient(115deg, rgb(255 255 255 / 78%), rgb(255 255 255 / 55%)), var(--fw-cloud) center / cover; +} +.docs-framework-diagram { width: 100%; max-width: 44rem; } +.docs-framework-diagram svg { overflow: visible; } +.docs-framework-diagram .fw-flow { + stroke-width: 1.25; + stroke-linecap: round; + stroke-linejoin: round; + opacity: 0.6; +} +.dark .docs-framework-panel { --fw-accent: #a0bbce; --fw-tint: #263139; } +.dark .docs-framework-panel[data-tone="lavender"] { --fw-accent: #bdb0df; --fw-tint: #302a3d; } +.dark .docs-framework-panel[data-tone="peach"] { --fw-accent: #d3b58e; --fw-tint: #383025; } +.dark .docs-framework-panel[data-tone="mint"] { --fw-accent: #a0c7bc; --fw-tint: #263931; } +.dark .docs-framework-stage { background: linear-gradient(115deg, rgb(24 24 24 / 90%), rgb(24 24 24 / 78%)), var(--fw-cloud) center / cover; } +@keyframes docs-framework-enter { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: translateY(0); } } +@media (prefers-reduced-motion: reduce) { + .docs-framework-panel { animation: none; } +} + +.docs-ai-card { + --ai-accent: #547994; + --ai-tint: #edf3f7; + --ai-cloud: url('/docs/framework-diagrams/blue.avif'); +} +.docs-ai-card[data-tone="peach"] { --ai-accent: #98754f; --ai-tint: #f7f0e7; --ai-cloud: url('/docs/framework-diagrams/peach.avif'); } +.docs-ai-card[data-tone="mint"] { --ai-accent: #527e72; --ai-tint: #eaf3f0; --ai-cloud: url('/docs/framework-diagrams/mint.avif'); } +.docs-ai-card-art { + display: flex; + align-items: center; + aspect-ratio: 360 / 220; + overflow: hidden; + border-top: 1px solid var(--border); + background: linear-gradient(115deg, rgb(255 255 255 / 78%), rgb(255 255 255 / 55%)), var(--ai-cloud) center / cover; +} +.docs-ai-card:hover .docs-ai-card-title { text-decoration: underline; text-underline-offset: 4px; text-decoration-thickness: 1px; } +.docs-ai-card:hover .rfm-marquee { animation-play-state: paused; } +.docs-ai-integrations { mask-image: linear-gradient(to right, transparent, black 12%, black 88%, transparent); } +.dark .docs-ai-card { --ai-accent: #a0bbce; --ai-tint: #263139; } +.dark .docs-ai-card[data-tone="peach"] { --ai-accent: #d3b58e; --ai-tint: #383025; } +.dark .docs-ai-card[data-tone="mint"] { --ai-accent: #a0c7bc; --ai-tint: #263931; } +.dark .docs-ai-card-art { background: linear-gradient(115deg, rgb(24 24 24 / 90%), rgb(24 24 24 / 78%)), var(--ai-cloud) center / cover; } +@media (prefers-reduced-motion: reduce) { + .docs-ai-integrations .rfm-marquee { animation-play-state: paused; } +} + +.docs-framework-live-connector { + width: 1px; + height: 2rem; + background: var(--fw-accent); + opacity: 0.6; +} +.docs-framework-counter-value { + background: var(--fw-tint); + color: var(--fw-accent); +} + +.docs-cloud-diagram { + --cloud-ink: #527e72; + --cloud-tint: #eaf3f0; + --cloud-line: #99b1a9; + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr) minmax(0, 1fr); + gap: 3rem; + padding: 3rem; + background: linear-gradient(115deg, rgb(255 255 255 / 78%), rgb(255 255 255 / 55%)), url('/docs/framework-diagrams/mint.avif') center / cover; +} +.docs-cloud-guides { position: relative; display: grid; grid-template-rows: repeat(2, minmax(0, 1fr)); gap: 1.5rem; } +.docs-cloud-hub { width: 100%; } +.docs-cloud-icon { color: var(--cloud-ink); } +.docs-cloud-chart-bar { background: var(--cloud-ink); opacity: 0.55; } +.docs-cloud-guide:hover h3 { text-decoration: underline; text-underline-offset: 4px; text-decoration-thickness: 1px; } +.docs-cloud-guides::before, +.docs-cloud-guide::after, +.docs-cloud-hub::before, +.docs-cloud-hub::after { content: ''; position: absolute; pointer-events: none; background: var(--cloud-line); } +.docs-cloud-guides::before { width: 1px; top: calc(25% - 0.375rem); bottom: calc(25% - 0.375rem); } +.docs-cloud-inputs::before { right: -1.5rem; } +.docs-cloud-outputs::before { left: -1.5rem; } +.docs-cloud-guide::after, +.docs-cloud-hub::before, +.docs-cloud-hub::after { height: 1px; width: 1.5rem; top: 50%; } +.docs-cloud-inputs .docs-cloud-guide::after, +.docs-cloud-hub::after { right: -1.5rem; } +.docs-cloud-outputs .docs-cloud-guide::after, +.docs-cloud-hub::before { left: -1.5rem; } +.dark .docs-cloud-diagram { + --cloud-ink: #a0c7bc; + --cloud-tint: #263931; + --cloud-line: #58766b; + background: linear-gradient(115deg, rgb(24 24 24 / 90%), rgb(24 24 24 / 78%)), url('/docs/framework-diagrams/mint.avif') center / cover; +} +@media (max-width: 1023px) { + .docs-cloud-diagram { grid-template-columns: minmax(0, 1fr); gap: 2rem; padding: 1.5rem; } + .docs-cloud-guides { gap: 2rem; } + .docs-cloud-guides::before, + .docs-cloud-hub::before { display: none; } + .docs-cloud-inputs .docs-cloud-guide::after, + .docs-cloud-outputs .docs-cloud-guide::after, + .docs-cloud-hub::after { width: 1px; height: 2rem; top: auto; bottom: -2rem; left: 50%; right: auto; } + .docs-cloud-outputs .docs-cloud-guide:last-child::after { display: none; } +} +@media (max-width: 479px) { + .docs-cloud-diagram { padding: 1rem; } + .docs-cloud-guide { padding: 1.25rem; } + .docs-cloud-hub > div:last-child { padding: 1rem 0.75rem; } +} diff --git a/packages/reflex-site-shared/src/reflex_site_shared/styles/assets/editorial/GradientButton.tsx b/packages/reflex-site-shared/src/reflex_site_shared/styles/assets/editorial/GradientButton.tsx new file mode 100644 index 00000000000..979d3af1277 --- /dev/null +++ b/packages/reflex-site-shared/src/reflex_site_shared/styles/assets/editorial/GradientButton.tsx @@ -0,0 +1,141 @@ +import type { ButtonHTMLAttributes, ReactNode } from "react"; + +import { cn } from "clsx-for-tailwind"; + +export type ButtonVariant = + | "primary" + | "success" + | "primary-bordered" + | "secondary" + | "destructive" + | "outline" + | "ghost"; +export type ButtonSize = + | "xs" + | "sm" + | "md" + | "lg" + | "xl" + | "icon-xs" + | "icon-sm" + | "icon-md" + | "icon-lg"; + +export interface ButtonProps extends ButtonHTMLAttributes { + children: ReactNode; + variant?: ButtonVariant; + size?: ButtonSize; + className?: string; + nativeButton?: boolean; +} + +export const focusRing = + "focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-ring"; + +const variants: Record = { + primary: + "bg-primary text-primary-foreground shadow-none hover:bg-primary-hover", + secondary: + "border-0 bg-accent text-foreground shadow-none hover:bg-accent-hover", + "primary-bordered": + "bg-primary text-primary-foreground shadow-none hover:bg-primary-hover", + success: "bg-success-9 text-white shadow-none hover:bg-success-10", + destructive: "bg-red-strong text-gray-white hover:bg-red-deep", + outline: + "border border-border bg-card text-muted-foreground shadow-small hover:bg-accent", + ghost: + "bg-transparent text-foreground shadow-none hover:bg-accent hover:text-foreground", +}; + +const sizes: Record = { + xs: "h-7 gap-1.5 px-3 text-xs", + sm: "h-9 gap-2 px-4 text-sm leading-none", + md: "h-11 gap-2 px-5 text-sm leading-none", + lg: "h-12 gap-2 px-6 text-base leading-none", + xl: "min-h-14 max-w-full gap-2.5 whitespace-normal px-8 py-3 text-center text-lg leading-6", + "icon-xs": "size-7 p-0", + "icon-sm": "size-9 p-0", + "icon-md": "size-11 p-0", + "icon-lg": "size-12 p-0", +}; + +/** A single variant API for buttons and link-shaped actions. */ +export function buttonVariants({ + variant = "primary", + size = "md", + className, +}: { + variant?: ButtonVariant; + size?: ButtonSize; + className?: string; +} = {}) { + return cn( + "relative inline-flex shrink-0 items-center justify-center whitespace-nowrap rounded-full font-medium transition-colors cursor-pointer box-border", + focusRing, + "disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-accent disabled:text-subtle-foreground disabled:shadow-none motion-reduce:transition-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", + variants[variant], + sizes[size], + className, + ); +} + +/** Reusable action primitive; all visual states are Tailwind variants. */ +export function GradientButton({ + children, + variant = "primary", + size = "md", + className, + nativeButton = true, + ...props +}: ButtonProps) { + const Component = + nativeButton || props["aria-haspopup"] === "dialog" ? "button" : "div"; + const glow = variant === "primary"; + return ( + { + props.onMouseMove?.(event); + if (!glow || props.disabled) return; + // The glow follows the pointer through CSS variables written straight + // to the element, so tracking never re-renders the button. + const bounds = event.currentTarget.getBoundingClientRect(); + event.currentTarget.style.setProperty( + "--glow-x", + `${event.clientX - bounds.left}px`, + ); + event.currentTarget.style.setProperty( + "--glow-y", + `${event.clientY - bounds.top}px`, + ); + }} + data-slot="button" + data-marketing-variant={variant} + className={buttonVariants({ + variant, + size, + className: cn("overflow-hidden", glow && "group/glow", className), + })} + > + {glow ? ( + <> + {/* Visible only while hovered; `disabled:pointer-events-none` on the + button means a disabled button never hovers. */} + + ); +} diff --git a/packages/reflex-site-shared/src/reflex_site_shared/views/footer.py b/packages/reflex-site-shared/src/reflex_site_shared/views/footer.py index 92f54d07bce..81f81eb17bb 100644 --- a/packages/reflex-site-shared/src/reflex_site_shared/views/footer.py +++ b/packages/reflex-site-shared/src/reflex_site_shared/views/footer.py @@ -56,6 +56,8 @@ def tab_item(mode: str, icon: str) -> rx.Component: return rx.el.button( get_icon(icon, class_name="shrink-0"), on_click=set_color_mode(mode), # type: ignore[reportArgumentType] + type="button", + aria_pressed=mode == color_mode, class_name=ui.cn( "flex items-center cursor-pointer justify-center rounded-lg transition-colors size-7 outline-none focus:outline-none ", rx.cond(mode == color_mode, active_cn, unactive_cn), @@ -74,7 +76,7 @@ def dark_mode_toggle() -> rx.Component: tab_item("system", "computer_footer"), tab_item("light", "sun_footer"), tab_item("dark", "moon_footer"), - class_name="flex flex-row gap-0.5 items-center p-0.5 [box-shadow:0_1px_0_0_rgba(0,_0,_0,_0.08),_0_0_0_1px_rgba(0,_0,_0,_0.08),_0_1px_2px_0_rgba(0,_0,_0,_0.02),_0_1px_4px_0_rgba(0,_0,_0,_0.02)] w-fit mt-auto bg-secondary-1 rounded-[0.625rem] dark:border dark:border-secondary-4 border border-transparent", + class_name="site-color-mode-toggle flex flex-row gap-0.5 items-center p-0.5 [box-shadow:0_1px_0_0_rgba(0,_0,_0,_0.08),_0_0_0_1px_rgba(0,_0,_0,_0.08),_0_1px_2px_0_rgba(0,_0,_0,_0.02),_0_1px_4px_0_rgba(0,_0,_0,_0.02)] w-fit mt-auto bg-secondary-1 rounded-[0.625rem] dark:border dark:border-secondary-4 border border-transparent", ) diff --git a/packages/reflex-site-shared/src/reflex_site_shared/views/sidebar/__init__.py b/packages/reflex-site-shared/src/reflex_site_shared/views/sidebar/__init__.py index a4b896ffdc6..d7f80347931 100644 --- a/packages/reflex-site-shared/src/reflex_site_shared/views/sidebar/__init__.py +++ b/packages/reflex-site-shared/src/reflex_site_shared/views/sidebar/__init__.py @@ -440,7 +440,10 @@ def drawer_top_offset(top_px: int | float) -> str: Returns: The Tailwind class string for the given top offset. """ - return f"!top-[{top_px}px] !h-[calc(100dvh-{top_px}px)]" + return ( + f"!top-[var(--docs-header-height,{top_px}px)] " + f"!h-[calc(100dvh-var(--docs-header-height,{top_px}px))]" + ) def navbar_sidebar_drawer( diff --git a/tests/units/reflex_site_shared/test_docs_shell.py b/tests/units/reflex_site_shared/test_docs_shell.py index 5fa8f78ae41..8c47b4b1297 100644 --- a/tests/units/reflex_site_shared/test_docs_shell.py +++ b/tests/units/reflex_site_shared/test_docs_shell.py @@ -146,7 +146,9 @@ def test_shared_sidebar_rows_keep_official_structure() -> None: assert "LucideGraduationCap" in category assert "group/details" in group assert "ArrowDown01Icon" in group - assert "left-[3rem]" in group + assert "docs-sidebar-group-trigger" in group + assert "docs-sidebar-group-items" in group + assert "open:true" in group def test_official_docs_footer_content_is_shared() -> None: @@ -163,7 +165,8 @@ def test_official_docs_footer_content_is_shared() -> None: assert "Links" in rendered assert "Documentation" in rendered assert "Resources" in rendered - assert "Social link for Github" in rendered + assert "Social link for GitHub" in rendered + assert "Social link for Forum" not in rendered assert "Pynecone, Inc." in rendered assert "https://reflex.dev/docs/getting-started/introduction/" not in rendered assert "/getting-started/introduction/" in rendered diff --git a/tests/units/reflex_site_shared/test_docs_typography.py b/tests/units/reflex_site_shared/test_docs_typography.py new file mode 100644 index 00000000000..d3d3a4e3442 --- /dev/null +++ b/tests/units/reflex_site_shared/test_docs_typography.py @@ -0,0 +1,26 @@ +"""Canonical destinations for docs links from authored and imported Markdown.""" + +import pytest +from reflex_site_shared.components.blocks.typography import doclink2 + + +@pytest.mark.parametrize( + ("href", "expected"), + [ + ("/docs/state/overview#example", "/docs/state/overview/#example"), + ( + "https://reflex.dev/docs/hosting/regions?q=one#title", + "https://reflex.dev/docs/hosting/regions/?q=one#title", + ), + ("/docs", "/docs/"), + ("/docs/llms.txt", "/docs/llms.txt"), + ("/docs/guide.md", "/docs/guide.md"), + ("#example", "#example"), + ("https://example.com/docs/guide", "https://example.com/docs/guide"), + ("/blog/post", "/blog/post"), + ("/docs/state/overview/", "/docs/state/overview/"), + ], +) +def test_markdown_doc_links_use_canonical_paths(href, expected): + """Only known docs page paths get a slash; assets and other sites stay intact.""" + assert str(doclink2("Guide", href=href).href) == f'"{expected}"' diff --git a/tests/units/reflex_site_shared/test_plugins.py b/tests/units/reflex_site_shared/test_plugins.py index e4d3565dcd7..1750b06c835 100644 --- a/tests/units/reflex_site_shared/test_plugins.py +++ b/tests/units/reflex_site_shared/test_plugins.py @@ -105,3 +105,35 @@ def test_docs_markdown_plugin_stages_assets_for_production_relocation( assert (static_dir / "guide.md").read_text(encoding="utf-8") == "# Guide\n" assert (static_dir / "guide" / ".md").read_text(encoding="utf-8") == "# Guide\n" assert not (static_dir / "guide.html").exists() + + +def test_editorial_theme_emits_matching_styles_and_button_without_changing_default(): + """Opt-in themes ship their button and CSS together, retaining legacy assets.""" + plugin = SharedSiteStylesPlugin(editorial=True) + assets = dict(plugin.get_static_assets()) + default_assets = dict(SharedSiteStylesPlugin().get_static_assets()) + stylesheet = Path("styles/reflex-site-shared/editorial.css") + button_path = Path("public/components/GradientButton.tsx") + + assert plugin.get_stylesheet_paths()[-1] == "./reflex-site-shared/editorial.css" + assert stylesheet in assets + assert stylesheet not in default_assets + assert ".dark" in assets[stylesheet] + assert "--c-slate-12: #181818" in assets[stylesheet] + assert "--c-slate-12: #f5f5f5" in assets[stylesheet] + assert "export function GradientButton" in assets[button_path] + assert "buttonVariants" in assets[button_path] + assert "buttonVariants" not in default_assets[button_path] + assert len(assets) == len(default_assets) + 1 + + +def test_editorial_theme_can_keep_consumer_font_loading(): + """Custom font loading does not remove the opt-in editorial stylesheet.""" + plugin = SharedSiteStylesPlugin(include_fonts=False, editorial=True) + paths = plugin.get_stylesheet_paths() + assert paths[-1].endswith("/editorial.css") + assert not any(path.endswith("/fonts.css") for path in paths) + assert all( + Path("styles") / path.removeprefix("./") in dict(plugin.get_static_assets()) + for path in paths + )