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:
stephan
2026-05-08 15:22:18 +02:00
parent a60ce90643
commit 48c839d927
662 changed files with 172811 additions and 1 deletions

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 KiB

File diff suppressed because it is too large Load Diff

View 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 });

View 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 });

View File

@@ -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 });

View 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%);
}