Radish alpha
r
Radicle website including documentation and guides
Radicle
Git (anonymous pull)
Log in to clone via SSH
app: Introduce opt-in design layout ported from radicle.dev
lftherios wants to merge 1 commit into master · opened 12 days ago

Series: First of 10 patches porting the radicle.dev visual design onto radicle.xyz. This patch lands the foundation; patches 2–10 migrate one page each (faq, legal, docs, work, history, download, guides index, guides stacked, home). They share no files, so they can be reviewed independently and merged in any order once this lands.

Purely additive — 14 new files, zero modifications to existing code. The site renders unchanged everywhere except /preview/, a demo page included here specifically for review.

Adds:

  • _layouts/app.html, _layouts/app-prose.html
  • _includes/app/{topbar,sidebar,footer}.html
  • assets/css/{app,app-colors,app-typography}.css
  • assets/fonts/Booton-{Regular,Medium,SemiBold}.woff2
  • assets/js/{app-menu,code-copy}.js
  • _pages/preview.md

Review: make serve and visit http://localhost:3000/preview/

Out of scope: Blog migration (20 posts), and removal of the now-orphaned guide.html/index.html/page.html layouts + guide.css — that cleanup is a separate follow-up patch once the migrations have all landed.

14 files changed +1867 -0 2494719f ef071b10
added _includes/app/footer.html
@@ -0,0 +1,54 @@
+
<footer class="app-footer">
+
  <div class="footer-links">
+
    <div class="link-column">
+
      <span class="column-header txt-medium-14">Guides</span>
+
      <a href="{{ '/guides/user/' | relative_url }}" class="txt-medium-14">User guide</a>
+
      <a href="{{ '/guides/protocol/' | relative_url }}" class="txt-medium-14">Protocol guide</a>
+
      <a href="{{ '/guides/seeder/' | relative_url }}" class="txt-medium-14">Seeder guide</a>
+
    </div>
+

+
    <div class="link-column">
+
      <span class="column-header txt-medium-14">Resources</span>
+
      <a href="{{ '/docs/' | relative_url }}" class="txt-medium-14">Docs</a>
+
      <a href="{{ '/faq/' | relative_url }}" class="txt-medium-14">FAQ</a>
+
      <a href="{{ '/#blog' | relative_url }}" class="txt-medium-14">Updates</a>
+
      <a href="{{ '/#updates' | relative_url }}" class="txt-medium-14">Releases</a>
+
    </div>
+

+
    <div class="link-column">
+
      <span class="column-header txt-medium-14">Social</span>
+
      <a
+
        href="https://bsky.app/profile/{{ site.atproto }}"
+
        target="_blank"
+
        rel="noopener noreferrer"
+
        class="txt-medium-14 arrow-link">
+
        Bluesky <span class="link-arrow link-arrow-up-right">↗</span>
+
      </a>
+
      <a
+
        href="https://twitter.com/radicle"
+
        target="_blank"
+
        rel="noopener noreferrer"
+
        class="txt-medium-14 arrow-link">
+
        Twitter <span class="link-arrow link-arrow-up-right">↗</span>
+
      </a>
+
      <a
+
        href="{{ site.fedi | fedi_to_https }}"
+
        target="_blank"
+
        rel="noopener noreferrer"
+
        class="txt-medium-14 arrow-link">
+
        Mastodon <span class="link-arrow link-arrow-up-right">↗</span>
+
      </a>
+
      <a
+
        href="https://radicle.zulipchat.com"
+
        target="_blank"
+
        rel="noopener noreferrer"
+
        class="txt-medium-14 arrow-link">
+
        Zulip <span class="link-arrow link-arrow-up-right">↗</span>
+
      </a>
+
    </div>
+
  </div>
+

+
  <div class="footer-bottom">
+
    <span class="copyright txt-medium-14">&copy; 2018&ndash;{{ 'now' | date: "%Y" }} The Radicle Team</span>
+
  </div>
+
</footer>
added _includes/app/sidebar.html
@@ -0,0 +1,73 @@
+
<aside class="sidebar">
+
  <nav class="top-section">
+
    <div class="nav-section">
+
      {% assign install_active = false %}
+
      {% if page.url == '/download/' %}{% assign install_active = true %}{% endif %}
+
      <a
+
        href="{{ '/download/' | relative_url }}"
+
        class="section-header{% if install_active %} active{% endif %}">
+
        {% if install_active %}<span class="dot"></span>{% endif %}Install
+
      </a>
+
    </div>
+

+
    <div class="nav-section">
+
      {% assign guides_active = false %}
+
      {% if page.url contains '/guides' %}{% assign guides_active = true %}{% endif %}
+
      <a
+
        href="{{ '/guides/' | relative_url }}"
+
        class="section-header{% if guides_active %} active{% endif %}">
+
        {% if guides_active %}<span class="dot"></span>{% endif %}Guides
+
      </a>
+
    </div>
+

+
    <div class="nav-section">
+
      {% assign docs_active = false %}
+
      {% if page.url == '/docs/' %}{% assign docs_active = true %}{% endif %}
+
      <a
+
        href="{{ '/docs/' | relative_url }}"
+
        class="section-header{% if docs_active %} active{% endif %}">
+
        {% if docs_active %}<span class="dot"></span>{% endif %}Docs
+
      </a>
+
    </div>
+

+
    <div class="nav-section">
+
      {% assign faq_active = false %}
+
      {% if page.url == '/faq/' %}{% assign faq_active = true %}{% endif %}
+
      <a
+
        href="{{ '/faq/' | relative_url }}"
+
        class="section-header{% if faq_active %} active{% endif %}">
+
        {% if faq_active %}<span class="dot"></span>{% endif %}FAQ
+
      </a>
+
    </div>
+

+
    <div class="external-links">
+
      <a
+
        href="https://{{ site.explorer }}"
+
        target="_blank"
+
        rel="noopener noreferrer"
+
        class="external-link arrow-link">
+
        Explore <span class="link-arrow link-arrow-up-right">↗</span>
+
      </a>
+
      <a
+
        href="https://radicle.garden"
+
        target="_blank"
+
        rel="noopener noreferrer"
+
        class="external-link arrow-link">
+
        Garden <span class="link-arrow link-arrow-up-right">↗</span>
+
      </a>
+
    </div>
+
  </nav>
+

+
  <div class="bottom-section">
+
    <a
+
      href="{{ '/#updates' | relative_url }}"
+
      class="bottom-link">
+
      Releases
+
    </a>
+
    <a
+
      href="{{ '/#blog' | relative_url }}"
+
      class="bottom-link">
+
      Updates
+
    </a>
+
  </div>
+
</aside>
added _includes/app/topbar.html
@@ -0,0 +1,77 @@
+
<header class="topbar">
+
  <a href="{{ '/' | relative_url }}" class="logo" aria-label="Radicle">
+
    <svg
+
      width="69"
+
      height="16"
+
      viewBox="0 0 69 16"
+
      fill="none"
+
      xmlns="http://www.w3.org/2000/svg">
+
      <path d="M1.84555 13.7149H0V15.7954H6.28119V13.7149H4.466V7.01257H8.02772V4.61917H1.84555V13.7149Z" fill="currentColor"/>
+
      <path d="M9.20898 12.8172C9.20898 10.7169 10.6268 9.62514 13.2473 9.23834L15.1852 8.96243C15.8783 8.86078 16.2149 8.63636 16.2149 8.06474C16.2149 6.98355 15.4704 6.37233 14.1859 6.37233C12.8195 6.37233 12.0961 7.10633 11.8809 8.14659L9.35156 7.76903C9.73968 5.91293 11.279 4.41458 14.1846 4.41458C17.0902 4.41458 18.8446 6.0159 18.8446 8.56507V15.7941H16.2651V13.7241H16.1634C15.807 14.9479 14.5938 15.9974 12.7483 15.9974C10.5964 15.9974 9.2103 14.7327 9.2103 12.8159L9.20898 12.8172ZM16.2136 10.9109V10.544L13.7872 10.9928C12.5331 11.2278 11.9826 11.7572 11.9826 12.5624C11.9826 13.4601 12.6453 13.9895 13.6552 13.9895C15.3687 13.9895 16.2149 12.7459 16.2149 10.9096L16.2136 10.9109Z" fill="currentColor"/>
+
      <path d="M20.5122 10.1861C20.5122 6.65872 22.3274 4.41449 25.0191 4.41449C26.5492 4.41449 27.7518 5.28182 28.3234 6.61647H28.4158V0.826372H31.0469V15.7953H28.4158V13.7465H28.3142C27.6211 15.1841 26.5505 16 25.031 16C22.3287 16 20.5135 13.7267 20.5135 10.1874L20.5122 10.1861ZM25.8455 13.7953C27.4152 13.7953 28.4053 12.3775 28.4053 10.1861C28.4053 7.99469 27.4165 6.58611 25.8455 6.58611C24.2746 6.58611 23.2858 7.99337 23.2858 10.1861C23.2858 12.3788 24.2851 13.7953 25.8455 13.7953Z" fill="currentColor"/>
+
      <path d="M37.3981 13.7149H39.2436V15.7954H32.9624V13.7149H34.7776V6.69967H33.0957V4.61914H37.3981V13.7149ZM34.441 1.54983C34.441 0.693069 35.1551 0 36.0212 0C36.8872 0 37.5816 0.693069 37.5816 1.54983C37.5816 2.4066 36.899 3.08911 36.0212 3.08911C35.1433 3.08911 34.441 2.4066 34.441 1.54983Z" fill="currentColor"/>
+
      <path d="M50.0937 7.98415L47.5644 8.52409C47.299 7.28052 46.5756 6.54653 45.3413 6.54653C43.7505 6.54653 42.7406 7.8825 42.7406 10.2073C42.7406 12.532 43.7598 13.868 45.3505 13.868C46.4924 13.868 47.3083 13.2462 47.5433 12.0026H50.1532C49.8574 14.5109 48.1954 16 45.3505 16C42.0871 16 39.9868 13.7267 39.9868 10.2086C39.9868 6.69042 42.0871 4.41716 45.34 4.41716C48.0317 4.41716 49.765 5.86534 50.0911 7.98547L50.0937 7.98415Z" fill="currentColor"/>
+
      <path d="M55.5284 13.7148H57.374V15.7953H51.0928V13.7148H52.908V2.90558H51.2261V0.826372H55.5284V13.7148Z" fill="currentColor"/>
+
      <path d="M57.9751 10.2271C57.9751 6.74989 60.1269 4.41458 63.3177 4.41458C66.2444 4.41458 68.5388 6.34197 68.2325 10.808H60.7276C60.8807 12.7855 61.921 13.938 63.3282 13.938C64.5414 13.938 65.3058 13.336 65.6015 12.2958H68.2629C67.9777 14.1822 66.1731 15.9974 63.3282 15.9974C60.0965 15.9974 57.9751 13.744 57.9751 10.2258V10.2271ZM65.6015 9.01392C65.5408 7.37167 64.7038 6.38289 63.3282 6.38289C62.0741 6.38289 61.0853 7.10633 60.7896 9.01392H65.6028H65.6015Z" fill="currentColor"/>
+
    </svg>
+
  </a>
+
  <span class="spacer" aria-hidden="true"></span>
+
  <a href="{{ '/download/' | relative_url }}" class="button primary">Install Radicle</a>
+
  <button
+
    class="mobile-menu-button"
+
    type="button"
+
    aria-label="Open navigation menu"
+
    aria-expanded="false"
+
    data-mobile-menu-toggle>
+
    <svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true" class="mobile-menu-icon">
+
      <circle class="dot-left" cx="2" cy="8" r="2" fill="currentColor"/>
+
      <circle class="dot-top" cx="8" cy="2" r="2" fill="currentColor"/>
+
      <circle class="dot-center" cx="8" cy="8" r="2" fill="currentColor"/>
+
      <circle class="dot-bottom" cx="8" cy="14" r="2" fill="currentColor"/>
+
      <circle class="dot-right" cx="14" cy="8" r="2" fill="currentColor"/>
+
    </svg>
+
  </button>
+
</header>
+

+
<nav class="mobile-nav-overlay" aria-label="Mobile navigation" data-mobile-menu>
+
  <div class="mobile-nav-content">
+
    <ul class="mobile-nav-main">
+
      <li>
+
        <a href="{{ '/download/' | relative_url }}" class="mobile-nav-card install">
+
          <div class="mobile-nav-text">
+
            <span class="mobile-nav-title">Install</span>
+
            <span class="mobile-nav-description">Install Radicle CLI or download the desktop app.</span>
+
          </div>
+
          <span class="mobile-nav-thumb install" aria-hidden="true"></span>
+
        </a>
+
      </li>
+
      <li>
+
        <a href="{{ '/guides/' | relative_url }}" class="mobile-nav-card learn">
+
          <div class="mobile-nav-text">
+
            <span class="mobile-nav-title">Guides</span>
+
            <span class="mobile-nav-description">Everything you need to get started with Radicle.</span>
+
          </div>
+
          <span class="mobile-nav-thumb learn" aria-hidden="true"></span>
+
        </a>
+
      </li>
+
      <li>
+
        <a href="https://{{ site.explorer }}" class="mobile-nav-card explore" target="_blank" rel="noopener noreferrer">
+
          <div class="mobile-nav-text">
+
            <span class="mobile-nav-title">Explore</span>
+
            <span class="mobile-nav-description">Browse projects, issues, and patches on the network.</span>
+
          </div>
+
          <span class="mobile-nav-thumb explore" aria-hidden="true"></span>
+
        </a>
+
      </li>
+
      <li>
+
        <a href="https://radicle.garden" class="mobile-nav-card garden" target="_blank" rel="noopener noreferrer">
+
          <div class="mobile-nav-text">
+
            <span class="mobile-nav-title">Garden</span>
+
            <span class="mobile-nav-description">The quickest way to get started on the network.</span>
+
          </div>
+
          <span class="mobile-nav-thumb garden" aria-hidden="true"></span>
+
        </a>
+
      </li>
+
    </ul>
+
  </div>
+
</nav>
added _layouts/app-prose.html
@@ -0,0 +1,8 @@
+
---
+
layout: app
+
---
+
<div class="page-container">
+
  <article class="markdown-content">
+
    {{ content }}
+
  </article>
+
</div>
added _layouts/app.html
@@ -0,0 +1,45 @@
+
<!doctype html>
+
<html lang="{{ site.lang | default: "en-US" }}">
+
  <head>
+
    <script>
+
      // Set the theme attribute before paint to avoid flicker. Mirrors the
+
      // logic in radicle.dev's app.html: prefer an explicit user choice from
+
      // localStorage, otherwise follow the system preference.
+
      (function () {
+
        try {
+
          var theme = localStorage.getItem("theme");
+
          var resolved;
+
          if (theme === "light" || theme === "dark") {
+
            resolved = theme;
+
          } else {
+
            resolved = window.matchMedia("(prefers-color-scheme: dark)").matches
+
              ? "dark"
+
              : "light";
+
          }
+
          document.documentElement.setAttribute("data-theme", resolved);
+
        } catch (_) {}
+
      })();
+
    </script>
+

+
    {% include meta.html %}
+

+
    <link rel="stylesheet" type="text/css" href="{{ '/assets/css/app.css' | relative_url }}"/>
+
    <link rel="stylesheet" type="text/css" href="{{ '/assets/css/app-colors.css' | relative_url }}"/>
+
    <link rel="stylesheet" type="text/css" href="{{ '/assets/css/app-typography.css' | relative_url }}"/>
+

+
    <link rel="alternate" type="application/rss+xml" title="RSS Feed for the Radicle Website" href="{{ '/feed.xml' | relative_url }}"/>
+
  </head>
+
  <body class="app-layout">
+
    <div class="app-shell">
+
      {% include app/topbar.html %}
+
      {% include app/sidebar.html %}
+
      <main class="main-content">
+
        {{ content }}
+
        {% include app/footer.html %}
+
      </main>
+
    </div>
+

+
    <script src="{{ '/assets/js/app-menu.js' | relative_url }}"></script>
+
    <script src="{{ '/assets/js/code-copy.js' | relative_url }}"></script>
+
  </body>
+
</html>
added _pages/preview.md
@@ -0,0 +1,79 @@
+
---
+
title: "Preview: app layout"
+
layout: app-prose
+
---
+

+
# Heading 1 — the sovereign code network
+

+
This page exists only as a visual sample for the new `app` layout. It is not
+
linked from anywhere on the site and is intended to be deleted before this
+
patch is finalised, or kept as a private design reference.
+

+
The layout is opt-in via `layout: app` (or `layout: app-prose` for markdown
+
pages). Existing pages on the site are unaffected.
+

+
## Heading 2 — typography & prose
+

+
Body copy uses the **Booton** display font for headings and a medium weight
+
for paragraphs. Inline links like [radicle.network][rn] inherit the brand
+
colour, while [external links use the same treatment](https://radicle.zulipchat.com).
+
Inline `code` is rendered in JetBrains Mono with a subtle surface background.
+

+
[rn]: https://radicle.network
+

+
### Heading 3
+

+
A nested list to verify spacing:
+

+
- First item with a [link inside](#).
+
- Second item with **bold text** and *italic text*.
+
- Third item with `inline code`.
+
  - Nested item.
+
  - Another nested item.
+

+
Ordered list:
+

+
1. First step
+
2. Second step
+
3. Third step
+

+
#### Heading 4
+

+
> Block quotes use the secondary text colour and a left border in the brand
+
> mid-tone. They should feel set off without dominating the page.
+

+
A code block (currently rendered with default Rouge styling — the dual-theme
+
Shiki port lands in a follow-up patch):
+

+
```sh
+
$ rad init --name heartwood
+
$ rad sync
+
$ rad inspect
+
```
+

+
A small table:
+

+
| Layer       | Component        | Status        |
+
| ----------- | ---------------- | ------------- |
+
| Storage     | Git              | stable        |
+
| Identity    | Cryptographic    | stable        |
+
| Transport   | NoiseXK          | stable        |
+

+
---
+

+
## Buttons & CTAs
+

+
<a href="{{ '/download/' | relative_url }}" class="button primary">Install Radicle</a>
+
<a href="https://{{ site.explorer }}" class="button secondary" target="_blank" rel="noopener noreferrer">Explore the network</a>
+

+
## Arrow links
+

+
<p>
+
  <a href="https://{{ site.explorer }}" target="_blank" rel="noopener noreferrer" class="arrow-link">
+
    Explore <span class="link-arrow link-arrow-up-right">↗</span>
+
  </a>
+
  &nbsp;
+
  <a href="{{ '/guides/' | relative_url }}" class="arrow-link">
+
    Read the guides <span class="link-arrow link-arrow-right">→</span>
+
  </a>
+
</p>
added assets/css/app-colors.css
@@ -0,0 +1,296 @@
+
/* Dark theme. */
+
:root {
+
  --color-surface-base: var(--color-neutrals-opaque-dark-0);
+
  --color-surface-canvas: var(--color-neutrals-opaque-dark-50);
+
  --color-surface-subtle: var(--color-neutrals-opaque-dark-100);
+
  --color-surface-mid: var(--color-neutrals-opaque-dark-150);
+
  --color-surface-strong: var(--color-neutrals-opaque-dark-200);
+
  --color-surface-alpha-subtle: var(--color-neutrals-alpha-dark-50);
+
  --color-surface-alpha-mid: var(--color-neutrals-alpha-dark-100);
+
  --color-surface-alpha-strong: var(--color-neutrals-alpha-dark-200);
+
  --color-surface-open: var(--color-accent-green-900);
+
  --color-surface-merged: var(--color-accent-blue-900);
+
  --color-surface-archived: var(--color-accent-pink-900);
+

+
  --color-text-primary: var(--color-neutrals-opaque-dark-900);
+
  --color-text-secondary: var(--color-neutrals-opaque-dark-700);
+
  --color-text-tertiary: var(--color-neutrals-opaque-dark-600);
+
  --color-text-quaternary: var(--color-neutrals-opaque-dark-500);
+
  --color-text-disabled: var(--color-neutrals-alpha-dark-400);
+
  --color-text-open: var(--color-accent-green-500);
+
  --color-text-merged: var(--color-accent-blue-500);
+
  --color-text-archived: var(--color-accent-pink-500);
+

+
  --color-border-subtle: var(--color-neutrals-opaque-dark-150);
+
  --color-border-mid: var(--color-neutrals-opaque-dark-200);
+
  --color-border-strong: var(--color-neutrals-opaque-dark-300);
+
  --color-border-alpha-subtle: var(--color-neutrals-alpha-dark-100);
+
  --color-border-alpha-mid: var(--color-neutrals-alpha-dark-200);
+

+
  --color-feedback-success-text: var(--color-semantic-green-400);
+
  --color-feedback-success-border: var(--color-semantic-green-700);
+
  --color-feedback-success-bg: var(--color-semantic-green-900);
+
  --color-feedback-warning-text: var(--color-semantic-amber-400);
+
  --color-feedback-warning-border: var(--color-semantic-amber-700);
+
  --color-feedback-warning-bg: var(--color-semantic-amber-900);
+
  --color-feedback-error-text: var(--color-semantic-red-400);
+
  --color-feedback-error-border: var(--color-semantic-red-700);
+
  --color-feedback-error-bg: var(--color-semantic-red-900);
+

+
  --color-code-keywords: var(--color-accent-blue-400);
+
  --color-code-strings: var(--color-accent-green-400);
+
  --color-code-numbers: var(--color-accent-purple-400);
+
  --color-code-comments: var(--color-neutrals-opaque-dark-700);
+
  --color-code-error: var(--color-semantic-red-400);
+
  --color-code-functions: var(--color-accent-emerald-400);
+

+
  /* Brand colors (blue). */
+
  --color-brand-bg: var(--color-accent-blue-600);
+
  --color-brand-hover: var(--color-accent-blue-500);
+
  --color-brand-text-light: var(--color-accent-blue-600);
+
  --color-brand-text-dark: var(--color-accent-blue-400);
+
  --color-text-on-brand: var(--color-neutrals-opaque-light-0);
+

+
  --color-surface-brand-primary: var(--color-brand-bg);
+
  --color-surface-brand-secondary: var(--color-brand-hover);
+
  --color-border-brand: var(--color-brand-hover);
+
  --color-text-brand: var(--color-brand-text-dark);
+
}
+

+
/* Light theme. */
+
:root[data-theme="light"] {
+
  --color-surface-base: var(--color-neutrals-opaque-light-50);
+
  --color-surface-canvas: var(--color-neutrals-opaque-light-0);
+
  --color-surface-subtle: var(--color-neutrals-opaque-light-100);
+
  --color-surface-mid: var(--color-neutrals-opaque-light-150);
+
  --color-surface-strong: var(--color-neutrals-opaque-light-200);
+
  --color-surface-alpha-subtle: var(--color-neutrals-alpha-light-50);
+
  --color-surface-alpha-mid: var(--color-neutrals-alpha-light-100);
+
  --color-surface-alpha-strong: var(--color-neutrals-alpha-light-200);
+
  --color-surface-open: var(--color-accent-green-100);
+
  --color-surface-merged: var(--color-accent-blue-100);
+
  --color-surface-archived: var(--color-accent-pink-100);
+

+
  --color-text-primary: var(--color-neutrals-opaque-light-900);
+
  --color-text-secondary: var(--color-neutrals-opaque-light-700);
+
  --color-text-tertiary: var(--color-neutrals-opaque-light-600);
+
  --color-text-quaternary: var(--color-neutrals-opaque-light-500);
+
  --color-text-disabled: var(--color-neutrals-alpha-light-400);
+
  --color-text-open: var(--color-accent-green-600);
+
  --color-text-merged: var(--color-accent-blue-600);
+
  --color-text-archived: var(--color-accent-pink-600);
+

+
  --color-border-subtle: var(--color-neutrals-opaque-light-150);
+
  --color-border-mid: var(--color-neutrals-opaque-light-200);
+
  --color-border-strong: var(--color-neutrals-opaque-light-300);
+
  --color-border-alpha-subtle: var(--color-neutrals-alpha-light-100);
+
  --color-border-alpha-mid: var(--color-neutrals-alpha-light-200);
+

+
  --color-feedback-success-text: var(--color-semantic-green-600);
+
  --color-feedback-success-border: var(--color-semantic-green-300);
+
  --color-feedback-success-bg: var(--color-semantic-green-100);
+
  --color-feedback-warning-text: var(--color-semantic-amber-600);
+
  --color-feedback-warning-border: var(--color-semantic-amber-300);
+
  --color-feedback-warning-bg: var(--color-semantic-amber-100);
+
  --color-feedback-error-text: var(--color-semantic-red-600);
+
  --color-feedback-error-border: var(--color-semantic-red-300);
+
  --color-feedback-error-bg: var(--color-semantic-red-100);
+

+
  --color-code-keywords: var(--color-accent-blue-600);
+
  --color-code-strings: var(--color-accent-green-600);
+
  --color-code-numbers: var(--color-accent-purple-600);
+
  --color-code-comments: var(--color-neutrals-opaque-light-500);
+
  --color-code-error: var(--color-semantic-red-600);
+
  --color-code-functions: var(--color-accent-emerald-600);
+

+
  /* Brand colors (blue). */
+
  --color-brand-bg: var(--color-accent-blue-600);
+
  --color-brand-hover: var(--color-accent-blue-500);
+
  --color-text-on-brand: var(--color-neutrals-opaque-light-0);
+
  --color-text-brand: var(--color-brand-text-light);
+
}
+

+
/* Internal color system, don't use directly! */
+
:root {
+
  --color-neutrals-opaque-light-0: #ffffff;
+
  --color-neutrals-opaque-light-50: #f8f9fa;
+
  --color-neutrals-opaque-light-100: #f1f3f5;
+
  --color-neutrals-opaque-light-150: #e9ebef;
+
  --color-neutrals-opaque-light-200: #e1e4e8;
+
  --color-neutrals-opaque-light-250: #d6d9e0;
+
  --color-neutrals-opaque-light-300: #c9cdd4;
+
  --color-neutrals-opaque-light-400: #a2a7b1;
+
  --color-neutrals-opaque-light-500: #7a8190;
+
  --color-neutrals-opaque-light-600: #5a5f6b;
+
  --color-neutrals-opaque-light-700: #3a3f49;
+
  --color-neutrals-opaque-light-800: #1f232b;
+
  --color-neutrals-opaque-light-900: #0b0d12;
+

+
  --color-neutrals-opaque-dark-0: #00060f;
+
  --color-neutrals-opaque-dark-50: #0a1018;
+
  --color-neutrals-opaque-dark-100: #141a22;
+
  --color-neutrals-opaque-dark-150: #1f242c;
+
  --color-neutrals-opaque-dark-200: #29303a;
+
  --color-neutrals-opaque-dark-250: #33383f;
+
  --color-neutrals-opaque-dark-300: #3d4248;
+
  --color-neutrals-opaque-dark-400: #52565c;
+
  --color-neutrals-opaque-dark-500: #666a6f;
+
  --color-neutrals-opaque-dark-600: #8f9296;
+
  --color-neutrals-opaque-dark-700: #b8babc;
+
  --color-neutrals-opaque-dark-800: #e0e1e2;
+
  --color-neutrals-opaque-dark-900: #ffffff;
+

+
  --color-neutrals-alpha-light-50: #00060f0a;
+
  --color-neutrals-alpha-light-100: #00060f14;
+
  --color-neutrals-alpha-light-200: #00060f1f;
+
  --color-neutrals-alpha-light-300: #00060f29;
+
  --color-neutrals-alpha-light-400: #00060f3d;
+
  --color-neutrals-alpha-light-500: #00060f52;
+
  --color-neutrals-alpha-light-600: #00060f7a;
+
  --color-neutrals-alpha-light-700: #00060fa3;
+
  --color-neutrals-alpha-light-800: #00060fcc;
+
  --color-neutrals-alpha-light-900: #00060feb;
+

+
  --color-neutrals-alpha-dark-50: #ffffff0a;
+
  --color-neutrals-alpha-dark-100: #ffffff14;
+
  --color-neutrals-alpha-dark-200: #ffffff1f;
+
  --color-neutrals-alpha-dark-300: #ffffff29;
+
  --color-neutrals-alpha-dark-400: #ffffff3d;
+
  --color-neutrals-alpha-dark-500: #ffffff52;
+
  --color-neutrals-alpha-dark-600: #ffffff7a;
+
  --color-neutrals-alpha-dark-700: #ffffffa3;
+
  --color-neutrals-alpha-dark-800: #ffffffcc;
+
  --color-neutrals-alpha-dark-900: #ffffffeb;
+

+
  --color-accent-blue-0: #f4f9ff;
+
  --color-accent-blue-100: #d6e9ff;
+
  --color-accent-blue-200: #afcfff;
+
  --color-accent-blue-300: #7fb0ff;
+
  --color-accent-blue-400: #4d94ff;
+
  --color-accent-blue-500: #1c77ff;
+
  --color-accent-blue-600: #165fcc;
+
  --color-accent-blue-700: #104799;
+
  --color-accent-blue-800: #0b3266;
+
  --color-accent-blue-900: #061d33;
+

+
  --color-accent-green-0: #f7fff2;
+
  --color-accent-green-100: #dffcc6;
+
  --color-accent-green-200: #bef98a;
+
  --color-accent-green-300: #99f24c;
+
  --color-accent-green-400: #73e926;
+
  --color-accent-green-500: #58e600;
+
  --color-accent-green-600: #46ba00;
+
  --color-accent-green-700: #358f00;
+
  --color-accent-green-800: #256400;
+
  --color-accent-green-900: #0a1f00;
+

+
  --color-accent-cyan-0: #f1fefe;
+
  --color-accent-cyan-100: #ccf9fa;
+
  --color-accent-cyan-200: #99eff0;
+
  --color-accent-cyan-300: #66e5e7;
+
  --color-accent-cyan-400: #33dbde;
+
  --color-accent-cyan-500: #00d4da;
+
  --color-accent-cyan-600: #00a8ae;
+
  --color-accent-cyan-700: #007d82;
+
  --color-accent-cyan-800: #005256;
+
  --color-accent-cyan-900: #00292b;
+

+
  --color-accent-purple-0: #f7f5ff;
+
  --color-accent-purple-100: #e0daff;
+
  --color-accent-purple-200: #c0b5ff;
+
  --color-accent-purple-300: #a08fff;
+
  --color-accent-purple-400: #8a74fa;
+
  --color-accent-purple-500: #886bf2;
+
  --color-accent-purple-600: #6c54c2;
+
  --color-accent-purple-700: #503f91;
+
  --color-accent-purple-800: #352a61;
+
  --color-accent-purple-900: #1b1530;
+

+
  --color-accent-pink-0: #fff6ff;
+
  --color-accent-pink-100: #ffedff;
+
  --color-accent-pink-200: #ffdbff;
+
  --color-accent-pink-300: #ffc9ff;
+
  --color-accent-pink-400: #ffb7ff;
+
  --color-accent-pink-500: #ffa5ff;
+
  --color-accent-pink-600: #cc84cc;
+
  --color-accent-pink-700: #996399;
+
  --color-accent-pink-800: #664266;
+
  --color-accent-pink-900: #332133;
+

+
  --color-accent-emerald-0: #f2fcf9;
+
  --color-accent-emerald-100: #ccf4e4;
+
  --color-accent-emerald-200: #99e9ca;
+
  --color-accent-emerald-300: #66ddb0;
+
  --color-accent-emerald-400: #33d196;
+
  --color-accent-emerald-500: #009f67;
+
  --color-accent-emerald-600: #007f52;
+
  --color-accent-emerald-700: #005f3e;
+
  --color-accent-emerald-800: #004029;
+
  --color-accent-emerald-900: #002015;
+

+
  --color-accent-citrus-0: #fcfff2;
+
  --color-accent-citrus-100: #f1ffbf;
+
  --color-accent-citrus-200: #e4ff80;
+
  --color-accent-citrus-300: #d6ff40;
+
  --color-accent-citrus-400: #caff20;
+
  --color-accent-citrus-500: #ccff38;
+
  --color-accent-citrus-600: #a4cc2d;
+
  --color-accent-citrus-700: #7c991f;
+
  --color-accent-citrus-800: #536613;
+
  --color-accent-citrus-900: #2b3307;
+

+
  --color-accent-olive-0: #fafaed;
+
  --color-accent-olive-100: #e6e5ba;
+
  --color-accent-olive-200: #d1cf86;
+
  --color-accent-olive-300: #bdb950;
+
  --color-accent-olive-400: #9e9900;
+
  --color-accent-olive-500: #585600;
+
  --color-accent-olive-600: #464400;
+
  --color-accent-olive-700: #343300;
+
  --color-accent-olive-800: #232200;
+
  --color-accent-olive-900: #111100;
+

+
  --color-semantic-red-0: #fff5f5;
+
  --color-semantic-red-100: #ffd6d6;
+
  --color-semantic-red-200: #ffafaf;
+
  --color-semantic-red-300: #ff8686;
+
  --color-semantic-red-400: #ff5c5c;
+
  --color-semantic-red-500: #ff4d4f;
+
  --color-semantic-red-600: #cc3e3f;
+
  --color-semantic-red-700: #992f2f;
+
  --color-semantic-red-800: #661f20;
+
  --color-semantic-red-900: #330f10;
+

+
  --color-semantic-amber-0: #fffdf2;
+
  --color-semantic-amber-100: #fff1b8;
+
  --color-semantic-amber-200: #ffe58f;
+
  --color-semantic-amber-300: #ffd666;
+
  --color-semantic-amber-400: #ffc53d;
+
  --color-semantic-amber-500: #faad14;
+
  --color-semantic-amber-600: #d48806;
+
  --color-semantic-amber-700: #ad6800;
+
  --color-semantic-amber-800: #874d00;
+
  --color-semantic-amber-900: #613400;
+

+
  --color-semantic-green-0: #f6fff1;
+
  --color-semantic-green-100: #d9f7be;
+
  --color-semantic-green-200: #b7eb8f;
+
  --color-semantic-green-300: #95de64;
+
  --color-semantic-green-400: #73d13d;
+
  --color-semantic-green-500: #52c41a;
+
  --color-semantic-green-600: #3d9914;
+
  --color-semantic-green-700: #2e7010;
+
  --color-semantic-green-800: #1e470a;
+
  --color-semantic-green-900: #0f2305;
+

+
  --color-semantic-blue-0: #f0f9ff;
+
  --color-semantic-blue-100: #c6e4ff;
+
  --color-semantic-blue-200: #91caff;
+
  --color-semantic-blue-300: #5bafff;
+
  --color-semantic-blue-400: #2896ff;
+
  --color-semantic-blue-500: #1890ff;
+
  --color-semantic-blue-600: #1373cc;
+
  --color-semantic-blue-700: #0e5799;
+
  --color-semantic-blue-800: #093c66;
+
  --color-semantic-blue-900: #051f33;
+
}
added assets/css/app-typography.css
@@ -0,0 +1,192 @@
+
/* Booton is the brand display font for the app layout. JetBrains Mono is
+
   already declared in fonts.css for the legacy layouts; we redeclare here so
+
   this file can stand alone for the app layout. */
+
@font-face {
+
  font-family: "Booton";
+
  font-style: normal;
+
  font-weight: 400;
+
  font-display: swap;
+
  src: url("/assets/fonts/Booton-Regular.woff2");
+
}
+

+
@font-face {
+
  font-family: "Booton";
+
  font-weight: 468;
+
  font-display: swap;
+
  src: url("/assets/fonts/Booton-Medium.woff2");
+
}
+

+
@font-face {
+
  font-family: "Booton";
+
  font-weight: 600;
+
  font-display: swap;
+
  src: url("/assets/fonts/Booton-SemiBold.woff2");
+
}
+

+
@font-face {
+
  font-family: "JetBrains Mono";
+
  font-style: normal;
+
  font-weight: 400;
+
  font-display: swap;
+
  src: url("/assets/fonts/JetBrainsMono-Regular.woff2");
+
}
+

+
@font-face {
+
  font-family: "JetBrains Mono";
+
  font-style: normal;
+
  font-weight: 500;
+
  font-display: swap;
+
  src: url("/assets/fonts/JetBrainsMono-Medium.woff2");
+
}
+

+
@font-face {
+
  font-family: "JetBrains Mono";
+
  font-weight: 600;
+
  font-display: swap;
+
  src: url("/assets/fonts/JetBrainsMono-SemiBold.woff2");
+
}
+

+
@font-face {
+
  font-family: "JetBrains Mono";
+
  font-weight: 700;
+
  font-display: swap;
+
  src: url("/assets/fonts/JetBrainsMono-Bold.woff2");
+
}
+

+
:root {
+
  /* Bold */
+
  --txt-bold-48: 600 48px/52px Booton, sans-serif;
+
  --txt-bold-32: 600 32px/40px Booton, sans-serif;
+
  --txt-bold-24: 600 24px/32px Booton, sans-serif;
+
  --txt-bold-22: 600 22px/30px Booton, sans-serif;
+
  --txt-bold-18: 600 18px/24px Booton, sans-serif;
+
  --txt-bold-16: 600 16px/22px Booton, sans-serif;
+
  --txt-bold-14: 600 14px/20px Booton, sans-serif;
+
  --txt-bold-12: 600 12px/16px Booton, sans-serif;
+

+
  /* Medium */
+
  --txt-medium-32: 468 32px/40px Booton, sans-serif;
+
  --txt-medium-24: 468 24px/32px Booton, sans-serif;
+
  --txt-medium-22: 468 22px/30px Booton, sans-serif;
+
  --txt-medium-20: 468 20px/28px Booton, sans-serif;
+
  --txt-medium-18: 468 18px/24px Booton, sans-serif;
+
  --txt-medium-16: 468 16px/22px Booton, sans-serif;
+
  --txt-medium-14: 468 14px/20px Booton, sans-serif;
+
  --txt-medium-12: 468 12px/16px Booton, sans-serif;
+
}
+

+
[data-codefont="system"] {
+
  --txt-code-regular: 400 0.875rem/1.25rem monospace;
+
}
+

+
[data-codefont="jetbrains"] {
+
  --txt-code-regular: 400 0.875rem/1.25rem "JetBrains Mono";
+
}
+

+
/* The font-family rule below is intentionally scoped to .app-layout, not html,
+
   so legacy layouts (which set their own html font-family in fonts.css) are
+
   unaffected. */
+
.app-layout {
+
  -ms-text-size-adjust: 100%;
+
  -webkit-font-smoothing: antialiased;
+
  -webkit-text-size-adjust: 100%;
+
  font-family: Booton, sans-serif;
+
  font-feature-settings: "zero";
+
  font-size: 16px;
+
  font-weight: 400;
+
  line-height: 1.25;
+
}
+

+
/* Bold utility classes */
+
.txt-bold-48 {
+
  font: var(--txt-bold-48);
+
  letter-spacing: -0.02em;
+
}
+

+
.txt-bold-32 {
+
  font: var(--txt-bold-32);
+
  letter-spacing: -0.02em;
+
}
+

+
.txt-bold-24 {
+
  font: var(--txt-bold-24);
+
}
+

+
.txt-bold-22 {
+
  font: var(--txt-bold-22);
+
}
+

+
.txt-bold-18 {
+
  font: var(--txt-bold-18);
+
}
+

+
.txt-bold-16 {
+
  font: var(--txt-bold-16);
+
}
+

+
.txt-bold-14 {
+
  font: var(--txt-bold-14);
+
}
+

+
.txt-bold-12 {
+
  font: var(--txt-bold-12);
+
}
+

+
/* Medium utility classes */
+
.txt-medium-32 {
+
  font: var(--txt-medium-32);
+
  letter-spacing: -0.01em;
+
}
+

+
.txt-medium-24 {
+
  font: var(--txt-medium-24);
+
}
+

+
.txt-medium-22 {
+
  font: var(--txt-medium-22);
+
}
+

+
.txt-medium-20 {
+
  font: var(--txt-medium-20);
+
}
+

+
.txt-medium-18 {
+
  font: var(--txt-medium-18);
+
}
+

+
.txt-medium-16 {
+
  font: var(--txt-medium-16);
+
}
+

+
.txt-medium-14 {
+
  font: var(--txt-medium-14);
+
}
+

+
.txt-medium-12 {
+
  font: var(--txt-medium-12);
+
}
+

+
.txt-code-regular {
+
  font: var(--txt-code-regular);
+
}
+

+
.app-layout p {
+
  margin: 1rem 0;
+
}
+

+
.txt-color-tertiary {
+
  color: var(--color-text-tertiary);
+
}
+

+
.txt-emoji {
+
  height: 1em;
+
  width: 1em;
+
  margin: 0 0.05em 0 0.1em;
+
  vertical-align: -0.1em;
+
}
+

+
.txt-overflow {
+
  overflow: hidden;
+
  text-overflow: ellipsis;
+
  white-space: nowrap;
+
}
added assets/css/app.css
@@ -0,0 +1,982 @@
+
/* Global styles for the app layout. Only loaded by _layouts/app.html, so it
+
   does not interact with the legacy CSS in common.css / page.css / blog.css /
+
   guide.css. */
+

+
:root {
+
  font-family: system-ui, Inter, Helvetica, Arial, sans-serif;
+
  line-height: 1.5;
+
  font-weight: 400;
+

+
  background-color: var(--color-surface-base);
+
  color: var(--color-text-primary);
+

+
  font-synthesis: none;
+
  text-rendering: optimizeLegibility;
+
  -webkit-font-smoothing: antialiased;
+
  -moz-osx-font-smoothing: grayscale;
+

+
  --border-radius-tiny: 2px;
+
  --border-radius-small: 4px;
+
  --border-radius-regular: 8px;
+
  --border-radius-round: 10rem;
+

+
  --scrollbar-width: 0.5rem;
+

+
  --button-regular-height: 2.5rem;
+
  --button-small-height: 2rem;
+
  --button-tiny-height: 1.5rem;
+

+
  --section-gap: 10rem;
+
}
+

+
.app-layout a:-webkit-any-link:not(.button) {
+
  font-weight: 600;
+
  text-decoration: none;
+
  color: var(--color-text-primary);
+
}
+

+
.app-layout a:-webkit-any-link:not(.button):hover {
+
  color: var(--color-text-secondary);
+
  text-decoration: underline;
+
}
+

+
.app-layout a.arrow-link,
+
.app-layout button.arrow-link {
+
  display: inline-flex;
+
  align-items: center;
+
  gap: 0.2rem;
+
  text-decoration: none;
+
}
+

+
.app-layout a.arrow-link:hover,
+
.app-layout button.arrow-link:hover {
+
  text-decoration: none;
+
}
+

+
.app-layout a.arrow-link:active,
+
.app-layout button.arrow-link:active {
+
  filter: brightness(0.85);
+
}
+

+
.link-arrow {
+
  display: inline-block;
+
  transition: transform 0.15s ease;
+
}
+

+
.arrow-link:hover .link-arrow-right {
+
  transform: translateX(0.15rem);
+
}
+

+
.arrow-link:hover .link-arrow-down {
+
  transform: translateY(0.15rem);
+
}
+

+
.arrow-link:hover .link-arrow-up-right {
+
  transform: translate(0.15rem, -0.15rem);
+
}
+

+
/* CSS Reset — scoped so it does not affect legacy pages that share the same
+
   stylesheet bucket via the browser cache. */
+

+
.app-layout,
+
.app-layout *,
+
.app-layout *::before,
+
.app-layout *::after {
+
  box-sizing: border-box;
+
}
+

+
.app-layout * {
+
  margin: 0;
+
}
+

+
@media (prefers-reduced-motion: no-preference) {
+
  html:has(.app-layout) {
+
    interpolate-size: allow-keywords;
+
  }
+
}
+

+
body.app-layout {
+
  overscroll-behavior: none;
+
  line-height: 1.5;
+
  -webkit-font-smoothing: antialiased;
+
}
+

+
.app-layout img,
+
.app-layout picture,
+
.app-layout video,
+
.app-layout canvas,
+
.app-layout svg {
+
  display: block;
+
  max-width: 100%;
+
}
+

+
.app-layout input,
+
.app-layout button,
+
.app-layout textarea,
+
.app-layout select {
+
  font: inherit;
+
}
+

+
.app-layout p,
+
.app-layout h1,
+
.app-layout h2,
+
.app-layout h3,
+
.app-layout h4,
+
.app-layout h5,
+
.app-layout h6 {
+
  overflow-wrap: break-word;
+
}
+

+
.app-layout p {
+
  text-wrap: pretty;
+
}
+

+
.global-flex {
+
  display: flex;
+
  align-items: center;
+
  gap: 0.5rem;
+
}
+

+
/* Shared page styles */
+
.page-container {
+
  padding: 6rem 1.5rem;
+
}
+

+
.app-layout h1[id],
+
.app-layout h2[id],
+
.app-layout h3[id] {
+
  scroll-margin-top: 5rem;
+
}
+

+
.page-header {
+
  margin-bottom: 3rem;
+
}
+

+
.page-label {
+
  text-transform: uppercase;
+
  letter-spacing: 0.1em;
+
  color: var(--color-text-tertiary);
+
  margin-bottom: 0.5rem;
+
}
+

+
.page-title {
+
  margin: 0 0 0.5rem;
+
}
+

+
.page-subtitle {
+
  color: var(--color-text-secondary);
+
  margin: 0;
+
}
+

+
/* Card list styles */
+
.card-list {
+
  list-style: none;
+
  padding: 0;
+
  display: flex;
+
  flex-direction: column;
+
  gap: 1rem;
+
}
+

+
.card-link {
+
  display: block;
+
  padding: 1.5rem;
+
  border: 1px solid var(--color-border-subtle);
+
  border-radius: var(--border-radius-small);
+
  text-decoration: none;
+
  color: inherit;
+
  transition: border-color 0.2s;
+
}
+

+
.card-link:hover {
+
  border-color: var(--color-brand-hover);
+
  text-decoration: none;
+
}
+

+
.card-link strong {
+
  display: block;
+
  color: var(--color-text-primary);
+
  margin-bottom: 0.25rem;
+
}
+

+
.card-link span {
+
  color: var(--color-text-secondary);
+
}
+

+
/* Shared styles for releases and updates listing pages */
+
.updates-feed {
+
  display: flex;
+
  flex-direction: column;
+
  gap: 0;
+
  max-width: 48rem;
+
}
+

+
.updates-feed .card-link {
+
  border: none;
+
  padding: 0 0 var(--section-gap);
+
  border-radius: 0;
+
  transition: opacity 0.15s ease;
+
}
+

+
.updates-feed article:last-child .card-link {
+
  padding-bottom: 0;
+
}
+

+
.updates-feed:has(.card-link:hover) .card-link {
+
  opacity: 0.6;
+
}
+

+
.updates-feed:has(.card-link:hover) .card-link:hover {
+
  opacity: 1;
+
}
+

+
.updates-feed .title {
+
  margin: 0;
+
  line-height: 1.15;
+
}
+

+
.updates-feed .date {
+
  display: block;
+
  margin-top: 1rem;
+
  color: var(--color-text-tertiary);
+
}
+

+
.updates-feed .subtitle {
+
  margin: 1rem 0 0;
+
  color: var(--color-text-secondary);
+
  max-width: 60ch;
+
}
+

+
/* Shared markdown/prose styles — single source of truth for all rendered
+
   markdown inside the app layout. */
+
.markdown-content {
+
  max-width: 48rem;
+
  font-weight: 468;
+
  line-height: 1.7;
+
  position: relative;
+
}
+

+
.markdown-content aside {
+
  position: absolute;
+
  left: 100%;
+
  margin-left: 2rem;
+
  width: 16rem;
+
  font: var(--txt-medium-14);
+
  line-height: 1.6;
+
  color: var(--color-text-tertiary);
+
  background: none !important;
+
}
+

+
.markdown-content aside p {
+
  font: var(--txt-medium-14);
+
  line-height: 1.6;
+
}
+

+
.guide-hero {
+
  height: 20rem;
+
  object-fit: cover;
+
  border-radius: var(--border-radius-small);
+
  margin-bottom: 2rem;
+
}
+

+
.guides-markdown .markdown-content {
+
  max-width: min(66rem, 100%);
+
}
+

+
.guides-markdown .markdown-content > :not(aside) {
+
  max-width: none;
+
  width: min(42rem, calc(100% - clamp(15rem, 26vw, 20rem) - 2rem));
+
}
+

+
.guides-markdown .markdown-content aside {
+
  position: static;
+
  float: right;
+
  clear: right;
+
  margin-left: 0;
+
  margin-right: 0;
+
  margin-bottom: 1rem;
+
  margin-inline-start: 0;
+
  width: clamp(15rem, 26vw, 20rem);
+
  font: var(--txt-medium-14);
+
  line-height: 1.6;
+
  color: var(--color-text-tertiary);
+
  background: none !important;
+
  overflow-wrap: anywhere;
+
  word-break: break-word;
+
}
+

+
@media (max-width: 72rem) {
+
  .guides-markdown .markdown-content {
+
    max-width: min(62rem, 100%);
+
  }
+

+
  .guides-markdown .markdown-content > :not(aside) {
+
    width: min(38rem, calc(100% - clamp(13.5rem, 30vw, 17rem) - 1.25rem));
+
  }
+

+
  .guides-markdown .markdown-content aside {
+
    width: clamp(13.5rem, 30vw, 17rem);
+
    margin-inline-start: 1.25rem;
+
  }
+
}
+

+
@media (max-width: 56rem) {
+
  .guides-markdown .markdown-content > :not(aside) {
+
    width: min(36rem, calc(100% - clamp(12rem, 32vw, 15rem) - 1rem));
+
  }
+

+
  .guides-markdown .markdown-content aside {
+
    width: clamp(12rem, 32vw, 15rem);
+
    margin-inline-start: 1rem;
+
  }
+
}
+

+
@media (max-width: 44rem) {
+
  .guides-markdown .markdown-content {
+
    max-width: 48rem;
+
  }
+

+
  .guides-markdown .markdown-content > :not(aside) {
+
    max-width: 100%;
+
    width: 100%;
+
  }
+

+
  .guides-markdown .markdown-content aside {
+
    float: none;
+
    clear: none;
+
    width: auto;
+
    margin: 1rem 0;
+
    padding: 0.75rem 1rem;
+
    background: var(--color-surface-subtle) !important;
+
    overflow-wrap: anywhere;
+
  }
+

+
  .guides-markdown .markdown-content aside p {
+
    margin: 0;
+
  }
+
}
+

+
.markdown-content h1 {
+
  font: var(--txt-bold-32);
+
  letter-spacing: -0.02em;
+
  margin-bottom: 0.5rem;
+
}
+

+
.markdown-content h2 {
+
  font: var(--txt-bold-24);
+
  margin-top: 2.5rem;
+
  margin-bottom: 1rem;
+
}
+

+
.markdown-content h3 {
+
  font: var(--txt-bold-18);
+
  margin-top: 2rem;
+
  margin-bottom: 0.75rem;
+
}
+

+
.markdown-content h4 {
+
  font: var(--txt-bold-16);
+
  margin-top: 1.5rem;
+
  margin-bottom: 0.5rem;
+
}
+

+
.markdown-content p {
+
  font: var(--txt-medium-16);
+
  line-height: 1.7;
+
  margin-bottom: 1rem;
+
}
+

+
.markdown-content ul,
+
.markdown-content ol {
+
  font: var(--txt-medium-16);
+
  line-height: 1.7;
+
  padding-left: 1.5rem;
+
  margin-bottom: 1rem;
+
}
+

+
.markdown-content li {
+
  margin-bottom: 0.5rem;
+
}
+

+
.markdown-content strong {
+
  font-weight: 600;
+
}
+

+
.markdown-content code {
+
  font-family: "JetBrains Mono", monospace;
+
  font-size: 0.9em;
+
  background: var(--color-surface-subtle);
+
  padding: 0.125rem 0.375rem;
+
  border-radius: var(--border-radius-tiny);
+
}
+

+
.markdown-content pre {
+
  padding: 0.5rem 0.75rem;
+
  border-radius: var(--border-radius-tiny);
+
  overflow-x: auto;
+
  background: var(--color-surface-subtle) !important;
+
  margin-bottom: 1rem;
+
}
+

+
.markdown-content pre code {
+
  font-family: "JetBrains Mono", monospace;
+
  font-weight: 500;
+
  font-size: 0.875rem;
+
  line-height: 1.25rem;
+
  background: none;
+
  padding: 0;
+
}
+

+
.copy-code-button {
+
  position: absolute;
+
  top: 0.5rem;
+
  right: 0.5rem;
+
  display: flex;
+
  justify-content: center;
+
  align-items: center;
+
  padding: 0.25rem;
+
  background: none;
+
  border: none;
+
  border-radius: 0.125rem;
+
  cursor: pointer;
+
  opacity: 0;
+
  transition: opacity 0.15s;
+
}
+

+
.markdown-content pre:hover .copy-code-button {
+
  opacity: 1;
+
}
+

+
.copy-code-button:hover {
+
  background: var(--color-surface-subtle);
+
}
+

+
.markdown-content a {
+
  color: var(--color-brand-hover);
+
}
+

+
.markdown-content blockquote {
+
  border-left: 3px solid var(--color-border-mid);
+
  padding-left: 1rem;
+
  margin: 1rem 0;
+
  color: var(--color-text-secondary);
+
}
+

+
.markdown-content table {
+
  border-collapse: collapse;
+
  margin-bottom: 1rem;
+
  width: 100%;
+
}
+

+
.markdown-content th,
+
.markdown-content td {
+
  font: var(--txt-medium-14);
+
  line-height: 1.5;
+
  padding: 0.5rem 0.75rem;
+
  border: 1px solid var(--color-border-subtle);
+
  text-align: left;
+
}
+

+
.markdown-content th {
+
  font: var(--txt-bold-14);
+
  background: var(--color-surface-subtle);
+
}
+

+
.markdown-content hr {
+
  border: none;
+
  border-top: 1px solid var(--color-border-subtle);
+
  margin: 2rem 0;
+
}
+

+
.markdown-content figure {
+
  margin: 2rem 0;
+
}
+

+
.markdown-content figure img {
+
  max-width: 100%;
+
  height: auto;
+
}
+

+
.markdown-content figcaption {
+
  font: var(--txt-medium-14);
+
  color: var(--color-text-tertiary);
+
  margin-top: 0.75rem;
+
}
+

+
/* Inline link styles */
+
.inline-link {
+
  color: var(--color-brand-hover);
+
  text-decoration: none;
+
}
+

+
.inline-link:hover {
+
  text-decoration: underline;
+
}
+

+
/* Buttons (port of the Svelte Button component). */
+
.app-layout .button {
+
  display: inline-flex;
+
  flex-direction: row;
+
  justify-content: center;
+
  align-items: center;
+
  padding: 0.375rem 0.75rem;
+
  gap: 0.5rem;
+
  height: var(--button-small-height);
+
  border-radius: var(--border-radius-tiny);
+
  border: none;
+
  cursor: pointer;
+
  text-decoration: none;
+
  font: var(--txt-bold-14);
+
  text-align: center;
+
}
+

+
.app-layout .button:hover {
+
  text-decoration: none;
+
}
+

+
.app-layout .button.primary {
+
  background: var(--color-text-primary);
+
  color: var(--color-surface-base);
+
}
+

+
.app-layout .button.primary:hover {
+
  opacity: 0.9;
+
  color: var(--color-surface-base);
+
}
+

+
.app-layout .button.secondary {
+
  background: var(--color-surface-subtle);
+
  color: var(--color-text-primary);
+
}
+

+
.app-layout .button.secondary:hover {
+
  background: var(--color-surface-mid);
+
  color: var(--color-text-primary);
+
}
+

+
/* App shell layout (grid: header / sidebar + main). */
+
.app-shell {
+
  display: grid;
+
  grid-template-columns: 18rem 1fr;
+
  grid-template-rows: 4rem 1fr;
+
  grid-template-areas:
+
    "header header"
+
    "sidebar main";
+
  max-width: 90rem;
+
  margin: 0 auto;
+
}
+

+
.app-shell .topbar {
+
  grid-area: header;
+
  position: sticky;
+
  top: 0;
+
  z-index: 10;
+
  background: var(--color-surface-base);
+
}
+

+
.app-shell .sidebar {
+
  grid-area: sidebar;
+
  position: sticky;
+
  top: 4rem;
+
  height: calc(100vh - 4rem);
+
}
+

+
.app-shell .main-content {
+
  grid-area: main;
+
  display: flex;
+
  flex-direction: column;
+
  min-height: 0;
+
  min-width: 0;
+
}
+

+
@media (max-width: 80rem) {
+
  .app-shell {
+
    grid-template-columns: 14rem 1fr;
+
  }
+
}
+

+
@media (max-width: 56rem) {
+
  .app-shell {
+
    grid-template-columns: 1fr;
+
    grid-template-rows: 4rem 1fr;
+
    grid-template-areas:
+
      "header"
+
      "main";
+
  }
+

+
  .app-shell .sidebar {
+
    display: none;
+
  }
+
}
+

+
/* Topbar styles (port of TopBar.svelte). */
+
.app-shell .topbar {
+
  height: 4rem;
+
  display: grid;
+
  grid-template-columns: 16.5rem 1fr auto;
+
  align-items: center;
+
  padding: 1rem 1.5rem;
+
  z-index: 100;
+
}
+

+
.app-shell .topbar .logo {
+
  display: flex;
+
  align-items: center;
+
  color: var(--color-text-primary);
+
}
+

+
.app-shell .topbar .logo svg {
+
  height: 1rem;
+
  width: auto;
+
}
+

+
.app-shell .topbar .spacer {
+
  min-width: 0;
+
}
+

+
.app-shell .topbar .mobile-menu-button {
+
  display: none;
+
  width: 2.5rem;
+
  height: 2.5rem;
+
  align-items: center;
+
  justify-content: center;
+
  border: none;
+
  border-radius: var(--border-radius-small);
+
  background: var(--color-surface-mid);
+
  color: var(--color-text-primary);
+
  cursor: pointer;
+
}
+

+
.app-shell .topbar .mobile-menu-button:hover {
+
  opacity: 0.85;
+
}
+

+
.mobile-menu-icon {
+
  display: block;
+
  overflow: visible;
+
  transition: transform 0.2s ease;
+
  transform-origin: center;
+
}
+

+
.mobile-menu-icon circle {
+
  transition: transform 0.2s ease;
+
}
+

+
.mobile-menu-icon.open {
+
  transform: rotate(45deg);
+
}
+

+
.mobile-menu-icon.open .dot-left {
+
  transform: translateX(-1px);
+
}
+

+
.mobile-menu-icon.open .dot-right {
+
  transform: translateX(1px);
+
}
+

+
.mobile-menu-icon.open .dot-top {
+
  transform: translateY(-1px);
+
}
+

+
.mobile-menu-icon.open .dot-bottom {
+
  transform: translateY(1px);
+
}
+

+
.mobile-nav-overlay {
+
  display: none;
+
  position: fixed;
+
  top: 4rem;
+
  right: 0;
+
  bottom: 0;
+
  left: 0;
+
  z-index: 90;
+
  background: var(--color-surface-base);
+
  padding: 1.5rem;
+
  overflow-y: auto;
+
}
+

+
.mobile-nav-overlay.open {
+
  display: block;
+
}
+

+
.mobile-nav-content {
+
  min-height: calc(100dvh - 7rem);
+
  display: flex;
+
  flex-direction: column;
+
  justify-content: space-between;
+
  gap: 2rem;
+
}
+

+
.mobile-nav-main {
+
  display: flex;
+
  flex-direction: column;
+
  gap: 1.5rem;
+
  list-style: none;
+
  padding: 0;
+
  margin: 0;
+
}
+

+
.mobile-nav-card {
+
  width: 100%;
+
  display: grid;
+
  grid-template-columns: 1fr 5.5rem;
+
  align-items: center;
+
  gap: 1rem;
+
  padding: 1rem;
+
  border-radius: var(--border-radius-small);
+
  color: var(--color-text-primary);
+
  text-decoration: none;
+
  background: var(--color-surface-subtle);
+
  min-height: 7.5rem;
+
}
+

+
.mobile-nav-card:hover {
+
  opacity: 0.92;
+
}
+

+
.mobile-nav-card.install {
+
  background: var(--color-accent-green-500);
+
  color: var(--color-neutrals-opaque-light-900);
+
}
+

+
.mobile-nav-card.learn {
+
  background: var(--color-accent-purple-500);
+
  color: var(--color-text-on-brand);
+
}
+

+
.mobile-nav-card.explore {
+
  background: var(--color-accent-blue-500);
+
  color: var(--color-text-on-brand);
+
}
+

+
.mobile-nav-card.garden {
+
  background: var(--color-accent-citrus-500);
+
  color: var(--color-neutrals-opaque-light-900);
+
}
+

+
.mobile-nav-text {
+
  display: flex;
+
  flex-direction: column;
+
  gap: 0.375rem;
+
}
+

+
.mobile-nav-title {
+
  font: var(--txt-bold-16);
+
  line-height: 1.05;
+
  letter-spacing: -0.02em;
+
}
+

+
.mobile-nav-description {
+
  font: var(--txt-medium-16);
+
  line-height: 1.3;
+
}
+

+
.mobile-nav-thumb {
+
  width: 5.5rem;
+
  height: 5.5rem;
+
  border-radius: var(--border-radius-tiny);
+
  background-size: cover;
+
  background-position: center;
+
}
+

+
@media (max-width: 56rem) {
+
  .app-shell .topbar {
+
    grid-template-columns: 1fr auto;
+
  }
+

+
  .app-shell .topbar .spacer {
+
    display: none;
+
  }
+

+
  .app-shell .topbar .button {
+
    display: none;
+
  }
+

+
  .app-shell .topbar .mobile-menu-button {
+
    display: inline-flex;
+
  }
+
}
+

+
body.app-layout:has(.mobile-nav-overlay.open) {
+
  overflow: hidden;
+
}
+

+
/* Sidebar styles (port of Sidebar.svelte). */
+
.app-shell .sidebar {
+
  display: flex;
+
  flex-direction: column;
+
  justify-content: space-between;
+
  padding: 6rem 1.5rem 1.5rem 1.5rem;
+
  gap: 1rem;
+
  overflow-y: auto;
+
}
+

+
.sidebar .top-section {
+
  display: flex;
+
  flex-direction: column;
+
  gap: 1.5rem;
+
}
+

+
.sidebar .nav-section {
+
  display: flex;
+
  flex-direction: column;
+
  gap: 0.5rem;
+
}
+

+
.sidebar .section-header {
+
  font: var(--txt-bold-16);
+
  letter-spacing: -0.005em;
+
  color: var(--color-text-tertiary);
+
  text-decoration: none;
+
  position: relative;
+
}
+

+
.sidebar .section-header:hover {
+
  color: var(--color-text-secondary);
+
}
+

+
.sidebar .section-header.active {
+
  color: var(--color-text-primary);
+
}
+

+
.sidebar .nav-links {
+
  display: flex;
+
  flex-direction: column;
+
  gap: 0.5rem;
+
}
+

+
.sidebar .nav-link {
+
  font: var(--txt-bold-16);
+
  letter-spacing: -0.005em;
+
  color: var(--color-text-tertiary);
+
  text-decoration: none;
+
  padding-left: 0.5rem;
+
  position: relative;
+
}
+

+
.sidebar .nav-link:hover {
+
  color: var(--color-text-secondary);
+
}
+

+
.sidebar .nav-link.active {
+
  color: var(--color-text-primary);
+
}
+

+
.sidebar .nav-link .dot {
+
  left: calc(0.5rem - 0.75rem);
+
}
+

+
.sidebar .external-links {
+
  display: flex;
+
  flex-direction: column;
+
  gap: 1.5rem;
+
}
+

+
.sidebar .external-link {
+
  font: var(--txt-bold-16);
+
  letter-spacing: -0.005em;
+
  color: var(--color-text-tertiary);
+
  text-decoration: none;
+
}
+

+
.sidebar .external-link:hover {
+
  color: var(--color-text-secondary);
+
}
+

+
.sidebar .bottom-section {
+
  display: flex;
+
  flex-direction: column;
+
  gap: 1rem;
+
}
+

+
.sidebar .bottom-link {
+
  font: var(--txt-bold-16);
+
  letter-spacing: -0.005em;
+
  color: var(--color-text-tertiary);
+
  text-decoration: none;
+
  position: relative;
+
}
+

+
.sidebar .bottom-link:hover {
+
  color: var(--color-text-secondary);
+
}
+

+
.sidebar .bottom-link.active {
+
  color: var(--color-text-primary);
+
}
+

+
.sidebar .dot {
+
  width: 5px;
+
  height: 5px;
+
  border-radius: 50%;
+
  background-color: var(--color-accent-blue-500);
+
  position: absolute;
+
  left: -0.75rem;
+
  top: 50%;
+
  transform: translateY(-50%);
+
}
+

+
/* Footer styles (port of Footer.svelte). */
+
.app-footer {
+
  display: flex;
+
  flex-direction: column;
+
  padding: 6rem 1.5rem 1rem 1rem;
+
  gap: 6rem;
+
  margin-top: auto;
+
  background: var(--color-surface-base);
+
}
+

+
.app-footer .footer-links {
+
  display: flex;
+
  flex-direction: row;
+
  justify-content: space-between;
+
  gap: 2rem;
+
}
+

+
.app-footer .link-column {
+
  display: flex;
+
  flex-direction: column;
+
  gap: 0.75rem;
+
  flex: 1;
+
}
+

+
.app-footer .column-header {
+
  color: var(--color-text-tertiary);
+
}
+

+
.app-footer .link-column a {
+
  color: var(--color-text-primary);
+
  text-decoration: none;
+
}
+

+
.app-footer .link-column a:hover {
+
  color: var(--color-text-secondary);
+
}
+

+
.app-footer .footer-bottom {
+
  display: flex;
+
  flex-direction: row;
+
  justify-content: space-between;
+
  align-items: center;
+
}
+

+
.app-footer .copyright {
+
  color: var(--color-text-tertiary);
+
}
+

+
@media (max-width: 64rem) {
+
  .app-footer {
+
    padding: 2rem 1.5rem 1.5rem;
+
    gap: 2.5rem;
+
  }
+

+
  .app-footer .footer-links {
+
    display: grid;
+
    grid-template-columns: 1fr 1fr;
+
    gap: 1.5rem;
+
  }
+

+
  .app-footer .footer-bottom {
+
    flex-direction: column;
+
    align-items: flex-start;
+
  }
+
}
added assets/fonts/Booton-Medium.woff2
added assets/fonts/Booton-Regular.woff2
added assets/fonts/Booton-SemiBold.woff2
added assets/js/app-menu.js
@@ -0,0 +1,23 @@
+
// Mobile menu toggle for the app layout (port of TopBar.svelte's reactive
+
// mobileMenuOpen state).
+
(function () {
+
  var btn = document.querySelector("[data-mobile-menu-toggle]");
+
  var overlay = document.querySelector("[data-mobile-menu]");
+
  if (!btn || !overlay) return;
+

+
  var icon = btn.querySelector(".mobile-menu-icon");
+

+
  function setOpen(open) {
+
    overlay.classList.toggle("open", open);
+
    if (icon) icon.classList.toggle("open", open);
+
    btn.setAttribute("aria-expanded", open ? "true" : "false");
+
    btn.setAttribute(
+
      "aria-label",
+
      open ? "Close navigation menu" : "Open navigation menu"
+
    );
+
  }
+

+
  btn.addEventListener("click", function () {
+
    setOpen(!overlay.classList.contains("open"));
+
  });
+
})();
added assets/js/code-copy.js
@@ -0,0 +1,38 @@
+
// Adds a copy-to-clipboard button to every <pre> inside .markdown-content.
+
// Port of MarkdownLayout.svelte's addCopyButtons action.
+
(function () {
+
  var pres = document.querySelectorAll(".markdown-content pre");
+
  if (!pres.length) return;
+

+
  pres.forEach(function (pre) {
+
    pre.style.position = "relative";
+

+
    var btn = document.createElement("button");
+
    btn.className = "copy-code-button";
+
    btn.type = "button";
+
    btn.setAttribute("aria-label", "Copy to clipboard");
+
    btn.textContent = "Copy";
+

+
    var timeout;
+
    btn.addEventListener("click", function () {
+
      var code = pre.querySelector("code");
+
      var text = code ? code.innerText : pre.innerText;
+
      navigator.clipboard
+
        .writeText(text)
+
        .then(function () {
+
          btn.textContent = "Copied";
+
          btn.setAttribute("aria-label", "Copied to clipboard");
+
          clearTimeout(timeout);
+
          timeout = setTimeout(function () {
+
            btn.textContent = "Copy";
+
            btn.setAttribute("aria-label", "Copy to clipboard");
+
          }, 2000);
+
        })
+
        .catch(function (err) {
+
          console.error("Failed to copy:", err);
+
        });
+
    });
+

+
    pre.appendChild(btn);
+
  });
+
})();