diff --git a/README.md b/README.md
index df3d2d2a..c8f8a1b7 100644
--- a/README.md
+++ b/README.md
@@ -7,237 +7,151 @@

-A monorepo of production-quality TypeScript libraries. Written with zero runtime dependencies, strict types, and a strong focus on correctness — every package ships with unit tests, full TypeScript type coverage, and automated CI on every pull request.
+Cleverbrush is a schema-first TypeScript framework monorepo. It provides the
+building blocks for contract-driven web applications: validation, object
+mapping, React forms, typed HTTP clients, server endpoint contracts, OpenAPI,
+dependency injection, environment parsing, persistence helpers, logging, and
+OpenTelemetry.
-The flagship package is **`@cleverbrush/schema`** — a schema validation library that is faster than Zod in 14 out of 15 benchmarks (up to 204× faster on invalid input), 3× smaller than Zod v4 in bundle size, and compatible with 50+ ecosystem tools via [Standard Schema v1](https://standardschema.dev/).
-
----
+`@cleverbrush/schema` is the foundation. A single schema definition can drive
+runtime validation, TypeScript inference, property descriptors, forms, mappers,
+JSON Schema, API contracts, and Standard Schema integrations.
## Packages
| Package | Description |
| --- | --- |
-| [`@cleverbrush/schema`](./libs/schema) | Schema definition, type inference, and runtime validation. [Standard Schema v1](https://standardschema.dev/) compatible — works with tRPC, TanStack Form, React Hook Form, T3 Env, Hono, and 50+ other tools. Wraps external schemas (Zod, Valibot, ArkType) via `extern()` |
-| [`@cleverbrush/mapper`](./libs/mapper) | Schema-driven object mapping with compile-time completeness checking and type-safe property selectors |
-| [`@cleverbrush/react-form`](./libs/react-form) | Headless, schema-driven form system for React — type-safe field binding, auto-field rendering, UI-agnostic |
-| [`@cleverbrush/schema-json`](./libs/schema-json) | Bidirectional JSON Schema conversion: `toJsonSchema()` + `fromJsonSchema()` with full type inference |
-| [`@cleverbrush/async`](./libs/async) | Async utilities: `Collector`, `debounce`, `throttle`, `retry` |
-| [`@cleverbrush/deep`](./libs/deep) | Deep operations on objects: deep equality, deep merge, flattening, hashing |
-| [`@cleverbrush/scheduler`](./libs/scheduler) | Cron-like job scheduler for Node.js using worker threads |
-| [`@cleverbrush/client`](./libs/client) | Typed HTTP client for `@cleverbrush/server` API contracts — Proxy-based, zero codegen, full type inference. Optional React + TanStack Query integration via `/react` subpath |
-| [`@cleverbrush/otel`](./libs/otel) | OpenTelemetry instrumentation — traces for HTTP, SQL, and outbound client calls; OTLP log sink with trace correlation; DI integration |
-| [`@cleverbrush/knex-clickhouse`](./libs/knex-clickhouse) | Knex query builder dialect for ClickHouse |
-
----
-
-## Why @cleverbrush/schema?
-
-If you have used Zod, Yup, or Joi, the fluent API will feel immediately familiar — with several important differences.
-
-### One schema, four capabilities
-
-```
-@cleverbrush/schema
- │
- ├── TypeScript inference (InferType)
- ├── Runtime validation (.validate() / .validateAsync())
- ├── @cleverbrush/mapper (type-safe object mapping)
- ├── @cleverbrush/react-form (auto-generated, schema-driven forms)
- └── @cleverbrush/schema-json (bidirectional JSON Schema)
-```
-
-Define a schema once and get all four capabilities for free — no duplication between types, validators, mappers, and form configs.
-
-### Quick example
+| [`@cleverbrush/schema`](./libs/schema) | Immutable fluent schemas with runtime validation, type inference, property descriptors, extensions, and Standard Schema v1 support. |
+| [`@cleverbrush/server`](./libs/server) | Schema-first HTTP endpoint contracts, validation, authorization, dependency injection, and RFC 9457 errors. |
+| [`@cleverbrush/client`](./libs/client) | Type-safe HTTP client for `@cleverbrush/server` contracts, with optional batching, retries, dedupe, cache tags, offline queue, and React integration. |
+| [`@cleverbrush/server-openapi`](./libs/server-openapi) | OpenAPI 3.x generation from server endpoint metadata. |
+| [`@cleverbrush/mapper`](./libs/mapper) | Schema-driven object mapping with compile-time completeness checks and type-safe property selectors. |
+| [`@cleverbrush/react-form`](./libs/react-form) | Headless React form primitives powered by schema property descriptors. |
+| [`@cleverbrush/schema-json`](./libs/schema-json) | JSON Schema generation and JSON Schema to Cleverbrush schema conversion. |
+| [`@cleverbrush/di`](./libs/di) | Small dependency-injection container used by server and application code. |
+| [`@cleverbrush/auth`](./libs/auth) | Principal and authorization utility types. |
+| [`@cleverbrush/env`](./libs/env) | Environment-variable parsing and validation with schema builders. |
+| [`@cleverbrush/orm`](./libs/orm) | Knex-backed ORM layer with typed entity maps and query helpers. |
+| [`@cleverbrush/orm-cli`](./libs/orm-cli) | CLI tooling for ORM migrations. |
+| [`@cleverbrush/knex-schema`](./libs/knex-schema) | Knex schema helpers that connect database names to schema metadata. |
+| [`@cleverbrush/knex-clickhouse`](./libs/knex-clickhouse) | ClickHouse dialect support for Knex. |
+| [`@cleverbrush/log`](./libs/log) | Structured logging pipeline, sinks, batching, redaction, and context helpers. |
+| [`@cleverbrush/otel`](./libs/otel) | OpenTelemetry setup and instrumentation helpers for apps and clients. |
+| [`@cleverbrush/async`](./libs/async) | Async utilities including collector, debounce, throttle, and retry. |
+| [`@cleverbrush/deep`](./libs/deep) | Deep equality, deep extension, flattening, and object utilities. |
+| [`@cleverbrush/scheduler`](./libs/scheduler) | Cron-like job scheduler with schema-validated job configuration. |
+
+## How The Pieces Fit
```ts
-import { object, string, number, InferType } from '@cleverbrush/schema';
+import { object, string, number, type InferType } from '@cleverbrush/schema';
+import { endpoint } from '@cleverbrush/server/contract';
const UserSchema = object({
- name: string().nonempty().minLength(2),
- email: string().email(),
- age: number().min(18).optional(),
+ id: number().int().min(1),
+ email: string().email(),
+ displayName: string().minLength(2)
});
-// TypeScript type — inferred automatically, no duplication
type User = InferType;
-// Runtime validation
-const result = UserSchema.validate({ name: 'Alice', email: 'alice@example.com' });
-if (result.valid) {
- console.log(result.object); // typed as User
-} else {
- const nameErrors = result.getErrorsFor((p) => p.name);
- console.log(nameErrors.errors); // ['Name must be at least 2 characters']
-}
-
-// Standard Schema interop — pass directly to tRPC, TanStack Form, T3 Env, …
-const validator = UserSchema['~standard'];
+const GetUserEndpoint = endpoint
+ .get('/api/users/:id')
+ .params(object({ id: number().int().min(1) }))
+ .responses({ 200: UserSchema });
```
-### Performance vs Zod
-
-Benchmarked with [Vitest bench](https://vitest.dev/guide/features.html#benchmarking) against Zod v4 on the same machine:
-
-| Benchmark | @cleverbrush/schema | Zod | Ratio |
-| --- | --- | --- | --- |
-| Array 100 objects — valid | 35,228 ops/s | 13,277 ops/s | **2.65× faster** |
-| Array 100 objects — invalid | 899,329 ops/s | 4,396 ops/s | **204× faster** |
-| Complex order — valid | 198,988 ops/s | 136,090 ops/s | **1.46× faster** |
-| Complex order — invalid | 884,706 ops/s | 26,106 ops/s | **33.9× faster** |
-| Flat object — valid | 1,001,194 ops/s | 840,725 ops/s | **1.19× faster** |
-| Flat object — invalid | 2,653,630 ops/s | 176,222 ops/s | **15.1× faster** |
-| Nested object — valid | 690,556 ops/s | 368,893 ops/s | **1.87× faster** |
-| Nested object — invalid | 2,739,319 ops/s | 87,245 ops/s | **31.4× faster** |
-| String — valid | 5,348,564 ops/s | 3,533,945 ops/s | **1.51× faster** |
-| String — invalid | 5,749,087 ops/s | 482,961 ops/s | **11.9× faster** |
-| Number — valid | 7,911,266 ops/s | 4,806,511 ops/s | **1.65× faster** |
-| Number — invalid | 5,387,475 ops/s | 637,513 ops/s | **8.45× faster** |
-| Union first branch | 1,925,508 ops/s | 1,529,547 ops/s | **1.26× faster** |
-| Union last branch | 676,107 ops/s | 732,682 ops/s | 0.92× |
-| Union no match — invalid | 5,873,118 ops/s | 385,453 ops/s | **15.2× faster** |
-
-**14 out of 15 benchmarks.** The early-exit optimization on invalid data produces especially large gains — up to 204× — because type errors are caught at the first failing field without evaluating the rest.
-
-Run the benchmarks yourself:
-```bash
-npm run bench
+That same schema can be reused across the stack:
+
+- Runtime validation through `.validate()` and `.validateAsync()`.
+- Type inference through `InferType`.
+- API input and response contracts in `@cleverbrush/server`.
+- Typed clients through `@cleverbrush/client`.
+- OpenAPI documents through `@cleverbrush/server-openapi`.
+- Type-safe object mapping through `@cleverbrush/mapper`.
+- React form fields through `@cleverbrush/react-form`.
+- JSON Schema interop through `@cleverbrush/schema-json`.
+- Standard Schema compatible integrations such as TanStack Form and T3 Env.
+
+## Repository Layout
+
+```text
+libs/ publishable @cleverbrush/* packages
+demos/ demo applications and e2e setup
+websites/ docs, schema site, playground, and shared website UI
+scripts/ build, release, docs, and website helper scripts
```
-### Bundle size vs competitors
-
-| Bundle | Gzipped | Notes |
-| --- | --- | --- |
-| `@cleverbrush/schema` (full) | **14 KB** | All builders + built-in extensions |
-| `@cleverbrush/schema/string` | **3.8 KB** | Sub-path import, one builder only |
-| `@cleverbrush/schema/object` | **5.8 KB** | Sub-path import, one builder only |
-| Zod v3 (full) | 14.4 KB | For reference |
-| Zod v4 (full) | **41 KB** | **3× larger than @cleverbrush/schema** |
-
-Sub-path exports (`@cleverbrush/schema/string`, `/number`, `/object`, `/array`, `/core`) enable fine-grained tree-shaking for bundle-critical applications.
-
-### Competitive feature comparison
-
-| | @cleverbrush/schema | Zod | Yup | Joi |
-| --- | --- | --- | --- | --- |
-| TypeScript type inference | ✓ | ✓ | ~ | ✗ |
-| [Standard Schema v1](https://standardschema.dev/) | ✓ | ✓ | ✗ | ✗ |
-| **PropertyDescriptors** (runtime introspection) | ✓ | ✗ | ✗ | ✗ |
-| **Type-safe extension system** | ✓ | ✗ | ✗ | ✗ |
-| **Built-in object mapper** | ✓ | ✗ | ✗ | ✗ |
-| **Built-in form generation** | ✓ | ✗ | ✗ | ✗ |
-| Bidirectional JSON Schema | ✓ | ~ (output only) | ✗ | ✗ |
-| **External schema interop** (`extern()`) | ✓ | ✗ | ✗ | ✗ |
-| JSDoc comment preservation | ✓ | ✗ | ✗ | ✗ |
-| Immutable fluent API | ✓ | ✓ | ✗ | ✗ |
-| Zero runtime dependencies | ✓ | ✓ | ✗ | ✗ |
-| Bundle size (full, gzipped) | **14 KB** | 41 KB (v4) | ~19 KB | ~26 KB |
-
-**PropertyDescriptors** are the architectural differentiator. Every schema emits a structured descriptor tree at runtime — not just a black-box validator function. This is what enables the mapper to provide type-safe property selectors, react-form to auto-generate fields, and schema-json to produce accurate JSON Schema output. No other popular validation library exposes this level of runtime metadata.
-
----
-
-## Code Quality
-
-Every pull request must pass all of the following gates before merging — enforced by the CI pipeline:
-
-| Gate | Tool | What it checks |
-| --- | --- | --- |
-| **Linting** | [Biome](https://biomejs.dev/) | Code style, formatting, and static analysis across all packages and the website |
-| **Type checking** | TypeScript (strict mode) | Strict null-checks, no implicit `any`, full type coverage |
-| **Unit tests** | [Vitest](https://vitest.dev/) | Runtime behaviour + type-level tests (`expectTypeOf`) — coverage spans all builders, extensions, edge cases, and error paths |
-| **Build** | [tsup](https://tsup.egoist.dev/) + [Turbo](https://turbo.build/) | ESM output compiles cleanly with no TypeScript errors |
-| **Benchmarks** | Vitest bench | Performance regressions are visible before merge |
-
-Run everything locally before opening a PR:
-
-```bash
-npm run lint # Biome static analysis
-npm run build # compile all packages
-npm test # unit tests + type checks
-npm run bench # performance benchmarks
-```
-
----
+The repository uses npm workspaces, Turborepo, TypeScript, Biome, Vitest, and
+ES modules.
## Development
-This project uses [npm workspaces](https://docs.npmjs.com/cli/using-npm/workspaces) and [Turborepo](https://turbo.build/) for incremental builds. All library source is under `libs/`.
-
-### Setup
-
-```bash
-npm install
-```
-
-### Build
+Use Node.js 20 or newer. Node.js 22 is recommended.
```bash
+npm ci
+npm run lint
npm run build
+npm run test
```
-### Test
+Useful targeted commands:
```bash
-npm test
+npx vitest --run libs/schema
+npm run typecheck:schema-site
+npm run typecheck:docs-site
+npm run build:schema-site
+npm run build:docs-site
```
-### Documentation
-
-API docs are generated by [TypeDoc](https://typedoc.org/) and published at https://docs.cleverbrush.com/.
+The demo app can be started with:
```bash
-npm run docs
+npm run dev:demo
```
-Each library has its own `README.md` with usage examples and full API reference.
-
----
-
-## Release
-
-This project uses [Changesets](https://github.com/changesets/changesets) for versioning and publishing. All packages are versioned together.
-
-1. **Add a changeset** after making changes:
+This starts the todo backend, frontend, and local database stack used by the
+demo workflow.
- ```bash
- npm run changeset
- ```
+## Documentation
- Follow the prompts to describe the change. A changeset file is created in `.changeset/`.
+- Framework docs: https://docs.cleverbrush.com
+- Schema docs and playground: https://schema.cleverbrush.com
+- Standard Schema: https://standardschema.dev
-2. **Version packages** when ready to release:
+Each package also has local source, tests, and exports under `libs/`.
- ```bash
- npm run version
- ```
+## Quality Gates
- This bumps `package.json` versions and updates `CHANGELOG.md` files.
+Every change should leave these commands passing:
-3. **Publish** to npm:
-
- ```bash
- npm run release
- ```
-
- For a beta release:
-
- ```bash
- npm run publish:beta
- ```
+```bash
+npm run lint
+npm run build
+npm run test
+```
----
+Website changes should also run the relevant site typecheck or build command.
+Published package behavior changes require a changeset.
-## Contributing
+## Release
-Contributions are welcome. See [CONTRIBUTING.md](./CONTRIBUTING.md) for full guidelines.
+This repository uses Changesets and fixed-version package releases.
-The short version: make sure your changes include tests, pass linting (`npm run lint`), and don't break existing tests (`npm test`). If you add or change behaviour, update the relevant JSDoc comments — that's all the documentation update that's usually needed.
+```bash
+npm run changeset
+npm run version
+npm run release
+```
-Extensions are the easiest place to start contributing — each one is a self-contained file with tests. Look for issues labelled **good first issue**.
+For beta releases:
----
+```bash
+npm run publish:beta
+```
## License
-[BSD-3-Clause](./LICENSE)
+BSD-3-Clause. See [LICENSE](./LICENSE).
diff --git a/package-lock.json b/package-lock.json
index 5918a68a..5382d67f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "cleverbrush-framework",
- "version": "3.1.0",
+ "version": "4.2.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "cleverbrush-framework",
- "version": "3.1.0",
+ "version": "4.2.0",
"license": "BSD 3-Clause",
"workspaces": [
"./libs/*",
@@ -149,7 +149,7 @@
},
"libs/async": {
"name": "@cleverbrush/async",
- "version": "4.0.0",
+ "version": "4.3.2",
"license": "BSD 3-Clause",
"devDependencies": {
"@types/node": "^25.4.0"
@@ -167,10 +167,10 @@
},
"libs/auth": {
"name": "@cleverbrush/auth",
- "version": "4.0.0",
+ "version": "4.3.2",
"license": "BSD 3-Clause",
"dependencies": {
- "@cleverbrush/schema": "^4.0.0"
+ "@cleverbrush/schema": "^4.3.2"
}
},
"libs/benchmarks": {
@@ -185,11 +185,11 @@
},
"libs/client": {
"name": "@cleverbrush/client",
- "version": "4.0.0",
+ "version": "4.3.2",
"license": "BSD 3-Clause",
"dependencies": {
- "@cleverbrush/schema": "^4.0.0",
- "@cleverbrush/server": "^4.0.0"
+ "@cleverbrush/schema": "^4.3.2",
+ "@cleverbrush/server": "^4.3.2"
},
"devDependencies": {
"@tanstack/react-query": "^5.75.0",
@@ -213,23 +213,23 @@
},
"libs/deep": {
"name": "@cleverbrush/deep",
- "version": "4.0.0",
+ "version": "4.3.2",
"license": "BSD 3-Clause"
},
"libs/di": {
"name": "@cleverbrush/di",
- "version": "4.0.0",
+ "version": "4.3.2",
"license": "BSD 3-Clause",
"dependencies": {
- "@cleverbrush/schema": "^4.0.0"
+ "@cleverbrush/schema": "^4.3.2"
}
},
"libs/env": {
"name": "@cleverbrush/env",
- "version": "4.0.0",
+ "version": "4.3.2",
"license": "BSD 3-Clause",
"dependencies": {
- "@cleverbrush/deep": "^4.0.0"
+ "@cleverbrush/deep": "^4.3.2"
},
"devDependencies": {
"@types/node": "^25.4.0"
@@ -250,11 +250,11 @@
},
"libs/knex-clickhouse": {
"name": "@cleverbrush/knex-clickhouse",
- "version": "4.0.0",
+ "version": "4.3.2",
"license": "BSD 3-Clause",
"dependencies": {
- "@cleverbrush/async": "^4.0.0",
- "@cleverbrush/deep": "^4.0.0",
+ "@cleverbrush/async": "^4.3.2",
+ "@cleverbrush/deep": "^4.3.2",
"@clickhouse/client": "^1.18.2"
},
"peerDependencies": {
@@ -263,10 +263,10 @@
},
"libs/knex-schema": {
"name": "@cleverbrush/knex-schema",
- "version": "4.0.0",
+ "version": "4.3.2",
"license": "BSD 3-Clause",
"dependencies": {
- "@cleverbrush/schema": "^4.0.0"
+ "@cleverbrush/schema": "^4.3.2"
},
"peerDependencies": {
"knex": ">=3.1.0"
@@ -274,11 +274,11 @@
},
"libs/log": {
"name": "@cleverbrush/log",
- "version": "4.0.0",
+ "version": "4.3.2",
"license": "BSD 3-Clause",
"dependencies": {
- "@cleverbrush/async": "^4.0.0",
- "@cleverbrush/schema": "^4.0.0"
+ "@cleverbrush/async": "^4.3.2",
+ "@cleverbrush/schema": "^4.3.2"
},
"devDependencies": {
"@types/node": "^25.4.0"
@@ -312,19 +312,19 @@
},
"libs/mapper": {
"name": "@cleverbrush/mapper",
- "version": "4.0.0",
+ "version": "4.3.2",
"license": "BSD 3-Clause",
"dependencies": {
- "@cleverbrush/schema": "^4.0.0"
+ "@cleverbrush/schema": "^4.3.2"
}
},
"libs/orm": {
"name": "@cleverbrush/orm",
- "version": "1.0.0",
+ "version": "4.3.2",
"license": "BSD 3-Clause",
"dependencies": {
- "@cleverbrush/knex-schema": "^4.0.0",
- "@cleverbrush/schema": "^4.0.0"
+ "@cleverbrush/knex-schema": "^4.3.2",
+ "@cleverbrush/schema": "^4.3.2"
},
"peerDependencies": {
"knex": ">=3.1.0"
@@ -332,7 +332,7 @@
},
"libs/orm-cli": {
"name": "@cleverbrush/orm-cli",
- "version": "1.0.0",
+ "version": "4.3.2",
"license": "BSD 3-Clause",
"dependencies": {
"@cleverbrush/knex-schema": "*",
@@ -347,7 +347,7 @@
},
"libs/otel": {
"name": "@cleverbrush/otel",
- "version": "4.0.0",
+ "version": "4.3.2",
"license": "BSD 3-Clause",
"dependencies": {
"@opentelemetry/api": "^1.9.0",
@@ -419,10 +419,10 @@
},
"libs/react-form": {
"name": "@cleverbrush/react-form",
- "version": "4.0.0",
+ "version": "4.3.2",
"license": "BSD 3-Clause",
"dependencies": {
- "@cleverbrush/schema": "^4.0.0"
+ "@cleverbrush/schema": "^4.3.2"
},
"devDependencies": {
"@types/react": "^19.0.0",
@@ -434,10 +434,10 @@
},
"libs/scheduler": {
"name": "@cleverbrush/scheduler",
- "version": "4.0.0",
+ "version": "4.3.2",
"license": "BSD 3-Clause",
"dependencies": {
- "@cleverbrush/schema": "^4.0.0"
+ "@cleverbrush/schema": "^4.3.2"
},
"devDependencies": {
"@types/node": "^25.4.0"
@@ -455,10 +455,10 @@
},
"libs/schema": {
"name": "@cleverbrush/schema",
- "version": "4.0.0",
+ "version": "4.3.2",
"license": "BSD 3-Clause",
"devDependencies": {
- "@cleverbrush/deep": "^4.0.0"
+ "@cleverbrush/deep": "^4.3.2"
},
"peerDependencies": {
"@standard-schema/spec": "^1.1.0"
@@ -466,7 +466,7 @@
},
"libs/schema-json": {
"name": "@cleverbrush/schema-json",
- "version": "4.0.0",
+ "version": "4.3.2",
"license": "BSD 3-Clause",
"peerDependencies": {
"@cleverbrush/schema": "^4.0.0",
@@ -475,12 +475,12 @@
},
"libs/server": {
"name": "@cleverbrush/server",
- "version": "4.0.0",
+ "version": "4.3.2",
"license": "BSD 3-Clause",
"dependencies": {
- "@cleverbrush/auth": "^4.0.0",
- "@cleverbrush/di": "^4.0.0",
- "@cleverbrush/schema": "^4.0.0",
+ "@cleverbrush/auth": "^4.3.2",
+ "@cleverbrush/di": "^4.3.2",
+ "@cleverbrush/schema": "^4.3.2",
"@fastify/busboy": "^3.2.0",
"ws": "^8.20.0"
},
@@ -501,7 +501,7 @@
},
"libs/server-openapi": {
"name": "@cleverbrush/server-openapi",
- "version": "4.0.0",
+ "version": "4.3.2",
"license": "BSD 3-Clause",
"peerDependencies": {
"@cleverbrush/auth": "^4.0.0",
@@ -8434,6 +8434,16 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/performative-ui": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/performative-ui/-/performative-ui-0.3.0.tgz",
+ "integrity": "sha512-hUY2Jk4D2iu143Kmd8G/wdY4B1sQC/1RBYDwaJr9/BhayW4404icSU5zJc2cTDNaKpgLmvCNvYfbWuIO50U40A==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^18.0.0 || ^19.0.0",
+ "react-dom": "^18.0.0 || ^19.0.0"
+ }
+ },
"node_modules/pg": {
"version": "8.20.0",
"resolved": "https://registry.npmjs.org/pg/-/pg-8.20.0.tgz",
@@ -11129,6 +11139,7 @@
"dependencies": {
"@cleverbrush/website-shared": "file:../shared",
"next": "^16.2.1",
+ "performative-ui": "^0.3.0",
"react": "^19.1.0",
"react-dom": "^19.1.0"
},
@@ -11165,6 +11176,7 @@
"@t3-oss/env-nextjs": "^0.13.11",
"@tanstack/react-form": "^1.28.6",
"next": "^16.2.1",
+ "performative-ui": "^0.3.0",
"react": "^19.2.4",
"react-dom": "^19.2.4"
},
@@ -11197,6 +11209,7 @@
"version": "0.0.0",
"dependencies": {
"next": "^16.2.1",
+ "performative-ui": "^0.3.0",
"react": "^19.1.0"
}
}
diff --git a/websites/docs/app/comparisons/page.tsx b/websites/docs/app/comparisons/page.tsx
index 68f6017f..efcea117 100644
--- a/websites/docs/app/comparisons/page.tsx
+++ b/websites/docs/app/comparisons/page.tsx
@@ -1,5 +1,6 @@
/** biome-ignore-all lint/security/noDangerouslySetInnerHtml: needed for code examples */
+import { PerformativeBeforeAfter } from '@cleverbrush/website-shared/components/Performative';
import { highlightTS } from '@cleverbrush/website-shared/lib/highlight';
import { docsMetadata } from '../site';
@@ -18,6 +19,20 @@ export default function ComparisonsPage() {
+
+
{/* ── Overview Table ──────────────────────────────── */}
Feature matrix
diff --git a/websites/docs/app/demo/page.tsx b/websites/docs/app/demo/page.tsx
index c49ae37a..67654009 100644
--- a/websites/docs/app/demo/page.tsx
+++ b/websites/docs/app/demo/page.tsx
@@ -1,3 +1,4 @@
+import { PerformativeMetricStrip } from '@cleverbrush/website-shared/components/Performative';
import Link from 'next/link';
import { docsMetadata } from '../site';
@@ -20,6 +21,15 @@ export default function DemoPage() {
+
+
{/* ── Docker Compose setup ─────────────────────────── */}
Running the demo
diff --git a/websites/docs/app/examples/page.tsx b/websites/docs/app/examples/page.tsx
index 09a1b6ac..d6307c42 100644
--- a/websites/docs/app/examples/page.tsx
+++ b/websites/docs/app/examples/page.tsx
@@ -1,5 +1,6 @@
/** biome-ignore-all lint/security/noDangerouslySetInnerHtml: needed for code examples */
+import { PerformativeGlassGrid } from '@cleverbrush/website-shared/components/Performative';
import { highlightTS } from '@cleverbrush/website-shared/lib/highlight';
import { docsMetadata } from '../site';
@@ -18,6 +19,32 @@ export default function ExamplesPage() {
+
+
{/* ── Overview ────────────────────────────────────── */}
Todo app
diff --git a/websites/docs/app/getting-started/page.tsx b/websites/docs/app/getting-started/page.tsx
index 510b2c52..ac28e3a6 100644
--- a/websites/docs/app/getting-started/page.tsx
+++ b/websites/docs/app/getting-started/page.tsx
@@ -1,6 +1,7 @@
/** biome-ignore-all lint/security/noDangerouslySetInnerHtml: needed for code examples */
import { InstallBanner } from '@cleverbrush/website-shared/components/InstallBanner';
+import { PerformativeCodeStage } from '@cleverbrush/website-shared/components/Performative';
import { highlightTS } from '@cleverbrush/website-shared/lib/highlight';
import Link from 'next/link';
import { docsMetadata } from '../site';
@@ -19,6 +20,25 @@ export default function GettingStartedPage() {
+
+
{/* ── Step 0: Install ─────────────────────────────── */}
{/* ── Hero ─────────────────────────────────────────────── */}
-
-
- Schema-first full-stack TypeScript framework
-
-
- One schema.
-
- Full stack.
-
-
- Define your data shapes once with{' '}
-
- @cleverbrush/schema
-
- . Get type-safe servers, auto-typed clients, OpenAPI docs,
- dependency injection, auth, and React forms — all from that
- single definition. Zero duplication. Zero drift.
-
-
-
- Get Started
-
+ Define data shapes once with{' '}
+
-
-
-
-
- Why Cleverbrush?
-
-
-
-
-
- GitHub
-
-
-
-
Contract-first
-
Zero codegen
-
OpenAPI 3.1
-
Built-in auth & DI
-
Client resilience
-
React integration
+ @cleverbrush/schema
+
+ . Get type-safe servers, auto-typed clients, OpenAPI
+ docs, dependency injection, auth, and React forms from
+ that single definition.
+ >
+ }
+ actions={[
+ {
+ href: '/getting-started',
+ label: 'Get started',
+ variant: 'glow'
+ },
+ {
+ href: '/why',
+ label: 'Why Cleverbrush?',
+ variant: 'wave'
+ },
+ {
+ href: 'https://github.com/cleverbrush/framework',
+ label: 'GitHub',
+ external: true,
+ variant: 'ghost'
+ }
+ ]}
+ metrics={[
+ { target: 18, label: 'workspace packages' },
+ { value: '0', label: 'client codegen steps' },
+ { value: '3.1', label: 'OpenAPI target' },
+ { value: '1', label: 'shared schema contract' }
+ ]}
+ badges={[
+ 'Contract-first REST',
+ 'Built-in auth and DI',
+ 'Typed resilient client',
+ 'Schema-driven React forms'
+ ]}
+ code={{
+ filename: 'contract.ts',
+ code: `import { defineApi, endpoint } from '@cleverbrush/server/contract';
+import { object, string, array } from '@cleverbrush/schema';
+
+const User = object({
+ id: string().uuid(),
+ email: string().email()
+});
+
+export const api = defineApi({
+ users: {
+ list: endpoint.get('/api/users').returns(array(User)),
+ create: endpoint.post('/api/users').body(User).returns(User)
+ }
+});`
+ }}
+ />
+
diff --git a/websites/docs/app/why/page.tsx b/websites/docs/app/why/page.tsx
index 1eda81bf..8782a76d 100644
--- a/websites/docs/app/why/page.tsx
+++ b/websites/docs/app/why/page.tsx
@@ -1,5 +1,6 @@
/** biome-ignore-all lint/security/noDangerouslySetInnerHtml: needed for code examples */
+import { PerformativeBeforeAfter } from '@cleverbrush/website-shared/components/Performative';
import { highlightTS } from '@cleverbrush/website-shared/lib/highlight';
import Link from 'next/link';
import { docsMetadata } from '../site';
@@ -18,6 +19,20 @@ export default function WhyPage() {
+
+
{/* ── The Problem ─────────────────────────────────── */}
The problem: types everywhere, in sync nowhere
diff --git a/websites/docs/package.json b/websites/docs/package.json
index ab648246..d9f05403 100644
--- a/websites/docs/package.json
+++ b/websites/docs/package.json
@@ -10,6 +10,7 @@
"dependencies": {
"@cleverbrush/website-shared": "file:../shared",
"next": "^16.2.1",
+ "performative-ui": "^0.3.0",
"react": "^19.1.0",
"react-dom": "^19.1.0"
},
diff --git a/websites/schema/app/layout.tsx b/websites/schema/app/layout.tsx
index b7b32fa7..41cb74e5 100644
--- a/websites/schema/app/layout.tsx
+++ b/websites/schema/app/layout.tsx
@@ -1,4 +1,5 @@
import type { Metadata } from 'next';
+import 'performative-ui/styles.css';
import '@cleverbrush/website-shared/styles/globals.css';
import { ConsentManager } from '@cleverbrush/website-shared/components/ConsentManager';
import type { FooterSection } from '@cleverbrush/website-shared/components/Footer';
diff --git a/websites/schema/app/mapper/page.tsx b/websites/schema/app/mapper/page.tsx
index 98b31038..3637b31f 100644
--- a/websites/schema/app/mapper/page.tsx
+++ b/websites/schema/app/mapper/page.tsx
@@ -1,4 +1,5 @@
import { InstallBanner } from '@cleverbrush/website-shared/components/InstallBanner';
+import { PerformativeBeforeAfter } from '@cleverbrush/website-shared/components/Performative';
import { highlightTS } from '@cleverbrush/website-shared/lib/highlight';
import { schemaMetadata } from '../site';
@@ -16,6 +17,20 @@ export default function MapperPage() {
+
+
{/* ── Installation ─────────────────────────────────── */}
void;
}
+const monacoVsPath = 'https://cdn.jsdelivr.net/npm/monaco-editor@0.55.1/min/vs';
+
+let monacoPrepared = false;
+
export function PlaygroundEditor({ code, onChange, onMount }: Props) {
const editorRef = useRef(null);
const monacoRef = useRef(null);
- const initDone = useRef(false);
+ const [loadState, setLoadState] = useState<'loading' | 'ready' | 'error'>(
+ 'loading'
+ );
+ const [loadError, setLoadError] = useState(null);
+
+ useEffect(() => {
+ let cancelled = false;
+
+ import('@monaco-editor/react')
+ .then(({ loader }) => {
+ loader.config({
+ paths: {
+ vs: monacoVsPath
+ }
+ });
+ return loader.init();
+ })
+ .then(monaco => {
+ if (cancelled) return;
+ prepareMonaco(monaco as MonacoInstance);
+ setLoadState('ready');
+ })
+ .catch(error => {
+ const message = describeMonacoError(error);
+ console.error('Monaco initialization error:', message);
+ if (cancelled) return;
+ setLoadError(message);
+ setLoadState('error');
+ });
+
+ return () => {
+ cancelled = true;
+ };
+ }, []);
const handleBeforeMount = useCallback((monaco: unknown) => {
- defineTheme(monaco as MonacoInstance);
+ prepareMonaco(monaco as MonacoInstance);
}, []);
const handleMount = useCallback(
(editor: unknown, monaco: unknown) => {
editorRef.current = editor;
monacoRef.current = monaco;
-
- if (!initDone.current) {
- initDone.current = true;
- configureMonaco(monaco as MonacoInstance);
- }
+ prepareMonaco(monaco as MonacoInstance);
onMount?.(editor, monaco);
},
@@ -47,6 +80,24 @@ export function PlaygroundEditor({ code, onChange, onMount }: Props) {
[onChange]
);
+ if (loadState === 'error') {
+ return (
+
+
+ Editor failed to load: {loadError ?? 'unknown error'}
+
+
+ );
+ }
+
+ if (loadState === 'loading') {
+ return (
+
+ );
+ }
+
return (
+
+
{/* ── Installation ─────────────────────────────────── */}
-
- The cornerstone of type-safe TypeScript
-
-
- One schema.
-
- Types, validation, forms.
-
-
- @cleverbrush/schema is an immutable, composable
- schema library that infers your TypeScript types at compile time
- and validates your data at runtime — with zero dependencies. It
- lays the foundation for a rich ecosystem — much like{' '}
-
- Zod
- {' '}
- has shown is possible.
-
-
-
- Explore the Schema Library
-
-
-
-
-
-
-
-
- Try in Playground
-
-
-
-
-
- GitHub
-
-
-
- Zero runtime dependencies
- Compile-time type inference
- Immutable & composable
- BSD-3 Licensed
-
- {smallGzip} min (full {fullGzip}) gzipped
-
- Standard Schema compatible
- 98% test coverage
- Faster than Zod in most tests
-
-
+ <>
+
+ @cleverbrush/schema infers TypeScript types
+ at compile time, validates untrusted data at runtime,
+ and exposes typed descriptors that power forms, mappers,
+ OpenAPI, and your own tooling.
+ >
+ }
+ actions={[
+ {
+ href: '/docs',
+ label: 'Explore the schema library',
+ variant: 'glow'
+ },
+ {
+ href: '/playground',
+ label: 'Try the playground',
+ variant: 'wave'
+ },
+ {
+ href: 'https://github.com/cleverbrush/framework',
+ label: 'GitHub',
+ external: true,
+ variant: 'ghost'
+ }
+ ]}
+ metrics={[
+ { value: smallGzip, label: 'minimal gzipped build' },
+ { value: fullGzip, label: 'full gzipped build' },
+ { value: '0', label: 'runtime dependencies' },
+ { target: 98, suffix: '%', label: 'line coverage' }
+ ]}
+ badges={[
+ 'Immutable builders',
+ 'Standard Schema compatible',
+ 'Typed field selectors',
+ 'BSD-3 licensed'
+ ]}
+ code={{
+ filename: 'schema.ts',
+ code: `import { object, string, number } from '@cleverbrush/schema';
+
+const User = object({
+ name: string().minLength(2),
+ email: string().email(),
+ age: number().min(0).max(150)
+});
+
+const result = User.validate(input);
+
+if (!result.valid) {
+ result.getErrorsFor(u => u.email);
+}`
+ }}
+ />
+
+ >
);
}
diff --git a/websites/schema/app/showcases/page.tsx b/websites/schema/app/showcases/page.tsx
index 4eed8306..17204158 100644
--- a/websites/schema/app/showcases/page.tsx
+++ b/websites/schema/app/showcases/page.tsx
@@ -1,9 +1,25 @@
-import Link from 'next/link';
+import { PerformativeGlassGrid } from '@cleverbrush/website-shared/components/Performative';
import { schemaMetadata } from '../site';
export const metadata = schemaMetadata('/showcases');
-const SHOWCASES = [
+interface Showcase {
+ href: string;
+ title: string;
+ description: string;
+ badge: string;
+ external?: boolean;
+}
+
+const SHOWCASES: Showcase[] = [
+ {
+ href: 'https://xpenser.cleverbrush.com',
+ title: 'xpenser',
+ description:
+ 'Self-hostable personal finance tracker and real Cleverbrush reference app using schemas, contracts, server handlers, typed clients, React forms, OpenAPI, observability, Telegram, and MCP.',
+ badge: 'App',
+ external: true
+ },
{
href: '/showcases/tanstack-form',
title: 'TanStack Form',
@@ -41,48 +57,16 @@ export default function ShowcasesPage() {
-
- {SHOWCASES.map(s => (
-
-
-
{s.title}
-
- {s.badge}
-
-
-
- {s.description}
-
-
- ))}
-
+ ({
+ title: s.title,
+ body: s.description,
+ icon: s.badge,
+ href: s.href,
+ external: s.external,
+ linkLabel: 'Open showcase'
+ }))}
+ />
);
diff --git a/websites/schema/app/showcases/t3-env/page.tsx b/websites/schema/app/showcases/t3-env/page.tsx
index 7e37c1af..72893537 100644
--- a/websites/schema/app/showcases/t3-env/page.tsx
+++ b/websites/schema/app/showcases/t3-env/page.tsx
@@ -1,6 +1,7 @@
'use client';
import { boolean, number, string } from '@cleverbrush/schema';
+import { PerformativeBeforeAfter } from '@cleverbrush/website-shared/components/Performative';
import { highlightTS } from '@cleverbrush/website-shared/lib/highlight';
import { useState } from 'react';
@@ -241,6 +242,20 @@ export default function T3EnvPage() {
+
+
{/* ── How it works ─────────────────────────── */}
How it works
diff --git a/websites/schema/app/showcases/tanstack-form/page.tsx b/websites/schema/app/showcases/tanstack-form/page.tsx
index 205e5677..0b385e8f 100644
--- a/websites/schema/app/showcases/tanstack-form/page.tsx
+++ b/websites/schema/app/showcases/tanstack-form/page.tsx
@@ -1,6 +1,7 @@
'use client';
import { boolean, number, string } from '@cleverbrush/schema';
+import { PerformativeBeforeAfter } from '@cleverbrush/website-shared/components/Performative';
import { highlightTS } from '@cleverbrush/website-shared/lib/highlight';
import { useForm } from '@tanstack/react-form';
import { useState } from 'react';
@@ -381,6 +382,20 @@ export default function TanStackFormPage() {
+
+
{/* ── How it works ─────────────────────────────── */}
How it works
diff --git a/websites/schema/next.config.ts b/websites/schema/next.config.ts
index e72f37ee..5eb2eae8 100644
--- a/websites/schema/next.config.ts
+++ b/websites/schema/next.config.ts
@@ -4,11 +4,11 @@ const isDev = process.env.NODE_ENV === 'development';
const cspHeader = [
"default-src 'self'",
- `script-src 'self' 'unsafe-inline'${isDev ? " 'unsafe-eval'" : ''} https://www.googletagmanager.com https://www.google-analytics.com`,
- "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com",
+ `script-src 'self' 'unsafe-inline'${isDev ? " 'unsafe-eval'" : ''} https://www.googletagmanager.com https://www.google-analytics.com https://cdn.jsdelivr.net`,
+ "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://cdn.jsdelivr.net",
"img-src 'self' data: blob:",
- "font-src 'self' https://fonts.gstatic.com data:",
- "connect-src 'self' https://www.google-analytics.com",
+ "font-src 'self' https://fonts.gstatic.com https://cdn.jsdelivr.net data:",
+ "connect-src 'self' https://www.google-analytics.com https://cdn.jsdelivr.net",
'frame-src https://www.googletagmanager.com',
"worker-src 'self' blob:",
"object-src 'none'",
diff --git a/websites/schema/package.json b/websites/schema/package.json
index 9c5d4312..691964b6 100644
--- a/websites/schema/package.json
+++ b/websites/schema/package.json
@@ -19,6 +19,7 @@
"@t3-oss/env-nextjs": "^0.13.11",
"@tanstack/react-form": "^1.28.6",
"next": "^16.2.1",
+ "performative-ui": "^0.3.0",
"react": "^19.2.4",
"react-dom": "^19.2.4"
},
diff --git a/websites/shared/components/Performative.tsx b/websites/shared/components/Performative.tsx
new file mode 100644
index 00000000..175da04e
--- /dev/null
+++ b/websites/shared/components/Performative.tsx
@@ -0,0 +1,611 @@
+'use client';
+
+import Link from 'next/link';
+import { useEffect, useMemo, useState } from 'react';
+import type { ReactNode } from 'react';
+import {
+ AsciiHero,
+ BeforeAfter,
+ Button,
+ CommunityBadge,
+ EyebrowPill,
+ FloatingSparkles,
+ GlassCard,
+ GradientText,
+ LogoMarquee,
+ MockIDE,
+ NodeGraphBackground,
+ StatCounter,
+ WordRoll
+} from 'performative-ui';
+import type { ButtonVariant, IdeToken } from 'performative-ui';
+
+export interface PerformativeAction {
+ href: string;
+ label: string;
+ external?: boolean;
+ variant?: ButtonVariant;
+}
+
+export interface PerformativeMetric {
+ label: string;
+ value?: string;
+ target?: number;
+ prefix?: string;
+ suffix?: string;
+ decimals?: number;
+}
+
+export interface PerformativeCodeSample {
+ filename: string;
+ code: string;
+ thinkingLabel?: ReactNode | false;
+}
+
+export interface PerformativeHeroProps {
+ eyebrow: string;
+ headline: string;
+ rotatingWords: string[];
+ body: ReactNode;
+ actions: PerformativeAction[];
+ metrics?: PerformativeMetric[];
+ badges?: string[];
+ code?: PerformativeCodeSample;
+ wordDirection?: 'up' | 'down';
+}
+
+export interface PerformativeFeature {
+ title: string;
+ body: ReactNode;
+ icon?: ReactNode;
+ href?: string;
+ external?: boolean;
+ linkLabel?: string;
+}
+
+export interface PerformativeProofItem {
+ title: ReactNode;
+ subtitle: ReactNode;
+ href: string;
+ icon?: ReactNode;
+}
+
+export interface PerformativeMarqueeItem {
+ label: string;
+ tone?: 'serif' | 'mono' | 'strong';
+}
+
+const keywordPattern =
+ /\b(import|from|const|type|export|return|await|async|if|else|new)\b/g;
+
+const HERO_CODE_CHAR_MS: [number, number] = [1, 8];
+const CODE_STAGE_CHAR_MS: [number, number] = [1, 6];
+
+function tokenizeLine(line: string): IdeToken[] {
+ const tokens: IdeToken[] = [];
+ let cursor = 0;
+ const matches = Array.from(line.matchAll(keywordPattern));
+
+ for (const match of matches) {
+ const index = match.index ?? 0;
+ if (index > cursor) {
+ tokens.push(...tokenizePlain(line.slice(cursor, index)));
+ }
+ tokens.push({ c: match[0], cls: 'key' });
+ cursor = index + match[0].length;
+ }
+
+ if (cursor < line.length) {
+ tokens.push(...tokenizePlain(line.slice(cursor)));
+ }
+
+ tokens.push({ c: '\n' });
+ return tokens;
+}
+
+function tokenizePlain(source: string): IdeToken[] {
+ const tokens: IdeToken[] = [];
+ const pattern =
+ /('[^']*'|"[^"]*"|`[^`]*`|\b\d+(?:\.\d+)?\b|\/\/.*$)/g;
+ let cursor = 0;
+ const matches = Array.from(source.matchAll(pattern));
+
+ for (const match of matches) {
+ const index = match.index ?? 0;
+ if (index > cursor) {
+ tokens.push({ c: source.slice(cursor, index) });
+ }
+ const value = match[0];
+ const cls = value.startsWith('//')
+ ? 'com'
+ : /^\d/.test(value)
+ ? 'num'
+ : 'str';
+ tokens.push({ c: value, cls });
+ cursor = index + value.length;
+ }
+
+ if (cursor < source.length) {
+ tokens.push({ c: source.slice(cursor) });
+ }
+
+ return tokens;
+}
+
+function tokenizeCode(code: string): IdeToken[] {
+ return code.split('\n').flatMap(tokenizeLine);
+}
+
+function useMounted() {
+ const [mounted, setMounted] = useState(false);
+
+ useEffect(() => {
+ setMounted(true);
+ }, []);
+
+ return mounted;
+}
+
+function ClientOnlySparkles() {
+ const mounted = useMounted();
+
+ if (!mounted) {
+ return
;
+ }
+
+ return (
+
+ );
+}
+
+function ActionButton({ action }: { action: PerformativeAction }) {
+ const variant = action.variant ?? 'ghost';
+
+ if (action.external) {
+ return (
+
+ {action.label}
+
+ );
+ }
+
+ return (
+
+ {action.label}
+
+ );
+}
+
+function StableWordRoll({
+ words,
+ direction
+}: {
+ words: string[];
+ direction: 'up' | 'down';
+}) {
+ const longestWord =
+ words.reduce(
+ (longest, word) =>
+ word.length > longest.length ? word : longest,
+ ''
+ ) || words[0];
+
+ return (
+
+
+ {longestWord}
+
+
+
+ );
+}
+
+function renderTypedTokens(tokens: IdeToken[], visibleChars: number) {
+ const rendered: ReactNode[] = [];
+ let remaining = visibleChars;
+
+ for (let index = 0; index < tokens.length; index++) {
+ if (remaining <= 0) {
+ break;
+ }
+
+ const token = tokens[index];
+ const text = token.c.slice(0, remaining);
+
+ if (text.length === 0) {
+ continue;
+ }
+
+ rendered.push(
+ token.cls ? (
+
+ {text}
+
+ ) : (
+ text
+ )
+ );
+ remaining -= text.length;
+ }
+
+ return rendered;
+}
+
+function OneShotIdeBody({
+ tokens,
+ charMs
+}: {
+ tokens: IdeToken[];
+ charMs: [number, number];
+}) {
+ const [visibleChars, setVisibleChars] = useState(0);
+ const [complete, setComplete] = useState(false);
+ const fullText = useMemo(
+ () => tokens.map(token => token.c).join(''),
+ [tokens]
+ );
+
+ useEffect(() => {
+ let cancelled = false;
+ let timeout: ReturnType
| undefined;
+ let current = 0;
+
+ setVisibleChars(0);
+ setComplete(false);
+
+ const tick = () => {
+ if (cancelled) {
+ return;
+ }
+
+ if (current >= fullText.length) {
+ setComplete(true);
+ return;
+ }
+
+ current += 1;
+ setVisibleChars(current);
+
+ const [min, max] = charMs;
+ const typedChar = fullText[current - 1];
+ const delay =
+ min +
+ Math.random() * (max - min) +
+ (typedChar === '\n' ? 120 : 0);
+
+ timeout = setTimeout(tick, delay);
+ };
+
+ timeout = setTimeout(tick, charMs[0]);
+
+ return () => {
+ cancelled = true;
+ if (timeout) {
+ clearTimeout(timeout);
+ }
+ };
+ }, [charMs, fullText]);
+
+ return (
+
+ {renderTypedTokens(tokens, visibleChars)}
+ {!complete && }
+
+ );
+}
+
+function OneShotMockIDE({
+ sample,
+ charMs
+}: {
+ sample: PerformativeCodeSample;
+ charMs: [number, number];
+}) {
+ const tokens = useMemo(() => tokenizeCode(sample.code), [sample.code]);
+
+ return (
+
+
+
+
+ );
+}
+
+export function PerformativeMetricStrip({
+ metrics,
+ className = ''
+}: {
+ metrics: PerformativeMetric[];
+ className?: string;
+}) {
+ return (
+
+ {metrics.map(metric => (
+
+
+ {metric.prefix}
+ {typeof metric.target === 'number' ? (
+
+ value.toLocaleString(undefined, {
+ maximumFractionDigits:
+ metric.decimals ?? 0,
+ minimumFractionDigits:
+ metric.decimals ?? 0
+ })
+ }
+ />
+ ) : (
+ metric.value
+ )}
+ {metric.suffix}
+
+ {metric.label}
+
+ ))}
+
+ );
+}
+
+export function PerformativeHero({
+ eyebrow,
+ headline,
+ rotatingWords,
+ body,
+ actions,
+ metrics = [],
+ badges = [],
+ code,
+ wordDirection = 'up'
+}: PerformativeHeroProps) {
+ return (
+
+
+
+
+
+
+
{eyebrow}
+
+ {headline}{' '}
+
+
+
{body}
+
+ {actions.map(action => (
+
+ ))}
+
+ {badges.length > 0 && (
+
+ {badges.map(badge => (
+ {badge}
+ ))}
+
+ )}
+
+ {code && (
+
+
+
+ )}
+
+ {metrics.length > 0 && (
+
+ )}
+
+ );
+}
+
+export function PerformativeGlassGrid({
+ items,
+ className = ''
+}: {
+ items: PerformativeFeature[];
+ className?: string;
+}) {
+ return (
+
+ {items.map(item => (
+
+ {item.icon && {item.icon} }
+ {item.title}
+ {item.body}
+ {item.href && item.linkLabel && (
+
+ {item.linkLabel}
+
+ )}
+
+ ))}
+
+ );
+}
+
+export function PerformativeBeforeAfter({
+ before,
+ after,
+ brand,
+ beforeLabel = 'Before',
+ afterLabel = 'After',
+ className = ''
+}: {
+ before: ReactNode[];
+ after: ReactNode[];
+ brand: ReactNode;
+ beforeLabel?: ReactNode;
+ afterLabel?: ReactNode;
+ className?: string;
+}) {
+ return (
+
+
+
+ );
+}
+
+export function PerformativeCodeStage({
+ sample,
+ className = ''
+}: {
+ sample: PerformativeCodeSample;
+ className?: string;
+}) {
+ return (
+
+
+
+ );
+}
+
+export function PerformativeProofRow({
+ items,
+ marquee = [],
+ className = ''
+}: {
+ items?: PerformativeProofItem[];
+ marquee?: PerformativeMarqueeItem[];
+ className?: string;
+}) {
+ return (
+
+ {marquee.length > 0 && (
+
({
+ kind: 'node',
+ key: item.label,
+ node: (
+
+ {item.label}
+
+ )
+ }))}
+ />
+ )}
+ {items && items.length > 0 && (
+
+ {items.map(item => (
+
+ ))}
+
+ )}
+
+ );
+}
+
+export function PerformativeSlippyWords({
+ words,
+ gradient = false,
+ className = ''
+}: {
+ words: string[];
+ gradient?: boolean;
+ className?: string;
+}) {
+ return (
+
+
+ {words.map(word => (
+
+ {word}
+
+ ))}
+
+
+ );
+}
+
+export function PerformativeGradientText({
+ children
+}: {
+ children: ReactNode;
+}) {
+ return {children} ;
+}
diff --git a/websites/shared/package.json b/websites/shared/package.json
index 91307421..cc08ba96 100644
--- a/websites/shared/package.json
+++ b/websites/shared/package.json
@@ -8,6 +8,7 @@
"./components/Navbar": "./components/Navbar.tsx",
"./components/Footer": "./components/Footer.tsx",
"./components/InstallBanner": "./components/InstallBanner.tsx",
+ "./components/Performative": "./components/Performative.tsx",
"./components/ConsentManager": "./components/ConsentManager.tsx",
"./components/PrivacyPage": "./components/PrivacyPage.tsx",
"./components/ThemeProvider": "./components/ThemeProvider.tsx",
@@ -17,6 +18,7 @@
},
"dependencies": {
"next": "^16.2.1",
+ "performative-ui": "^0.3.0",
"react": "^19.1.0"
}
}
diff --git a/websites/shared/styles/globals.css b/websites/shared/styles/globals.css
index 89685c9c..5f6ad418 100644
--- a/websites/shared/styles/globals.css
+++ b/websites/shared/styles/globals.css
@@ -3,7 +3,7 @@
══════════════════════════════════════════════════════════════════ */
/* ── Google Fonts ────────────────────────────────────────────────── */
-@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&family=JetBrains+Mono:wght@400;500;600&display=swap");
+@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&family=JetBrains+Mono:wght@400;500;600&family=Instrument+Serif:ital@0;1&display=swap");
/* ── CSS Custom Properties ───────────────────────────────────────── */
:root {
@@ -2329,6 +2329,12 @@ pre code {
height: 100%;
color: var(--text-muted);
font-size: 0.85rem;
+ padding: 1rem;
+ text-align: center;
+}
+
+.pg-editor-loading--error {
+ color: var(--pui-danger, #f87171);
}
.pg-spinner {
@@ -4086,3 +4092,605 @@ pre code {
color: var(--text-secondary);
line-height: 1.5;
}
+
+/* ══════════════════════════════════════════════════════════════════
+ Performative UI integration
+ ══════════════════════════════════════════════════════════════════ */
+
+:root {
+ --pui-grad-from: #22c55e;
+ --pui-grad-mid: #38bdf8;
+ --pui-grad-to: #f59e0b;
+ --pui-grad: linear-gradient(
+ 120deg,
+ var(--pui-grad-from),
+ var(--pui-grad-mid),
+ var(--pui-grad-to)
+ );
+ --pui-bg: var(--bg-primary);
+ --pui-bg-elev: var(--bg-card);
+ --pui-bg-soft: var(--bg-secondary);
+ --pui-border: var(--border-subtle);
+ --pui-border-bright: rgba(56, 189, 248, 0.26);
+ --pui-fg: var(--text-primary);
+ --pui-fg-dim: var(--text-secondary);
+ --pui-fg-mute: var(--text-muted);
+ --pui-glass: rgba(8, 12, 20, 0.76);
+ --pui-glass-deep: rgba(8, 12, 20, 0.84);
+ --pui-glass-soft: rgba(8, 12, 20, 0.54);
+}
+
+[data-theme="light"] {
+ --pui-bg: var(--bg-primary);
+ --pui-bg-elev: rgba(255, 255, 255, 0.88);
+ --pui-bg-soft: var(--bg-secondary);
+ --pui-border: var(--border-subtle);
+ --pui-border-bright: rgba(8, 145, 178, 0.24);
+ --pui-fg: var(--text-primary);
+ --pui-fg-dim: var(--text-secondary);
+ --pui-fg-mute: var(--text-muted);
+ --pui-glass: rgba(255, 255, 255, 0.78);
+ --pui-glass-deep: rgba(255, 255, 255, 0.88);
+ --pui-glass-soft: rgba(255, 255, 255, 0.62);
+}
+
+.cb-pui-hero,
+.cb-pui-hero *,
+.cb-pui-metrics,
+.cb-pui-metrics *,
+.cb-pui-glass-grid,
+.cb-pui-glass-grid *,
+.cb-pui-before-after,
+.cb-pui-before-after *,
+.cb-pui-code-stage,
+.cb-pui-code-stage *,
+.cb-pui-proof,
+.cb-pui-proof * {
+ letter-spacing: 0;
+}
+
+.cb-pui-hero {
+ position: relative;
+ isolation: isolate;
+ overflow: hidden;
+ margin-top: 60px;
+ padding: 5rem 0 3rem;
+ background:
+ linear-gradient(rgba(255, 255, 255, 0.018) 1px, transparent 1px),
+ linear-gradient(
+ 90deg,
+ rgba(255, 255, 255, 0.018) 1px,
+ transparent 1px
+ ),
+ linear-gradient(180deg, var(--bg-primary), rgba(9, 13, 20, 0.96));
+ background-size:
+ 42px 42px,
+ 42px 42px,
+ auto;
+}
+
+[data-theme="light"] .cb-pui-hero {
+ background:
+ linear-gradient(rgba(0, 0, 0, 0.035) 1px, transparent 1px),
+ linear-gradient(90deg, rgba(0, 0, 0, 0.035) 1px, transparent 1px),
+ linear-gradient(180deg, #f8f9fc, #eef0f6);
+ background-size:
+ 42px 42px,
+ 42px 42px,
+ auto;
+}
+
+.cb-pui-hero__nodes,
+.cb-pui-hero__ascii,
+.cb-pui-hero__sparkles {
+ position: absolute;
+ inset: 0;
+}
+
+.cb-pui-hero__nodes {
+ z-index: 0;
+ opacity: 0.8;
+}
+
+.cb-pui-hero__ascii {
+ z-index: 1;
+ opacity: 0.7;
+ mask-image: linear-gradient(90deg, transparent, #000 20%, #000 80%);
+ -webkit-mask-image: linear-gradient(
+ 90deg,
+ transparent,
+ #000 20%,
+ #000 80%
+ );
+}
+
+.cb-pui-hero__sparkles {
+ z-index: 2;
+ opacity: 0.42;
+}
+
+.cb-pui-hero__inner {
+ position: relative;
+ z-index: 3;
+ display: grid;
+ grid-template-columns: minmax(0, 1fr);
+ gap: 2rem;
+ align-items: center;
+}
+
+.cb-pui-hero__copy {
+ max-width: 720px;
+ text-align: left;
+}
+
+.cb-pui-hero .pui-eyebrow {
+ margin-bottom: 1.1rem;
+}
+
+.cb-pui-hero h1 {
+ margin: 0 0 1.15rem;
+ color: var(--text-primary);
+ font-size: 2.55rem;
+ line-height: 1.08;
+ font-weight: 900;
+}
+
+.cb-pui-word-roll {
+ display: inline-grid;
+ grid-template-areas: "word";
+ vertical-align: baseline;
+ white-space: nowrap;
+}
+
+.cb-pui-word-roll__sizer,
+.cb-pui-word-roll__roll {
+ grid-area: word;
+}
+
+.cb-pui-word-roll__sizer {
+ visibility: hidden;
+ pointer-events: none;
+}
+
+.cb-pui-word-roll__roll {
+ width: 100%;
+}
+
+.cb-pui-word-roll .pui-roll__sizer {
+ min-width: 100%;
+}
+
+.cb-pui-hero__body {
+ max-width: 660px;
+ margin: 0;
+ color: var(--text-secondary);
+ font-size: 1.02rem;
+ line-height: 1.72;
+}
+
+.cb-pui-hero__body code {
+ white-space: nowrap;
+}
+
+.cb-pui-hero__actions {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.75rem;
+ margin: 1.75rem 0 1.5rem;
+}
+
+.cb-pui-hero__stage {
+ position: relative;
+ min-width: 0;
+}
+
+.cb-pui-hero__stage .pui-ide,
+.cb-pui-code-stage .pui-ide {
+ display: flex;
+ flex-direction: column;
+ width: 100%;
+ overflow: hidden;
+}
+
+.cb-pui-hero__stage .pui-ide {
+ height: 380px;
+}
+
+.cb-pui-hero__stage .pui-ide__body {
+ flex: 1 1 auto;
+ min-height: 0;
+ overflow: auto;
+}
+
+.cb-pui-code-stage .pui-ide {
+ height: 320px;
+}
+
+.cb-pui-badges {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.55rem;
+ max-width: 700px;
+}
+
+.cb-pui-badges span {
+ display: inline-flex;
+ align-items: center;
+ min-height: 2rem;
+ padding: 0.35rem 0.8rem;
+ border: 1px solid rgba(56, 189, 248, 0.2);
+ border-radius: 999px;
+ background: rgba(15, 23, 42, 0.46);
+ color: var(--text-secondary);
+ font-size: 0.78rem;
+ font-weight: 600;
+}
+
+[data-theme="light"] .cb-pui-badges span {
+ background: rgba(255, 255, 255, 0.72);
+}
+
+[data-theme="light"] .cb-pui-hero .pui-btn--wave {
+ color: #0f172a;
+ border-color: rgba(8, 145, 178, 0.34);
+}
+
+[data-theme="light"] .cb-pui-hero .pui-btn--wave:hover {
+ color: #0e7490;
+ border-color: rgba(8, 145, 178, 0.55);
+}
+
+.cb-pui-metrics {
+ position: relative;
+ z-index: 3;
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ align-items: stretch;
+ gap: 0.55rem;
+ margin-top: 1.35rem;
+}
+
+.cb-pui-metric {
+ min-width: 0;
+ min-height: 4.35rem;
+ padding: 0.7rem 0.8rem;
+ border: 1px solid rgba(148, 163, 184, 0.16);
+ border-radius: 0.65rem;
+ background: rgba(8, 12, 20, 0.42);
+ backdrop-filter: blur(12px);
+ display: grid;
+ align-content: center;
+ gap: 0.25rem;
+}
+
+[data-theme="light"] .cb-pui-metric {
+ background: rgba(255, 255, 255, 0.58);
+}
+
+.cb-pui-metric strong {
+ display: block;
+ min-width: 0;
+ color: var(--text-primary);
+ font-size: 1.3rem;
+ line-height: 1;
+ font-weight: 900;
+ font-variant-numeric: tabular-nums;
+ white-space: nowrap;
+}
+
+.cb-pui-metric .pui-stat {
+ display: inline;
+ letter-spacing: 0;
+}
+
+.cb-pui-metric > span {
+ display: block;
+ margin-top: 0;
+ color: var(--text-muted);
+ font-size: 0.72rem;
+ line-height: 1.25;
+}
+
+.cb-pui-glass-grid {
+ display: grid;
+ grid-template-columns: 1fr;
+ gap: 1rem;
+ margin: 1.5rem 0;
+}
+
+.cb-pui-glass-card {
+ height: 100%;
+}
+
+.cb-pui-glass-card .pui-glass-card__body {
+ margin-bottom: 0;
+}
+
+.cb-pui-glass-card .pui-glass-card__link {
+ margin-top: 1rem;
+}
+
+.cb-pui-before-after {
+ margin: 1.5rem 0;
+}
+
+.cb-pui-before-after .pui-ba__panel {
+ min-width: 0;
+}
+
+.cb-pui-before-after .pui-ba__panel li {
+ overflow-wrap: anywhere;
+}
+
+.cb-pui-code-stage {
+ margin: 1.5rem 0;
+}
+
+.cb-pui-code-stage .pui-ide__body {
+ flex: 1 1 auto;
+ min-height: 0;
+ overflow: auto;
+}
+
+.cb-pui-proof {
+ position: relative;
+ z-index: 3;
+ margin: 2rem 0;
+}
+
+.cb-pui-logo {
+ display: inline-flex;
+ align-items: center;
+ min-height: 2rem;
+ color: var(--pui-logo-text-strong);
+ font-family: "Inter", sans-serif;
+ font-size: 1rem;
+ font-weight: 800;
+ white-space: nowrap;
+}
+
+.cb-pui-logo--mono {
+ font-family: "JetBrains Mono", monospace;
+ font-size: 0.92rem;
+ font-weight: 600;
+}
+
+.cb-pui-logo--serif {
+ font-family: "Instrument Serif", serif;
+ font-size: 1.35rem;
+ font-weight: 400;
+}
+
+.cb-pui-community-grid {
+ display: grid;
+ grid-template-columns: 1fr;
+ gap: 0.8rem;
+ margin-top: 1rem;
+}
+
+.cb-pui-community-grid .pui-community {
+ width: 100%;
+}
+
+.cb-pui-slippy {
+ margin: 1.5rem 0;
+}
+
+.cb-pui-slippy__track {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+ gap: 0.75rem;
+}
+
+.cb-pui-slippy__word {
+ display: inline-flex;
+ align-items: center;
+ min-height: 2.25rem;
+ padding: 0.45rem 1rem;
+ border: 1px solid var(--border-subtle);
+ border-radius: 999px;
+ background: rgba(15, 23, 42, 0.34);
+ color: var(--text-muted);
+ font-size: 0.88rem;
+ font-weight: 600;
+}
+
+.cb-pui-slippy__word.is-gradient {
+ border-color: var(--border-subtle);
+ background: rgba(15, 23, 42, 0.34);
+ color: var(--text-muted);
+}
+
+[data-theme="light"] .cb-pui-slippy__word,
+[data-theme="light"] .cb-pui-slippy__word.is-gradient {
+ background: rgba(255, 255, 255, 0.68);
+}
+
+.page .section-header {
+ position: relative;
+ isolation: isolate;
+ padding: 2.75rem 1.5rem;
+ margin-bottom: 2rem;
+ border: 1px solid rgba(56, 189, 248, 0.16);
+ border-radius: var(--radius-lg);
+ background:
+ linear-gradient(rgba(255, 255, 255, 0.018) 1px, transparent 1px),
+ linear-gradient(
+ 90deg,
+ rgba(255, 255, 255, 0.018) 1px,
+ transparent 1px
+ ),
+ rgba(12, 16, 24, 0.62);
+ background-size:
+ 34px 34px,
+ 34px 34px,
+ auto;
+ overflow: hidden;
+}
+
+[data-theme="light"] .page .section-header {
+ background:
+ linear-gradient(rgba(0, 0, 0, 0.028) 1px, transparent 1px),
+ linear-gradient(90deg, rgba(0, 0, 0, 0.028) 1px, transparent 1px),
+ rgba(255, 255, 255, 0.76);
+ background-size:
+ 34px 34px,
+ 34px 34px,
+ auto;
+}
+
+.page .section-header::before {
+ content: "";
+ position: absolute;
+ top: 0;
+ right: 0;
+ left: 0;
+ height: 2px;
+ background: var(--pui-grad);
+ background-size: 200% 200%;
+ animation: pui-grad-shift 8s ease infinite;
+}
+
+.page .section-header h1,
+.page .section-header h2 {
+ font-size: 2.15rem;
+ line-height: 1.1;
+}
+
+.page .section-header .subtitle {
+ max-width: 720px;
+}
+
+.page .card,
+.page .why-box,
+.page .feature-card {
+ border-color: rgba(56, 189, 248, 0.13);
+ background:
+ linear-gradient(180deg, rgba(255, 255, 255, 0.035), transparent),
+ var(--bg-card);
+ box-shadow: 0 16px 42px rgba(0, 0, 0, 0.16);
+}
+
+[data-theme="light"] .page .card,
+[data-theme="light"] .page .why-box,
+[data-theme="light"] .page .feature-card {
+ box-shadow: 0 12px 30px rgba(15, 23, 42, 0.06);
+}
+
+.page .card:hover,
+.page .why-box:hover,
+.page .feature-card:hover {
+ border-color: rgba(56, 189, 248, 0.28);
+}
+
+.page .why-box::before {
+ background: var(--pui-grad);
+}
+
+@media (min-width: 860px) {
+ .cb-pui-hero {
+ padding: 6.5rem 0 3.5rem;
+ }
+
+ .cb-pui-hero__inner {
+ grid-template-columns: minmax(0, 0.92fr) minmax(360px, 0.78fr);
+ gap: 2.5rem;
+ }
+
+ .cb-pui-hero h1 {
+ font-size: 4rem;
+ }
+
+ .cb-pui-metrics {
+ grid-template-columns: repeat(4, minmax(0, 1fr));
+ }
+
+ .cb-pui-glass-grid {
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ }
+
+ .cb-pui-community-grid {
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ }
+}
+
+@media (max-width: 859px) {
+ .cb-pui-hero__copy {
+ text-align: center;
+ margin: 0 auto;
+ }
+
+ .cb-pui-hero__actions,
+ .cb-pui-badges {
+ justify-content: center;
+ }
+
+ .cb-pui-hero__stage .pui-ide__body {
+ min-height: 0;
+ }
+}
+
+@media (max-width: 560px) {
+ .cb-pui-hero {
+ padding: 4rem 0 2.5rem;
+ }
+
+ .cb-pui-hero h1 {
+ font-size: 2.05rem;
+ }
+
+ .cb-pui-word-roll .pui-roll__word {
+ left: 50%;
+ transform: translate(-50%, 100%);
+ }
+
+ .cb-pui-word-roll .pui-roll__word--active {
+ transform: translate(-50%, 0);
+ }
+
+ .cb-pui-word-roll .pui-roll__word--past {
+ transform: translate(-50%, -100%);
+ }
+
+ .cb-pui-word-roll .pui-roll--down .pui-roll__word {
+ transform: translate(-50%, -100%);
+ }
+
+ .cb-pui-word-roll .pui-roll--down .pui-roll__word--active {
+ transform: translate(-50%, 0);
+ }
+
+ .cb-pui-word-roll .pui-roll--down .pui-roll__word--past {
+ transform: translate(-50%, 100%);
+ }
+
+ .cb-pui-hero__stage .pui-ide {
+ height: 300px;
+ }
+
+ .cb-pui-hero__body {
+ font-size: 0.96rem;
+ }
+
+ .cb-pui-hero .pui-btn {
+ width: 100%;
+ }
+
+ .page .section-header {
+ padding: 2rem 1rem;
+ }
+
+ .page .section-header h1,
+ .page .section-header h2 {
+ font-size: 1.8rem;
+ }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .cb-pui-hero__nodes,
+ .cb-pui-hero__ascii,
+ .cb-pui-hero__sparkles {
+ display: none;
+ }
+}