diff --git a/.github/workflows/deploy-pages.yml b/.github/workflows/deploy-pages.yml new file mode 100644 index 0000000..294816c --- /dev/null +++ b/.github/workflows/deploy-pages.yml @@ -0,0 +1,77 @@ +name: Deploy to GitHub Pages + +on: + push: + branches: + - main + pull_request: + branches: + - main + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: pages-${{ github.ref }} + cancel-in-progress: false + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Install bun + uses: oven-sh/setup-bun@v2 + + - name: Cache bun dependencies + uses: actions/cache@v5 + with: + path: ~/.bun/install/cache + key: bun-${{ runner.os }}-${{ hashFiles('**/bun.lock') }} + restore-keys: | + bun-${{ runner.os }}- + + - name: Install Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + + - name: Cache Next.js build + uses: actions/cache@v5 + with: + path: | + apps/landing/.next/cache + key: nextjs-${{ runner.os }}-${{ hashFiles('**/bun.lock') }}-${{ hashFiles('apps/landing/**/*.{js,jsx,ts,tsx,md,mdx}') }} + restore-keys: | + nextjs-${{ runner.os }}-${{ hashFiles('**/bun.lock') }}- + + - name: Install dependencies + run: bun install + + - name: Build landing + run: bun run --filter landing build + + - name: Disable Jekyll + run: touch apps/landing/out/.nojekyll + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: ./apps/landing/out + + deploy: + needs: build + runs-on: ubuntu-latest + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore index 9eb58b6..a07b923 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ bin/ *.iml .idea/ .omc +node_modules diff --git a/.oxlintrc.json b/.oxlintrc.json new file mode 100644 index 0000000..bca1fb2 --- /dev/null +++ b/.oxlintrc.json @@ -0,0 +1,3 @@ +{ + "extends": ["node_modules/eslint-plugin-devup/oxlintrc.json"] +} \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md index eef4bd2..03ce067 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1 +1,77 @@ -@AGENTS.md \ No newline at end of file +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +@AGENTS.md + +## Repository Shape + +Vespera is a **hybrid monorepo** with two workspaces living side-by-side at the repo root: + +| Workspace | Manager | Members | Purpose | +|-----------|---------|---------|---------| +| Cargo (`Cargo.toml`) | cargo | `crates/*`, `examples/*` (excluding `examples/java-jni-demo`) | OpenAPI engine, proc-macros, JNI bridge | +| Bun (`package.json`) | bun | `apps/*` | Marketing/docs site + admin panel (Next.js) | + +Both live at the root — `bun run ...` operates on the Node side; `cargo ...` on the Rust side. Many root scripts deliberately cross the boundary (e.g., `prelint` runs `cargo clippy/fmt/check` **before** oxlint runs on JS). See `AGENTS.md` for crate-level detail. + +## Common Commands (Root) + +```bash +# Rust side +cargo build # Build all crates +cargo test --workspace # All Rust tests +cargo test -p vespera_macro # One crate +cargo test --test -- # Single integration test +cargo tarpaulin --out stdout # Coverage (run via `bun run posttest`) + +# Lint / format (order matters — `prelint` hook runs Rust FIRST) +bun run lint # oxlint over JS/TS (runs after prelint → cargo clippy+fmt+check) +bun run lint:fix # oxlint --fix (prelint:fix runs cargo clippy --fix + fmt first) + +# Front-end workspace +bun run dev # Runs `dev` in every apps/* (Next.js dev servers) +bun run build # Builds apps/front and apps/admin +cd apps/front && bun dev # Single-app dev (preferred over -F flag, per devfive-frontend skill) + +# Tests (Bun side) +bun test # Root runs bun test + tarpaulin via posttest hook + +# Release tooling +bun run changepacks # Version bumps via @changepacks/cli +``` + +> **`prelint` gotcha:** `bun run lint` triggers `cargo clippy -- -D warnings && cargo fmt --check && cargo check` first. Any Rust warning fails the JS lint. Run `bun run lint:fix` (which chains `cargo clippy --fix && cargo fmt`) to auto-resolve both sides. + +## Frontend (`apps/front`) + +Next.js 16 App Router + React 19 + @devup-ui/react (build-time CSS-in-JS). Theme tokens live in `apps/front/devup.json` and use the `$token` syntax in JSX props only. + +- `apps/front/src/app/` contains **only** `layout.tsx` and `page.tsx` — all other components belong in `src/components/` (per devfive-frontend conventions). +- `src/api.ts` is generated/edited via `@devup-api/fetch`; it currently contains a placeholder `/users/users` call that fails typecheck — a known scaffolding leftover, not a regression. +- Styling: use devup-ui shorthand props (`bg`, `p`, `w`, `_hover`, `[mobile,null,pc]` responsive arrays). Never `style={{...}}` or Tailwind. See `~/.claude/skills/devup-ui/SKILL.md` via the `/devup-ui` skill. + +## Rust × Java Boundary + +The JNI integration (`crates/vespera_jni` → `libs/vespera-bridge/` Java lib) is load-bearing for `examples/rust-jni-demo`. When touching: + +- `vespera_inprocess` owns transport-agnostic dispatch (no JNI deps). +- `vespera_jni` is a thin layer depending on `vespera_inprocess` + `jni` + `tokio/rt-multi-thread`. +- User code depends on `vespera` only, with `features = ["jni"]` — never `vespera_jni` directly. Breaking this invariant is an AGENTS.md-listed anti-pattern. + +The Java package `com.devfive.vespera.bridge` is **fixed** because the JNI symbol name is derived from it. Renaming it breaks the native load. + +## Pre-Commit (Husky) + +`bun run prepare` installs husky. Commits trigger whatever lives in `.husky/` — typically a `lint` pass. Never bypass with `--no-verify`; fix the underlying Rust or oxlint finding. + +## Where Tests Live + +| Concern | Location | +|---------|----------| +| Macro integration tests | `crates/vespera_macro/tests/` (+ `insta` snapshots) | +| Core unit tests | `crates/vespera_core/src/**` inline `#[cfg(test)]` | +| JNI end-to-end | `examples/rust-jni-demo` (Rust + Java + Gradle) | +| Front tests | `apps/front/src/__tests__/` (bun test + bun-test-env-dom) | + +Snapshot tests use `insta` — run `cargo insta review` to accept drifts. diff --git a/Cargo.lock b/Cargo.lock index b00af2e..9a17bbd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3712,7 +3712,7 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "vespera" -version = "0.1.50" +version = "0.1.51" dependencies = [ "axum", "axum-extra", @@ -3731,7 +3731,7 @@ dependencies = [ [[package]] name = "vespera_core" -version = "0.1.50" +version = "0.1.51" dependencies = [ "rstest", "serde", @@ -3740,7 +3740,7 @@ dependencies = [ [[package]] name = "vespera_inprocess" -version = "0.1.50" +version = "0.1.51" dependencies = [ "axum", "http", @@ -3753,7 +3753,7 @@ dependencies = [ [[package]] name = "vespera_jni" -version = "0.1.50" +version = "0.1.51" dependencies = [ "jni", "tokio", @@ -3762,7 +3762,7 @@ dependencies = [ [[package]] name = "vespera_macro" -version = "0.1.50" +version = "0.1.51" dependencies = [ "insta", "proc-macro2", diff --git a/apps/landing/.gitignore b/apps/landing/.gitignore new file mode 100644 index 0000000..5ef6a52 --- /dev/null +++ b/apps/landing/.gitignore @@ -0,0 +1,41 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files (can opt-in for committing if needed) +.env* + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/apps/landing/README.md b/apps/landing/README.md new file mode 100644 index 0000000..e215bc4 --- /dev/null +++ b/apps/landing/README.md @@ -0,0 +1,36 @@ +This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). + +## Getting Started + +First, run the development server: + +```bash +npm run dev +# or +yarn dev +# or +pnpm dev +# or +bun dev +``` + +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. + +You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. + +This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. + +## Learn More + +To learn more about Next.js, take a look at the following resources: + +- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. +- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. + +You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! + +## Deploy on Vercel + +The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. + +Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. diff --git a/apps/landing/devup.json b/apps/landing/devup.json new file mode 100644 index 0000000..e83e008 --- /dev/null +++ b/apps/landing/devup.json @@ -0,0 +1,492 @@ +{ + "theme": { + "colors": { + "light": { + "vesperaPrimary": "#377DFF", + "vesperaPrimaryDark": "#003EA0", + "vespertidePrimary": "#0EC35C", + "vespertidePrimaryDark": "#0A6F36", + "vespertideSelected": "#CDF4CF", + "vespertideHover": "#F2F6F2", + "vespertideActive": "#E9F5E9", + "vespertideSecondary": "#FDF6DE", + "link": "#006BFF", + "text": "#171D2B", + "menutext": "#10131F", + "vesperaBg": "#F5F5F5", + "vespertideBg": "#F6F7F4", + "containerBackground": "#FFF", + "border": "#D6D9DF", + "success": "#4CAF50", + "warning": "#FF9800", + "error": "#F44336", + "info": "#2196F3", + "base": "#FFF", + "negativeBase": "#000", + "title": "#10131F", + "caption": "#9EA5B3", + "textSub": "#4B5263", + "cardBase": "#F3F4F6", + "vesperaHover": "#F9FAFB", + "vesperaSelected": "#DBEBFF", + "vesperaActive": "#EFF6FF" + }, + "dark": { + "vesperaPrimary": "#0058E4", + "vesperaPrimaryDark": "#0D3160", + "vespertidePrimary": "#0EC35C", + "vespertidePrimaryDark": "#0A6F36", + "vespertideSelected": "#3B9D65", + "vespertideHover": "#343B3C", + "vespertideActive": "#1F3724", + "vespertideSecondary": "#151C0B", + "link": "#006BFF", + "text": "#B6B6B6", + "menutext": "#FFF", + "vesperaBg": "#000", + "vespertideBg": "#000", + "containerBackground": "#181D24", + "border": "#626770", + "success": "#4CAF50", + "warning": "#FF9800", + "error": "#F44336", + "info": "#2196F3", + "base": "#000", + "negativeBase": "#FFF", + "title": "#FAFAFA", + "caption": "#959DAA", + "textSub": "#959CAF", + "cardBase": "#14171D", + "vesperaHover": "#212733", + "vesperaSelected": "#003EA0", + "vesperaActive": "#0A274D" + } + }, + "length": { + "light": { + "spacingSpacing02": "2px", + "spacingSpacing04": "4px", + "spacingSpacing06": "6px", + "spacingSpacing08": "8px", + "spacingSpacing12": "12px", + "spacingSpacing16": "16px", + "spacingSpacing20": "20px", + "spacingSpacing24": "24px", + "spacingSpacing32": "32px", + "spacingSpacing40": "40px", + "spacingSpacing48": "48px", + "spacingSpacing64": "64px", + "spacingSpacing80": "80px", + "spacingSpacing120": "120px", + "spacingSpacing160": "160px", + "spacingSpacing200": "200px", + "borderRadiusRadius04": "4px", + "borderRadiusRadius08": "8px", + "borderRadiusRadius12": "12px", + "borderRadiusRadius16": "16px", + "borderRadiusRadius20": "20px", + "borderRadiusRadius24": "24px", + "borderRadiusRadius32": "32px", + "borderRadiusRadius40": "40px", + "borderRadiusRadiusMax": "9999px" + }, + "dark": { + "spacingSpacing02": "2px", + "spacingSpacing04": "4px", + "spacingSpacing06": "6px", + "spacingSpacing08": "8px", + "spacingSpacing12": "12px", + "spacingSpacing16": "16px", + "spacingSpacing20": "20px", + "spacingSpacing24": "24px", + "spacingSpacing32": "32px", + "spacingSpacing40": "40px", + "spacingSpacing48": "48px", + "spacingSpacing64": "64px", + "spacingSpacing80": "80px", + "spacingSpacing120": "120px", + "spacingSpacing160": "160px", + "spacingSpacing200": "200px", + "borderRadiusRadius04": "4px", + "borderRadiusRadius08": "8px", + "borderRadiusRadius12": "12px", + "borderRadiusRadius16": "16px", + "borderRadiusRadius20": "20px", + "borderRadiusRadius24": "24px", + "borderRadiusRadius32": "32px", + "borderRadiusRadius40": "40px", + "borderRadiusRadiusMax": "9999px" + } + }, + "typography": { + "displaySm": { + "fontFamily": "Pretendard", + "fontWeight": 700, + "fontSize": "48px", + "lineHeight": 1.3, + "letterSpacing": "-0.02em" + }, + "h1": [ + { + "fontFamily": "Pretendard", + "fontWeight": 600, + "fontSize": "28px", + "lineHeight": 1.4, + "letterSpacing": "-0.01em" + }, + null, + null, + { + "fontFamily": "Pretendard", + "fontWeight": 700, + "fontSize": "40px", + "lineHeight": 1.4, + "letterSpacing": "-0.01em" + }, + null, + null + ], + "h2": [ + { + "fontFamily": "Pretendard", + "fontWeight": 700, + "fontSize": "24px", + "lineHeight": 1.3, + "letterSpacing": "-0.01em" + }, + null, + null, + { + "fontFamily": "Pretendard", + "fontWeight": 700, + "fontSize": "36px", + "lineHeight": 1.3, + "letterSpacing": "-0.01em" + }, + null, + null + ], + "h3": [ + { + "fontFamily": "Pretendard", + "fontWeight": 700, + "fontSize": "22px", + "lineHeight": 1.4, + "letterSpacing": "-0.01em" + }, + null, + null, + { + "fontFamily": "Pretendard", + "fontWeight": 700, + "fontSize": "32px", + "lineHeight": 1.4, + "letterSpacing": "-0.01em" + }, + null, + null + ], + "h4": [ + { + "fontFamily": "Pretendard", + "fontWeight": 700, + "fontSize": "20px", + "lineHeight": 1.4, + "letterSpacing": "-0.01em" + }, + null, + null, + { + "fontFamily": "Pretendard", + "fontWeight": 700, + "fontSize": "28px", + "lineHeight": 1.4, + "letterSpacing": "-0.01em" + }, + null, + null + ], + "h5": [ + { + "fontFamily": "Pretendard", + "fontWeight": 600, + "fontSize": "18px", + "lineHeight": 1.4, + "letterSpacing": "-0.01em" + }, + null, + null, + { + "fontFamily": "Pretendard", + "fontWeight": 600, + "fontSize": "24px", + "lineHeight": 1.3, + "letterSpacing": "-0.01em" + }, + null, + null + ], + "title": [ + { + "fontFamily": "Pretendard", + "fontWeight": 500, + "fontSize": "17px", + "lineHeight": 1.4, + "letterSpacing": "-0.02em" + }, + null, + null, + { + "fontFamily": "Pretendard", + "fontWeight": 500, + "fontSize": "20px", + "lineHeight": 1.4, + "letterSpacing": "-0.02em" + }, + null, + null + ], + "bodyLg": [ + { + "fontFamily": "Pretendard", + "fontWeight": 500, + "fontSize": "16px", + "lineHeight": 1.6, + "letterSpacing": "-0.02em" + }, + null, + null, + { + "fontFamily": "Pretendard", + "fontWeight": 600, + "fontSize": "18px", + "lineHeight": 1.5, + "letterSpacing": "-0.02em" + }, + null, + null + ], + "body": [ + { + "fontFamily": "Pretendard", + "fontWeight": 500, + "fontSize": "15px", + "lineHeight": 1.5, + "letterSpacing": "-0.02em" + }, + null, + null, + { + "fontFamily": "Pretendard", + "fontWeight": 500, + "fontSize": "16px", + "lineHeight": 1.5, + "letterSpacing": "-0.02em" + }, + null, + null + ], + "bodySm": [ + { + "fontFamily": "Pretendard", + "fontWeight": 400, + "fontSize": "14px", + "lineHeight": 1.5, + "letterSpacing": "-0.02em" + }, + null, + null, + { + "fontFamily": "Pretendard", + "fontWeight": 400, + "fontSize": "15px", + "lineHeight": 1.6, + "letterSpacing": "-0.02em" + }, + null, + null + ], + "caption": [ + { + "fontFamily": "Pretendard", + "fontWeight": 500, + "fontSize": "13px", + "lineHeight": 1.5, + "letterSpacing": "-0.01em" + }, + null, + null, + { + "fontFamily": "Pretendard", + "fontWeight": 500, + "fontSize": "14px", + "lineHeight": 1.5, + "letterSpacing": "-0.01em" + }, + null, + null + ], + "buttonSm": [ + { + "fontFamily": "Pretendard", + "fontWeight": 600, + "fontSize": "14px", + "lineHeight": 1.2, + "letterSpacing": "-0.02em" + }, + null, + null, + { + "fontFamily": "Pretendard", + "fontWeight": 500, + "fontSize": "14px", + "lineHeight": 1.5, + "letterSpacing": "-0.02em" + }, + null, + null + ], + "tiny": { + "fontFamily": "Pretendard", + "fontWeight": 500, + "fontSize": "12px", + "lineHeight": 1.5, + "letterSpacing": "-0.01em" + }, + "titleB": [ + { + "fontFamily": "Pretendard", + "fontWeight": 700, + "fontSize": "17px", + "lineHeight": 1.4, + "letterSpacing": "-0.02em" + }, + null, + null, + { + "fontFamily": "Pretendard", + "fontWeight": 700, + "fontSize": "20px", + "lineHeight": 1.4, + "letterSpacing": "-0.02em" + }, + null, + null + ], + "menu": [ + { + "fontFamily": "Pretendard", + "fontWeight": 600, + "fontSize": "15px", + "lineHeight": 1.3, + "letterSpacing": "0em" + }, + null, + null, + { + "fontFamily": "Pretendard", + "fontWeight": 600, + "fontSize": "16px", + "lineHeight": 1.3, + "letterSpacing": "0em" + }, + null, + null + ], + "captionB": [ + { + "fontFamily": "Pretendard", + "fontWeight": 700, + "fontSize": "13px", + "lineHeight": 1.5, + "letterSpacing": "-0.01em" + }, + null, + null, + { + "fontFamily": "Pretendard", + "fontWeight": 700, + "fontSize": "14px", + "lineHeight": 1.5, + "letterSpacing": "-0.01em" + }, + null, + null + ], + "tinyB": { + "fontFamily": "Pretendard", + "fontWeight": 700, + "fontSize": "12px", + "lineHeight": 1.5, + "letterSpacing": "-0.01em" + }, + "bodyLgEb": [ + { + "fontFamily": "Pretendard", + "fontWeight": 800, + "fontSize": "16px", + "lineHeight": 1.6, + "letterSpacing": "-0.02em" + }, + null, + null, + { + "fontFamily": "Pretendard", + "fontWeight": 800, + "fontSize": "18px", + "lineHeight": 1.5, + "letterSpacing": "-0.02em" + }, + null, + null + ], + "footerB": [ + { + "fontFamily": "Pretendard", + "fontWeight": 700, + "fontSize": "14px", + "lineHeight": 1.6, + "letterSpacing": "-0.02em" + }, + null, + null, + { + "fontFamily": "Pretendard", + "fontWeight": 700, + "fontSize": "15px", + "lineHeight": 1.6, + "letterSpacing": "-0.02em" + }, + null, + null + ], + "footerCopy": [ + { + "fontFamily": "Pretendard", + "fontWeight": 400, + "fontSize": "12px", + "lineHeight": 1.2, + "letterSpacing": "-0.02em" + }, + null, + null, + { + "fontFamily": "Pretendard", + "fontWeight": 500, + "fontSize": "13px", + "lineHeight": 1.2, + "letterSpacing": "-0.02em" + }, + null, + null + ], + "dispalySm": { + "fontFamily": "Pretendard", + "fontWeight": 700, + "fontSize": "32px", + "lineHeight": 1.3, + "letterSpacing": "-0.02em" + } + } + } +} \ No newline at end of file diff --git a/apps/landing/eslint.config.mjs b/apps/landing/eslint.config.mjs new file mode 100644 index 0000000..c9f9713 --- /dev/null +++ b/apps/landing/eslint.config.mjs @@ -0,0 +1,3 @@ +import { configs } from 'eslint-plugin-devup' + +export default configs.recommended diff --git a/apps/landing/next.config.ts b/apps/landing/next.config.ts new file mode 100644 index 0000000..4852158 --- /dev/null +++ b/apps/landing/next.config.ts @@ -0,0 +1,23 @@ +import { devupApi } from '@devup-api/next-plugin' +import { DevupUI } from '@devup-ui/next-plugin' +import createMDX from '@next/mdx' +import type { NextConfig } from 'next' + +const withMDX = createMDX({ + extension: /\.mdx?$/, + options: { + rehypePlugins: ['rehype-slug', 'rehype-pretty-code'], + }, +}) + +const nextConfig: NextConfig = { + /* config options here */ + pageExtensions: ['js', 'jsx', 'md', 'mdx', 'ts', 'tsx'], + output: 'export', + experimental: { + optimizePackageImports: ['@devup-ui/reset-css', '@devup-ui/components'], + }, + reactCompiler: true, +} + +export default DevupUI(devupApi(withMDX(nextConfig))) diff --git a/apps/landing/openapi.json b/apps/landing/openapi.json new file mode 100644 index 0000000..71578cc --- /dev/null +++ b/apps/landing/openapi.json @@ -0,0 +1,37 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "API", + "version": "0.1.0" + }, + "servers": [ + { + "url": "http://localhost:3000" + } + ], + "paths": { + "/health": { + "get": { + "operationId": "health", + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": {}, + "required": [], + "additionalProperties": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "components": {} +} \ No newline at end of file diff --git a/apps/landing/package.json b/apps/landing/package.json new file mode 100644 index 0000000..71b69c6 --- /dev/null +++ b/apps/landing/package.json @@ -0,0 +1,44 @@ +{ + "name": "landing", + "version": "0.1.0", + "type": "module", + "private": true, + "scripts": { + "dev": "node ./script.js && next dev", + "search": "node ./script.js", + "build": "node ./script.js && next build", + "start": "npx serve ./out", + "test": "bun test" + }, + "dependencies": { + "@devup-api/fetch": "^0.1", + "@devup-api/react-query": "^0.1", + "@devup-ui/components": "^0.1.44", + "@devup-ui/react": "^1", + "@devup-ui/reset-css": "^1", + "@mdx-js/loader": "^3.1.1", + "@mdx-js/react": "^3.1.1", + "@next/mdx": "^16.2.4", + "clsx": "^2.1.1", + "next": "^16", + "react": "^19", + "rehype-pretty-code": "^0.14.3", + "rehype-sanitize": "^6.0.0", + "rehype-slug": "^6.0.0", + "rehype-stringify": "^10.0.1", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.1.2", + "shiki": "^4.0.2", + "unified": "^11.0.5" + }, + "devDependencies": { + "@types/mdx": "^2.0.13", + "@devup-api/next-plugin": "^0.1", + "@devup-ui/next-plugin": "^1", + "@types/node": "^25", + "@types/react": "^19", + "@types/react-syntax-highlighter": "^15.5.13", + "babel-plugin-react-compiler": "^1.0.0", + "typescript": "^6.0.3" + } +} \ No newline at end of file diff --git a/apps/landing/public/file.svg b/apps/landing/public/file.svg new file mode 100644 index 0000000..004145c --- /dev/null +++ b/apps/landing/public/file.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/landing/public/globe.svg b/apps/landing/public/globe.svg new file mode 100644 index 0000000..567f17b --- /dev/null +++ b/apps/landing/public/globe.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/landing/public/icons/arrow-up-right.svg b/apps/landing/public/icons/arrow-up-right.svg new file mode 100644 index 0000000..e83c482 --- /dev/null +++ b/apps/landing/public/icons/arrow-up-right.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/apps/landing/public/icons/chevron.svg b/apps/landing/public/icons/chevron.svg new file mode 100644 index 0000000..787fb78 --- /dev/null +++ b/apps/landing/public/icons/chevron.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/landing/public/icons/close.svg b/apps/landing/public/icons/close.svg new file mode 100644 index 0000000..f0cbf71 --- /dev/null +++ b/apps/landing/public/icons/close.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/apps/landing/public/icons/devfive.svg b/apps/landing/public/icons/devfive.svg new file mode 100644 index 0000000..55ca9cd --- /dev/null +++ b/apps/landing/public/icons/devfive.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/landing/public/icons/discord.svg b/apps/landing/public/icons/discord.svg new file mode 100644 index 0000000..27e00cd --- /dev/null +++ b/apps/landing/public/icons/discord.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/landing/public/icons/external-link.svg b/apps/landing/public/icons/external-link.svg new file mode 100644 index 0000000..670253b --- /dev/null +++ b/apps/landing/public/icons/external-link.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/apps/landing/public/icons/github.svg b/apps/landing/public/icons/github.svg new file mode 100644 index 0000000..100f5b3 --- /dev/null +++ b/apps/landing/public/icons/github.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/landing/public/icons/hamburger.svg b/apps/landing/public/icons/hamburger.svg new file mode 100644 index 0000000..59fbaed --- /dev/null +++ b/apps/landing/public/icons/hamburger.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/apps/landing/public/icons/kakao.svg b/apps/landing/public/icons/kakao.svg new file mode 100644 index 0000000..54f71e7 --- /dev/null +++ b/apps/landing/public/icons/kakao.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/landing/public/icons/logo-image.svg b/apps/landing/public/icons/logo-image.svg new file mode 100644 index 0000000..c5aa2e2 --- /dev/null +++ b/apps/landing/public/icons/logo-image.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/apps/landing/public/icons/logo-text.svg b/apps/landing/public/icons/logo-text.svg new file mode 100644 index 0000000..559a457 --- /dev/null +++ b/apps/landing/public/icons/logo-text.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/apps/landing/public/icons/search.svg b/apps/landing/public/icons/search.svg new file mode 100644 index 0000000..d65b2c5 --- /dev/null +++ b/apps/landing/public/icons/search.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/apps/landing/public/icons/theme-dark.svg b/apps/landing/public/icons/theme-dark.svg new file mode 100644 index 0000000..10e348d --- /dev/null +++ b/apps/landing/public/icons/theme-dark.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/landing/public/icons/theme-light.svg b/apps/landing/public/icons/theme-light.svg new file mode 100644 index 0000000..17ea7b5 --- /dev/null +++ b/apps/landing/public/icons/theme-light.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/landing/public/images/code.webp b/apps/landing/public/images/code.webp new file mode 100644 index 0000000..af825e8 Binary files /dev/null and b/apps/landing/public/images/code.webp differ diff --git a/apps/landing/public/images/hero.webp b/apps/landing/public/images/hero.webp new file mode 100644 index 0000000..f58c3ae Binary files /dev/null and b/apps/landing/public/images/hero.webp differ diff --git a/apps/landing/public/images/join-us-bg.webp b/apps/landing/public/images/join-us-bg.webp new file mode 100644 index 0000000..c398521 Binary files /dev/null and b/apps/landing/public/images/join-us-bg.webp differ diff --git a/apps/landing/public/next.svg b/apps/landing/public/next.svg new file mode 100644 index 0000000..5174b28 --- /dev/null +++ b/apps/landing/public/next.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/landing/public/og-image.webp b/apps/landing/public/og-image.webp new file mode 100644 index 0000000..c330ab5 Binary files /dev/null and b/apps/landing/public/og-image.webp differ diff --git a/apps/landing/public/search.json b/apps/landing/public/search.json new file mode 100644 index 0000000..4e5e7d7 --- /dev/null +++ b/apps/landing/public/search.json @@ -0,0 +1 @@ +[null,null,null,null,{"text":"## What is Devup UI?eeeeeeeeeeee\r\n\r\n**Devup UI is not just another CSS-in-JS library — it's the future of CSS-in-JS itself.**\r\n\r\nDevup UI is a zero-runtime CSS-in-JS preprocessor powered by Rust and WebAssembly. It transforms all your styles at build time, completely eliminating runtime overhead while providing full CSS-in-JS syntax coverage.\r\n\r\n### The Problem with Traditional CSS-in-JS\r\n\r\nTraditional CSS-in-JS solutions force you to choose between:\r\n\r\n- **Developer Experience**: Intuitive APIs, co-located styles, dynamic theming\r\n- **Performance**: No runtime overhead, fast page loads, optimal Core Web Vitals\r\n\r\nLibraries like styled-components and Emotion offer great DX but execute JavaScript at runtime to generate styles. Zero-runtime alternatives like Vanilla Extract sacrifice some flexibility for performance.\r\n\r\n### The Devup UI Solution\r\n\r\nDevup UI eliminates this trade-off entirely. Our Rust-powered preprocessor analyzes your code at build time and handles every CSS-in-JS pattern:\r\n\r\n- **Variables** — Dynamic values become CSS custom properties\r\n- **Conditionals** — Ternary expressions are statically analyzed\r\n- **Responsive Arrays** — Breakpoint-based styles are pre-generated\r\n- **Pseudo Selectors** — `_hover`, `_focus`, `_active` work seamlessly\r\n- **Themes** — Type-safe theme tokens with zero-cost switching\r\n\r\n### Key Advantages\r\n\r\n\r\n \r\n \r\n Feature\r\n Devup UI\r\n styled-components\r\n Emotion\r\n Vanilla Extract\r\n \r\n \r\n \r\n \r\n Zero Runtime\r\n Yes\r\n No\r\n No\r\n Yes\r\n \r\n \r\n Dynamic Values\r\n Yes\r\n Yes\r\n Yes\r\n Limited\r\n \r\n \r\n Full Syntax Coverage\r\n Yes\r\n Yes\r\n Yes\r\n No\r\n \r\n \r\n Type-Safe Themes\r\n Yes\r\n Limited\r\n Limited\r\n Yes\r\n \r\n \r\n Build Performance\r\n Fastest\r\n N/A\r\n N/A\r\n Fast\r\n \r\n \r\n
\r\n\r\n### How It Works\r\n\r\n```tsx\r\n// You write familiar CSS-in-JS syntax\r\nconst example = \r\n\r\n// Devup UI transforms it at build time\r\nconst generated =
\r\n\r\n// With optimized atomic CSS\r\n// .a { background-color: red; }\r\n// .b { padding: 16px; } /* 4 * 4 = 16px */\r\n// .c:hover { background-color: blue; }\r\n```\r\n\r\n> Numeric values are multiplied by 4. `p={4}` becomes `padding: 16px`.\r\n\r\nClass names use compact base-37 encoding (`a`, `b`, ... `z`, `_`, `aa`, `ab`, ...) for minimal CSS output.\r\n\r\n### Familiar API\r\n\r\nIf you've used styled-components or Emotion, you'll feel right at home:\r\n\r\n```tsx\r\nimport { styled } from '@devup-ui/react'\r\n\r\nconst Card = styled('div', {\r\n bg: 'white',\r\n p: 4, // 4 * 4 = 16px\r\n borderRadius: '8px',\r\n boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)',\r\n _hover: {\r\n boxShadow: '0 10px 15px rgba(0, 0, 0, 0.1)',\r\n },\r\n})\r\n```\r\n\r\n### Proven Performance\r\n\r\nBenchmarks on Next.js (GitHub Actions - ubuntu-latest):\r\n\r\n\r\n \r\n \r\n Library\r\n Version\r\n Build Time\r\n Build Size\r\n \r\n \r\n \r\n \r\n tailwindcss\r\n 4.1.13\r\n 19.31s\r\n 59,521,539 bytes\r\n \r\n \r\n styleX\r\n 0.15.4\r\n 41.78s\r\n 86,869,452 bytes\r\n \r\n \r\n vanilla-extract\r\n 1.17.4\r\n 19.50s\r\n 61,494,033 bytes\r\n \r\n \r\n kuma-ui\r\n 1.5.9\r\n 20.93s\r\n 69,924,179 bytes\r\n \r\n \r\n panda-css\r\n 1.3.1\r\n 20.64s\r\n 64,573,260 bytes\r\n \r\n \r\n chakra-ui\r\n 3.27.0\r\n 28.81s\r\n 222,435,802 bytes\r\n \r\n \r\n mui\r\n 7.3.2\r\n 20.86s\r\n 97,964,458 bytes\r\n \r\n \r\n **devup-ui (per-file css)**\r\n **1.0.18**\r\n **16.90s**\r\n 59,540,459 bytes\r\n \r\n \r\n **devup-ui (single css)**\r\n **1.0.18**\r\n **17.05s**\r\n **59,520,196 bytes**\r\n \r\n \r\n tailwindcss (turbopack)\r\n 4.1.13\r\n 6.72s\r\n 5,355,082 bytes\r\n \r\n \r\n **devup-ui (single css + turbopack)**\r\n **1.0.18**\r\n 10.34s\r\n **4,772,050 bytes**\r\n \r\n \r\n
\r\n\r\n### Get Started\r\n\r\nReady to experience the future of CSS-in-JS? Head to the [Installation](/docs/installation) guide to get started in minutes.\r\n","title":"What is Devup UI?eeeeeeeeeeee","url":"/documentation/concept/concept-1"},null,null,null,null,null,{"text":"## What is Devup UI?\r\n\r\n**Devup UI is not just another CSS-in-JS library — it's the future of CSS-in-JS itself.**\r\n\r\nDevup UI is a zero-runtime CSS-in-JS preprocessor powered by Rust and WebAssembly. It transforms all your styles at build time, completely eliminating runtime overhead while providing full CSS-in-JS syntax coverage.\r\n\r\n### The Problem with Traditional CSS-in-JS\r\n\r\nTraditional CSS-in-JS solutions force you to choose between:\r\n\r\n- **Developer Experience**: Intuitive APIs, co-located styles, dynamic theming\r\n- **Performance**: No runtime overhead, fast page loads, optimal Core Web Vitals\r\n\r\nLibraries like styled-components and Emotion offer great DX but execute JavaScript at runtime to generate styles. Zero-runtime alternatives like Vanilla Extract sacrifice some flexibility for performance.\r\n\r\n### The Devup UI Solution\r\n\r\nDevup UI eliminates this trade-off entirely. Our Rust-powered preprocessor analyzes your code at build time and handles every CSS-in-JS pattern:\r\n\r\n- **Variables** — Dynamic values become CSS custom properties\r\n- **Conditionals** — Ternary expressions are statically analyzed\r\n- **Responsive Arrays** — Breakpoint-based styles are pre-generated\r\n- **Pseudo Selectors** — `_hover`, `_focus`, `_active` work seamlessly\r\n- **Themes** — Type-safe theme tokens with zero-cost switching\r\n\r\n### Key Advantages\r\n\r\n\r\n \r\n \r\n Feature\r\n Devup UI\r\n styled-components\r\n Emotion\r\n Vanilla Extract\r\n \r\n \r\n \r\n \r\n Zero Runtime\r\n Yes\r\n No\r\n No\r\n Yes\r\n \r\n \r\n Dynamic Values\r\n Yes\r\n Yes\r\n Yes\r\n Limited\r\n \r\n \r\n Full Syntax Coverage\r\n Yes\r\n Yes\r\n Yes\r\n No\r\n \r\n \r\n Type-Safe Themes\r\n Yes\r\n Limited\r\n Limited\r\n Yes\r\n \r\n \r\n Build Performance\r\n Fastest\r\n N/A\r\n N/A\r\n Fast\r\n \r\n \r\n
\r\n\r\n### How It Works\r\n\r\n```tsx\r\n// You write familiar CSS-in-JS syntax\r\nconst example = \r\n\r\n// Devup UI transforms it at build time\r\nconst generated =
\r\n\r\n// With optimized atomic CSS\r\n// .a { background-color: red; }\r\n// .b { padding: 16px; } /* 4 * 4 = 16px */\r\n// .c:hover { background-color: blue; }\r\n```\r\n\r\n> Numeric values are multiplied by 4. `p={4}` becomes `padding: 16px`.\r\n\r\nClass names use compact base-37 encoding (`a`, `b`, ... `z`, `_`, `aa`, `ab`, ...) for minimal CSS output.\r\n\r\n### Familiar API\r\n\r\nIf you've used styled-components or Emotion, you'll feel right at home:\r\n\r\n```tsx\r\nimport { styled } from '@devup-ui/react'\r\n\r\nconst Card = styled('div', {\r\n bg: 'white',\r\n p: 4, // 4 * 4 = 16px\r\n borderRadius: '8px',\r\n boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)',\r\n _hover: {\r\n boxShadow: '0 10px 15px rgba(0, 0, 0, 0.1)',\r\n },\r\n})\r\n```\r\n\r\n### Proven Performance\r\n\r\nBenchmarks on Next.js (GitHub Actions - ubuntu-latest):\r\n\r\n\r\n \r\n \r\n Library\r\n Version\r\n Build Time\r\n Build Size\r\n \r\n \r\n \r\n \r\n tailwindcss\r\n 4.1.13\r\n 19.31s\r\n 59,521,539 bytes\r\n \r\n \r\n styleX\r\n 0.15.4\r\n 41.78s\r\n 86,869,452 bytes\r\n \r\n \r\n vanilla-extract\r\n 1.17.4\r\n 19.50s\r\n 61,494,033 bytes\r\n \r\n \r\n kuma-ui\r\n 1.5.9\r\n 20.93s\r\n 69,924,179 bytes\r\n \r\n \r\n panda-css\r\n 1.3.1\r\n 20.64s\r\n 64,573,260 bytes\r\n \r\n \r\n chakra-ui\r\n 3.27.0\r\n 28.81s\r\n 222,435,802 bytes\r\n \r\n \r\n mui\r\n 7.3.2\r\n 20.86s\r\n 97,964,458 bytes\r\n \r\n \r\n **devup-ui (per-file css)**\r\n **1.0.18**\r\n **16.90s**\r\n 59,540,459 bytes\r\n \r\n \r\n **devup-ui (single css)**\r\n **1.0.18**\r\n **17.05s**\r\n **59,520,196 bytes**\r\n \r\n \r\n tailwindcss (turbopack)\r\n 4.1.13\r\n 6.72s\r\n 5,355,082 bytes\r\n \r\n \r\n **devup-ui (single css + turbopack)**\r\n **1.0.18**\r\n 10.34s\r\n **4,772,050 bytes**\r\n \r\n \r\n
\r\n\r\n### Get Started\r\n\r\nReady to experience the future of CSS-in-JS? Head to the [Installation](/docs/installation) guide to get started in minutes.\r\n","title":"What is Devup UI?","url":"/documentation/overview"},null,null,null,null] \ No newline at end of file diff --git a/apps/landing/public/vercel.svg b/apps/landing/public/vercel.svg new file mode 100644 index 0000000..7705396 --- /dev/null +++ b/apps/landing/public/vercel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/landing/public/window.svg b/apps/landing/public/window.svg new file mode 100644 index 0000000..b2b2a44 --- /dev/null +++ b/apps/landing/public/window.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/landing/script.js b/apps/landing/script.js new file mode 100644 index 0000000..8ea6a2c --- /dev/null +++ b/apps/landing/script.js @@ -0,0 +1,40 @@ +import { glob, readFile, writeFile } from 'node:fs/promises' + +const files = await glob('src/**/*.mdx') + +const q = [] +for await (const file of files) { + q.push( + readFile(file, { + encoding: 'utf-8', + }).then((content) => { + const titleIndex = content.toString().indexOf('#') + if (content.trim().length === 0 || titleIndex === -1) { + return null + } + const titleMatch = /^#+\s+(.*)/m.exec(content.toString()) + if (!titleMatch) { + return null + } + + const url = + '/' + + file + .replace(/\\/g, '/') + .replace(/^src\/app\//, '') + .replace(/\/\[\.\.\.[^\]]+\]\//, '/') + .replace(/\.mdx$/, '') + .replace(/\./g, '/') + + return { + text: content.toString().substring(titleIndex), + title: titleMatch[1], + url, + } + }), + ) +} + +const res = await Promise.all(q) + +await writeFile('public/search.json', JSON.stringify(res)) diff --git a/apps/landing/src/__tests__/__snapshots__/page.browser.test.tsx.snap b/apps/landing/src/__tests__/__snapshots__/page.browser.test.tsx.snap new file mode 100644 index 0000000..7e0d66c --- /dev/null +++ b/apps/landing/src/__tests__/__snapshots__/page.browser.test.tsx.snap @@ -0,0 +1,15 @@ +// Bun Snapshot v1, https://bun.sh/docs/test/snapshots + +exports[`HomePage should render 1`] = ` +"
+ +
" +`; + +exports[`HomePage should render 2`] = ` +"
+ HomePage +
" +`; diff --git a/apps/landing/src/__tests__/page.browser.test.tsx b/apps/landing/src/__tests__/page.browser.test.tsx new file mode 100644 index 0000000..158d929 --- /dev/null +++ b/apps/landing/src/__tests__/page.browser.test.tsx @@ -0,0 +1,13 @@ +import { ThemeScript } from '@devup-ui/react' +import { describe, expect, it } from 'bun:test' +import { render } from 'bun-test-env-dom' + +import HomePage from '@/app/page' + +describe('HomePage', () => { + it('should render', () => { + const { container } = render() + expect(container).toMatchSnapshot() + expect().toMatchSnapshot() + }) +}) diff --git a/apps/landing/src/__tests__/test.test.ts b/apps/landing/src/__tests__/test.test.ts new file mode 100644 index 0000000..a22ef26 --- /dev/null +++ b/apps/landing/src/__tests__/test.test.ts @@ -0,0 +1,7 @@ +import { describe, expect, it } from 'bun:test' + +describe('Test', () => { + it('should be true', () => { + expect(true).toBe(true) + }) +}) diff --git a/apps/landing/src/app/about-us/page.tsx b/apps/landing/src/app/about-us/page.tsx new file mode 100644 index 0000000..6407c88 --- /dev/null +++ b/apps/landing/src/app/about-us/page.tsx @@ -0,0 +1,60 @@ +import { Grid, Text, VStack } from '@devup-ui/react' +import type { Metadata } from 'next' + +import { AboutUs } from '@/components/about-us' + +export const metadata: Metadata = { + title: 'Vespera - About us', + description: 'About the team behind Vespera', + alternates: { + canonical: '/about-us', + }, + openGraph: { + title: 'Vespera - About us', + description: 'About the team behind Vespera', + url: '/about-us', + siteName: 'Vespera', + }, +} + +export default function Page() { + return ( + + + About us + + + + + Title + + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam + venenatis, elit in hendrerit porta, augue ante scelerisque diam, ac + egestas lacus est nec urna. Cras commodo risus hendrerit, suscipit + nibh at, porttitor dui. + + + + + + + + + + ) +} diff --git a/apps/landing/src/app/documentation/[...name]/api.api-1.mdx b/apps/landing/src/app/documentation/[...name]/api.api-1.mdx new file mode 100644 index 0000000..7b4d68d --- /dev/null +++ b/apps/landing/src/app/documentation/[...name]/api.api-1.mdx @@ -0,0 +1 @@ +empty \ No newline at end of file diff --git a/apps/landing/src/app/documentation/[...name]/api.api-2.mdx b/apps/landing/src/app/documentation/[...name]/api.api-2.mdx new file mode 100644 index 0000000..7b4d68d --- /dev/null +++ b/apps/landing/src/app/documentation/[...name]/api.api-2.mdx @@ -0,0 +1 @@ +empty \ No newline at end of file diff --git a/apps/landing/src/app/documentation/[...name]/api.api-3.mdx b/apps/landing/src/app/documentation/[...name]/api.api-3.mdx new file mode 100644 index 0000000..7b4d68d --- /dev/null +++ b/apps/landing/src/app/documentation/[...name]/api.api-3.mdx @@ -0,0 +1 @@ +empty \ No newline at end of file diff --git a/apps/landing/src/app/documentation/[...name]/api.mdx b/apps/landing/src/app/documentation/[...name]/api.mdx new file mode 100644 index 0000000..7b4d68d --- /dev/null +++ b/apps/landing/src/app/documentation/[...name]/api.mdx @@ -0,0 +1 @@ +empty \ No newline at end of file diff --git a/apps/landing/src/app/documentation/[...name]/concept.concept-1.mdx b/apps/landing/src/app/documentation/[...name]/concept.concept-1.mdx new file mode 100644 index 0000000..56a0be6 --- /dev/null +++ b/apps/landing/src/app/documentation/[...name]/concept.concept-1.mdx @@ -0,0 +1,215 @@ +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeaderCell, + TableRow, +} from '@/components/mdx/components/Table' + +export const metadata = { + title: 'What is Devup UI?', + alternates: { + canonical: '/docs/overview', + }, +} + +## What is Devup UI?eeeeeeeeeeee + +**Devup UI is not just another CSS-in-JS library — it's the future of CSS-in-JS itself.** + +Devup UI is a zero-runtime CSS-in-JS preprocessor powered by Rust and WebAssembly. It transforms all your styles at build time, completely eliminating runtime overhead while providing full CSS-in-JS syntax coverage. + +### The Problem with Traditional CSS-in-JS + +Traditional CSS-in-JS solutions force you to choose between: + +- **Developer Experience**: Intuitive APIs, co-located styles, dynamic theming +- **Performance**: No runtime overhead, fast page loads, optimal Core Web Vitals + +Libraries like styled-components and Emotion offer great DX but execute JavaScript at runtime to generate styles. Zero-runtime alternatives like Vanilla Extract sacrifice some flexibility for performance. + +### The Devup UI Solution + +Devup UI eliminates this trade-off entirely. Our Rust-powered preprocessor analyzes your code at build time and handles every CSS-in-JS pattern: + +- **Variables** — Dynamic values become CSS custom properties +- **Conditionals** — Ternary expressions are statically analyzed +- **Responsive Arrays** — Breakpoint-based styles are pre-generated +- **Pseudo Selectors** — `_hover`, `_focus`, `_active` work seamlessly +- **Themes** — Type-safe theme tokens with zero-cost switching + +### Key Advantages + + + + + Feature + Devup UI + styled-components + Emotion + Vanilla Extract + + + + + Zero Runtime + Yes + No + No + Yes + + + Dynamic Values + Yes + Yes + Yes + Limited + + + Full Syntax Coverage + Yes + Yes + Yes + No + + + Type-Safe Themes + Yes + Limited + Limited + Yes + + + Build Performance + Fastest + N/A + N/A + Fast + + +
+ +### How It Works + +```tsx +// You write familiar CSS-in-JS syntax +const example = + +// Devup UI transforms it at build time +const generated =
+ +// With optimized atomic CSS +// .a { background-color: red; } +// .b { padding: 16px; } /* 4 * 4 = 16px */ +// .c:hover { background-color: blue; } +``` + +> Numeric values are multiplied by 4. `p={4}` becomes `padding: 16px`. + +Class names use compact base-37 encoding (`a`, `b`, ... `z`, `_`, `aa`, `ab`, ...) for minimal CSS output. + +### Familiar API + +If you've used styled-components or Emotion, you'll feel right at home: + +```tsx +import { styled } from '@devup-ui/react' + +const Card = styled('div', { + bg: 'white', + p: 4, // 4 * 4 = 16px + borderRadius: '8px', + boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)', + _hover: { + boxShadow: '0 10px 15px rgba(0, 0, 0, 0.1)', + }, +}) +``` + +### Proven Performance + +Benchmarks on Next.js (GitHub Actions - ubuntu-latest): + + + + + Library + Version + Build Time + Build Size + + + + + tailwindcss + 4.1.13 + 19.31s + 59,521,539 bytes + + + styleX + 0.15.4 + 41.78s + 86,869,452 bytes + + + vanilla-extract + 1.17.4 + 19.50s + 61,494,033 bytes + + + kuma-ui + 1.5.9 + 20.93s + 69,924,179 bytes + + + panda-css + 1.3.1 + 20.64s + 64,573,260 bytes + + + chakra-ui + 3.27.0 + 28.81s + 222,435,802 bytes + + + mui + 7.3.2 + 20.86s + 97,964,458 bytes + + + **devup-ui (per-file css)** + **1.0.18** + **16.90s** + 59,540,459 bytes + + + **devup-ui (single css)** + **1.0.18** + **17.05s** + **59,520,196 bytes** + + + tailwindcss (turbopack) + 4.1.13 + 6.72s + 5,355,082 bytes + + + **devup-ui (single css + turbopack)** + **1.0.18** + 10.34s + **4,772,050 bytes** + + +
+ +### Get Started + +Ready to experience the future of CSS-in-JS? Head to the [Installation](/docs/installation) guide to get started in minutes. diff --git a/apps/landing/src/app/documentation/[...name]/concept.concept-2.mdx b/apps/landing/src/app/documentation/[...name]/concept.concept-2.mdx new file mode 100644 index 0000000..7b4d68d --- /dev/null +++ b/apps/landing/src/app/documentation/[...name]/concept.concept-2.mdx @@ -0,0 +1 @@ +empty \ No newline at end of file diff --git a/apps/landing/src/app/documentation/[...name]/concept.concept-3.mdx b/apps/landing/src/app/documentation/[...name]/concept.concept-3.mdx new file mode 100644 index 0000000..7b4d68d --- /dev/null +++ b/apps/landing/src/app/documentation/[...name]/concept.concept-3.mdx @@ -0,0 +1 @@ +empty \ No newline at end of file diff --git a/apps/landing/src/app/documentation/[...name]/concept.mdx b/apps/landing/src/app/documentation/[...name]/concept.mdx new file mode 100644 index 0000000..e69de29 diff --git a/apps/landing/src/app/documentation/[...name]/features.mdx b/apps/landing/src/app/documentation/[...name]/features.mdx new file mode 100644 index 0000000..7b4d68d --- /dev/null +++ b/apps/landing/src/app/documentation/[...name]/features.mdx @@ -0,0 +1 @@ +empty \ No newline at end of file diff --git a/apps/landing/src/app/documentation/[...name]/installation.mdx b/apps/landing/src/app/documentation/[...name]/installation.mdx new file mode 100644 index 0000000..7b4d68d --- /dev/null +++ b/apps/landing/src/app/documentation/[...name]/installation.mdx @@ -0,0 +1 @@ +empty \ No newline at end of file diff --git a/apps/landing/src/app/documentation/[...name]/overview.mdx b/apps/landing/src/app/documentation/[...name]/overview.mdx new file mode 100644 index 0000000..2f40a4b --- /dev/null +++ b/apps/landing/src/app/documentation/[...name]/overview.mdx @@ -0,0 +1,215 @@ +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeaderCell, + TableRow, +} from '@/components/mdx/components/Table' + +export const metadata = { + title: 'What is Devup UI?', + alternates: { + canonical: '/docs/overview', + }, +} + +## What is Devup UI? + +**Devup UI is not just another CSS-in-JS library — it's the future of CSS-in-JS itself.** + +Devup UI is a zero-runtime CSS-in-JS preprocessor powered by Rust and WebAssembly. It transforms all your styles at build time, completely eliminating runtime overhead while providing full CSS-in-JS syntax coverage. + +### The Problem with Traditional CSS-in-JS + +Traditional CSS-in-JS solutions force you to choose between: + +- **Developer Experience**: Intuitive APIs, co-located styles, dynamic theming +- **Performance**: No runtime overhead, fast page loads, optimal Core Web Vitals + +Libraries like styled-components and Emotion offer great DX but execute JavaScript at runtime to generate styles. Zero-runtime alternatives like Vanilla Extract sacrifice some flexibility for performance. + +### The Devup UI Solution + +Devup UI eliminates this trade-off entirely. Our Rust-powered preprocessor analyzes your code at build time and handles every CSS-in-JS pattern: + +- **Variables** — Dynamic values become CSS custom properties +- **Conditionals** — Ternary expressions are statically analyzed +- **Responsive Arrays** — Breakpoint-based styles are pre-generated +- **Pseudo Selectors** — `_hover`, `_focus`, `_active` work seamlessly +- **Themes** — Type-safe theme tokens with zero-cost switching + +### Key Advantages + + + + + Feature + Devup UI + styled-components + Emotion + Vanilla Extract + + + + + Zero Runtime + Yes + No + No + Yes + + + Dynamic Values + Yes + Yes + Yes + Limited + + + Full Syntax Coverage + Yes + Yes + Yes + No + + + Type-Safe Themes + Yes + Limited + Limited + Yes + + + Build Performance + Fastest + N/A + N/A + Fast + + +
+ +### How It Works + +```tsx +// You write familiar CSS-in-JS syntax +const example = + +// Devup UI transforms it at build time +const generated =
+ +// With optimized atomic CSS +// .a { background-color: red; } +// .b { padding: 16px; } /* 4 * 4 = 16px */ +// .c:hover { background-color: blue; } +``` + +> Numeric values are multiplied by 4. `p={4}` becomes `padding: 16px`. + +Class names use compact base-37 encoding (`a`, `b`, ... `z`, `_`, `aa`, `ab`, ...) for minimal CSS output. + +### Familiar API + +If you've used styled-components or Emotion, you'll feel right at home: + +```tsx +import { styled } from '@devup-ui/react' + +const Card = styled('div', { + bg: 'white', + p: 4, // 4 * 4 = 16px + borderRadius: '8px', + boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)', + _hover: { + boxShadow: '0 10px 15px rgba(0, 0, 0, 0.1)', + }, +}) +``` + +### Proven Performance + +Benchmarks on Next.js (GitHub Actions - ubuntu-latest): + + + + + Library + Version + Build Time + Build Size + + + + + tailwindcss + 4.1.13 + 19.31s + 59,521,539 bytes + + + styleX + 0.15.4 + 41.78s + 86,869,452 bytes + + + vanilla-extract + 1.17.4 + 19.50s + 61,494,033 bytes + + + kuma-ui + 1.5.9 + 20.93s + 69,924,179 bytes + + + panda-css + 1.3.1 + 20.64s + 64,573,260 bytes + + + chakra-ui + 3.27.0 + 28.81s + 222,435,802 bytes + + + mui + 7.3.2 + 20.86s + 97,964,458 bytes + + + **devup-ui (per-file css)** + **1.0.18** + **16.90s** + 59,540,459 bytes + + + **devup-ui (single css)** + **1.0.18** + **17.05s** + **59,520,196 bytes** + + + tailwindcss (turbopack) + 4.1.13 + 6.72s + 5,355,082 bytes + + + **devup-ui (single css + turbopack)** + **1.0.18** + 10.34s + **4,772,050 bytes** + + +
+ +### Get Started + +Ready to experience the future of CSS-in-JS? Head to the [Installation](/docs/installation) guide to get started in minutes. diff --git a/apps/landing/src/app/documentation/[...name]/page.tsx b/apps/landing/src/app/documentation/[...name]/page.tsx new file mode 100644 index 0000000..ae54c5f --- /dev/null +++ b/apps/landing/src/app/documentation/[...name]/page.tsx @@ -0,0 +1,72 @@ +import type { Metadata } from 'next' +import { notFound } from 'next/navigation' + +import { SIDE_MENU_ITEMS, SideMenuItem } from '@/constants' + +function getPageNamesFromSideMenuItems(items: SideMenuItem[]): string[] { + function joinNames(item: SideMenuItem, prefix: string = ''): string[] { + const name = [...(prefix ? [prefix] : []), item.value].join('.') + return [ + name, + ...(item.children?.flatMap((child) => joinNames(child, name)) ?? []), + ] + } + + return items.flatMap((item) => joinNames(item)) +} + +function findLabelFromSegments( + items: SideMenuItem[], + segments: string[], +): string | undefined { + const [head, ...rest] = segments + const match = items.find((item) => item.value === head) + if (!match) return undefined + if (rest.length === 0) return match.label + return findLabelFromSegments(match.children ?? [], rest) +} + +export const dynamicParams = false + +export function generateStaticParams() { + const names = getPageNamesFromSideMenuItems(SIDE_MENU_ITEMS.documentation) + return names.map((name) => ({ name: name.split('.') })) +} + +export async function generateMetadata({ + params, +}: { + params: Promise<{ name: string[] }> +}): Promise { + const { name } = await params + const label = + findLabelFromSegments(SIDE_MENU_ITEMS.documentation, name) ?? name.join(' ') + const title = `Vespera - ${label}` + const description = `${label} · Vespera documentation` + const url = `/documentation/${name.join('/')}` + return { + title, + description, + alternates: { + canonical: url, + }, + openGraph: { + title, + description, + url, + siteName: 'Vespera', + }, + } +} + +export default async function Page({ + params, +}: { + params: Promise<{ name: string[] }> +}) { + const { name } = await params + const names = getPageNamesFromSideMenuItems(SIDE_MENU_ITEMS.documentation) + if (!names.includes(name.join('.'))) notFound() + const { default: Documentation } = await import(`./${name.join('.')}.mdx`) + return +} diff --git a/apps/landing/src/app/documentation/[...name]/theme.mdx b/apps/landing/src/app/documentation/[...name]/theme.mdx new file mode 100644 index 0000000..7b4d68d --- /dev/null +++ b/apps/landing/src/app/documentation/[...name]/theme.mdx @@ -0,0 +1 @@ +empty \ No newline at end of file diff --git a/apps/landing/src/app/documentation/[...name]/theme.theme-1.mdx b/apps/landing/src/app/documentation/[...name]/theme.theme-1.mdx new file mode 100644 index 0000000..7b4d68d --- /dev/null +++ b/apps/landing/src/app/documentation/[...name]/theme.theme-1.mdx @@ -0,0 +1 @@ +empty \ No newline at end of file diff --git a/apps/landing/src/app/documentation/[...name]/theme.theme-2.mdx b/apps/landing/src/app/documentation/[...name]/theme.theme-2.mdx new file mode 100644 index 0000000..7b4d68d --- /dev/null +++ b/apps/landing/src/app/documentation/[...name]/theme.theme-2.mdx @@ -0,0 +1 @@ +empty \ No newline at end of file diff --git a/apps/landing/src/app/documentation/[...name]/theme.theme-3.mdx b/apps/landing/src/app/documentation/[...name]/theme.theme-3.mdx new file mode 100644 index 0000000..e69de29 diff --git a/apps/landing/src/app/documentation/_components/edit.tsx b/apps/landing/src/app/documentation/_components/edit.tsx new file mode 100644 index 0000000..904632c --- /dev/null +++ b/apps/landing/src/app/documentation/_components/edit.tsx @@ -0,0 +1,36 @@ +import { Box, Flex, Text } from '@devup-ui/react' + +export function Edit() { + return ( + + + Edit this page + + + + ) +} diff --git a/apps/landing/src/app/documentation/_components/search-sheet/index.tsx b/apps/landing/src/app/documentation/_components/search-sheet/index.tsx new file mode 100644 index 0000000..d157b9b --- /dev/null +++ b/apps/landing/src/app/documentation/_components/search-sheet/index.tsx @@ -0,0 +1,45 @@ +import { Box, css } from '@devup-ui/react' + +import { Effect } from '@/components/header/effect' +import { Search as SearchComponent } from '@/components/search' +import { SearchForm } from '@/components/search/form' +import { SheetRouteTrigger } from '@/components/sheet/router' + +import { SearchSheetRouteContainer } from './route-container' + +export function SearchSheet() { + return ( + + + + + + + + + + + ) +} diff --git a/apps/landing/src/app/documentation/_components/search-sheet/route-container.tsx b/apps/landing/src/app/documentation/_components/search-sheet/route-container.tsx new file mode 100644 index 0000000..747f1b2 --- /dev/null +++ b/apps/landing/src/app/documentation/_components/search-sheet/route-container.tsx @@ -0,0 +1,25 @@ +'use client' +import { ComponentProps } from 'react' + +import { useSearchContext } from '@/components/search/provider' +import { SheetRouteContainer } from '@/components/sheet/router' + +export function RouteContainer( + props: ComponentProps, +) { + const { insideClickRefs } = useSearchContext() + return ( + { + if (!el) return + insideClickRefs.current.add(el) + return () => { + insideClickRefs.current.delete(el) + } + }} + {...props} + /> + ) +} + +export { RouteContainer as SearchSheetRouteContainer } diff --git a/apps/landing/src/app/documentation/layout.tsx b/apps/landing/src/app/documentation/layout.tsx new file mode 100644 index 0000000..edda028 --- /dev/null +++ b/apps/landing/src/app/documentation/layout.tsx @@ -0,0 +1,93 @@ +import { Box, Flex, Text, VStack } from '@devup-ui/react' + +import { SideMenu } from '@/components/side-menu' +import { SideMenuProvider } from '@/components/side-menu/side-menu-provider' +import { TableOfContentsProvider } from '@/components/table-of-contents' +import { + TableOfContentsAnchor, + TableOfContentsIterator, +} from '@/components/table-of-contents/iterator' +import { SIDE_MENU_ITEMS } from '@/constants' + +import { Edit } from './_components/edit' + +export default function PageLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( + + + + + + + {SIDE_MENU_ITEMS.documentation.map( + ({ value, label, children }) => ( + + {label} + + ), + )} + + + + + + + + {children} + + + + + + + Contents + + + + + + + + + + + + + + ) +} diff --git a/apps/landing/src/app/documentation/page.tsx b/apps/landing/src/app/documentation/page.tsx new file mode 100644 index 0000000..0dd5b2a --- /dev/null +++ b/apps/landing/src/app/documentation/page.tsx @@ -0,0 +1,20 @@ +import type { Metadata } from 'next' +import { redirect } from 'next/navigation' + +export const metadata: Metadata = { + title: 'Vespera - Documentation', + description: 'Vespera documentation', + alternates: { + canonical: '/documentation', + }, + openGraph: { + title: 'Vespera - Documentation', + description: 'Vespera documentation', + url: '/documentation', + siteName: 'Vespera', + }, +} + +export default function Page() { + redirect('/documentation/overview') +} diff --git a/apps/landing/src/app/favicon.ico b/apps/landing/src/app/favicon.ico new file mode 100644 index 0000000..3b28cd5 Binary files /dev/null and b/apps/landing/src/app/favicon.ico differ diff --git a/apps/landing/src/app/layout.tsx b/apps/landing/src/app/layout.tsx new file mode 100644 index 0000000..77c45a5 --- /dev/null +++ b/apps/landing/src/app/layout.tsx @@ -0,0 +1,193 @@ +import { globalCss, ThemeScript } from '@devup-ui/react' +import { resetCss } from '@devup-ui/reset-css' +import type { Metadata } from 'next' + +import { Footer } from '@/components/footer' +import { Header } from '@/components/header' +import { HeaderProvider } from '@/components/header/header-provider' +import { MobileMenu } from '@/components/mobile-menu' +import { SearchDimmer } from '@/components/search/dimmer' +import { + SearchContextBoundary, + SearchProvider, +} from '@/components/search/provider' +import { SearchResult } from '@/components/search/result' +import { + SheetRoute, + SheetRouteBoundary, + SheetRouter, +} from '@/components/sheet/router' + +import { SearchSheet } from './documentation/_components/search-sheet' + +resetCss() + +globalCss({ + html: { + scrollPaddingTop: '68px', + }, + body: { + fontFamily: 'Pretendard', + }, + figure: { + margin: 0, + }, + code: { + py: '8px', + px: '16px', + fontFamily: 'D2Coding', + fontSize: ['13px', '15px'], + fontStyle: 'normal', + fontWeight: 700, + lineHeight: '1.5', + letterSpacing: '-0.03em', + }, + pre: { + borderRadius: '10px', + }, + 'pre,code,figure': { + overflowX: 'auto', + }, + 'pre>code': { + overflowX: 'auto', + }, + a: { + textDecoration: 'none', + }, + fontFaces: [ + { + fontFamily: 'Pretendard', + src: 'url(https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/packages/pretendard/dist/web/static/woff2/Pretendard-ExtraBold.woff2) format("woff2")', + fontWeight: 800, + fontStyle: 'normal', + }, + { + fontFamily: 'Pretendard', + src: 'url(https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/packages/pretendard/dist/web/static/woff2/Pretendard-Bold.woff2) format("woff2")', + fontWeight: 700, + fontStyle: 'normal', + }, + { + fontFamily: 'Pretendard', + src: 'url(https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/packages/pretendard/dist/web/static/woff2/Pretendard-SemiBold.woff2) format("woff2")', + fontWeight: 600, + fontStyle: 'normal', + }, + { + fontFamily: 'Pretendard', + src: 'url(https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/packages/pretendard/dist/web/static/woff2/Pretendard-Medium.woff2) format("woff2")', + fontWeight: 500, + fontStyle: 'normal', + }, + { + fontFamily: 'Pretendard', + src: 'url(https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/packages/pretendard/dist/web/static/woff2/Pretendard-Regular.woff2) format("woff2")', + fontWeight: 400, + fontStyle: 'normal', + }, + { + fontFamily: 'Pretendard', + src: 'url(https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/packages/pretendard/dist/web/static/woff2/Pretendard-Light.woff2) format("woff2")', + fontWeight: 300, + fontStyle: 'normal', + }, + { + fontFamily: 'Pretendard', + src: 'url(https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/packages/pretendard/dist/web/static/woff2/Pretendard-Thin.woff2) format("woff2")', + fontWeight: 100, + fontStyle: 'normal', + }, + { + fontFamily: 'D2Coding', + src: 'url(https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_three@1.0/D2Coding.woff) format("woff")', + fontWeight: 400, + fontDisplay: 'swap', + }, + ], +}) + +export const metadata: Metadata = { + title: 'Vespera', + description: 'FastAPI-like DX for Rust/Axum with automated OpenAPI 3.1', + alternates: { + canonical: 'https://vespera.devfive.kr', + }, + metadataBase: new URL('https://vespera.devfive.kr'), + openGraph: { + title: 'Vespera', + description: 'FastAPI-like DX for Rust/Axum with automated OpenAPI 3.1', + images: ['https://vespera.devfive.kr/og-image.webp'], + siteName: 'Vespera', + type: 'website', + url: 'https://vespera.devfive.kr', + }, +} + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode +}>) { + return ( + + + + {[ + 'ExtraBold', + 'Bold', + 'SemiBold', + 'Medium', + 'Regular', + 'Light', + 'Thin', + ].map((font) => ( + + ))} + + + + + + + + + + + +
+ + + + + + + + + + + + + {children} +