Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 96 additions & 0 deletions site/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion site/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@
"type": "module",
"scripts": {
"dev": "astro dev",
"build": "astro build",
"build": "astro build && npx pagefind --site dist",
"preview": "astro preview"
},
"dependencies": {
"@astrojs/mdx": "^5.0.2",
"@astrojs/sitemap": "^3.7.1",
"@tailwindcss/vite": "^4.2.2",
"astro": "^6.0.8",
"pagefind": "^1.4.0",
"tailwindcss": "^4.2.2"
}
}
16 changes: 16 additions & 0 deletions site/src/components/DocsSearch.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
// This component renders only a trigger button.
// The search modal is rendered once in Base.astro via SearchModal.
---
<button
data-search-trigger
class="flex items-center gap-2 px-3 py-1.5 text-xs text-on-surface-variant border border-outline-variant bg-surface-container-low hover:border-outline transition-colors font-mono w-full lg:w-48"
type="button"
aria-label="Search documentation"
>
<svg class="w-3.5 h-3.5 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
<span class="flex-1 text-left">Search docs...</span>
<kbd class="hidden lg:inline text-[10px] text-on-surface-variant/60 border border-outline-variant/50 px-1 rounded-sm">⌘K</kbd>
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
</button>
60 changes: 35 additions & 25 deletions site/src/components/DocsSidebar.astro
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
import { docsNav } from '../data/docs-nav';
import type { NavItem } from '../data/docs-nav';
import DocsSearch from './DocsSearch.astro';

interface Props {
currentPath: string;
Expand All @@ -20,6 +21,9 @@ function isParentActive(item: NavItem): boolean {

<aside id="docs-sidebar" class="docs-sidebar hidden lg:block w-60 shrink-0">
<div class="sticky top-16 h-[calc(100vh-4rem)] overflow-y-auto py-8 pr-4 pl-2">
<div class="mb-6">
<DocsSearch />
</div>
<nav aria-label="Documentation sidebar">
{docsNav.map((group) => (
<div class="mb-8">
Expand Down Expand Up @@ -93,7 +97,7 @@ function isParentActive(item: NavItem): boolean {
<div id="sidebar-overlay" class="fixed inset-0 z-40 bg-black/50 hidden lg:hidden" aria-hidden="true"></div>

<!-- Mobile sidebar drawer -->
<aside id="docs-sidebar-mobile" class="fixed top-16 left-0 bottom-0 z-50 w-72 bg-surface-container-lowest border-r border-outline-variant transform -translate-x-full transition-transform duration-200 lg:hidden overflow-y-auto">
<aside id="docs-sidebar-mobile" class="fixed top-16 left-0 bottom-14 md:bottom-0 z-50 w-72 bg-surface-container-lowest border-r border-outline-variant transform -translate-x-full transition-transform duration-200 lg:hidden overflow-y-auto">
<nav class="py-6 px-4" aria-label="Documentation sidebar mobile">
{docsNav.map((group) => (
<div class="mb-8">
Expand All @@ -111,7 +115,7 @@ function isParentActive(item: NavItem): boolean {
<a
href={item.href}
class:list={[
'flex items-center justify-between px-3 py-1.5 text-[13px] transition-colors',
'flex items-center justify-between px-3 py-2 text-sm transition-colors',
active
? 'text-primary font-medium border-l-2 border-primary bg-primary/5'
: 'text-on-surface-variant hover:text-on-surface border-l-2 border-transparent hover:border-outline-variant',
Expand Down Expand Up @@ -140,7 +144,7 @@ function isParentActive(item: NavItem): boolean {
<a
href={child.href}
class:list={[
'block pl-6 pr-3 py-1 text-[12px] transition-colors',
'block pl-6 pr-3 py-1.5 text-[13px] transition-colors',
childActive
? 'text-primary font-medium border-l-2 border-primary bg-primary/5'
: 'text-on-surface-variant/80 hover:text-on-surface border-l-2 border-outline-variant/30 hover:border-outline-variant',
Expand All @@ -159,38 +163,44 @@ function isParentActive(item: NavItem): boolean {
</ul>
</div>
))}

<!-- Site navigation -->
<div class="mt-4 pt-4 border-t border-outline-variant">
<h4 class="text-[10px] font-headline font-bold uppercase tracking-[0.15em] text-on-surface-variant/60 mb-3 px-3">
Site
</h4>
<ul>
<li>
<a href="/" class="flex items-center gap-2 px-3 py-2 text-sm text-on-surface-variant hover:text-on-surface transition-colors">
Home
</a>
</li>
<li>
<a href="https://github.com/dvflw/mantle" class="flex items-center gap-2 px-3 py-2 text-sm text-on-surface-variant hover:text-on-surface transition-colors">
GitHub
</a>
Comment thread
michaelmcnees marked this conversation as resolved.
</li>
</ul>
</div>
</nav>
</aside>

<script is:inline>
function initSidebar() {
const toggleBtn = document.getElementById('sidebar-toggle');
const mobileSidebar = document.getElementById('docs-sidebar-mobile');
const overlay = document.getElementById('sidebar-overlay');
if (!toggleBtn || !mobileSidebar || !overlay) return;

function openSidebar() {
mobileSidebar.classList.remove('-translate-x-full');
overlay.classList.remove('hidden');
}
var mobileSidebar = document.getElementById('docs-sidebar-mobile');
if (!mobileSidebar) return;

function closeSidebar() {
mobileSidebar.classList.add('-translate-x-full');
overlay.classList.add('hidden');
var overlay = document.getElementById('sidebar-overlay');
if (overlay) overlay.classList.add('hidden');
}

const newBtn = toggleBtn.cloneNode(true);
toggleBtn.parentNode?.replaceChild(newBtn, toggleBtn);
newBtn.addEventListener('click', () => {
const isOpen = !mobileSidebar.classList.contains('-translate-x-full');
if (isOpen) closeSidebar(); else openSidebar();
});

const newOverlay = overlay.cloneNode(true);
overlay.parentNode?.replaceChild(newOverlay, overlay);
newOverlay.addEventListener('click', closeSidebar);
var overlay = document.getElementById('sidebar-overlay');
if (overlay) overlay.onclick = closeSidebar;

mobileSidebar.querySelectorAll('a').forEach((a) => {
a.addEventListener('click', closeSidebar);
mobileSidebar.querySelectorAll('a').forEach(function(a) {
a.onclick = closeSidebar;
});
}
initSidebar();
Expand Down
78 changes: 57 additions & 21 deletions site/src/components/Nav.astro
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
---
import DocsSearch from './DocsSearch.astro';

interface Props {
isHome?: boolean;
}
const { isHome = false } = Astro.props;
const prefix = isHome ? '' : '/';
const isDocs = !isHome;
const isDocs = Astro.url.pathname.startsWith('/docs');
---
<nav class="fixed top-0 left-0 right-0 z-50 border-b border-outline-variant bg-surface/80 backdrop-blur-md">
<div class={`nav-inner mx-auto px-6 flex items-center justify-between h-16 ${isHome ? 'max-w-[1200px]' : 'max-w-[1280px]'}`}>
Expand All @@ -21,6 +23,13 @@ const isDocs = !isHome;
<a href="/docs/getting-started" class:list={["text-sm transition-colors", isDocs ? "text-primary font-medium" : "text-on-surface-variant hover:text-on-surface"]}>Docs</a>
</div>

<!-- Search (desktop, docs pages only) -->
{isDocs && (
<div class="hidden md:block">
<DocsSearch />
</div>
)}

Copilot AI Mar 23, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isDocs is derived as !isHome, so non-docs pages like /404 are treated as docs: the desktop search input shows up and the mobile “Menu” button renders but cannot work (no docs sidebar on that page). Determine isDocs from the current path (e.g., Astro.url.pathname.startsWith('/docs')) or pass an explicit isDocs prop from layouts/pages that need it.

Copilot uses AI. Check for mistakes.

<!-- Right side -->
<div class="flex items-center gap-4">
<a href="https://github.com/dvflw/mantle" class="text-on-surface-variant hover:text-on-surface transition-colors" aria-label="GitHub">
Expand All @@ -31,39 +40,66 @@ const isDocs = !isHome;
</a>
</div>

<!-- Mobile hamburger -->
<button id="mobile-menu-btn" class="md:hidden text-on-surface-variant hover:text-on-surface" aria-label="Menu">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"/></svg>
</button>
</div>
</nav>

<!-- Mobile menu -->
<div id="mobile-menu" class="hidden md:hidden border-t border-outline-variant bg-surface/95 backdrop-blur-md">
<div class="px-6 py-4 flex flex-col gap-3">
<a href={`${prefix}#how-it-works`} class="text-sm text-on-surface-variant hover:text-on-surface transition-colors">How It Works</a>
<a href={`${prefix}#connectors`} class="text-sm text-on-surface-variant hover:text-on-surface transition-colors">Connectors</a>
<a href={`${prefix}#comparison`} class="text-sm text-on-surface-variant hover:text-on-surface transition-colors">Compare</a>
<a href="/docs/getting-started" class:list={["text-sm transition-colors", isDocs ? "text-primary font-medium" : "text-on-surface-variant hover:text-on-surface"]}>Docs</a>
<a href={`${prefix}#get-started`} class="inline-flex justify-center px-4 py-2 bg-primary text-on-primary text-sm font-medium mt-2">Get Started</a>
</div>
<!-- Mobile bottom navigation bar -->
<nav id="mobile-bottom-nav" class="fixed bottom-0 left-0 right-0 z-50 md:hidden border-t border-outline-variant bg-surface/95 backdrop-blur-md">
<div class="flex items-center justify-around h-14 px-2">
<a href="/" class:list={["bottom-nav-item flex flex-col items-center gap-0.5 px-3 py-1 text-[11px] transition-colors", isHome ? "text-primary" : "text-on-surface-variant hover:text-on-surface"]}>
<svg class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M2.25 12l8.954-8.955a1.126 1.126 0 011.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25"/></svg>
<span>Home</span>
</a>
{isDocs && (
<button id="bottom-nav-docs-btn" class="bottom-nav-item flex flex-col items-center gap-0.5 px-3 py-1 text-[11px] text-on-surface-variant hover:text-on-surface transition-colors" aria-label="Documentation menu">
<svg class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5"/></svg>
<span>Menu</span>
</button>
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
)}
<a href="/docs/getting-started" class:list={["bottom-nav-item flex flex-col items-center gap-0.5 px-3 py-1 text-[11px] transition-colors", isDocs ? "text-primary" : "text-on-surface-variant hover:text-on-surface"]}>
<svg class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M12 6.042A8.967 8.967 0 006 3.75c-1.052 0-2.062.18-3 .512v14.25A8.987 8.987 0 016 18c2.305 0 4.408.867 6 2.292m0-14.25a8.966 8.966 0 016-2.292c1.052 0 2.062.18 3 .512v14.25A8.987 8.987 0 0018 18a8.967 8.967 0 00-6 2.292m0-14.25v14.25"/></svg>
<span>Docs</span>
</a>
<a href="https://github.com/dvflw/mantle" class="bottom-nav-item flex flex-col items-center gap-0.5 px-3 py-1 text-[11px] text-on-surface-variant hover:text-on-surface transition-colors" aria-label="GitHub">
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24"><path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z"/></svg>
<span>GitHub</span>
</a>
</div>
</nav>
Comment on lines +37 to 59

Copilot AI Mar 23, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The mobile bottom nav is fixed and will overlay page content unless every page/layout adds bottom padding. This PR adds padding for the home page and Docs layout, but other pages (e.g. 404) still use min-h-screen without a bottom offset and can be obscured on small screens. Consider moving the bottom padding into a shared layout (e.g. Base) or conditionally applying it whenever #mobile-bottom-nav is present.

Copilot uses AI. Check for mistakes.

<style>
.nav-inner {
transition: max-width 0.3s ease;
}
#mobile-bottom-nav {
padding-bottom: env(safe-area-inset-bottom, 0px);
}
.bottom-nav-item {
-webkit-tap-highlight-color: transparent;
min-width: 3.5rem;
}
.bottom-nav-item:active {
opacity: 0.7;
}
</style>

<script is:inline>
function initNav() {
const btn = document.getElementById('mobile-menu-btn');
const menu = document.getElementById('mobile-menu');
if (!btn || !menu) return;
// Remove old listeners by cloning
const newBtn = btn.cloneNode(true);
btn.parentNode?.replaceChild(newBtn, btn);
newBtn.addEventListener('click', () => menu.classList.toggle('hidden'));
var btn = document.getElementById('bottom-nav-docs-btn');
var docsSidebar = document.getElementById('docs-sidebar-mobile');
if (!btn || !docsSidebar) return;

btn.onclick = function() {
var overlay = document.getElementById('sidebar-overlay');
var isOpen = !docsSidebar.classList.contains('-translate-x-full');
if (isOpen) {
docsSidebar.classList.add('-translate-x-full');
if (overlay) overlay.classList.add('hidden');
} else {
docsSidebar.classList.remove('-translate-x-full');
if (overlay) overlay.classList.remove('hidden');
}
Comment on lines 77 to +100

Copilot AI Mar 23, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

initNav() captures overlay once, but DocsSidebar later replaces #sidebar-overlay via cloneNode in its own initializer. That leaves this click handler toggling classes on a detached overlay node, so the overlay may never appear. Avoid replacing the overlay element in DocsSidebar, or re-query #sidebar-overlay inside the click handler / re-run init after DocsSidebar’s setup so both components act on the same element.

Copilot uses AI. Check for mistakes.
};
}
initNav();
document.addEventListener('astro:after-swap', initNav);
Expand Down
Loading
Loading