diff --git a/README.md b/README.md
index 4868de45..fc3c8698 100644
--- a/README.md
+++ b/README.md
@@ -1,101 +1,205 @@
-
-
+
+
CipherStash Stack for TypeScript
-

-

-

-

+
Field-level encryption for TypeScript apps — search encrypted data without decrypting it, with
+ zero-knowledge key management. Every value gets its own key, and your keys never leave your AWS KMS.
+

+

+

+

+

+

+
+
⭐ Star this repo if encryption you can actually query is your thing!
-## What is the stack?
+
+
+> **CipherStash never sees your plaintext.** Data is encrypted in your app with a unique key per value via
+> [ZeroKMS][zerokms], rooted in your own [AWS KMS][aws-kms] — so a database breach leaks only ciphertext.
+> [See the security architecture →][security-architecture]
+
+## Quick start
-- [Encryption](https://cipherstash.com/docs/stack/cipherstash/encryption): Field-level encryption for TypeScript apps with searchable encrypted queries, zero-knowledge key management, and first-class ORM support.
+You'll need a free CipherStash account to provision keys and a workspace — it takes about a minute.
-## Quick look at the stack in action
+**1. Create a free account** → **[cipherstash.com/signup][signup]**
-**Encryption**
+**2. Initialize your project** — the wizard authenticates you, builds an encryption schema, and wires up your database:
+
+```bash
+npx stash init
+```
+
+**3. Encrypt, search, and decrypt:**
```typescript
-import { Encryption, encryptedTable, encryptedColumn } from "@cipherstash/stack";
+import { Encryption } from "@cipherstash/stack";
+import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema";
-// 1. Define your schema
+// Define which columns are encrypted — and how you want to query them
const users = encryptedTable("users", {
email: encryptedColumn("email").equality().freeTextSearch(),
});
-// 2. Initialize the client
const client = await Encryption({ schemas: [users] });
-// 3. Encrypt
-const encryptResult = await client.encrypt("secret@example.com", {
- column: users.email,
- table: users,
+// Encrypt → store the ciphertext in your own database
+const enc = await client.encrypt("alice@example.com", { table: users, column: users.email });
+
+// Search WITHOUT decrypting — the part nobody else does
+const term = await client.encryptQuery("alice@example.com", {
+ table: users, column: users.email, queryType: "equality",
});
-if (encryptResult.failure) {
- // Handle errors your way
-}
-
-// 4. Decrypt
-const decryptResult = await client.decrypt(encryptResult.data);
-if (decryptResult.failure) {
- // Handle errors your way
-}
-// decryptResult.data => "secret@example.com"
+// → drop term.data straight into your WHERE clause
+
+// Decrypt when you need the plaintext back
+const dec = await client.decrypt(enc.data);
```
-## Install
+Prefer the long version? Follow the **[5-minute quickstart →][quickstart]**
-```bash
-npm install @cipherstash/stack
-# or
-yarn add @cipherstash/stack
-# or
-pnpm add @cipherstash/stack
-# or
-bun add @cipherstash/stack
+## What's in the Stack
+
+Three building blocks for protecting sensitive data in TypeScript apps — use one, or all three together.
+
+### 🔐 Searchable encryption
+
+Encrypt individual fields and still run real queries against them — exact match, full-text search,
+range/sorting, and encrypted JSONB — all on ciphertext, in PostgreSQL.
+
+```typescript
+const users = encryptedTable("users", {
+ email: encryptedColumn("email").equality().freeTextSearch().orderAndRange(),
+ metadata: encryptedColumn("metadata").searchableJson(), // encrypted JSONB queries
+});
```
-> [!IMPORTANT]
-> **You need to opt out of bundling when using `@cipherstash/stack`.**
-> It uses Node.js specific features and requires the native Node.js `require`.
-> Read more about bundling in the [documentation](https://cipherstash.com/docs/stack/deploy/bundling).
+→ [Searchable encryption][searchable-encryption] · [Schema][schema] · [Encrypt & decrypt][encrypt-decrypt] · [Bulk & model operations][model-ops]
+
+### 🔗 ORM & database integrations
+
+Drop encryption into the stack you already use. Type-safe operators let you query encrypted columns
+exactly like normal ones.
+
+| Integration | Status | Guide |
+|---|---|---|
+| PostgreSQL (raw SQL) | ✅ | [Docs][encryption] |
+| Supabase | ✅ | [Docs][supabase] |
+| Drizzle ORM | ✅ | [Docs][drizzle] |
+| Prisma (Prisma Next) | ✅ | [Docs][prisma-next] |
+| DynamoDB | ✅ | [Docs][dynamodb] |
+
+```typescript
+// Drizzle: query encrypted columns with auto-encrypting operators
+const results = await db.select().from(usersTable)
+ .where(await ops.eq(usersTable.email, "alice@example.com"));
+```
-## Features
+### 👤 Identity-aware encryption
-- **[Searchable encryption](https://cipherstash.com/docs/stack/cipherstash/encryption/searchable-encryption)**: query encrypted data with equality, free text search, range, and [JSONB queries](https://cipherstash.com/docs/stack/cipherstash/encryption/searchable-encryption#jsonb-queries-with-searchablejson).
-- **[Type-safe schema](https://cipherstash.com/docs/stack/cipherstash/encryption/schema)**: define encrypted tables and columns with `encryptedTable` / `encryptedColumn`
-- **[Model & bulk operations](https://cipherstash.com/docs/stack/cipherstash/encryption/encrypt-decrypt#model-operations)**: encrypt and decrypt entire objects or batches with `encryptModel` / `bulkEncryptModels`.
-- **[Identity-aware encryption](https://cipherstash.com/docs/stack/cipherstash/encryption/identity)**: bind encryption to user identity with lock contexts for policy-based access control.
+Bind a record's encryption key to the end user's identity, so only *that* user can decrypt their data.
+`OidcFederationStrategy` federates your identity provider's OIDC JWT into the client — every ZeroKMS
+request then authenticates *as that user* — and `.withLockContext({ identityClaim })` binds the data key to
+a claim. Works with any OIDC provider: Clerk, Supabase Auth, Auth0, Okta, and more.
-## Integrations
+```typescript
+import { Encryption, OidcFederationStrategy } from "@cipherstash/stack";
+
+// Authenticate every request as the signed-in user via their OIDC JWT
+const client = await Encryption({
+ schemas: [users],
+ config: { strategy: OidcFederationStrategy.create(workspaceCrn, () => getUserJwt()) },
+});
-- [Encryption + Drizzle](https://cipherstash.com/docs/stack/cipherstash/encryption/drizzle)
-- [Encryption + Supabase](https://cipherstash.com/docs/stack/cipherstash/encryption/supabase)
-- [Encryption + DynamoDB](https://cipherstash.com/docs/stack/cipherstash/encryption/dynamodb)
+// Bind the data key to a claim — the same claim is required to decrypt
+await client
+ .encrypt("alice@example.com", { table: users, column: users.email })
+ .withLockContext({ identityClaim: ["sub"] });
+```
-## Use cases
+→ [Identity-aware encryption][identity]
-- **Trusted data access**: ensure only your end-users can access their sensitive data using identity-bound encryption
-- **Reduce breach impact**: limit the blast radius of exploited vulnerabilities to only the data the affected user can decrypt
+> The Stack also ships a `stash` CLI for auth, schema, and database setup. See the [SDK reference][reference].
-## Documentation
+## How it works
-- [Documentation](https://cipherstash.com/docs)
-- [Quickstart](https://cipherstash.com/docs/stack/quickstart)
-- [SDK and API reference](https://cipherstash.com/docs/stack/reference)
+Encryption happens in your application. Ciphertext is stored as an [EQL][eql] JSON payload in your database;
+plaintext and root keys never reach CipherStash. Per-value keys are issued in bulk by ZeroKMS (so millions
+of unique keys stay fast), and every decryption is logged for compliance.
-## Contributing
+→ [Security architecture][security-architecture] · [ZeroKMS][zerokms]
-Contributions are welcome and highly appreciated. However, before you jump right into it, we would like you to review our [Contribution Guidelines](CONTRIBUTE.md) to make sure you have a smooth experience contributing.
+## Why CipherStash
-## Security
+- **Trusted data access** — only your end-users can access their sensitive data, enforced cryptographically.
+- **Shrink the blast radius** — a breached vulnerability exposes only what one user can decrypt, not your whole table.
+- **Meet compliance faster** — exceed the encryption requirements of SOC 2 and ISO 27001, with an audit trail of every decryption.
-For our full security policy, supported versions, and contributor guidelines, see [SECURITY.md](./SECURITY.md).
+## Install
-## License
+```bash
+npm install @cipherstash/stack # or: yarn / pnpm / bun add @cipherstash/stack
+```
-This project is [MIT licensed](./LICENSE.md).
+> [!IMPORTANT]
+> **Opt out of bundling `@cipherstash/stack`.** It uses native Node.js features (a Rust FFI module) and the
+> native `require`. [Bundling guide →][bundling]
+
+**Requirements:** Node.js ≥ 18.
+
+## Migrating from Protect.js
+
+> [!NOTE]
+> **`@cipherstash/protect` (Protect.js) is now legacy and in maintenance mode.** It still receives critical
+> security fixes, but all new development has moved to `@cipherstash/stack`. New projects should use the
+> Stack; existing Protect.js users can migrate with the mapping below.
+
+| `@cipherstash/protect` | `@cipherstash/stack` |
+|---|---|
+| `protect(config)` | `Encryption(config)` |
+| `csTable` / `csColumn` | `encryptedTable` / `encryptedColumn` |
+| `@cipherstash/protect/identify` | `@cipherstash/stack/identity` |
+
+Method signatures and the `Result` (`data` / `failure`) pattern are unchanged. [Full migration guide →][reference]
+
+## Documentation & community
+
+- 📚 [Documentation][docs] · [Quickstart][quickstart] · [SDK reference][reference]
+- 🧩 [Example apps][examples]
+- 💬 [Discord community][discord]
+
+## Contributing · Security · License
+
+Contributions are welcome — see [CONTRIBUTE.md][contribute]. For our security policy and responsible
+disclosure, see [SECURITY.md][security-policy]. [MIT licensed][license].
+
+
+[signup]: https://cipherstash.com/signup?utm_source=github&utm_medium=stack_readme
+[docs]: https://cipherstash.com/docs/stack?utm_source=github&utm_medium=stack_readme
+[quickstart]: https://cipherstash.com/docs/stack/quickstart?utm_source=github&utm_medium=stack_readme
+[reference]: https://cipherstash.com/docs/stack/reference?utm_source=github&utm_medium=stack_readme
+[encryption]: https://cipherstash.com/docs/stack/cipherstash/encryption?utm_source=github&utm_medium=stack_readme
+[searchable-encryption]: https://cipherstash.com/docs/stack/cipherstash/encryption/searchable-encryption?utm_source=github&utm_medium=stack_readme
+[schema]: https://cipherstash.com/docs/stack/cipherstash/encryption/schema?utm_source=github&utm_medium=stack_readme
+[encrypt-decrypt]: https://cipherstash.com/docs/stack/cipherstash/encryption/encrypt-decrypt?utm_source=github&utm_medium=stack_readme
+[model-ops]: https://cipherstash.com/docs/stack/cipherstash/encryption/encrypt-decrypt?utm_source=github&utm_medium=stack_readme#model-operations
+[supabase]: https://cipherstash.com/docs/stack/cipherstash/encryption/supabase?utm_source=github&utm_medium=stack_readme
+[drizzle]: https://cipherstash.com/docs/stack/cipherstash/encryption/drizzle?utm_source=github&utm_medium=stack_readme
+[prisma-next]: https://cipherstash.com/docs/stack/cipherstash/encryption/prisma-next?utm_source=github&utm_medium=stack_readme
+[dynamodb]: https://cipherstash.com/docs/stack/cipherstash/encryption/dynamodb?utm_source=github&utm_medium=stack_readme
+[identity]: https://cipherstash.com/docs/stack/cipherstash/encryption/identity?utm_source=github&utm_medium=stack_readme
+[security-architecture]: https://cipherstash.com/docs/stack/reference/security-architecture?utm_source=github&utm_medium=stack_readme
+[zerokms]: https://cipherstash.com/docs/stack/cipherstash/kms?utm_source=github&utm_medium=stack_readme
+[bundling]: https://cipherstash.com/docs/stack/deploy/bundling?utm_source=github&utm_medium=stack_readme
+[eql]: https://github.com/cipherstash/encrypt-query-language
+[aws-kms]: https://docs.aws.amazon.com/kms/latest/developerguide/overview.html
+[discord]: https://discord.gg/5qwXUFb6PB
+[examples]: ./examples
+[contribute]: ./CONTRIBUTE.md
+[security-policy]: ./SECURITY.md
+[license]: ./LICENSE.md
diff --git a/docs/plans/readme-visual-assets.md b/docs/plans/readme-visual-assets.md
new file mode 100644
index 00000000..d453dbdd
--- /dev/null
+++ b/docs/plans/readme-visual-assets.md
@@ -0,0 +1,136 @@
+# README visual assets — spec
+
+Two visual assets for the refreshed root `README.md`. Both target the gaps competitor
+READMEs leave open:
+
+1. **Architecture diagram** — security/infra READMEs (Infisical, Vault) bury their architecture
+ off-README. A clear "how it works" diagram is the single biggest trust signal we can add.
+2. **Type-safety autocomplete GIF** — none of the TS-first leaders (Prisma, Drizzle, React Email)
+ *show* their type-safety story; they only describe it. An autocomplete GIF beats all of them.
+
+## Shared conventions
+
+- **Host in-repo** under `docs/images/` so assets are version-controlled.
+- **Reference with absolute URLs** (`https://raw.githubusercontent.com/cipherstash/stack/main/docs/images/...`).
+ Relative paths render on GitHub but **break on the npm package page** — npm needs absolute URLs.
+- **Light + dark variants** using GitHub's mode switch:
+ ```html
+
+
+ ```
+- **Brand**: use the CipherStash palette and logo; match the dark-theme look of cipherstash.com.
+- **Accessibility**: every asset needs descriptive `alt` text (provided below). For the GIF, keep motion
+ calm and the loop short (respect users who dislike motion).
+
+---
+
+## Asset 1 — Architecture diagram ("How it works")
+
+**Goal.** In one glance, prove the core trust claim: *plaintext and root keys never reach CipherStash; the
+database only ever holds ciphertext; every decryption is audited.*
+
+**Placement.** Under the `## How it works` heading, above the "Security architecture" doc link.
+
+**Format.** SVG preferred (crisp, tiny, theme-able). Target ~1400px wide, responsive height.
+
+**Layout (left → right data flow):**
+
+```
+┌─────────────────────────┐ ciphertext ┌──────────────────────────┐
+│ YOUR APP (TypeScript) │ ── EQL JSON payload ──▶ │ YOUR DATABASE │
+│ @cipherstash/stack │ │ PostgreSQL / JSONB │
+│ • encrypt / decrypt │ ◀── encrypted rows ─── │ • stores ciphertext only│
+│ • search on ciphertext │ │ • searchable (EQL) │
+└───────────┬─────────────┘ └──────────────────────────┘
+ │ per-value key requests (bulk)
+ ▼
+┌─────────────────────────┐ root key ┌──────────────────────────┐
+│ ZeroKMS │ ───────────▶ │ YOUR AWS KMS │
+│ • unique key per value │ │ • root key never leaves │
+│ • bulk key ops (fast) │ └──────────────────────────┘
+│ • decryption audit log │
+└─────────────────────────┘
+```
+
+**Trust-boundary callouts to overlay (the persuasive part):**
+- A dashed "trust boundary" line around *Your App + Your Database + Your AWS KMS* labelled
+ **"Plaintext and root keys never leave your boundary."**
+- A badge on ZeroKMS: **"CipherStash never sees plaintext."**
+- A small tag near the audit log: **"Every decryption logged → SOC 2 / ISO 27001 evidence."**
+
+**Alt text:**
+> "CipherStash architecture: encryption and decryption happen in your TypeScript app; only ciphertext
+> (EQL JSON) is stored in your PostgreSQL database. ZeroKMS issues a unique key per value, rooted in your
+> own AWS KMS. Plaintext and root keys never reach CipherStash, and every decryption is logged for audit."
+
+**Tooling.** Figma, Excalidraw, or draw.io → export SVG (light + dark). Keep text as real text (not
+outlines) where possible for crispness and accessibility.
+
+**Interim option (ship today, no designer needed).** GitHub renders Mermaid natively, so this can go in
+immediately and be swapped for the designed SVG later:
+
+```mermaid
+flowchart LR
+ App["Your App (TypeScript)
@cipherstash/stack
encrypt · decrypt · search"]
+ DB[("Your Database
PostgreSQL / JSONB
ciphertext only")]
+ ZKMS["ZeroKMS
unique key per value
bulk ops · audit log"]
+ KMS["Your AWS KMS
root key never leaves"]
+
+ App -- "ciphertext (EQL JSON)" --> DB
+ App -- "per-value key requests" --> ZKMS
+ ZKMS -- "root key" --> KMS
+
+ subgraph Boundary["Your trust boundary — plaintext & root keys never leave"]
+ App
+ DB
+ KMS
+ end
+```
+
+---
+
+## Asset 2 — Type-safety / autocomplete GIF
+
+**Goal.** Show the DX payoff in motion: an encrypted field stays **fully typed and queryable** — encryption
+adds security without taking away autocomplete, inference, or compile-time safety.
+
+**Placement.** Inside the `### 🔐 Searchable encryption` pillar, or a short "Developer experience" callout.
+
+**Storyboard (single seamless loop, ≤ 10s):**
+1. Show a schema: `encryptedTable("users", { email: encryptedColumn("email").equality().freeTextSearch() })`.
+2. Type `await client.encryptModel(user, users)` and hover the result — tooltip shows the **schema-aware
+ return type**: `email → Encrypted`, `id → string`, `createdAt → Date` (only schema fields change type).
+3. Start typing a query: `.where(await ops.eq(usersTable.email, "` — show autocomplete offering the typed
+ operator and the column.
+4. Briefly trigger a **red squiggle** by accessing a field not in the schema (or wrong type) — proving
+ errors are caught at compile time.
+
+**Recording specs:**
+- VS Code, clean theme (record a **dark** primary; a light alt is nice-to-have).
+- Font size 16–18px; minimap off; hide activity/status bar clutter; zoom so code is legible on mobile.
+- Crop tight to the editor region. Width 1280–1440px.
+- Length 8–12s, seamless loop. **File budget < 5 MB** (ideally < 3 MB) so the README stays fast.
+
+**Formats:**
+- Ship a **`.gif`** for universal rendering (works on npm and GitHub).
+- Optionally also provide an `.mp4`/`.webm` and embed via `