diff --git a/docs/site/docusaurus.config.js b/docs/site/docusaurus.config.js index d4524451db..d4e4a9d1cd 100644 --- a/docs/site/docusaurus.config.js +++ b/docs/site/docusaurus.config.js @@ -93,6 +93,7 @@ const config = { clientModules: [ "./src/client/webmcp.js", + "./src/client/kapa-sidebar.js", ], onBrokenLinks: "throw", @@ -297,6 +298,9 @@ const config = { "data-project-name": "Walrus Knowledge", "data-project-color": "#37c3b0ff", "data-button-hide": "true", + "data-view-mode": "sidebar", + "data-modal-overlay-hidden": "true", + "data-modal-lock-scroll": "false", "data-modal-title": "Ask Walrus AI", "data-modal-ask-ai-input-placeholder": "Ask me anything about Walrus!", "data-modal-example-questions": diff --git a/docs/site/src/client/kapa-sidebar.js b/docs/site/src/client/kapa-sidebar.js new file mode 100644 index 0000000000..636d3928b1 --- /dev/null +++ b/docs/site/src/client/kapa-sidebar.js @@ -0,0 +1,85 @@ +// Copyright (c) Walrus Foundation +// SPDX-License-Identifier: Apache-2.0 + +// Detects Kapa sidebar open/close and toggles .kapa-sidebar-open on . +// Kapa renders in Shadow DOM so we can't query its internals. +// Strategy: hook Kapa.open for instant open detection, then use +// elementFromPoint to detect close (checks if right edge of screen +// is covered by a non-docusaurus element). + +if (typeof window !== "undefined") { + const OPEN_CLASS = "kapa-sidebar-open"; + let kapaOpen = false; + let hookedRef = null; + + function syncClass() { + document.documentElement.classList.toggle(OPEN_CLASS, kapaOpen); + } + + function hookKapa() { + if (!window.Kapa || !window.Kapa.open || window.Kapa.open === hookedRef) + return; + + const origOpen = window.Kapa.open; + const origClose = window.Kapa.close; + + window.Kapa.open = function (...args) { + kapaOpen = true; + syncClass(); + return origOpen.apply(this, args); + }; + + window.Kapa.close = function (...args) { + kapaOpen = false; + syncClass(); + return origClose.apply(this, args); + }; + + hookedRef = window.Kapa.open; + } + + // Check if Kapa sidebar is covering the right side of the viewport. + // Since Kapa uses Shadow DOM, we can't query its elements directly. + // Instead, check if the element at the right edge of the screen + // belongs to the doc app or to something else (Kapa's panel). + function isSidebarVisible() { + const x = window.innerWidth - 50; + const y = window.innerHeight / 2; + const el = document.elementFromPoint(x, y); + if (!el) return false; + const docRoot = document.getElementById("__docusaurus"); + if (docRoot && docRoot.contains(el)) return false; + if (el === document.body || el === document.documentElement) return false; + return true; + } + + // Hook into History API so the class persists across SPA navigation. + // Docusaurus uses pushState for client-side routing. + const origPush = history.pushState.bind(history); + const origReplace = history.replaceState.bind(history); + history.pushState = function (...args) { + const r = origPush(...args); + syncClass(); + return r; + }; + history.replaceState = function (...args) { + const r = origReplace(...args); + syncClass(); + return r; + }; + window.addEventListener("popstate", syncClass); + + // Poll every 300ms: re-hook Kapa if it reinitializes, and + // detect open/close state via elementFromPoint fallback. + setInterval(() => { + hookKapa(); + + const visible = isSidebarVisible(); + if (visible && !kapaOpen) { + kapaOpen = true; + } else if (!visible && kapaOpen) { + kapaOpen = false; + } + syncClass(); + }, 300); +} diff --git a/docs/site/src/components/LandingPage.tsx b/docs/site/src/components/LandingPage.tsx index f620b62f37..b6b997a3f0 100644 --- a/docs/site/src/components/LandingPage.tsx +++ b/docs/site/src/components/LandingPage.tsx @@ -1,9 +1,11 @@ // Copyright (c) Walrus Foundation // SPDX-License-Identifier: Apache-2.0 -import React, { useEffect } from 'react'; +import React, { useEffect, useState } from 'react'; +import { createPortal } from 'react-dom'; import Head from '@docusaurus/Head'; import WalrusLogo from '@site/static/img/Walrus_Docs.svg'; +import SearchModal from '@site/src/components/Search/SearchModal'; function hideEl(el: HTMLElement): () => void { const prev = el.style.display; @@ -129,225 +131,143 @@ html, body { background: #0d0f12 !important; } /* ── Hero ── */ .landing-root .hero { - position: relative; padding: 72px 0 56px; overflow: hidden; + position: relative; padding: 48px 0 24px; overflow: hidden; background: var(--black); } .landing-root .hero-inner { - position: relative; z-index: 5; max-width: 1500px; - margin-bottom: 28px; padding-top: 20px; -} -.landing-root .hero-badge { - display: inline-block; - font-family: var(--mono); font-size: 0.72rem; font-weight: 500; - letter-spacing: 0.08em; text-transform: uppercase; - color: var(--purple); margin-bottom: 16px; + position: relative; z-index: 5; + margin-bottom: 0; padding-top: 0; + text-align: center; opacity: 0; animation: landingFadeIn 0.5s ease forwards 0.1s; } -.landing-root .hero h1 { - font-size: clamp(2rem, 5vw, 3rem); - font-weight: 500; line-height: 1.08; letter-spacing: -0.025em; - margin-bottom: 14px; color: var(--white); - opacity: 0; animation: landingFadeIn 0.6s ease forwards 0.2s; +.landing-root .hero-title { + font-size: clamp(1.75rem, 4vw, 2.5rem); + font-weight: 600; line-height: 1.15; letter-spacing: -0.03em; + margin: 0 0 12px; color: var(--white); } -.landing-root .hero p { - font-size: 1rem; color: var(--white); line-height: 1.6; - max-width: 1500px; - opacity: 0; animation: landingFadeIn 0.6s ease forwards 0.35s; +.landing-root .hero-sub { + font-size: 1rem; color: var(--white); opacity: 0.55; + line-height: 1.55; max-width: 560px; margin: 0 auto; } -/* ── Quick-start cards ── */ -.landing-root .quickstart { - display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; - padding-bottom: 15px; - opacity: 0; animation: landingFadeIn 0.6s ease forwards 0.5s; +/* ── Product card grid (Mem0-inspired) ── */ +.landing-root .product-grid { + display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px; + padding: 32px 0 0; + opacity: 0; animation: landingFadeIn 0.6s ease forwards 0.3s; } -@media (max-width: 900px) { - .landing-root .quickstart { grid-template-columns: repeat(2, 1fr); } +@media (max-width: 640px) { + .landing-root .product-grid { grid-template-columns: 1fr; } } -@media (max-width: 520px) { - .landing-root .quickstart { grid-template-columns: 1fr; } -} -.landing-root .qs-card { + +.landing-root .product-card { background: var(--surface); border: 1px solid var(--border); - border-radius: var(--radius); padding: 22px 18px; - transition: all 0.25s ease; cursor: pointer; - display: flex; flex-direction: column; gap: 8px; -} -.landing-root .qs-card:hover { - border-color: var(--purple); background: var(--surface-hover); + border-radius: 14px; padding: 0; + transition: all 0.2s ease; cursor: pointer; + display: flex; flex-direction: column; + text-decoration: none !important; + overflow: hidden; +} +.landing-root .product-card:hover { + border-color: rgba(255,255,255,0.18); transform: translateY(-2px); - box-shadow: 0 8px 32px rgba(0,0,0,0.3); -} -.landing-root .qs-card .qs-icon { - width: 34px; height: 34px; border-radius: 9px; - background: var(--purple-dim); - display: flex; align-items: center; justify-content: center; -} -.landing-root .qs-card .qs-icon svg { - width: 17px; height: 17px; color: var(--purple); -} -.landing-root .qs-card h3 { - font-size: 0.9rem; font-weight: 600; - line-height: 1.3; margin: 0; color: var(--white); -} -.landing-root .qs-card p { - font-size: 0.8rem; color: var(--white); - line-height: 1.45; flex: 1; margin: 0; -} -.landing-root .qs-card .qs-arrow { - font-size: 0.75rem; color: var(--purple); font-weight: 500; - display: flex; align-items: center; gap: 4px; margin-top: 2px; -} -.landing-root .qs-card .qs-arrow svg { width: 10px; height: 10px; } - -/* ── Divider ── */ -.landing-root .divider { - border: none; border-top: 1px solid var(--border); margin: 0; -} - -/* ── Section heading ── */ -.landing-root .section-head { - padding: 20px 0 10px; - display: flex; align-items: baseline; gap: 12px; -} -.landing-root .section-head .mono-label { - font-family: var(--mono); font-size: 1.5rem; font-weight: 500; - letter-spacing: 0.1em; text-transform: uppercase; - color: var(--purple); flex-shrink: 0; -} -.landing-root .section-head h2 { - font-size: 1.4rem; font-weight: 500; - letter-spacing: -0.015em; margin: 0; color: var(--white); + box-shadow: 0 8px 24px rgba(0,0,0,0.25); } -/* ── Capabilities ── */ -.landing-root .cap-grid { - display: grid; grid-template-columns: 1fr 1fr; - gap: 10px; padding-bottom: 15px; -} -@media (max-width: 700px) { - .landing-root .cap-grid { grid-template-columns: 1fr; } -} -.landing-root .cap-card { - background: var(--surface); border: 1px solid var(--border); - border-radius: var(--radius); padding: 24px 22px; - transition: border-color 0.2s, background 0.2s; -} -.landing-root .cap-card:hover { - border-color: var(--border-hover); - background: var(--surface-hover); -} -.landing-root .cap-card h3 { - font-size: 0.9rem; font-weight: 600; - margin: 0 0 6px 0; color: var(--white); - display: flex; align-items: center; gap: 8px; +.landing-root .product-card-thumb { + height: 120px; + background: linear-gradient(135deg, rgba(202,177,255,0.06) 0%, rgba(152,239,221,0.04) 100%); + display: flex; align-items: center; justify-content: center; + border-bottom: 1px solid var(--border); } -.landing-root .cap-card h3 .tag { - font-family: var(--mono); font-size: 0.62rem; font-weight: 500; - color: var(--purple); background: var(--purple-dim); - padding: 2px 7px; border-radius: 4px; letter-spacing: 0.03em; +.landing-root .product-card-thumb svg { + width: 48px; height: 48px; color: var(--purple); opacity: 0.5; } -.landing-root .cap-card p { - font-size: 0.85rem; color: var(--white); - line-height: 1.55; margin: 0; +.landing-root .product-card-thumb--muted { + background: linear-gradient(135deg, rgba(255,255,255,0.02) 0%, rgba(255,255,255,0.01) 100%); } -.landing-root .cap-card .cap-detail { - margin-top: 10px; padding-top: 10px; - border-top: 1px solid var(--border); - font-family: var(--mono); font-size: 0.72rem; - color: var(--white); line-height: 1.6; opacity: 0.5; +.landing-root .product-card-thumb--muted svg { + color: var(--gray-muted); opacity: 0.3; } -/* ── Use-case rows ── */ -.landing-root .usecase-grid { - display: grid; grid-template-columns: repeat(4, 1fr); - gap: 10px; padding-bottom: 15px; -} -@media (max-width: 900px) { - .landing-root .usecase-grid { grid-template-columns: repeat(2, 1fr); } -} -@media (max-width: 520px) { - .landing-root .usecase-grid { grid-template-columns: 1fr; } -} -.landing-root .uc-card { +.landing-root .product-card h3 { + font-size: 0.95rem; font-weight: 700; + line-height: 1.3; margin: 0; + color: var(--white); + padding: 16px 18px 0; +} +.landing-root .product-card p { + font-size: 0.82rem; color: var(--white); opacity: 0.5; + line-height: 1.5; margin: 0; + padding: 6px 18px 0; + flex: 1; +} +.landing-root .product-card .under-hood { + font-style: italic; opacity: 0.6; +} +.landing-root .coming-soon-badge { + font-size: 0.6rem; font-weight: 500; letter-spacing: 0.03em; + text-transform: uppercase; opacity: 0.5; + background: rgba(255,255,255,0.08); border: 1px solid rgba(255,255,255,0.1); + border-radius: 4px; padding: 2px 6px; margin-left: 8px; + vertical-align: middle; +} +.landing-root .product-card .product-arrow { + display: flex; align-items: center; justify-content: flex-end; + padding: 12px 18px; + color: var(--purple); opacity: 0.4; + transition: opacity 0.2s ease; +} +.landing-root .product-card:hover .product-arrow { opacity: 1; } +.landing-root .product-card .product-arrow svg { width: 12px; height: 12px; } +.landing-root .product-card--muted { opacity: 0.7; } +.landing-root .product-card--muted:hover { opacity: 1; } + +/* ── Landing search bar ── */ +.landing-root .landing-search { + max-width: 560px; margin: 0 auto; + padding: 20px 0 0; + opacity: 0; animation: landingFadeIn 0.5s ease forwards 0.2s; +} +.landing-root .landing-search-btn { + width: 100%; display: flex; align-items: center; gap: 14px; background: var(--surface); border: 1px solid var(--border); - border-radius: var(--radius); padding: 22px 18px; - transition: border-color 0.2s; -} -.landing-root .uc-card:hover { - border-color: var(--border-hover); -} -.landing-root .uc-card h4 { - font-size: 0.85rem; font-weight: 600; - margin: 0 0 8px 0; color: var(--white); -} -.landing-root .uc-card ul { - list-style: none; padding: 0; margin: 0; -} -.landing-root .uc-card li { - font-size: 0.78rem; color: var(--white); line-height: 1.5; - padding: 2px 0 2px 14px; position: relative; opacity: 0.7; + border-radius: var(--radius); padding: 18px 24px; + cursor: pointer; transition: all 0.2s ease; + font-family: var(--sans); } -.landing-root .uc-card li::before { - content: ''; position: absolute; left: 0; top: 9px; - width: 4px; height: 4px; border-radius: 50%; - background: var(--purple); opacity: 0.6; +.landing-root .landing-search-btn:hover { + border-color: rgba(255,255,255,0.16); background: var(--surface-hover); + box-shadow: 0 6px 24px rgba(0,0,0,0.25); } - -.landing-root .hero-lead { - font-size: 1.1rem; - line-height: 1.5; - margin-bottom: 1px; - color: var(--white); +.landing-root .landing-search-btn svg { + width: 20px; height: 20px; color: var(--purple); flex-shrink: 0; } - -.landing-root .hero-body { - font-size: 0.9rem; - line-height: 1.6; - margin-bottom: 1px; - color: var(--white); - opacity: 0.6; +.landing-root .landing-search-btn span { + font-size: 1rem; color: rgba(255,255,255,0.35); font-weight: 400; } - -.landing-root .hero-features { - list-style: none; - padding: 0; - margin: 0; - display: flex; - gap: 24px; - flex-wrap: wrap; +.landing-root .landing-search-btn kbd { + margin-left: auto; font-family: var(--sans); + font-size: 0.72rem; color: rgba(255,255,255,0.25); + background: rgba(255,255,255,0.06); border: 1px solid rgba(255,255,255,0.08); + border-radius: 6px; padding: 3px 9px; font-weight: 500; } -.landing-root .hero-features li { - font-size: 0.85rem; - font-weight: 500; - color: var(--white); - display: flex; - align-items: center; - gap: 8px; +/* ── Search modal z-index fix ── */ +.landing-root .fixed.inset-0, +.landing-root [class*="z-500"] { + z-index: 9999 !important; } -.landing-root .hero-features li::before { - content: ''; - width: 6px; - height: 6px; - border-radius: 50%; - background: var(--purple); - flex-shrink: 0; +/* ── Divider ── */ +.landing-root .divider { + border: none; border-top: 1px solid var(--border); margin: 0; } -/* ── Not-for ── */ -.landing-root .notfor { padding-bottom: 64px; } -.landing-root .notfor-row { display: flex; gap: 8px; flex-wrap: wrap; } -.landing-root .notfor-chip { - font-size: 0.8rem; color: var(--white); opacity: 0.7; - background: var(--surface); border: 1px solid var(--border); - border-radius: 10px; padding: 9px 16px; -} /* ── Footer ── */ .landing-root .page-footer { - border-top: 1px solid var(--border); padding: 36px 0; + border-top: 1px solid var(--border); margin-top: 80px; padding: 36px 0; display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 16px; @@ -378,6 +298,8 @@ html, body { background: #0d0f12 !important; } `; export default function LandingPage() { + const [searchOpen, setSearchOpen] = useState(false); + useEffect(() => { const cleanups: Array<() => void> = []; @@ -421,9 +343,21 @@ export default function LandingPage() { .querySelectorAll('.landing-root .scroll-reveal') .forEach((el) => obs.observe(el)); + // "/" key opens the search bar + function handleKey(e: KeyboardEvent) { + if (e.key === '/' && !e.metaKey && !e.ctrlKey + && !(e.target instanceof HTMLInputElement) + && !(e.target instanceof HTMLTextAreaElement)) { + e.preventDefault(); + setSearchOpen(true); + } + } + document.addEventListener('keydown', handleKey); + return () => { cleanups.forEach((fn) => fn()); obs.disconnect(); + document.removeEventListener('keydown', handleKey); }; }, []); @@ -454,346 +388,84 @@ export default function LandingPage() {