Files
nibiru-framework.com/docs/design-system/source/docs-system/components/extras.jsx
stephan 48c839d927 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>
2026-05-08 15:22:18 +02:00

292 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* 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 });