@veluai/velu 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/dist/cli.js +11 -0
  2. package/package.json +52 -0
  3. package/runtime/velu-ui/base.css +311 -0
  4. package/runtime/velu-ui/components/Accordion.jsx +64 -0
  5. package/runtime/velu-ui/components/ApiClient.jsx +121 -0
  6. package/runtime/velu-ui/components/ApiField.jsx +87 -0
  7. package/runtime/velu-ui/components/ApiPath.jsx +63 -0
  8. package/runtime/velu-ui/components/ApiSidebar.jsx +122 -0
  9. package/runtime/velu-ui/components/AskBar.jsx +71 -0
  10. package/runtime/velu-ui/components/Callout.jsx +114 -0
  11. package/runtime/velu-ui/components/Card.jsx +131 -0
  12. package/runtime/velu-ui/components/Chatbot.jsx +596 -0
  13. package/runtime/velu-ui/components/CodeBlock.jsx +375 -0
  14. package/runtime/velu-ui/components/Columns.jsx +56 -0
  15. package/runtime/velu-ui/components/Field.jsx +81 -0
  16. package/runtime/velu-ui/components/Image.jsx +163 -0
  17. package/runtime/velu-ui/components/MethodBadge.jsx +31 -0
  18. package/runtime/velu-ui/components/NavSelect.jsx +108 -0
  19. package/runtime/velu-ui/components/PageFeedback.jsx +219 -0
  20. package/runtime/velu-ui/components/PageFooter.jsx +213 -0
  21. package/runtime/velu-ui/components/PageHeader.jsx +414 -0
  22. package/runtime/velu-ui/components/PageNav.jsx +77 -0
  23. package/runtime/velu-ui/components/PoweredBy.jsx +51 -0
  24. package/runtime/velu-ui/components/Prompt.jsx +115 -0
  25. package/runtime/velu-ui/components/Search.jsx +366 -0
  26. package/runtime/velu-ui/components/Sidebar.jsx +191 -0
  27. package/runtime/velu-ui/components/Steps.jsx +65 -0
  28. package/runtime/velu-ui/components/ThemeToggle.jsx +48 -0
  29. package/runtime/velu-ui/components/Toc.jsx +537 -0
  30. package/runtime/velu-ui/components/TocBar.jsx +195 -0
  31. package/runtime/velu-ui/components/Tree.jsx +87 -0
  32. package/runtime/velu-ui/components/TryItBar.jsx +90 -0
  33. package/runtime/velu-ui/components/accordion.css +92 -0
  34. package/runtime/velu-ui/components/api.css +479 -0
  35. package/runtime/velu-ui/components/ask-bar.css +94 -0
  36. package/runtime/velu-ui/components/card.css +105 -0
  37. package/runtime/velu-ui/components/chatbot.css +617 -0
  38. package/runtime/velu-ui/components/code-block.css +263 -0
  39. package/runtime/velu-ui/components/docs-layout.css +775 -0
  40. package/runtime/velu-ui/components/field.css +82 -0
  41. package/runtime/velu-ui/components/image.css +237 -0
  42. package/runtime/velu-ui/components/nav-select.css +157 -0
  43. package/runtime/velu-ui/components/page-feedback.css +241 -0
  44. package/runtime/velu-ui/components/page-footer.css +130 -0
  45. package/runtime/velu-ui/components/page-header.css +520 -0
  46. package/runtime/velu-ui/components/page-nav.css +50 -0
  47. package/runtime/velu-ui/components/powered-by.css +66 -0
  48. package/runtime/velu-ui/components/prompt.css +99 -0
  49. package/runtime/velu-ui/components/search.css +307 -0
  50. package/runtime/velu-ui/components/sidebar.css +144 -0
  51. package/runtime/velu-ui/components/steps.css +77 -0
  52. package/runtime/velu-ui/components/theme-toggle.css +70 -0
  53. package/runtime/velu-ui/components/toc-bar.css +234 -0
  54. package/runtime/velu-ui/components/tree.css +49 -0
  55. package/runtime/velu-ui/index.js +45 -0
  56. package/runtime/velu-ui/lib/copyText.js +64 -0
  57. package/runtime/velu-ui/lib/lang-icons.jsx +156 -0
  58. package/runtime/velu-ui/lib/prism-langs.js +957 -0
  59. package/runtime/velu-ui/lib/prism-loader.js +74 -0
  60. package/runtime/velu-ui/lib/resolveIcon.jsx +29 -0
  61. package/runtime/velu-ui/lib/scrollIntoNearestView.js +66 -0
  62. package/runtime/velu-ui/mdx-components.jsx +85 -0
  63. package/runtime/velu-ui/primitives/Cluster.jsx +49 -0
  64. package/runtime/velu-ui/primitives/Stack.jsx +63 -0
  65. package/runtime/velu-ui/primitives/Switcher.jsx +57 -0
  66. package/runtime/velu-ui/primitives/stack.css +3 -0
  67. package/runtime/velu-ui/primitives/switcher.css +25 -0
  68. package/runtime/velu-ui/styles.css +43 -0
  69. package/runtime/velu-ui/tokens.css +4 -0
  70. package/schema/velu.schema.json +167 -0
  71. package/src/navigation.js +434 -0
  72. package/src/runtime/App.jsx +1473 -0
  73. package/src/runtime/client-entry.jsx +22 -0
  74. package/src/runtime/server-entry.jsx +16 -0
  75. package/src/template.html +48 -0
  76. package/templates/starter/ai-tools/claude-code.mdx +26 -0
  77. package/templates/starter/ai-tools/cursor.mdx +17 -0
  78. package/templates/starter/api-reference/endpoint/create.mdx +24 -0
  79. package/templates/starter/api-reference/endpoint/get.mdx +27 -0
  80. package/templates/starter/api-reference/introduction.mdx +28 -0
  81. package/templates/starter/development.mdx +19 -0
  82. package/templates/starter/essentials/code.mdx +28 -0
  83. package/templates/starter/essentials/images.mdx +29 -0
  84. package/templates/starter/essentials/markdown.mdx +25 -0
  85. package/templates/starter/essentials/navigation.mdx +39 -0
  86. package/templates/starter/essentials/settings.mdx +30 -0
  87. package/templates/starter/favicon.svg +6 -0
  88. package/templates/starter/index.mdx +31 -0
  89. package/templates/starter/quickstart.mdx +31 -0
  90. package/templates/starter/velu.json +33 -0
package/dist/cli.js ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+ var me=Object.defineProperty;var g=(e,t)=>()=>(e&&(t=e(e=0)),t);var j=(e,t)=>{for(var o in t)me(e,o,{get:t[o],enumerable:!0})};var R={};j(R,{initProject:()=>ye});import y from"node:fs/promises";import w from"node:path";import{fileURLToPath as ge}from"node:url";async function ye(e,t){try{if((await y.readdir(e)).length>0)throw new Error(`Directory "${t}" already exists and is not empty. Choose a different name or remove the folder first.`)}catch(i){if(i.code!=="ENOENT")throw i}await y.mkdir(e,{recursive:!0}),await y.cp(ve,e,{recursive:!0});let o=w.join(e,"velu.json"),n=JSON.parse(await y.readFile(o,"utf-8"));n.name=t,await y.writeFile(o,JSON.stringify(n,null,2)+`
3
+ `,"utf-8");let r=await L(e);console.log(`[velu] created ${t}/`);for(let i of r)console.log(` ${i}`);console.log(""),console.log("Next steps:"),console.log(` cd ${t}`),console.log(" velu dev")}async function L(e,t=e,o=[]){let n=await y.readdir(e,{withFileTypes:!0});for(let r of n){let i=w.join(e,r.name);r.isDirectory()?await L(i,t,o):o.push(w.relative(t,i).split(w.sep).join("/"))}return o.sort()}var he,ve,_=g(()=>{he=w.dirname(ge(import.meta.url)),ve=w.resolve(he,"..","templates","starter")});import{visit as we}from"unist-util-visit";import{toString as xe}from"mdast-util-to-string";import be from"github-slugger";import{valueToEstree as Se}from"estree-util-value-to-estree";function E(){return e=>{let t=new be,o=[];we(e,"heading",s=>{let l=xe(s);o.push({depth:s.depth,id:t.slug(l),label:l})});let n={children:[]},r=[{node:n,depth:0}];for(let s of o){for(;r.length>1&&r[r.length-1].depth>=s.depth;)r.pop();let l={id:s.id,label:s.label},a=r[r.length-1].node;a.children||(a.children=[]),a.children.push(l),r.push({node:l,depth:s.depth})}let i=n.children;e.children.unshift({type:"mdxjsEsm",value:"",data:{estree:{type:"Program",sourceType:"module",body:[{type:"ExportNamedDeclaration",specifiers:[],declaration:{type:"VariableDeclaration",kind:"const",declarations:[{type:"VariableDeclarator",id:{type:"Identifier",name:"toc"},init:Se(i)}]}}]}}})}}var C=g(()=>{});import ke from"node:fs/promises";import I from"node:path";function N(e={},t){let o=e.colors?.primary??$e;return{name:e.name??t??"docs",colors:{primary:o,light:e.colors?.light??o,dark:e.colors?.dark??o},favicon:e.favicon??null,font:{family:e.font?.family??Pe},navigation:e.navigation??null}}async function z(e){let t=I.join(e,"velu.json"),o;try{o=await ke.readFile(t,"utf-8")}catch(r){throw r.code==="ENOENT"?new Error(`No velu.json found in ${e}. Run \`velu init <name>\` to scaffold one.`):r}let n;try{n=JSON.parse(o)}catch(r){throw new Error(`velu.json is not valid JSON: ${r.message}`)}return N(n,I.basename(e))}function V(e){let{colors:t,font:o}=e;return[":root:root {",` --accent-color: ${t.light};`,` --font-sans: "${o.family}", sans-serif;`,"}",':root:root[data-theme="dark"] {',` --accent-color: ${t.dark};`,"}"].join(`
4
+ `)}var $e,Pe,B=g(()=>{$e="#dc143c",Pe="Google Sans Flex"});import Ae from"github-slugger";import Te from"iso-639-1";function Ne(e){let t=String(e).toLowerCase();return Te.getNativeName(t)||e}function P(e){return new Ae().slug(String(e??""))}function Fe(e){let t=String(e).replace(/^\/+|\/+$/g,"").split("/").filter(Boolean);return t[t.length-1]==="index"&&t.pop(),"/"+t.join("/")}function G(e){let t={kind:"root",children:b(e??{})};return D(t.children),t}function b(e){let t=Array.isArray(e.anchors)?e.anchors.map(Re):[];if(Array.isArray(e.products))return[...t,...e.products.map(Oe)];if(Array.isArray(e.versions))return[...t,...e.versions.map(Ue)];if(Array.isArray(e.languages))return[...t,...e.languages.map(je)];if(Array.isArray(e.tabs))return[...t,...e.tabs.map(Le)];let o=[...t];return Array.isArray(e.groups)&&o.push(...e.groups.map(M)),Array.isArray(e.pages)&&o.push(...e.pages.map(J)),o}function Oe(e){return{kind:"product",label:e.name??e.product,slug:P(e.product),icon:e.icon,color:e.color,default:!!e.default,children:b(e)}}function Ue(e){return{kind:"version",label:e.version,slug:P(e.version),default:!!e.default,children:b(e)}}function je(e){let t=String(e.language);return{kind:"language",code:t,label:Ne(t),slug:P(t),default:!!e.default,children:b(e)}}function Le(e){return{kind:"tab",label:e.tab,slug:P(e.tab),icon:e.icon,href:e.href,children:b(e)}}function Re(e){return{kind:"anchor",label:e.anchor,icon:e.icon,href:e.href,color:e.color}}function M(e){let t=(e.pages??[]).map(J);return e.root&&t.unshift({kind:"page",pagePath:e.root}),{kind:"group",label:e.group,icon:e.icon,expanded:!!e.expanded,root:e.root,children:t}}function J(e){return typeof e=="string"?{kind:"page",pagePath:e}:M(e)}function D(e){for(let t of Ee){let o=e.filter(n=>n.kind===t);o.length&&!o.some(n=>n.default)&&(o[0].default=!0)}for(let t of e)t.children&&D(t.children)}function q(e){let t=[],o=(n,r,i)=>{if(n.kind==="page"){t.push({pagePath:n.pagePath,url:_e(n.pagePath,r),ctx:{...r},ancestors:i,node:n});return}let s=r;n.kind==="product"?s={...r,product:n.default?null:n.slug}:n.kind==="version"?s={...r,version:n.default?null:n.slug}:n.kind==="language"&&(s={...r,language:n.default?null:n.slug});let l=n.kind==="root"?i:[...i,n];for(let a of n.children??[])o(a,s,l)};return o(e,{product:null,version:null,language:null},[]),t}function _e(e,t){let o=[t.product,t.version,t.language].filter(Boolean),n=Fe(e).split("/").filter(Boolean);return"/"+[...o,...n].join("/")}var Ee,F=g(()=>{Ee=["product","version","language"]});import Ce from"node:fs";import H from"node:path";function W(e,t){let o=[],n=[],r=new Set;for(let i of q(e)){if(r.has(i.url)){o.push(`duplicate URL "${i.url}" (page "${i.pagePath}") \u2014 keeping the first occurrence`);continue}r.add(i.url);let s=i.pagePath.replace(/^\/+|\/+$/g,""),l=H.join(t,...s.split("/"))+".mdx",a=Ce.existsSync(l);a||o.push(`page "${i.pagePath}" \u2192 ${H.relative(t,l)} not found`),n.push({pagePath:i.pagePath,url:i.url,fileAbsPath:l,exists:a})}return{pageEntries:n,warnings:o}}var Y=g(()=>{F()});import Ie from"node:fs";import K from"node:path";function Z(e){return{name:"velu-site",resolveId(t){return t===Q?X:null},load(t){return t!==X?null:ze(e)}}}function ze(e){let t={};try{t=JSON.parse(Ie.readFileSync(K.join(e,"velu.json"),"utf-8"))}catch{}let o=G(t.navigation??{}),{pageEntries:n,warnings:r}=W(o,e);for(let a of r)console.warn(`[velu] nav: ${a}`);let i=[],s=[],l=0;for(let a of n)if(a.exists){let A=JSON.stringify(a.fileAbsPath.split(K.sep).join("/")),p=`__p${l++}`;i.push(`import * as ${p} from ${A};`),s.push(` ${JSON.stringify(a.url)}: { Component: ${p}.default, frontmatter: ${p}.frontmatter, toc: ${p}.toc },`)}else s.push(` ${JSON.stringify(a.url)}: { missing: true },`);return["// AUTO-GENERATED by vite-plugin-velu-site. Do not edit.",i.join(`
5
+ `),"","export const pages = {",s.join(`
6
+ `),"};",`export const navigation = ${JSON.stringify(o)};`,""].join(`
7
+ `)}var Q,X,ee=g(()=>{F();Y();Q="virtual:velu-site",X="\0"+Q});var re={};j(re,{startDevServer:()=>at});import Ve from"node:fs/promises";import te from"node:fs";import c from"node:path";import{fileURLToPath as Be}from"node:url";import{createRequire as Ge}from"node:module";import Me from"chokidar";import Je from"express";import De from"@tailwindcss/vite";import qe from"@vitejs/plugin-react";import He from"@mdx-js/rollup";import We from"remark-frontmatter";import Ye from"remark-mdx-frontmatter";import Ke from"remark-gfm";import Xe from"rehype-slug";import{createServer as Qe,searchForWorkspaceRoot as Ze}from"vite";function rt(e){return`<link rel="stylesheet" href="${`https://fonts.googleapis.com/css2?family=${encodeURIComponent(e).replace(/%20/g,"+")}:wght@300;400;500;600;700&display=swap`}" />`}async function ne(e){try{return await z(e)}catch(t){return console.warn(`[velu] ${t.message} Using defaults.`),N({},c.basename(e))}}async function it(e,t){let o=await e.moduleGraph.getModuleByUrl(t,!0),n=new Map,r=new Set;async function i(s){if(!(!s||r.has(s.url))){if(r.add(s.url),s.id&&tt.test(s.id)){let l=s.url.includes("?")?"&":"?",a=await e.transformRequest(`${s.url}${l}direct`);a?.code&&n.set(s.url,a.code)}for(let l of s.importedModules)await i(l)}}return await i(o),[...n.values()].join(`
8
+ `)}async function at(e){console.log(`[velu-cli] dev server for: ${e}`);let t=await ne(e),o=e;try{o=te.realpathSync(e)}catch{}let r=Ge(import.meta.url).resolve("@mdx-js/react"),i=c.resolve(S,"runtime","velu-ui"),s=te.existsSync(i)?i:c.resolve(S,"..","velu-ui","src"),l=[{find:"@mdx-js/react",replacement:r},{find:/^velu-ui$/,replacement:c.join(s,"index.js")},{find:/^velu-ui\//,replacement:s+"/"}],a=Number(process.env.PORT)||5173,A=a+1,p=Je(),d=await Qe({root:S,appType:"custom",plugins:[Z(e),He({remarkPlugins:[We,Ye,Ke,E],rehypePlugins:[Xe],providerImportSource:"@mdx-js/react"}),qe(),De()],optimizeDeps:{esbuildOptions:{loader:{".mdx":"jsx"}}},resolve:{alias:l,dedupe:["react","react-dom","@mdx-js/react"]},server:{middlewareMode:!0,fs:{allow:[Ze(S),e,o]},hmr:{port:A}}});p.use(d.middlewares),p.get(oe,async(v,u,x)=>{if(!t.favicon)return u.status(404).end();let m=c.resolve(e,t.favicon.replace(/^\//,""));u.sendFile(m,f=>{f&&u.status(404).end()})}),p.use("*",async(v,u,x)=>{let m=v.originalUrl;try{let f=await Ve.readFile(st,"utf-8");f=await d.transformIndexHtml(m,f);let k="/src/runtime/server-entry.jsx",{render:$}=await d.ssrLoadModule(k),T=await $(m),U=await it(d,k),le=U?`<style data-velu-ssr>${U}</style>`:"",ce=`<style data-velu-config>
9
+ ${V(t)}
10
+ </style>`,ue=ot.has(t.font.family)?"":rt(t.font.family),fe=t.favicon?`<link rel="icon" href="${oe}" />`:"",pe=[le,ce,ue,fe].filter(Boolean).join(`
11
+ `),de=f.replace("<!--app-title-->",nt(t.name)).replace("<!--app-head-->",pe).replace("<!--app-html-->",T);u.status(200).set({"Content-Type":"text/html"}).end(de)}catch(f){d.ssrFixStacktrace(f),x(f)}}),Me.watch(e,{ignoreInitial:!0,ignored:/(^|[\\/])(node_modules|\.git)([\\/]|$)/,awaitWriteFinish:{stabilityThreshold:100,pollInterval:20}}).on("all",async(v,u)=>{console.log(`[velu-cli] content ${v}: ${c.relative(e,u)}`);let x=c.basename(u)==="velu.json",m=u.endsWith(".mdx"),f=x||m&&(v==="add"||v==="unlink");if(x&&(t=await ne(e)),f)d.moduleGraph.invalidateAll();else if(m){let k=u.split(c.sep).join("/"),$=d.moduleGraph.getModulesByFile(k);if($)for(let T of $)d.moduleGraph.invalidateModule(T)}d.ws.send({type:"full-reload"})}),p.listen(a,()=>{console.log(`[velu-cli] listening on http://localhost:${a}`)})}var et,tt,ot,oe,nt,S,st,ie=g(()=>{C();B();ee();et=c.dirname(Be(import.meta.url)),tt=/\.(css|less|sass|scss|styl|stylus|pcss|postcss)(\?|$)/,ot=new Set(["Google Sans Flex","Google Sans Code","Outfit"]),oe="/@velu-favicon",nt=e=>String(e).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;");S=c.resolve(et,".."),st=c.resolve(S,"src","template.html")});import se from"node:path";import h from"node:process";var[,,ae,O]=h.argv;async function lt(){switch(ae){case"init":{O||(console.error("Usage: velu init <project-name>"),h.exit(1));let e=O,t=se.resolve(h.cwd(),e),{initProject:o}=await Promise.resolve().then(()=>(_(),R));await o(t,e);break}case"dev":{let e=se.resolve(h.cwd(),O??"."),{startDevServer:t}=await Promise.resolve().then(()=>(ie(),re));await t(e);break}default:console.log("Usage:"),console.log(" velu init <project-name> scaffold a new docs project"),console.log(" velu dev [dir] start the dev preview server"),h.exit(ae?1:0)}}lt().catch(e=>{console.error(h.env.VELU_DEBUG?e:`Error: ${e.message}`),h.exit(1)});
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@veluai/velu",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "bin": "./dist/cli.js",
6
+ "publishConfig": {
7
+ "access": "public"
8
+ },
9
+ "files": [
10
+ "dist/**",
11
+ "src/runtime/**",
12
+ "src/navigation.js",
13
+ "src/template.html",
14
+ "runtime/velu-ui/**",
15
+ "schema/**",
16
+ "templates/**"
17
+ ],
18
+ "scripts": {
19
+ "build": "node scripts/build.mjs",
20
+ "prepack": "node scripts/build.mjs"
21
+ },
22
+ "dependencies": {
23
+ "@icons-pack/react-simple-icons": "^13.0.0",
24
+ "@mdx-js/react": "^3.1.1",
25
+ "@mdx-js/rollup": "^3.1.1",
26
+ "@tailwindcss/vite": "^4.1.0",
27
+ "@vitejs/plugin-react": "^4.3.4",
28
+ "chokidar": "^3.6.0",
29
+ "estree-util-value-to-estree": "^3.5.0",
30
+ "express": "^4.21.1",
31
+ "github-slugger": "^2.0.0",
32
+ "iso-639-1": "^3.1.5",
33
+ "lucide-react": "^0.460.0",
34
+ "mdast-util-to-string": "^4.0.0",
35
+ "prism-react-renderer": "^2.4.0",
36
+ "prismjs": "^1.29.0",
37
+ "react": "^18.3.1",
38
+ "react-dom": "^18.3.1",
39
+ "react-router-dom": "^6.28.0",
40
+ "rehype-slug": "^6.0.0",
41
+ "remark-frontmatter": "^5.0.0",
42
+ "remark-gfm": "^4.0.1",
43
+ "remark-mdx-frontmatter": "^5.2.0",
44
+ "tailwindcss": "^4.1.0",
45
+ "unist-util-visit": "^5.1.0",
46
+ "vite": "^5.4.11"
47
+ },
48
+ "devDependencies": {
49
+ "esbuild": "^0.28.0",
50
+ "velu-ui": "0.0.0"
51
+ }
52
+ }
@@ -0,0 +1,311 @@
1
+ /* Fonts are loaded via <link rel="preconnect"/"stylesheet"> in template.html
2
+ (faster than an @import inside critical CSS — non-blocking, parallel). */
3
+ :root {
4
+ --ratio: 1.25;
5
+ --s-6: calc(var(--s-5) / var(--ratio));
6
+ --s-5: calc(var(--s-4) / var(--ratio));
7
+ --s-4: calc(var(--s-3) / var(--ratio));
8
+ --s-3: calc(var(--s-2) / var(--ratio));
9
+ --s-2: calc(var(--s-1) / var(--ratio));
10
+ --s-1: calc(var(--s0) / var(--ratio));
11
+ --s0: 1rem;
12
+ --s1: calc(var(--s0) * var(--ratio));
13
+ --s2: calc(var(--s1) * var(--ratio));
14
+ --s3: calc(var(--s2) * var(--ratio));
15
+ --s4: calc(var(--s3) * var(--ratio));
16
+ --s5: calc(var(--s4) * var(--ratio));
17
+ }
18
+
19
+ /* Semantic color tokens — verbatim from Figma (1:1 names, no translation).
20
+ Components reference ONLY these (never raw hex), so theme switching =
21
+ swapping this block (zero component changes).
22
+ --page-bg is PROVISIONAL: Figma has no explicit page-background token yet
23
+ (see note to Aravind) — replace once defined. */
24
+ :root {
25
+ --page-bg: #ffffff;
26
+
27
+ --border-color: #dcdcde;
28
+ --text-color: #000000;
29
+ --accent-color: #dc143c;
30
+ --muted-color: #7a7a7a;
31
+ --surface-color: #f2f3f4;
32
+
33
+ --note-bg-color: #f2f3f4;
34
+ --note-stroke-color: #6b7280;
35
+ --warning-bg-color: #fef3e2;
36
+ --warning-stroke-color: #d97706;
37
+ --info-bg-color: #e8f4fd;
38
+ --info-stroke-color: #3b82f6;
39
+ --tip-bg-color: #e8f5e9;
40
+ --tip-stroke-color: #16a34a;
41
+ --check-bg-color: #e6f9ed;
42
+ --check-stroke-color: #22c55e;
43
+ --danger-bg-color: #fde8e8;
44
+ --danger-stroke-color: #dc2626;
45
+ --callout-bg-color: #f3e8fd;
46
+ --callout-stroke-color: #8b5cf6;
47
+
48
+ --post-pill-color: #e8f4fd;
49
+ --post-pill-stroke-color: #91bbf9;
50
+ --post-cta-color: #3064e3;
51
+ --get-pill-color: #e6f9ed;
52
+ --get-pill-stroke-color: #22c55e;
53
+ --get-cta-color: #22c55e;
54
+ --put-pill-color: #fef3e2;
55
+ --put-pill-stroke-color: #d97706;
56
+ --put-cta-color: #d97706;
57
+ --delete-pill-color: #fde8e8;
58
+ --delete-pill-stroke-color: #dc2626;
59
+ --delete-cta-color: #dc2626;
60
+ --patch-pill-color: #f3e8fd;
61
+ --patch-pill-stroke-color: #8b5cf6;
62
+ --patch-cta-color: #8b5cf6;
63
+ }
64
+ :root[data-theme="dark"] {
65
+ --page-bg: #0D0E10;
66
+
67
+ --border-color: #262827;
68
+ --text-color: #eeeff1;
69
+ --accent-color: #dc143c;
70
+ --muted-color: #7a7a7a;
71
+ --surface-color: #323334;
72
+
73
+ --note-bg-color: #1a1a1c;
74
+ --note-stroke-color: #eeeff1;
75
+ --warning-bg-color: #2a1f0d;
76
+ --warning-stroke-color: #7a5a1f;
77
+ --info-bg-color: #0d1f2a;
78
+ --info-stroke-color: #1f5a7a;
79
+ --tip-bg-color: #0d2a1a;
80
+ --tip-stroke-color: #1f7a4a;
81
+ --check-bg-color: #0d2a1a;
82
+ --check-stroke-color: #1f7a4a;
83
+ --danger-bg-color: #2a0d0d;
84
+ --danger-stroke-color: #7a1f1f;
85
+ --callout-bg-color: #1f0d2a;
86
+ --callout-stroke-color: #5a1f7a;
87
+
88
+ --post-pill-color: #1e3a8a;
89
+ --post-pill-stroke-color: #3b82f6;
90
+ --post-cta-color: #2563eb;
91
+ --get-pill-color: #065f46;
92
+ --get-pill-stroke-color: #10b981;
93
+ --get-cta-color: #059669;
94
+ --put-pill-color: #78350f;
95
+ --put-pill-stroke-color: #f59e0b;
96
+ --put-cta-color: #d97706;
97
+ --delete-pill-color: #7f1d1d;
98
+ --delete-pill-stroke-color: #ef4444;
99
+ --delete-cta-color: #dc2626;
100
+ --patch-pill-color: #4c1d95;
101
+ --patch-pill-stroke-color: #8b5cf6;
102
+ --patch-cta-color: #7c3aed;
103
+ }
104
+
105
+ :root {
106
+ --measure: 66ch;
107
+ --font-sans: "Google Sans Flex", sans-serif;
108
+ --font-mono: "Google Sans Code", ui-monospace, SFMono-Regular, monospace;
109
+ --font-brand: "Outfit", var(--font-sans);
110
+ font-family: var(--font-sans);
111
+
112
+ /* Approx height of the docs PageHeader (used as the offset for sticky
113
+ sidebars / TOC so they stick BELOW the header, not behind it). */
114
+ --velu-header-height: 7rem;
115
+
116
+ /* Fluid font sizes — 375px → 1440px viewport */
117
+ --f-body: 1rem; /* 16 → 16 */
118
+ --f-h7: calc(var(--f-h6) / var(--ratio)); /* micro UI text — one modular step below h6 (chips, captions, kbd) */
119
+ --f-h6: clamp(0.75rem, 0.188vw + 0.68rem, 0.875rem); /* 12 → 14 (reversed: mobile 14 → desktop 12, see note) */
120
+ --f-h5: 1rem; /* 16 → 16 */
121
+ --f-h4: clamp(1.125rem, 0.235vw + 1.07rem, 1.375rem); /* 18 → 22 */
122
+ --f-h3: clamp(1.3125rem,0.423vw + 1.214rem, 1.75rem); /* 21 → 28 */
123
+ --f-h2: clamp(1.5rem, 0.704vw + 1.335rem, 2.25rem); /* 24 → 36 */
124
+ --f-h1: clamp(1.6875rem,1.972vw + 1.226rem, 3rem); /* 27 → 48 */
125
+
126
+ /* Line-height tokens — unitless (height ÷ font-size from Figma). The
127
+ single source of truth for leading; components reference these, never
128
+ a hardcoded ratio. */
129
+ --lh-body: 1.75;
130
+ --lh-h1: 1.75;
131
+ --lh-h2: 1.78;
132
+ --lh-h3: 1.71;
133
+ --lh-h4: 1.82;
134
+ --lh-h5: 1.75;
135
+ --lh-h6: 1.71;
136
+ --lh-h7: 1.71;
137
+
138
+ /* Hairline width — single source of truth for every box border, divider
139
+ and rail. (Sub-pixel: crisp on hi-dpi; some 1x displays round it.) */
140
+ --border-width: 0.5px;
141
+
142
+ /* Icon stroke weight — single source of truth for every lucide icon. */
143
+ --icon-stroke: 1.5;
144
+
145
+ /* Icon size scale — em-relative so an icon next to text inherits the
146
+ text size automatically. `--icon-size` matches surrounding text;
147
+ `--icon-size-lg` is one modular step larger (for nav lists / trees
148
+ where the icon should slightly outweigh the label). */
149
+ --icon-size: 1em;
150
+ --icon-size-lg: calc(1em * var(--ratio));
151
+
152
+ /* Corner-radius scale, derived from --s0 (16px) — no raw literals.
153
+ sm 8 / md 16 / lg 32. Components pick the right step. */
154
+ --radius-sm: calc(var(--s0) / 2);
155
+ --radius-md: var(--s0);
156
+ --radius-lg: calc(var(--s0) * 2);
157
+
158
+ /* Font-weight scale — single source of truth so components never
159
+ write raw 300/400/500/600/700. Matches Google Sans Flex's
160
+ variable-font axis stops. */
161
+ --weight-light: 300;
162
+ --weight-normal: 400;
163
+ --weight-medium: 500;
164
+ --weight-semibold: 600;
165
+ --weight-bold: 700;
166
+ }
167
+
168
+ /* lucide-react adds class="lucide …" to every icon svg. This one rule
169
+ tokenizes stroke weight app-wide (CSS overrides the svg's stroke-width
170
+ attribute), so no component sets it per-icon. */
171
+ .lucide {
172
+ stroke-width: var(--icon-stroke);
173
+ }
174
+
175
+ /* Prose rhythm — wraps MDX content. MDX compiles to a flat list of
176
+ sibling block elements (h1, p, the velu-ui components, etc.).
177
+ Spacing is scaled by HEADING LEVEL so the document's structure is
178
+ readable at a glance:
179
+ - default sibling pair (paragraph rhythm): --s1
180
+ - before any heading (section break): scaled by level
181
+ - after a heading (heading → first child): one step under default
182
+ The rules use adjacent-sibling selectors so every same-level heading
183
+ gets the same lead-in regardless of what came before it (sibling
184
+ equality), and the larger before-heading gap reads as the section
185
+ break in every case. The owl rule sets the default; per-heading
186
+ rules below override it via source order (same specificity). */
187
+
188
+ /* Scroll-anchor offset for in-page links: when a TOC click lands on
189
+ a heading, the browser scrolls so the heading sits at the viewport
190
+ top — which would otherwise put it BEHIND the sticky chrome
191
+ (header + TocBar). `scroll-margin-block-start` tells the scroll
192
+ algorithm to leave that much space above the target.
193
+
194
+ --velu-scroll-offset is published by App.jsx as
195
+ `headerHeight + tocbarHeight + 1rem`, measured live via
196
+ ResizeObserver. The scroll-spy reads the SAME variable as its
197
+ trigger line, so scrollIntoView lands the heading exactly where
198
+ the spy promotes it — no magic numbers, no drift. */
199
+ .velu-hero h1,
200
+ .velu-prose :is(h1, h2, h3, h4, h5, h6) {
201
+ scroll-margin-block-start: var(--velu-scroll-offset, 5rem);
202
+ }
203
+
204
+ /* Default prose rhythm. */
205
+ .velu-prose > * + * {
206
+ margin-block-start: var(--s1);
207
+ }
208
+
209
+ /* Before-heading: progressively larger gap above higher-level headings.
210
+ Same level → same gap → siblings read at equal intervals. */
211
+ .velu-prose > * + h2 {
212
+ margin-block-start: var(--s4);
213
+ }
214
+ .velu-prose > * + h3 {
215
+ margin-block-start: var(--s3);
216
+ }
217
+ .velu-prose > * + h4 {
218
+ margin-block-start: var(--s2);
219
+ }
220
+
221
+ /* After-heading: tighter, so the heading reads as "introducing" what
222
+ follows rather than floating alone above a chasm. */
223
+ .velu-prose > h1 + *,
224
+ .velu-prose > h2 + *,
225
+ .velu-prose > h3 + *,
226
+ .velu-prose > h4 + *,
227
+ .velu-prose > h5 + *,
228
+ .velu-prose > h6 + * {
229
+ margin-block-start: var(--s0);
230
+ }
231
+
232
+ /* Page hero — the frontmatter (title + description) is wrapped in
233
+ `.velu-hero` so the title and description sit tight as a single
234
+ unit, and a larger break separates the hero from the prose body. */
235
+ .velu-hero > h1 + p {
236
+ margin-block-start: var(--s-2);
237
+ }
238
+ /* hero → body is an h1 → h2 transition (level-down, not peer-to-peer),
239
+ so the break is one modular step LARGER than the regular section
240
+ break above an h2 (--s4). Keeps the scale strictly monotonic:
241
+ level-down break > peer break. */
242
+ .velu-hero + .velu-prose {
243
+ margin-block-start: var(--s5);
244
+ }
245
+
246
+ /* Sidebars / TOC scroll container: discreet thin scrollbar with a
247
+ tokenised colour in every state.
248
+
249
+ No transitions: Firefox doesn't honour CSS transitions on
250
+ `scrollbar-color` consistently, and the in-between interpolation +
251
+ Firefox's auto-derived hover/active colours can flash a yellow/amber
252
+ accent (it picks an OS / system accent when interpolating). Hard
253
+ switches avoid that. Same reason every WebKit thumb pseudo-state is
254
+ set explicitly — otherwise Chromium can fall back to the Windows 11
255
+ system accent on hover. */
256
+ .velu-hide-scrollbar {
257
+ scrollbar-width: thin;
258
+ /* Single colour both at rest and on hover. Firefox auto-darkens on
259
+ interaction, but starting from a defined light grey keeps the
260
+ derived shade in the same family (no yellow flash). */
261
+ scrollbar-color: var(--border-color) transparent;
262
+ }
263
+ .velu-hide-scrollbar::-webkit-scrollbar {
264
+ inline-size: 6px;
265
+ block-size: 6px;
266
+ }
267
+ .velu-hide-scrollbar::-webkit-scrollbar-track {
268
+ background: transparent;
269
+ }
270
+ .velu-hide-scrollbar::-webkit-scrollbar-thumb {
271
+ background: var(--border-color);
272
+ border-radius: 999px;
273
+ }
274
+ .velu-hide-scrollbar::-webkit-scrollbar-thumb:hover,
275
+ .velu-hide-scrollbar::-webkit-scrollbar-thumb:active {
276
+ background: var(--muted-color);
277
+ }
278
+
279
+ /* Unitless line-height ratios (height ÷ font-size from Figma) */
280
+ body {
281
+ font-size: var(--f-body);
282
+ line-height: var(--lh-body);
283
+ font-weight: var(--weight-normal);
284
+ background: var(--page-bg);
285
+ color: var(--text-color);
286
+ }
287
+ h1 { font-size: var(--f-h1); line-height: var(--lh-h1); font-weight: var(--weight-semibold); }
288
+ h2 { font-size: var(--f-h2); line-height: var(--lh-h2); font-weight: var(--weight-medium); }
289
+ h3 { font-size: var(--f-h3); line-height: var(--lh-h3); font-weight: var(--weight-medium); }
290
+ h4 { font-size: var(--f-h4); line-height: var(--lh-h4); font-weight: var(--weight-normal); }
291
+ h5 { font-size: var(--f-h5); line-height: var(--lh-h5); font-weight: var(--weight-normal); }
292
+ h6 { font-size: var(--f-h6); line-height: var(--lh-h6); font-weight: var(--weight-normal); }
293
+
294
+ * {
295
+ max-width: var(--measure);
296
+ box-sizing: border-box;
297
+ }
298
+
299
+ html,
300
+ body,
301
+ div,
302
+ header,
303
+ nav,
304
+ main,
305
+ footer,
306
+ pre,
307
+ code,
308
+ span,
309
+ button {
310
+ max-width: none;
311
+ }
@@ -0,0 +1,64 @@
1
+ import React from 'react';
2
+ import resolveIcon from '../lib/resolveIcon.jsx';
3
+
4
+ /**
5
+ * Accordion + AccordionGroup — collapsible disclosure.
6
+ *
7
+ * Native <details>/<summary>: zero JS, SSR-safe, accessible, chevron rotates
8
+ * via CSS on [open] (same pattern as Sidebar). Fully tokenized — border /
9
+ * hover / expanded surface / radius / spacing / type all from base.css
10
+ * tokens, so light + dark theme automatically via [data-theme].
11
+ *
12
+ * - <Accordion title=… defaultOpen?>content</Accordion> — standalone bordered
13
+ * rounded box.
14
+ * - <AccordionGroup><Accordion/>…</AccordionGroup> — items joined into ONE
15
+ * bordered box with dividers between them (no per-item border/radius). A
16
+ * context tells nested Accordions they're grouped so they drop their own
17
+ * chrome; the group container owns the border + radius + dividers.
18
+ *
19
+ * @param {{ title: React.ReactNode, defaultOpen?: boolean,
20
+ * children: React.ReactNode, className?: string }} props
21
+ */
22
+
23
+ const GroupCtx = React.createContext(false);
24
+
25
+ export function Accordion({
26
+ title,
27
+ defaultOpen = false,
28
+ children,
29
+ className = '',
30
+ ...rest
31
+ }) {
32
+ const inGroup = React.useContext(GroupCtx);
33
+ const cls = [
34
+ 'velu-accordion',
35
+ inGroup ? 'velu-accordion--in-group' : '',
36
+ className,
37
+ ]
38
+ .filter(Boolean)
39
+ .join(' ');
40
+
41
+ return (
42
+ <details className={cls} open={defaultOpen || undefined} {...rest}>
43
+ <summary className="velu-accordion__summary">
44
+ <span className="velu-accordion__chevron" aria-hidden="true">
45
+ {resolveIcon('chevron-right', { size: '1em' })}
46
+ </span>
47
+ <span className="velu-accordion__title">{title}</span>
48
+ </summary>
49
+ <div className="velu-accordion__content">{children}</div>
50
+ </details>
51
+ );
52
+ }
53
+
54
+ export function AccordionGroup({ children, className = '', ...rest }) {
55
+ return (
56
+ <GroupCtx.Provider value={true}>
57
+ <div className={`velu-accordion-group ${className}`.trim()} {...rest}>
58
+ {children}
59
+ </div>
60
+ </GroupCtx.Provider>
61
+ );
62
+ }
63
+
64
+ export default Accordion;
@@ -0,0 +1,121 @@
1
+ import React from 'react';
2
+ import resolveIcon from '../lib/resolveIcon.jsx';
3
+ import Cluster from '../primitives/Cluster.jsx';
4
+ import MethodBadge from './MethodBadge.jsx';
5
+ import TryItBar from './TryItBar.jsx';
6
+
7
+ /**
8
+ * ApiClient — the "try-it" playground layout shell.
9
+ *
10
+ * <ApiClient
11
+ * method="DELETE"
12
+ * label="Trigger Delete"
13
+ * path="/project/preview/{projectId}"
14
+ * description="Trigger a documentation deployment…"
15
+ * onClose={() => …}
16
+ * aside={
17
+ * <CodeGroup>…response / samples / examples…</CodeGroup>
18
+ * }
19
+ * >
20
+ * <AccordionGroup>
21
+ * <Accordion title="Authorization">
22
+ * <ApiField name="Authorization" type="string" required
23
+ * prefix="Bearer" />
24
+ * </Accordion>
25
+ * <Accordion title="Path">
26
+ * <ApiField name="projectId" type="string" required />
27
+ * </Accordion>
28
+ * </AccordionGroup>
29
+ * </ApiClient>
30
+ *
31
+ * ApiClient is a pure layout shell — it owns the frame, the close
32
+ * button, the operation/endpoint head row, and the two-column body.
33
+ * It does NOT re-implement collapsibles or code panels:
34
+ *
35
+ * - LEFT column = `children` — compose the request sections with the
36
+ * shared <Accordion> / <AccordionGroup> + <ApiField>.
37
+ * - RIGHT column = `aside` — compose the response / code-sample
38
+ * panels with the shared <CodeBlock> / <CodeGroup>.
39
+ *
40
+ * CTAs (Send, the operation dropdown) are presentational stubs — wire
41
+ * `onSend` / a real selector when the OpenAPI adapter lands.
42
+ */
43
+
44
+ export default function ApiClient({
45
+ method = 'GET',
46
+ label,
47
+ path = '',
48
+ description,
49
+ onSend,
50
+ onClose,
51
+ aside,
52
+ children,
53
+ className = '',
54
+ ...rest
55
+ }) {
56
+ const key = String(method).toLowerCase();
57
+ const cls = `velu-api-client velu-api-client--${key} ${className}`.trim();
58
+
59
+ return (
60
+ <div className={cls} {...rest}>
61
+ {/* Close button row — only when shown as a modal (onClose set). */}
62
+ {onClose && (
63
+ <div className="velu-api-client__close-row">
64
+ <button
65
+ type="button"
66
+ className="velu-api-client__close"
67
+ onClick={onClose}
68
+ aria-label="Close"
69
+ >
70
+ {resolveIcon('x', { size: '1em' })}
71
+ </button>
72
+ </div>
73
+ )}
74
+
75
+ {/* Head — operation dropdown (dummy) + TryItBar relabeled "Send".
76
+ Cluster keeps each at its natural width: the dropdown hugs its
77
+ text (flex: none), the bar fills the rest (flex: 1). When the
78
+ bar can't keep its min width, Cluster wraps it below the
79
+ dropdown. */}
80
+ <Cluster
81
+ space="var(--s-1)"
82
+ align="flex-start"
83
+ className="velu-api-client__head"
84
+ >
85
+ <div className="velu-api-client__op">
86
+ <MethodBadge method={method} />
87
+ {label && (
88
+ <span className="velu-api-client__op-label">{label}</span>
89
+ )}
90
+ <span className="velu-api-client__op-chevron" aria-hidden="true">
91
+ {resolveIcon('chevron-down', { size: '1em' })}
92
+ </span>
93
+ </div>
94
+ <TryItBar
95
+ method={method}
96
+ path={path}
97
+ cta="Send"
98
+ onTry={onSend}
99
+ className="velu-api-client__bar"
100
+ />
101
+ </Cluster>
102
+
103
+ {/* Two-column body — left: request sections, right: response /
104
+ code panels. */}
105
+ <div className="velu-api-client__body">
106
+ <div className="velu-api-client__left">
107
+ {label && (
108
+ <h3 className="velu-api-client__title">{label}</h3>
109
+ )}
110
+ {description && (
111
+ <p className="velu-api-client__description">{description}</p>
112
+ )}
113
+ {children}
114
+ </div>
115
+ {aside != null && (
116
+ <div className="velu-api-client__right">{aside}</div>
117
+ )}
118
+ </div>
119
+ </div>
120
+ );
121
+ }
@@ -0,0 +1,87 @@
1
+ import React, { useId } from 'react';
2
+ import Cluster from '../primitives/Cluster.jsx';
3
+
4
+ /**
5
+ * ApiField — an input-bearing field for the ApiClient playground.
6
+ *
7
+ * Distinct from <Field> (which is doc-only — it documents a parameter
8
+ * but has no control). ApiField renders a real <input> the user types
9
+ * a value into when trying an endpoint.
10
+ *
11
+ * <ApiSection title="Path">
12
+ * <ApiField name="projectId" type="string" required>
13
+ * Your project ID. Copy it from the API keys page.
14
+ * </ApiField>
15
+ * </ApiSection>
16
+ *
17
+ * Layout — a flex-wrap pair:
18
+ * [ name + type + required ] [ prefix? input ]
19
+ * [ description … ]
20
+ * The info column (label + description) and the control column wrap
21
+ * onto separate lines on narrow widths; no media queries.
22
+ *
23
+ * - `name` parameter identifier (mono, accent)
24
+ * - `type` schema type → grey chip ("string", "boolean", …)
25
+ * - `required` boolean → red "required" pill
26
+ * - `default` pre-fills the input's value
27
+ * - `prefix` static text shown inside the input before the field
28
+ * (e.g. "Bearer" for an Authorization header)
29
+ * - `placeholder` input placeholder (defaults to `enter <name>`)
30
+ * - `value`/`onChange` optional controlled-input wiring; uncontrolled
31
+ * otherwise
32
+ * - `children` description block
33
+ */
34
+
35
+ export default function ApiField({
36
+ name,
37
+ type,
38
+ required = false,
39
+ default: defaultValue,
40
+ prefix,
41
+ placeholder,
42
+ value,
43
+ onChange,
44
+ children,
45
+ className = '',
46
+ ...rest
47
+ }) {
48
+ const inputId = useId();
49
+ const controlled = value !== undefined;
50
+ return (
51
+ <div className={`velu-api-field ${className}`.trim()} {...rest}>
52
+ <div className="velu-api-field__info">
53
+ <Cluster space="var(--s-1)" align="baseline">
54
+ <label className="velu-api-field__name" htmlFor={inputId}>
55
+ {name}
56
+ </label>
57
+ {type != null && (
58
+ <span className="velu-api-field__chip">{type}</span>
59
+ )}
60
+ {required && (
61
+ <span className="velu-api-field__required">required</span>
62
+ )}
63
+ </Cluster>
64
+ {children != null && children !== false && (
65
+ <div className="velu-api-field__desc">{children}</div>
66
+ )}
67
+ </div>
68
+ <div className="velu-api-field__control">
69
+ {prefix != null && (
70
+ <span className="velu-api-field__prefix">{prefix}</span>
71
+ )}
72
+ <input
73
+ id={inputId}
74
+ className="velu-api-field__input"
75
+ type="text"
76
+ placeholder={placeholder ?? `enter ${name}`}
77
+ {...(controlled
78
+ ? { value, onChange }
79
+ : {
80
+ defaultValue:
81
+ defaultValue != null ? String(defaultValue) : undefined,
82
+ })}
83
+ />
84
+ </div>
85
+ </div>
86
+ );
87
+ }