diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index 4deca22..61259ce 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -31,6 +31,9 @@ jobs: - name: πŸ“¦ Install dependencies run: pnpm install + - name: πŸ” Lint + run: pnpm run lint + - name: 🎭 Install Playwright browsers run: pnpm exec playwright install --with-deps diff --git a/.gitignore b/.gitignore index de5f65b..7e00e5a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ node_modules/ dist/ +public/blog/ .idea .vscode .DS_Store diff --git a/README.md b/README.md index ff19a3e..c05d9db 100644 --- a/README.md +++ b/README.md @@ -1,48 +1,72 @@ -# Astro Starter Kit: Basics +# Explainer -```sh -npm create astro@latest -- --template basics -``` +A documentation framework built on Astro 5 and React 19. Write your docs in MDX, get a fast static site with built-in search, blog, SEO, and dark mode. -[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/astro/tree/latest/examples/basics) -[![Open with CodeSandbox](https://assets.codesandbox.io/github/button-edit-lime.svg)](https://codesandbox.io/p/sandbox/github/withastro/astro/tree/latest/examples/basics) -[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/withastro/astro?devcontainer_path=.devcontainer/basics/devcontainer.json) +## Features -> πŸ§‘β€πŸš€ **Seasoned astronaut?** Delete this file. Have fun! +| Category | Feature | Description | +| ------------- | --------------------- | --------------------------------------------------------- | +| Documentation | Multi-section docs | Nested sidebar with collapsible sections and doc switcher | +| Documentation | Custom MDX directives | `::component{attr="value"}` syntax for rich content | +| Documentation | 8+ MDX components | Callout, Card, Code Group, Code Preview, Step, and more | +| Documentation | Mermaid diagrams | Rendered at build time via rehype-mermaid | +| Blog | Full blog system | Tags, drafts, author profiles, publication dates | +| Blog | RSS feed | Auto-generated at `/rss.xml` | +| Navigation | Integrated search | Command palette (Cmd+K) with fuzzy filtering | +| Navigation | Instant transitions | Astro View Transitions with persistent sidebar | +| Navigation | Breadcrumbs | Auto-generated page hierarchy | +| Code | Syntax highlighting | Shiki dual-theme (light/dark) with 60+ language icons | +| Code | Code transformers | Diff, line highlight, focus, word highlight, error levels | +| Theming | Dark mode | Light, dark, and system preference with localStorage | +| Theming | Tailwind CSS v4 | OKLCH color system with shadcn/ui components | +| SEO | Meta tags | Open Graph, Twitter Cards, canonical URLs | +| SEO | Sitemap | Auto-generated via @astrojs/sitemap | +| SEO | OG images | Auto-generated at build time (Satori + Resvg) | +| Accessibility | Standards | Skip link, prefers-reduced-motion, keyboard navigation | +| Accessibility | Custom 404 | Branded error page with navigation links | +| DX | Linting & formatting | Biome with strict TypeScript | +| DX | CI/CD | GitHub Actions β€” lint, build, deploy to Cloudflare Pages | +| DX | Docker | Multi-stage build with Nginx | -![just-the-basics](https://github.com/withastro/astro/assets/2244813/a0a5533c-a856-4198-8470-2d67b1d7c554) +## Tech Stack -## πŸš€ Project Structure +Astro 5 / React 19 / MDX / TypeScript / Tailwind CSS v4 / shadcn/ui + Radix UI / Shiki / Biome / Cloudflare Pages -Inside of your Astro project, you'll see the following folders and files: +## Getting Started -```text -/ -β”œβ”€β”€ public/ -β”‚ └── favicon.svg -β”œβ”€β”€ src/ -β”‚ β”œβ”€β”€ layouts/ -β”‚ β”‚ └── Layout.astro -β”‚ └── pages/ -β”‚ └── index.astro -└── package.json +```sh +pnpm install +pnpm dev ``` -To learn more about the folder structure of an Astro project, refer to [our guide on project structure](https://docs.astro.build/en/basics/project-structure/). +Open [http://localhost:4321](http://localhost:4321). + +| Command | Action | +| --------------- | -------------------------------- | +| `pnpm dev` | Start dev server | +| `pnpm build` | Build for production (`./dist/`) | +| `pnpm preview` | Preview production build | +| `pnpm lint` | Check code with Biome | +| `pnpm lint:fix` | Fix linting issues | +| `pnpm format` | Format code with Biome | + +## Configuration + +All settings are centralized in `explainer.config.ts` via `defineExplainerConfig()`: + +- **Project** β€” name, repository URL +- **SEO** β€” title, description, default thumbnail +- **Socials** β€” GitHub, Twitter, LinkedIn links +- **Blog** β€” default thumbnail, author profiles +- **Navbar** β€” custom navigation links +- **Content** β€” MDX component mappings, 60+ language icon mappings -## 🧞 Commands +## Deployment -All commands are run from the root of the project, from a terminal: +**Cloudflare Pages** β€” Automatic via GitHub Actions on push to `main` (lint + build + deploy). -| Command | Action | -| :------------------------ | :----------------------------------------------- | -| `npm install` | Installs dependencies | -| `npm run dev` | Starts local dev server at `localhost:4321` | -| `npm run build` | Build your production site to `./dist/` | -| `npm run preview` | Preview your build locally, before deploying | -| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` | -| `npm run astro -- --help` | Get help using the Astro CLI | +**Docker** β€” `docker build -t explainer . && docker run -p 8080:8080 explainer` (Node 20 Alpine + Nginx 1.28 Alpine). -## πŸ‘€ Want to learn more? +## License -Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat). +See [LICENSE](LICENSE) for details. diff --git a/astro.config.mjs b/astro.config.mjs index 6186e82..ee3a791 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -16,6 +16,7 @@ import icon from "astro-icon"; import rehypeMermaid from "rehype-mermaid"; import remarkBlockParser from "./src/lib/plugins/parser/plugin"; import remarkDirectivePkg from "remark-directive"; +import remarkStripLayout from "./src/lib/plugins/remark-strip-layout"; import sitemap from "@astrojs/sitemap"; import { buildDocIntegration } from "./src/hooks/build-doc"; import transformerMetaLabel from "./src/lib/plugins/shiki/transformer-meta-label"; @@ -46,7 +47,7 @@ export default defineConfig({ type: "shiki", excludeLangs: ["mermaid"], }, - remarkPlugins: [remarkDirectivePkg, remarkBlockParser], + remarkPlugins: [remarkDirectivePkg, remarkBlockParser, remarkStripLayout], rehypePlugins: [rehypeMermaid], }, diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..57e3b6e --- /dev/null +++ b/biome.json @@ -0,0 +1,40 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", + "vcs": { + "enabled": true, + "clientKind": "git", + "useIgnoreFile": true + }, + "files": { + "ignore": ["dist", "node_modules", ".astro", "*.astro"] + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 2, + "lineWidth": 100 + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "correctness": { + "noUnusedImports": "warn", + "noUnusedVariables": "warn" + }, + "style": { + "noNonNullAssertion": "off" + }, + "suspicious": { + "noExplicitAny": "warn" + } + } + }, + "javascript": { + "formatter": { + "quoteStyle": "double", + "semicolons": "always", + "trailingCommas": "all" + } + } +} diff --git a/content/blog/index.mdx b/content/blog/index.mdx index 42206c3..39d95f0 100644 --- a/content/blog/index.mdx +++ b/content/blog/index.mdx @@ -1,6 +1,7 @@ --- title: Blog description: Lorem ipsum +layout: blog-wrapper --- import config from "../../explainer.config"; @@ -16,10 +17,10 @@ import config from "../../explainer.config";

Anim aute id magna aliqua ad ad non deserunt sunt. Qui irure qui lorem - cupidatat commodo. Elit sunt amet fugiat veniam occaecat fugiat. + cupidatat commodo. Elit sont amet fugiat veniam occaecat fugiat.

-::articles +::articles{perLine=3} diff --git a/content/blog/welcome.mdx b/content/blog/welcome.mdx index f0416f2..52cefa7 100644 --- a/content/blog/welcome.mdx +++ b/content/blog/welcome.mdx @@ -1,5 +1,5 @@ --- -title: Discover Explainer +title: Discover Explainer fondations and use markdown description: "Discover Explainer is a tool that helps you create beautiful, responsive, and accessible web documentation using Astro. It's built with Astro, Tailwind CSS, and TypeScript." permalink: welcome authors: diff --git a/content/blog/whats-new-2.0.mdx b/content/blog/whats-new-2.0.mdx new file mode 100644 index 0000000..e8b83b6 --- /dev/null +++ b/content/blog/whats-new-2.0.mdx @@ -0,0 +1,157 @@ +--- +title: What's new in Explainer 2.0 +description: "A major update bringing new components, improved navigation, auto-generated thumbnails, and a polished design system." +permalink: whats-new-2.0 +authors: + - leadcode_dev +publishedAt: "2026-02-23" +--- + +# What's new in Explainer 2.0 + +We're excited to announce a major update to Explainer that brings a host of new features, components, and design improvements. Here's a summary of everything that's changed. + +:::callout{variant="info"} +This release includes breaking changes. If you're upgrading from a previous version, please review each section carefully. +::: + +--- + +## New landing page + +Explainer now ships with a fully designed landing page β€” ready to use out of the box. + +::::card-group +:::card{title="Hero Section" icon="lucide:sparkles"} +A bold, animated hero with primary and secondary call-to-action buttons. +::: +:::card{title="Features Grid" icon="lucide:layout-grid"} +Showcase your project's key features in a responsive grid layout. +::: +:::card{title="Testimonials" icon="lucide:message-circle"} +Display user feedback with a clean, rotating testimonial section. +::: +:::card{title="Blog Section" icon="lucide:newspaper"} +Automatically pull your latest blog posts onto the homepage. +::: +:::: + +--- + +## New MDX components + +::::card-group +:::card{title="Step Groups" icon="lucide:list-ordered"} +Guide readers through multi-step processes with a clear, numbered layout. +::: +:::card{title="Code Previews" icon="lucide:eye"} +Show live rendered output alongside source code β€” perfect for documenting UI components. +::: +:::card{title="Blockquote" icon="lucide:quote"} +Styled citations with optional author attribution using the `β€”` convention. +::: +:::card{title="Code Groups" icon="lucide:columns-2"} +Tabbed code blocks to present multiple files or languages side by side. +::: +:::card{title="Card Groups" icon="lucide:layout-grid"} +Display feature cards in a responsive grid with icons and descriptions. +::: +:::card{title="Articles" icon="lucide:newspaper"} +Embed a blog article listing directly inside your documentation pages. +::: +:::: + +--- + +## Heading anchor links + +All `h2` and `h3` headings now generate clickable anchor links. Hover over any heading to reveal the `#` symbol, click it to update the URL, and share a direct link to that section. + +> The best documentation is the one you can link to directly. +> +> β€” The Explainer Team + +--- + +## Improved navigation + +::::step-group +:::step{title="Doc Switcher"} +A redesigned documentation switcher in the sidebar with descriptions and active state indicators. +::: +:::step{title="Navbar Dropdowns"} +The navbar now supports dropdown menus with icons and descriptions for richer navigation. +::: +:::step{title="Full-text Search"} +The search bar now indexes all documentation content for full-text search across your docs. +::: +:::step{title="Previous / Next"} +Navigate between documentation pages with contextual links at the bottom of each page. +::: +:::: + +--- + +## Auto-generated thumbnails + +Blog posts and documentation pages now get automatically generated Open Graph thumbnails during build. Powered by Satori, each thumbnail features your project's primary color, the page title, and a clean dark design. + +:::callout{variant="success"} +No manual image creation needed β€” thumbnails are generated at build time and served as static assets. +::: + +--- + +## Documentation restructuring + +The documentation now supports multiple doc collections with a clean switcher. Direct pages at the root of a collection are also supported alongside nested folder structures. + +:::codegroup + +```txt [Before] +content/ + docs/ + getting-started/ + installation.mdx + configuration.mdx +``` + +```txt [After] +content/ + docs/ + framework/ + getting-started/ + installation.mdx + documentation/ + demo.mdx +``` + +::: + +--- + +## Design polish + +:::callout{variant="warning"} +If you customized the color tokens in your `global.css`, make sure to update them to match the new naming convention. +::: + +| Feature | Status | +| ---------------- | -------- | +| Landing page | New | +| Step groups | New | +| Code previews | New | +| Blockquote | New | +| Card groups | New | +| Heading anchors | New | +| Doc switcher | Improved | +| Navbar dropdowns | Improved | +| Search | Improved | +| Thumbnails | Improved | +| Dark mode | Improved | + +--- + +## Getting started + +Ready to try it out? Head over to the [Getting Started](/docs/framework/getting-started/getting-started) guide to set up your own Explainer documentation. diff --git a/content/docs/documentation/_default.mdx b/content/docs/documentation/_default.mdx index cf1cde0..5f7c109 100644 --- a/content/docs/documentation/_default.mdx +++ b/content/docs/documentation/_default.mdx @@ -1,10 +1,9 @@ --- -label: Documentation -directory: documentation +label: Demo +directory: demo description: Lorem ipsum dolor sit amet, consectetur adipiscing elit. -permalink: framework +permalink: demo icon: lucide:cuboid collection: - - getting-started - - foundamentals + - demo --- diff --git a/content/docs/documentation/demo.mdx b/content/docs/documentation/demo.mdx new file mode 100644 index 0000000..559dd52 --- /dev/null +++ b/content/docs/documentation/demo.mdx @@ -0,0 +1,164 @@ +--- +title: Demo +description: Lorem ipsum dolor sit amet, consectetur adipiscing elit. +permalink: demo +icon: lucide:tag +--- + +# Demo + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris. + +## Getting started + +Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident. + +:::callout{variant="info"} +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae. +::: + +--- + +## Installation + +Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam. + +::::step-group +:::step{title="Install dependencies"} +Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit. + +```bash +pnpm install +``` + +::: +:::step{title="Configure the project"} +Sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. + +```ts [explainer.config.ts] +import { defineExplainerConfig } from "@/utils"; + +export default defineExplainerConfig({ + projectName: "My Project", + seo: { + title: "My Project", + description: "Lorem ipsum dolor sit amet.", + thumbnail: "https://placehold.co/1200x630", + }, +}); +``` + +::: +:::step{title="Start the dev server"} +Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet. + +```bash +pnpm dev +``` + +::: +:::: + +--- + +## Code examples + +Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur. + +:::codegroup + +```ts [server.ts] +import express from "express"; + +const app = express(); + +app.get("/", (req, res) => { + res.json({ message: "Hello World" }); +}); + +app.listen(3000); +``` + +```ts [client.ts] +const response = await fetch("http://localhost:3000"); +const data = await response.json(); + +console.log(data.message); +``` + +::: + +### Highlighted code + +At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium voluptatum. + +```rs [entities/user.rs] +pub struct User { + pub firstname: String, // [!code highlight] + pub lastname: String, + pub email: String, // [!code highlight] +} + +impl User { + pub fn new() -> Self { // [!code --] + pub fn new(firstname: String, lastname: String, email: String) -> Self { // [!code ++] + Self { firstname, lastname, email } + } +} +``` + +--- + +## Features overview + +Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet. + +::::card-group +:::card{title="Fast" icon="lucide:zap"} +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore. +::: +:::card{title="Flexible" icon="lucide:settings"} +Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip. +::: +:::card{title="Extensible" icon="lucide:puzzle"} +Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat. +::: +:::: + +--- + +## Important notes + +Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores. + +:::callout{variant="info"} +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore. +::: + +:::callout{variant="success"} +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore. +::: + +:::callout{variant="warning"} +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore. +::: + +:::callout{variant="danger"} +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore. +::: + +--- + +## Summary + +| Feature | Description | Status | +| ---------- | ---------------------------- | ------ | +| Routing | File-based routing system | Stable | +| Components | Built-in MDX components | Stable | +| Theming | Light and dark mode support | Stable | +| Search | Full-text search across docs | Beta | +| Blog | Integrated blog system | Stable | + +> Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. +> +> β€” John Doe diff --git a/content/docs/documentation/getting-started/configuration.mdx b/content/docs/documentation/getting-started/configuration.mdx deleted file mode 100644 index 665e992..0000000 --- a/content/docs/documentation/getting-started/configuration.mdx +++ /dev/null @@ -1,58 +0,0 @@ ---- -title: Configuration -description: Lorem ipsum dolor sit amet, consectetur adipiscing elit. -permalink: configuration -icon: lucide:settings-2 -visibility: - - navbar ---- - -# Configuration - -The project configuration is simple and centralized in the `explainer.config.ts` file. - -This file uses the `defineExplainerConfig` function to define all the configurations for the site. Here is an overview of the different configuration sections available: - -```ts [explainer.config.ts] -import { defineExplainerConfig } from "@/utils"; - -export default defineExplainerConfig({ - repository: "https://github.com/LeadcodeDev/explainer", - projectName: "Explainer", - seo: { - title: "Explainer β€’ Make your own documentation easily", - description: - "Quickly design your documentation and optimise it for search engine optimisation to showcase your products.", - thumbnail: "https://placehold.co/1200x630", - }, - socials: { - media: { - github: "https://github.com/LeadcodeDev/explainer", - twitter: "https://twitter.com/LeadcodeDev", - linkedin: "https://linkedin.com/in/leadcode-dev", - }, - }, - blog: { - defaults: { - thumbnail: "https://placehold.co/1200x630", - }, - authors: { - leadcode_dev: { - name: "LeadcodeDev", - avatar: "https://avatars.githubusercontent.com/u/8946317?v=4", - href: "https://github.com/LeadcodeDev", - }, - }, - }, - navbar: [ - { - label: "API", - href: "/api", - }, - { - label: "Blog", - href: "/blog", - }, - ], -}); -``` diff --git a/content/docs/framework/_default.mdx b/content/docs/framework/_default.mdx new file mode 100644 index 0000000..812bfde --- /dev/null +++ b/content/docs/framework/_default.mdx @@ -0,0 +1,10 @@ +--- +label: Framework +directory: documentation +description: Lorem ipsum dolor sit amet, consectetur adipiscing elit. +permalink: framework +icon: lucide:book +collection: + - getting-started + - foundamentals +--- diff --git a/content/docs/documentation/foundamentals/_default.mdx b/content/docs/framework/foundamentals/_default.mdx similarity index 100% rename from content/docs/documentation/foundamentals/_default.mdx rename to content/docs/framework/foundamentals/_default.mdx diff --git a/content/docs/documentation/foundamentals/blog.mdx b/content/docs/framework/foundamentals/blog.mdx similarity index 92% rename from content/docs/documentation/foundamentals/blog.mdx rename to content/docs/framework/foundamentals/blog.mdx index 07c6067..8121d5e 100644 --- a/content/docs/documentation/foundamentals/blog.mdx +++ b/content/docs/framework/foundamentals/blog.mdx @@ -19,12 +19,13 @@ To create a new article, you need to create a new markdown file in the `content/ You should use the frontmatter on top of the file to define the article metadata. -> [!WARNING] -> The `permalink` property is required. If you modify it, the URL of the article will change. +:::callout{variant="warning"} +The `permalink` property is required. If you modify it, the URL of the article will change. +::: -:::codegroup labels=[frontmatter, schema] +:::codegroup -```mdx +```mdx [frontmatter] --- title: "Article title" description: "Article description" @@ -37,7 +38,7 @@ publishedAt: 2024-01-01:23:00:00 --- ``` -```ts +```ts [schema] const schema = z.object({ title: z.string(), description: z.string(), @@ -55,8 +56,9 @@ const schema = z.object({ The article will be visible on the blog page if the `publishedAt` date is defined and is in the future. -> [!NOTE] -> You can also remove or comment the `publishedAt` date to unpublish the article. +:::callout{variant="warning"} +You can also remove or comment the `publishedAt` date to unpublish the article. +::: In the blog index page, the articles are sorted by `publishedAt` date and displayed in descending order. diff --git a/content/docs/documentation/foundamentals/components/_default.mdx b/content/docs/framework/foundamentals/components/_default.mdx similarity index 100% rename from content/docs/documentation/foundamentals/components/_default.mdx rename to content/docs/framework/foundamentals/components/_default.mdx diff --git a/content/docs/documentation/foundamentals/components/callout.mdx b/content/docs/framework/foundamentals/components/callout.mdx similarity index 100% rename from content/docs/documentation/foundamentals/components/callout.mdx rename to content/docs/framework/foundamentals/components/callout.mdx diff --git a/content/docs/documentation/foundamentals/components/card.mdx b/content/docs/framework/foundamentals/components/card.mdx similarity index 96% rename from content/docs/documentation/foundamentals/components/card.mdx rename to content/docs/framework/foundamentals/components/card.mdx index 9ec2631..36d0583 100644 --- a/content/docs/documentation/foundamentals/components/card.mdx +++ b/content/docs/framework/foundamentals/components/card.mdx @@ -51,8 +51,9 @@ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor i You can customize the number of elements per line by setting the `cols` prop. -> [!note] -> Default card per line was fixed to 2 +:::callout{variant="info"} +Default card per line was fixed to 2 +::: ::::::preview @@ -143,8 +144,9 @@ Vous pouvez personaliser chacune de vos cartes en utilisant les props suivantes - `label`: Le texte Γ  afficher en haut de la carte. (required) - `icon`: L'icΓ΄ne Γ  afficher en haut de la carte. -> [!note] -> The cards use the [`iconify`](https://icon-sets.iconify.design/) library to display icons. +:::callout{variant="info"} +The cards use the [`iconify`](https://icon-sets.iconify.design/) library to display icons. +::: ::::::preview diff --git a/content/docs/documentation/foundamentals/components/code-block.mdx b/content/docs/framework/foundamentals/components/code-block.mdx similarity index 97% rename from content/docs/documentation/foundamentals/components/code-block.mdx rename to content/docs/framework/foundamentals/components/code-block.mdx index 7c083df..f8a4562 100644 --- a/content/docs/documentation/foundamentals/components/code-block.mdx +++ b/content/docs/framework/foundamentals/components/code-block.mdx @@ -261,8 +261,9 @@ impl User { ## Group code blocks -> [!warning] This is a **collapsible** callout -> Only code blocks can be used in a group. +:::callout{variant="warning"} +This is a **collapsible** callout. Only code blocks can be used in a group. +::: :::::preview ::::code-preview diff --git a/content/docs/documentation/foundamentals/components/code-preview.mdx b/content/docs/framework/foundamentals/components/code-preview.mdx similarity index 100% rename from content/docs/documentation/foundamentals/components/code-preview.mdx rename to content/docs/framework/foundamentals/components/code-preview.mdx diff --git a/content/docs/documentation/foundamentals/components/markdown.mdx b/content/docs/framework/foundamentals/components/markdown.mdx similarity index 100% rename from content/docs/documentation/foundamentals/components/markdown.mdx rename to content/docs/framework/foundamentals/components/markdown.mdx diff --git a/content/docs/documentation/foundamentals/components/step.mdx b/content/docs/framework/foundamentals/components/step.mdx similarity index 100% rename from content/docs/documentation/foundamentals/components/step.mdx rename to content/docs/framework/foundamentals/components/step.mdx diff --git a/content/docs/documentation/foundamentals/components/text.mdx b/content/docs/framework/foundamentals/components/text.mdx similarity index 76% rename from content/docs/documentation/foundamentals/components/text.mdx rename to content/docs/framework/foundamentals/components/text.mdx index e240ca1..5ebc4b2 100644 --- a/content/docs/documentation/foundamentals/components/text.mdx +++ b/content/docs/framework/foundamentals/components/text.mdx @@ -79,6 +79,30 @@ _Italic text_ ::: :::: +## Blockquote + +Use the `>` syntax to create a styled citation. Add an author with a line starting by `β€”`. + +::::preview +:::code-preview + +> Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium. +> +> β€” John Doe + +::: + +:::content-preview + +```mdx +> Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium. +> +> β€” John Doe +``` + +::: +:::: + ## Headers You can create headers from level 1 (largest) to level 6 (smallest): diff --git a/content/docs/documentation/foundamentals/docs.mdx b/content/docs/framework/foundamentals/docs.mdx similarity index 100% rename from content/docs/documentation/foundamentals/docs.mdx rename to content/docs/framework/foundamentals/docs.mdx diff --git a/content/docs/documentation/foundamentals/routing.mdx b/content/docs/framework/foundamentals/routing.mdx similarity index 100% rename from content/docs/documentation/foundamentals/routing.mdx rename to content/docs/framework/foundamentals/routing.mdx diff --git a/content/docs/documentation/getting-started/_default.mdx b/content/docs/framework/getting-started/_default.mdx similarity index 100% rename from content/docs/documentation/getting-started/_default.mdx rename to content/docs/framework/getting-started/_default.mdx diff --git a/content/docs/framework/getting-started/configuration.mdx b/content/docs/framework/getting-started/configuration.mdx new file mode 100644 index 0000000..39fa56e --- /dev/null +++ b/content/docs/framework/getting-started/configuration.mdx @@ -0,0 +1,126 @@ +--- +title: Configuration +description: Lorem ipsum dolor sit amet, consectetur adipiscing elit. +permalink: configuration +icon: lucide:settings-2 +visibility: + - navbar +--- + +# Configuration + +The project configuration is simple and centralized in the `explainer.config.ts` file. + +This file uses the `defineExplainerConfig` function to define all the configurations for the site. Here is an overview of the different configuration sections available: + +```ts [explainer.config.ts] +import { defineExplainerConfig } from "@/utils"; + +export default defineExplainerConfig({ + repository: "https://github.com/LeadcodeDev/explainer", + projectName: "Explainer", + seo: { + title: "Explainer β€’ Make your own documentation easily", + description: + "Quickly design your documentation and optimise it for search engine optimisation to showcase your products.", + thumbnail: "https://placehold.co/1200x630", + }, + socials: { + media: { + github: "https://github.com/LeadcodeDev/explainer", + twitter: "https://twitter.com/LeadcodeDev", + linkedin: "https://linkedin.com/in/leadcode-dev", + }, + }, + blog: { + defaults: { + thumbnail: "https://placehold.co/1200x630", + }, + authors: { + leadcode_dev: { + name: "LeadcodeDev", + avatar: "https://avatars.githubusercontent.com/u/8946317?v=4", + href: "https://github.com/LeadcodeDev", + }, + }, + }, + navbar: [ + { + label: "API", + href: "/api", + }, + { + label: "Blog", + href: "/blog", + }, + ], +}); +``` + +--- + +## Navbar + +The `navbar` array supports two types of entries: **simple links** and **dropdown menus**. + +### Simple link + +A simple link navigates directly to a page. You can optionally add an icon. + +```ts +navbar: [ + { + label: "Blog", + href: "/blog", + icon: "lucide:newspaper", // optional + }, +], +``` + +| Property | Type | Description | +| -------- | -------- | --------------------------------- | +| `label` | `string` | Display text | +| `href` | `string` | Target URL | +| `icon` | `string` | Iconify icon identifier (optional)| + +### Dropdown + +A dropdown entry displays a collapsible menu with a list of sub-items. Each sub-item can have its own label, description, icon and link. + +```ts +navbar: [ + { + label: "Resources", + icon: "lucide:book", + children: [ + { + label: "API Reference", + description: "Full API docs", + icon: "mdi:code-braces", + href: "/api", + }, + { + label: "Changelog", + description: "What's new", + icon: "mdi:history", + href: "/changelog", + }, + ], + }, +], +``` + +| Property | Type | Description | +| ---------- | -------- | ------------------------------------ | +| `label` | `string` | Display text for the trigger | +| `icon` | `string` | Iconify icon on the trigger (optional)| +| `children` | `array` | List of sub-items | + +Each child item accepts the following properties: + +| Property | Type | Description | +| ------------- | -------- | ------------------------------------ | +| `label` | `string` | Display text | +| `description` | `string` | Short description shown below label (optional) | +| `icon` | `string` | Iconify icon identifier (optional) | +| `href` | `string` | Target URL | diff --git a/content/docs/documentation/getting-started/deploy.mdx b/content/docs/framework/getting-started/deploy.mdx similarity index 100% rename from content/docs/documentation/getting-started/deploy.mdx rename to content/docs/framework/getting-started/deploy.mdx diff --git a/content/docs/documentation/getting-started/getting-started.mdx b/content/docs/framework/getting-started/getting-started.mdx similarity index 100% rename from content/docs/documentation/getting-started/getting-started.mdx rename to content/docs/framework/getting-started/getting-started.mdx diff --git a/content/docs/documentation/getting-started/installation.mdx b/content/docs/framework/getting-started/installation.mdx similarity index 100% rename from content/docs/documentation/getting-started/installation.mdx rename to content/docs/framework/getting-started/installation.mdx diff --git a/content/docs/documentation/getting-started/project-structure.mdx b/content/docs/framework/getting-started/project-structure.mdx similarity index 100% rename from content/docs/documentation/getting-started/project-structure.mdx rename to content/docs/framework/getting-started/project-structure.mdx diff --git a/content/index.mdx b/content/index.mdx index 848382a..590fdc4 100644 --- a/content/index.mdx +++ b/content/index.mdx @@ -1,162 +1,9 @@ --- --- -import { BackgroundAnimation } from "@/components/ui/background-animation"; -import { Icon } from "@iconify/react"; -import { Button } from "@/components/ui/button"; -import { Badge } from "@/components/ui/badge"; - -
-
- -
-
-
- -

- Explain your ideas with Markdown -

-
- Quickly design your documentation and optimise it for search engine - optimisation to showcase your products. - - Make elegant documentation for your project with Explainer. -
-
- - -
-
-
-
-
-
-
-
- Built with - Astro - and - React -
-
- Optimised yet open to modification, Explainer gives you - everything you need to create perfect documentation. -
-
-
-
- -
-
-
- -
-
-
-
-
- Powered by Mineral -
-
- Easy to use and customisable, Explainer provides you with a multitude - of tools to enable you to create your documentation quickly without - wasting time on details; only your content matters. -
-
-
- Magnifique visuel propulsΓ© par UI -
-
- -
-
- -
-
-
-
-
- -
-
-
-

- Follow our blog for updates and news -

-

- Learn more about our latest developments. We share tips and tricks to - help you succeed.og posts and follow us -

-
-
- -
- ::articles -
-
+::hero-section +::features-section +::highlights-section +::testimonials-section +::cta-section +::blog-section diff --git a/explainer.config.ts b/explainer.config.ts index bbc7cba..2d89342 100644 --- a/explainer.config.ts +++ b/explainer.config.ts @@ -1,3 +1,4 @@ +import Blockquote from "@/components/content/blockquote/blockquote.astro"; import Callout from "@/components/content/callout.astro"; import CardGroup from "@/components/content/card-group/card-group.astro"; import Card from "@/components/content/card-group/card.astro"; @@ -6,6 +7,8 @@ import CodePreview from "@/components/content/code-preview/code-preview.astro"; import ContentPreview from "@/components/content/code-preview/content-preview.astro"; import Preview from "@/components/content/code-preview/preview.astro"; import CodeBlock from "@/components/content/codeblock.astro"; +import H2 from "@/components/content/heading/h2.astro"; +import H3 from "@/components/content/heading/h3.astro"; import StepGroup from "@/components/content/step-group/step-group.astro"; import Step from "@/components/content/step-group/step.astro"; import Articles from "@/components/elements/articles.astro"; @@ -40,6 +43,48 @@ export default defineExplainerConfig({ }, }, navbar: [ + { + label: "Documentations", + icon: "lucide:book", + children: [ + { + label: "Getting Started", + description: "Introduction to Explainer", + icon: "lucide:info", + href: "/docs/framework/getting-started/getting-started", + }, + { + label: "Installation", + description: "Install and set up the project", + icon: "lucide:play", + href: "/docs/framework/getting-started/installation", + }, + { + label: "Project Structure", + description: "Understand the folder layout", + icon: "lucide:layout-panel-top", + href: "/docs/framework/getting-started/project-structure", + }, + { + label: "Configuration", + description: "Configure your site", + icon: "lucide:settings-2", + href: "/docs/framework/getting-started/configuration", + }, + { + label: "Deployment", + description: "Deploy your documentation", + icon: "lucide:cloud-lightning", + href: "/docs/framework/getting-started/deploy", + }, + { + label: "Routing", + description: "File-based routing system", + icon: "lucide:signpost", + href: "/docs/framework/foundamentals/routing", + }, + ], + }, { label: "API", href: "/api", @@ -63,6 +108,9 @@ export default defineExplainerConfig({ "content-preview": ContentPreview, preview: Preview, articles: Articles, + blockquote: Blockquote, + h2: H2, + h3: H3, }, icons: { markdown: "devicon:markdown", @@ -82,6 +130,7 @@ export default defineExplainerConfig({ npx: "devicon:npm", yarn: "devicon:yarn", pnpm: "devicon:pnpm", + cargo: "catppuccin:cargo", bun: "devicon:bun", vite: "devicon:vite", "tailwind.config.js": "devicon:tailwindcss", diff --git a/package.json b/package.json index 80a65a7..78c1ec1 100644 --- a/package.json +++ b/package.json @@ -6,13 +6,16 @@ "dev": "astro dev", "build": "astro build", "preview": "astro preview", - "astro": "astro" + "astro": "astro", + "lint": "biome check src/", + "lint:fix": "biome check --write src/", + "format": "biome format --write src/" }, "dependencies": { "@astrojs/mdx": "^4.3.12", "@astrojs/react": "^4.4.2", + "@astrojs/rss": "^4.0.11", "@astrojs/sitemap": "^3.6.0", - "@expressive-code/plugin-line-numbers": "^0.40.2", "@iconify-json/devicon": "^1.2.23", "@iconify/react": "^6.0.0", "@lucide/astro": "^0.488.0", @@ -24,39 +27,20 @@ "@resvg/resvg-js": "^2.6.2", "@scalar/api-reference-react": "^0.6.19", "@tailwindcss/vite": "^4.0.17", - "@types/culori": "^4.0.1", - "@types/hast": "^3.0.4", - "@types/luxon": "^3.6.2", - "@types/mdast": "^4.0.4", - "@types/node": "^24.10.0", - "@types/react": "^19.0.12", - "@types/react-dom": "^19.0.4", "astro": "^5.16.3", "astro-icon": "^1.1.5", - "autoprefixer": "^10.4.21", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", "culori": "^4.0.2", "gray-matter": "^4.0.3", - "hast": "^1.0.0", - "hast-util-to-html": "^9.0.5", - "hastscript": "^9.0.1", "lucide-react": "^0.485.0", "luxon": "^3.6.1", - "mdast": "^3.0.0", - "mermaid": "^11.6.0", - "motion": "^12.6.3", "playwright": "^1.51.1", "react": "^19.1.0", "react-dom": "^19.1.0", - "react-tweet": "^3.2.2", - "rehype-callouts": "^2.0.2", - "rehype-code-group": "^0.2.4", "rehype-mermaid": "^3.0.0", "remark-directive": "^4.0.0", - "remark-toc": "^9.0.0", - "roboto": "link:@types/@fontsource/roboto", "satori": "^0.18.3", "tailwind-merge": "^3.0.2", "tailwindcss": "^4.0.17", @@ -65,6 +49,14 @@ "unist-util-visit": "^5.0.0" }, "devDependencies": { - "@shikijs/transformers": "^3.2.1" + "@biomejs/biome": "^1.9.4", + "@shikijs/transformers": "^3.2.1", + "@types/culori": "^4.0.1", + "@types/hast": "^3.0.4", + "@types/luxon": "^3.6.2", + "@types/mdast": "^4.0.4", + "@types/node": "^24.10.0", + "@types/react": "^19.0.12", + "@types/react-dom": "^19.0.4" } -} \ No newline at end of file +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 33b7ae1..96bdaed 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,12 +11,12 @@ dependencies: '@astrojs/react': specifier: ^4.4.2 version: 4.4.2(@types/node@24.10.0)(@types/react-dom@19.1.6)(@types/react@19.1.8)(react-dom@19.1.0)(react@19.1.0) + '@astrojs/rss': + specifier: ^4.0.11 + version: 4.0.15 '@astrojs/sitemap': specifier: ^3.6.0 version: 3.6.0 - '@expressive-code/plugin-line-numbers': - specifier: ^0.40.2 - version: 0.40.2 '@iconify-json/devicon': specifier: ^1.2.23 version: 1.2.29 @@ -50,36 +50,12 @@ dependencies: '@tailwindcss/vite': specifier: ^4.0.17 version: 4.1.11(vite@7.0.0) - '@types/culori': - specifier: ^4.0.1 - version: 4.0.1 - '@types/hast': - specifier: ^3.0.4 - version: 3.0.4 - '@types/luxon': - specifier: ^3.6.2 - version: 3.6.2 - '@types/mdast': - specifier: ^4.0.4 - version: 4.0.4 - '@types/node': - specifier: ^24.10.0 - version: 24.10.0 - '@types/react': - specifier: ^19.0.12 - version: 19.1.8 - '@types/react-dom': - specifier: ^19.0.4 - version: 19.1.6(@types/react@19.1.8) astro: specifier: ^5.16.3 version: 5.16.3(@types/node@24.10.0)(typescript@5.8.3) astro-icon: specifier: ^1.1.5 version: 1.1.5 - autoprefixer: - specifier: ^10.4.21 - version: 10.4.21(postcss@8.5.6) class-variance-authority: specifier: ^0.7.1 version: 0.7.1 @@ -95,30 +71,12 @@ dependencies: gray-matter: specifier: ^4.0.3 version: 4.0.3 - hast: - specifier: ^1.0.0 - version: 1.0.0 - hast-util-to-html: - specifier: ^9.0.5 - version: 9.0.5 - hastscript: - specifier: ^9.0.1 - version: 9.0.1 lucide-react: specifier: ^0.485.0 version: 0.485.0(react@19.1.0) luxon: specifier: ^3.6.1 version: 3.6.1 - mdast: - specifier: ^3.0.0 - version: 3.0.0 - mermaid: - specifier: ^11.6.0 - version: 11.7.0 - motion: - specifier: ^12.6.3 - version: 12.23.0(react-dom@19.1.0)(react@19.1.0) playwright: specifier: ^1.51.1 version: 1.53.2 @@ -128,27 +86,12 @@ dependencies: react-dom: specifier: ^19.1.0 version: 19.1.0(react@19.1.0) - react-tweet: - specifier: ^3.2.2 - version: 3.2.2(react-dom@19.1.0)(react@19.1.0) - rehype-callouts: - specifier: ^2.0.2 - version: 2.1.1 - rehype-code-group: - specifier: ^0.2.4 - version: 0.2.4(typescript@5.8.3) rehype-mermaid: specifier: ^3.0.0 version: 3.0.0(playwright@1.53.2) remark-directive: specifier: ^4.0.0 version: 4.0.0 - remark-toc: - specifier: ^9.0.0 - version: 9.0.0 - roboto: - specifier: link:@types/@fontsource/roboto - version: link:@types/@fontsource/roboto satori: specifier: ^0.18.3 version: 0.18.3 @@ -169,9 +112,33 @@ dependencies: version: 5.0.0 devDependencies: + '@biomejs/biome': + specifier: ^1.9.4 + version: 1.9.4 '@shikijs/transformers': specifier: ^3.2.1 version: 3.7.0 + '@types/culori': + specifier: ^4.0.1 + version: 4.0.1 + '@types/hast': + specifier: ^3.0.4 + version: 3.0.4 + '@types/luxon': + specifier: ^3.6.2 + version: 3.6.2 + '@types/mdast': + specifier: ^4.0.4 + version: 4.0.4 + '@types/node': + specifier: ^24.10.0 + version: 24.10.0 + '@types/react': + specifier: ^19.0.12 + version: 19.1.8 + '@types/react-dom': + specifier: ^19.0.4 + version: 19.1.6(@types/react@19.1.8) packages: @@ -292,6 +259,13 @@ packages: - yaml dev: false + /@astrojs/rss@4.0.15: + resolution: {integrity: sha512-uXO/k6AhRkIDXmRoc6xQpoPZrimQNUmS43X4+60yunfuMNHtSRN5e/FiSi7NApcZqmugSMc5+cJi8ovqgO+qIg==} + dependencies: + fast-xml-parser: 5.3.7 + piccolore: 0.1.3 + dev: false + /@astrojs/sitemap@3.6.0: resolution: {integrity: sha512-4aHkvcOZBWJigRmMIAJwRQXBS+ayoP5z40OklTXYXhUDhwusz+DyDl+nSshY6y9DvkVEavwNcFO8FD81iGhXjg==} dependencies: @@ -512,6 +486,94 @@ packages: '@babel/helper-validator-identifier': 7.28.5 dev: false + /@biomejs/biome@1.9.4: + resolution: {integrity: sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==} + engines: {node: '>=14.21.3'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@biomejs/cli-darwin-arm64': 1.9.4 + '@biomejs/cli-darwin-x64': 1.9.4 + '@biomejs/cli-linux-arm64': 1.9.4 + '@biomejs/cli-linux-arm64-musl': 1.9.4 + '@biomejs/cli-linux-x64': 1.9.4 + '@biomejs/cli-linux-x64-musl': 1.9.4 + '@biomejs/cli-win32-arm64': 1.9.4 + '@biomejs/cli-win32-x64': 1.9.4 + dev: true + + /@biomejs/cli-darwin-arm64@1.9.4: + resolution: {integrity: sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@biomejs/cli-darwin-x64@1.9.4: + resolution: {integrity: sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@biomejs/cli-linux-arm64-musl@1.9.4: + resolution: {integrity: sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@biomejs/cli-linux-arm64@1.9.4: + resolution: {integrity: sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@biomejs/cli-linux-x64-musl@1.9.4: + resolution: {integrity: sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@biomejs/cli-linux-x64@1.9.4: + resolution: {integrity: sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@biomejs/cli-win32-arm64@1.9.4: + resolution: {integrity: sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@biomejs/cli-win32-x64@1.9.4: + resolution: {integrity: sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + /@braintree/sanitize-url@7.1.1: resolution: {integrity: sha512-i1L7noDNxtFyL5DmZafWy1wRVhGehQmzZaz1HiN5e7iylJMSZR7ekOV7NsIqa5qBldlLrsKv4HbgFUVlQrz8Mw==} dev: false @@ -676,11 +738,6 @@ packages: w3c-keyname: 2.2.8 dev: false - /@ctrl/tinycolor@4.1.0: - resolution: {integrity: sha512-WyOx8cJQ+FQus4Mm4uPIZA64gbk3Wxh0so5Lcii0aJifqwoVOlfFtorjLE0Hen4OYyHZMXDWqMmaQemBhgxFRQ==} - engines: {node: '>=14'} - dev: false - /@emnapi/runtime@1.7.1: resolution: {integrity: sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==} requiresBuild: true @@ -914,26 +971,6 @@ packages: dev: false optional: true - /@expressive-code/core@0.40.2: - resolution: {integrity: sha512-gXY3v7jbgz6nWKvRpoDxK4AHUPkZRuJsM79vHX/5uhV9/qX6Qnctp/U/dMHog/LCVXcuOps+5nRmf1uxQVPb3w==} - dependencies: - '@ctrl/tinycolor': 4.1.0 - hast-util-select: 6.0.4 - hast-util-to-html: 9.0.5 - hast-util-to-text: 4.0.2 - hastscript: 9.0.1 - postcss: 8.5.6 - postcss-nested: 6.2.0(postcss@8.5.6) - unist-util-visit: 5.0.0 - unist-util-visit-parents: 6.0.1 - dev: false - - /@expressive-code/plugin-line-numbers@0.40.2: - resolution: {integrity: sha512-YMLkn68n9a9DI/4fQW/f6QJ33uQUzHmGdV3pDl+f6fVTxv7rvhRja+UtPksm0ZJpft6vrrACV8wS2TaH77SBzw==} - dependencies: - '@expressive-code/core': 0.40.2 - dev: false - /@floating-ui/core@1.7.2: resolution: {integrity: sha512-wNB5ooIKHQc+Kui96jE/n69rHFWAVoxn5CAzL1Xdd8FG03cgY3MLO+GF9U3W737fYDSgPWA6MReKhBQBop6Pcw==} dependencies: @@ -3058,7 +3095,7 @@ packages: /@types/culori@4.0.1: resolution: {integrity: sha512-43M51r/22CjhbOXyGT361GZ9vncSVQ39u62x5eJdBQFviI8zWp2X5jzqg7k4M6PVgDQAClpy2bUe2dtwEgEDVQ==} - dev: false + dev: true /@types/d3-array@3.2.1: resolution: {integrity: sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==} @@ -3272,7 +3309,7 @@ packages: /@types/luxon@3.6.2: resolution: {integrity: sha512-R/BdP7OxEMc44l2Ex5lSXHoIXTB2JLNa3y2QISIbr58U/YcsffyQrYW//hZSdrfxrjRZj3GcUoxMPGdO8gSYuw==} - dev: false + dev: true /@types/mdast@4.0.4: resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} @@ -3307,7 +3344,6 @@ packages: resolution: {integrity: sha512-qzQZRBqkFsYyaSWXuEHc2WR9c0a0CXwiE5FWUvn7ZM+vdy1uZLfCunD38UzhuB7YN/J11ndbDBcTmOdxJo9Q7A==} dependencies: undici-types: 7.16.0 - dev: false /@types/react-dom@19.1.6(@types/react@19.1.8): resolution: {integrity: sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==} @@ -3315,13 +3351,11 @@ packages: '@types/react': ^19.0.0 dependencies: '@types/react': 19.1.8 - dev: false /@types/react@19.1.8: resolution: {integrity: sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==} dependencies: csstype: 3.1.3 - dev: false /@types/sax@1.2.7: resolution: {integrity: sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==} @@ -3342,10 +3376,6 @@ packages: dev: false optional: true - /@types/ungap__structured-clone@1.2.0: - resolution: {integrity: sha512-ZoaihZNLeZSxESbk9PUAPZOlSpcKx81I1+4emtULDVmBLkYutTcMlCj2K9VNlf9EWODxdO6gkAqEaLorXwZQVA==} - dev: false - /@types/unist@2.0.11: resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} dev: false @@ -3827,22 +3857,6 @@ packages: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} dev: false - /autoprefixer@10.4.21(postcss@8.5.6): - resolution: {integrity: sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==} - engines: {node: ^10 || ^12 || >=14} - hasBin: true - peerDependencies: - postcss: ^8.1.0 - dependencies: - browserslist: 4.25.1 - caniuse-lite: 1.0.30001726 - fraction.js: 4.3.7 - normalize-range: 0.1.2 - picocolors: 1.1.1 - postcss: 8.5.6 - postcss-value-parser: 4.2.0 - dev: false - /axios@1.10.0: resolution: {integrity: sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==} dependencies: @@ -3875,10 +3889,6 @@ packages: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} dev: false - /bcp-47-match@2.0.3: - resolution: {integrity: sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ==} - dev: false - /boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} dev: false @@ -4195,10 +4205,6 @@ packages: nth-check: 2.1.1 dev: false - /css-selector-parser@3.1.3: - resolution: {integrity: sha512-gJMigczVZqYAk0hPVzx/M4Hm1D9QOtqkdQk9005TNzDIUGzo5cnHEDiKUT7jGPximL/oYb+LIitcHFQ4aKupxg==} - dev: false - /css-to-react-native@3.2.0: resolution: {integrity: sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==} dependencies: @@ -4251,7 +4257,6 @@ packages: /csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} - dev: false /culori@4.0.2: resolution: {integrity: sha512-1+BhOB8ahCn4O0cep0Sh2l9KCOfOdY+BXJnKMHFFzDEouSr/el18QwXEMRlOj9UY5nCeA8UN3a/82rUWRBeyBw==} @@ -4669,11 +4674,6 @@ packages: engines: {node: '>=0.3.1'} dev: false - /direction@2.0.1: - resolution: {integrity: sha512-9S6m9Sukh1cZNknO1CWAr2QAWsbKLafQiyM5gZ7VgXHeuaoUwffKN4q6NC4A/Mf9iiPlOXQEKW/Mv/mh9/3YFA==} - hasBin: true - dev: false - /dlv@1.1.3: resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} dev: false @@ -4967,6 +4967,13 @@ packages: resolution: {integrity: sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==} dev: false + /fast-xml-parser@5.3.7: + resolution: {integrity: sha512-JzVLro9NQv92pOM/jTCR6mHlJh2FGwtomH8ZQjhFj/R29P2Fnj38OgPJVtcvYw6SuKClhgYuwUZf5b3rd8u2mA==} + hasBin: true + dependencies: + strnum: 2.1.2 + dev: false + /fd-slicer@1.1.0: resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} dependencies: @@ -5057,31 +5064,6 @@ packages: mime-types: 2.1.35 dev: false - /fraction.js@4.3.7: - resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} - dev: false - - /framer-motion@12.23.0(react-dom@19.1.0)(react@19.1.0): - resolution: {integrity: sha512-xf6NxTGAyf7zR4r2KlnhFmsRfKIbjqeBupEDBAaEtVIBJX96sAon00kMlsKButSIRwPSHjbRrAPnYdJJ9kyhbA==} - peerDependencies: - '@emotion/is-prop-valid': '*' - react: ^18.0.0 || ^19.0.0 - react-dom: ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - '@emotion/is-prop-valid': - optional: true - react: - optional: true - react-dom: - optional: true - dependencies: - motion-dom: 12.22.0 - motion-utils: 12.19.0 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) - tslib: 2.8.1 - dev: false - /fs-minipass@2.1.0: resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} engines: {node: '>= 8'} @@ -5360,26 +5342,6 @@ packages: unist-util-position: 5.0.0 dev: false - /hast-util-select@6.0.4: - resolution: {integrity: sha512-RqGS1ZgI0MwxLaKLDxjprynNzINEkRHY2i8ln4DDjgv9ZhcYVIHN9rlpiYsqtFwrgpYU361SyWDQcGNIBVu3lw==} - dependencies: - '@types/hast': 3.0.4 - '@types/unist': 3.0.3 - bcp-47-match: 2.0.3 - comma-separated-tokens: 2.0.3 - css-selector-parser: 3.1.3 - devlop: 1.1.0 - direction: 2.0.1 - hast-util-has-property: 3.0.0 - hast-util-to-string: 3.0.1 - hast-util-whitespace: 3.0.0 - nth-check: 2.1.1 - property-information: 7.1.0 - space-separated-tokens: 2.0.2 - unist-util-visit: 5.0.0 - zwitch: 2.0.4 - dev: false - /hast-util-to-estree@3.1.3: resolution: {integrity: sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w==} dependencies: @@ -5452,12 +5414,6 @@ packages: zwitch: 2.0.4 dev: false - /hast-util-to-string@3.0.1: - resolution: {integrity: sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==} - dependencies: - '@types/hast': 3.0.4 - dev: false - /hast-util-to-text@4.0.2: resolution: {integrity: sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==} dependencies: @@ -5472,11 +5428,6 @@ packages: dependencies: '@types/hast': 3.0.4 - /hast@1.0.0: - resolution: {integrity: sha512-vFUqlRV5C+xqP76Wwq2SrM0kipnmpxJm7OfvVXpB35Fp+Fn4MV+ozr+JZr5qFvyR1q/U+Foim2x+3P+x9S1PLA==} - deprecated: Renamed to rehype - dev: false - /hastscript@9.0.1: resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==} dependencies: @@ -6174,23 +6125,6 @@ packages: '@types/mdast': 4.0.4 dev: false - /mdast-util-toc@7.1.0: - resolution: {integrity: sha512-2TVKotOQzqdY7THOdn2gGzS9d1Sdd66bvxUyw3aNpWfcPXCLYSJCCgfPy30sEtuzkDraJgqF35dzgmz6xlvH/w==} - dependencies: - '@types/mdast': 4.0.4 - '@types/ungap__structured-clone': 1.2.0 - '@ungap/structured-clone': 1.3.0 - github-slugger: 2.0.0 - mdast-util-to-string: 4.0.0 - unist-util-is: 6.0.0 - unist-util-visit: 5.0.0 - dev: false - - /mdast@3.0.0: - resolution: {integrity: sha512-xySmf8g4fPKMeC07jXGz971EkLbWAJ83s4US2Tj9lEdnZ142UP5grN73H1Xd3HzrdbU5o9GYYP/y8F9ZSwLE9g==} - deprecated: '`mdast` was renamed to `remark`' - dev: false - /mdn-data@2.0.28: resolution: {integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==} dev: false @@ -6665,36 +6599,6 @@ packages: ufo: 1.6.1 dev: false - /motion-dom@12.22.0: - resolution: {integrity: sha512-ooH7+/BPw9gOsL9VtPhEJHE2m4ltnhMlcGMhEqA0YGNhKof7jdaszvsyThXI6LVIKshJUZ9/CP6HNqQhJfV7kw==} - dependencies: - motion-utils: 12.19.0 - dev: false - - /motion-utils@12.19.0: - resolution: {integrity: sha512-BuFTHINYmV07pdWs6lj6aI63vr2N4dg0vR+td0rtrdpWOhBzIkEklZyLcvKBoEtwSqx8Jg06vUB5RS0xDiUybw==} - dev: false - - /motion@12.23.0(react-dom@19.1.0)(react@19.1.0): - resolution: {integrity: sha512-PPNwblArRH9GRC4F3KtOTiIaYd+mtp324vYq3HIL+ueseoAVqPRK5TPFTAQBcIprfVd0NWo3DLzZSiyWaYFXXQ==} - peerDependencies: - '@emotion/is-prop-valid': '*' - react: ^18.0.0 || ^19.0.0 - react-dom: ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - '@emotion/is-prop-valid': - optional: true - react: - optional: true - react-dom: - optional: true - dependencies: - framer-motion: 12.23.0(react-dom@19.1.0)(react@19.1.0) - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) - tslib: 2.8.1 - dev: false - /mrmime@2.0.1: resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} engines: {node: '>=10'} @@ -6748,11 +6652,6 @@ packages: engines: {node: '>=0.10.0'} dev: false - /normalize-range@0.1.2: - resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} - engines: {node: '>=0.10.0'} - dev: false - /nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} dependencies: @@ -6969,24 +6868,6 @@ packages: points-on-curve: 0.2.0 dev: false - /postcss-nested@6.2.0(postcss@8.5.6): - resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==} - engines: {node: '>=12.0'} - peerDependencies: - postcss: ^8.2.14 - dependencies: - postcss: 8.5.6 - postcss-selector-parser: 6.1.2 - dev: false - - /postcss-selector-parser@6.1.2: - resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} - engines: {node: '>=4'} - dependencies: - cssesc: 3.0.0 - util-deprecate: 1.0.2 - dev: false - /postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} dev: false @@ -7137,19 +7018,6 @@ packages: tslib: 2.8.1 dev: false - /react-tweet@3.2.2(react-dom@19.1.0)(react@19.1.0): - resolution: {integrity: sha512-hIkxAVPpN2RqWoDEbo3TTnN/pDcp9/Jb6pTgiA4EbXa9S+m2vHIvvZKHR+eS0PDIsYqe+zTmANRa5k6+/iwGog==} - peerDependencies: - react: ^18.0.0 || ^19.0.0 - react-dom: ^18.0.0 || ^19.0.0 - dependencies: - '@swc/helpers': 0.5.17 - clsx: 2.1.1 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) - swr: 2.3.4(react@19.1.0) - dev: false - /react@19.1.0: resolution: {integrity: sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==} engines: {node: '>=0.10.0'} @@ -7214,33 +7082,6 @@ packages: regex-utilities: 2.3.0 dev: false - /rehype-callouts@2.1.1: - resolution: {integrity: sha512-tPaywsSPUzgUJg+anafzr6wdXuWL6YQMoilNueO8ehYf1DWZMYH0bpPMuai5RUNdtcmQNOvOhwHLhh8p8b09nA==} - engines: {node: ^20.0.0 || >=22.0.0, pnpm: ^10.0.0} - dependencies: - '@types/hast': 3.0.4 - hast-util-from-html: 2.0.3 - hast-util-is-element: 3.0.0 - hastscript: 9.0.1 - unist-util-visit: 5.0.0 - dev: false - - /rehype-code-group@0.2.4(typescript@5.8.3): - resolution: {integrity: sha512-ExGjvA5dZz+UiTyIRWGclk1RDuTjqMiC0JxxSpcQ8W9AHWaJmzdyzQB4MiofnzRc2SCTvXDzuFhd1Co5TLMKmA==} - engines: {node: '>=20.0.0'} - peerDependencies: - typescript: '>=5.0.4' - peerDependenciesMeta: - typescript: - optional: true - dependencies: - hast-util-to-string: 3.0.1 - rehype: 13.0.2 - typescript: 5.8.3 - unified: 11.0.5 - unist-util-visit: 5.0.0 - dev: false - /rehype-external-links@3.0.0: resolution: {integrity: sha512-yp+e5N9V3C6bwBeAC4n796kc86M4gJCdlVhiMTxIrJG5UHDMh+PJANf9heqORJbt1nrCbDwIlAZKjANIaVBbvw==} dependencies: @@ -7403,13 +7244,6 @@ packages: unified: 11.0.5 dev: false - /remark-toc@9.0.0: - resolution: {integrity: sha512-KJ9txbo33GjDAV1baHFze7ij4G8c7SGYoY8Kzsm2gzFpbhL/bSoVpMMzGa3vrNDSWASNd/3ppAqL7cP2zD6JIA==} - dependencies: - '@types/mdast': 4.0.4 - mdast-util-toc: 7.1.0 - dev: false - /require-from-string@2.0.2: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} @@ -7698,6 +7532,10 @@ packages: engines: {node: '>=0.10.0'} dev: false + /strnum@2.1.2: + resolution: {integrity: sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ==} + dev: false + /style-mod@4.1.2: resolution: {integrity: sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==} dev: false @@ -7746,16 +7584,6 @@ packages: sax: 1.4.3 dev: false - /swr@2.3.4(react@19.1.0): - resolution: {integrity: sha512-bYd2lrhc+VarcpkgWclcUi92wYCpOgMws9Sd1hG1ntAu0NEy+14CbotuFjshBU2kt9rYj9TSmDcybpxpeTU1fg==} - peerDependencies: - react: ^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - dependencies: - dequal: 2.0.3 - react: 19.1.0 - use-sync-external-store: 1.5.0(react@19.1.0) - dev: false - /tabbable@6.2.0: resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==} dev: false @@ -7903,7 +7731,6 @@ packages: /undici-types@7.16.0: resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} - dev: false /undici@6.21.3: resolution: {integrity: sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==} @@ -8135,18 +7962,6 @@ packages: tslib: 2.8.1 dev: false - /use-sync-external-store@1.5.0(react@19.1.0): - resolution: {integrity: sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - dependencies: - react: 19.1.0 - dev: false - - /util-deprecate@1.0.2: - resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - dev: false - /uuid@11.1.0: resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} hasBin: true @@ -8269,11 +8084,11 @@ packages: dependencies: '@types/node': 24.10.0 esbuild: 0.25.5 - fdir: 6.4.6(picomatch@4.0.2) - picomatch: 4.0.2 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 postcss: 8.5.6 rollup: 4.44.1 - tinyglobby: 0.2.14 + tinyglobby: 0.2.15 optionalDependencies: fsevents: 2.3.3 dev: false diff --git a/src/assets/css/global.css b/src/assets/css/global.css index 8dd702b..302a1c3 100644 --- a/src/assets/css/global.css +++ b/src/assets/css/global.css @@ -39,7 +39,7 @@ --sidebar-border: oklch(0.922 0 0); --sidebar-ring: oklch(0.708 0 0); --tab-container: oklch(0.967 0.001 286.375); - --container-width: 90rem; + --container-width: 72rem; } .dark { @@ -169,5 +169,21 @@ ::view-transition-old(root), ::view-transition-new(root) { - animation-duration: 0.3s !important; + animation: none !important; +} + +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + scroll-behavior: auto !important; + } + + ::view-transition-old(root), + ::view-transition-new(root) { + animation: none !important; + } } diff --git a/src/assets/css/markdown.css b/src/assets/css/markdown.css index fe52751..ae1a19e 100644 --- a/src/assets/css/markdown.css +++ b/src/assets/css/markdown.css @@ -60,6 +60,30 @@ h6 { scroll-margin-top: 100px; } +.heading-link { + color: inherit; + text-decoration: none; + font-weight: inherit; +} + +.heading-link:hover { + color: inherit; + text-decoration: none; +} + +.heading-link::after { + content: "#"; + margin-left: 0.5rem; + opacity: 0; + color: var(--primary); + transition: opacity 0.15s ease; + font-weight: 400; +} + +.heading-link:hover::after { + opacity: 0.6; +} + .prose { color: var(--paragraph-color); @@ -69,6 +93,12 @@ h6 { font-weight: 500; } + a.heading-link { + color: inherit; + text-decoration: none; + font-weight: inherit; + } + strong { font-weight: 600; } @@ -117,20 +147,6 @@ h6 { margin-bottom: 2em; } - blockquote { - font-style: italic; - border-left-width: 0.2rem; - border-left-color: var(--primary); - margin-top: 1em; - margin-bottom: 1em; - padding-top: 0.5em; - padding-bottom: 0.5em; - padding-left: 1em; - background-color: var(--muted); - border-top-right-radius: 0.25rem; - border-bottom-right-radius: 0.25rem; - } - h1 { font-weight: 800; font-size: 2.25em; diff --git a/src/content.config.ts b/src/content.config.ts index 32ac681..09d64b1 100644 --- a/src/content.config.ts +++ b/src/content.config.ts @@ -48,7 +48,11 @@ const pages = defineCollection({ pattern: ["**/*.{md,mdx}", "blog/index.{md,mdx}", "!docs/**/*"], base: "./content", }), - schema: z.object({}), + schema: z.object({ + layout: z.string().optional(), + title: z.string().optional(), + description: z.string().optional(), + }), }); const directories = readdirSync(join(process.cwd(), "content/docs")); @@ -79,6 +83,8 @@ const blog = defineCollection({ permalink: z.string().optional(), thumbnail: z.string().optional(), authors: z.array(z.string()).optional(), + tags: z.array(z.string()).optional(), + draft: z.boolean().optional().default(false), publishedAt: z.string().optional(), }), }); diff --git a/src/hooks/build-doc.ts b/src/hooks/build-doc.ts index 5485317..491441b 100644 --- a/src/hooks/build-doc.ts +++ b/src/hooks/build-doc.ts @@ -1,27 +1,97 @@ import { Resvg } from "@resvg/resvg-js"; import type { AstroIntegration } from "astro"; import matter from "gray-matter"; -import { mkdir, readFile, writeFile } from "node:fs/promises"; +import { mkdir, readdir, readFile, writeFile } from "node:fs/promises"; import { join } from "node:path"; import { generateThumbnail } from "../lib/components/content/thumbnail"; +async function renderThumbnail(svg: string, outputPath: string): Promise { + const resvg = new Resvg(svg, { + background: "transparent", + fitTo: { mode: "width", value: 960 }, + }); + const png = resvg.render(); + await writeFile(outputPath, png.asPng()); +} + +async function buildBlogMetadataMap() { + const blogDir = join(process.cwd(), "content/blog"); + const allBlogFiles = await readdir(blogDir); + const blogFiles = allBlogFiles.filter( + (f) => (f.endsWith(".mdx") || f.endsWith(".md")) && f !== "index.mdx", + ); + const blogMap = new Map< + string, + { title: string; description: string; thumbnail?: string } + >(); + for (const file of blogFiles) { + const content = await readFile(join(blogDir, file), "utf8"); + const { data } = matter(content); + if (data.permalink) { + blogMap.set(data.permalink, { + title: data.title, + description: data.description, + thumbnail: data.thumbnail, + }); + } + } + return blogMap; +} + +async function generateBlogThumbnails( + blogMap: Map< + string, + { title: string; description: string; thumbnail?: string } + >, + outputDir: string, + logger: { info: (msg: string) => void; warn: (msg: string) => void }, +) { + const tasks = [...blogMap.entries()] + .filter(([, data]) => !data.thumbnail) + .map(([slug, data]) => async () => { + const thumbnail = await generateThumbnail("Blog", data.title); + const location = join(outputDir, "blog", slug); + await mkdir(location, { recursive: true }); + await renderThumbnail(thumbnail, join(location, "thumbnail.png")); + logger.info(`Thumbnail generated for blog/${slug}`); + }); + + await Promise.all( + tasks.map(async (task) => { + try { + await task(); + } catch (error) { + logger.warn(`Failed to generate thumbnail: ${error}`); + } + }), + ); +} + export function buildDocIntegration(): AstroIntegration { return { name: "@explainer/renderer", hooks: { + "astro:config:setup": async ({ logger }) => { + const blogMap = await buildBlogMetadataMap(); + const publicDir = join(process.cwd(), "public"); + await generateBlogThumbnails(blogMap, publicDir, logger); + }, "astro:build:done": async ({ pages, logger }) => { - logger.info("Starting thumbnail generation"); - const docPaths = pages - .filter((element) => element.pathname.startsWith("docs/")) - .map((element) => element.pathname); + logger.info("Starting doc thumbnail generation"); - await Promise.all( - docPaths.map(async (path) => { - const file = join(path.replace("docs/", "").replace(/\/$/, "")); - const { data } = await readFile( - join(process.cwd(), "content/docs", `${file}.mdx`), - "utf8", - ).then(matter); + // Collect doc thumbnail tasks + const docTasks = pages + .filter((element) => element.pathname.startsWith("docs/")) + .map((element) => async () => { + const file = element.pathname + .replace("docs/", "") + .replace(/\/$/, ""); + const { data } = matter( + await readFile( + join(process.cwd(), "content/docs", `${file}.mdx`), + "utf8", + ), + ); const thumbnail = await generateThumbnail( file.split("/").at(0), @@ -30,18 +100,21 @@ export function buildDocIntegration(): AstroIntegration { ); const location = join(process.cwd(), "dist", "docs"); - await mkdir(join(location, file), { - recursive: true, - }); - - const resvg = new Resvg(thumbnail, { - background: "transparent", - fitTo: { mode: "width", value: 960 }, - }); - - const png = resvg.render(); - await writeFile(join(location, file, "thumbnail.png"), png.asPng()); - logger.info(`Thumbnail generated for ${file}`); + await mkdir(join(location, file), { recursive: true }); + await renderThumbnail( + thumbnail, + join(location, file, "thumbnail.png"), + ); + logger.info(`Thumbnail generated for docs/${file}`); + }); + + await Promise.all( + docTasks.map(async (task) => { + try { + await task(); + } catch (error) { + logger.warn(`Failed to generate thumbnail: ${error}`); + } }), ); }, diff --git a/src/lib/components/content/blockquote/blockquote.astro b/src/lib/components/content/blockquote/blockquote.astro new file mode 100644 index 0000000..ca7108c --- /dev/null +++ b/src/lib/components/content/blockquote/blockquote.astro @@ -0,0 +1,45 @@ +--- + +--- + +
+ +
+
+ +
+
+
+ + + + diff --git a/src/lib/components/content/callout.astro b/src/lib/components/content/callout.astro index a3be9d2..24a9cd0 100644 --- a/src/lib/components/content/callout.astro +++ b/src/lib/components/content/callout.astro @@ -22,10 +22,13 @@ const icons: Record = { const variants: Record & Record<"_default", string> = { _default: "bg-background !text-muted-foreground", - info: "bg-blue-50 border-blue-200 !text-blue-500", - success: "bg-green-50 border-green-300 !text-green-700", - warning: "bg-yellow-50 border-orange-200 !text-yellow-700", - danger: "bg-red-100 border-red-300 !text-red-500", + info: "bg-blue-50 dark:bg-blue-400/20 border-blue-200 dark:border-blue-400/20 text-blue-500 dark:text-blue-200/75", + success: + "bg-green-50 dark:bg-green-400/20 border-green-300 dark:border-green-400/20 text-green-700 dark:text-green-300/75", + warning: + "bg-orange-50 dark:bg-yellow-400/20 border-orange-200 dark:border-yellow-300/50 text-yellow-700 dark:text-yellow-300/50", + danger: + "bg-red-50 dark:bg-red-400/20 border-red-300 dark:border-red-400/20 text-red-500 dark:text-red-300/75", }; const currentIcon = props.variant ? icons[props.variant] : undefined; diff --git a/src/lib/components/content/card-group/card-group.astro b/src/lib/components/content/card-group/card-group.astro index 8cff351..83253a0 100644 --- a/src/lib/components/content/card-group/card-group.astro +++ b/src/lib/components/content/card-group/card-group.astro @@ -20,6 +20,6 @@ const gridCols = { }; --- -
+
diff --git a/src/lib/components/content/code-group/code-group.tsx b/src/lib/components/content/code-group/code-group.tsx index 800c195..e7d1f96 100644 --- a/src/lib/components/content/code-group/code-group.tsx +++ b/src/lib/components/content/code-group/code-group.tsx @@ -19,11 +19,11 @@ export default function CodeGroupComponent( >([]); const containerRef = useRef(null); - const parseProp = (prop: any) => { + const parseProp = (prop: string | string[] | undefined): string[] => { if (Array.isArray(prop)) return prop; if (typeof prop === "string") { try { - return JSON.parse(prop); + return JSON.parse(prop) as string[]; } catch { return []; } @@ -140,7 +140,7 @@ export default function CodeGroupComponent( return (
- {tabsData.map((tab: any, i: number) => ( + {tabsData.map((tab, i) => ( +
+
+
diff --git a/src/lib/components/elements/doc-switcher.tsx b/src/lib/components/elements/doc-switcher.tsx index 3941f85..ec3bab1 100644 --- a/src/lib/components/elements/doc-switcher.tsx +++ b/src/lib/components/elements/doc-switcher.tsx @@ -1,3 +1,4 @@ +import { isDocSection, type DocSection, type DocTreeNode } from "@/utils"; import { Icon } from "@iconify/react"; import { Button } from "../ui/button"; import { @@ -9,32 +10,54 @@ import { } from "../ui/dropdown-menu"; type Props = { - current: any; - items: any[]; + current: DocSection; + items: DocTreeNode[]; }; +function getFirstPageHref(node: DocTreeNode): string | null { + if (!isDocSection(node)) { + return `/docs/${node.id}`; + } + for (const child of node.children) { + const href = getFirstPageHref(child); + if (href) return href; + } + return null; +} + export default function DocSwitcher(props: Props) { return ( - +
+ +
Documentations - {props.items.map((element) => ( - setActiveTeam(team)} - className="gap-2 p-2 text-muted" - > -
- -
- {element.data.label} -
- ))} + {props.items.map((element) => { + const href = getFirstPageHref(element); + const isCurrent = element.id === props.current.id; + const icon = isDocSection(element) + ? (element.data.icon ?? "lucide:book-open") + : (element.data.icon ?? "lucide:file-text"); + + return ( + + +
+ +
+ {"label" in element.data + ? element.data.label + : element.data.title} +
+
+ ); + })}
); diff --git a/src/lib/components/elements/features-section.astro b/src/lib/components/elements/features-section.astro new file mode 100644 index 0000000..923615d --- /dev/null +++ b/src/lib/components/elements/features-section.astro @@ -0,0 +1,121 @@ +--- +import { Icon } from "@iconify/react"; +import config from "explainer.config"; + +const title = "Everything you need, out of the box"; +const description = `${config.projectName} gives you the tools to focus on your content, not your tooling.`; + +const features = [ + { + icon: "mdi:language-markdown", + title: "Markdown & MDX", + description: + "Write content in Markdown or MDX with custom components, directives, and JSX support.", + }, + { + icon: "mdi:flash-outline", + title: "Lightning fast", + description: + "Static site generation for instant page loads. Zero JavaScript shipped by default.", + }, + { + icon: "mdi:code-braces", + title: "Rich code blocks", + description: + "Dual-theme syntax highlighting, line diffs, focus mode, and 60+ language icons.", + }, + { + icon: "mdi:magnify", + title: "Built-in search", + description: + "Command palette search so your readers find any page instantly.", + }, + { + icon: "mdi:search-web", + title: "SEO ready", + description: + "Auto-generated OG thumbnails, sitemap, robots.txt, and RSS feed.", + }, + { + icon: "mdi:theme-light-dark", + title: "Dark mode", + description: + "Full light and dark theme support with system preference detection.", + }, +]; +--- + +
+ + + + +
+
+

+ Features +

+

+ {title} +

+

+ {description} +

+
+ +
+ { + features.map((feature) => ( +
+
+
+ +
+
+

+ {feature.title} +

+

+ {feature.description} +

+
+
+
+ )) + } +
+
+
diff --git a/src/lib/components/elements/footer.tsx b/src/lib/components/elements/footer.tsx index 514be14..40f4550 100644 --- a/src/lib/components/elements/footer.tsx +++ b/src/lib/components/elements/footer.tsx @@ -3,7 +3,8 @@ import config from "explainer.config"; export default function Footer() { return ( -