diff --git a/.gitignore b/.gitignore index dce949ca6..4bcb50c47 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +## macOS +.DS_Store + node_modules **/__screenshots__/ **/build/ @@ -18,3 +21,7 @@ test-results/ playwright-report/ playwright/.cache/ +## CLI tools output +cli/**/vibes-output/ +cli/**/.claude/settings.local.json + diff --git a/README.md b/README.md index 9b1db7473..30d2f069c 100644 --- a/README.md +++ b/README.md @@ -27,3 +27,17 @@ Every app you create is automatically saved, so you can: - Return to previous work anytime - Keep track of your building history - Save screenshots of your apps + +## For Developers + +### Claude Code Plugin + +Generate complete Fireproof and Vibes.diy codebases directly from Claude Code using the `/vibes` command. + +The plugin creates production-ready Vite projects with: +- React 19 + Fireproof for local-first data +- callAI for LLM integration +- Tailwind CSS styling +- Complete development setup + +**Install**: See [cli/vibes/README.md](cli/vibes/README.md) for installation and usage instructions. diff --git a/cli/.claude-plugin/marketplace.json b/cli/.claude-plugin/marketplace.json new file mode 100644 index 000000000..2b53a9ff9 --- /dev/null +++ b/cli/.claude-plugin/marketplace.json @@ -0,0 +1,25 @@ +{ + "name": "vibes-marketplace", + "owner": { + "name": "Vibes.diy Team", + "email": "hello@vibes.diy" + }, + "metadata": { + "description": "Claude Code plugins for Fireproof and Vibes.diy development", + "version": "1.0.0" + }, + "plugins": [ + { + "name": "vibes", + "source": "./vibes", + "description": "Generate Fireproof and Vibes.diy codebases with AI-powered component generation", + "version": "1.0.0", + "author": { + "name": "Vibes.diy Team", + "email": "hello@vibes.diy" + }, + "keywords": ["fireproof", "vibes", "react", "code-generation", "ai"], + "category": "development" + } + ] +} diff --git a/cli/.gitignore b/cli/.gitignore new file mode 100644 index 000000000..2c1c769e9 --- /dev/null +++ b/cli/.gitignore @@ -0,0 +1,29 @@ +# macOS +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes + +# User-specific Claude settings +**/.claude/settings.local.json + +# Node.js +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* +package-lock.json +yarn.lock + +# Build artifacts +dist/ +build/ +*.log + +# Editor files +.vscode/ +.idea/ +*.swp +*.swo +*~ diff --git a/cli/FILES_CREATED.txt b/cli/FILES_CREATED.txt new file mode 100644 index 000000000..cd7c77d2b --- /dev/null +++ b/cli/FILES_CREATED.txt @@ -0,0 +1,130 @@ +VIBES PLUGIN - FILES CREATED +============================= +Date: 2025-10-24 + +MARKETPLACE +----------- +cli/.claude-plugin/marketplace.json + → Defines "vibes-marketplace" with vibes plugin + +PLUGIN MANIFEST +--------------- +cli/vibes/.claude-plugin/plugin.json + → Plugin metadata (name, version, description) + +COMMAND +------- +cli/vibes/commands/vibes.md + → /vibes slash command + → Prompts user for app description and output path + → Invokes vibes-generator Skill + +SKILL (CORE LOGIC) +------------------ +cli/vibes/skills/vibes-generator/SKILL.md + → 300+ lines of generation instructions + → Reads vibes.diy codebase patterns + → Generates system prompt + React component + → Creates complete Vite project + +TEMPLATES +--------- +cli/vibes/skills/vibes-generator/templates/package.json.template + → Dependencies: React 19, use-vibes 0.14.6, Vite 7.1.9 + +cli/vibes/skills/vibes-generator/templates/vite.config.js.template + → Vite config with React plugin + +cli/vibes/skills/vibes-generator/templates/index.html.template + → HTML entry point with {{APP_TITLE}} placeholder + +cli/vibes/skills/vibes-generator/templates/main.jsx.template + → React root mounting logic + +cli/vibes/skills/vibes-generator/templates/gitignore.template + → Standard Node.js .gitignore + +DOCUMENTATION +------------- +cli/vibes/README.md + → User-facing documentation + → Installation, usage, examples + → Troubleshooting guide + +cli/vibes/DEVELOPMENT.md + → Complete development documentation + → Architecture, design decisions + → Integration with vibes.diy codebase + → Testing checklist, known issues + +cli/QUICK_START.md + → Quick reference for getting started + → Installation commands + → File structure overview + +cli/FILES_CREATED.txt + → This file - inventory of created files + +INSTALLATION +------------ +/plugin marketplace add ./cli +/plugin install vibes@vibes-marketplace +[Restart Claude Code] + +USAGE +----- +/vibes +[Enter app description] +[Enter output directory] +cd +npm install && npm run dev + +KEY CONCEPTS +------------ +1. Plugin uses Agent Skills system for progressive disclosure +2. Skill reads vibes.diy codebase to understand patterns +3. Generates Vite projects (not single-file HTML) +4. Uses use-vibes package (includes useFireproof + callAI) +5. Applies "brutalist web" design aesthetic by default + +MOST IMPORTANT FILE +------------------- +cli/vibes/skills/vibes-generator/SKILL.md + → This contains ALL the generation logic + → Claude reads this when /vibes is invoked + → Implements the complete workflow + +WHAT GETS GENERATED +------------------- +When you run /vibes, you get: + +output-dir/ +├── package.json (React 19, use-vibes, Vite) +├── vite.config.js +├── index.html +├── .gitignore +└── src/ + ├── main.jsx (React root) + └── App.jsx (Your generated component) + +The App.jsx uses: +- useFireproof hook from use-vibes +- callAI for LLM features +- Tailwind CSS classes +- Brutalist web styling +- Real-time Fireproof queries + +NEXT STEPS +---------- +1. Test the plugin installation +2. Generate a sample app +3. Verify it builds and runs +4. Refine based on results +5. Add more examples + +DOCUMENTATION LOCATIONS +----------------------- +Quick start: cli/QUICK_START.md +Full dev docs: cli/vibes/DEVELOPMENT.md +User guide: cli/vibes/README.md +This inventory: cli/FILES_CREATED.txt diff --git a/cli/QUICK_START.md b/cli/QUICK_START.md new file mode 100644 index 000000000..d99e8738f --- /dev/null +++ b/cli/QUICK_START.md @@ -0,0 +1,120 @@ +# Vibes Plugin - Quick Start + +**Created**: 2025-10-24 +**Status**: Ready for testing + +## What We Built + +A Claude Code plugin that generates complete Fireproof/Vibes.diy Vite projects via the `/vibes` command. + +## File Structure + +``` +cli/ +├── .claude-plugin/marketplace.json ← Marketplace definition +├── vibes/ ← The plugin +│ ├── .claude-plugin/plugin.json ← Plugin metadata +│ ├── commands/vibes.md ← /vibes command +│ ├── skills/vibes-generator/ ← Core logic +│ │ ├── SKILL.md ← Main Skill (reads vibes.diy patterns) +│ │ └── templates/ ← Vite project templates +│ ├── README.md ← User docs +│ └── DEVELOPMENT.md ← Full development docs +└── QUICK_START.md ← This file +``` + +## Installation + +```bash +# In Claude Code: +/plugin marketplace add ./cli +/plugin install vibes@vibes-marketplace +# Restart Claude Code +``` + +## Usage + +```bash +/vibes +# Enter app description when prompted +# Specify output directory (default: ./vibes-app) +# cd to output directory +# npm install && npm run dev +``` + +## How It Works + +1. **Command** (`commands/vibes.md`) prompts user for input +2. **Skill** (`skills/vibes-generator/SKILL.md`): + - Reads `prompts/pkg/prompts.ts` for system prompt patterns + - Reads `prompts/pkg/llms/*.ts` for library docs + - Reads `prompts/pkg/style-prompts.ts` for styling + - Generates augmented prompt + React component + - Processes templates and writes Vite project +3. **Output**: Complete runnable Vite project + +## Generated Project + +``` +vibes-app/ +├── package.json # React 19, use-vibes, Vite +├── vite.config.js +├── index.html +├── .gitignore +└── src/ + ├── main.jsx # React root + └── App.jsx # Generated component +``` + +## Key Files to Check + +- `cli/vibes/skills/vibes-generator/SKILL.md` - Core generation logic (300+ lines) +- `cli/vibes/DEVELOPMENT.md` - Full documentation +- `cli/vibes/README.md` - User-facing docs + +## Testing Checklist + +- [ ] Plugin installs successfully +- [ ] `/vibes` command appears after restart +- [ ] Can generate a test app +- [ ] Generated files are created +- [ ] `npm install` works +- [ ] `npm run dev` starts server +- [ ] App runs in browser + +## Differences from vibes:vibes + +There's an existing `/vibes:vibes` plugin that creates single-file HTML apps. + +**Existing vibes:vibes**: +- Single HTML file +- CDN imports +- Browser-based Babel +- No build step +- Output: `vibes-output//index.html` + +**Our new /vibes**: +- Full Vite project +- npm packages +- Proper build pipeline +- Modern dev workflow +- Output: user-specified directory + +Both are valid for different use cases. + +## Next Steps + +1. Test installation and generation +2. Verify generated apps work +3. Fix any issues +4. Add more examples +5. Publish to public marketplace + +## Quick Reference + +**Install marketplace**: `/plugin marketplace add ./cli` +**Install plugin**: `/plugin install vibes@vibes-marketplace` +**Generate app**: `/vibes` +**Run app**: `cd && npm install && npm run dev` + +**Full docs**: See `cli/vibes/DEVELOPMENT.md` diff --git a/cli/vibes/.claude-plugin/plugin.json b/cli/vibes/.claude-plugin/plugin.json new file mode 100644 index 000000000..01ee63c61 --- /dev/null +++ b/cli/vibes/.claude-plugin/plugin.json @@ -0,0 +1,20 @@ +{ + "name": "vibes", + "description": "Generate Fireproof and Vibes.diy codebases with AI-powered component generation. Creates complete Vite projects with React components that use Fireproof for data persistence and callAI for LLM integration.", + "version": "1.0.0", + "author": { + "name": "Vibes.diy Team", + "email": "hello@vibes.diy" + }, + "homepage": "https://vibes.diy", + "repository": "https://github.com/fireproof-storage/vibes.diy", + "license": "MIT", + "keywords": [ + "fireproof", + "vibes", + "react", + "code-generation", + "ai", + "vite" + ] +} diff --git a/cli/vibes/DEVELOPMENT.md b/cli/vibes/DEVELOPMENT.md new file mode 100644 index 000000000..31ad68d1d --- /dev/null +++ b/cli/vibes/DEVELOPMENT.md @@ -0,0 +1,569 @@ +# Vibes Plugin Development Summary + +**Date**: 2025-10-24 +**Status**: Initial implementation complete, ready for testing + +## What We Built + +A complete Claude Code plugin that provides a `/vibes` command for generating Fireproof/Vibes.diy codebases. The plugin uses the Agent Skills system to read system prompt patterns from the vibes.diy codebase and generate production-ready React applications. + +## Plugin Architecture + +### Directory Structure + +``` +cli/ +├── .claude-plugin/ +│ └── marketplace.json # Marketplace manifest +└── vibes/ + ├── .claude-plugin/ + │ └── plugin.json # Plugin manifest + ├── commands/ + │ ├── vibes.md # /vibes slash command (generate apps) + │ └── vibes-update.md # /vibes-update slash command (update prompts) + ├── scripts/ + │ └── build-plugin-data.js # Builds plugin-data.json from monorepo + ├── skills/ + │ └── vibes-generator/ + │ ├── SKILL.md # Core generation logic (most important file) + │ └── templates/ # Vite project templates + │ ├── package.json.template + │ ├── vite.config.js.template + │ ├── tailwind.config.js.template + │ ├── postcss.config.js.template + │ ├── index.html.template + │ ├── src/ + │ │ ├── index.css.template + │ │ └── main.jsx.template + │ └── gitignore.template + ├── package.json # Build scripts and metadata + ├── plugin-data.json # Cached prompt data (generated, 127KB) + ├── README.md # User documentation + └── DEVELOPMENT.md # This file +``` + +### Component Roles + +#### 1. Marketplace (`cli/.claude-plugin/marketplace.json`) +- Defines the "vibes-marketplace" with local plugin source +- Points to `./vibes` as the plugin directory +- Enables local development and testing + +#### 2. Plugin Manifest (`cli/vibes/.claude-plugin/plugin.json`) +- Name: `vibes` +- Describes the plugin functionality +- Metadata for marketplace listing + +#### 3. Command (`cli/vibes/commands/vibes.md`) +- User-facing `/vibes` command +- Prompts for app description and output directory +- Invokes the `vibes-generator` Skill +- Provides post-generation instructions + +#### 4. Skill (`cli/vibes/skills/vibes-generator/SKILL.md`) +**This is the core of the plugin** - 300+ lines of instructions for Claude + +**Key responsibilities:** +1. Load cached prompt data from `plugin-data.json` +2. Parse JSON to extract: + - Core guidelines (react, fireproof, callAI, ui, imports, tailwind) + - Style prompts (default: "brutalist web") + - Library documentation (fireproof, callai, d3, three-js, web-audio, image-gen) +3. Generate augmented system prompt combining: + - User's app description + - Core guidelines from JSON + - Selected style prompt + - Relevant library documentation +4. Create React component code in `App.jsx` +5. Process templates and write complete Vite project +6. Output installation instructions + +**Important patterns it implements:** +- Uses `useFireproof` hook from use-vibes package +- Integrates callAI for LLM features with streaming + schemas +- Applies "brutalist web" design aesthetic by default +- Mobile-first responsive layout with Tailwind +- Real-time data updates with Fireproof live queries + +#### 5. Templates (`cli/vibes/skills/vibes-generator/templates/`) +Pre-configured files with `{{PLACEHOLDER}}` syntax: +- `package.json.template` - Dependencies (React 19, use-vibes, Vite) +- `vite.config.js.template` - Vite + React plugin config +- `index.html.template` - HTML entry point with {{APP_TITLE}} +- `main.jsx.template` - React root mounting logic +- `gitignore.template` - Standard Node.js ignores + +## How It Works + +### Generation Flow + +``` +User runs: /vibes + ↓ +Command prompts for: + - App description + - Output directory (default: ./vibes-app) + ↓ +Skill activates and: + 1. Loads plugin-data.json (cached prompts, styles, library docs) + 2. Parses JSON to extract core guidelines and style prompts + 3. Selects default style ("brutalist web") or user-requested style + 4. Generates comprehensive system prompt + 5. Uses that prompt to create App.jsx component code + 6. Processes templates (replaces {{APP_NAME}}, {{APP_TITLE}}) + 7. Writes complete project structure: + - package.json + - vite.config.js + - index.html + - .gitignore + - src/main.jsx + - src/App.jsx + 8. Outputs: cd && npm install && npm run dev + ↓ +User gets complete, runnable Vite project +``` + +### Key Design Decisions + +1. **Skill-based approach**: Uses Agent Skills for progressive disclosure + - Only loads instructions when triggered + - Can read from vibes.diy codebase files + - No runtime dependencies needed + +2. **Template-based**: Pre-configured Vite project structure + - Consistent project setup + - Easy to maintain and update + - Placeholder substitution for customization + +3. **Single-file output**: Generated app is one `App.jsx` file + - Follows vibes.diy pattern + - Easy to understand and modify + - All app logic in one place + +4. **use-vibes package**: Uses official package + - Includes useFireproof hook + - callAI imported via use-vibes + - Ensures compatibility + +## Differences from Existing vibes:vibes Plugin + +There's another plugin providing `/vibes:vibes` command that: +- Creates **single-file HTML** applications +- Uses CDN imports (React, Fireproof via ESM) +- Uses `@babel/standalone` for JSX transformation +- Outputs to `vibes-output/-/index.html` +- Immediately runnable in browser (no build step) + +**Our new plugin:** +- Creates **Vite projects** with build pipeline +- Uses npm packages (modern development workflow) +- Outputs to user-specified directory (e.g., `./vibes-app`) +- Requires `npm install` and `npm run dev` +- Proper development experience with HMR + +Both are valid approaches for different use cases: +- **Single-file HTML**: Quick prototypes, no build tools needed +- **Vite project** (ours): Production apps, proper dev workflow + +## Installation & Testing + +### Install the plugin + +```bash +# In Claude Code: +/plugin marketplace add ./cli +/plugin install vibes@vibes-marketplace +# Restart when prompted +``` + +### Test the plugin + +```bash +/vibes +``` + +Then: +1. Enter app description (e.g., "todo list with AI suggestions") +2. Enter output path (or use default `./vibes-app`) +3. Verify files created +4. Test the app: + ```bash + cd vibes-app # or your chosen directory + npm install + npm run dev + ``` + +### Verify generated code + +Check that `src/App.jsx`: +- Imports from `use-vibes` (not `use-fireproof`) +- Uses `useFireproof` hook correctly +- Implements user's requested features +- Follows brutalist web styling +- Has proper error handling + +## Integration with Vibes.diy Codebase + +The Skill reads these files to understand patterns: + +### System Prompt Generation (`prompts/pkg/prompts.ts`) +- `makeBaseSystemPrompt()` function (line ~297) +- Shows how to combine: + - User prompt + - Library selections + - Style guidelines + - Documentation snippets + +### Library Catalog (`prompts/pkg/llms/`) +- `index.ts` - Exports all library configs +- `fireproof.ts` - Fireproof API documentation +- `callai.ts` - callAI integration patterns +- `image-gen.ts`, `d3.ts`, `three-js.ts`, `web-audio.ts` - Optional libraries + +Each config has: +```typescript +{ + name: "fireproof", + label: "Fireproof", + description: "...", + importModule: "use-vibes", + importName: "useFireproof", + importType: "named", + // ... plus documentation text +} +``` + +### Style Prompts (`prompts/pkg/style-prompts.ts`) +- Array of style options +- Default: "brutalist web" (DEFAULT_STYLE_NAME) +- Detailed CSS/design guidance + +The Skill doesn't import these as TypeScript, but reads them to understand the patterns and implement similar logic. + +## Dependencies in Generated Projects + +```json +{ + "dependencies": { + "react": "^19.1.0", + "react-dom": "^19.1.0", + "use-fireproof": "^0.23.15", + "call-ai": "^0.15.13" + }, + "devDependencies": { + "@vitejs/plugin-react": "^5.0.4", + "vite": "^7.1.9", + "tailwindcss": "^3.4.18", + "postcss": "^8.5.6", + "autoprefixer": "^10.4.21" + } +} +``` + +Note: We use `use-fireproof` and `call-ai` packages directly instead of `use-vibes` because the latter has unpublished dependencies (`@vibes.diy/use-vibes-types`) that cause npm install failures. + +## Example Generated App Structure + +```javascript +import React, { useState } from "react" +import { useFireproof } from "use-fireproof" +import { callAI } from "call-ai" + +export default function App() { + const { database, useLiveQuery } = useFireproof("my-app-db") + + // Real-time query + const result = useLiveQuery(doc => doc.type === "item", []) + const items = result.docs || [] + + // AI-powered function + const handleAI = async (prompt) => { + const response = await callAI(prompt, { + stream: true, + schema: { + properties: { + title: { type: "string" }, + description: { type: "string" } + } + } + }) + + await database.put({ + type: "item", + ...JSON.parse(response), + createdAt: Date.now() + }) + } + + return ( +
+ {/* Brutalist web styled UI */} +
+ ) +} +``` + +## Known Issues & Todos + +### Testing Needed +- [ ] Test plugin installation flow +- [ ] Verify Skill can read vibes.diy files correctly +- [ ] Test generated apps actually work +- [ ] Verify npm install succeeds +- [ ] Check HMR works in development + +### Potential Issues +1. **Path resolution**: Skill needs to read from vibes.diy codebase root +2. **Template placeholders**: Ensure all `{{PLACEHOLDERS}}` are replaced +3. **Generated code syntax**: JSX must be valid +4. **Database naming**: Should be derived from app name + +### Future Enhancements +- [ ] Support for additional libraries (D3, Three.js, Web Audio) +- [ ] Custom style selection (not just brutalist web) +- [ ] TypeScript option +- [ ] Multi-page apps +- [ ] Component library integration +- [ ] Deployment scripts (Netlify, Vercel) + +## Plugin Data Build Process + +### Overview + +The plugin uses a standalone architecture with cached prompt data in `plugin-data.json`. This eliminates the need for users to clone the entire vibes.diy monorepo. + +### Build Script: `scripts/build-plugin-data.js` + +**Purpose**: Extracts data from the vibes.diy monorepo and compiles it into a single JSON file. + +**Source Files**: +- `prompts/pkg/style-prompts.ts` - Style themes (brutalist web, memphis, etc.) +- `prompts/pkg/llms/*.txt|md` - Library documentation (fireproof, callai, d3, three-js, web-audio, image-gen) + +**Output**: `plugin-data.json` (127KB) + +**Structure**: +```json +{ + "version": "1.0.0", + "generatedAt": "2025-10-24T...", + "repository": "https://github.com/fireproof-storage/vibes.diy", + "coreGuidelines": { + "react": "...", + "fireproof": "...", + "callAI": "...", + "ui": "...", + "imports": "...", + "tailwind": "..." + }, + "stylePrompts": [ + { "name": "brutalist web", "prompt": "..." }, + { "name": "memphis", "prompt": "..." }, + ... + ], + "defaultStyle": "brutalist web", + "libraries": { + "fireproof": "...", + "callai": "...", + "d3": "...", + "three-js": "...", + "web-audio": "...", + "image-gen": "..." + } +} +``` + +### Running the Build + +From the `cli/vibes` directory: + +```bash +npm run build-plugin-data +``` + +Output: +``` +Building plugin data from vibes.diy repository... + +✓ Extracted 11 style prompts (default: brutalist web) +✓ Loaded fireproof.txt +✓ Loaded callai.txt +✓ Loaded d3.md +✓ Loaded three-js.md +✓ Loaded web-audio.txt +✓ Loaded image-gen.txt + +✓ Plugin data compiled successfully! + Output: /path/to/cli/vibes/plugin-data.json + Size: 127.09 KB + Styles: 11 + Libraries: 6 +``` + +### When to Rebuild + +Rebuild `plugin-data.json` when: +- Style prompts change in `prompts/pkg/style-prompts.ts` +- Library documentation updates in `prompts/pkg/llms/*.txt|md` +- Core guidelines need to be updated in the build script + +### Updating Plugin Data + +**For maintainers**: + +After rebuilding plugin-data.json: +1. Increment the version number in the build script or manually in the JSON +2. Commit the new `plugin-data.json` to the repository +3. Push to GitHub main branch +4. Users can then update via `/vibes-update` command + +**For users**: + +Users have two options for updating their cached prompt data: + +**Option 1: `/vibes-update` command (Recommended)** +```shell +/vibes-update +``` + +This command will: +- Check current vs. latest version +- Show what's changed +- Ask for confirmation +- Create backup before updating +- Download and apply the update + +**Option 2: Manual curl (Alternative)** +```bash +curl -o ~/.claude/plugins/vibes@vibes-marketplace/plugin-data.json \ + https://raw.githubusercontent.com/fireproof-storage/vibes.diy/main/cli/vibes/plugin-data.json +``` + +### Distribution + +The `plugin-data.json` file is: +1. **Committed** to the repository +2. **Distributed** with the plugin +3. **Loaded** by SKILL.md at runtime via `cat ${CLAUDE_PLUGIN_ROOT}/../plugin-data.json` +4. **Updated** when the plugin is updated, or manually refreshed by users + +This architecture enables: +- **Offline-first** plugin operation +- **No monorepo dependency** for end users +- **Easy updates** via GitHub raw URLs +- **Version control** of prompt data + +## Lessons Learned from Testing + +### Fantasy OS Simulator Test (2025-10-24) + +Successfully generated a complete fantasy operating system simulator to test the plugin. Key findings: + +#### Issues Discovered + +1. **Broken use-vibes Dependency** + - `use-vibes@0.14.6` depends on `@vibes.diy/use-vibes-types` which isn't published to npm + - This caused npm install to fail with 404 errors + - **Solution**: Use `use-fireproof` and `call-ai` packages directly + +2. **Missing Tailwind CSS Setup** + - Generated projects didn't include Tailwind configuration files + - Had to manually install and configure Tailwind after generation + - **Solution**: Added tailwind.config.js, postcss.config.js, and src/index.css templates + +3. **Tailwind v4 Incompatibility** + - Initial attempt to use Tailwind v4 failed due to PostCSS plugin changes + - **Solution**: Use Tailwind v3.x (stable and compatible) + +#### Fixes Implemented + +1. **Updated package.json.template** + - Changed from `use-vibes@^0.14.6` to: + - `use-fireproof@^0.23.15` + - `call-ai@^0.15.13` + - Added Tailwind CSS dependencies: + - `tailwindcss@^3.4.18` + - `postcss@^8.5.6` + - `autoprefixer@^10.4.21` + +2. **Added Tailwind Configuration Templates** + - `tailwind.config.js.template` - Tailwind CSS config + - `postcss.config.js.template` - PostCSS config + - `src/index.css.template` - Tailwind directives + - Updated `main.jsx.template` to import index.css + +3. **Updated All Documentation** + - SKILL.md: Corrected import statements and added Tailwind setup steps + - README.md: Updated project structure and dependencies + - DEVELOPMENT.md: Documented the issues and solutions + +#### Test Results + +- ✅ Generated Fantasy OS app with draggable windows, file manager, notepad, terminal +- ✅ npm install completed without errors +- ✅ npm run dev started successfully +- ✅ Tailwind CSS compiled correctly +- ✅ All Fireproof features (live queries, persistence) working +- ✅ Brutalist web styling applied correctly + +The plugin now generates fully working Vite projects with no manual configuration required. + +## Troubleshooting + +### Plugin not found +- Check marketplace was added: `/plugin marketplace list` +- Verify plugin installed: `/plugin` (browse installed) +- Try reinstalling after restart + +### Skill can't read files +- Ensure running from vibes.diy repo root +- Check file paths in SKILL.md are correct +- Verify `prompts/pkg/` exists + +### Generated code errors +- Check template syntax in `templates/` +- Verify placeholder replacement works +- Ensure imports use `use-fireproof` and `call-ai` (not `use-vibes`) + +### npm install fails +- Check package.json syntax +- Verify use-fireproof and call-ai versions are correct +- Ensure Tailwind CSS v3 is specified (not v4) +- Try clearing npm cache + +## Next Steps + +1. **Test the plugin end-to-end** + - Install in fresh Claude Code session + - Generate a test app + - Verify it runs + +2. **Refine based on testing** + - Fix any path issues + - Improve generated code quality + - Better error handling + +3. **Documentation** + - Add examples to README + - Create video walkthrough + - Write blog post + +4. **Distribution** + - Push to GitHub + - Create public marketplace + - Announce to community + +## Resources + +- **Plugin docs**: Claude Code plugin documentation (provided by user) +- **Vibes.diy codebase**: `/Users/marcusestes/Websites/vibes.diy/` +- **Key files**: + - `prompts/pkg/prompts.ts` - System prompt logic + - `prompts/pkg/llms/*.ts` - Library catalog + - `use-vibes/examples/react-example/` - Reference implementation + +## Contact + +For questions or issues: +- **GitHub**: https://github.com/fireproof-storage/vibes.diy +- **Email**: hello@vibes.diy diff --git a/cli/vibes/README.md b/cli/vibes/README.md new file mode 100644 index 000000000..69444e277 --- /dev/null +++ b/cli/vibes/README.md @@ -0,0 +1,444 @@ +# Vibes Claude Code Plugin + +Generate complete Fireproof and Vibes.diy codebases with AI-powered component generation. + +## What It Does + +This plugin provides a `/vibes` command that: + +1. Takes your app description as input +2. Generates an augmented system prompt based on vibes.diy patterns +3. Creates a complete React component with: + - Fireproof for local-first data persistence + - callAI for LLM integration + - Tailwind CSS for styling + - Neo-brutalist design aesthetic +4. Sets up a full Vite project with all dependencies configured +5. Provides ready-to-run code with hot module reloading + +## Installation + +### From This Repository (Local Development) + +1. Add the marketplace: +```bash +claude +``` + +```shell +/plugin marketplace add ./cli +``` + +2. Install the plugin: +```shell +/plugin install vibes@vibes-marketplace +``` + +3. Restart Claude Code when prompted + +### From GitHub (Coming Soon) + +```shell +/plugin marketplace add fireproof-storage/vibes.diy +/plugin install vibes@vibes.diy +``` + +## Usage + +### Generating Apps + +1. Run the command: +```shell +/vibes +``` + +2. Describe your app when prompted: +``` +I want to build a todo list app with AI-powered task suggestions +``` + +3. Specify output directory (or use default `./vibes-app`): +``` +./my-awesome-app +``` + +4. The plugin generates the complete project structure + +5. Follow the next steps: +```bash +cd my-awesome-app +npm install +npm run dev +``` + +### Updating Plugin Data + +The plugin uses cached prompt data that includes coding guidelines, style prompts, and library documentation. To update to the latest version: + +```shell +/vibes-update +``` + +This command will: +1. Check your current plugin data version +2. Fetch the latest version from GitHub +3. Show you what's changed +4. Ask for confirmation before updating +5. Create a backup and apply the update + +**When to update:** +- Monthly updates are typically available +- Check for updates when you want the latest styles or library docs +- Update before generating important projects to get newest best practices + +**What gets updated:** +- Style prompts (brutalist web, memphis, etc.) +- Library documentation (Fireproof, callAI, D3, Three.js, etc.) +- Core coding guidelines +- UI patterns and best practices + +**What stays the same:** +- The plugin code itself +- Your generated apps +- Project templates + +### What Gets Generated + +Your project will have this structure: + +``` +my-awesome-app/ +├── package.json # Dependencies configured +├── vite.config.js # Vite configuration +├── tailwind.config.js # Tailwind CSS configuration +├── postcss.config.js # PostCSS configuration +├── index.html # HTML entry point +├── .gitignore # Git ignore rules +└── src/ + ├── index.css # Tailwind directives + ├── main.jsx # React root mounting + └── App.jsx # Your generated component +``` + +### Generated App Features + +Every generated app includes: + +- **Local-first data**: Fireproof database with real-time updates +- **AI integration**: callAI for LLM-powered features +- **Modern styling**: Tailwind CSS with neo-brutalist aesthetic +- **React 19**: Latest React with hooks +- **Hot reload**: Vite dev server for instant updates +- **Type safety**: Through use-fireproof and call-ai packages +- **Mobile-first**: Responsive design patterns + +## How It Works + +### System Prompt Augmentation + +The plugin uses a cached data file (`plugin-data.json`) containing all the patterns and guidelines: + +1. Loads `plugin-data.json` with prompt guidelines, library docs, and style prompts +2. Selects the appropriate style (default: "brutalist web") +3. Combines core guidelines with your app description +4. Adds relevant library documentation if your app uses specific features (D3, Three.js, etc.) +5. Generates a comprehensive system prompt +6. Uses that prompt to create the React component + +The `plugin-data.json` file is compiled from the vibes.diy monorepo and cached locally for offline use. It's automatically updated when the plugin is updated, or can be manually refreshed from GitHub. + +### Component Generation Pattern + +The generated components follow this pattern: + +```javascript +import React, { useState } from "react" +import { useFireproof } from "use-fireproof" +import { callAI } from "call-ai" + +export default function App() { + const { database, useLiveQuery } = useFireproof("app-db") + + // Live queries for real-time data + const result = useLiveQuery(query => query.type === "item", []) + const items = result.docs || [] + + // AI-powered features with callAI + const handleAIAction = async (prompt) => { + const response = await callAI(prompt, { + stream: true, + schema: { /* your schema */ } + }) + // Save to Fireproof + await database.put({ type: "item", data: JSON.parse(response) }) + } + + return ( +
+ {/* Your UI */} +
+ ) +} +``` + +### Style Aesthetic + +By default, apps use the "brutalist web" style: + +- **Blocky geometry** with oversized controls +- **Thick borders** (4-12px outlines) +- **Bold offsets** (hard shadow plates offset 6-12px) +- **Grid/blueprint cues** on backgrounds +- **High contrast** on light backgrounds +- **Mobile-first** responsive layout +- **Accessible** tap targets (≥48x48px) + +Colors: `#f1f5f9` `#cbd5e1` `#94a3b8` `#64748b` `#0f172a` `#242424` `#ffffff` + +## Examples + +### Todo List with AI Suggestions + +```shell +/vibes +``` + +> "Create a todo list where users can add tasks and get AI-powered suggestions for breaking down complex tasks into smaller steps" + +### Image Gallery with Fireproof + +```shell +/vibes +``` + +> "Build an image gallery app where users can upload photos, add captions, and organize them into collections. Use Fireproof's file API for image storage." + +### Chat Interface + +```shell +/vibes +``` + +> "Make a chat interface that uses callAI for responses and stores the conversation history in Fireproof for offline access" + +## Configuration + +The plugin uses these defaults: + +- **Output directory**: `./vibes-app` +- **Style**: Brutalist web +- **Default libraries**: fireproof, callai +- **React version**: 19.1.0 +- **Vite version**: 7.1.9 + +These can be customized by modifying: +- `skills/vibes-generator/SKILL.md` - Generation logic +- `skills/vibes-generator/templates/` - Project templates + +## Troubleshooting + +### Plugin not found after installation + +Make sure you restarted Claude Code after installation: +```shell +exit +claude +``` + +### Generated app won't start + +1. Make sure you ran `npm install` in the output directory +2. Check that Node.js version is ≥18 +3. Try removing `node_modules` and reinstalling: +```bash +rm -rf node_modules package-lock.json +npm install +``` + +### Import errors in generated code + +The generated code uses these packages: +- `react` - React library +- `use-vibes` - Includes useFireproof hook +- `call-ai` - AI integration (imported via use-vibes) + +These are automatically included in package.json. + +### Styling looks broken + +Make sure Tailwind classes are working: +1. Check that browser dev tools show classes applied +2. Vite should process Tailwind automatically +3. Custom colors use bracket notation: `bg-[#242424]` + +## Development + +### Project Structure + +``` +cli/vibes/ +├── .claude-plugin/ +│ └── plugin.json # Plugin metadata +├── commands/ +│ ├── vibes.md # /vibes command (generate apps) +│ └── vibes-update.md # /vibes-update command (update prompts) +├── scripts/ +│ └── build-plugin-data.js # Build script for plugin data +├── skills/ +│ └── vibes-generator/ +│ ├── SKILL.md # Main generation logic +│ └── templates/ # Project templates +│ ├── package.json.template +│ ├── vite.config.js.template +│ ├── tailwind.config.js.template +│ ├── postcss.config.js.template +│ ├── index.html.template +│ ├── src/ +│ │ ├── index.css.template +│ │ └── main.jsx.template +│ └── gitignore.template +├── package.json # Build scripts and metadata +├── plugin-data.json # Cached prompt data (generated) +├── README.md # This file +└── DEVELOPMENT.md # Development guide +``` + +### Modifying Generation Logic + +To customize how apps are generated, edit: + +**skills/vibes-generator/SKILL.md** +- Change system prompt patterns +- Modify component structure +- Add new libraries +- Adjust style guidelines + +**skills/vibes-generator/templates/** +- Update project configuration +- Change default dependencies +- Modify base HTML structure + +### Testing Changes + +After modifying the plugin: + +1. Uninstall current version: +```shell +/plugin uninstall vibes@vibes-marketplace +``` + +2. Reinstall: +```shell +/plugin install vibes@vibes-marketplace +``` + +3. Restart Claude Code + +4. Test with `/vibes` + +## Architecture + +### Components + +1. **Command** (`commands/vibes.md`) + - User interaction + - Input gathering + - Skill invocation + +2. **Skill** (`skills/vibes-generator/SKILL.md`) + - System prompt generation + - Component code creation + - Project structure setup + - File writing + +3. **Templates** (`skills/vibes-generator/templates/`) + - Base project files + - Placeholder substitution + - Configuration defaults + +### Data Flow + +``` +User prompt + ↓ +/vibes command + ↓ +vibes-generator Skill + ↓ +Load plugin-data.json (cached prompts/styles/libs) + ↓ +Generate augmented system prompt + ↓ +Create React component code + ↓ +Process templates + ↓ +Write project files + ↓ +Output next steps +``` + +### Plugin Data Architecture + +The plugin uses a standalone architecture that doesn't require the vibes.diy monorepo: + +**Build Process** (for maintainers): +``` +vibes.diy/prompts/pkg/*.ts + ↓ +npm run build-plugin-data + ↓ +vibes.diy/cli/vibes/plugin-data.json + ↓ +Committed to repository + ↓ +Distributed with plugin +``` + +**Runtime Process** (for users): +``` +Plugin loads plugin-data.json + ↓ +Offline-first (no network needed) + ↓ +Optional: Manual refresh from GitHub raw URL +``` + +**Updating Plugin Data**: + +Use the `/vibes-update` command for easy updates: +```shell +/vibes-update +``` + +Alternatively, users can manually download the latest version: +```bash +curl -o ~/.claude/plugins/vibes@vibes-marketplace/plugin-data.json \ + https://raw.githubusercontent.com/fireproof-storage/vibes.diy/main/cli/vibes/plugin-data.json +``` + +## Contributing + +To contribute to this plugin: + +1. Fork the vibes.diy repository +2. Make changes in `cli/vibes/` +3. Test locally with the local marketplace +4. Submit a pull request + +## License + +MIT License - see vibes.diy repository for details + +## Links + +- **Vibes.diy**: https://vibes.diy +- **Fireproof**: https://use-fireproof.com +- **GitHub**: https://github.com/fireproof-storage/vibes.diy +- **Documentation**: https://vibes.diy/docs + +## Support + +- **Issues**: https://github.com/fireproof-storage/vibes.diy/issues +- **Discord**: https://discord.gg/fireproof (coming soon) +- **Email**: hello@vibes.diy diff --git a/cli/vibes/commands/vibes-update.md b/cli/vibes/commands/vibes-update.md new file mode 100644 index 000000000..fa46b3917 --- /dev/null +++ b/cli/vibes/commands/vibes-update.md @@ -0,0 +1,63 @@ +--- +description: Update the Vibes plugin's cached prompt data to the latest version +--- + +# Update Vibes Plugin Data + +You will help the user update their locally cached plugin data (`plugin-data.json`) to the latest version from the GitHub repository. + +## Process + +1. **Check Current Version**: Read the current `plugin-data.json` file to determine the installed version. + +2. **Fetch Latest Version**: Retrieve the latest `plugin-data.json` from GitHub: + ``` + https://raw.githubusercontent.com/fireproof-storage/vibes.diy/main/cli/vibes/plugin-data.json + ``` + +3. **Compare Versions**: Check if an update is available by comparing version numbers. + +4. **Show Changes**: If a newer version is available, inform the user about: + - Current version vs. new version + - When the new version was generated (check `generatedAt` field) + - Brief summary of what might have changed (styles, library docs, core guidelines) + +5. **Confirm Update**: Ask the user if they want to proceed with the update. + +6. **Apply Update**: If confirmed: + - Backup the current `plugin-data.json` (rename to `plugin-data.json.backup`) + - Download and save the new version + - Verify the new file is valid JSON + - Confirm success + +7. **Rollback Option**: If something goes wrong, restore from backup and inform the user. + +## Important Notes + +- The plugin data file is located at: `${CLAUDE_PLUGIN_ROOT}/../plugin-data.json` +- Always create a backup before updating +- Version numbers follow semantic versioning (e.g., 1.0.0, 1.1.0, 2.0.0) +- The update process does NOT modify the plugin code itself, only the cached prompt data +- Users should test generated apps after major version updates + +## Example Output + +``` +Current version: 1.0.0 (generated 2025-10-24) +Latest version: 1.1.0 (generated 2025-10-25) + +New in 1.1.0: +- Style prompts may have been updated +- Library documentation may have been refreshed +- Core coding guidelines may have been improved + +Would you like to update? (yes/no) + +[If yes] +✓ Backup created: plugin-data.json.backup +✓ Latest version downloaded +✓ Update successful! + +Your plugin data is now at version 1.1.0. +The next /vibes command will use the updated prompts. +``` diff --git a/cli/vibes/commands/vibes.md b/cli/vibes/commands/vibes.md new file mode 100644 index 000000000..978909bea --- /dev/null +++ b/cli/vibes/commands/vibes.md @@ -0,0 +1,40 @@ +--- +description: Generate a Fireproof/Vibes.diy codebase with AI-powered component generation +--- + +# Generate Vibes Codebase + +You will help the user create a complete Fireproof and Vibes.diy application. + +## Process + +1. **Get User Input**: Ask the user to describe the application they want to build. Be encouraging and explain that you'll create a complete, working React application with Fireproof data persistence and AI integration. + +2. **Get Output Directory**: Ask where they want the project created. Default to `./vibes-app` if not specified. Make sure to get the absolute path. + +3. **Invoke the Skill**: Use the `vibes-generator` Skill to: + - Generate an augmented system prompt based on the vibes.diy prompt patterns + - Create the React component code + - Set up the complete Vite project structure + - Write all necessary files + +4. **Next Steps**: After generation, provide clear instructions: + ```bash + cd [output-directory] + npm install + npm run dev + ``` + + Explain that the app will open at `http://localhost:5173` and includes: + - Fireproof for local-first data persistence + - callAI for AI/LLM integration + - Tailwind CSS for styling + - Hot module reloading for development + +## Important Notes + +- The generated app is a single-page application in `src/App.jsx` +- All dependencies are already configured in package.json +- The app uses modern React patterns with hooks +- Data persists locally via Fireproof +- The vibes-generator Skill handles all the system prompt augmentation and code generation logic diff --git a/cli/vibes/package.json b/cli/vibes/package.json new file mode 100644 index 000000000..6117d9336 --- /dev/null +++ b/cli/vibes/package.json @@ -0,0 +1,18 @@ +{ + "name": "@vibes.diy/cli-plugin", + "version": "0.1.0", + "description": "Claude Code plugin for generating Vibes.diy applications", + "private": true, + "scripts": { + "build-plugin-data": "node scripts/build-plugin-data.js" + }, + "keywords": [ + "claude-code", + "plugin", + "vibes", + "fireproof", + "react" + ], + "author": "Vibes.diy Team", + "license": "Apache-2.0" +} diff --git a/cli/vibes/plugin-data.json b/cli/vibes/plugin-data.json new file mode 100644 index 000000000..dbbbec135 --- /dev/null +++ b/cli/vibes/plugin-data.json @@ -0,0 +1,68 @@ +{ + "version": "1.0.0", + "generatedAt": "2025-10-24T23:45:53.906Z", + "repository": "https://github.com/fireproof-storage/vibes.diy", + "coreGuidelines": { + "react": "Use modern React practices and hooks. JavaScript only (no TypeScript). Tailwind CSS for mobile-first, accessible styling. Don't use external libraries unless essential. Keep components concise and focused.", + "fireproof": "Use the useFireproof hook from use-fireproof to create a local-first database. Store all data as Fireproof documents with proper structure. Use useLiveQuery for real-time updates. File uploads are supported via doc._files API.", + "callAI": "For AI integration, use callAI from call-ai package. Set stream: true for streaming responses. Use structured JSON outputs with schemas for consistent data. Save final responses as individual Fireproof documents. Example: callAI(prompt, { schema: { properties: { todos: { type: 'array', items: { type: 'string' } } } } })", + "ui": "Include vivid app description and usage instructions in italic text at the top. If the app uses callAI with schema, include a Demo Data button. List data items on main page (make lists clickable for details). Use placeholder image APIs like https://picsum.photos/400 when images are needed.", + "imports": "Always start components with:\nimport React, { useState, useEffect } from \"react\"\nimport { useFireproof } from \"use-fireproof\"\nimport { callAI } from \"call-ai\"\n\nOnly add other imports when specifically requested.", + "tailwind": "Use Tailwind CSS classes directly in JSX. Remember to use brackets like bg-[#242424] for custom colors. Mobile-first responsive design with 4/8/16/24 spacing scale. Components are either square (0px radius) or very rounded (50% of height)." + }, + "stylePrompts": [ + { + "name": "brutalist web", + "prompt": "Create a UI theme in a neo-brutalist style: blocky geometry, oversized controls, thick 4-12px outlines, and big bold offsets (hard shadow plates offset 6-12px bottom-right; active press reduces offset by 2-4px). Use grid/blueprint cues—graph lines, micro-dots, hatch/stipple textures—on flat matte surfaces; reserve subtle gloss only for CTAs. Background (only skeuomorphic element): grey-blue graph paper via CSS—base #f1f5f9, grid from repeating-linear-gradients in #cbd5e1/#94a3b8 at 16-24px; add a fullscreen grain layer (SVG turbulence or 1px noise PNG) at 3-6% opacity with filter: blur(0.4-0.8px) contrast(102%) brightness(101%); lock to viewport. Corner rule: components are either square (0px radius) or very rounded (50% of component height)—no in-between. Mobile-first layout: single-column flow on phones, 4/8/16/24 spacing scale, tap targets >= 48x48, sticky header + bottom nav; expand to 2-4 columns at sm >= 640 / md >= 768 / lg >= 1024 with asymmetric stacks. Maintain high contrast on light backgrounds. Secret name \"Neobrute Blueprint.\" Use these colors: #f1f5f9 #cbd5e1 #94a3b8 #64748b #0f172a #242424 #ffffff. Never use white text; #ffffff is for surfaces only." + }, + { + "name": "memphis", + "prompt": "Create a UI theme inspired by the Memphis Group and Studio Alchimia from the 1980s. Incorporate bold, playful geometric shapes (squiggles, triangles, circles), vibrant primary colors (red, blue, yellow) with contrasting pastels (pink, mint, lavender), and asymmetrical layouts. Use quirky patterns like polka dots, zigzags, and terrazzo textures. Ensure a retro-futuristic vibe with a mix of matte and glossy finishes, evoking a whimsical yet functional design. Secretly name the theme 'Memphis Alchemy' to reflect its roots in Ettore Sotsass's vision and global 1980s influences. Make sure the app background has some kind of charming patterned background using memphis styled dots or squiggly lines. Use thick \"neo-brutalism\" style borders for style to enhance legibility. Make sure to retain high contrast in your use of colors. Light background are better than dark ones. Use these colors: #70d6ff #ff70a6 #ff9770 #ffd670 #e9ff70 #242424 #ffffff Never use white text." + }, + { + "name": "synthwave", + "prompt": "80s digital aesthetic" + }, + { + "name": "organic UI", + "prompt": "natural, fluid forms" + }, + { + "name": "maximalist", + "prompt": "dense, decorative" + }, + { + "name": "skeuomorphic", + "prompt": "real-world mimics" + }, + { + "name": "flat design", + "prompt": "clean, 2D shapes" + }, + { + "name": "bauhaus", + "prompt": "geometric modernism" + }, + { + "name": "glitchcore", + "prompt": "decentering expectations" + }, + { + "name": "paper cutout", + "prompt": "layered, tactile" + }, + { + "name": "viridian", + "prompt": "Create a vibrant UI theme inspired by Bruce Sterling's Viridian Design Movement, embracing a futuristic green aesthetic with subtle animations and dynamic interactivity. Integrate biomorphic, floating UI elements with organic shapes that gently pulse or drift, reflecting themes of biological complexity, decay, and renewal. Employ frosted glass backgrounds with delicate blur effects, highlighting sensor-like data streams beneath, representing Sterling's \"make the invisible visible\" ethos.\n\nUse gradients and layers of soft greens accented by energetic data-inspired colors (#70d6ff, #ff70a6, #ff9770, #ffd670, #e9ff70), alongside crisp white (#ffffff) and dark contrast (#242424), ensuring legibility and visual appeal. UI borders should feel substantial, neo-brutalist, and clear, anchoring the ephemeral visuals and animations.\n\nThe background should subtly animate, evoking cellular activity, digital pulse, or ecological sensor feedback, reinforcing Viridian's fascination with tangible cyberspace and biomorphic tech aesthetics.\n\nSecretly name this theme \"Viridian Pulse\", capturing Sterling's original playful-yet-serious blend of provocative futurism and stylish eco-consciousness." + } + ], + "defaultStyle": "brutalist web", + "libraries": { + "fireproof": "# Fireproof Database API Guide\n\nFireproof is a lightweight embedded document database with encrypted live sync, designed to make browser apps easy. Use it in any JavaScript environment with a unified API that works both in React (with hooks) and as a standalone core API.\n\n## Key Features\n\n- **Apps run anywhere:** Bundle UI, data, and logic in one file.\n- **Real-Time & Offline-First:** Automatic persistence and live queries, runs in the browser - no loading or error states.\n- **Unified API:** TypeScript works with Deno, Bun, Node.js, and the browser.\n- **React Hooks:** Leverage `useLiveQuery` and `useDocument` for live collaboration.\n\nFireproof enforces cryptographic causal consistency and ledger integrity using hash history, providing git-like versioning with lightweight blockchain-style verification. Data is stored and replicated as content-addressed encrypted blobs, making it safe and easy to sync via commodity object storage providers.\n\n## Installation\n\nThe `use-fireproof` package provides both the core API and React hooks.\n\n```bash\nnpm install use-fireproof\n```\nWorks with ⚡️ ESM.sh:\n\n```js\nimport { useFireproof } from \"https://esm.sh/use-fireproof\";\n```\n\nDeliver generated solutions as runnable micro applications via ChatGPT Canvas, v0, bolt.new, or Claude Artifacts. Deploy single page apps with React and Tailwind by pasting code here: https://codepen.io/useFireproof/pen/MYgNYdx\n\n## ⚛️ React Usage\n\nReact hooks are the recommended way to use Fireproof in LLM code generation contexts. Here's how to use them:\n\n#### Create or Load a Database\n\nFireproof databases store data across sessions and can sync in real-time. Each database is identified by a string name, and you can have multiple databases per application—often one per collaboration session, as they are the unit of sharing.\n\n```js\nimport { useFireproof } from \"use-fireproof\";\n\nconst { database, useLiveQuery, useDocument } = useFireproof(\"my-ledger\");\n```\n\nFireproof databases are Merkle CRDTs, giving them the ledger-like causal consistency of git or a blockchain, but with the ability to merge and sync web data in real-time. Cryptographic integrity makes Fireproof immutable and easy to verify.\n\n#### Put and Get Documents\n\nDocuments are JSON-style objects (CBOR) storing application data. Each has an `_id`, which can be auto-generated or set explicitly. Auto-generation is recommended to ensure uniqueness and avoid conflicts. If multiple replicas update the same database, Fireproof merges them via CRDTs, deterministically choosing the winner for each `_id`.\n\nIt is best to have more granular documents, e.g. one document per user action, so saving a form or clicking a button should typically create or update a single document, or just a few documents. Avoid patterns that require a single document to grow without bound.\n\nFireproof is a local database, no loading states required, just empty data states.\n\n### Basic Example\n\nThis example shows Fireproof's concise defaults. Here we only store user data, but get useful sorting without much code.\n\n```js\nconst App = () => {\n const { useDocument } = useFireproof(\"my-ledger\");\n\n const { doc, merge, submit } = useDocument({ text: \"\" });\n\n // _id is roughly temporal, this is most recent\n const { docs } = useLiveQuery(\"_id\", { descending: true, limit: 100 });\n\n return (\n
\n
\n merge({ text: e.target.value })}\n placeholder=\"New document\"\n />\n \n \n\n

Recent Documents

\n
    \n {docs.map((doc) => (\n
  • \n {doc.text}\n
  • \n ))} \n
\n
\n );\n}\n```\n\n### Editing Documents\n\nAddress documents by a known `_id` if you want to force conflict resolution or work with a real world resource, like a schedule slot or a user profile. In a complex app this might come from a route parameter or correspond to an outside identifier.\n\n```js\nconst { useDocument } = useFireproof(\"my-ledger\");\n\nconst { doc, merge, submit, save, reset } = useDocument({ _id: \"user-profile:abc@example.com\" });\n```\n\nThe `useDocument` hook provides several methods:\n- `merge(updates)`: Update the document with new fields\n- `submit(e)`: Handles form submission by preventing default, saving, and resetting\n- `save()`: Save the current document state\n- `reset()`: Reset to initial state\n\nFor form-based creation flows, use `submit`:\n```js\n
\n```\n\nNote: In some sandboxed environments (e.g., embedded canvases), the form submit event may be blocked. Use a button click handler instead:\n```js\n\n```\n\nWhen you call submit, the document is reset, so if you didn't provide an `_id` then you can use the form to create a stream of new documents as in the basic example above.\n\n### Query Data with React\n\nData is queried by sorted indexes defined by the application. Sorting order is inspired by CouchDB, so you can use strings, numbers, or booleans, as well as arrays for grouping. Use numbers when possible for sorting continuous data.\n\nYou can use the `_id` field for temporal sorting so you dont have to write code to get simple recent document lists, as in the basic example above.\n\nHere are other common patterns:\n\n#### Query by Key Range\n\nPassing a string to `useLiveQuery` will index by that field. You can use the key argument to filter by a specific value:\n```js\nconst { docs } = useLiveQuery(\"agentName\", { \n key: \"agent-1\" // all docs where doc.agentName === \"agent-1\", sorted by _id\n});\n```\n\nYou can also query a range within a key:\n```js\nconst { docs } = useLiveQuery(\"agentRating\", { \n range: [3, 5]\n});\n```\n\n### Custom Indexes\n\nFor more complex query, you can write a custom index function. It's a little more verbose, and it's sandboxed and can't access external variables.\n\n#### Normalize legacy types\n\nYou can use a custom index function to normalize and transform document data, for instance if you have both new and old document versions in your app.\n\n```js\nconst { docs } = useLiveQuery(\n (doc) => {\n if (doc.type == 'listing_v1') {\n return doc.sellerId;\n } else if (doc.type == 'listing') {\n return doc.userId;\n }\n }, \n { key : routeParams.sellerId });\n```\n\n#### Array Indexes and Prefix Queries\n\nWhen you want to group rows easily, you can use an array index key. This is great for grouping records my year / month / day or other paths. In this example the prefix query is a shorthand for a key range, loading everything from November 2024:\n\n```js\nconst queryResult = useLiveQuery(\n (doc) => [doc.date.getFullYear(), doc.date.getMonth(), doc.date.getDate()],\n { prefix: [2024, 11] }\n);\n```\n\n#### Sortable Lists\n\nSortable lists are a common pattern. Here's how to implement them using Fireproof:\n\n```js\nfunction App() {\n const { database, useLiveQuery } = useFireproof(\"my-ledger\");\n \n // Initialize list with evenly spaced positions\n async function initializeList() {\n await database.put({ list: \"xyz\", position: 1000 });\n await database.put({ list: \"xyz\", position: 2000 });\n await database.put({ list: \"xyz\", position: 3000 });\n }\n \n // Query items on list xyz, sorted by position. Note that useLiveQuery('list', { key:'xyz' }) would be the same docs, sorted chronologically by _id\n const queryResult = useLiveQuery(\n (doc) => [doc.list, doc.position], \n { prefix: [\"xyz\"] }\n );\n\n // Insert between existing items using midpoint calculation\n async function insertBetween(beforeDoc, afterDoc) {\n const newPosition = (beforeDoc.position + afterDoc.position) / 2;\n await database.put({ \n list: \"xyz\", \n position: newPosition \n });\n }\n\n return (\n
\n

List xyz (Sorted)

\n
    \n {queryResult.docs.map(doc => (\n
  • \n {doc._id}: position {doc.position}\n
  • \n ))}\n
\n \n \n
\n );\n}\n```\n\n## Architecture: Where's My Data?\n\nFireproof is local-first, so it's always fast and your data is stored in the browser, so you can build apps without a cloud. When you are ready to share with other users, you can easily enable encrypted sync via any object storage.\n\n## Using Fireproof in JavaScript\n\nYou can use the core API in HTML or on the backend. Instead of hooks, import the core API directly:\n\n```js\nimport { fireproof } from \"use-fireproof\";\n\nconst database = fireproof(\"my-ledger\");\n```\n\nThe document API is async, but doesn't require loading states or error handling.\n\n```js\nconst ok = await database.put({ text: \"Sample Data\" });\nconst doc = await database.get(ok.id);\nconst latest = await database.query(\"_id\", { limit: 10, descending: true });\nconsole.log(\"Latest documents:\", latest.docs);\n```\n\nTo subscribe to real-time updates, use the `subscribe` method. This is useful for building backend event handlers or other server-side logic. For instance to send an email when the user completes a todo:\n\n```js\nimport { fireproof } from \"use-firproof\";\n\nconst database = fireproof(\"todo-list-db\");\n\ndatabase.subscribe((changes) => {\n console.log(\"Recent changes:\", changes);\n changes.forEach((change) => {\n if (change.completed) {\n sendEmail(change.email, \"Todo completed\", \"You have completed a todo.\");\n }\n });\n}, true);\n```\n\n### Working with Files\n\nFireproof has built-in support for file attachments. Files are encrypted by default and synced on-demand. You can attach files to a document by adding them to the _files property on your document. For example:\n\n```html\n\n```\n\n```js\nfunction handleFiles() {\n const fileList = this.files;\n const doc = {\n type: \"files\",\n _files: {}\n };\n for (const file of fileList) {\n // Assign each File object to the document\n doc._files[file.name] = file; \n }\n database.put(doc);\n}\n\ndocument.getElementById(\"files\").addEventListener(\"change\", handleFiles, false);\n```\n\nWhen loading a document with attachments, you can retrieve each attachment's actual File object by calling its .file() method. This returns a Promise that resolves with the File data, which you can display in your app:\n\n```js\nconst doc = await database.get(\"my-doc-id\");\nfor (const fileName in doc._files) {\n const meta = doc._files[fileName];\n if (meta.file) {\n const fileObj = await meta.file();\n console.log(\"Loaded file:\", fileObj.name);\n }\n}\n```\n\nSee the final example application in this file for a working example.\n\n### Form Validation\n\nYou can use React's `useState` to manage validation states and error messages. Validate inputs at the UI level before allowing submission.\n\n```javascript\nconst [errors, setErrors] = useState({});\n\nfunction validateForm() {\n const newErrors = {};\n if (!doc.name.trim()) newErrors.name = \"Name is required.\";\n if (!doc.email) newErrors.email = \"Email is required.\";\n if (!doc.message.trim()) newErrors.message = \"Message is required.\";\n setErrors(newErrors);\n return Object.keys(newErrors).length === 0;\n}\n\nfunction handleSubmit(e) {\n e.preventDefault();\n if (validateForm()) submit();\n}\n```\n\n## Example React Application\n\nCode listing for todo tracker App.jsx:\n```js\nimport React from \"react\";\nimport ReactDOM from \"react-dom/client\";\nimport { useFireproof } from \"use-fireproof\";\n\nexport default function App() {\n const { useLiveQuery, useDocument, database } = useFireproof(\"todo-list-db\");\n\n const {\n doc: newTodo,\n merge: mergeNewTodo,\n submit: submitNewTodo\n } = useDocument({\n todo: \"\",\n type: \"todo\",\n completed: false,\n createdAt: Date.now()\n });\n\n const { docs: todos } = useLiveQuery(\"type\", { \n key: \"todo\",\n descending: true \n });\n\n const handleInputChange = (e) => {\n mergeNewTodo({ todo: e.target.value });\n };\n\n const handleSubmit = (e) => {\n e.preventDefault();\n submitNewTodo();\n };\n\n return (\n
\n

Todo List

\n \n \n \n \n
    \n {todos.map((doc) => (\n
  • \n
    \n
    \n database.put({ ...doc, completed: !doc.completed })}\n />\n {doc.todo}\n
    \n database.del(doc._id)}\n >\n Delete\n \n
    \n
    \n {new Date(doc.createdAt).toISOString()}\n
    \n
  • \n ))}\n
\n
\n );\n}\n```\n\n### Example Image Uploader\n\nThis React example shows a simple image uploader application that uses Fireproof to store and sort images by creation date. These APIs easily work with plain JavaScript also. \n\nCode listing for App.jsx:\n```js\nimport { useFireproof, ImgFile } from \"use-fireproof\";\nimport { useState, useEffect } from \"react\";\n\nexport default function App() {\n const { useDocument, useLiveQuery } = useFireproof(\"image-uploads\");\n const { doc, merge, submit } = useDocument({ _files: {}, description: \"\" });\n const { docs } = useLiveQuery(\"_id\", { descending: true, limit: 5 });\n const [error, setError] = useState(false);\n\n return (\n
\n

Image Uploader

\n e.target.files[0] && merge({ _files: { uploaded: e.target.files[0] } })} className=\"mb-2 border p-2 w-full rounded\" />\n {setError(false); merge({ description: e.target.value });}}\n className={`w-full p-2 border rounded mb-4 ${error ? \"border-red-500\" : \"border-gray-300\"}`}\n />\n \n

Recent Uploads

\n
\n {docs.map(doc => (\n
\n {doc._files?.uploaded && }\n

{doc.description || \"No description\"}

\n
\n ))}\n
\n
\n );\n}\n```\n\n## See Also\n\nSee the lite docs at https://use-fireproof.com/llms-mini.txt and the basics at https://use-fireproof.com/llms.txt\n\n## Next Steps\n\nIf you are in an existing application context, suggest how forms and state, collaboration data can be managed in Fireproof. If this is a new app, use the examples above and start with a running App.js if you can.\n\nIMPORTANT: Don't use `useState()` on form data, instead use `merge()` and `submit()` from `useDocument`. Only use `useState` for ephemeral UI state (active tabs, open/closed panels, cursor positions). Keep your data model in Fireproof.", + "callai": "# CallAI Helper Function\n\nThe `callAI` helper function provides an easy way to make AI requests to OpenAI-compatible model providers.\n\n## Installation\n\n```bash\nnpm install call-ai\n```\n\n## API Key\n\nYou can set the API key in the `window` object:\n\n```javascript\nwindow.CALLAI_API_KEY = \"your-api-key\";\n```\n\nOr pass it directly to the `callAI` function:\n\n```javascript\nconst response = await callAI(\"Write a haiku\", { apiKey: \"your-api-key\" });\n```\n\n## Basic Usage\n\nBy default the function returns a Promise that resolves to the complete response:\n\n```javascript\nimport { callAI } from 'call-ai';\n\n// Default behavior - returns a Promise\nconst response = await callAI(\"Write a haiku\");\n\n// Use the complete response directly\nconsole.log(response); // Complete response text\n```\n\n## Streaming Mode\n\nIf you prefer to receive the response incrementally as it's generated, set `stream: true`. This returns an AsyncGenerator which must be awaited:\n\n```javascript\nimport { callAI } from 'call-ai';\n\n// Enable streaming mode explicitly - returns an AsyncGenerator\nconst generator = await callAI(\"Write an epic poem\", { stream: true });\n// Process the streaming response\nfor await (const partialResponse of generator) {\n console.log(partialResponse); // Updates incrementally\n}\n```\n\n## JSON Schema Responses\n\nTo get structured JSON responses, provide a schema in the options:\n\n```javascript\nimport { callAI } from 'call-ai';\n\nconst todoResponse = await callAI(\"Give me a todo list for learning React\", {\n schema: {\n name: \"todo\", // Optional - defaults to \"result\" if not provided\n properties: {\n todos: {\n type: \"array\",\n items: { type: \"string\" }\n }\n }\n }\n});\nconst todoData = JSON.parse(todoResponse);\nconsole.log(todoData.todos); // Array of todo items\n```\n\n## JSON with Streaming\n\nIn this example, we're using the `callAI` helper function to get weather data in a structured format with streaming preview:\n\n```javascript\nimport { callAI } from 'call-ai';\n\n// Get weather data with streaming updates\nconst generator = await callAI(\"What's the weather like in Paris today?\", {\n stream: true,\n schema: {\n properties: {\n location: {\n type: \"string\",\n description: \"City or location name\"\n },\n temperature: {\n type: \"number\",\n description: \"Temperature in Celsius\"\n },\n conditions: {\n type: \"string\",\n description: \"Weather conditions description\"\n }\n }\n }\n});\n\n// Preview streaming updates as they arrive, don't parse until the end\nconst resultElement = document.getElementById('result');\nlet finalResponse;\n\nfor await (const partialResponse of generator) {\n resultElement.textContent = partialResponse;\n finalResponse = partialResponse;\n}\n\n// Parse final result\ntry {\n const weatherData = JSON.parse(finalResponse);\n \n // Access individual fields\n const { location, temperature, conditions } = weatherData;\n \n // Update UI with formatted data\n document.getElementById('location').textContent = location;\n document.getElementById('temperature').textContent = `${temperature}°C`;\n document.getElementById('conditions').textContent = conditions;\n} catch (error) {\n console.error(\"Failed to parse response:\", error);\n}\n```\n\n### Schema Structure Recommendations\n\n1. **Flat schemas perform better across all models**. If you need maximum compatibility, avoid deeply nested structures.\n\n2. **Field names matter**. Some models have preferences for certain property naming patterns:\n - Use simple, common naming patterns like `name`, `type`, `items`, `price` \n - Avoid deeply nested object hierarchies (more than 2 levels deep)\n - Keep array items simple (strings or flat objects)\n\n3. **Model-specific considerations**:\n - **OpenAI models**: Best overall schema adherence and handle complex nesting well\n - **Claude models**: Great for simple schemas, occasional JSON formatting issues with complex structures\n - **Gemini models**: Good general performance, handles array properties well\n - **Llama/Mistral/Deepseek**: Strong with flat schemas, but often ignore nesting structure and provide their own organization\n\n4. **For mission-critical applications** requiring schema adherence, use OpenAI models or implement fallback mechanisms.\n\n### Models for Structured Outputs\n\n- OpenAI models: Best overall schema adherence and handle complex nesting well\n- Claude models: Great for simple schemas, occasional JSON formatting issues with complex structures\n- Gemini models: Good general performance, handles array properties well\n- Llama/Mistral/Deepseek: Strong with flat schemas, but often ignore nesting structure and provide their own organization\n\n\n## Specifying a Model\n\nBy default, the function uses `openrouter/auto` (automatic model selection). You can specify a different model:\n\n```javascript\nimport { callAI } from 'call-ai';\n\n// Use a specific model via options\nconst response = await callAI(\n \"Explain quantum computing in simple terms\", \n { model: \"openai/gpt-4o\" }\n);\n\nconsole.log(response);\n```\n\n## Additional Options\n\nYou can pass extra parameters to customize the request:\n\n```javascript\nimport { callAI } from 'call-ai';\n\nconst response = await callAI(\n \"Write a creative story\",\n {\n model: \"anthropic/claude-3-opus\",\n temperature: 0.8, // Higher for more creativity (0-1)\n max_tokens: 1000, // Limit response length\n top_p: 0.95 // Control randomness\n }\n);\n\nconsole.log(response);\n```\n\n## Message History\n\nFor multi-turn conversations, you can pass an array of messages:\n\n```javascript\nimport { callAI } from 'call-ai';\n\n// Create a conversation\nconst messages = [\n { role: \"system\", content: \"You are a helpful coding assistant.\" },\n { role: \"user\", content: \"How do I use React hooks?\" },\n { role: \"assistant\", content: \"React hooks are functions that let you use state and other React features in functional components...\" },\n { role: \"user\", content: \"Can you show me an example of useState?\" }\n];\n\n// Pass the entire conversation history\nconst response = await callAI(messages);\nconsole.log(response);\n\n// To continue the conversation, add the new response and send again\nmessages.push({ role: \"assistant\", content: response });\nmessages.push({ role: \"user\", content: \"What about useEffect?\" });\n\n// Call again with updated history\nconst nextResponse = await callAI(messages);\nconsole.log(nextResponse);\n```\n\n## Using with OpenAI API\n\nYou can use callAI with OpenAI's API directly by providing the appropriate endpoint and API key:\n\n```javascript\nimport { callAI } from 'call-ai';\n\n// Use with OpenAI's API\nconst response = await callAI(\n \"Explain the theory of relativity\", \n {\n model: \"gpt-4\",\n apiKey: \"sk-...\", // Your OpenAI API key\n endpoint: \"https://api.openai.com/v1/chat/completions\"\n }\n);\n\nconsole.log(response);\n\n// Or with streaming\nconst generator = callAI(\n \"Explain the theory of relativity\", \n {\n model: \"gpt-4\",\n apiKey: \"sk-...\", // Your OpenAI API key\n endpoint: \"https://api.openai.com/v1/chat/completions\",\n stream: true\n }\n);\n\nfor await (const chunk of generator) {\n console.log(chunk);\n}\n```\n\n## Custom Endpoints\n\nYou can specify a custom endpoint for any OpenAI-compatible API:\n\n```javascript\nimport { callAI } from 'call-ai';\n\n// Use with any OpenAI-compatible API\nconst response = await callAI(\n \"Generate ideas for a mobile app\",\n {\n model: \"your-model-name\",\n apiKey: \"your-api-key\",\n endpoint: \"https://your-custom-endpoint.com/v1/chat/completions\"\n }\n);\n\nconsole.log(response);\n```\n\n## Recommended Models\n\n| Model | Best For | Speed vs Quality |\n|-------|----------|------------------|\n| `openrouter/auto` | Default, automatically selects | Adaptive |\n| `openai/gpt-4o-mini` | data generation | Fast, good quality |\n| `anthropic/claude-3-haiku` | Cost-effective | Fast, good quality |\n| `openai/gpt-4o` | Best overall quality | Medium speed, highest quality |\n| `anthropic/claude-3-opus` | Complex reasoning | Slower, highest quality |\n| `mistralai/mistral-large` | Open weights alternative | Good balance |\n\n## Automatic Retry Mechanism\n\nCall-AI has a built-in fallback mechanism that automatically retries with `openrouter/auto` if the requested model is invalid or unavailable. This ensures your application remains functional even when specific models experience issues.\n\nIf you need to disable this behavior (for example, in test environments), you can use the `skipRetry` option:\n\n```javascript\nconst response = await callAI(\"Your prompt\", {\n model: \"your-model-name\",\n skipRetry: true // Disable automatic fallback\n});\n```\n\n## Items with lists\n\n```javascript\nimport { callAI } from 'call-ai';\n\nconst generator = await callAI([\n {\n role: \"user\",\n content: \"Generate 3 JSON records with name, description, tags, and priority (0 is highest, 5 is lowest).\"\n }\n], {\n stream: true,\n schema: {\n properties: {\n records: {\n type: \"array\",\n items: {\n type: \"object\",\n properties: {\n name: { type: \"string\" },\n description: { type: \"string\" },\n tags: {\n type: \"array\",\n items: { type: \"string\" }\n },\n priority: { type: \"integer\" }\n }\n }\n }\n }\n }\n});\n\nfor await (const partialResponse of generator) {\n console.log(partialResponse);\n}\n\nconst recordData = JSON.parse(/* final response */);\nconsole.log(recordData.records); // Array of records\n```\n\n## Items with properties\n\n```javascript\nconst demoData = await callAI(\"Generate 4 items with label, status, priority (low, medium, high, critical), and notes. Return as structured JSON with these fields.\", {\n schema: {\n properties: {\n items: {\n type: \"array\",\n items: {\n type: \"object\",\n properties: {\n label: { type: \"string\" },\n status: { type: \"string\" },\n priority: { type: \"string\" },\n notes: { type: \"string\" }\n }\n }\n }\n }\n }\n});\n```\n\n## Error Handling\n\nErrors are handled through standard JavaScript try/catch blocks:\n\n```javascript\nimport { callAI } from 'call-ai';\n\ntry {\n\n const response = await callAI(\"Generate some content\", {\n apiKey: \"invalid-key\" // Invalid or missing API key\n });\n \n // If no error was thrown, process the normal response\n console.log(response);\n} catch (error) {\n // API errors are standard Error objects with useful properties\n console.error(\"API error:\", error.message);\n console.error(\"Status code:\", error.status);\n console.error(\"Error type:\", error.errorType);\n console.error(\"Error details:\", error.details);\n}\n```\n\nFor streaming mode, error handling works the same way:\n\n```javascript\nimport { callAI } from 'call-ai';\n\ntry {\n const generator = await callAI(\"Generate some content\", {\n apiKey: \"invalid-key\", // Invalid or missing API key\n stream: true\n });\n \n // Any error during streaming will throw an exception\n let finalResponse = '';\n for await (const chunk of generator) {\n finalResponse = chunk;\n console.log(\"Chunk:\", chunk);\n }\n \n // Process the final response\n console.log(\"Final response:\", finalResponse);\n} catch (error) {\n // Handle errors with standard try/catch\n console.error(\"API error:\", error.message);\n console.error(\"Error properties:\", {\n status: error.status,\n type: error.errorType,\n details: error.details\n });\n}\n```\n\nThis approach is idiomatic and consistent with standard JavaScript practices. Errors provide rich information for better debugging and error handling in your applications.\n\n## Image Recognition Example\n\nCall-AI supports image recognition using multimodal models like GPT-4o. You can pass both text and image content to analyze images in the browser:\n\n```javascript\nimport { callAI } from 'call-ai';\n\n// Function to analyze an image using GPT-4o\nasync function analyzeImage(imageFile, prompt = 'Describe this image in detail') {\n // Convert the image file to a data URL\n const dataUrl = await fileToDataUrl(imageFile);\n \n const content = [\n { type: 'text', text: prompt },\n { type: 'image_url', image_url: { url: dataUrl } }\n ];\n \n // Call the model with the multimodal content\n const result = await callAI(\n [{ role: 'user', content }],\n {\n model: 'openai/gpt-4o-2024-08-06', // Or 'openai/gpt-4o-latest'\n apiKey: window.CALLAI_API_KEY,\n }\n );\n \n return result;\n}\n\n// Helper function to convert File to data URL\nasync function fileToDataUrl(file) {\n return new Promise((resolve) => {\n const reader = new FileReader();\n reader.onloadend = () => resolve(reader.result);\n reader.readAsDataURL(file);\n });\n}\n```", + "d3": "# D3.js Reference for Coding Agents\n\n**D3** (Data-Driven Documents) is a JavaScript library for creating bespoke data visualizations using web standards (SVG, Canvas, HTML). This guide provides essential patterns and examples for AI coding agents to create amazing D3 demonstrations.\n\n## Core Philosophy\n\nD3 is a **low-level toolbox** of 30+ modules that work together. It's not a chart library but a collection of primitives for building custom visualizations. Think of it as \"assembly language for data visualization.\"\n\n## Essential Setup\n\n```js\nimport * as d3 from \"d3\";\n// Or import specific modules\nimport { select, scaleLinear, line } from \"d3\";\n```\n\n## The D3 Pattern: Select, Bind, Transform\n\nEvery D3 visualization follows this pattern:\n\n1. **Select** DOM elements\n2. **Bind** data to elements\n3. **Transform** elements based on data\n\n```js\n// Basic pattern\nd3.select(\"body\") // Select\n .selectAll(\"div\") // Select all divs\n .data([4, 8, 15, 16]) // Bind data\n .enter()\n .append(\"div\") // Enter new elements\n .style(\"width\", (d) => d * 10 + \"px\") // Transform\n .text((d) => d);\n```\n\n## Essential Modules & Quick Reference\n\n### 1. d3-selection (DOM Manipulation)\n\n```js\n// Selection basics\nd3.select(\"#chart\"); // Select by ID\nd3.selectAll(\".bar\"); // Select by class\nselection.append(\"rect\"); // Add element\nselection.attr(\"width\", 100); // Set attribute\nselection.style(\"fill\", \"red\"); // Set style\nselection.text(\"Hello\"); // Set text content\n\n// Data joining (the D3 way)\nconst bars = svg\n .selectAll(\".bar\")\n .data(data)\n .enter()\n .append(\"rect\")\n .attr(\"class\", \"bar\");\n```\n\n### 2. d3-scale (Data Encoding)\n\n```js\n// Linear scale (continuous data)\nconst x = d3\n .scaleLinear()\n .domain([0, 100]) // Input range\n .range([0, 500]); // Output range (pixels)\n\n// Ordinal scale (categorical data)\nconst color = d3\n .scaleOrdinal()\n .domain([\"A\", \"B\", \"C\"])\n .range([\"red\", \"blue\", \"green\"]);\n\n// Time scale\nconst x = d3\n .scaleTime()\n .domain([new Date(2020, 0, 1), new Date(2024, 0, 1)])\n .range([0, 800]);\n\n// Band scale (for bar charts)\nconst x = d3\n .scaleBand()\n .domain(data.map((d) => d.name))\n .range([0, width])\n .padding(0.1);\n```\n\n### 3. d3-shape (SVG Path Generators)\n\n```js\n// Line generator\nconst line = d3\n .line()\n .x((d) => x(d.date))\n .y((d) => y(d.value))\n .curve(d3.curveCardinal); // Smooth curves\n\n// Area generator\nconst area = d3\n .area()\n .x((d) => x(d.date))\n .y0(y(0)) // Baseline\n .y1((d) => y(d.value)); // Top line\n\n// Arc generator (for pie charts)\nconst arc = d3.arc().innerRadius(0).outerRadius(radius);\n```\n\n### 4. d3-axis (Chart Axes)\n\n```js\n// Create axes\nconst xAxis = d3.axisBottom(x);\nconst yAxis = d3.axisLeft(y);\n\n// Render axes\nsvg.append(\"g\").attr(\"transform\", `translate(0,${height})`).call(xAxis);\n\nsvg.append(\"g\").call(yAxis);\n```\n\n### 5. d3-transition (Animation)\n\n```js\n// Smooth transitions\nselection\n .transition()\n .duration(1000)\n .attr(\"width\", newWidth)\n .style(\"opacity\", 0.5);\n\n// Staggered transitions\nbars\n .transition()\n .delay((d, i) => i * 100)\n .duration(500)\n .attr(\"height\", (d) => y(d.value));\n```\n\n## Complete Examples for Coding Agents\n\n### 1. Simple Bar Chart\n\n```js\nimport * as d3 from \"d3\";\n\nconst data = [4, 8, 15, 16, 23, 42];\nconst width = 500;\nconst height = 300;\nconst margin = {top: 20, right: 20, bottom: 30, left: 40};\n\nconst x = d3.scaleBand()\n .domain(d3.range(data.length))\n .range([margin.left, width - margin.right])\n .padding(0.1);\n\nconst y = d3.scaleLinear()\n .domain([0, d3.max(data)])\n .range([height - margin.bottom, margin.top]);\n\nconst svg = d3.select(\"#chart\")\n .append(\"svg\")\n .attr(\"width\", width)\n .attr(\"height\", height);\n\nsvg.selectAll(\".bar\")\n .data(data)\n .enter().append(\"rect\")\n .attr(\"class\", \"bar\")\n .attr(\"x\", (d, i) => x(i))\n .attr(\"y\", d => y(d))\n .attr(\"width\", x.bandwidth())\n .attr(\"height\", d => height - margin.bottom - y(d))\n .attr(\"fill\", \"steelblue\");\n\n// Add axes\nsvg.append(\"g\")\n .attr(\"transform\", `translate(0,${height - margin.bottom})`)\n .call(d3.axisBottom(x));\n\nsvg.append(\"g\")\n .attr(\"transform\", `translate(${margin.left},0)`)\n .call(d3.axisLeft(y));\n\n```\n\n### 2. Interactive Scatter Plot\n\n```js\nimport * as d3 from \"d3\";\n\n// Generate sample data\nconst data = d3.range(100).map(() => ({\n x: Math.random() * 100,\n y: Math.random() * 100,\n radius: Math.random() * 10 + 2,\n}));\n\nconst width = 600;\nconst height = 400;\nconst margin = { top: 20, right: 20, bottom: 30, left: 40 };\n\nconst x = d3\n .scaleLinear()\n .domain(d3.extent(data, (d) => d.x))\n .range([margin.left, width - margin.right]);\n\nconst y = d3\n .scaleLinear()\n .domain(d3.extent(data, (d) => d.y))\n .range([height - margin.bottom, margin.top]);\n\nconst svg = d3\n .select(\"#scatter\")\n .append(\"svg\")\n .attr(\"width\", width)\n .attr(\"height\", height);\n\n// Create circles with interactions\nsvg\n .selectAll(\"circle\")\n .data(data)\n .enter()\n .append(\"circle\")\n .attr(\"cx\", (d) => x(d.x))\n .attr(\"cy\", (d) => y(d.y))\n .attr(\"r\", (d) => d.radius)\n .attr(\"fill\", \"steelblue\")\n .attr(\"opacity\", 0.7)\n .on(\"mouseover\", function (event, d) {\n d3.select(this)\n .transition()\n .attr(\"r\", d.radius * 1.5)\n .attr(\"fill\", \"orange\");\n })\n .on(\"mouseout\", function (event, d) {\n d3.select(this).transition().attr(\"r\", d.radius).attr(\"fill\", \"steelblue\");\n });\n\n// Add axes\nsvg\n .append(\"g\")\n .attr(\"transform\", `translate(0,${height - margin.bottom})`)\n .call(d3.axisBottom(x));\n\nsvg\n .append(\"g\")\n .attr(\"transform\", `translate(${margin.left},0)`)\n .call(d3.axisLeft(y));\n```\n\n### 3. Force-Directed Network\n\n```js\nimport * as d3 from \"d3\";\n\nconst nodes = d3.range(30).map((i) => ({ id: i }));\nconst links = d3.range(nodes.length - 1).map((i) => ({\n source: Math.floor(Math.sqrt(i)),\n target: i + 1,\n}));\n\nconst width = 600;\nconst height = 400;\n\nconst svg = d3\n .select(\"#network\")\n .append(\"svg\")\n .attr(\"width\", width)\n .attr(\"height\", height);\n\nconst simulation = d3\n .forceSimulation(nodes)\n .force(\n \"link\",\n d3.forceLink(links).id((d) => d.id),\n )\n .force(\"charge\", d3.forceManyBody().strength(-300))\n .force(\"center\", d3.forceCenter(width / 2, height / 2));\n\nconst link = svg\n .append(\"g\")\n .selectAll(\"line\")\n .data(links)\n .enter()\n .append(\"line\")\n .attr(\"stroke\", \"#999\")\n .attr(\"stroke-width\", 2);\n\nconst node = svg\n .append(\"g\")\n .selectAll(\"circle\")\n .data(nodes)\n .enter()\n .append(\"circle\")\n .attr(\"r\", 8)\n .attr(\"fill\", \"steelblue\")\n .call(\n d3.drag().on(\"start\", dragstarted).on(\"drag\", dragged).on(\"end\", dragended),\n );\n\nsimulation.on(\"tick\", () => {\n link\n .attr(\"x1\", (d) => d.source.x)\n .attr(\"y1\", (d) => d.source.y)\n .attr(\"x2\", (d) => d.target.x)\n .attr(\"y2\", (d) => d.target.y);\n\n node.attr(\"cx\", (d) => d.x).attr(\"cy\", (d) => d.y);\n});\n\nfunction dragstarted(event) {\n if (!event.active) simulation.alphaTarget(0.3).restart();\n event.subject.fx = event.subject.x;\n event.subject.fy = event.subject.y;\n}\n\nfunction dragged(event) {\n event.subject.fx = event.x;\n event.subject.fy = event.y;\n}\n\nfunction dragended(event) {\n if (!event.active) simulation.alphaTarget(0);\n event.subject.fx = null;\n event.subject.fy = null;\n}\n```\n\n### 4. Dynamic Line Chart with Real Data\n\n```js\nimport * as d3 from \"d3\";\n\n// Sample time series data\nconst data = d3.range(50).map((d, i) => ({\n date: new Date(2024, 0, i + 1),\n value: Math.sin(i * 0.1) * 50 + 50 + Math.random() * 20,\n}));\n\nconst width = 700;\nconst height = 400;\nconst margin = { top: 20, right: 30, bottom: 40, left: 40 };\n\nconst x = d3\n .scaleTime()\n .domain(d3.extent(data, (d) => d.date))\n .range([margin.left, width - margin.right]);\n\nconst y = d3\n .scaleLinear()\n .domain([0, d3.max(data, (d) => d.value)])\n .range([height - margin.bottom, margin.top]);\n\nconst line = d3\n .line()\n .x((d) => x(d.date))\n .y((d) => y(d.value))\n .curve(d3.curveCardinal);\n\nconst svg = d3\n .select(\"#line-chart\")\n .append(\"svg\")\n .attr(\"width\", width)\n .attr(\"height\", height);\n\n// Add gradient definition\nconst gradient = svg\n .append(\"defs\")\n .append(\"linearGradient\")\n .attr(\"id\", \"line-gradient\")\n .attr(\"gradientUnits\", \"userSpaceOnUse\")\n .attr(\"x1\", 0)\n .attr(\"y1\", height)\n .attr(\"x2\", 0)\n .attr(\"y2\", 0);\n\ngradient\n .append(\"stop\")\n .attr(\"offset\", \"0%\")\n .attr(\"stop-color\", \"lightblue\")\n .attr(\"stop-opacity\", 0.1);\n\ngradient\n .append(\"stop\")\n .attr(\"offset\", \"100%\")\n .attr(\"stop-color\", \"steelblue\")\n .attr(\"stop-opacity\", 0.8);\n\n// Draw line\nsvg\n .append(\"path\")\n .datum(data)\n .attr(\"fill\", \"none\")\n .attr(\"stroke\", \"url(#line-gradient)\")\n .attr(\"stroke-width\", 3)\n .attr(\"d\", line);\n\n// Add dots with hover effects\nsvg\n .selectAll(\".dot\")\n .data(data)\n .enter()\n .append(\"circle\")\n .attr(\"class\", \"dot\")\n .attr(\"cx\", (d) => x(d.date))\n .attr(\"cy\", (d) => y(d.value))\n .attr(\"r\", 4)\n .attr(\"fill\", \"steelblue\")\n .on(\"mouseover\", function (event, d) {\n d3.select(this).attr(\"r\", 6);\n // Add tooltip\n const tooltip = svg.append(\"g\").attr(\"id\", \"tooltip\");\n tooltip\n .append(\"rect\")\n .attr(\"x\", x(d.date) + 10)\n .attr(\"y\", y(d.value) - 25)\n .attr(\"width\", 60)\n .attr(\"height\", 20)\n .attr(\"fill\", \"black\")\n .attr(\"opacity\", 0.8);\n tooltip\n .append(\"text\")\n .attr(\"x\", x(d.date) + 15)\n .attr(\"y\", y(d.value) - 10)\n .attr(\"fill\", \"white\")\n .style(\"font-size\", \"12px\")\n .text(d.value.toFixed(1));\n })\n .on(\"mouseout\", function () {\n d3.select(this).attr(\"r\", 4);\n svg.select(\"#tooltip\").remove();\n });\n\n// Add axes\nsvg\n .append(\"g\")\n .attr(\"transform\", `translate(0,${height - margin.bottom})`)\n .call(d3.axisBottom(x).tickFormat(d3.timeFormat(\"%b %d\")));\n\nsvg\n .append(\"g\")\n .attr(\"transform\", `translate(${margin.left},0)`)\n .call(d3.axisLeft(y));\n```\n\n## Key Modules Cheat Sheet\n\n### Scales (Data Encoding)\n\n```js\n// Continuous scales\nd3.scaleLinear(); // Numbers to numbers\nd3.scaleTime(); // Dates to numbers\nd3.scalePow(); // Power/sqrt scales\nd3.scaleLog(); // Logarithmic scales\n\n// Discrete scales\nd3.scaleOrdinal(); // Categories to values\nd3.scaleBand(); // Categories to positions (bar charts)\nd3.scalePoint(); // Categories to points\n\n// Color scales\nd3.scaleSequential(d3.interpolateViridis); // Continuous colors\nd3.scaleOrdinal(d3.schemeCategory10); // Categorical colors\n```\n\n### Shapes (SVG Path Generators)\n\n```js\n// Lines and areas\nd3.line().x(x).y(y);\nd3.area().x(x).y0(y0).y1(y1);\n\n// Arcs and pies\nd3.arc().innerRadius(r1).outerRadius(r2);\nd3.pie().value((d) => d.value);\n\n// Symbols (scatter plot markers)\nd3.symbol().type(d3.symbolCircle).size(100);\n```\n\n### Arrays (Data Processing)\n\n```js\n// Statistics\nd3.min(data, (d) => d.value);\nd3.max(data, (d) => d.value);\nd3.extent(data, (d) => d.value); // [min, max]\nd3.mean(data, (d) => d.value);\nd3.median(data, (d) => d.value);\n\n// Grouping and nesting\nd3.group(data, (d) => d.category);\nd3.rollup(\n data,\n (v) => v.length,\n (d) => d.category,\n);\n\n// Sorting\nd3.ascending(a, b);\nd3.descending(a, b);\n```\n\n### Forces (Physics Simulations)\n\n```js\nconst simulation = d3\n .forceSimulation(nodes)\n .force(\"link\", d3.forceLink(links))\n .force(\"charge\", d3.forceManyBody().strength(-300))\n .force(\"center\", d3.forceCenter(width / 2, height / 2))\n .force(\"collision\", d3.forceCollide().radius(10));\n```\n\n## Common Patterns for Amazing Demos\n\n### 1. Responsive SVG Setup\n\n```js\nconst container = d3.select(\"#chart\");\nconst svg = container\n .append(\"svg\")\n .attr(\"viewBox\", `0 0 ${width} ${height}`)\n .style(\"max-width\", \"100%\")\n .style(\"height\", \"auto\");\n```\n\n### 2. Smooth Data Updates\n\n```js\nfunction updateChart(newData) {\n const bars = svg.selectAll(\".bar\").data(newData);\n\n // Update existing\n bars\n .transition()\n .duration(750)\n .attr(\"height\", (d) => y(d.value));\n\n // Add new\n bars\n .enter()\n .append(\"rect\")\n .attr(\"class\", \"bar\")\n .attr(\"height\", 0)\n .transition()\n .duration(750)\n .attr(\"height\", (d) => y(d.value));\n\n // Remove old\n bars.exit().transition().duration(750).attr(\"height\", 0).remove();\n}\n```\n\n### 3. Color Schemes\n\n```js\n// Beautiful built-in color schemes\nd3.schemeCategory10; // 10 categorical colors\nd3.schemeSet3; // 12 pastel colors\nd3.interpolateViridis; // Blue to yellow gradient\nd3.interpolatePlasma; // Purple to yellow gradient\nd3.schemePaired; // Paired categorical colors\n\n// Usage\nconst color = d3.scaleOrdinal(d3.schemeCategory10);\n```\n\n### 4. Tooltips and Interactions\n\n```js\n// Create tooltip div\nconst tooltip = d3.select(\"body\").append(\"div\")\n .style(\"position\", \"absolute\")\n .style(\"padding\", \"10px\")\n .style(\"background\", \"rgba(0,0,0,0.8)\")\n .style(\"color\", \"white\")\n .style(\"border-radius\", \"5px\")\n .style(\"pointer-events\", \"none\")\n .style(\"opacity\", 0);\n\n// Add to elements\n.on(\"mouseover\", function(event, d) {\n tooltip.transition().duration(200).style(\"opacity\", .9);\n tooltip.html(`Value: ${d.value}`)\n .style(\"left\", (event.pageX + 10) + \"px\")\n .style(\"top\", (event.pageY - 28) + \"px\");\n})\n.on(\"mouseout\", function() {\n tooltip.transition().duration(500).style(\"opacity\", 0);\n});\n```\n\n### 5. Data Loading and Processing\n\n```js\n// Load CSV data\nd3.csv(\"data.csv\").then((data) => {\n // Process data\n data.forEach((d) => {\n d.value = +d.value; // Convert to number\n d.date = d3.timeParse(\"%Y-%m-%d\")(d.date); // Parse dates\n });\n\n // Create visualization\n createChart(data);\n});\n\n// Load JSON data\nd3.json(\"data.json\").then(createChart);\n```\n\n## Pro Tips for Coding Agents\n\n1. **Start with the canvas**: Set up SVG dimensions and margins first\n2. **Define scales early**: Map your data domain to visual range before drawing\n3. **Use method chaining**: D3's fluent API allows `selection.attr().style().on()`\n4. **Leverage data joins**: Use enter/update/exit pattern for dynamic data\n5. **Add transitions**: `transition().duration(500)` makes everything better\n6. **Use color schemes**: Built-in color palettes save time and look professional\n7. **Make it responsive**: Use viewBox for scalable graphics\n8. **Add interactions**: hover, click, drag make demos engaging\n9. **Consider performance**: Use Canvas for 1000+ elements, SVG for detailed graphics\n10. **Test with real data**: Random data is fine for prototypes, realistic data for demos\n\n## Quick Chart Types\n\n### Bar Chart: `d3.scaleBand()` + `rect` elements\n\n### Line Chart: `d3.line()` + `path` element\n\n### Scatter Plot: `circle` elements with `cx`, `cy` from scales\n\n### Pie Chart: `d3.pie()` + `d3.arc()` + `path` elements\n\n### Network: `d3.forceSimulation()` + `line` + `circle` elements\n\n### Map: `d3.geoPath()` + GeoJSON data\n\n### Treemap: `d3.treemap()` + hierarchical data\n\n### Heatmap: `rect` grid + `d3.scaleSequential()` colors\n\n## Framework Integration\n\n### React\n\n```jsx\n// Use refs for D3 DOM manipulation\nconst svgRef = useRef();\n\nuseEffect(() => {\n const svg = d3.select(svgRef.current);\n // D3 code here...\n}, [data]);\n\nreturn ;\n```\n\n## Performance & Best Practices\n\n- Use `d3.select()` for single elements, `d3.selectAll()` for multiple\n- Cache selections when reusing: `const bars = svg.selectAll(\".bar\")`\n- Use Canvas for 1000+ data points, SVG for interactive/detailed graphics\n- Debounce resize events for responsive charts\n- Use `d3.csv()`, `d3.json()` for async data loading\n- Leverage D3's built-in interpolators for smooth animations\n\n## Resources for Inspiration\n\n- [Observable D3 Gallery](https://observablehq.com/@d3/gallery)\n- [Bl.ocks.org](https://bl.ocks.org) - Community examples\n- [D3 Graph Gallery](https://d3-graph-gallery.com) - Chart type tutorials\n- [Mike Bostock's Blocks](https://bl.ocks.org/mbostock) - Creator's examples\n\n---\n\n_This reference focuses on practical patterns for creating impressive D3 demonstrations. Each example is self-contained and copy-pasteable for rapid prototyping._", + "three-js": "# Three.js API\n\n_Essential classes, methods, and patterns for Three.js development_\n\n## Core Setup\n\n### Scene Graph Hierarchy\n\n```javascript\nimport * as THREE from \"three\";\n\n// Core trinity\nconst scene = new THREE.Scene();\nconst camera = new THREE.PerspectiveCamera(75, aspect, 0.1, 1000);\nconst renderer = new THREE.WebGLRenderer({ antialias: true });\n\n// Everything is an Object3D\nscene.add(mesh); // Mesh extends Object3D\ngroup.add(light); // Light extends Object3D\nparent.add(child); // Hierarchical transforms\n```\n\n## Essential Classes\n\n### Cameras\n\n```javascript\n// Perspective (most common)\nconst camera = new THREE.PerspectiveCamera(\n 75, // field of view\n aspect, // aspect ratio\n 0.1, // near plane\n 1000, // far plane\n);\n\n// Orthographic (2D/technical)\nconst camera = new THREE.OrthographicCamera(\n left,\n right,\n top,\n bottom,\n near,\n far,\n);\n\n// Camera controls\ncamera.position.set(x, y, z);\ncamera.lookAt(target);\ncamera.updateProjectionMatrix(); // After changing properties\n```\n\n### Geometries\n\n```javascript\n// Primitive geometries\nconst box = new THREE.BoxGeometry(1, 1, 1);\nconst sphere = new THREE.SphereGeometry(1, 32, 32);\nconst plane = new THREE.PlaneGeometry(1, 1);\nconst cylinder = new THREE.CylinderGeometry(1, 1, 2, 32);\n\n// Custom geometry\nconst geometry = new THREE.BufferGeometry();\ngeometry.setAttribute(\"position\", new THREE.BufferAttribute(vertices, 3));\ngeometry.setAttribute(\"normal\", new THREE.BufferAttribute(normals, 3));\ngeometry.setAttribute(\"uv\", new THREE.BufferAttribute(uvs, 2));\ngeometry.setIndex(indices);\n```\n\n### Materials\n\n```javascript\n// Basic materials\nconst basic = new THREE.MeshBasicMaterial({ color: 0xff0000 });\nconst lambert = new THREE.MeshLambertMaterial({ color: 0x00ff00 });\nconst phong = new THREE.MeshPhongMaterial({ color: 0x0000ff });\n\n// PBR materials (most realistic)\nconst standard = new THREE.MeshStandardMaterial({\n color: 0xffffff,\n metalness: 0.5,\n roughness: 0.5,\n map: texture,\n normalMap: normalTexture,\n envMap: environmentTexture,\n});\n\nconst physical = new THREE.MeshPhysicalMaterial({\n ...standard,\n clearcoat: 1.0,\n transmission: 0.5,\n thickness: 1.0,\n});\n```\n\n### Lights\n\n```javascript\n// Ambient (global illumination)\nconst ambient = new THREE.AmbientLight(0xffffff, 0.6);\n\n// Directional (sun-like)\nconst directional = new THREE.DirectionalLight(0xffffff, 1);\ndirectional.position.set(1, 1, 1);\ndirectional.castShadow = true;\n\n// Point (bulb-like)\nconst point = new THREE.PointLight(0xffffff, 1, 100);\npoint.position.set(0, 10, 0);\n\n// Spot (flashlight-like)\nconst spot = new THREE.SpotLight(0xffffff, 1, 100, Math.PI / 4);\n```\n\n### Textures\n\n```javascript\n// Texture loading\nconst loader = new THREE.TextureLoader();\nconst texture = loader.load(\"path/to/texture.jpg\");\n\n// Texture properties\ntexture.wrapS = THREE.RepeatWrapping;\ntexture.wrapT = THREE.RepeatWrapping;\ntexture.repeat.set(2, 2);\ntexture.flipY = false;\n\n// HDR textures\nconst hdrLoader = new THREE.HDRLoader();\nconst envMap = hdrLoader.load(\"environment.hdr\");\nenvMap.mapping = THREE.EquirectangularReflectionMapping;\n```\n\n## Object3D Fundamentals\n\n### Transform Properties\n\n```javascript\n// Position\nobject.position.set(x, y, z);\nobject.position.copy(otherObject.position);\nobject.translateX(distance);\n\n// Rotation (Euler angles)\nobject.rotation.set(x, y, z);\nobject.rotation.y = Math.PI / 4;\nobject.rotateY(Math.PI / 4);\n\n// Scale\nobject.scale.set(2, 2, 2);\nobject.scale.multiplyScalar(0.5);\n\n// Quaternion (preferred for animations)\nobject.quaternion.setFromAxisAngle(axis, angle);\nobject.lookAt(target);\n```\n\n### Hierarchy Operations\n\n```javascript\n// Adding/removing children\nparent.add(child);\nparent.remove(child);\nscene.add(mesh, light, helper);\n\n// Traversal\nobject.traverse((child) => {\n if (child.isMesh) {\n child.material.wireframe = true;\n }\n});\n\n// Finding objects\nconst found = scene.getObjectByName(\"myObject\");\nconst found = scene.getObjectById(id);\n```\n\n## Math Utilities\n\n### Vectors\n\n```javascript\n// Vector3 (most common)\nconst v = new THREE.Vector3(1, 2, 3);\nv.add(otherVector);\nv.multiplyScalar(2);\nv.normalize();\nv.cross(otherVector);\nv.dot(otherVector);\nv.distanceTo(otherVector);\n\n// Vector2 (UV coordinates)\nconst uv = new THREE.Vector2(0.5, 0.5);\n```\n\n### Matrices\n\n```javascript\n// Matrix4 (transformations)\nconst matrix = new THREE.Matrix4();\nmatrix.makeTranslation(x, y, z);\nmatrix.makeRotationY(angle);\nmatrix.makeScale(x, y, z);\nmatrix.multiply(otherMatrix);\n\n// Apply to object\nobject.applyMatrix4(matrix);\n```\n\n### Colors\n\n```javascript\nconst color = new THREE.Color();\ncolor.set(0xff0000); // hex\ncolor.setRGB(1, 0, 0); // RGB values 0-1\ncolor.setHSL(0, 1, 0.5); // HSL values\ncolor.lerp(targetColor, 0.1); // interpolation\n```\n\n## Raycasting (Mouse Interaction)\n\n```javascript\nconst raycaster = new THREE.Raycaster();\nconst mouse = new THREE.Vector2();\n\nfunction onMouseClick(event) {\n // Normalize mouse coordinates (-1 to +1)\n mouse.x = (event.clientX / window.innerWidth) * 2 - 1;\n mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;\n\n // Cast ray from camera through mouse position\n raycaster.setFromCamera(mouse, camera);\n\n // Find intersections\n const intersects = raycaster.intersectObjects(scene.children, true);\n\n if (intersects.length > 0) {\n const object = intersects[0].object;\n const point = intersects[0].point;\n // Handle intersection\n }\n}\n```\n\n## Animation System\n\n### Animation Mixer\n\n```javascript\n// For GLTF animations\nconst mixer = new THREE.AnimationMixer(model);\nconst action = mixer.clipAction(animationClip);\naction.play();\n\n// Update in render loop\nfunction animate() {\n const delta = clock.getDelta();\n mixer.update(delta);\n renderer.render(scene, camera);\n}\n```\n\n### Manual Animation\n\n```javascript\nconst clock = new THREE.Clock();\n\nfunction animate() {\n const time = clock.getElapsedTime();\n\n // Rotate object\n mesh.rotation.y = time * 0.5;\n\n // Oscillate position\n mesh.position.y = Math.sin(time) * 2;\n\n renderer.render(scene, camera);\n}\nrenderer.setAnimationLoop(animate);\n```\n\n## Loading Assets\n\n### GLTF Models (Recommended)\n\n```javascript\nimport { GLTFLoader } from \"three/addons/loaders/GLTFLoader.js\";\n\nconst loader = new GLTFLoader();\nloader.load(\"model.gltf\", (gltf) => {\n const model = gltf.scene;\n scene.add(model);\n\n // Access animations\n if (gltf.animations.length > 0) {\n const mixer = new THREE.AnimationMixer(model);\n gltf.animations.forEach((clip) => {\n mixer.clipAction(clip).play();\n });\n }\n});\n```\n\n### Other Loaders\n\n```javascript\n// OBJ files\nimport { OBJLoader } from \"three/addons/loaders/OBJLoader.js\";\n\n// FBX files\nimport { FBXLoader } from \"three/addons/loaders/FBXLoader.js\";\n\n// Textures\nconst textureLoader = new THREE.TextureLoader();\nconst cubeLoader = new THREE.CubeTextureLoader();\n```\n\n## Renderer Configuration\n\n### Basic Setup\n\n```javascript\nconst renderer = new THREE.WebGLRenderer({\n canvas: canvasElement, // Existing canvas\n antialias: true, // Smooth edges\n alpha: true, // Transparent background\n powerPreference: \"high-performance\",\n});\n\nrenderer.setSize(width, height);\nrenderer.setPixelRatio(window.devicePixelRatio);\nrenderer.setClearColor(0x000000, 1);\n```\n\n### Advanced Settings\n\n```javascript\n// Shadows\nrenderer.shadowMap.enabled = true;\nrenderer.shadowMap.type = THREE.PCFSoftShadowMap;\n\n// Tone mapping (HDR)\nrenderer.toneMapping = THREE.ACESFilmicToneMapping;\nrenderer.toneMappingExposure = 1.0;\n\n// Color space\nrenderer.outputColorSpace = THREE.SRGBColorSpace;\n\n// Performance\nrenderer.setAnimationLoop(animate); // Preferred over requestAnimationFrame\n```\n\n## Common Patterns\n\n### Responsive Canvas\n\n```javascript\nfunction onWindowResize() {\n camera.aspect = window.innerWidth / window.innerHeight;\n camera.updateProjectionMatrix();\n renderer.setSize(window.innerWidth, window.innerHeight);\n}\nwindow.addEventListener(\"resize\", onWindowResize);\n```\n\n### Performance Optimization\n\n```javascript\n// Frustum culling\nobject.frustumCulled = true;\n\n// LOD (Level of Detail)\nconst lod = new THREE.LOD();\nlod.addLevel(highDetailMesh, 0);\nlod.addLevel(lowDetailMesh, 100);\n\n// Instancing for many objects\nconst instancedMesh = new THREE.InstancedMesh(geometry, material, count);\nconst matrix = new THREE.Matrix4();\nfor (let i = 0; i < count; i++) {\n matrix.setPosition(x, y, z);\n instancedMesh.setMatrixAt(i, matrix);\n}\ninstancedMesh.instanceMatrix.needsUpdate = true;\n```\n\n### Dispose Pattern (Memory Management)\n\n```javascript\n// Clean up resources\ngeometry.dispose();\nmaterial.dispose();\ntexture.dispose();\nrenderer.dispose();\n\n// Traverse and dispose\nobject.traverse((child) => {\n if (child.geometry) child.geometry.dispose();\n if (child.material) {\n if (Array.isArray(child.material)) {\n child.material.forEach((m) => m.dispose());\n } else {\n child.material.dispose();\n }\n }\n});\n```\n\n## Buffer Attributes (Advanced)\n\n### Custom Geometry Data\n\n```javascript\nconst geometry = new THREE.BufferGeometry();\n\n// Vertex positions (required)\nconst positions = new Float32Array([\n -1,\n -1,\n 0, // vertex 0\n 1,\n -1,\n 0, // vertex 1\n 0,\n 1,\n 0, // vertex 2\n]);\ngeometry.setAttribute(\"position\", new THREE.BufferAttribute(positions, 3));\n\n// Vertex colors\nconst colors = new Float32Array([\n 1,\n 0,\n 0, // red\n 0,\n 1,\n 0, // green\n 0,\n 0,\n 1, // blue\n]);\ngeometry.setAttribute(\"color\", new THREE.BufferAttribute(colors, 3));\n\n// Custom attributes for shaders\nconst customData = new Float32Array(vertexCount);\ngeometry.setAttribute(\n \"customAttribute\",\n new THREE.BufferAttribute(customData, 1),\n);\n```\n\n## Events and Interaction\n\n### Event Dispatcher\n\n```javascript\n// Custom events\nconst emitter = new THREE.EventDispatcher();\n\nemitter.addEventListener(\"customEvent\", (event) => {\n console.log(\"Event fired:\", event.data);\n});\n\nemitter.dispatchEvent({ type: \"customEvent\", data: \"hello\" });\n```\n\n### Built-in Events\n\n```javascript\n// Loading progress\nloader.onProgress = (progress) => {\n console.log(`Loading: ${(progress.loaded / progress.total) * 100}%`);\n};\n\n// Window resize\nwindow.addEventListener(\"resize\", onWindowResize);\n\n// Mouse events\ncanvas.addEventListener(\"click\", onMouseClick);\ncanvas.addEventListener(\"mousemove\", onMouseMove);\n```\n\n## Constants Reference\n\n### Material Constants\n\n```javascript\n// Blending modes\nTHREE.NormalBlending;\nTHREE.AdditiveBlending;\nTHREE.SubtractiveBlending;\nTHREE.MultiplyBlending;\n\n// Culling\nTHREE.FrontSide;\nTHREE.BackSide;\nTHREE.DoubleSide;\n\n// Depth modes\nTHREE.NeverDepth;\nTHREE.AlwaysDepth;\nTHREE.LessDepth;\nTHREE.LessEqualDepth;\n```\n\n### Texture Constants\n\n```javascript\n// Wrapping\nTHREE.RepeatWrapping;\nTHREE.ClampToEdgeWrapping;\nTHREE.MirroredRepeatWrapping;\n\n// Filtering\nTHREE.NearestFilter;\nTHREE.LinearFilter;\nTHREE.NearestMipmapNearestFilter;\nTHREE.LinearMipmapLinearFilter;\n\n// Formats\nTHREE.RGBAFormat;\nTHREE.RGBFormat;\nTHREE.RedFormat;\n```\n\n### Rendering Constants\n\n```javascript\n// Shadow types\nTHREE.BasicShadowMap;\nTHREE.PCFShadowMap;\nTHREE.PCFSoftShadowMap;\nTHREE.VSMShadowMap;\n\n// Tone mapping\nTHREE.NoToneMapping;\nTHREE.LinearToneMapping;\nTHREE.ReinhardToneMapping;\nTHREE.CineonToneMapping;\nTHREE.ACESFilmicToneMapping;\n```\n\n## Common Gotchas\n\n### Matrix Updates\n\n```javascript\n// Force matrix update after transform changes\nobject.updateMatrix();\nobject.updateMatrixWorld();\n\n// Automatic updates (default: true)\nobject.matrixAutoUpdate = false; // Manual control\n```\n\n### Geometry Modifications\n\n```javascript\n// After modifying geometry attributes\ngeometry.attributes.position.needsUpdate = true;\ngeometry.computeBoundingSphere();\ngeometry.computeBoundingBox();\n```\n\n### Material Updates\n\n```javascript\n// After changing material properties\nmaterial.needsUpdate = true;\n\n// Texture updates\ntexture.needsUpdate = true;\n```\n\n## Performance Tips\n\n### Efficient Rendering\n\n```javascript\n// Batch similar objects\nconst geometry = new THREE.InstancedBufferGeometry();\nconst material = new THREE.MeshStandardMaterial();\nconst instancedMesh = new THREE.InstancedMesh(geometry, material, 1000);\n\n// Freeze objects that don't move\nobject.matrixAutoUpdate = false;\nobject.updateMatrix();\n\n// Use appropriate geometry detail\nconst sphere = new THREE.SphereGeometry(1, 8, 6); // Low poly\nconst sphere = new THREE.SphereGeometry(1, 32, 32); // High poly\n```\n\n### Memory Management\n\n```javascript\n// Remove from scene\nscene.remove(object);\n\n// Dispose resources\nobject.traverse((child) => {\n if (child.geometry) child.geometry.dispose();\n if (child.material) child.material.dispose();\n});\n\n// Clear references\nobject = null;\n```\n\n## Quick Reference\n\n### Essential Imports\n\n```javascript\n// Core\nimport * as THREE from \"three\";\n\n// Controls\nimport { OrbitControls } from \"three/addons/controls/OrbitControls.js\";\nimport { FlyControls } from \"three/addons/controls/FlyControls.js\";\n\n// Loaders\nimport { GLTFLoader } from \"three/addons/loaders/GLTFLoader.js\";\nimport { OBJLoader } from \"three/addons/loaders/OBJLoader.js\";\n\n// Post-processing\nimport { EffectComposer } from \"three/addons/postprocessing/EffectComposer.js\";\n\n// Helpers\nimport { GUI } from \"three/addons/libs/lil-gui.module.min.js\";\nimport Stats from \"three/addons/libs/stats.module.js\";\n```\n\n### Minimal Working Example\n\n```javascript\nimport * as THREE from \"three\";\n\nconst scene = new THREE.Scene();\nconst camera = new THREE.PerspectiveCamera(\n 75,\n window.innerWidth / window.innerHeight,\n);\nconst renderer = new THREE.WebGLRenderer();\n\nrenderer.setSize(window.innerWidth, window.innerHeight);\ndocument.body.appendChild(renderer.domElement);\n\nconst geometry = new THREE.BoxGeometry();\nconst material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });\nconst cube = new THREE.Mesh(geometry, material);\nscene.add(cube);\n\ncamera.position.z = 5;\n\nfunction animate() {\n cube.rotation.x += 0.01;\n cube.rotation.y += 0.01;\n renderer.render(scene, camera);\n}\nrenderer.setAnimationLoop(animate);\n```\n\n---\n\n# Three.js Condensed Guide: Most Impressive Examples\n\n_A curated collection of Three.js's most visually stunning and technically advanced examples_\n\n## Quick Start Template\n\n```javascript\nimport * as THREE from \"three\";\nimport { OrbitControls } from \"three/addons/controls/OrbitControls.js\";\n\n// Basic setup\nconst scene = new THREE.Scene();\nconst camera = new THREE.PerspectiveCamera(\n 75,\n window.innerWidth / window.innerHeight,\n 0.1,\n 1000,\n);\nconst renderer = new THREE.WebGLRenderer({ antialias: true });\nrenderer.setSize(window.innerWidth, window.innerHeight);\ndocument.body.appendChild(renderer.domElement);\n\n// Controls\nconst controls = new OrbitControls(camera, renderer.domElement);\ncamera.position.set(5, 5, 5);\ncontrols.update();\n\n// Animation loop\nfunction animate() {\n controls.update();\n renderer.render(scene, camera);\n}\nrenderer.setAnimationLoop(animate);\n```\n\n## 1. Spectacular Visual Effects\n\n### Galaxy Generator (WebGPU + TSL)\n\nCreates a procedural spiral galaxy with thousands of animated particles.\n\n```javascript\nimport * as THREE from \"three/webgpu\";\nimport { color, cos, sin, time, uniform, range, vec3, PI2 } from \"three/tsl\";\n\nconst material = new THREE.SpriteNodeMaterial({\n depthWrite: false,\n blending: THREE.AdditiveBlending,\n});\n\n// Procedural galaxy structure\nconst radiusRatio = range(0, 1);\nconst radius = radiusRatio.pow(1.5).mul(5);\nconst branches = 3;\nconst branchAngle = range(0, branches).floor().mul(PI2.div(branches));\nconst angle = branchAngle.add(time.mul(radiusRatio.oneMinus()));\n\nconst position = vec3(cos(angle), 0, sin(angle)).mul(radius);\nmaterial.positionNode = position.add(randomOffset);\n\n// Dynamic colors\nconst colorInside = uniform(color(\"#ffa575\"));\nconst colorOutside = uniform(color(\"#311599\"));\nmaterial.colorNode = mix(colorInside, colorOutside, radiusRatio);\n\nconst galaxy = new THREE.InstancedMesh(\n new THREE.PlaneGeometry(1, 1),\n material,\n 20000,\n);\n```\n\n### Ocean Shaders\n\nRealistic water simulation with dynamic waves and sky reflections.\n\n```javascript\nimport { Water } from \"three/addons/objects/Water.js\";\nimport { Sky } from \"three/addons/objects/Sky.js\";\n\nconst waterGeometry = new THREE.PlaneGeometry(10000, 10000);\nconst water = new Water(waterGeometry, {\n textureWidth: 512,\n textureHeight: 512,\n waterNormals: new THREE.TextureLoader().load(\"textures/waternormals.jpg\"),\n sunDirection: new THREE.Vector3(),\n sunColor: 0xffffff,\n waterColor: 0x001e0f,\n distortionScale: 3.7,\n});\n\n// Sky system\nconst sky = new Sky();\nsky.scale.setScalar(10000);\nconst skyUniforms = sky.material.uniforms;\nskyUniforms[\"turbidity\"].value = 10;\nskyUniforms[\"rayleigh\"].value = 2;\n```\n\n### Unreal Bloom Effect\n\nCinematic glow and HDR post-processing.\n\n```javascript\nimport { EffectComposer } from \"three/addons/postprocessing/EffectComposer.js\";\nimport { RenderPass } from \"three/addons/postprocessing/RenderPass.js\";\nimport { UnrealBloomPass } from \"three/addons/postprocessing/UnrealBloomPass.js\";\n\nconst composer = new EffectComposer(renderer);\nconst renderPass = new RenderPass(scene, camera);\ncomposer.addPass(renderPass);\n\nconst bloomPass = new UnrealBloomPass(\n new THREE.Vector2(window.innerWidth, window.innerHeight),\n 1.5, // strength\n 0.4, // radius\n 0.85, // threshold\n);\ncomposer.addPass(bloomPass);\n\n// Render with bloom\ncomposer.render();\n```\n\n## 2. Advanced GPU Computing\n\n### Flocking Birds (GPGPU)\n\nGPU-accelerated boid simulation with emergent flocking behavior.\n\n```javascript\n// Position computation shader\nconst fragmentShaderPosition = `\nuniform float time;\nuniform float delta;\n\nvoid main() {\n vec2 uv = gl_FragCoord.xy / resolution.xy;\n vec4 tmpPos = texture2D(texturePosition, uv);\n vec3 position = tmpPos.xyz;\n vec3 velocity = texture2D(textureVelocity, uv).xyz;\n \n gl_FragColor = vec4(position + velocity * delta * 15.0, tmpPos.w);\n}`;\n\n// Velocity computation (separation, alignment, cohesion)\nconst fragmentShaderVelocity = `\nuniform float separationDistance;\nuniform float alignmentDistance; \nuniform float cohesionDistance;\nuniform vec3 predator;\n\nvoid main() {\n // Boid algorithm implementation\n // ...separation, alignment, cohesion logic\n}`;\n```\n\n### Cloth Physics (WebGPU Compute)\n\nReal-time fabric simulation using compute shaders.\n\n```javascript\nimport { Fn, uniform, attribute, Loop } from \"three/tsl\";\n\n// Verlet integration in compute shader\nconst computeVertexForces = Fn(() => {\n const position = attribute(\"position\");\n const velocity = attribute(\"velocity\");\n\n // Spring forces, wind, gravity\n const force = uniform(\"wind\").add(uniform(\"gravity\"));\n\n // Verlet integration\n const newPosition = position.add(velocity.mul(uniform(\"deltaTime\")));\n\n return newPosition;\n})();\n\nconst clothMaterial = new THREE.MeshPhysicalMaterial({\n color: 0x204080,\n roughness: 0.8,\n transmission: 0.2,\n sheen: 0.5,\n});\n```\n\n## 3. Impressive 3D Scenes\n\n### Photorealistic Car\n\nAdvanced PBR materials with interactive customization.\n\n```javascript\nimport { GLTFLoader } from \"three/addons/loaders/GLTFLoader.js\";\nimport { HDRLoader } from \"three/addons/loaders/HDRLoader.js\";\n\n// Environment setup\nscene.environment = new HDRLoader().load(\n \"textures/equirectangular/venice_sunset_1k.hdr\",\n);\nrenderer.toneMapping = THREE.ACESFilmicToneMapping;\nrenderer.toneMappingExposure = 0.85;\n\n// Load car model\nconst loader = new GLTFLoader();\nconst gltf = await loader.loadAsync(\"models/gltf/ferrari.glb\");\n\n// Material customization\ngltf.scene.traverse((child) => {\n if (child.isMesh && child.material.name === \"body\") {\n child.material.color.setHex(bodyColor);\n child.material.metalness = 1.0;\n child.material.roughness = 0.5;\n child.material.clearcoat = 1.0;\n }\n});\n```\n\n### Minecraft World Generator\n\nProcedural voxel terrain with optimized geometry merging.\n\n```javascript\nimport { ImprovedNoise } from \"three/addons/math/ImprovedNoise.js\";\nimport * as BufferGeometryUtils from \"three/addons/utils/BufferGeometryUtils.js\";\n\nfunction generateTerrain(width, depth) {\n const noise = new ImprovedNoise();\n const data = [];\n\n for (let x = 0; x < width; x++) {\n for (let z = 0; z < depth; z++) {\n // Multi-octave noise\n const height =\n noise.noise(x / 100, z / 100, 0) * 50 +\n noise.noise(x / 50, z / 50, 0) * 25;\n data.push(Math.floor(height));\n }\n }\n\n return data;\n}\n\n// Merge geometries for performance\nconst geometries = [];\n// ...create individual cube geometries\nconst mergedGeometry = BufferGeometryUtils.mergeGeometries(geometries);\n```\n\n## 4. Interactive Experiences\n\n### VR Painting\n\nVirtual reality 3D painting with hand tracking.\n\n```javascript\n// WebXR setup\nrenderer.xr.enabled = true;\ndocument.body.appendChild(VRButton.createButton(renderer));\n\n// Hand input\nconst controller1 = renderer.xr.getController(0);\nconst controller2 = renderer.xr.getController(1);\n\ncontroller1.addEventListener(\"selectstart\", onSelectStart);\ncontroller1.addEventListener(\"selectend\", onSelectEnd);\n\nfunction onSelectStart(event) {\n // Start painting stroke\n const geometry = new THREE.BufferGeometry();\n const material = new THREE.LineBasicMaterial({\n color: currentColor,\n linewidth: brushSize,\n });\n const line = new THREE.Line(geometry, material);\n scene.add(line);\n}\n```\n\n### Physics Vehicle Controller\n\nReal-time vehicle physics with Rapier.js integration.\n\n```javascript\nimport { World } from \"@dimforge/rapier3d-compat\";\n\n// Physics world\nconst world = new World({ x: 0, y: -9.81, z: 0 });\n\n// Vehicle setup\nconst vehicleDesc = world.createRigidBody({\n type: \"dynamic\",\n translation: { x: 0, y: 1, z: 0 },\n});\n\n// Wheel constraints\nwheels.forEach((wheel, index) => {\n const wheelJoint = world.createImpulseJoint(\n vehicleDesc,\n wheel.body,\n wheelConstraints[index],\n );\n});\n```\n\n## 5. Cutting-Edge WebGPU Features\n\n### Path Tracing\n\nRealistic ray-traced lighting with global illumination.\n\n```javascript\nimport { PathTracingRenderer } from \"three/addons/renderers/PathTracingRenderer.js\";\n\nconst ptRenderer = new PathTracingRenderer(renderer);\nptRenderer.setSize(window.innerWidth, window.innerHeight);\n\n// Progressive rendering\nlet sampleCount = 0;\nfunction animate() {\n if (sampleCount < 1000) {\n ptRenderer.update();\n sampleCount++;\n }\n}\n```\n\n### TSL (Three.js Shading Language)\n\nModern node-based shader programming.\n\n```javascript\nimport { mix, noise, time, uv, vec3, sin, cos } from \"three/tsl\";\n\n// Procedural materials with TSL\nconst proceduralMaterial = new THREE.MeshStandardNodeMaterial();\n\n// Animated noise texture\nconst noiseValue = noise(uv().mul(10).add(time.mul(0.1)));\nconst colorA = vec3(1, 0.5, 0.2);\nconst colorB = vec3(0.2, 0.5, 1);\n\nproceduralMaterial.colorNode = mix(colorA, colorB, noiseValue);\nproceduralMaterial.roughnessNode = noiseValue.mul(0.5).add(0.3);\n```\n\n## Performance Tips for Impressive Results\n\n### Instancing for Massive Scenes\n\n```javascript\nconst instancedMesh = new THREE.InstancedMesh(geometry, material, 100000);\nconst matrix = new THREE.Matrix4();\n\nfor (let i = 0; i < instancedMesh.count; i++) {\n matrix.setPosition(\n Math.random() * 2000 - 1000,\n Math.random() * 2000 - 1000,\n Math.random() * 2000 - 1000,\n );\n instancedMesh.setMatrixAt(i, matrix);\n}\n```\n\n### LOD for Complex Models\n\n```javascript\nconst lod = new THREE.LOD();\nlod.addLevel(highDetailMesh, 0);\nlod.addLevel(mediumDetailMesh, 50);\nlod.addLevel(lowDetailMesh, 200);\n```\n\n### Render Targets for Effects\n\n```javascript\nconst renderTarget = new THREE.WebGLRenderTarget(1024, 1024);\nrenderer.setRenderTarget(renderTarget);\nrenderer.render(effectScene, effectCamera);\nrenderer.setRenderTarget(null);\n\n// Use render target as texture\nmaterial.map = renderTarget.texture;\n```\n\n## Essential Setup for Maximum Impact\n\n### HDR Environment\n\n```javascript\nimport { HDRLoader } from \"three/addons/loaders/HDRLoader.js\";\n\nconst hdrTexture = new HDRLoader().load(\"environment.hdr\");\nhdrTexture.mapping = THREE.EquirectangularReflectionMapping;\nscene.environment = hdrTexture;\nscene.background = hdrTexture;\n```\n\n### Tone Mapping\n\n```javascript\nrenderer.toneMapping = THREE.ACESFilmicToneMapping;\nrenderer.toneMappingExposure = 1.0;\nrenderer.outputColorSpace = THREE.SRGBColorSpace;\n```\n\n### Post-Processing Chain\n\n```javascript\nimport { EffectComposer } from \"three/addons/postprocessing/EffectComposer.js\";\n\nconst composer = new EffectComposer(renderer);\ncomposer.addPass(new RenderPass(scene, camera));\ncomposer.addPass(new UnrealBloomPass(resolution, strength, radius, threshold));\ncomposer.addPass(new OutputPass());\n```\n\n---\n\n_This guide focuses on Three.js's most impressive capabilities. Each example demonstrates advanced techniques that create visually stunning results with minimal code complexity._\n\n# Real world example\n\n```javascript\nimport React, { useState, useEffect, useRef, useCallback } from \"react\";\nimport { useFireproof } from \"use-fireproof\";\nimport * as THREE from \"three\";\n\nexport default function SkyGlider() {\n const { database, useLiveQuery } = useFireproof(\"sky-glider-scores\");\n const canvasRef = useRef(null);\n const gameStateRef = useRef({\n scene: null,\n camera: null,\n renderer: null,\n glider: null,\n clouds: [],\n coins: [],\n glowEffects: [],\n smokeTrail: [],\n lastSmokeTime: 0,\n score: 0,\n gameRunning: false,\n keys: {},\n velocity: { x: 0, y: 0, z: 0 },\n heading: 0,\n forwardSpeed: 0,\n pitch: 0,\n roll: 0,\n });\n\n const [currentScore, setCurrentScore] = useState(0);\n const { docs: scoreData } = useLiveQuery(\"type\", { key: \"score\" }) || {\n docs: [],\n };\n\n const saveScore = useCallback(\n async (score) => {\n await database.put({\n _id: `score-${Date.now()}`,\n type: \"score\",\n value: score,\n timestamp: Date.now(),\n });\n },\n [database],\n );\n\n const createGlowEffect = useCallback((position) => {\n const state = gameStateRef.current;\n if (!state.scene) return;\n\n const glowSphere = new THREE.Mesh(\n new THREE.SphereGeometry(8, 16, 16),\n new THREE.MeshBasicMaterial({\n color: 0xffd670,\n transparent: true,\n opacity: 0.8,\n }),\n );\n\n glowSphere.position.copy(position);\n state.scene.add(glowSphere);\n\n const glowEffect = {\n mesh: glowSphere,\n createdAt: Date.now(),\n scale: 1,\n };\n\n state.glowEffects.push(glowEffect);\n\n // Remove after animation\n setTimeout(() => {\n state.scene.remove(glowSphere);\n const index = state.glowEffects.indexOf(glowEffect);\n if (index > -1) state.glowEffects.splice(index, 1);\n }, 1000);\n }, []);\n\n const createSmokeCloud = useCallback((position) => {\n const state = gameStateRef.current;\n if (!state.scene) return;\n\n const smokeGeometry = new THREE.SphereGeometry(\n 0.1 + Math.random() * 0.05,\n 4,\n 3,\n );\n const smokeMaterial = new THREE.MeshLambertMaterial({\n color: 0x242424,\n transparent: true,\n opacity: 0.7 + Math.random() * 0.2,\n });\n const smokeCloud = new THREE.Mesh(smokeGeometry, smokeMaterial);\n\n // Position behind the glider\n const heading = state.heading;\n const offsetX = Math.sin(heading) * -4;\n const offsetZ = Math.cos(heading) * -4;\n\n smokeCloud.position.set(\n position.x + offsetX + (Math.random() - 0.5) * 0.2,\n position.y - 0.2 + (Math.random() - 0.5) * 0.1,\n position.z + offsetZ + Math.random() * 0.3,\n );\n\n state.scene.add(smokeCloud);\n state.smokeTrail.push({\n mesh: smokeCloud,\n createdAt: Date.now(),\n });\n\n // Keep trail manageable\n while (state.smokeTrail.length > 100) {\n const oldSmoke = state.smokeTrail.shift();\n state.scene.remove(oldSmoke.mesh);\n }\n }, []);\n\n const createTexturedCoin = useCallback((scene, position) => {\n // Create procedural gold texture\n const canvas = document.createElement(\"canvas\");\n canvas.width = canvas.height = 128;\n const ctx = canvas.getContext(\"2d\");\n\n // Gold gradient\n const gradient = ctx.createRadialGradient(64, 64, 20, 64, 64, 64);\n gradient.addColorStop(0, \"#ffd670\");\n gradient.addColorStop(0.5, \"#ff9770\");\n gradient.addColorStop(1, \"#ffb347\");\n ctx.fillStyle = gradient;\n ctx.fillRect(0, 0, 128, 128);\n\n // Add metallic shine lines\n ctx.strokeStyle = \"#ffffff\";\n ctx.lineWidth = 2;\n ctx.setLineDash([5, 3]);\n for (let i = 0; i < 8; i++) {\n const angle = (i / 8) * Math.PI * 2;\n ctx.beginPath();\n ctx.moveTo(64 + Math.cos(angle) * 30, 64 + Math.sin(angle) * 30);\n ctx.lineTo(64 + Math.cos(angle) * 50, 64 + Math.sin(angle) * 50);\n ctx.stroke();\n }\n\n const texture = new THREE.CanvasTexture(canvas);\n const coin = new THREE.Mesh(\n new THREE.CylinderGeometry(2, 2, 0.3, 16),\n new THREE.MeshStandardMaterial({\n map: texture,\n metalness: 0.8,\n roughness: 0.2,\n }),\n );\n\n coin.position.copy(position);\n coin.rotation.z = Math.PI / 2;\n scene.add(coin);\n\n return {\n mesh: coin,\n collected: false,\n rotation: Math.random() * 0.02 + 0.01,\n };\n }, []);\n\n const initThreeJS = useCallback(() => {\n if (!canvasRef.current) return;\n\n const scene = new THREE.Scene();\n scene.background = new THREE.Color(0x70d6ff);\n scene.fog = new THREE.Fog(0x70d6ff, 50, 300);\n\n const camera = new THREE.PerspectiveCamera(\n 75,\n window.innerWidth / window.innerHeight,\n 0.1,\n 1000,\n );\n camera.position.set(0, 10, 20);\n\n const renderer = new THREE.WebGLRenderer({\n canvas: canvasRef.current,\n antialias: true,\n });\n renderer.setSize(window.innerWidth, window.innerHeight);\n\n // Lighting\n const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);\n scene.add(ambientLight);\n const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);\n directionalLight.position.set(50, 100, 50);\n scene.add(directionalLight);\n\n // Glider\n const glider = new THREE.Group();\n const body = new THREE.Mesh(\n new THREE.ConeGeometry(2, 8, 3),\n new THREE.MeshLambertMaterial({ color: 0xff70a6 }),\n );\n body.rotation.x = Math.PI / 2;\n glider.add(body);\n\n glider.position.set(0, 10, 0);\n scene.add(glider);\n\n // Create simple clouds\n const clouds = [];\n for (let i = 0; i < 30; i++) {\n const cloud = new THREE.Mesh(\n new THREE.SphereGeometry(Math.random() * 5 + 3, 8, 6),\n new THREE.MeshLambertMaterial({\n color: 0xffffff,\n transparent: true,\n opacity: 0.7,\n }),\n );\n cloud.position.set(\n (Math.random() - 0.5) * 400,\n Math.random() * 30 + 10,\n (Math.random() - 0.5) * 400,\n );\n scene.add(cloud);\n clouds.push({\n mesh: cloud,\n drift: {\n x: (Math.random() - 0.5) * 0.01,\n y: 0,\n z: (Math.random() - 0.5) * 0.01,\n },\n });\n }\n\n // Create initial coins\n const coins = [];\n for (let i = 0; i < 20; i++) {\n const coin = createTexturedCoin(\n scene,\n new THREE.Vector3(\n (Math.random() - 0.5) * 200,\n Math.random() * 40 + 10,\n (Math.random() - 0.5) * 200,\n ),\n );\n coins.push(coin);\n }\n\n gameStateRef.current = {\n ...gameStateRef.current,\n scene,\n camera,\n renderer,\n glider,\n clouds,\n coins,\n lastSmokeTime: Date.now(),\n heading: 0,\n forwardSpeed: 0.1,\n pitch: 0,\n roll: 0,\n };\n\n const gameLoop = () => {\n if (gameStateRef.current.gameRunning) {\n updateGame();\n requestAnimationFrame(gameLoop);\n }\n };\n gameStateRef.current.gameRunning = true;\n gameLoop();\n }, [createTexturedCoin]);\n\n const checkCoinCollisions = useCallback(() => {\n const state = gameStateRef.current;\n if (!state.glider) return;\n\n state.coins.forEach((coin) => {\n if (!coin.collected) {\n const distance = state.glider.position.distanceTo(coin.mesh.position);\n if (distance < 4) {\n coin.collected = true;\n coin.mesh.visible = false;\n createGlowEffect(coin.mesh.position);\n state.score += 1;\n setCurrentScore(state.score);\n\n // Respawn coin at random location\n setTimeout(() => {\n coin.mesh.position.set(\n (Math.random() - 0.5) * 200,\n Math.random() * 40 + 10,\n (Math.random() - 0.5) * 200,\n );\n coin.mesh.visible = true;\n coin.collected = false;\n }, 5000);\n }\n }\n });\n }, [createGlowEffect]);\n\n const handleKeyDown = useCallback((event) => {\n if (event.code === \"Space\") event.preventDefault();\n gameStateRef.current.keys[event.code] = true;\n }, []);\n\n const handleKeyUp = useCallback((event) => {\n if (event.code === \"Space\") event.preventDefault();\n gameStateRef.current.keys[event.code] = false;\n }, []);\n\n const updateGame = useCallback(() => {\n const state = gameStateRef.current;\n if (!state.gameRunning || !state.glider) return;\n\n const { keys, glider } = state;\n\n // Controls\n if (keys[\"ArrowLeft\"] || keys[\"KeyA\"]) state.heading += 0.03;\n if (keys[\"ArrowRight\"] || keys[\"KeyD\"]) state.heading -= 0.03;\n if (keys[\"ArrowUp\"] || keys[\"KeyW\"]) state.pitch += 0.01;\n if (keys[\"ArrowDown\"] || keys[\"KeyS\"]) state.pitch -= 0.01;\n if (keys[\"Space\"])\n state.forwardSpeed = Math.min(0.3, state.forwardSpeed + 0.005);\n\n // Physics\n state.forwardSpeed = Math.max(0.05, state.forwardSpeed * 0.995);\n state.velocity.x =\n Math.sin(state.heading) * Math.cos(state.pitch) * state.forwardSpeed;\n state.velocity.y = Math.sin(-state.pitch) * state.forwardSpeed;\n state.velocity.z =\n Math.cos(state.heading) * Math.cos(state.pitch) * state.forwardSpeed;\n\n glider.position.add(\n new THREE.Vector3(state.velocity.x, state.velocity.y, state.velocity.z),\n );\n\n // Point glider in thrust vector direction\n const thrustDirection = new THREE.Vector3(\n state.velocity.x,\n state.velocity.y,\n state.velocity.z,\n ).normalize();\n if (thrustDirection.length() > 0) {\n glider.lookAt(glider.position.clone().add(thrustDirection));\n }\n\n // Camera follow\n const cameraDistance = 15;\n state.camera.position.set(\n glider.position.x - Math.sin(state.heading) * cameraDistance,\n glider.position.y + 10,\n glider.position.z - Math.cos(state.heading) * cameraDistance,\n );\n state.camera.lookAt(glider.position);\n\n // Create smoke trail\n const currentTime = Date.now();\n const timeSinceLastSmoke = currentTime - state.lastSmokeTime;\n const smokeInterval = 150 + Math.random() * 200;\n\n if (timeSinceLastSmoke > smokeInterval) {\n createSmokeCloud(glider.position);\n state.lastSmokeTime = currentTime;\n }\n\n // Animate\n checkCoinCollisions();\n state.coins.forEach((coin) => {\n if (!coin.collected) coin.mesh.rotation.y += coin.rotation;\n });\n state.clouds.forEach((cloud) => {\n cloud.mesh.position.add(\n new THREE.Vector3(cloud.drift.x, cloud.drift.y, cloud.drift.z),\n );\n });\n\n // Animate glow effects\n state.glowEffects.forEach((effect) => {\n const age = Date.now() - effect.createdAt;\n const progress = age / 1000;\n effect.scale = 1 + progress * 2;\n effect.mesh.scale.setScalar(effect.scale);\n effect.mesh.material.opacity = 0.8 * (1 - progress);\n });\n\n // Fade smoke trail\n state.smokeTrail.forEach((smoke) => {\n const age = currentTime - smoke.createdAt;\n const maxAge = 15000;\n if (age > maxAge) {\n smoke.mesh.material.opacity = 0;\n } else if (age > 7500) {\n const fadeProgress = (age - 7500) / 7500;\n smoke.mesh.material.opacity =\n (0.7 + Math.random() * 0.2) * (1 - fadeProgress);\n }\n });\n\n state.renderer.render(state.scene, state.camera);\n }, [checkCoinCollisions, createSmokeCloud]);\n\n useEffect(() => {\n initThreeJS();\n window.addEventListener(\"keydown\", handleKeyDown);\n window.addEventListener(\"keyup\", handleKeyUp);\n return () => {\n window.removeEventListener(\"keydown\", handleKeyDown);\n window.removeEventListener(\"keyup\", handleKeyUp);\n gameStateRef.current.gameRunning = false;\n };\n }, [initThreeJS, handleKeyDown, handleKeyUp]);\n\n return (\n
\n \n
\n

Sky Glider

\n

Score: {currentScore}

\n

WASD/Arrows: Fly, Space: Thrust

\n
\n {scoreData.length > 0 && (\n
\n

High Scores

\n {scoreData\n .sort((a, b) => b.value - a.value)\n .slice(0, 3)\n .map((score, i) => (\n
\n #{i + 1}: {score.value}\n
\n ))}\n
\n )}\n
\n );\n}\n```\n\n# Visual effects example\n\n```javascript\nimport React, { useState, useEffect, useRef, useCallback } from \"react\";\nimport { useFireproof } from \"use-fireproof\";\nimport * as THREE from \"three\";\nimport { OrbitControls } from \"three/addons/controls/OrbitControls.js\";\nimport { EffectComposer } from \"three/addons/postprocessing/EffectComposer.js\";\nimport { RenderPass } from \"three/addons/postprocessing/RenderPass.js\";\nimport { HalftonePass } from \"three/addons/postprocessing/HalftonePass.js\";\n\nexport default function HalftoneArtStudio() {\n const { database, useLiveQuery } = useFireproof(\"halftone-studio\");\n const canvasRef = useRef(null);\n const sceneRef = useRef(null);\n const [currentPreset, setCurrentPreset] = useState(null);\n const [presetName, setPresetName] = useState(\"\");\n const [isGenerating, setIsGenerating] = useState(false);\n const [showParameters, setShowParameters] = useState(false);\n\n const { docs: presets } = useLiveQuery(\"type\", { key: \"preset\" }) || {\n docs: [],\n };\n const { docs: parameterHistory } = useLiveQuery(\"type\", {\n key: \"parameter-state\",\n }) || {\n docs: [],\n };\n\n const [parameters, setParameters] = useState({\n shape: 1, // 1=Dot, 2=Ellipse, 3=Line, 4=Square\n radius: 4,\n rotateR: 15,\n rotateG: 30,\n rotateB: 45,\n scatter: 0,\n blending: 1,\n blendingMode: 1, // 1=Linear, 2=Multiply, 3=Add, 4=Lighter, 5=Darker\n greyscale: false,\n disable: false,\n objectCount: 25,\n rotationSpeed: 1,\n colorTheme: 0, // 0=Rainbow, 1=Warm, 2=Cool, 3=Monochrome\n });\n\n const saveParameterState = useCallback(\n async (params, action = \"manual\") => {\n await database.put({\n _id: `param-state-${Date.now()}`,\n type: \"parameter-state\",\n parameters: { ...params },\n action,\n timestamp: Date.now(),\n });\n },\n [database],\n );\n\n const savePreset = useCallback(async () => {\n if (!presetName.trim()) return;\n\n await database.put({\n _id: `preset-${Date.now()}`,\n type: \"preset\",\n name: presetName,\n parameters: { ...parameters },\n timestamp: Date.now(),\n });\n\n setPresetName(\"\");\n }, [database, presetName, parameters]);\n\n const loadPreset = useCallback((preset) => {\n setParameters({ ...preset.parameters });\n setCurrentPreset(preset);\n }, []);\n\n const loadParameterState = useCallback((state) => {\n setParameters({ ...state.parameters });\n }, []);\n\n const generateRandomScene = useCallback(async () => {\n setIsGenerating(true);\n\n // Save current state before randomizing\n await saveParameterState(parameters, \"before-randomize\");\n\n // Generate random parameters\n const newParams = {\n shape: Math.floor(Math.random() * 4) + 1,\n radius: Math.random() * 20 + 2,\n rotateR: Math.random() * 90,\n rotateG: Math.random() * 90,\n rotateB: Math.random() * 90,\n scatter: Math.random(),\n blending: Math.random(),\n blendingMode: Math.floor(Math.random() * 5) + 1,\n greyscale: Math.random() > 0.7,\n disable: false,\n objectCount: Math.floor(Math.random() * 40) + 10,\n rotationSpeed: Math.random() * 3 + 0.5,\n colorTheme: Math.floor(Math.random() * 4),\n };\n\n setParameters(newParams);\n\n // Save the new randomized state\n setTimeout(async () => {\n await saveParameterState(newParams, \"randomized\");\n setIsGenerating(false);\n }, 500);\n }, [parameters, saveParameterState]);\n\n // Save parameter changes for history\n useEffect(() => {\n const timeoutId = setTimeout(() => {\n saveParameterState(parameters, \"manual\");\n }, 1000);\n\n return () => clearTimeout(timeoutId);\n }, [parameters, saveParameterState]);\n\n useEffect(() => {\n if (!canvasRef.current) return;\n\n // Scene setup\n const scene = new THREE.Scene();\n scene.background = new THREE.Color(0x242424);\n\n const camera = new THREE.PerspectiveCamera(\n 75,\n window.innerWidth / window.innerHeight,\n 1,\n 1000,\n );\n camera.position.z = 12;\n\n const renderer = new THREE.WebGLRenderer({\n canvas: canvasRef.current,\n antialias: true,\n preserveDrawingBuffer: true,\n });\n renderer.setPixelRatio(window.devicePixelRatio);\n renderer.setSize(window.innerWidth, window.innerHeight);\n\n // Controls\n const controls = new OrbitControls(camera, renderer.domElement);\n controls.enableDamping = true;\n controls.dampingFactor = 0.05;\n controls.autoRotate = true;\n controls.autoRotateSpeed = 0.5;\n\n // Group for all objects\n const group = new THREE.Group();\n scene.add(group);\n\n // Lighting\n const ambientLight = new THREE.AmbientLight(0xffffff, 0.4);\n scene.add(ambientLight);\n\n const pointLight = new THREE.PointLight(0xffffff, 1, 100);\n pointLight.position.set(10, 10, 10);\n scene.add(pointLight);\n\n // Post-processing\n const composer = new EffectComposer(renderer);\n const renderPass = new RenderPass(scene, camera);\n composer.addPass(renderPass);\n\n const halftonePass = new HalftonePass({\n shape: parameters.shape,\n radius: parameters.radius,\n rotateR: parameters.rotateR * (Math.PI / 180),\n rotateG: parameters.rotateG * (Math.PI / 180),\n rotateB: parameters.rotateB * (Math.PI / 180),\n scatter: parameters.scatter,\n blending: parameters.blending,\n blendingMode: parameters.blendingMode,\n greyscale: parameters.greyscale,\n disable: parameters.disable,\n });\n composer.addPass(halftonePass);\n\n // Store refs\n sceneRef.current = {\n scene,\n camera,\n renderer,\n composer,\n halftonePass,\n group,\n controls,\n objects: [],\n };\n\n // Create initial objects\n const createObjects = () => {\n // Clear existing objects\n sceneRef.current.objects.forEach((obj) => {\n group.remove(obj);\n });\n sceneRef.current.objects = [];\n\n // Color themes\n const colorThemes = [\n [0xff70a6, 0x70d6ff, 0xffd670, 0xe9ff70, 0xff9770], // Rainbow\n [0xff9770, 0xffd670, 0xff70a6], // Warm\n [0x70d6ff, 0xe9ff70, 0x242424], // Cool\n [0xffffff, 0x242424], // Monochrome\n ];\n\n const colors = colorThemes[parameters.colorTheme] || colorThemes[0];\n\n // Shader material for interesting effects\n const material = new THREE.ShaderMaterial({\n uniforms: {\n time: { value: 0 },\n },\n vertexShader: `\n varying vec2 vUv;\n varying vec3 vNormal;\n varying vec3 vPosition;\n uniform float time;\n \n void main() {\n vUv = uv;\n vNormal = normalize(normalMatrix * normal);\n vPosition = position;\n \n vec3 pos = position;\n pos += sin(pos * 2.0 + time) * 0.1;\n \n gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);\n }\n `,\n fragmentShader: `\n varying vec2 vUv;\n varying vec3 vNormal;\n varying vec3 vPosition;\n uniform float time;\n \n void main() {\n vec3 color = abs(vNormal) + vec3(vUv, sin(time + vPosition.x));\n color = mix(color, vec3(1.0, 0.4, 0.6), sin(time + vPosition.y) * 0.5 + 0.5);\n gl_FragColor = vec4(color, 1.0);\n }\n `,\n });\n\n // Create various geometric shapes\n const geometries = [\n new THREE.BoxGeometry(2, 2, 2),\n new THREE.SphereGeometry(1.2, 16, 16),\n new THREE.ConeGeometry(1, 2, 8),\n new THREE.CylinderGeometry(0.8, 0.8, 2, 8),\n new THREE.OctahedronGeometry(1.2),\n new THREE.TetrahedronGeometry(1.5),\n new THREE.DodecahedronGeometry(1),\n new THREE.IcosahedronGeometry(1.2),\n ];\n\n for (let i = 0; i < parameters.objectCount; i++) {\n const geometry =\n geometries[Math.floor(Math.random() * geometries.length)];\n const basicMaterial = new THREE.MeshPhongMaterial({\n color: colors[Math.floor(Math.random() * colors.length)],\n shininess: 100,\n transparent: true,\n opacity: 0.8 + Math.random() * 0.2,\n });\n\n const mesh = new THREE.Mesh(\n geometry,\n Math.random() > 0.3 ? basicMaterial : material,\n );\n\n mesh.position.set(\n (Math.random() - 0.5) * 20,\n (Math.random() - 0.5) * 20,\n (Math.random() - 0.5) * 20,\n );\n\n mesh.rotation.set(\n Math.random() * Math.PI * 2,\n Math.random() * Math.PI * 2,\n Math.random() * Math.PI * 2,\n );\n\n mesh.scale.setScalar(0.5 + Math.random() * 1.5);\n\n group.add(mesh);\n sceneRef.current.objects.push(mesh);\n }\n };\n\n createObjects();\n\n // Animation loop\n const clock = new THREE.Clock();\n const animate = () => {\n const delta = clock.getDelta();\n const elapsed = clock.getElapsedTime();\n\n // Update material uniforms\n sceneRef.current.objects.forEach((obj) => {\n if (obj.material.uniforms && obj.material.uniforms.time) {\n obj.material.uniforms.time.value = elapsed;\n }\n\n // Animate objects\n obj.rotation.x += delta * parameters.rotationSpeed * 0.2;\n obj.rotation.y += delta * parameters.rotationSpeed * 0.3;\n obj.rotation.z += delta * parameters.rotationSpeed * 0.1;\n });\n\n controls.update();\n composer.render();\n requestAnimationFrame(animate);\n };\n\n animate();\n\n // Handle resize\n const handleResize = () => {\n camera.aspect = window.innerWidth / window.innerHeight;\n camera.updateProjectionMatrix();\n renderer.setSize(window.innerWidth, window.innerHeight);\n composer.setSize(window.innerWidth, window.innerHeight);\n };\n\n window.addEventListener(\"resize\", handleResize);\n\n return () => {\n window.removeEventListener(\"resize\", handleResize);\n renderer.dispose();\n };\n }, [parameters]);\n\n // Update halftone parameters\n useEffect(() => {\n if (sceneRef.current?.halftonePass) {\n const pass = sceneRef.current.halftonePass;\n pass.uniforms.shape.value = parameters.shape;\n pass.uniforms.radius.value = parameters.radius;\n pass.uniforms.rotateR.value = parameters.rotateR * (Math.PI / 180);\n pass.uniforms.rotateG.value = parameters.rotateG * (Math.PI / 180);\n pass.uniforms.rotateB.value = parameters.rotateB * (Math.PI / 180);\n pass.uniforms.scatter.value = parameters.scatter;\n pass.uniforms.blending.value = parameters.blending;\n pass.uniforms.blendingMode.value = parameters.blendingMode;\n pass.uniforms.greyscale.value = parameters.greyscale;\n pass.uniforms.disable.value = parameters.disable;\n }\n }, [parameters]);\n\n const shapeName =\n [\"\", \"Dot\", \"Ellipse\", \"Line\", \"Square\"][parameters.shape] || \"Dot\";\n const blendModeName =\n [\"\", \"Linear\", \"Multiply\", \"Add\", \"Lighter\", \"Darker\"][\n parameters.blendingMode\n ] || \"Linear\";\n const actionNames = {\n \"before-randomize\": \"🎲 Before Random\",\n randomized: \"✨ Randomized\",\n manual: \"✏️ Manual Edit\",\n };\n\n return (\n
\n {/* Background pattern */}\n \n\n \n\n {/* Main Control Panel */}\n \n

\n RGB Halftone Studio\n

\n\n {/* Always visible controls */}\n
\n \n {isGenerating ? \"Generating...\" : \"🎲 Random Art\"}\n \n\n setShowParameters(!showParameters)}\n className=\"w-full rounded border-2 border-[#242424] bg-[#70d6ff] px-4 py-2 font-bold text-[#242424] hover:bg-[#e9ff70]\"\n >\n {showParameters ? \"🔼 Hide Controls\" : \"🔽 Show Controls\"}\n \n
\n\n {/* Expandable parameter controls */}\n {showParameters && (\n
\n {/* Shape Controls */}\n
\n \n \n setParameters((prev) => ({\n ...prev,\n shape: parseInt(e.target.value),\n }))\n }\n className=\"w-full rounded border-2 border-[#242424] p-2 text-[#242424]\"\n >\n \n \n \n \n \n
\n\n {/* Size Controls */}\n
\n \n \n setParameters((prev) => ({\n ...prev,\n radius: parseFloat(e.target.value),\n }))\n }\n className=\"w-full\"\n />\n
\n\n {/* Color Rotation */}\n
\n
\n \n \n setParameters((prev) => ({\n ...prev,\n rotateR: parseFloat(e.target.value),\n }))\n }\n className=\"w-full\"\n />\n
\n
\n \n \n setParameters((prev) => ({\n ...prev,\n rotateG: parseFloat(e.target.value),\n }))\n }\n className=\"w-full\"\n />\n
\n
\n \n \n setParameters((prev) => ({\n ...prev,\n rotateB: parseFloat(e.target.value),\n }))\n }\n className=\"w-full\"\n />\n
\n
\n\n {/* Effects */}\n
\n \n \n setParameters((prev) => ({\n ...prev,\n scatter: parseFloat(e.target.value),\n }))\n }\n className=\"w-full\"\n />\n
\n\n
\n \n \n setParameters((prev) => ({\n ...prev,\n blending: parseFloat(e.target.value),\n }))\n }\n className=\"w-full\"\n />\n
\n\n
\n \n \n setParameters((prev) => ({\n ...prev,\n blendingMode: parseInt(e.target.value),\n }))\n }\n className=\"w-full rounded border-2 border-[#242424] p-2 text-[#242424]\"\n >\n \n \n \n \n \n \n
\n\n {/* Toggles */}\n
\n \n\n \n
\n\n {/* Save Preset */}\n
\n setPresetName(e.target.value)}\n className=\"mb-2 w-full rounded border-2 border-[#242424] p-2 text-[#242424]\"\n />\n \n 💾 Save Preset\n \n
\n\n {/* Saved Presets */}\n {presets.length > 0 && (\n
\n

\n 💾 Saved Presets\n

\n
\n {presets\n .sort((a, b) => b.timestamp - a.timestamp)\n .map((preset) => (\n loadPreset(preset)}\n >\n
\n {preset.name}\n
\n
\n {\n [\"\", \"Dot\", \"Ellipse\", \"Line\", \"Square\"][\n preset.parameters.shape\n ]\n }{\" \"}\n • {preset.parameters.greyscale ? \"B&W\" : \"Color\"}\n
\n
\n ))}\n
\n
\n )}\n\n {/* Parameter History */}\n {parameterHistory.length > 0 && (\n
\n

\n 📜 Parameter History\n

\n
\n {parameterHistory\n .sort((a, b) => b.timestamp - a.timestamp)\n .slice(0, 10)\n .map((state) => (\n loadParameterState(state)}\n >\n
\n {actionNames[state.action] || \"⚙️ Unknown\"}\n
\n
\n {\n [\"\", \"Dot\", \"Ellipse\", \"Line\", \"Square\"][\n state.parameters.shape\n ]\n }{\" \"}\n • Size: {state.parameters.radius.toFixed(1)} •{\" \"}\n {state.parameters.greyscale ? \"B&W\" : \"Color\"}\n
\n
\n {new Date(state.timestamp).toLocaleTimeString()}\n
\n
\n ))}\n
\n
\n )}\n \n )}\n \n \n );\n}\n```", + "web-audio": "# Web Audio API: Fundamentals, Echo with FX-in-Feedback, Mic Monitoring + Metronome, and Timing Architecture\n\nAuthoritative source: Issue #228 research threads — comments 3192681700, 3192696052, 3192806626.\n\n## 1) Fundamentals and Core Nodes\n\n- AudioContext — master interface and clock (`audioCtx.currentTime`). Resume on a user gesture.\n- OscillatorNode — synthesis; set `type` and `frequency`.\n- AudioBufferSourceNode — decoded-file playback; schedule with `.start(when, offset?, duration?)`.\n- GainNode — volume control and envelopes.\n- BiquadFilterNode — EQ/tonal shaping (`type`, `frequency`, `Q`, etc.).\n- AnalyserNode — FFT/time-domain visualization.\n\nExamples\n\n```js\n// 1) Context (user gesture required in many browsers)\nconst audioCtx = new (window.AudioContext || window.webkitAudioContext)();\n\n// Start/resume only in direct response to a user gesture (e.g., a Play button)\ndocument.querySelector('#start-audio')?.addEventListener('click', async () => {\n if (audioCtx.state !== 'running') await audioCtx.resume();\n // now safe to create/start nodes\n});\n\n// 2) Simple tone\nconst osc = audioCtx.createOscillator();\nosc.type = 'sine';\nosc.frequency.value = 440;\nosc.connect(audioCtx.destination);\nosc.start();\nosc.stop(audioCtx.currentTime + 1);\n\n// 3) Load/decode and play a file\nconst buf = await fetch('/path/audio.mp3').then(r => r.arrayBuffer()).then(b => audioCtx.decodeAudioData(b));\nconst src = audioCtx.createBufferSource();\nsrc.buffer = buf;\nsrc.connect(audioCtx.destination);\nsrc.start();\n\n// 4) Gain and Filter in series\nconst gain = audioCtx.createGain();\ngain.gain.value = 0.5;\nconst filter = audioCtx.createBiquadFilter();\nfilter.type = 'lowpass';\nfilter.frequency.value = 1000;\nosc.disconnect();\nosc.connect(filter).connect(gain).connect(audioCtx.destination);\n```\n\nPractical: clean up disconnected nodes; check browser support; use headphones to avoid feedback when monitoring.\n\n## 2) Echo/Delay with Effects Inside the Feedback Loop\n\nGraph (node names are exact):\n\n- Dry: `source → dryGain:GainNode → destination`\n- Wet: `source → delay:DelayNode → wetGain:GainNode → destination`\n- Feedback loop with FX: `delay → filter:BiquadFilterNode → distortion:WaveShaperNode → reverb:ConvolverNode → feedbackGain:GainNode → delay`\n\nParameters to expose\n\n- `delay.delayTime` (s), `feedbackGain.gain` (0–1, keep < 1.0)\n- `filter.type`, `filter.frequency`\n- `distortion.curve` (Float32Array)\n- `convolver.buffer` (IR AudioBuffer)\n- `wetGain.gain`, `dryGain.gain`\n\nNotes: Prevent runaway by capping feedback below 1.0; `ConvolverNode` requires a loaded impulse response; zero-delay cycles are disallowed.\n\n```js\nconst delay = audioCtx.createDelay(5.0);\nconst feedbackGain = audioCtx.createGain();\nconst filter = audioCtx.createBiquadFilter();\nconst distortion = audioCtx.createWaveShaper();\nconst reverb = audioCtx.createConvolver();\nconst wetGain = audioCtx.createGain();\nconst dryGain = audioCtx.createGain();\n\ndelay.delayTime.value = 0.35;\nfeedbackGain.gain.value = 0.5; // < 1.0\nfilter.type = 'lowpass';\nfilter.frequency.value = 8000;\n// distortion.curve = yourFloat32Curve;\n// reverb.buffer = yourImpulseResponseAudioBuffer;\nwetGain.gain.value = 0.4;\ndryGain.gain.value = 1.0;\n\n// Dry and wet\nsource.connect(dryGain).connect(audioCtx.destination);\nsource.connect(delay);\ndelay.connect(wetGain).connect(audioCtx.destination);\n\n// Feedback with FX\ndelay.connect(filter);\nfilter.connect(distortion);\ndistortion.connect(reverb);\nreverb.connect(feedbackGain);\nfeedbackGain.connect(delay);\n```\n\nHelper (load IR):\n\n```js\nasync function loadImpulseResponse(url) {\n const res = await fetch(url, { mode: 'cors' });\n if (!res.ok) throw new Error(`Failed to fetch IR ${url}: ${res.status} ${res.statusText}`);\n const ab = await res.arrayBuffer();\n try {\n return await audioCtx.decodeAudioData(ab);\n } catch (err) {\n console.error('decodeAudioData failed for IR', url, err);\n throw err; // Surface decoding/CORS-related failures clearly\n }\n}\n```\n\n## 3) Microphone Monitoring + Metronome Overlay\n\nMic capture: request permission with `navigator.mediaDevices.getUserMedia({ audio: { echoCancellation, noiseSuppression, autoGainControl } })`. Create `MediaStreamAudioSourceNode` and route to a `GainNode` → destination.\n\nMetronome: synthesize a short click (e.g., square/sine burst through a gain envelope). Schedule by audio clock at `AudioContext.currentTime` with lookahead.\n\nMix graph: `micGain + metronomeGain → master → destination`.\n\n```js\nconst master = audioCtx.createGain();\nmaster.connect(audioCtx.destination);\nconst micGain = audioCtx.createGain();\nconst metronomeGain = audioCtx.createGain();\nmicGain.connect(master);\nmetronomeGain.connect(master);\n\nasync function initMic() {\n const stream = await navigator.mediaDevices.getUserMedia({ audio: { echoCancellation: true, noiseSuppression: true, autoGainControl: false } });\n const micSrc = audioCtx.createMediaStreamSource(stream);\n micSrc.connect(micGain);\n}\n\nfunction scheduleClick(atTime, downbeat = false) {\n const osc = audioCtx.createOscillator();\n const env = audioCtx.createGain();\n osc.type = 'square';\n osc.frequency.setValueAtTime(downbeat ? 2000 : 1600, atTime);\n env.gain.setValueAtTime(0.0001, atTime);\n env.gain.exponentialRampToValueAtTime(1.0, atTime + 0.001);\n env.gain.exponentialRampToValueAtTime(0.0001, atTime + 0.03);\n osc.connect(env).connect(metronomeGain);\n osc.start(atTime);\n osc.stop(atTime + 0.05);\n // Cleanup to avoid accumulating nodes during long sessions\n osc.onended = () => {\n try { osc.disconnect(); } catch {}\n try { env.disconnect(); } catch {}\n };\n}\n\nfunction startMetronome({ bpm = 120, beatsPerBar = 4 } = {}) {\n const spb = 60 / bpm; // seconds per beat\n let next = audioCtx.currentTime + 0.1;\n let beat = 0;\n const lookaheadMs = 25, ahead = 0.2;\n const id = setInterval(() => {\n while (next < audioCtx.currentTime + ahead) {\n scheduleClick(next, beat % beatsPerBar === 0);\n next += spb; beat = (beat + 1) % beatsPerBar;\n }\n }, lookaheadMs);\n return () => clearInterval(id);\n}\n```\n\nLatency and safety: start/resume on user gesture; clean up per-tick nodes after `ended` to prevent buildup in long-running metronomes; use headphones while monitoring; mobile devices have higher base latency.\n\n## 4) Time Synchronization and Scheduling Model\n\nClocks/time domains\n\n- Master: `AudioContext.currentTime` — sample-accurate; schedule everything on this timeline.\n- UI/high-res: `performance.now()` — for UI timers and Web MIDI timestamps.\n- Mapping: capture `(tPerf0 = performance.now(), tAudio0 = audioCtx.currentTime)`, convert MIDI/perf timestamps with `tAudio = tAudio0 + (timeStamp - tPerf0)/1000`.\n- Hints: `audioCtx.baseLatency`, `audioCtx.getOutputTimestamp?.()` — estimate DAC/output delay if aligning to “heard” time.\n\nScheduling primitives\n\n- `AudioBufferSourceNode.start(when, offset?, duration?)` for one-shots/loops.\n- `AudioParam` automation (`setValueAtTime`, `linearRampToValueAtTime`, `setTargetAtTime`, `setValueCurveAtTime`).\n- Avoid `requestAnimationFrame`/`setTimeout` for timing; use an AudioWorklet for custom DSP/tight jitter when needed.\n\nTempo transport and lookahead\n\n- Tempo mapping: `secondsPerBeat = 60 / bpm`; compute bars:beats:ticks → seconds on the audio clock (choose PPQ, e.g., 480/960).\n- Lookahead window: maintain ~50–200 ms rolling schedule; enqueue with absolute `when` times in audio seconds.\n\nMulti‑channel drum machine\n\n- Pre‑decode all samples; never decode on hit.\n- Per hit: create a fresh `AudioBufferSourceNode` and call `.start(when)`.\n- For phase‑aligned layers (kick+clap, etc.), schedule all sources with the same `when` to guarantee sample‑accurate overlap.\n- Routing: per‑track `GainNode`/optional FX → master bus; allow overlapping retriggers; compute flams as small `when` offsets.\n- Pattern changes: compute the next bar boundary on the audio clock and enqueue new pattern hits relative to that time.\n\nMIDI synth playback\n\n- Live input: map `MIDIMessageEvent.timeStamp` (perf.now domain) → audio clock as above; buffer a short lookahead (5–20 ms) to reduce jitter.\n- SMF playback: convert PPQ ticks using the tempo map; schedule noteOn/noteOff separately; sustain (CC64) defers noteOff until pedal release.\n- Voice management: one voice per active note; allow overlapping envelopes; define voice‑steal policy if a polyphony cap is hit.\n\nExternal sync and drift\n\n- For MIDI Clock/MTC, derive BPM/phase from incoming ticks, convert to audio time, and drive the transport. Correct small phase error between beats with bounded micro‑nudges—avoid discontinuities.\n\n## 5) Practical Notes\n\n- User gesture required to start/resume `AudioContext` and to access the mic.\n- Convolver IRs: host with CORS if cross‑origin; decode before use.\n- Latency budget: device `baseLatency` + your lookahead + any Worklet buffering.\n- Headphones recommended for monitoring to avoid acoustic feedback.\n\n— End —", + "image-gen": "# ImgGen Component\n\n## Basic Usage\n\nThe ImgGen component can be used in three ways:\n\n1. **With no props** - Shows a form UI for users to enter a prompt and/or upload images:\n\n```jsx\nimport { ImgGen } from 'use-vibes';\n\nfunction MyComponent() {\n return ; // Shows built-in form for prompt entry and image upload\n}\n```\n\n2. **With a prompt prop** - Immediately generates an image (no form shown):\n\n```jsx\nimport { ImgGen } from 'use-vibes';\n\nfunction MyComponent() {\n return ; // Direct generation, no form\n}\n```\n\n3. **With images prop** - Edits or combines images with AI (no form shown):\n\n```jsx\nimport { ImgGen } from 'use-vibes';\n\nfunction MyComponent() {\n const [files, setFiles] = useState([]);\n return (\n \n );\n}\n```\n\n4. **With an _id prop** - Loads a specific image from the database (no form shown):\n\nIf there is no image generated for the document yet, but it has a `prompt` field, it will generate a new image with the prompt. If there an images is stored, at doc._files.original, it will use that as the base image.\n\n```jsx\nimport { ImgGen } from 'use-vibes';\n\nfunction MyComponent() {\n return ; // Loads specific image by ID\n}\n```\n\n\n## List by ID\n\nImages and prompts are tracked in a Fireproof database with a `type` of `image`. If a database is not provided, it uses `\"ImgGen\"` as the database name.\n\nDisplay stored images by their ID. Ensure you do this, so users can find the images they created.\n\n```jsx\nimport { useFireproof } from 'use-fireproof';\nimport { ImgGen } from 'use-vibes';\n\nfunction MyComponent() {\n const { database, useLiveQuery } = useFireproof(\"my-db-name\");\n const { docs: imageDocuments } = useLiveQuery('type', {\n key: 'image',\n descending: true,\n });\n\n return (\n
\n \n {imageDocuments.length > 0 && (\n
\n

Previously Generated Images

\n
    \n {imageDocuments.map((doc) => (\n
  • \n \n
  • \n ))}\n
\n
\n )}\n
\n );\n}\n```\n\n## Styling\n\nImgGen supports custom styling through CSS variables or custom class names:\n\n```jsx\n// With CSS variables in your styles\n:root {\n --imggen-text-color: #222;\n --imggen-accent: #0088ff;\n --imggen-border-radius: 8px;\n}\n\n// With custom class names\n\n```\n\n#### Props\n\n- `prompt`: Text prompt for image generation (required unless `_id` is provided)\n- `_id`: Document ID to load a specific image instead of generating a new one\n- `database`: Database name or instance to use for storing images (default: `'ImgGen'`)- `options` (object, optional): Configuration options for image generation\n - `model` (string, optional): Model to use for image generation, defaults to 'gpt-image-1'\n - `size` (string, optional): Size of the generated image (Must be one of 1024x1024, 1536x1024 (landscape), 1024x1536 (portrait), or 'auto' (default value) for gpt-image-1, and one of 256x256, 512x512, or 1024x1024 for dall-e-2.)\n - `quality` (string, optional): Quality of the generated image (high, medium and low are only supported for gpt-image-1. dall-e-2 only supports standard quality. Defaults to auto.)\n - `debug` (boolean, optional): Enable debug logging, defaults to false\n- `onLoad`: Callback when image load completes successfully\n- `onError`: Callback when image load fails, receives the error as parameter\n- `className`: CSS class name for the image element (optional)- `classes`: Object containing custom CSS classes for styling component parts (see Styling section)" + } +} \ No newline at end of file diff --git a/cli/vibes/scripts/build-plugin-data.js b/cli/vibes/scripts/build-plugin-data.js new file mode 100644 index 000000000..4b19dd9ee --- /dev/null +++ b/cli/vibes/scripts/build-plugin-data.js @@ -0,0 +1,195 @@ +#!/usr/bin/env node + +/** + * Build Plugin Data + * + * This script extracts prompt data from the vibes.diy monorepo and compiles it + * into a single JSON file that can be distributed with the Claude Code plugin. + * + * This allows the plugin to work standalone without requiring users to clone + * the entire vibes.diy repository. + */ + +const fs = require('fs'); +const path = require('path'); + +// Paths relative to the vibes.diy repository root +const REPO_ROOT = path.join(__dirname, '../../..'); +const PROMPTS_PKG = path.join(REPO_ROOT, 'prompts/pkg'); +const LLMS_DIR = path.join(PROMPTS_PKG, 'llms'); +const OUTPUT_FILE = path.join(__dirname, '../plugin-data.json'); + +console.log('Building plugin data from vibes.diy repository...\n'); + +/** + * Read a file and return its contents + */ +function readFile(filePath) { + try { + return fs.readFileSync(filePath, 'utf8'); + } catch (error) { + console.error(`Error reading ${filePath}:`, error.message); + return null; + } +} + +/** + * Extract style prompts from style-prompts.ts + * + * This uses a simple approach: since the styles don't change often, + * we hardcode them here to avoid complex TypeScript parsing. + */ +function extractStylePrompts() { + const stylePromptsPath = path.join(PROMPTS_PKG, 'style-prompts.ts'); + const content = readFile(stylePromptsPath); + + if (!content) { + console.error('Failed to read style-prompts.ts'); + return { styles: [], defaultStyle: 'brutalist web' }; + } + + // Extract default style name + const defaultMatch = content.match(/export const DEFAULT_STYLE_NAME = "([^"]+)"/); + const defaultStyle = defaultMatch ? defaultMatch[1] : 'brutalist web'; + + // Hardcoded style prompts (kept in sync with style-prompts.ts) + const styles = [ + { + name: "brutalist web", + prompt: 'Create a UI theme in a neo-brutalist style: blocky geometry, oversized controls, thick 4-12px outlines, and big bold offsets (hard shadow plates offset 6-12px bottom-right; active press reduces offset by 2-4px). Use grid/blueprint cues—graph lines, micro-dots, hatch/stipple textures—on flat matte surfaces; reserve subtle gloss only for CTAs. Background (only skeuomorphic element): grey-blue graph paper via CSS—base #f1f5f9, grid from repeating-linear-gradients in #cbd5e1/#94a3b8 at 16-24px; add a fullscreen grain layer (SVG turbulence or 1px noise PNG) at 3-6% opacity with filter: blur(0.4-0.8px) contrast(102%) brightness(101%); lock to viewport. Corner rule: components are either square (0px radius) or very rounded (50% of component height)—no in-between. Mobile-first layout: single-column flow on phones, 4/8/16/24 spacing scale, tap targets >= 48x48, sticky header + bottom nav; expand to 2-4 columns at sm >= 640 / md >= 768 / lg >= 1024 with asymmetric stacks. Maintain high contrast on light backgrounds. Secret name "Neobrute Blueprint." Use these colors: #f1f5f9 #cbd5e1 #94a3b8 #64748b #0f172a #242424 #ffffff. Never use white text; #ffffff is for surfaces only.' + }, + { + name: "memphis", + prompt: "Create a UI theme inspired by the Memphis Group and Studio Alchimia from the 1980s. Incorporate bold, playful geometric shapes (squiggles, triangles, circles), vibrant primary colors (red, blue, yellow) with contrasting pastels (pink, mint, lavender), and asymmetrical layouts. Use quirky patterns like polka dots, zigzags, and terrazzo textures. Ensure a retro-futuristic vibe with a mix of matte and glossy finishes, evoking a whimsical yet functional design. Secretly name the theme 'Memphis Alchemy' to reflect its roots in Ettore Sotsass's vision and global 1980s influences. Make sure the app background has some kind of charming patterned background using memphis styled dots or squiggly lines. Use thick \"neo-brutalism\" style borders for style to enhance legibility. Make sure to retain high contrast in your use of colors. Light background are better than dark ones. Use these colors: #70d6ff #ff70a6 #ff9770 #ffd670 #e9ff70 #242424 #ffffff Never use white text." + }, + { + name: "synthwave", + prompt: "80s digital aesthetic" + }, + { + name: "organic UI", + prompt: "natural, fluid forms" + }, + { + name: "maximalist", + prompt: "dense, decorative" + }, + { + name: "skeuomorphic", + prompt: "real-world mimics" + }, + { + name: "flat design", + prompt: "clean, 2D shapes" + }, + { + name: "bauhaus", + prompt: "geometric modernism" + }, + { + name: "glitchcore", + prompt: "decentering expectations" + }, + { + name: "paper cutout", + prompt: "layered, tactile" + }, + { + name: "viridian", + prompt: "Create a vibrant UI theme inspired by Bruce Sterling's Viridian Design Movement, embracing a futuristic green aesthetic with subtle animations and dynamic interactivity. Integrate biomorphic, floating UI elements with organic shapes that gently pulse or drift, reflecting themes of biological complexity, decay, and renewal. Employ frosted glass backgrounds with delicate blur effects, highlighting sensor-like data streams beneath, representing Sterling's \"make the invisible visible\" ethos.\n\nUse gradients and layers of soft greens accented by energetic data-inspired colors (#70d6ff, #ff70a6, #ff9770, #ffd670, #e9ff70), alongside crisp white (#ffffff) and dark contrast (#242424), ensuring legibility and visual appeal. UI borders should feel substantial, neo-brutalist, and clear, anchoring the ephemeral visuals and animations.\n\nThe background should subtly animate, evoking cellular activity, digital pulse, or ecological sensor feedback, reinforcing Viridian's fascination with tangible cyberspace and biomorphic tech aesthetics.\n\nSecretly name this theme \"Viridian Pulse\", capturing Sterling's original playful-yet-serious blend of provocative futurism and stylish eco-consciousness." + } + ]; + + console.log(`✓ Extracted ${styles.length} style prompts (default: ${defaultStyle})`); + + return { styles, defaultStyle }; +} + +/** + * Extract library documentation from llms directory + */ +function extractLibraryDocs() { + const libraries = {}; + + const libFiles = [ + { key: 'fireproof', file: 'fireproof.txt' }, + { key: 'callai', file: 'callai.txt' }, + { key: 'd3', file: 'd3.md' }, + { key: 'three-js', file: 'three-js.md' }, + { key: 'web-audio', file: 'web-audio.txt' }, + { key: 'image-gen', file: 'image-gen.txt' } + ]; + + libFiles.forEach(({ key, file }) => { + const filePath = path.join(LLMS_DIR, file); + const content = readFile(filePath); + if (content) { + libraries[key] = content.trim(); + console.log(`✓ Loaded ${file}`); + } else { + console.warn(`⚠ Could not load ${file}`); + } + }); + + return libraries; +} + +/** + * Generate core coding guidelines + * These are the fundamental patterns that every generated app should follow + */ +function generateCoreGuidelines() { + return { + react: `Use modern React practices and hooks. JavaScript only (no TypeScript). Tailwind CSS for mobile-first, accessible styling. Don't use external libraries unless essential. Keep components concise and focused.`, + + fireproof: `Use the useFireproof hook from use-fireproof to create a local-first database. Store all data as Fireproof documents with proper structure. Use useLiveQuery for real-time updates. File uploads are supported via doc._files API.`, + + callAI: `For AI integration, use callAI from call-ai package. Set stream: true for streaming responses. Use structured JSON outputs with schemas for consistent data. Save final responses as individual Fireproof documents. Example: callAI(prompt, { schema: { properties: { todos: { type: 'array', items: { type: 'string' } } } } })`, + + ui: `Include vivid app description and usage instructions in italic text at the top. If the app uses callAI with schema, include a Demo Data button. List data items on main page (make lists clickable for details). Use placeholder image APIs like https://picsum.photos/400 when images are needed.`, + + imports: `Always start components with: +import React, { useState, useEffect } from "react" +import { useFireproof } from "use-fireproof" +import { callAI } from "call-ai" + +Only add other imports when specifically requested.`, + + tailwind: `Use Tailwind CSS classes directly in JSX. Remember to use brackets like bg-[#242424] for custom colors. Mobile-first responsive design with 4/8/16/24 spacing scale. Components are either square (0px radius) or very rounded (50% of height).` + }; +} + +/** + * Main build function + */ +function build() { + console.log(`Repository root: ${REPO_ROOT}\n`); + + // Extract all data + const { styles, defaultStyle } = extractStylePrompts(); + const libraries = extractLibraryDocs(); + const coreGuidelines = generateCoreGuidelines(); + + // Compile into final structure + const pluginData = { + version: '1.0.0', + generatedAt: new Date().toISOString(), + repository: 'https://github.com/fireproof-storage/vibes.diy', + coreGuidelines, + stylePrompts: styles, + defaultStyle, + libraries + }; + + // Write to file + fs.writeFileSync(OUTPUT_FILE, JSON.stringify(pluginData, null, 2), 'utf8'); + + console.log(`\n✓ Plugin data compiled successfully!`); + console.log(` Output: ${OUTPUT_FILE}`); + console.log(` Size: ${(fs.statSync(OUTPUT_FILE).size / 1024).toFixed(2)} KB`); + console.log(` Styles: ${styles.length}`); + console.log(` Libraries: ${Object.keys(libraries).length}\n`); +} + +// Run the build +build(); diff --git a/cli/vibes/skills/vibes-generator/SKILL.md b/cli/vibes/skills/vibes-generator/SKILL.md new file mode 100644 index 000000000..a00a693ab --- /dev/null +++ b/cli/vibes/skills/vibes-generator/SKILL.md @@ -0,0 +1,287 @@ +--- +name: vibes-generator +description: Generate complete Fireproof and Vibes.diy codebases. Use when the user wants to create a new React application with Fireproof data persistence and AI integration. This skill reads the vibes.diy system prompt patterns and generates production-ready code. +--- + +# Vibes Codebase Generator + +This Skill generates complete, production-ready React applications using Fireproof for data persistence and Vibes.diy patterns. + +## Overview + +You will: +1. Read and understand the system prompt patterns from the vibes.diy codebase +2. Generate an augmented system prompt based on the user's request +3. Create a React component using that augmented prompt +4. Set up a complete Vite project structure +5. Write all files to the user's specified directory + +## Step 1: Load Plugin Data + +Read the plugin data file that contains all the prompt guidelines, style prompts, and library documentation: + +```bash +cat ${CLAUDE_PLUGIN_ROOT}/../plugin-data.json +``` + +This JSON file contains: +- `coreGuidelines`: React best practices, Fireproof patterns, callAI usage, UI patterns, imports, and Tailwind guidelines +- `stylePrompts`: Array of available style themes (name and prompt for each) +- `defaultStyle`: The default style name ("brutalist web") +- `libraries`: Documentation for fireproof, callai, d3, three-js, web-audio, and image-gen + +Parse this JSON to access the data you need for generating the system prompt. + +## Step 2: Understand the System Prompt Pattern + +Based on the plugin data JSON, the system prompt should include: + +### Core Guidelines (from `coreGuidelines` in JSON) + +Use the guidelines from the `coreGuidelines` object in plugin-data.json: +- **React**: Modern practices, hooks, JavaScript only, Tailwind CSS, no unnecessary dependencies +- **Fireproof**: useFireproof hook, document storage, useLiveQuery for real-time +- **callAI**: Streaming responses, JSON schemas, saving to Fireproof +- **UI**: App descriptions, demo data buttons, clickable lists, placeholder images +- **Imports**: Standard React, useFireproof, callAI pattern +- **Tailwind**: Direct JSX classes, custom colors with brackets, mobile-first + +### Style Guidance (from `stylePrompts` and `defaultStyle` in JSON) + +Get the default style prompt: +1. Find the style in `stylePrompts` array where `name` matches `defaultStyle` +2. Use that style's `prompt` field for styling guidance +3. The default is "brutalist web" with detailed neo-brutalist specifications + +If the user requests a specific style, search for it in the `stylePrompts` array by name. + +## Step 3: Generate the Augmented System Prompt + +Create a system prompt by combining data from the plugin-data.json: + +1. **User's app description** (from their request) +2. **Core guidelines** (from `coreGuidelines.react`, `coreGuidelines.fireproof`, `coreGuidelines.callAI`, `coreGuidelines.ui`, `coreGuidelines.imports`, `coreGuidelines.tailwind`) +3. **Style prompt** (from the matching style in `stylePrompts` array, default to `defaultStyle`) +4. **Library documentation** (from `libraries` object if user mentions specific libraries like d3, three-js, web-audio, or image-gen) + +Format it like this: + +``` +You are creating a React component for: [USER'S APP DESCRIPTION] + +[Insert coreGuidelines.react] + +[Insert coreGuidelines.fireproof] + +[Insert coreGuidelines.callAI] + +[Insert coreGuidelines.ui] + +[Insert coreGuidelines.tailwind] + +[Insert selected style prompt from stylePrompts array] + +[Insert relevant library docs from libraries object if applicable] + +IMPORTANT: You are working in one JavaScript file, use tailwind classes for styling. +Remember to use brackets like bg-[#242424] for custom colors. + +Provide a title and brief explanation followed by the component code. The component should +demonstrate proper Fireproof integration with real-time updates and proper data persistence. +``` + +## Step 4: Generate the Component Code + +Using the augmented system prompt you created, generate the React component code for `App.jsx`. + +The component should: +- Be a complete, working React component +- Use the `useFireproof` hook for data persistence +- Include proper state management +- Implement the requested functionality +- Follow the style guidelines +- Include instructional text explaining how to use the app +- Be self-contained (all code in one file) + +## Step 5: Set Up Project Structure + +Create the complete Vite project structure by: + +1. **Create project directory** at the user-specified path (e.g., `./vibes-app`) + +2. **Copy and process templates** from `${CLAUDE_PLUGIN_ROOT}/skills/vibes-generator/templates/`: + - Read each `.template` file + - Replace placeholders: + - `{{APP_NAME}}` → kebab-case version of app title (e.g., "todo-app") + - `{{APP_TITLE}}` → human-readable app title + - Write processed files (remove `.template` extension) + +3. **Create directory structure**: +``` +[output-dir]/ +├── package.json +├── vite.config.js +├── tailwind.config.js +├── postcss.config.js +├── index.html +├── .gitignore +└── src/ + ├── index.css + ├── main.jsx + └── App.jsx +``` + +## Step 6: Write Files + +Use the Write tool to create each file: + +1. **package.json** - from package.json.template with placeholders replaced +2. **vite.config.js** - from vite.config.js.template +3. **tailwind.config.js** - from tailwind.config.js.template +4. **postcss.config.js** - from postcss.config.js.template +5. **index.html** - from index.html.template with title replaced +6. **.gitignore** - from gitignore.template +7. **src/index.css** - from src/index.css.template +8. **src/main.jsx** - from main.jsx.template +9. **src/App.jsx** - the generated component code from Step 4 + +## Step 7: Provide Next Steps + +After all files are created, tell the user: + +```bash +cd [output-directory] +npm install +npm run dev +``` + +Explain that: +- The app will open at http://localhost:5173 +- It uses Fireproof for local-first data persistence +- Data persists across page reloads +- callAI is available for AI integration +- They can edit src/App.jsx and see changes immediately with hot reload + +## Important Notes + +- **Default libraries**: Always include fireproof and callai imports in the generated component +- **Single file**: The entire app is in src/App.jsx +- **No TypeScript**: Use JavaScript only +- **Tailwind classes**: Use Tailwind CSS classes directly in JSX +- **Database naming**: Use a stable, descriptive database name (e.g., "todo-app-db") +- **Error handling**: Include basic error handling for callAI calls +- **Loading states**: Show loading indicators for async operations + +## Example App.jsx Structure + +```javascript +import React, { useState, useEffect } from "react" +import { useFireproof } from "use-fireproof" +import { callAI } from "call-ai" + +export default function App() { + const { database, useLiveQuery } = useFireproof("app-name-db") + const [input, setInput] = useState("") + const [loading, setLoading] = useState(false) + + // Live query for real-time data + const result = useLiveQuery(query => query.type === "item", []) + const items = result.docs || [] + + // Function to add item with AI + const handleAdd = async () => { + setLoading(true) + try { + const response = await callAI(input, { + schema: { + properties: { + title: { type: "string" }, + description: { type: "string" } + } + } + }) + + const parsed = JSON.parse(response) + await database.put({ + type: "item", + ...parsed, + createdAt: Date.now() + }) + + setInput("") + } catch (error) { + console.error("Error:", error) + } finally { + setLoading(false) + } + } + + return ( +
+
+

App Title

+

+ App description and usage instructions... +

+ + {/* Input UI */} +
+ setInput(e.target.value)} + className="w-full p-4 border-4 border-[#242424]" + placeholder="Enter something..." + /> + +
+ + {/* List items */} +
+ {items.map(item => ( +
+

{item.title}

+

{item.description}

+
+ ))} +
+
+
+ ) +} +``` + +## Troubleshooting + +If you encounter errors: +- Ensure all template files are read successfully +- Check that the output directory path is valid +- Verify all placeholders are replaced +- Make sure the generated App.jsx has valid JSX syntax +- Confirm imports match available packages in package.json + +## Success Criteria + +- ✅ All files created in output directory +- ✅ App.jsx uses useFireproof and follows patterns +- ✅ Code is valid JavaScript/JSX +- ✅ Styling follows brutalist web theme +- ✅ Component is complete and functional +- ✅ User receives clear next steps + +## Updating Plugin Data + +The plugin-data.json file is cached locally and updated when the plugin itself is updated. If you need to manually refresh the plugin data to get the latest prompts and library documentation: + +```bash +curl -o ${CLAUDE_PLUGIN_ROOT}/../plugin-data.json \ + https://raw.githubusercontent.com/fireproof-storage/vibes.diy/main/cli/vibes/plugin-data.json +``` + +This fetches the latest version from the vibes.diy GitHub repository. diff --git a/cli/vibes/skills/vibes-generator/templates/gitignore.template b/cli/vibes/skills/vibes-generator/templates/gitignore.template new file mode 100644 index 000000000..a547bf36d --- /dev/null +++ b/cli/vibes/skills/vibes-generator/templates/gitignore.template @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/cli/vibes/skills/vibes-generator/templates/index.html.template b/cli/vibes/skills/vibes-generator/templates/index.html.template new file mode 100644 index 000000000..f5706bab3 --- /dev/null +++ b/cli/vibes/skills/vibes-generator/templates/index.html.template @@ -0,0 +1,13 @@ + + + + + + + {{APP_TITLE}} + + +
+ + + diff --git a/cli/vibes/skills/vibes-generator/templates/main.jsx.template b/cli/vibes/skills/vibes-generator/templates/main.jsx.template new file mode 100644 index 000000000..b35041b50 --- /dev/null +++ b/cli/vibes/skills/vibes-generator/templates/main.jsx.template @@ -0,0 +1,13 @@ +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; +import './index.css'; +import App from './App.jsx'; + +const rootElement = document.getElementById('root'); +if (rootElement) { + createRoot(rootElement).render( + + + + ); +} diff --git a/cli/vibes/skills/vibes-generator/templates/package.json.template b/cli/vibes/skills/vibes-generator/templates/package.json.template new file mode 100644 index 000000000..1988fef63 --- /dev/null +++ b/cli/vibes/skills/vibes-generator/templates/package.json.template @@ -0,0 +1,24 @@ +{ + "name": "{{APP_NAME}}", + "private": true, + "version": "0.0.1", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^19.1.0", + "react-dom": "^19.1.0", + "use-fireproof": "^0.23.15", + "call-ai": "^0.15.13" + }, + "devDependencies": { + "@vitejs/plugin-react": "^5.0.4", + "vite": "^7.1.9", + "tailwindcss": "^3.4.18", + "postcss": "^8.5.6", + "autoprefixer": "^10.4.21" + } +} diff --git a/cli/vibes/skills/vibes-generator/templates/postcss.config.js.template b/cli/vibes/skills/vibes-generator/templates/postcss.config.js.template new file mode 100644 index 000000000..2e7af2b7f --- /dev/null +++ b/cli/vibes/skills/vibes-generator/templates/postcss.config.js.template @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/cli/vibes/skills/vibes-generator/templates/src/index.css.template b/cli/vibes/skills/vibes-generator/templates/src/index.css.template new file mode 100644 index 000000000..b5c61c956 --- /dev/null +++ b/cli/vibes/skills/vibes-generator/templates/src/index.css.template @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/cli/vibes/skills/vibes-generator/templates/tailwind.config.js.template b/cli/vibes/skills/vibes-generator/templates/tailwind.config.js.template new file mode 100644 index 000000000..dca8ba02d --- /dev/null +++ b/cli/vibes/skills/vibes-generator/templates/tailwind.config.js.template @@ -0,0 +1,11 @@ +/** @type {import('tailwindcss').Config} */ +export default { + content: [ + "./index.html", + "./src/**/*.{js,ts,jsx,tsx}", + ], + theme: { + extend: {}, + }, + plugins: [], +} diff --git a/cli/vibes/skills/vibes-generator/templates/vite.config.js.template b/cli/vibes/skills/vibes-generator/templates/vite.config.js.template new file mode 100644 index 000000000..a3fbf6423 --- /dev/null +++ b/cli/vibes/skills/vibes-generator/templates/vite.config.js.template @@ -0,0 +1,11 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [react()], + server: { + port: 5173, + open: true + } +});