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

View File

@@ -0,0 +1,523 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Nibiru — Docs Design System</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter+Tight:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="docs-system/tokens.css" />
<link rel="stylesheet" href="docs-system/components.css" />
<style>
body {
margin: 0;
background: #050208;
font-family: "Inter Tight", system-ui, sans-serif;
color: #f4eedb;
}
</style>
</head>
<body>
<!-- Lotus logo as data URI (so the bundler can inline it & it works with file://) -->
<img id="lotus-asset" src="docs-system/assets/lotus.png" style="display:none" alt=""/>
<script>
// Convert the loaded image to a data URL once, expose globally for components.
(function () {
const img = document.getElementById('lotus-asset');
function publish() {
try {
const c = document.createElement('canvas');
c.width = img.naturalWidth || 861;
c.height = img.naturalHeight || 569;
c.getContext('2d').drawImage(img, 0, 0);
window.NIBIRU_LOTUS = c.toDataURL('image/png');
} catch (e) {
window.NIBIRU_LOTUS = img.src;
}
}
if (img.complete && img.naturalWidth) publish();
else img.addEventListener('load', publish, { once: true });
})();
</script>
<template id="__bundler_thumbnail" data-bg-color="#0b0410">
<svg viewBox="0 0 1200 800" xmlns="http://www.w3.org/2000/svg">
<defs>
<radialGradient id="bg" cx="50%" cy="50%" r="60%">
<stop offset="0%" stop-color="#1a0824"/>
<stop offset="100%" stop-color="#05020a"/>
</radialGradient>
<linearGradient id="petalP" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#d8a8ff"/>
<stop offset="100%" stop-color="#8a78c4"/>
</linearGradient>
<linearGradient id="petalB" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#bfd4ee"/>
<stop offset="100%" stop-color="#7ea0cc"/>
</linearGradient>
</defs>
<rect width="1200" height="800" fill="url(#bg)"/>
<g transform="translate(600 420)" opacity="0.95">
<ellipse cx="0" cy="-80" rx="38" ry="140" fill="url(#petalP)"/>
<ellipse cx="-130" cy="-30" rx="38" ry="140" fill="url(#petalP)" transform="rotate(-30 -130 -30)"/>
<ellipse cx="130" cy="-30" rx="38" ry="140" fill="url(#petalB)" transform="rotate(30 130 -30)"/>
<ellipse cx="-230" cy="40" rx="34" ry="120" fill="url(#petalP)" transform="rotate(-55 -230 40)"/>
<ellipse cx="230" cy="40" rx="34" ry="120" fill="url(#petalB)" transform="rotate(55 230 40)"/>
</g>
<text x="600" y="640" text-anchor="middle" font-family="system-ui, sans-serif" font-size="64" font-weight="300" fill="#d8a8ff" letter-spacing="2">Nibiru</text>
<text x="600" y="700" text-anchor="middle" font-family="system-ui, sans-serif" font-size="22" fill="#8ea8d4" letter-spacing="3">Docs Design System</text>
</svg>
</template>
<div id="root"></div>
<script src="https://unpkg.com/react@18.3.1/umd/react.development.js" integrity="sha384-hD6/rw4ppMLGNu3tX5cjIb+uRZ7UkRJ6BPkLpg4hAu/6onKUg4lLsHAs9EBPT82L" crossorigin="anonymous"></script>
<script src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.development.js" integrity="sha384-u6aeetuaXnQ38mYT8rp6sbXaQe3NL9t+IBXmnYxwkUI2Hw4bsp2Wvmx4yRQF1uAm" crossorigin="anonymous"></script>
<script src="https://unpkg.com/@babel/standalone@7.29.0/babel.min.js" integrity="sha384-m08KidiNqLdpJqLq95G/LEi8Qvjl/xUYll3QILypMoQ65QorJ9Lvtp2RXYGBFj1y" crossorigin="anonymous"></script>
<script type="text/babel" src="design-canvas.jsx"></script>
<script type="text/babel" src="docs-system/components/navigation.jsx"></script>
<script type="text/babel" src="docs-system/components/typography-and-code.jsx"></script>
<script type="text/babel" src="docs-system/components/extras.jsx"></script>
<script type="text/babel" data-presets="react">
(() => {
const { DesignCanvas, DCSection, DCArtboard } = window;
// ============================================================
// Frame wrapper — gives each artboard the docs-frame baseline
// ============================================================
function Frame({ theme = "dark", padded = true, children, style }) {
return (
<div
className={"docs-frame " + (theme === "light" ? "theme-light" : "")}
style={{ width: "100%", height: "100%", ...style }}
>
<div style={{ background: "var(--space)", minHeight: "100%", height: "100%", padding: padded ? 32 : 0 }} className="cosmic-bg">
{children}
</div>
</div>
);
}
// ============================================================
// FULL PAGE PREVIEW (the marquee artboard)
// ============================================================
function FullPagePreview({ theme = "dark" }) {
const tocItems = [
{ id: "overview", level: 1, label: "Overview" },
{ id: "box", level: 1, label: "What's in the box" },
{ id: "mmvc", level: 1, label: "What MMVC actually means" },
{ id: "lifecycle", level: 1, label: "The request lifecycle" },
{ id: "for-whom", level: 1, label: "Who Nibiru is for" },
];
return (
<div className={"docs-frame " + (theme === "light" ? "theme-light" : "")} style={{ height: "100%" }}>
<div className="docs-page-preview-shell cosmic-bg">
<TopNav theme={theme} />
<div className="docs-page-preview-body">
<Sidebar />
<div className="docs-page-preview-content">
<PageHeader
crumbs={["Get Started", "What is Nibiru?"]}
title="What is Nibiru?"
summary="A modular MVC PHP framework — MMVC — built for rapid prototyping without giving up the discipline of a real framework."
lastUpdated="2 days ago"
/>
<Prose>
<h2 id="overview">Overview</h2>
<p>
<strong>Nibiru</strong> is a modular <em>MMVC</em> framework Model, <strong>Module</strong>, View, Controller small enough to fit in your head, powerful enough to back production apps.
</p>
<p>
The name is a wink at Babylonian astronomy: <strong>Nibiru</strong> was the celestial crossing-point associated with Marduk. The framework runs on the same idea a single point through which your modules, controllers, views and data cross paths.
</p>
<h2 id="box">What's in the box</h2>
<DocTable
headers={["Layer", "Description"]}
rows={[
["Routing & dispatch", <>URL-pattern + SEO-URL parsing, soft 404, automatic action lookup.</>],
["MVC + a second M", <>Controllers, Views (Smarty), Models, plus first-class <strong>Modules</strong> with traits, plugins, interfaces, settings and an observer pattern.</>],
["Multi-database", <>Native MySQL, PDO, PostgreSQL via libpq (<code>psql</code> / <code>postgresql</code>) and ODBC, all behind a unified <code>Db</code> adapter.</>],
["Forms", <>28+ field types built fluently with <code>Form::addInputType…()</code> and a layout helper for divs.</>],
]}
/>
<Callout kind="tip" title="Quick win">
Run <code>nibiru new myapp</code> to scaffold a working project in under 30 seconds.
</Callout>
<h2 id="mmvc">What MMVC actually means</h2>
<p>
The extra <strong>M</strong> stands for <em>Module</em> — a self-contained unit that can declare its own routes, controllers, models, views, and settings. Modules slot into the framework's registry and are wired together at boot.
</p>
</Prose>
<Pagination prev={null} next="Why Nibiru, not Laravel" />
</div>
<RightTOC items={tocItems} activeId="box" />
</div>
</div>
</div>
);
}
// ============================================================
// CANVAS
// ============================================================
function App() {
return (
<DesignCanvas title="Nibiru — Docs Design System" defaultZoom={0.65}>
{/* ============ SECTION: FULL PAGE ============ */}
<DCSection id="full" title="Full page · the system in context">
<DCArtboard id="full-dark" label="Dark — primary" width={1440} height={840}>
<FullPagePreview theme="dark" />
</DCArtboard>
<DCArtboard id="full-light" label="Light — daylight reading" width={1440} height={840}>
<FullPagePreview theme="light" />
</DCArtboard>
</DCSection>
{/* ============ SECTION: TOP NAV ============ */}
<DCSection id="nav" title="Top navigation">
<DCArtboard id="nav-dark" label="Top nav — dark" width={1280} height={120}>
<Frame padded={false}><TopNav theme="dark" /></Frame>
</DCArtboard>
<DCArtboard id="nav-light" label="Top nav — light" width={1280} height={120}>
<Frame theme="light" padded={false}><TopNav theme="light" /></Frame>
</DCArtboard>
</DCSection>
{/* ============ SECTION: SIDEBAR ============ */}
<DCSection id="sidebar" title="Sidebar nav · sectioned with collapsibles">
<DCArtboard id="sb-dark" label="Sidebar — dark" width={320} height={760}>
<Frame padded={false}><Sidebar /></Frame>
</DCArtboard>
<DCArtboard id="sb-light" label="Sidebar — light" width={320} height={760}>
<Frame theme="light" padded={false}><Sidebar /></Frame>
</DCArtboard>
<DCArtboard id="toc" label="On-this-page TOC" width={280} height={520}>
<Frame padded={false}>
<RightTOC
activeId="lifecycle"
items={[
{ id: "overview", level: 1, label: "Overview" },
{ id: "box", level: 1, label: "What's in the box" },
{ id: "mmvc", level: 1, label: "What MMVC means" },
{ id: "module", level: 2, label: "The Module layer" },
{ id: "lifecycle", level: 1, label: "Request lifecycle" },
{ id: "edge", level: 2, label: "Edge cases" },
{ id: "for-whom", level: 1, label: "Who it's for" },
]}
/>
</Frame>
</DCArtboard>
</DCSection>
{/* ============ SECTION: PAGE HEADER & TYPOGRAPHY ============ */}
<DCSection id="prose" title="Page header & body typography">
<DCArtboard id="header" label="Page header" width={780} height={280}>
<Frame>
<PageHeader
crumbs={["The Framework", "Architecture (MMVC)"]}
title="Architecture (MMVC)"
summary="How Model, Module, View, and Controller cross paths through a single dispatcher."
lastUpdated="2 days ago"
/>
</Frame>
</DCArtboard>
<DCArtboard id="prose-dark" label="Prose — dark" width={780} height={620}>
<Frame>
<Prose>
<h2>Headings, body, and rhythm</h2>
<p>
Body text uses <strong>Inter Tight</strong> at 16px with a 1.7 line height the right balance between density and readability for technical content. Inline accents like <em>emphasized terms</em> use the magenta accent color, and <code>inline code</code> sits in a tinted token.
</p>
<h3>Lists carry their own rhythm</h3>
<ul>
<li>List markers pick up the magenta accent.</li>
<li>Spacing between items is tight 6px so dense reference content stays scannable.</li>
<li>Sub-bullets nest cleanly without losing the magenta thread.</li>
</ul>
<blockquote>
"MMVC is the discipline of MVC plus the modularity of plugins — without the indirection of either."
</blockquote>
<p>
Block quotes get a magenta left rail with a subtle gradient pulling the eye toward the quote.
</p>
</Prose>
</Frame>
</DCArtboard>
<DCArtboard id="prose-light" label="Prose — light" width={780} height={620}>
<Frame theme="light">
<Prose>
<h2>Headings, body, and rhythm</h2>
<p>
On the <strong>light theme</strong>, accent colors shift to higher-contrast equivalents purple replaces magenta, teal replaces cyan keeping <em>emphasized terms</em> and <code>inline code</code> legible against the parchment background.
</p>
<h3>Code samples stay dark</h3>
<p>
Code blocks remain on the cosmic plum background even in light mode. The contrast against parchment grounds the page and signals "this is a runnable artifact."
</p>
</Prose>
</Frame>
</DCArtboard>
</DCSection>
{/* ============ SECTION: CODE BLOCKS ============ */}
<DCSection id="code" title="Code blocks · Mission-Control aesthetic">
<DCArtboard id="code-php" label="PHP" width={760} height={440}>
<Frame>
<CodeBlock filename="app/Controllers/BookingController.php" lang="php">
{phpSample}
</CodeBlock>
</Frame>
</DCArtboard>
<DCArtboard id="code-sql" label="SQL" width={760} height={300}>
<Frame>
<CodeBlock filename="queries/recent-bookings.sql" lang="sql">
{sqlSample}
</CodeBlock>
</Frame>
</DCArtboard>
<DCArtboard id="code-yaml" label="YAML config" width={760} height={300}>
<Frame>
<CodeBlock filename="config/app.yaml" lang="yaml">
{yamlSample}
</CodeBlock>
</Frame>
</DCArtboard>
<DCArtboard id="code-tabs" label="Tabbed code (PHP / SQL / YAML)" width={760} height={460}>
<Frame>
<Tabs tabs={[
{ label: "PHP", content: <CodeBlock filename="BookingController.php" lang="php">{phpSample}</CodeBlock> },
{ label: "SQL", content: <CodeBlock filename="queries.sql" lang="sql">{sqlSample}</CodeBlock> },
{ label: "YAML", content: <CodeBlock filename="config/app.yaml" lang="yaml">{yamlSample}</CodeBlock> },
]}/>
</Frame>
</DCArtboard>
</DCSection>
{/* ============ SECTION: CALLOUTS ============ */}
<DCSection id="callouts" title="Callouts · cosmic-coded by severity">
<DCArtboard id="callout-stack" label="All four kinds" width={780} height={460}>
<Frame>
<Callout kind="note" title="Note">
The dispatcher resolves the controller and action automatically you rarely need to wire routes by hand.
</Callout>
<Callout kind="tip" title="Tip">
Use <code>nibiru make:module Blog</code> to scaffold a complete module routes, controller, model, and views.
</Callout>
<Callout kind="warning" title="Backwards-compat break">
In <code>2.0</code>, <code>Form::addInput()</code> is deprecated. Use <code>Form::addInputType()</code> instead.
</Callout>
<Callout kind="danger" title="Don't do this in production">
Never run <code>nibiru migrate --fresh</code> against a production database it drops every table.
</Callout>
</Frame>
</DCArtboard>
</DCSection>
{/* ============ SECTION: TABLES ============ */}
<DCSection id="tables" title="Reference tables">
<DCArtboard id="table-dark" label="Reference table — dark" width={780} height={440}>
<Frame>
<DocTable
headers={["Field type", "Renders", "Validation", "Example"]}
rows={[
["text", "Single-line input", "string", <code>$form->addInputType('text', 'name')</code>],
["email", "Email input + envelope icon", "RFC 5322", <code>$form->addInputType('email', 'address')</code>],
["password", "Masked input + reveal toggle", "min length", <code>$form->addInputType('password', 'pw')</code>],
["select", "Native dropdown", "value-in-set", <code>$form->addInputType('select', 'role')</code>],
["file", "Drag-drop uploader", "mime + size", <code>$form->addInputType('file', 'avatar')</code>],
["date", "Native date picker", "ISO-8601", <code>$form->addInputType('date', 'dob')</code>],
]}
/>
</Frame>
</DCArtboard>
</DCSection>
{/* ============ SECTION: API REFERENCE ============ */}
<DCSection id="api" title="API reference blocks">
<DCArtboard id="api-block" label="Method signature + params + returns" width={780} height={620}>
<Frame>
<ApiBlock
signature={{
name: "Form::addInputType",
args: [
{ name: "type", type: "string" },
{ name: "name", type: "string" },
{ name: "options", type: "array" },
],
returns: "Form",
}}
params={[
{ name: "type", type: "string", required: true, desc: "The input type — one of 28 supported types (text, email, select, file, date, etc.)." },
{ name: "name", type: "string", required: true, desc: "Field name. Becomes the form key and the HTML name attribute." },
{ name: "options", type: "array", default: "[]", desc: "Field-specific options: validation rules, placeholder, default value, attributes." },
]}
returns={{ type: "Form", desc: "The form instance, for fluent chaining." }}
/>
</Frame>
</DCArtboard>
</DCSection>
{/* ============ SECTION: CARDS ============ */}
<DCSection id="cards" title="Feature card grid">
<DCArtboard id="cards-grid" label="Get-started card grid" width={780} height={320}>
<Frame>
<CardGrid cards={[
{
title: "Quick Start",
desc: "Scaffold, run, and ship your first Nibiru app in 60 seconds.",
glow: "linear-gradient(135deg, #b86bff, #ff7ab8)",
icon: <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="white" strokeWidth="2"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg>,
},
{
title: "Modules",
desc: "The fourth letter in MMVC — first-class plugins with their own scope.",
glow: "linear-gradient(135deg, #6ad9ff, #b86bff)",
icon: <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="white" strokeWidth="2"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/></svg>,
},
{
title: "Routing",
desc: "URL-pattern + SEO-URL parsing with automatic action lookup.",
glow: "linear-gradient(135deg, #ffb574, #ff7ab8)",
icon: <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="white" strokeWidth="2"><circle cx="6" cy="19" r="3"/><circle cx="18" cy="5" r="3"/><path d="M6 16V8a4 4 0 0 1 4-4h4M18 8v8a4 4 0 0 1-4 4h-4"/></svg>,
},
{
title: "Database",
desc: "MySQL, PostgreSQL, ODBC — one unified Db adapter.",
glow: "linear-gradient(135deg, #6ee7b0, #6ad9ff)",
icon: <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="white" strokeWidth="2"><ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M3 5v14a9 3 0 0 0 18 0V5M3 12a9 3 0 0 0 18 0"/></svg>,
},
]}/>
</Frame>
</DCArtboard>
</DCSection>
{/* ============ SECTION: SEARCH MODAL ============ */}
<DCSection id="search" title="Search modal · ⌘K">
<DCArtboard id="search-dark" label="Search modal — dark" width={680} height={580}>
<div style={{ width: "100%", height: "100%", display: "flex", alignItems: "center", justifyContent: "center", background: "rgba(11,4,16,0.85)", padding: 32 }} className="docs-frame cosmic-bg">
<SearchModal />
</div>
</DCArtboard>
<DCArtboard id="search-light" label="Search modal — light" width={680} height={580}>
<div className="docs-frame theme-light" style={{ width: "100%", height: "100%", display: "flex", alignItems: "center", justifyContent: "center", background: "rgba(250,246,236,0.7)", padding: 32 }}>
<SearchModal />
</div>
</DCArtboard>
</DCSection>
{/* ============ SECTION: PAGINATION + FAB + 404 ============ */}
<DCSection id="utility" title="Pagination, floating help, empty states">
<DCArtboard id="pagination" label="Page navigation footer" width={780} height={160}>
<Frame>
<Pagination prev="Quick Start" next="Project Structure" />
</Frame>
</DCArtboard>
<DCArtboard id="fab" label="Floating help button" width={300} height={240}>
<Frame>
<div style={{ position: "absolute", bottom: 32, right: 32 }}>
<FAB />
</div>
</Frame>
</DCArtboard>
<DCArtboard id="404" label="404 / off-orbit" width={680} height={620}>
<Frame>
<div style={{ height: "100%", display: "flex", alignItems: "center", justifyContent: "center" }}>
<NotFound />
</div>
</Frame>
</DCArtboard>
</DCSection>
{/* ============ SECTION: MOBILE ============ */}
<DCSection id="mobile" title="Mobile drawer">
<DCArtboard id="drawer-dark" label="Mobile drawer — dark" width={360} height={680}>
<div className="docs-frame" style={{ width: "100%", height: "100%", display: "flex", alignItems: "center", justifyContent: "center", padding: 16, background: "var(--space)" }}>
<MobileDrawer />
</div>
</DCArtboard>
<DCArtboard id="drawer-light" label="Mobile drawer — light" width={360} height={680}>
<div className="docs-frame theme-light" style={{ width: "100%", height: "100%", display: "flex", alignItems: "center", justifyContent: "center", padding: 16, background: "var(--space)" }}>
<MobileDrawer />
</div>
</DCArtboard>
</DCSection>
{/* ============ SECTION: COLOR / TYPE TOKENS ============ */}
<DCSection id="tokens" title="Tokens · color & type">
<DCArtboard id="palette" label="Cosmic palette" width={920} height={360}>
<Frame>
<div style={{ display: "grid", gridTemplateColumns: "repeat(6, 1fr)", gap: 12 }}>
{[
["space", "#0b0410", "Deepest void"],
["space-2", "#120822", "Panel surface"],
["space-3", "#1a0c2e", "Raised surface"],
["plum", "#2a1545", "Nebula plum"],
["star", "#f4eedb", "Primary text"],
["muted", "#8b85a3", "Tertiary text"],
["nebula-mag", "#b86bff", "Accent · links"],
["nebula-cyan", "#6ad9ff", "Accent · note"],
["nebula-amber","#ffb574", "Accent · warn"],
["nebula-rose", "#ff7ab8", "Accent · danger"],
["nebula-green","#6ee7b0", "Accent · tip"],
["nebula-mag-2","#d8a8ff", "Accent · hover"],
].map(([name, hex, desc]) => (
<div key={name} style={{ borderRadius: 12, border: "1px solid var(--line)", overflow: "hidden", background: "var(--space-2)" }}>
<div style={{ height: 80, background: hex, position: "relative", boxShadow: "inset 0 0 0 1px rgba(255,255,255,0.04)" }}/>
<div style={{ padding: "10px 12px" }}>
<div style={{ fontFamily: "var(--font-mono)", fontSize: 11, color: "var(--star)", letterSpacing: "0.04em" }}>--{name}</div>
<div style={{ fontFamily: "var(--font-mono)", fontSize: 10, color: "var(--muted)", marginTop: 2 }}>{hex}</div>
<div style={{ fontSize: 11, color: "var(--star-soft)", marginTop: 4 }}>{desc}</div>
</div>
</div>
))}
</div>
</Frame>
</DCArtboard>
<DCArtboard id="type" label="Type scale" width={780} height={520}>
<Frame>
<div className="docs-frame">
<div style={{ display: "flex", flexDirection: "column", gap: 18 }}>
{[
["48 · page hero", 48, 600, "Architecture (MMVC)"],
["36 · page title", 36, 600, "What is Nibiru?"],
["28 · h2", 28, 600, "What's in the box"],
["22 · h3", 22, 600, "The Module layer"],
["18 · summary", 18, 400, "A modular MVC PHP framework — MMVC."],
["16 · body", 16, 400, "Body text uses Inter Tight at 16px with 1.7 leading."],
["13 · ui", 13, 500, "Sidebar items, buttons, breadcrumbs"],
["11 · mono / kbd", 11, 500, "GET STARTED · ⌘K · ROUTING & DISPATCH"],
].map(([label, size, weight, sample]) => (
<div key={label} style={{ display: "grid", gridTemplateColumns: "180px 1fr", alignItems: "baseline", gap: 24 }}>
<div style={{ fontFamily: "var(--font-mono)", fontSize: 11, color: "var(--muted)", letterSpacing: "0.06em", textTransform: "uppercase" }}>{label}</div>
<div style={{
fontFamily: typeof label === "string" && label.includes("mono") ? "var(--font-mono)" : "var(--font-sans)",
fontSize: size, fontWeight: weight, color: "var(--star)",
letterSpacing: size >= 28 ? "-0.02em" : "0",
lineHeight: 1.15,
}}>{sample}</div>
</div>
))}
</div>
</div>
</Frame>
</DCArtboard>
</DCSection>
</DesignCanvas>
);
}
ReactDOM.createRoot(document.getElementById("root")).render(<App />);
})();
</script>
</body>
</html>