diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..8215fa1
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,14 @@
+# Algolia Configuration
+# Get these values from your Algolia dashboard: https://www.algolia.com/dashboard
+
+# Your Algolia Application ID
+ALGOLIA_APP_ID=YOUR_APP_ID
+
+# Admin API Key (for indexing script only - DO NOT expose this in client code)
+ALGOLIA_ADMIN_API_KEY=YOUR_ADMIN_API_KEY
+
+# Search-Only API Key (safe to use in client code)
+ALGOLIA_SEARCH_API_KEY=YOUR_SEARCH_API_KEY
+
+# Your Algolia index name
+ALGOLIA_INDEX_NAME=typecomposer_docs
diff --git a/ALGOLIA_SETUP.md b/ALGOLIA_SETUP.md
new file mode 100644
index 0000000..4389c5e
--- /dev/null
+++ b/ALGOLIA_SETUP.md
@@ -0,0 +1,175 @@
+# Algolia InstantSearch Integration
+
+This documentation provides instructions for setting up and configuring Algolia InstantSearch for the TypeComposer documentation.
+
+## Overview
+
+The TypeComposer docs now include Algolia InstantSearch for fast and relevant search functionality. The search is accessible through:
+
+- **Search button** in the navigation bar
+- **Keyboard shortcut**: `⌘K` (Mac) or `Ctrl+K` (Windows/Linux)
+- **ESC key** to close the search modal
+
+## Configuration
+
+### 1. Algolia Account Setup
+
+1. Create an account at [Algolia](https://www.algolia.com/)
+2. Create a new application or use an existing one
+3. Create a new index for your documentation (e.g., `typecomposer_docs`)
+
+### 2. Update Algolia Credentials
+
+Update the following constants in `/src/components/search/AlgoliaSearch.ts`:
+
+```typescript
+const ALGOLIA_APP_ID = "YOUR_APP_ID"; // Replace with your Algolia Application ID
+const ALGOLIA_SEARCH_API_KEY = "YOUR_SEARCH_API_KEY"; // Replace with your Search-Only API Key
+const ALGOLIA_INDEX_NAME = "typecomposer_docs"; // Replace with your index name
+```
+
+**Important**: Use the **Search-Only API Key** (not the Admin API Key) for client-side searches.
+
+### 3. Index Your Documentation
+
+To make your documentation searchable, you need to index it in Algolia. There are several ways to do this:
+
+#### Option A: Using Algolia Crawler (Recommended)
+
+1. Go to your Algolia dashboard
+2. Navigate to **Data sources** > **Crawler**
+3. Create a new crawler with your website URL
+4. Configure the crawler to extract:
+ - `title` - Page title
+ - `content` - Page content/text
+ - `path` - Document path for navigation
+ - `hierarchy` - Document structure (h1, h2, h3, etc.)
+
+#### Option B: Using Algolia DocSearch (For Open Source)
+
+If your project is open source and publicly available, you can apply for [Algolia DocSearch](https://docsearch.algolia.com/) which provides free search for documentation.
+
+#### Option C: Manual Indexing
+
+You can create a script to manually index your MDX content:
+
+```javascript
+import algoliasearch from 'algoliasearch';
+import fs from 'fs';
+import path from 'path';
+
+const client = algoliasearch('YOUR_APP_ID', 'YOUR_ADMIN_API_KEY');
+const index = client.initIndex('typecomposer_docs');
+
+// Read and parse your MDX files
+const contentDir = './content';
+const records = [];
+
+// Process each MDX file
+fs.readdirSync(contentDir, { recursive: true }).forEach(file => {
+ if (file.endsWith('.mdx')) {
+ const content = fs.readFileSync(path.join(contentDir, file), 'utf-8');
+
+ records.push({
+ objectID: file,
+ title: extractTitle(content),
+ content: extractContent(content),
+ path: file.replace('.mdx', '').replace('/content', '/docs'),
+ });
+ }
+});
+
+// Upload to Algolia
+index.saveObjects(records);
+```
+
+## Search Configuration
+
+### Customizing Search Behavior
+
+The search is configured in `/src/components/search/AlgoliaSearch.ts`. You can customize:
+
+- **Number of results**: Change `hitsPerPage` in the `configure` widget
+- **Search attributes**: Modify the `searchableAttributes` in your Algolia index settings
+- **Faceting**: Add filters for categories, tags, etc.
+
+### Customizing Search UI
+
+The search styles are defined in `/src/components/search/search.scss`. You can customize:
+
+- Modal appearance and positioning
+- Search input styling
+- Results display format
+- Colors to match your theme
+
+### Search Result Template
+
+The search results template is configured in the `hits` widget:
+
+```typescript
+templates: {
+ item: (hit: any, { html, components }: any) => html`
+
+ ${components.Highlight({ hit, attribute: "title" })}
+ ${components.Snippet({ hit, attribute: "content" })}
+
+ `,
+}
+```
+
+Customize this template to change how search results are displayed.
+
+## Features
+
+### Current Features
+
+- ✅ InstantSearch integration with Algolia
+- ✅ Modal-based search interface
+- ✅ Keyboard shortcuts (⌘K / Ctrl+K)
+- ✅ Highlighted search terms
+- ✅ Content snippets in results
+- ✅ Responsive design
+- ✅ Theme-aware styling (dark/light mode)
+
+### Planned Enhancements
+
+- 🔄 Autocomplete suggestions
+- 🔄 Search filters (by category, type)
+- 🔄 Recent searches
+- 🔄 Keyboard navigation in results
+
+## Troubleshooting
+
+### Search Not Working
+
+1. **Check credentials**: Ensure `ALGOLIA_APP_ID`, `ALGOLIA_SEARCH_API_KEY`, and `ALGOLIA_INDEX_NAME` are correct
+2. **Verify index**: Make sure your Algolia index contains records
+3. **Check browser console**: Look for error messages
+4. **API Key permissions**: Ensure the Search-Only API Key has the correct permissions
+
+### No Results Found
+
+1. **Index is empty**: Index your documentation content
+2. **Attribute configuration**: Ensure `searchableAttributes` in Algolia includes `title` and `content`
+3. **Path format**: Verify the `path` field in your records matches the expected format
+
+### Styling Issues
+
+1. **Theme variables**: Ensure CSS variables are defined in `/src/styles/style.scss`
+2. **Import order**: Check that search styles are imported after base styles
+3. **Specificity**: Some styles may need `!important` to override defaults
+
+## Resources
+
+- [Algolia InstantSearch Documentation](https://www.algolia.com/doc/guides/building-search-ui/what-is-instantsearch/js/)
+- [Algolia Dashboard](https://www.algolia.com/dashboard)
+- [DocSearch Program](https://docsearch.algolia.com/)
+- [Algolia API Reference](https://www.algolia.com/doc/api-reference/)
+
+## Support
+
+For issues or questions:
+
+1. Check the [Algolia community forum](https://discourse.algolia.com/)
+2. Review [InstantSearch issues on GitHub](https://github.com/algolia/instantsearch/issues)
+3. Open an issue in the TypeComposer docs repository
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..1195e3f
--- /dev/null
+++ b/README.md
@@ -0,0 +1,160 @@
+# TypeComposer Documentation
+
+Official documentation for [TypeComposer](https://github.com/typecomposer/typecomposer) - a framework for building web and native user interfaces.
+
+## Features
+
+- 📚 Comprehensive documentation for TypeComposer framework
+- 🔍 **Fast search powered by Algolia InstantSearch**
+- 🎨 Dark/Light theme support
+- 📱 Responsive design
+- 💻 Interactive playground
+- 🎯 Component examples and API reference
+
+## Getting Started
+
+### Prerequisites
+
+- Node.js 18+
+- npm or yarn
+
+### Installation
+
+```bash
+# Clone the repository
+git clone https://github.com/TypeComposer/docs.git
+cd docs
+
+# Install dependencies
+npm install
+
+# Start development server
+npm run dev
+```
+
+### Build
+
+```bash
+# Build for production
+npm run build
+
+# Preview production build
+npm run preview
+```
+
+## Search Setup
+
+This documentation includes Algolia InstantSearch for fast and relevant search functionality. See [ALGOLIA_SETUP.md](./ALGOLIA_SETUP.md) for detailed setup instructions.
+
+### Quick Setup
+
+1. Copy `.env.example` to `.env`:
+ ```bash
+ cp .env.example .env
+ ```
+
+2. Update `.env` with your Algolia credentials:
+ ```env
+ ALGOLIA_APP_ID=your_app_id
+ ALGOLIA_SEARCH_API_KEY=your_search_api_key
+ ALGOLIA_INDEX_NAME=typecomposer_docs
+ ```
+
+3. Update credentials in `/src/components/search/AlgoliaSearch.ts`
+
+4. Index your documentation:
+ ```bash
+ node scripts/index-algolia.js
+ ```
+
+### Using Search
+
+- Click the search icon in the navigation bar
+- Or use keyboard shortcut: `⌘K` (Mac) or `Ctrl+K` (Windows/Linux)
+- Press `ESC` to close the search modal
+
+## Project Structure
+
+```
+docs/
+├── content/ # MDX documentation files
+│ ├── components/ # Component documentation
+│ ├── elements/ # Element documentation
+│ └── layout/ # Layout documentation
+├── src/
+│ ├── components/ # UI components
+│ │ ├── navbar/ # Navigation bar
+│ │ ├── sidebar/ # Sidebar navigation
+│ │ └── search/ # Algolia search components
+│ ├── pages/ # Page components
+│ ├── styles/ # Global styles
+│ └── utils/ # Utility functions
+├── scripts/ # Build and utility scripts
+│ └── index-algolia.js # Algolia indexing script
+└── public/ # Static assets
+```
+
+## Technology Stack
+
+- **Framework**: [TypeComposer](https://github.com/typecomposer/typecomposer)
+- **Build Tool**: [Vite](https://vitejs.dev/)
+- **Styling**: [Tailwind CSS](https://tailwindcss.com/) + SCSS
+- **Search**: [Algolia InstantSearch](https://www.algolia.com/doc/guides/building-search-ui/what-is-instantsearch/js/)
+- **Content**: MDX (Markdown + JSX)
+- **Syntax Highlighting**: [Highlight.js](https://highlightjs.org/)
+
+## Development
+
+### Adding Documentation
+
+1. Create a new `.mdx` file in the appropriate `content/` subdirectory
+2. Add the route in `src/router/router.ts`
+3. Update sidebar navigation in `src/assets/data.json`
+4. Re-index for search: `node scripts/index-algolia.js`
+
+### Customizing Theme
+
+Theme colors are defined in `src/styles/style.scss`:
+- Light theme: `[data-theme="light"]`
+- Dark theme: `[data-theme="dark"]`
+
+### Search Customization
+
+Search UI can be customized in:
+- `/src/components/search/AlgoliaSearch.ts` - Search logic and widgets
+- `/src/components/search/SearchModal.ts` - Modal behavior
+- `/src/components/search/search.scss` - Search styles
+
+## Scripts
+
+- `npm run dev` - Start development server
+- `npm run build` - Build for production
+- `npm run preview` - Preview production build
+- `npm run clean` - Clean build cache and restart
+
+## Contributing
+
+Contributions are welcome! Please feel free to submit a Pull Request.
+
+1. Fork the repository
+2. Create your feature branch (`git checkout -b feature/AmazingFeature`)
+3. Commit your changes (`git commit -m 'Add some AmazingFeature'`)
+4. Push to the branch (`git push origin feature/AmazingFeature`)
+5. Open a Pull Request
+
+## License
+
+This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
+
+## Links
+
+- [TypeComposer Framework](https://github.com/typecomposer/typecomposer)
+- [Documentation Website](https://www.typecomposer.io)
+- [NPM Package](https://www.npmjs.com/package/typecomposer)
+- [Algolia Setup Guide](./ALGOLIA_SETUP.md)
+
+## Support
+
+- 📧 Email: support@typecomposer.io
+- 💬 Discord: [Join our community](https://discord.gg/typecomposer)
+- 🐛 Issues: [GitHub Issues](https://github.com/TypeComposer/docs/issues)
diff --git a/package-lock.json b/package-lock.json
index 60bbdc0..dfb12f9 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,7 +10,9 @@
"dependencies": {
"@codesandbox/sandpack-client": "^2.19.8",
"@mdx-js/mdx": "^3.1.1",
+ "algoliasearch": "^5.40.0",
"highlight.js": "^11.11.1",
+ "instantsearch.js": "^4.80.0",
"lucide": "^0.544.0",
"remark-gfm": "^4.0.1",
"typecomposer": "^0.1.54",
@@ -27,17 +29,214 @@
"vite": "^7.1.9"
}
},
- "node_modules/@alloc/quick-lru": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
- "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
- "dev": true,
+ "node_modules/@algolia/abtesting": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/@algolia/abtesting/-/abtesting-1.6.0.tgz",
+ "integrity": "sha512-c4M/Z/KWkEG+RHpZsWKDTTlApXu3fe4vlABNcpankWBhdMe4oPZ/r4JxEr2zKUP6K+BT66tnp8UbHmgOd/vvqQ==",
"license": "MIT",
+ "dependencies": {
+ "@algolia/client-common": "5.40.0",
+ "@algolia/requester-browser-xhr": "5.40.0",
+ "@algolia/requester-fetch": "5.40.0",
+ "@algolia/requester-node-http": "5.40.0"
+ },
"engines": {
- "node": ">=10"
+ "node": ">= 14.0.0"
+ }
+ },
+ "node_modules/@algolia/client-abtesting": {
+ "version": "5.40.0",
+ "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.40.0.tgz",
+ "integrity": "sha512-qegVlgHtmiS8m9nEsuKUVhlw1FHsIshtt5nhNnA6EYz3g+tm9+xkVZZMzkrMLPP7kpoheHJZAwz2MYnHtwFa9A==",
+ "license": "MIT",
+ "dependencies": {
+ "@algolia/client-common": "5.40.0",
+ "@algolia/requester-browser-xhr": "5.40.0",
+ "@algolia/requester-fetch": "5.40.0",
+ "@algolia/requester-node-http": "5.40.0"
},
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "engines": {
+ "node": ">= 14.0.0"
+ }
+ },
+ "node_modules/@algolia/client-analytics": {
+ "version": "5.40.0",
+ "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.40.0.tgz",
+ "integrity": "sha512-Dw2c+6KGkw7mucnnxPyyMsIGEY8+hqv6oB+viYB612OMM3l8aNaWToBZMnNvXsyP+fArwq7XGR+k3boPZyV53A==",
+ "license": "MIT",
+ "dependencies": {
+ "@algolia/client-common": "5.40.0",
+ "@algolia/requester-browser-xhr": "5.40.0",
+ "@algolia/requester-fetch": "5.40.0",
+ "@algolia/requester-node-http": "5.40.0"
+ },
+ "engines": {
+ "node": ">= 14.0.0"
+ }
+ },
+ "node_modules/@algolia/client-common": {
+ "version": "5.40.0",
+ "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.40.0.tgz",
+ "integrity": "sha512-dbE4+MJIDsTghG3hUYWBq7THhaAmqNqvW9g2vzwPf5edU4IRmuYpKtY3MMotes8/wdTasWG07XoaVhplJBlvdg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14.0.0"
+ }
+ },
+ "node_modules/@algolia/client-insights": {
+ "version": "5.40.0",
+ "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.40.0.tgz",
+ "integrity": "sha512-SH6zlROyGUCDDWg71DlCnbbZ/zEHYPZC8k901EAaBVhvY43Ju8Wa6LAcMPC4tahcDBgkG2poBy8nJZXvwEWAlQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@algolia/client-common": "5.40.0",
+ "@algolia/requester-browser-xhr": "5.40.0",
+ "@algolia/requester-fetch": "5.40.0",
+ "@algolia/requester-node-http": "5.40.0"
+ },
+ "engines": {
+ "node": ">= 14.0.0"
+ }
+ },
+ "node_modules/@algolia/client-personalization": {
+ "version": "5.40.0",
+ "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.40.0.tgz",
+ "integrity": "sha512-EgHjJEEf7CbUL9gJHI1ULmAtAFeym2cFNSAi1uwHelWgLPcnLjYW2opruPxigOV7NcetkGu+t2pcWOWmZFuvKQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@algolia/client-common": "5.40.0",
+ "@algolia/requester-browser-xhr": "5.40.0",
+ "@algolia/requester-fetch": "5.40.0",
+ "@algolia/requester-node-http": "5.40.0"
+ },
+ "engines": {
+ "node": ">= 14.0.0"
+ }
+ },
+ "node_modules/@algolia/client-query-suggestions": {
+ "version": "5.40.0",
+ "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.40.0.tgz",
+ "integrity": "sha512-HvE1jtCag95DR41tDh7cGwrMk4X0aQXPOBIhZRmsBPolMeqRJz0kvfVw8VCKvA1uuoAkjFfTG0X0IZED+rKXoA==",
+ "license": "MIT",
+ "dependencies": {
+ "@algolia/client-common": "5.40.0",
+ "@algolia/requester-browser-xhr": "5.40.0",
+ "@algolia/requester-fetch": "5.40.0",
+ "@algolia/requester-node-http": "5.40.0"
+ },
+ "engines": {
+ "node": ">= 14.0.0"
+ }
+ },
+ "node_modules/@algolia/client-search": {
+ "version": "5.40.0",
+ "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.40.0.tgz",
+ "integrity": "sha512-nlr/MMgoLNUHcfWC5Ns2ENrzKx9x51orPc6wJ8Ignv1DsrUmKm0LUih+Tj3J+kxYofzqQIQRU495d4xn3ozMbg==",
+ "license": "MIT",
+ "dependencies": {
+ "@algolia/client-common": "5.40.0",
+ "@algolia/requester-browser-xhr": "5.40.0",
+ "@algolia/requester-fetch": "5.40.0",
+ "@algolia/requester-node-http": "5.40.0"
+ },
+ "engines": {
+ "node": ">= 14.0.0"
+ }
+ },
+ "node_modules/@algolia/events": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/@algolia/events/-/events-4.0.1.tgz",
+ "integrity": "sha512-FQzvOCgoFXAbf5Y6mYozw2aj5KCJoA3m4heImceldzPSMbdyS4atVjJzXKMsfX3wnZTFYwkkt8/z8UesLHlSBQ==",
+ "license": "MIT"
+ },
+ "node_modules/@algolia/ingestion": {
+ "version": "1.40.0",
+ "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.40.0.tgz",
+ "integrity": "sha512-OfHnhE+P0f+p3i90Kmshf9Epgesw5oPV1IEUOY4Mq1HV7cQk16gvklVN1EaY/T9sVavl+Vc3g4ojlfpIwZFA4g==",
+ "license": "MIT",
+ "dependencies": {
+ "@algolia/client-common": "5.40.0",
+ "@algolia/requester-browser-xhr": "5.40.0",
+ "@algolia/requester-fetch": "5.40.0",
+ "@algolia/requester-node-http": "5.40.0"
+ },
+ "engines": {
+ "node": ">= 14.0.0"
+ }
+ },
+ "node_modules/@algolia/monitoring": {
+ "version": "1.40.0",
+ "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.40.0.tgz",
+ "integrity": "sha512-SWANV32PTKhBYvwKozeWP9HOnVabOixAuPdFFGoqtysTkkwutrtGI/rrh80tvG+BnQAmZX0vUmD/RqFZVfr/Yg==",
+ "license": "MIT",
+ "dependencies": {
+ "@algolia/client-common": "5.40.0",
+ "@algolia/requester-browser-xhr": "5.40.0",
+ "@algolia/requester-fetch": "5.40.0",
+ "@algolia/requester-node-http": "5.40.0"
+ },
+ "engines": {
+ "node": ">= 14.0.0"
+ }
+ },
+ "node_modules/@algolia/recommend": {
+ "version": "5.40.0",
+ "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.40.0.tgz",
+ "integrity": "sha512-1Qxy9I5bSb3mrhPk809DllMa561zl5hLsMR6YhIqNkqQ0OyXXQokvJ2zApSxvd39veRZZnhN+oGe+XNoNwLgkw==",
+ "license": "MIT",
+ "dependencies": {
+ "@algolia/client-common": "5.40.0",
+ "@algolia/requester-browser-xhr": "5.40.0",
+ "@algolia/requester-fetch": "5.40.0",
+ "@algolia/requester-node-http": "5.40.0"
+ },
+ "engines": {
+ "node": ">= 14.0.0"
+ }
+ },
+ "node_modules/@algolia/requester-browser-xhr": {
+ "version": "5.40.0",
+ "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.40.0.tgz",
+ "integrity": "sha512-MGt94rdHfkrVjfN/KwUfWcnaeohYbWGINrPs96f5J7ZyRYpVLF+VtPQ2FmcddFvK4gnKXSu8BAi81hiIhUpm3w==",
+ "license": "MIT",
+ "dependencies": {
+ "@algolia/client-common": "5.40.0"
+ },
+ "engines": {
+ "node": ">= 14.0.0"
+ }
+ },
+ "node_modules/@algolia/requester-fetch": {
+ "version": "5.40.0",
+ "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.40.0.tgz",
+ "integrity": "sha512-wXQ05JZZ10Dr642QVAkAZ4ZZlU+lh5r6dIBGmm9WElz+1EaQ6BNYtEOTV6pkXuFYsZpeJA89JpDOiwBOP9j24w==",
+ "license": "MIT",
+ "dependencies": {
+ "@algolia/client-common": "5.40.0"
+ },
+ "engines": {
+ "node": ">= 14.0.0"
+ }
+ },
+ "node_modules/@algolia/requester-node-http": {
+ "version": "5.40.0",
+ "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.40.0.tgz",
+ "integrity": "sha512-5qCRoySnzpbQVg2IPLGFCm4LF75pToxI5tdjOYgUMNL/um91aJ4dH3SVdBEuFlVsalxl8mh3bWPgkUmv6NpJiQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@algolia/client-common": "5.40.0"
+ },
+ "engines": {
+ "node": ">= 14.0.0"
+ }
+ },
+ "node_modules/@babel/runtime": {
+ "version": "7.28.4",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz",
+ "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
}
},
"node_modules/@codesandbox/nodebox": {
@@ -601,44 +800,6 @@
"url": "https://opencollective.com/unified"
}
},
- "node_modules/@nodelib/fs.scandir": {
- "version": "2.1.5",
- "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
- "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@nodelib/fs.stat": "2.0.5",
- "run-parallel": "^1.1.9"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/@nodelib/fs.stat": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
- "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/@nodelib/fs.walk": {
- "version": "1.2.8",
- "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
- "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@nodelib/fs.scandir": "2.1.5",
- "fastq": "^1.6.0"
- },
- "engines": {
- "node": ">= 8"
- }
- },
"node_modules/@open-draft/deferred-promise": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz",
@@ -1537,6 +1698,12 @@
"@types/ms": "*"
}
},
+ "node_modules/@types/dom-speech-recognition": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/@types/dom-speech-recognition/-/dom-speech-recognition-0.0.1.tgz",
+ "integrity": "sha512-udCxb8DvjcDKfk1WTBzDsxFbLgYxmQGKrE/ricoMqHRNjSlSUCcamVTA5lIQqzY10mY5qCY0QDwBfFEwhfoDPw==",
+ "license": "MIT"
+ },
"node_modules/@types/estree": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
@@ -1552,6 +1719,12 @@
"@types/estree": "*"
}
},
+ "node_modules/@types/google.maps": {
+ "version": "3.58.1",
+ "resolved": "https://registry.npmjs.org/@types/google.maps/-/google.maps-3.58.1.tgz",
+ "integrity": "sha512-X9QTSvGJ0nCfMzYOnaVs/k6/4L+7F5uCS+4iUmkLEls6J9S/Phv+m/i3mDeyc49ZBgwab3EFO1HEoBY7k98EGQ==",
+ "license": "MIT"
+ },
"node_modules/@types/hast": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz",
@@ -1561,6 +1734,12 @@
"@types/unist": "*"
}
},
+ "node_modules/@types/hogan.js": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/@types/hogan.js/-/hogan.js-3.0.5.tgz",
+ "integrity": "sha512-/uRaY3HGPWyLqOyhgvW9Aa43BNnLZrNeQxl2p8wqId4UHMfPKolSB+U7BlZyO1ng7MkLnyEAItsBzCG0SDhqrA==",
+ "license": "MIT"
+ },
"node_modules/@types/mdast": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz",
@@ -1592,6 +1771,12 @@
"undici-types": "~7.14.0"
}
},
+ "node_modules/@types/qs": {
+ "version": "6.14.0",
+ "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz",
+ "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==",
+ "license": "MIT"
+ },
"node_modules/@types/unist": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
@@ -1604,6 +1789,12 @@
"integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
"license": "ISC"
},
+ "node_modules/abbrev": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
+ "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
+ "license": "ISC"
+ },
"node_modules/acorn": {
"version": "8.15.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
@@ -1625,6 +1816,43 @@
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
}
},
+ "node_modules/algoliasearch": {
+ "version": "5.40.0",
+ "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.40.0.tgz",
+ "integrity": "sha512-a9aIL2E3Z7uYUPMCmjMFFd5MWhn+ccTubEvnMy7rOTZCB62dXBJtz0R5BZ/TPuX3R9ocBsgWuAbGWQ+Ph4Fmlg==",
+ "license": "MIT",
+ "dependencies": {
+ "@algolia/abtesting": "1.6.0",
+ "@algolia/client-abtesting": "5.40.0",
+ "@algolia/client-analytics": "5.40.0",
+ "@algolia/client-common": "5.40.0",
+ "@algolia/client-insights": "5.40.0",
+ "@algolia/client-personalization": "5.40.0",
+ "@algolia/client-query-suggestions": "5.40.0",
+ "@algolia/client-search": "5.40.0",
+ "@algolia/ingestion": "1.40.0",
+ "@algolia/monitoring": "1.40.0",
+ "@algolia/recommend": "5.40.0",
+ "@algolia/requester-browser-xhr": "5.40.0",
+ "@algolia/requester-fetch": "5.40.0",
+ "@algolia/requester-node-http": "5.40.0"
+ },
+ "engines": {
+ "node": ">= 14.0.0"
+ }
+ },
+ "node_modules/algoliasearch-helper": {
+ "version": "3.26.0",
+ "resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.26.0.tgz",
+ "integrity": "sha512-Rv2x3GXleQ3ygwhkhJubhhYGsICmShLAiqtUuJTUkr9uOCOXyF2E71LVT4XDnVffbknv8XgScP4U0Oxtgm+hIw==",
+ "license": "MIT",
+ "dependencies": {
+ "@algolia/events": "^4.0.1"
+ },
+ "peerDependencies": {
+ "algoliasearch": ">= 3.1 < 6"
+ }
+ },
"node_modules/astring": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/astring/-/astring-1.9.0.tgz",
@@ -1682,13 +1910,6 @@
"url": "https://github.com/sponsors/wooorm"
}
},
- "node_modules/balanced-match": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
- "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
@@ -1790,16 +2011,6 @@
"ieee754": "^1.2.1"
}
},
- "node_modules/camelcase-css": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
- "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 6"
- }
- },
"node_modules/caniuse-lite": {
"version": "1.0.30001749",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001749.tgz",
@@ -1992,19 +2203,6 @@
"url": "https://github.com/sponsors/wooorm"
}
},
- "node_modules/dompurify": {
- "version": "3.1.7",
- "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.1.7.tgz",
- "integrity": "sha512-VaTstWtsneJY8xzy7DekmYWEOZcmzIe3Qb3zPd4STve1OBTa+e+WmS1ITQec1fZYXI3HCsOZZiSMpG6oxoWMWQ==",
- "license": "(MPL-2.0 OR Apache-2.0)"
- },
- "node_modules/dlv": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
- "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/dotenv": {
"version": "16.6.1",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
@@ -2017,13 +2215,6 @@
"url": "https://dotenvx.com"
}
},
- "node_modules/eastasianwidth": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
- "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/electron-to-chromium": {
"version": "1.5.234",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.234.tgz",
@@ -2368,6 +2559,24 @@
"node": ">=12.0.0"
}
},
+ "node_modules/hogan.js": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/hogan.js/-/hogan.js-3.0.2.tgz",
+ "integrity": "sha512-RqGs4wavGYJWE07t35JQccByczmNUXQT0E12ZYV1VKYu5UiAU9lsos/yBAcf840+zrUQQxgVduCR5/B8nNtibg==",
+ "dependencies": {
+ "mkdirp": "0.3.0",
+ "nopt": "1.0.10"
+ },
+ "bin": {
+ "hulk": "bin/hulk"
+ }
+ },
+ "node_modules/htm": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/htm/-/htm-3.1.1.tgz",
+ "integrity": "sha512-983Vyg8NwUE7JkZ6NmOqpCZ+sh1bKv2iYTlUkzlWmA5JD2acKoxd4KVxbMmxX/85mtfdnDmTFoNKcg5DGAvxNQ==",
+ "license": "Apache-2.0"
+ },
"node_modules/ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
@@ -2401,6 +2610,38 @@
"integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==",
"license": "MIT"
},
+ "node_modules/instantsearch-ui-components": {
+ "version": "0.11.2",
+ "resolved": "https://registry.npmjs.org/instantsearch-ui-components/-/instantsearch-ui-components-0.11.2.tgz",
+ "integrity": "sha512-XxwqUY6NifxSvHYfyfJRiGhqYqHXQFcFNOEjNyFptB9HOkx14yCdayKar4BZxGx353FnFD6b4Z8LdzbGud+RFA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.27.6"
+ }
+ },
+ "node_modules/instantsearch.js": {
+ "version": "4.80.0",
+ "resolved": "https://registry.npmjs.org/instantsearch.js/-/instantsearch.js-4.80.0.tgz",
+ "integrity": "sha512-l2HTW6+tBs2hGrby43Y7HbWayTifFFKk/ikRU5BXFpCigG6CRAYZLE1NFifAskaj3SVQiY8O8B/T6hJyeMsScA==",
+ "license": "MIT",
+ "dependencies": {
+ "@algolia/events": "^4.0.1",
+ "@types/dom-speech-recognition": "^0.0.1",
+ "@types/google.maps": "^3.55.12",
+ "@types/hogan.js": "^3.0.0",
+ "@types/qs": "^6.5.3",
+ "algoliasearch-helper": "3.26.0",
+ "hogan.js": "^3.0.2",
+ "htm": "^3.0.0",
+ "instantsearch-ui-components": "0.11.2",
+ "preact": "^10.10.0",
+ "qs": "^6.5.1 < 6.10",
+ "search-insights": "^2.17.2"
+ },
+ "peerDependencies": {
+ "algoliasearch": ">= 3.1 < 6"
+ }
+ },
"node_modules/is-alphabetical": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz",
@@ -2740,9 +2981,9 @@
}
},
"node_modules/lucide": {
- "version": "0.545.0",
- "resolved": "https://registry.npmjs.org/lucide/-/lucide-0.545.0.tgz",
- "integrity": "sha512-mrBH0upkb1TH8ZLkf0XERQAKMdTJ1C+Offr7eSvanmdQ7JrV8jkrFw5jgRAS8fygiZgG4X9mfEg/BXCL+mNMRw==",
+ "version": "0.544.0",
+ "resolved": "https://registry.npmjs.org/lucide/-/lucide-0.544.0.tgz",
+ "integrity": "sha512-U5ORwr5z9Sx7bNTDFaW55RbjVdQEnAcT3vws9uz3vRT1G4XXJUDAhRZdxhFoIyHEvjmTkzzlEhjSLYM5n4mb5w==",
"license": "ISC"
},
"node_modules/magic-string": {
@@ -2777,18 +3018,6 @@
"url": "https://github.com/sponsors/wooorm"
}
},
- "node_modules/marked": {
- "version": "14.0.0",
- "resolved": "https://registry.npmjs.org/marked/-/marked-14.0.0.tgz",
- "integrity": "sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==",
- "license": "MIT",
- "bin": {
- "marked": "bin/marked.js"
- },
- "engines": {
- "node": ">= 18"
- }
- },
"node_modules/mdast-util-find-and-replace": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz",
@@ -3841,6 +4070,29 @@
"node": ">=16 || 14 >=14.17"
}
},
+ "node_modules/minizlib": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz",
+ "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "minipass": "^7.1.2"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/mkdirp": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz",
+ "integrity": "sha512-OHsdUcVAQ6pOtg5JYWpCBo9W/GySVuwvP9hueRMW7UqshC0tbfzLv8wjySTPm3tfUZ/21CE9E1pJagOA91Pxew==",
+ "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)",
+ "license": "MIT/X11",
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -3879,49 +4131,37 @@
"dev": true,
"license": "MIT"
},
- "node_modules/normalize-range": {
- "version": "0.1.2",
- "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz",
- "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==",
- "dev": true,
+ "node_modules/nopt": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz",
+ "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==",
"license": "MIT",
+ "dependencies": {
+ "abbrev": "1"
+ },
+ "bin": {
+ "nopt": "bin/nopt.js"
+ },
"engines": {
- "node": ">=0.10.0"
+ "node": "*"
}
},
- "node_modules/object-assign": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
- "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "node_modules/normalize-range": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz",
+ "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
- "node_modules/object-hash": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
- "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 6"
- }
- },
"node_modules/outvariant": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.0.tgz",
"integrity": "sha512-AlWY719RF02ujitly7Kk/0QlV+pXGFDHrHf9O2OKqyqgBieaPOIeuSkL8sRK6j2WK+/ZAURq2kZsY0d8JapUiw==",
"license": "MIT"
},
- "node_modules/package-json-from-dist": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
- "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
- "dev": true,
- "license": "BlueOak-1.0.0"
- },
"node_modules/parse-entities": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz",
@@ -4007,6 +4247,16 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/preact": {
+ "version": "10.27.2",
+ "resolved": "https://registry.npmjs.org/preact/-/preact-10.27.2.tgz",
+ "integrity": "sha512-5SYSgFKSyhCbk6SrXyMpqjb5+MQBgfvEKE/OC+PujcY34sOpqtr+0AZQtPYx5IA6VxynQ7rUPCtKzyovpj9Bpg==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/preact"
+ }
+ },
"node_modules/property-information": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz",
@@ -4017,6 +4267,18 @@
"url": "https://github.com/sponsors/wooorm"
}
},
+ "node_modules/qs": {
+ "version": "6.9.7",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.7.tgz",
+ "integrity": "sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/readdirp": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
@@ -4255,6 +4517,12 @@
"@parcel/watcher": "^2.4.1"
}
},
+ "node_modules/search-insights": {
+ "version": "2.17.3",
+ "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.17.3.tgz",
+ "integrity": "sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==",
+ "license": "MIT"
+ },
"node_modules/source-map": {
"version": "0.7.6",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz",
diff --git a/package.json b/package.json
index 9e0c2d5..e5a11cf 100644
--- a/package.json
+++ b/package.json
@@ -14,19 +14,21 @@
"dependencies": {
"@codesandbox/sandpack-client": "^2.19.8",
"@mdx-js/mdx": "^3.1.1",
+ "algoliasearch": "^5.40.0",
"highlight.js": "^11.11.1",
+ "instantsearch.js": "^4.80.0",
"lucide": "^0.544.0",
"remark-gfm": "^4.0.1",
"typecomposer": "^0.1.54",
"typecomposer-plugin": "^1.0.10"
},
"devDependencies": {
+ "@tailwindcss/vite": "^4.1.14",
"@types/node": "^24.7.1",
"autoprefixer": "^10.4.21",
"csstype": "^3.1.3",
"sass": "^1.93.2",
"tailwindcss": "^4.1.14",
- "@tailwindcss/vite": "^4.1.14",
"typescript": "^5.9.3",
"vite": "^7.1.9"
}
diff --git a/scripts/index-algolia.js b/scripts/index-algolia.js
new file mode 100755
index 0000000..36b662a
--- /dev/null
+++ b/scripts/index-algolia.js
@@ -0,0 +1,177 @@
+#!/usr/bin/env node
+
+/**
+ * Algolia Documentation Indexer
+ *
+ * This script indexes MDX documentation files to Algolia for search functionality.
+ *
+ * Usage:
+ * 1. Install dependencies: npm install algoliasearch
+ * 2. Set environment variables:
+ * - ALGOLIA_APP_ID
+ * - ALGOLIA_ADMIN_API_KEY (use Admin API Key, not Search-Only)
+ * - ALGOLIA_INDEX_NAME
+ * 3. Run: node scripts/index-algolia.js
+ */
+
+import algoliasearch from 'algoliasearch';
+import fs from 'fs';
+import path from 'path';
+import { fileURLToPath } from 'url';
+
+const __filename = fileURLToPath(import.meta.URL);
+const __dirname = path.dirname(__filename);
+
+// Algolia configuration from environment variables
+const ALGOLIA_APP_ID = process.env.ALGOLIA_APP_ID;
+const ALGOLIA_ADMIN_API_KEY = process.env.ALGOLIA_ADMIN_API_KEY;
+const ALGOLIA_INDEX_NAME = process.env.ALGOLIA_INDEX_NAME || 'typecomposer_docs';
+
+if (!ALGOLIA_APP_ID || !ALGOLIA_ADMIN_API_KEY) {
+ console.error('❌ Missing required environment variables:');
+ console.error(' - ALGOLIA_APP_ID');
+ console.error(' - ALGOLIA_ADMIN_API_KEY');
+ console.error('\nPlease set these environment variables and try again.');
+ process.exit(1);
+}
+
+// Initialize Algolia client
+const client = algoliasearch(ALGOLIA_APP_ID, ALGOLIA_ADMIN_API_KEY);
+const index = client.initIndex(ALGOLIA_INDEX_NAME);
+
+/**
+ * Extract title from MDX content
+ */
+function extractTitle(content) {
+ const titleMatch = content.match(/^#\s+(.+)$/m);
+ return titleMatch ? titleMatch[1].trim() : 'Untitled';
+}
+
+/**
+ * Extract plain text content from MDX
+ */
+function extractContent(content) {
+ // Remove MDX/MD syntax and code blocks
+ let text = content
+ .replace(/```[\s\S]*?```/g, '') // Remove code blocks
+ .replace(/`[^`]+`/g, '') // Remove inline code
+ .replace(/^#+\s+/gm, '') // Remove headers
+ .replace(/\[([^\]]+)\]\([^)]+\)/g, '$1') // Convert links to text
+ .replace(/[*_~`]/g, '') // Remove formatting
+ .replace(/\n+/g, ' ') // Replace newlines with spaces
+ .trim();
+
+ // Limit content length for better search results
+ if (text.length > 500) {
+ text = text.substring(0, 500) + '...';
+ }
+
+ return text;
+}
+
+/**
+ * Extract headings structure from MDX
+ */
+function extractHierarchy(content) {
+ const headings = content.match(/^#+\s+.+$/gm) || [];
+ return headings.map(h => {
+ const level = h.match(/^#+/)[0].length;
+ const text = h.replace(/^#+\s+/, '').trim();
+ return { level, text };
+ });
+}
+
+/**
+ * Recursively read all MDX files from a directory
+ */
+function getMDXFiles(dir, fileList = []) {
+ const files = fs.readdirSync(dir);
+
+ files.forEach(file => {
+ const filePath = path.join(dir, file);
+ const stat = fs.statSync(filePath);
+
+ if (stat.isDirectory()) {
+ getMDXFiles(filePath, fileList);
+ } else if (file.endsWith('.mdx')) {
+ fileList.push(filePath);
+ }
+ });
+
+ return fileList;
+}
+
+/**
+ * Process MDX files and create Algolia records
+ */
+async function indexDocumentation() {
+ console.log('🔍 Starting documentation indexing...\n');
+
+ const contentDir = path.join(__dirname, '..', 'content');
+ const mdxFiles = getMDXFiles(contentDir);
+
+ console.log(`📚 Found ${mdxFiles.length} MDX files\n`);
+
+ const records = [];
+
+ mdxFiles.forEach((filePath, index) => {
+ const content = fs.readFileSync(filePath, 'utf-8');
+ const relativePath = path.relative(contentDir, filePath);
+ const docPath = relativePath.replace('.mdx', '').toLowerCase();
+
+ const title = extractTitle(content);
+ const textContent = extractContent(content);
+ const hierarchy = extractHierarchy(content);
+
+ records.push({
+ objectID: docPath,
+ title,
+ content: textContent,
+ path: docPath,
+ hierarchy: hierarchy,
+ url: `#/docs/${docPath}`,
+ });
+
+ console.log(` ✓ Processed: ${relativePath}`);
+ });
+
+ console.log('\n📤 Uploading records to Algolia...\n');
+
+ try {
+ // Clear existing records
+ await index.clearObjects();
+ console.log(' ✓ Cleared existing index');
+
+ // Upload new records
+ const { objectIDs } = await index.saveObjects(records);
+ console.log(` ✓ Uploaded ${objectIDs.length} records`);
+
+ // Configure index settings
+ await index.setSettings({
+ searchableAttributes: [
+ 'title',
+ 'hierarchy.text',
+ 'content',
+ ],
+ attributesToHighlight: ['title', 'content'],
+ attributesToSnippet: ['content:30'],
+ highlightPreTag: '',
+ highlightPostTag: '',
+ });
+ console.log(' ✓ Configured index settings');
+
+ console.log('\n✅ Indexing complete!\n');
+ console.log(` Index name: ${ALGOLIA_INDEX_NAME}`);
+ console.log(` Total records: ${objectIDs.length}\n`);
+
+ } catch (error) {
+ console.error('❌ Error indexing documentation:', error.message);
+ process.exit(1);
+ }
+}
+
+// Run indexing
+indexDocumentation().catch(error => {
+ console.error('❌ Fatal error:', error);
+ process.exit(1);
+});
diff --git a/src/components/navbar/NavBar.ts b/src/components/navbar/NavBar.ts
index a3dd030..a1aaabc 100644
--- a/src/components/navbar/NavBar.ts
+++ b/src/components/navbar/NavBar.ts
@@ -1,5 +1,7 @@
import { AnchorElement, Component, DivElement, ImageElement, Router, SvgElement, App, ref, computed, ButtonElement, BorderPanel } from "typecomposer";
-import { Sun, Moon, createElement, Menu } from "lucide";
+import { Sun, Moon, createElement, Menu, Search } from "lucide";
+import { SearchModal } from "@/components/search/SearchModal";
+import "@/components/search/search.scss";
class ThemeToggle extends Component {
constructor() {
@@ -34,17 +36,35 @@ class NavLinks extends Component {
this.append(new AnchorElement({ rlink: "docs", text: "Docs" }));
this.append(new AnchorElement({ rlink: "playground", text: "Playground" }));
this.append(new AnchorElement({ text: "GitHub", href: "https://github.com/typecomposer/typecomposer" }));
- this.append(new ThemeToggle());
}
}
export class NavBar extends Component {
open = false;
+ private searchModal: SearchModal;
constructor() {
super({ className: "flex items-center justify-between w-screen h-16 px-6 navbar" });
this.append(new Logo());
this.append(new NavLinks());
+
+ // Add search button
+ this.append(
+ new ButtonElement({
+ className: "search-button flex items-center gap-2 px-3 py-2 rounded-md",
+ children: [
+ createElement(Search),
+ new DivElement({
+ className: "search-shortcut hidden md:block",
+ textContent: "⌘K"
+ })
+ ],
+ onclick: () => this.searchModal.open(),
+ })
+ );
+
+ this.append(new ThemeToggle());
+
this.append(
new ButtonElement({
className: "btn-sidebar m-2",
@@ -56,6 +76,10 @@ export class NavBar extends Component {
})
);
this.btn();
+
+ // Add search modal
+ this.searchModal = new SearchModal();
+ this.append(this.searchModal);
}
btn() {
diff --git a/src/components/search/AlgoliaSearch.ts b/src/components/search/AlgoliaSearch.ts
new file mode 100644
index 0000000..84a7300
--- /dev/null
+++ b/src/components/search/AlgoliaSearch.ts
@@ -0,0 +1,119 @@
+import { Component, DivElement, InputElement } from "typecomposer";
+import { liteClient as algoliasearch } from "algoliasearch/lite";
+import instantsearch from "instantsearch.js";
+import { searchBox, hits, configure } from "instantsearch.js/es/widgets";
+
+// Algolia credentials - these should be replaced with actual values
+const ALGOLIA_APP_ID = "YOUR_APP_ID";
+const ALGOLIA_SEARCH_API_KEY = "YOUR_SEARCH_API_KEY";
+const ALGOLIA_INDEX_NAME = "typecomposer_docs";
+
+// Instance counter for unique IDs - safe in browser's single-threaded environment
+let instanceCounter = 0;
+
+export class AlgoliaSearch extends Component {
+ private searchInstance: any;
+ private searchContainer: DivElement;
+ private searchBoxDiv: DivElement;
+ private hitsDiv: DivElement;
+ private searchBoxId: string;
+ private hitsId: string;
+
+ constructor() {
+ super({
+ className: "algolia-search-container",
+ });
+
+ // Generate unique IDs for this instance
+ const instanceId = ++instanceCounter;
+ this.searchBoxId = `searchbox-${instanceId}`;
+ this.hitsId = `hits-${instanceId}`;
+
+ // Create container for search
+ this.searchContainer = new DivElement({
+ className: "search-wrapper",
+ });
+
+ // Create search elements with unique IDs
+ this.searchBoxDiv = new DivElement({ id: this.searchBoxId });
+ this.hitsDiv = new DivElement({ id: this.hitsId });
+
+ this.searchContainer.append(this.searchBoxDiv, this.hitsDiv);
+ this.append(this.searchContainer);
+ }
+
+ onInit(): void {
+ // Use requestAnimationFrame to ensure DOM is ready before initializing InstantSearch
+ // This ensures the search container elements are mounted before InstantSearch tries to use them
+ requestAnimationFrame(() => {
+ // Verify DOM elements exist before initializing
+ if (this.searchBoxDiv && this.hitsDiv) {
+ this.initializeSearch();
+ }
+ });
+ }
+
+ private initializeSearch(): void {
+ // Initialize Algolia search client
+ const searchClient = algoliasearch(ALGOLIA_APP_ID, ALGOLIA_SEARCH_API_KEY);
+
+ // Create InstantSearch instance
+ this.searchInstance = instantsearch({
+ indexName: ALGOLIA_INDEX_NAME,
+ searchClient,
+ insights: false,
+ });
+
+ // Configure search widgets
+ this.searchInstance.addWidgets([
+ configure({
+ hitsPerPage: 8,
+ }),
+
+ searchBox({
+ container: `#${this.searchBoxId}`,
+ placeholder: "Search documentation...",
+ showReset: true,
+ showSubmit: false,
+ cssClasses: {
+ root: "search-box-root",
+ form: "search-box-form",
+ input: "search-box-input",
+ reset: "search-box-reset",
+ },
+ }),
+
+ hits({
+ container: `#${this.hitsId}`,
+ cssClasses: {
+ root: "hits-root",
+ list: "hits-list",
+ item: "hits-item",
+ },
+ templates: {
+ item: (hit: any, { html, components }: any) => html`
+
+ ${components.Highlight({ hit, attribute: "title" })}
+ ${components.Snippet({ hit, attribute: "content" })}
+
+ `,
+ empty: (results: any, { html }: any) => html`
+
+ No results found for ${results.query}.
+
+ `,
+ },
+ }),
+ ]);
+
+ // Start InstantSearch
+ this.searchInstance.start();
+ }
+
+ onDestroy(): void {
+ // Clean up InstantSearch instance when component is destroyed
+ if (this.searchInstance) {
+ this.searchInstance.dispose();
+ }
+ }
+}
diff --git a/src/components/search/SearchModal.ts b/src/components/search/SearchModal.ts
new file mode 100644
index 0000000..6c08613
--- /dev/null
+++ b/src/components/search/SearchModal.ts
@@ -0,0 +1,80 @@
+import { Component, DivElement, ButtonElement } from "typecomposer";
+import { X, createElement } from "lucide";
+import { AlgoliaSearch } from "./AlgoliaSearch";
+
+export class SearchModal extends Component {
+ private modal: DivElement;
+ private backdrop: DivElement;
+ private searchComponent: AlgoliaSearch;
+
+ constructor() {
+ super({
+ className: "search-modal-wrapper",
+ style: {
+ display: "none",
+ },
+ });
+
+ // Create backdrop
+ this.backdrop = new DivElement({
+ className: "search-modal-backdrop",
+ onclick: () => this.close(),
+ });
+
+ // Create modal
+ this.modal = new DivElement({
+ className: "search-modal",
+ });
+
+ // Create close button
+ const closeButton = new ButtonElement({
+ className: "search-modal-close",
+ children: [createElement(X)],
+ onclick: () => this.close(),
+ });
+
+ // Create search component
+ this.searchComponent = new AlgoliaSearch();
+
+ this.modal.append(closeButton, this.searchComponent);
+ this.append(this.backdrop, this.modal);
+ }
+
+ open(): void {
+ this.style.display = "flex";
+ document.body.style.overflow = "hidden";
+
+ // Focus on search input after a small delay
+ setTimeout(() => {
+ const searchInput = this.querySelector(".ais-SearchBox-input");
+ searchInput?.focus();
+ }, 100);
+ }
+
+ close(): void {
+ this.style.display = "none";
+ document.body.style.overflow = "";
+ }
+
+ // Handle keyboard shortcuts
+ onInit(): void {
+ const handleKeydown = (e: KeyboardEvent) => {
+ // Open search with Cmd/Ctrl + K
+ if ((e.metaKey || e.ctrlKey) && e.key === "k") {
+ e.preventDefault();
+ this.open();
+ }
+ // Close search with Escape
+ if (e.key === "Escape" && this.style.display === "flex") {
+ this.close();
+ }
+ };
+
+ document.addEventListener("keydown", handleKeydown);
+
+ // Clean up event listener on destroy
+ this.addEventListener("destroy", () => {
+ document.removeEventListener("keydown", handleKeydown);
+ });
+ }
+}
diff --git a/src/components/search/search.scss b/src/components/search/search.scss
new file mode 100644
index 0000000..f1ebe37
--- /dev/null
+++ b/src/components/search/search.scss
@@ -0,0 +1,226 @@
+/* Algolia Search Styles */
+
+/* Search Modal Wrapper */
+.search-modal-wrapper {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ z-index: 9999;
+ align-items: flex-start;
+ justify-content: center;
+ padding-top: 10vh;
+}
+
+.search-modal-backdrop {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: rgba(0, 0, 0, 0.5);
+ backdrop-filter: blur(4px);
+}
+
+.search-modal {
+ position: relative;
+ width: 90%;
+ max-width: 640px;
+ max-height: 80vh;
+ background-color: var(--primary-background-color);
+ border-radius: 12px;
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
+ overflow: hidden;
+ display: flex;
+ flex-direction: column;
+}
+
+.search-modal-close {
+ position: absolute;
+ top: 16px;
+ right: 16px;
+ width: 32px;
+ height: 32px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: transparent;
+ border: none;
+ color: var(--text-secondary);
+ cursor: pointer;
+ border-radius: 6px;
+ transition: all 0.2s;
+ z-index: 10;
+}
+
+.search-modal-close:hover {
+ background-color: var(--secondary-background-color);
+ color: var(--text-primary);
+}
+
+/* Search Button in NavBar */
+.search-button {
+ background: var(--secondary-background-color);
+ border: 1px solid var(--border-color);
+ color: var(--text-secondary);
+ cursor: pointer;
+ transition: all 0.2s;
+}
+
+.search-button:hover {
+ background: var(--primary-background-color);
+ border-color: var(--text-primary);
+ color: var(--text-primary);
+}
+
+.search-shortcut {
+ font-size: 12px;
+ padding: 2px 6px;
+ background: var(--primary-background-color);
+ border: 1px solid var(--border-color);
+ border-radius: 4px;
+ font-family: monospace;
+}
+
+/* Algolia Search Container */
+.algolia-search-container {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+}
+
+.search-wrapper {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+}
+
+/* Search Box Customization */
+.ais-SearchBox {
+ padding: 16px;
+ border-bottom: 1px solid var(--border-color);
+}
+
+.ais-SearchBox-form {
+ position: relative;
+}
+
+.ais-SearchBox-input {
+ width: 100%;
+ padding: 12px 40px 12px 16px;
+ font-size: 16px;
+ border: 1px solid var(--border-color);
+ border-radius: 8px;
+ background-color: var(--secondary-background-color);
+ color: var(--text-primary);
+ outline: none;
+ transition: all 0.2s;
+}
+
+.ais-SearchBox-input:focus {
+ border-color: var(--accent-color, #007bff);
+ box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.1);
+}
+
+.ais-SearchBox-input::placeholder {
+ color: var(--text-secondary);
+}
+
+.ais-SearchBox-reset {
+ position: absolute;
+ right: 12px;
+ top: 50%;
+ transform: translateY(-50%);
+ background: transparent;
+ border: none;
+ color: var(--text-secondary);
+ cursor: pointer;
+ padding: 4px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.ais-SearchBox-reset:hover {
+ color: var(--text-primary);
+}
+
+.ais-SearchBox-resetIcon {
+ width: 16px;
+ height: 16px;
+}
+
+/* Hits (Results) Customization */
+.ais-Hits {
+ flex: 1;
+ overflow-y: auto;
+ padding: 8px;
+}
+
+.ais-Hits-list {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+}
+
+.ais-Hits-item {
+ margin-bottom: 4px;
+}
+
+.hit-link {
+ display: block;
+ padding: 12px 16px;
+ border-radius: 8px;
+ text-decoration: none;
+ color: var(--text-primary);
+ transition: background-color 0.2s;
+}
+
+.hit-link:hover {
+ background-color: var(--secondary-background-color);
+}
+
+.hit-title {
+ font-size: 16px;
+ font-weight: 600;
+ margin-bottom: 4px;
+ color: var(--text-primary);
+}
+
+.hit-content {
+ font-size: 14px;
+ color: var(--text-secondary);
+ line-height: 1.5;
+}
+
+.hits-empty {
+ padding: 32px 16px;
+ text-align: center;
+ color: var(--text-secondary);
+}
+
+/* Highlight styles */
+.ais-Highlight-highlighted,
+.ais-Snippet-highlighted {
+ background-color: rgba(255, 215, 0, 0.3);
+ font-weight: 600;
+ font-style: normal;
+}
+
+/* Responsive adjustments */
+@media (max-width: 768px) {
+ .search-modal {
+ width: 95%;
+ max-height: 90vh;
+ }
+
+ .search-modal-wrapper {
+ padding-top: 5vh;
+ }
+
+ .search-shortcut {
+ display: none !important;
+ }
+}