Initial public push: docs cosmos v4 + AI module + framework groundwork
This is the snapshot the production landing site (nibiru-framework.com) is deployed from. Brings together the recent splash + docs migration to the v4 "Cosmos" design system, the new in-framework AI module, and the framework groundwork that backs the framework-reference extraction. What lands: - docs/: Astro + Starlight site with the v4 dark cosmic palette, GalaxyHero canvas constellation, Mission Control chat (wired to /api/oracle → api.neuronetz.ai via providers.mjs Ollama), 5-panel MMVC stage (Model · AI · Module · Controller · View), translated EN/DE/JA/ES/FR content, PWA + sitemap + llms.txt + Umami analytics. - docs/design-system/: canonical mockup bundle (source/index-v2.html for splash, source/docs-system.html + preview/ for docs, SPEC.md, tokens). - docs/scripts/extraction/framework-reference-v2.md: deep framework reference (~1.6k lines, file:line citations, every public factory and idiom — basis for the LoRA training corpus. - application/module/ai/: AI module with chat / embed / RAG / agent plugins, plus pdoQuery / httpGet / fileRead tools and Modelfile + smoke-test in training/. - application/module/users/: user / ACL / form-factory traits used as the reference plugin pattern for the framework docs. - application/settings/config/database/: schema + seed migrations including the AI module tables (200–203). - Form factory + autogenerator changes the framework-reference-v2 covers. Production secrets stay out: docs/.env, settings.production.ini and ai.production.ini are all gitignored (.example files are in tree). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
File diff suppressed because one or more lines are too long
BIN
docs/design-system/source/docs-system/assets/lotus.png
Normal file
BIN
docs/design-system/source/docs-system/assets/lotus.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 151 KiB |
BIN
docs/design-system/source/docs-system/assets/nibiru-full.png
Normal file
BIN
docs/design-system/source/docs-system/assets/nibiru-full.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 258 KiB |
1103
docs/design-system/source/docs-system/components.css
Normal file
1103
docs/design-system/source/docs-system/components.css
Normal file
File diff suppressed because it is too large
Load Diff
291
docs/design-system/source/docs-system/components/extras.jsx
Normal file
291
docs/design-system/source/docs-system/components/extras.jsx
Normal file
@@ -0,0 +1,291 @@
|
||||
/* global React */
|
||||
const { useState: useEState } = React;
|
||||
|
||||
// ============================================================
|
||||
// TABS
|
||||
// ============================================================
|
||||
function Tabs({ tabs }) {
|
||||
const [active, setActive] = useEState(0);
|
||||
return (
|
||||
<div className="tabs">
|
||||
<div className="tabs-list" role="tablist">
|
||||
{tabs.map((t, i) => (
|
||||
<button
|
||||
key={i}
|
||||
role="tab"
|
||||
aria-selected={i === active}
|
||||
className={"tabs-trigger" + (i === active ? " active" : "")}
|
||||
onClick={() => setActive(i)}
|
||||
>
|
||||
{t.icon && <span className="tabs-icon">{t.icon}</span>}
|
||||
{t.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="tabs-panel">
|
||||
{tabs[active].content}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// API BLOCK (parameter list)
|
||||
// ============================================================
|
||||
function ApiBlock({ signature, params = [], returns }) {
|
||||
return (
|
||||
<div className="api-block">
|
||||
<div className="api-signature">
|
||||
<span className="tk-fn">{signature.name}</span>
|
||||
<span className="api-paren">(</span>
|
||||
{signature.args.map((a, i) => (
|
||||
<React.Fragment key={i}>
|
||||
<span className="tk-var">{a.name}</span>
|
||||
<span className="api-colon">: </span>
|
||||
<span className="tk-cn">{a.type}</span>
|
||||
{i < signature.args.length - 1 && <span className="api-comma">, </span>}
|
||||
</React.Fragment>
|
||||
))}
|
||||
<span className="api-paren">)</span>
|
||||
{signature.returns && (
|
||||
<>
|
||||
<span className="api-arrow"> → </span>
|
||||
<span className="tk-cn">{signature.returns}</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="api-section-label">Parameters</div>
|
||||
<ul className="api-params">
|
||||
{params.map((p, i) => (
|
||||
<li key={i} className="api-param">
|
||||
<div className="api-param-head">
|
||||
<span className="api-param-name">{p.name}</span>
|
||||
<span className="api-param-type">{p.type}</span>
|
||||
{p.required && <span className="api-param-required">required</span>}
|
||||
{p.default !== undefined && <span className="api-param-default">default: <code>{p.default}</code></span>}
|
||||
</div>
|
||||
<div className="api-param-desc">{p.desc}</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
{returns && (
|
||||
<>
|
||||
<div className="api-section-label">Returns</div>
|
||||
<div className="api-returns">
|
||||
<span className="tk-cn">{returns.type}</span>
|
||||
<span className="api-returns-desc"> — {returns.desc}</span>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// CARD GRID (feature cards)
|
||||
// ============================================================
|
||||
function CardGrid({ cards }) {
|
||||
return (
|
||||
<div className="card-grid">
|
||||
{cards.map((c, i) => (
|
||||
<a href="#" key={i} className="feature-card">
|
||||
<div className="feature-card-icon" style={{ background: c.glow }}>
|
||||
{c.icon}
|
||||
</div>
|
||||
<div className="feature-card-title">{c.title}</div>
|
||||
<div className="feature-card-desc">{c.desc}</div>
|
||||
<div className="feature-card-arrow">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<path d="M7 17 17 7M7 7h10v10"/>
|
||||
</svg>
|
||||
</div>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// SEARCH MODAL
|
||||
// ============================================================
|
||||
function SearchModal() {
|
||||
const recent = ["Routing", "Form::addInputType", "Migrations"];
|
||||
const results = [
|
||||
{ section: "The Framework", title: "Routing", excerpt: "URL-pattern + SEO-URL parsing, soft 404, automatic action lookup.", kind: "page" },
|
||||
{ section: "The Framework", title: "Routing › Soft 404", excerpt: "When a route doesn't match, the dispatcher falls through to a soft 404 view.", kind: "section" },
|
||||
{ section: "Get Started", title: "Quick Start › Define a route", excerpt: "Routes are declared in config/routes.php and matched by URL pattern.", kind: "section" },
|
||||
{ section: "API", title: "Router::register()", excerpt: "Register a new route at runtime. Supports pattern matching with named parameters.", kind: "api" },
|
||||
];
|
||||
return (
|
||||
<div className="search-modal">
|
||||
<div className="search-input-wrap">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" className="search-icon">
|
||||
<circle cx="11" cy="11" r="8"/>
|
||||
<path d="m21 21-4.35-4.35"/>
|
||||
</svg>
|
||||
<input className="search-input" placeholder="Search docs, API, examples..." defaultValue="rout" />
|
||||
<kbd className="search-kbd">esc</kbd>
|
||||
</div>
|
||||
<div className="search-recent">
|
||||
<div className="search-section-label">Recent</div>
|
||||
<div className="search-recent-list">
|
||||
{recent.map((r, i) => (
|
||||
<button key={i} className="search-chip">
|
||||
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg>
|
||||
{r}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="search-results">
|
||||
<div className="search-section-label">Results</div>
|
||||
{results.map((r, i) => (
|
||||
<button key={i} className={"search-result" + (i === 0 ? " active" : "")}>
|
||||
<div className={"search-result-kind kind-" + r.kind}>{r.kind}</div>
|
||||
<div className="search-result-body">
|
||||
<div className="search-result-title">
|
||||
<span className="search-result-section">{r.section}</span>
|
||||
<span className="search-result-sep">›</span>
|
||||
<span dangerouslySetInnerHTML={{ __html: r.title.replace(/(rout)/i, "<mark>$1</mark>") }} />
|
||||
</div>
|
||||
<div className="search-result-excerpt">{r.excerpt}</div>
|
||||
</div>
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" className="search-result-go">
|
||||
<path d="M7 17 17 7M7 7h10v10"/>
|
||||
</svg>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="search-footer">
|
||||
<span><kbd>↑↓</kbd> navigate</span>
|
||||
<span><kbd>↵</kbd> open</span>
|
||||
<span><kbd>⌘K</kbd> close</span>
|
||||
<div className="search-credit">Powered by <strong>Nibiru Search</strong></div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// FAB (floating help button)
|
||||
// ============================================================
|
||||
function FAB() {
|
||||
return (
|
||||
<div className="fab-wrap">
|
||||
<button className="fab" aria-label="Help">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
|
||||
<path d="M12 5v14M5 12h14"/>
|
||||
</svg>
|
||||
</button>
|
||||
<div className="fab-tooltip">Ask AI assistant</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// PAGINATION (prev / next page)
|
||||
// ============================================================
|
||||
function Pagination({ prev, next }) {
|
||||
return (
|
||||
<nav className="page-nav">
|
||||
{prev ? (
|
||||
<a href="#" className="page-nav-link page-nav-prev">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="m15 18-6-6 6-6"/></svg>
|
||||
<div className="page-nav-stack">
|
||||
<div className="page-nav-label">Previous</div>
|
||||
<div className="page-nav-title">{prev}</div>
|
||||
</div>
|
||||
</a>
|
||||
) : <div />}
|
||||
{next ? (
|
||||
<a href="#" className="page-nav-link page-nav-next">
|
||||
<div className="page-nav-stack">
|
||||
<div className="page-nav-label">Next</div>
|
||||
<div className="page-nav-title">{next}</div>
|
||||
</div>
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="m9 18 6-6-6-6"/></svg>
|
||||
</a>
|
||||
) : <div />}
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// MOBILE DRAWER
|
||||
// ============================================================
|
||||
function MobileDrawer() {
|
||||
return (
|
||||
<div className="mobile-drawer">
|
||||
<div className="mobile-drawer-header">
|
||||
<div className="mobile-drawer-brand">
|
||||
<LotusMark size={26} />
|
||||
<span>Nibiru</span>
|
||||
</div>
|
||||
<button className="mobile-drawer-close" aria-label="Close">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="m18 6-12 12M6 6l12 12"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<button className="mobile-drawer-search">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/></svg>
|
||||
<span>Search</span>
|
||||
<kbd>⌘K</kbd>
|
||||
</button>
|
||||
<Sidebar />
|
||||
<div className="mobile-drawer-footer">
|
||||
<button className="mobile-drawer-pill">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41"/></svg>
|
||||
Theme
|
||||
</button>
|
||||
<button className="mobile-drawer-pill">EN</button>
|
||||
<a href="#" className="mobile-drawer-pill">GitHub ↗</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 404 / EMPTY STATE
|
||||
// ============================================================
|
||||
function NotFound() {
|
||||
return (
|
||||
<div className="not-found">
|
||||
<div className="not-found-orbit">
|
||||
<svg viewBox="0 0 320 200" width="100%" height="100%" aria-hidden="true">
|
||||
<defs>
|
||||
<radialGradient id="lostStar" cx="50%" cy="50%" r="50%">
|
||||
<stop offset="0%" stopColor="#fff4d9"/>
|
||||
<stop offset="50%" stopColor="#ffb574"/>
|
||||
<stop offset="100%" stopColor="#b86bff" stopOpacity="0"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
{/* dashed orbit */}
|
||||
<ellipse cx="160" cy="100" rx="140" ry="60" fill="none" stroke="rgba(184,107,255,0.3)" strokeWidth="1" strokeDasharray="3 6"/>
|
||||
<ellipse cx="160" cy="100" rx="100" ry="42" fill="none" stroke="rgba(184,107,255,0.18)" strokeWidth="1" strokeDasharray="3 6"/>
|
||||
{/* central star */}
|
||||
<circle cx="160" cy="100" r="50" fill="url(#lostStar)"/>
|
||||
<circle cx="160" cy="100" r="6" fill="#fff4d9"/>
|
||||
{/* drifting probe (off-orbit) */}
|
||||
<g transform="translate(280 40)">
|
||||
<circle r="3" fill="#6ad9ff"/>
|
||||
<circle r="8" fill="#6ad9ff" opacity="0.2"/>
|
||||
</g>
|
||||
<text x="290" y="35" fill="#6ad9ff" fontSize="10" fontFamily="JetBrains Mono">PROBE-404</text>
|
||||
<line x1="160" y1="100" x2="280" y2="40" stroke="#6ad9ff" strokeWidth="1" strokeDasharray="2 3" opacity="0.4"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div className="not-found-code">404 / off-orbit</div>
|
||||
<h2 className="not-found-title">This page drifted away from Nibiru.</h2>
|
||||
<p className="not-found-desc">
|
||||
The probe couldn't find the page you requested. It may have been moved, renamed, or pulled into a different orbit.
|
||||
</p>
|
||||
<div className="not-found-actions">
|
||||
<a href="#" className="btn btn-primary">Return to docs</a>
|
||||
<a href="#" className="btn btn-ghost">Open search</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Object.assign(window, { Tabs, ApiBlock, CardGrid, SearchModal, FAB, Pagination, MobileDrawer, NotFound });
|
||||
192
docs/design-system/source/docs-system/components/navigation.jsx
Normal file
192
docs/design-system/source/docs-system/components/navigation.jsx
Normal file
@@ -0,0 +1,192 @@
|
||||
/* global React */
|
||||
const { useState } = React;
|
||||
|
||||
// ============================================================
|
||||
// TOP NAV
|
||||
// ============================================================
|
||||
function TopNav({ theme = "dark", onToggleTheme = () => {}, locale = "EN" }) {
|
||||
return (
|
||||
<header className="topnav">
|
||||
<div className="topnav-brand">
|
||||
<LotusMark />
|
||||
<div className="topnav-brand-text">
|
||||
<div className="topnav-brand-name">Nibiru</div>
|
||||
<div className="topnav-brand-tag">Create, Invent, Impress</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button className="topnav-search" aria-label="Search docs">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<circle cx="11" cy="11" r="8"/>
|
||||
<path d="m21 21-4.35-4.35"/>
|
||||
</svg>
|
||||
<span>Search</span>
|
||||
<kbd>⌘ K</kbd>
|
||||
</button>
|
||||
|
||||
<div className="topnav-right">
|
||||
<a href="#" className="topnav-icon" aria-label="GitHub">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M12 .5C5.65.5.5 5.65.5 12c0 5.08 3.29 9.39 7.86 10.91.58.1.79-.25.79-.56v-1.97c-3.2.69-3.87-1.54-3.87-1.54-.52-1.33-1.27-1.69-1.27-1.69-1.04-.71.08-.7.08-.7 1.15.08 1.76 1.18 1.76 1.18 1.02 1.75 2.68 1.25 3.34.96.1-.74.4-1.25.72-1.54-2.55-.29-5.24-1.28-5.24-5.69 0-1.26.45-2.29 1.18-3.1-.12-.29-.51-1.46.11-3.04 0 0 .96-.31 3.15 1.18a10.95 10.95 0 0 1 5.74 0c2.18-1.49 3.14-1.18 3.14-1.18.62 1.58.23 2.75.11 3.04.74.81 1.18 1.84 1.18 3.1 0 4.42-2.69 5.39-5.26 5.68.41.36.78 1.06.78 2.13v3.16c0 .31.21.67.8.56C20.21 21.39 23.5 17.08 23.5 12 23.5 5.65 18.35.5 12 .5Z"/>
|
||||
</svg>
|
||||
</a>
|
||||
<button className="topnav-icon" aria-label="Toggle theme" onClick={onToggleTheme}>
|
||||
{theme === "dark" ? (
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>
|
||||
</svg>
|
||||
) : (
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<circle cx="12" cy="12" r="4"/>
|
||||
<path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41"/>
|
||||
</svg>
|
||||
)}
|
||||
</button>
|
||||
<button className="topnav-locale" aria-label="Locale">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<circle cx="12" cy="12" r="10"/>
|
||||
<path d="M2 12h20M12 2a15 15 0 0 1 0 20M12 2a15 15 0 0 0 0 20"/>
|
||||
</svg>
|
||||
<span>{locale}</span>
|
||||
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
|
||||
<path d="m6 9 6 6 6-6"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
function LotusMark({ size = 36 }) {
|
||||
// Real Nibiru logo, sourced from window.NIBIRU_LOTUS so the bundler picks it up
|
||||
// (it would miss a relative path inside a JSX template string).
|
||||
const src = (typeof window !== "undefined" && window.NIBIRU_LOTUS) || "docs-system/assets/lotus.png";
|
||||
return (
|
||||
<img
|
||||
src={src}
|
||||
width={size}
|
||||
height={Math.round(size * 569 / 861)}
|
||||
alt="Nibiru"
|
||||
className="lotus-mark"
|
||||
style={{ display: "block" }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// SIDEBAR
|
||||
// ============================================================
|
||||
function Sidebar() {
|
||||
const [openSections, setOpenSections] = useState({
|
||||
"get-started": true, "framework": true, "cli": false, "advanced": false,
|
||||
});
|
||||
const toggle = (id) => setOpenSections((s) => ({ ...s, [id]: !s[id] }));
|
||||
|
||||
const nav = [
|
||||
{
|
||||
id: "get-started", label: "Get Started",
|
||||
items: [
|
||||
{ label: "What is Nibiru?", active: true },
|
||||
{ label: "Why Nibiru, not Laravel" },
|
||||
{ label: "Installation" },
|
||||
{ label: "Quick Start" },
|
||||
{ label: "Project Structure" },
|
||||
{ label: "Run It Locally" },
|
||||
{ label: "Deployment" },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "framework", label: "The Framework",
|
||||
items: [
|
||||
{ label: "Architecture (MMVC)" },
|
||||
{ label: "Bootstrap & Dispatcher" },
|
||||
{ label: "Routing" },
|
||||
{ label: "Controllers" },
|
||||
{ label: "Views & Smarty" },
|
||||
{ label: "Models" },
|
||||
{ label: "Modules", badge: "core" },
|
||||
{ label: "Forms" },
|
||||
{ label: "Database & Migrations" },
|
||||
{ label: "Auth" },
|
||||
{ label: "Config & Settings" },
|
||||
{ label: "Pagination" },
|
||||
{ label: "Registry" },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "cli", label: "CLI",
|
||||
items: [
|
||||
{ label: "nibiru new" },
|
||||
{ label: "nibiru migrate" },
|
||||
{ label: "nibiru make" },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "advanced", label: "Advanced",
|
||||
items: [
|
||||
{ label: "Plugins" },
|
||||
{ label: "Observer pattern" },
|
||||
{ label: "Caching", badge: "new" },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<aside className="sidebar">
|
||||
<nav>
|
||||
{nav.map((section) => (
|
||||
<div key={section.id} className="sidebar-section">
|
||||
<button
|
||||
className="sidebar-heading"
|
||||
onClick={() => toggle(section.id)}
|
||||
aria-expanded={openSections[section.id]}
|
||||
>
|
||||
<span>{section.label}</span>
|
||||
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"
|
||||
style={{ transform: openSections[section.id] ? "rotate(0deg)" : "rotate(-90deg)", transition: "transform 200ms" }}>
|
||||
<path d="m6 9 6 6 6-6"/>
|
||||
</svg>
|
||||
</button>
|
||||
{openSections[section.id] && (
|
||||
<ul className="sidebar-list">
|
||||
{section.items.map((item) => (
|
||||
<li key={item.label}>
|
||||
<a href="#" className={"sidebar-item" + (item.active ? " active" : "")}>
|
||||
<span>{item.label}</span>
|
||||
{item.badge && <span className={"sidebar-badge sidebar-badge-" + item.badge}>{item.badge}</span>}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</nav>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// RIGHT TOC ("On this page")
|
||||
// ============================================================
|
||||
function RightTOC({ items, activeId }) {
|
||||
return (
|
||||
<aside className="toc">
|
||||
<div className="toc-label">On this page</div>
|
||||
<ul className="toc-list">
|
||||
{items.map((item) => (
|
||||
<li key={item.id} className={"toc-item toc-level-" + item.level}>
|
||||
<a href={"#" + item.id} className={item.id === activeId ? "active" : ""}>
|
||||
{item.label}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<div className="toc-edit">
|
||||
<a href="#"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M12 20h9M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4Z"/></svg> Edit this page</a>
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
|
||||
Object.assign(window, { TopNav, Sidebar, RightTOC, LotusMark });
|
||||
@@ -0,0 +1,166 @@
|
||||
/* global React */
|
||||
const { useState: useTState } = React;
|
||||
|
||||
// ============================================================
|
||||
// PAGE HEADER (breadcrumbs, title, last-updated)
|
||||
// ============================================================
|
||||
function PageHeader({ crumbs = [], title, lastUpdated, summary }) {
|
||||
return (
|
||||
<header className="page-header">
|
||||
<nav className="breadcrumbs" aria-label="Breadcrumb">
|
||||
{crumbs.map((c, i) => (
|
||||
<React.Fragment key={i}>
|
||||
<a href="#">{c}</a>
|
||||
{i < crumbs.length - 1 && <span className="breadcrumb-sep">/</span>}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</nav>
|
||||
<h1 className="page-title">{title}</h1>
|
||||
{summary && <p className="page-summary">{summary}</p>}
|
||||
{lastUpdated && (
|
||||
<div className="page-meta">
|
||||
<span className="page-meta-dot" />
|
||||
Updated {lastUpdated}
|
||||
</div>
|
||||
)}
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// PROSE — typography sample block
|
||||
// ============================================================
|
||||
function Prose({ children }) {
|
||||
return <div className="prose">{children}</div>;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// CODE BLOCK (Mission Control aesthetic)
|
||||
// ============================================================
|
||||
function CodeBlock({ filename, lang = "php", children, lineNumbers = false }) {
|
||||
const [copied, setCopied] = useTState(false);
|
||||
const onCopy = () => {
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 1400);
|
||||
};
|
||||
|
||||
// children is array of {type, text} tokens, or a string
|
||||
const rendered = Array.isArray(children) ? children : [{ text: children }];
|
||||
|
||||
return (
|
||||
<div className="codeblock">
|
||||
<div className="codeblock-header">
|
||||
<div className="codeblock-dots">
|
||||
<span /><span /><span />
|
||||
</div>
|
||||
{filename && <div className="codeblock-filename">{filename}</div>}
|
||||
<div className="codeblock-lang">{lang}</div>
|
||||
<button className="codeblock-copy" onClick={onCopy} aria-label="Copy">
|
||||
{copied ? (
|
||||
<><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><polyline points="20 6 9 17 4 12"/></svg> Copied</>
|
||||
) : (
|
||||
<><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg> Copy</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
<pre className="codeblock-pre">
|
||||
<code>
|
||||
{rendered.map((line, i) => (
|
||||
<div key={i} className="codeblock-line">
|
||||
{lineNumbers && <span className="codeblock-ln">{i + 1}</span>}
|
||||
<span dangerouslySetInnerHTML={{ __html: line.text }} />
|
||||
</div>
|
||||
))}
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Token helper — wraps text in a syntax-color span
|
||||
function tk(cls, text) {
|
||||
return `<span class="tk-${cls}">${text}</span>`;
|
||||
}
|
||||
|
||||
// Pre-tokenized PHP sample (saves us writing a real lexer)
|
||||
const phpSample = [
|
||||
{ text: `${tk("kw", "namespace")} ${tk("ns", "App\\\\Controllers")};` },
|
||||
{ text: `` },
|
||||
{ text: `${tk("kw", "use")} ${tk("ns", "Nibiru\\\\Controller")};` },
|
||||
{ text: `${tk("kw", "use")} ${tk("ns", "Nibiru\\\\Form")};` },
|
||||
{ text: `` },
|
||||
{ text: `${tk("kw", "class")} ${tk("cn", "BookingController")} ${tk("kw", "extends")} ${tk("cn", "Controller")} {` },
|
||||
{ text: ` ${tk("kw", "public")} ${tk("kw", "function")} ${tk("fn", "create")}(${tk("var", "$req")}) {` },
|
||||
{ text: ` ${tk("var", "$form")} = ${tk("kw", "new")} ${tk("cn", "Form")}();` },
|
||||
{ text: ` ${tk("var", "$form")}->${tk("fn", "addInputType")}(${tk("str", "'email'")}, ${tk("str", "'address'")});` },
|
||||
{ text: ` ${tk("kw", "return")} ${tk("var", "$this")}->${tk("fn", "view")}(${tk("str", "'booking/create'")}, [` },
|
||||
{ text: ` ${tk("str", "'form'")} => ${tk("var", "$form")},` },
|
||||
{ text: ` ]);` },
|
||||
{ text: ` }` },
|
||||
{ text: `}` },
|
||||
];
|
||||
|
||||
const sqlSample = [
|
||||
{ text: `${tk("kw", "SELECT")} ${tk("var", "id")}, ${tk("var", "title")}, ${tk("var", "created_at")}` },
|
||||
{ text: `${tk("kw", "FROM")} ${tk("cn", "bookings")}` },
|
||||
{ text: `${tk("kw", "WHERE")} ${tk("var", "user_id")} = ${tk("num", "42")}` },
|
||||
{ text: ` ${tk("kw", "AND")} ${tk("var", "status")} = ${tk("str", "'confirmed'")}` },
|
||||
{ text: `${tk("kw", "ORDER BY")} ${tk("var", "created_at")} ${tk("kw", "DESC")}` },
|
||||
{ text: `${tk("kw", "LIMIT")} ${tk("num", "20")};` },
|
||||
];
|
||||
|
||||
const yamlSample = [
|
||||
{ text: `${tk("kw", "app")}:` },
|
||||
{ text: ` ${tk("kw", "name")}: ${tk("str", "Nibiru")}` },
|
||||
{ text: ` ${tk("kw", "version")}: ${tk("str", "2.0.0")}` },
|
||||
{ text: ` ${tk("kw", "modules")}:` },
|
||||
{ text: ` - ${tk("str", "auth")}` },
|
||||
{ text: ` - ${tk("str", "blog")}` },
|
||||
{ text: ` - ${tk("str", "api")}` },
|
||||
];
|
||||
|
||||
// ============================================================
|
||||
// CALLOUTS (cosmic-coded)
|
||||
// ============================================================
|
||||
function Callout({ kind = "note", title, children }) {
|
||||
const icons = {
|
||||
note: <svg width="16" height="16" viewBox="0 0 24 24" fill="none"><circle cx="12" cy="12" r="3" fill="currentColor"/><circle cx="12" cy="12" r="9" stroke="currentColor" strokeWidth="1.5" fill="none"/></svg>,
|
||||
tip: <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2 14 8l6 1-4.5 4 1 6-4.5-3-4.5 3 1-6L4 9l6-1z"/></svg>,
|
||||
warning: <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><circle cx="12" cy="12" r="9"/><path d="M12 7v6M12 16h.01"/></svg>,
|
||||
danger: <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="m12 2 10 18H2L12 2zM12 9v4M12 17h.01"/></svg>,
|
||||
};
|
||||
const labels = { note: "Note", tip: "Tip", warning: "Warning", danger: "Danger" };
|
||||
return (
|
||||
<div className={"callout callout-" + kind}>
|
||||
<div className="callout-glyph">{icons[kind]}</div>
|
||||
<div className="callout-body">
|
||||
<div className="callout-title">{title || labels[kind]}</div>
|
||||
<div className="callout-content">{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// TABLE
|
||||
// ============================================================
|
||||
function DocTable({ headers, rows }) {
|
||||
return (
|
||||
<div className="doc-table-wrap">
|
||||
<table className="doc-table">
|
||||
<thead>
|
||||
<tr>{headers.map((h, i) => <th key={i}>{h}</th>)}</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{rows.map((row, i) => (
|
||||
<tr key={i}>
|
||||
{row.map((cell, j) => <td key={j}>{cell}</td>)}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Object.assign(window, { PageHeader, Prose, CodeBlock, Callout, DocTable, phpSample, sqlSample, yamlSample, tk });
|
||||
197
docs/design-system/source/docs-system/tokens.css
Normal file
197
docs/design-system/source/docs-system/tokens.css
Normal file
@@ -0,0 +1,197 @@
|
||||
/* ============================================================
|
||||
NIBIRU DOCS — DESIGN TOKENS
|
||||
Dark primary (cosmic), light variant (parchment)
|
||||
============================================================ */
|
||||
|
||||
:root {
|
||||
/* ---- Cosmic palette (carried from start page) ---- */
|
||||
--space: #0b0410; /* deepest void */
|
||||
--space-2: #120822; /* panel bg */
|
||||
--space-3: #1a0c2e; /* raised */
|
||||
--plum: #2a1545; /* deep nebula plum */
|
||||
--plum-2: #3a1d5e; /* hover plum */
|
||||
|
||||
--star: #f4eedb; /* cream — primary text */
|
||||
--star-soft: #d8d2c0; /* secondary text */
|
||||
--muted: #8b85a3; /* tertiary / labels */
|
||||
--muted-2: #5e5878; /* very muted */
|
||||
|
||||
--line: rgba(244, 238, 219, 0.08);
|
||||
--line-strong: rgba(244, 238, 219, 0.16);
|
||||
--line-glow: rgba(184, 107, 255, 0.25);
|
||||
|
||||
/* ---- Accents (the "nebula" colors) ---- */
|
||||
--nebula-mag: #b86bff; /* magenta — primary accent, links */
|
||||
--nebula-mag-2: #d8a8ff;
|
||||
--nebula-cyan: #6ad9ff; /* cyan — secondary accent */
|
||||
--nebula-amber: #ffb574; /* warm star highlight */
|
||||
--nebula-rose: #ff7ab8; /* hot pink */
|
||||
--nebula-green: #6ee7b0; /* aurora green */
|
||||
|
||||
/* ---- Semantic (callouts) ---- */
|
||||
--note-fg: #6ad9ff;
|
||||
--note-bg: rgba(106, 217, 255, 0.06);
|
||||
--note-border: rgba(106, 217, 255, 0.30);
|
||||
|
||||
--tip-fg: #6ee7b0;
|
||||
--tip-bg: rgba(110, 231, 176, 0.06);
|
||||
--tip-border: rgba(110, 231, 176, 0.30);
|
||||
|
||||
--warn-fg: #ffb574;
|
||||
--warn-bg: rgba(255, 181, 116, 0.06);
|
||||
--warn-border: rgba(255, 181, 116, 0.32);
|
||||
|
||||
--danger-fg: #ff7ab8;
|
||||
--danger-bg: rgba(255, 122, 184, 0.06);
|
||||
--danger-border:rgba(255, 122, 184, 0.32);
|
||||
|
||||
/* ---- Type ---- */
|
||||
--font-sans: "Inter Tight", "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
|
||||
--font-mono: "JetBrains Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace;
|
||||
|
||||
--fs-xs: 12px;
|
||||
--fs-sm: 13px;
|
||||
--fs-base: 15px;
|
||||
--fs-md: 16px;
|
||||
--fs-lg: 18px;
|
||||
--fs-xl: 22px;
|
||||
--fs-2xl: 28px;
|
||||
--fs-3xl: 36px;
|
||||
--fs-4xl: 48px;
|
||||
|
||||
/* ---- Spacing ---- */
|
||||
--space-1: 4px;
|
||||
--space-2x: 8px;
|
||||
--space-3x: 12px;
|
||||
--space-4: 16px;
|
||||
--space-5: 20px;
|
||||
--space-6: 24px;
|
||||
--space-8: 32px;
|
||||
--space-10: 40px;
|
||||
--space-12: 48px;
|
||||
--space-16: 64px;
|
||||
|
||||
/* ---- Radii ---- */
|
||||
--r-sm: 4px;
|
||||
--r-md: 8px;
|
||||
--r-lg: 12px;
|
||||
--r-xl: 16px;
|
||||
--r-2xl: 20px;
|
||||
--r-pill: 999px;
|
||||
|
||||
/* ---- Shadows ---- */
|
||||
--shadow-sm: 0 1px 2px rgba(0,0,0,0.3);
|
||||
--shadow-md: 0 8px 24px -8px rgba(0,0,0,0.5);
|
||||
--shadow-lg: 0 24px 48px -16px rgba(0,0,0,0.6);
|
||||
--shadow-glow: 0 0 24px rgba(184, 107, 255, 0.25);
|
||||
|
||||
/* ---- Layout ---- */
|
||||
--nav-h: 60px;
|
||||
--sidebar-w: 280px;
|
||||
--toc-w: 240px;
|
||||
--content-max: 760px;
|
||||
}
|
||||
|
||||
/* ---- Light variant (parchment, retained from current docs) ---- */
|
||||
.theme-light {
|
||||
--space: #faf6ec;
|
||||
--space-2: #f3eedc;
|
||||
--space-3: #ece5cf;
|
||||
--plum: #ede4ff;
|
||||
--plum-2: #ddd0f5;
|
||||
|
||||
--star: #1a1330;
|
||||
--star-soft: #4a4360;
|
||||
--muted: #7a7390;
|
||||
--muted-2: #a39db5;
|
||||
|
||||
--line: rgba(26, 19, 48, 0.08);
|
||||
--line-strong: rgba(26, 19, 48, 0.16);
|
||||
--line-glow: rgba(122, 56, 208, 0.30);
|
||||
|
||||
--nebula-mag: #7a38d0;
|
||||
--nebula-mag-2: #9b5fee;
|
||||
--nebula-cyan: #1f8aaa;
|
||||
--nebula-amber: #c4731a;
|
||||
--nebula-rose: #c43e7b;
|
||||
--nebula-green: #2a8b5e;
|
||||
|
||||
--note-fg: #1f6f8a;
|
||||
--note-bg: rgba(31, 138, 170, 0.08);
|
||||
--note-border: rgba(31, 138, 170, 0.30);
|
||||
|
||||
--tip-fg: #2a8b5e;
|
||||
--tip-bg: rgba(42, 139, 94, 0.08);
|
||||
--tip-border: rgba(42, 139, 94, 0.30);
|
||||
|
||||
--warn-fg: #c4731a;
|
||||
--warn-bg: rgba(196, 115, 26, 0.08);
|
||||
--warn-border: rgba(196, 115, 26, 0.32);
|
||||
|
||||
--danger-fg: #c43e7b;
|
||||
--danger-bg: rgba(196, 62, 123, 0.08);
|
||||
--danger-border:rgba(196, 62, 123, 0.32);
|
||||
|
||||
--shadow-sm: 0 1px 2px rgba(26, 19, 48, 0.08);
|
||||
--shadow-md: 0 8px 24px -8px rgba(26, 19, 48, 0.12);
|
||||
--shadow-lg: 0 24px 48px -16px rgba(26, 19, 48, 0.16);
|
||||
--shadow-glow: 0 0 24px rgba(122, 56, 208, 0.18);
|
||||
}
|
||||
|
||||
/* ---- Base reset for in-frame components ---- */
|
||||
.docs-frame {
|
||||
font-family: var(--font-sans);
|
||||
background: var(--space);
|
||||
color: var(--star);
|
||||
font-size: var(--fs-base);
|
||||
line-height: 1.55;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
font-feature-settings: "ss01", "cv11";
|
||||
}
|
||||
.docs-frame *,
|
||||
.docs-frame *::before,
|
||||
.docs-frame *::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.docs-frame button {
|
||||
font-family: inherit;
|
||||
background: none;
|
||||
border: none;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
}
|
||||
.docs-frame a {
|
||||
color: var(--nebula-mag);
|
||||
text-decoration: none;
|
||||
text-decoration-color: rgba(184, 107, 255, 0.4);
|
||||
text-underline-offset: 3px;
|
||||
}
|
||||
.docs-frame a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.docs-frame ::selection {
|
||||
background: rgba(184, 107, 255, 0.35);
|
||||
color: var(--star);
|
||||
}
|
||||
|
||||
/* Subtle galaxy texture for atmospheric backgrounds */
|
||||
.cosmic-bg {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.cosmic-bg::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background:
|
||||
radial-gradient(ellipse at 20% 0%, rgba(184, 107, 255, 0.08), transparent 50%),
|
||||
radial-gradient(ellipse at 80% 100%, rgba(106, 217, 255, 0.05), transparent 50%);
|
||||
pointer-events: none;
|
||||
}
|
||||
.theme-light.cosmic-bg::before,
|
||||
.theme-light .cosmic-bg::before {
|
||||
background:
|
||||
radial-gradient(ellipse at 20% 0%, rgba(122, 56, 208, 0.05), transparent 50%),
|
||||
radial-gradient(ellipse at 80% 100%, rgba(31, 138, 170, 0.04), transparent 50%);
|
||||
}
|
||||
Reference in New Issue
Block a user