diff --git a/.github/workflows/deploy-pages.yml b/.github/workflows/deploy-pages.yml new file mode 100644 index 00000000..294816c7 --- /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 b1ae8261..95c509c2 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ coverage lcov.info .sisyphus .omc +node_modules diff --git a/Cargo.lock b/Cargo.lock index 1cdbe4c6..63a30e1f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3713,7 +3713,7 @@ dependencies = [ [[package]] name = "vespertide" -version = "0.1.60" +version = "0.1.61" dependencies = [ "sea-orm", "tokio", @@ -3723,7 +3723,7 @@ dependencies = [ [[package]] name = "vespertide-cli" -version = "0.1.60" +version = "0.1.61" dependencies = [ "anyhow", "assert_cmd", @@ -3752,7 +3752,7 @@ dependencies = [ [[package]] name = "vespertide-config" -version = "0.1.60" +version = "0.1.61" dependencies = [ "clap", "schemars", @@ -3762,7 +3762,7 @@ dependencies = [ [[package]] name = "vespertide-core" -version = "0.1.60" +version = "0.1.61" dependencies = [ "rstest", "schemars", @@ -3774,7 +3774,7 @@ dependencies = [ [[package]] name = "vespertide-exporter" -version = "0.1.60" +version = "0.1.61" dependencies = [ "insta", "rstest", @@ -3786,7 +3786,7 @@ dependencies = [ [[package]] name = "vespertide-loader" -version = "0.1.60" +version = "0.1.61" dependencies = [ "anyhow", "rstest", @@ -3801,7 +3801,7 @@ dependencies = [ [[package]] name = "vespertide-macro" -version = "0.1.60" +version = "0.1.61" dependencies = [ "proc-macro2", "runtime-macros", @@ -3816,11 +3816,11 @@ dependencies = [ [[package]] name = "vespertide-naming" -version = "0.1.60" +version = "0.1.61" [[package]] name = "vespertide-planner" -version = "0.1.60" +version = "0.1.61" dependencies = [ "insta", "rstest", @@ -3831,7 +3831,7 @@ dependencies = [ [[package]] name = "vespertide-query" -version = "0.1.60" +version = "0.1.61" dependencies = [ "insta", "rstest", diff --git a/apps/landing/.gitignore b/apps/landing/.gitignore new file mode 100644 index 00000000..5ef6a520 --- /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 00000000..e215bc4c --- /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 00000000..e83e0082 --- /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 00000000..c9f97135 --- /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 00000000..0848d21a --- /dev/null +++ b/apps/landing/next.config.ts @@ -0,0 +1,25 @@ +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', + basePath: '/vespertide', + assetPrefix: '/vespertide/', + 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 00000000..71578cca --- /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 00000000..71b69c6b --- /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 00000000..004145cd --- /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 00000000..567f17b0 --- /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 00000000..e83c482f --- /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 00000000..787fb782 --- /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 00000000..f0cbf712 --- /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 00000000..55ca9cdd --- /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 00000000..27e00cd3 --- /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 00000000..670253b3 --- /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 00000000..100f5b32 --- /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 00000000..59fbaed7 --- /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 00000000..54f71e74 --- /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 00000000..c5aa2e2e --- /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 00000000..440eec05 --- /dev/null +++ b/apps/landing/public/icons/logo-text.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/apps/landing/public/icons/search.svg b/apps/landing/public/icons/search.svg new file mode 100644 index 00000000..d65b2c56 --- /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 00000000..10e348d0 --- /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 00000000..17ea7b5b --- /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 00000000..af825e80 Binary files /dev/null and b/apps/landing/public/images/code.webp differ diff --git a/apps/landing/public/images/hero-figure.webp b/apps/landing/public/images/hero-figure.webp new file mode 100644 index 00000000..bfba2d23 Binary files /dev/null and b/apps/landing/public/images/hero-figure.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 00000000..fe27a3a2 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 00000000..5174b28c --- /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 00000000..a7db0df2 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 00000000..4e5e7d7f --- /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 00000000..77053960 --- /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 00000000..b2b2a44f --- /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 00000000..8ea6a2cf --- /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 00000000..7e0d66c0 --- /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 00000000..158d9297 --- /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 00000000..a22ef267 --- /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/_components/example.tsx b/apps/landing/src/app/_components/example.tsx new file mode 100644 index 00000000..6e69244b --- /dev/null +++ b/apps/landing/src/app/_components/example.tsx @@ -0,0 +1,102 @@ +'use client' + +import { Flex, Image } from '@devup-ui/react' +import { ComponentProps, createContext, useContext, useState } from 'react' + +const ExampleContext = createContext<{ + selected: string + setSelected: (selected: string) => void + selectedExample?: { + id: string + title: string + description: string + imageUrl: string + } +} | null>(null) + +export function useExample() { + const context = useContext(ExampleContext) + if (!context) { + throw new Error('useExample must be used within a ExampleProvider') + } + return context +} + +export function ExampleProvider({ + defaultSelected = '', + examples, + children, +}: { + defaultSelected?: string + examples: { + id: string + title: string + description: string + imageUrl: string + }[] + children: React.ReactNode +}) { + const [selected, setSelected] = useState(defaultSelected) + const selectedExample = examples.find((example) => example.id === selected) + return ( + + {children} + + ) +} + +export function ExampleContainer({ + value, + ...props +}: ComponentProps> & { value?: string }) { + const { selected, setSelected } = useExample() + const isSelected = selected === value + return ( + setSelected(value) : undefined} + overflow="hidden" + px="$spacingSpacing24" + py="$spacingSpacing20" + styleOrder={1} + transition="all .1s" + {...props} + /> + ) +} + +export function ExampleImage({ + ...props +}: Omit>, 'src'>) { + const { selectedExample } = useExample() + return ( + {selectedExample?.title + ) +} + +export function Example() {} diff --git a/apps/landing/src/app/_components/join-icon-button.tsx b/apps/landing/src/app/_components/join-icon-button.tsx new file mode 100644 index 00000000..24128d88 --- /dev/null +++ b/apps/landing/src/app/_components/join-icon-button.tsx @@ -0,0 +1,21 @@ +import { Flex } from '@devup-ui/react' +import { ComponentProps } from 'react' + +export function JoinIconButton(props: ComponentProps>) { + return ( + + ) +} 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 00000000..259d1da4 --- /dev/null +++ b/apps/landing/src/app/about-us/page.tsx @@ -0,0 +1,59 @@ +import { Grid, Text, VStack } from '@devup-ui/react' +import type { Metadata } from 'next' + +import { AboutUs } from '@/components/about-us' + +export const metadata: Metadata = { + title: 'Vespertide - About us', + description: 'About the team behind Vespertide', + alternates: { + canonical: '/about-us', + }, + openGraph: { + title: 'Vespertide - About us', + description: 'About the team behind Vespertide', + url: '/about-us', + siteName: 'Vespertide', + }, +} + +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 00000000..7b4d68d7 --- /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 00000000..7b4d68d7 --- /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 00000000..7b4d68d7 --- /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 00000000..7b4d68d7 --- /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 00000000..56a0be64 --- /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 00000000..7b4d68d7 --- /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 00000000..7b4d68d7 --- /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 00000000..e69de29b 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 00000000..7b4d68d7 --- /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 00000000..7b4d68d7 --- /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 00000000..2f40a4bc --- /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 00000000..77909811 --- /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 = `Vespertide - ${label}` + const description = `${label} · Vespertide documentation` + const url = `/documentation/${name.join('/')}` + return { + title, + description, + alternates: { + canonical: url, + }, + openGraph: { + title, + description, + url, + siteName: 'Vespertide', + }, + } +} + +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 00000000..7b4d68d7 --- /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 00000000..7b4d68d7 --- /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 00000000..7b4d68d7 --- /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 00000000..e69de29b 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 00000000..904632c1 --- /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 00000000..d157b9ba --- /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 00000000..747f1b25 --- /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 00000000..edda0282 --- /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 00000000..3b782c6a --- /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: 'Vespertide - Documentation', + description: 'Vespertide documentation', + alternates: { + canonical: '/documentation', + }, + openGraph: { + title: 'Vespertide - Documentation', + description: 'Vespertide documentation', + url: '/documentation', + siteName: 'Vespertide', + }, +} + +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 00000000..3b28cd59 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 00000000..1bd60ad8 --- /dev/null +++ b/apps/landing/src/app/layout.tsx @@ -0,0 +1,213 @@ +import { globalCss, keyframes, 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: 'Vespertide', + description: + 'Declarative database schema management for Rust — define schemas in JSON, diff against migrations, and generate typed actions and SQL.', + alternates: { + canonical: 'https://vespertide.devfive.kr', + }, + metadataBase: new URL('https://vespertide.devfive.kr'), + openGraph: { + title: 'Vespertide', + description: + 'Declarative database schema management for Rust — define schemas in JSON, diff against migrations, and generate typed actions and SQL.', + images: ['https://vespertide.devfive.kr/og-image.webp'], + siteName: 'Vespertide', + type: 'website', + url: 'https://vespertide.devfive.kr', + }, +} + +export const dim = keyframes({ + from: { + opacity: 0, + }, + to: { + opacity: 1, + }, +}) + +export const brighten = keyframes({ + from: { + opacity: 1, + }, + to: { + opacity: 0, + }, +}) + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode +}>) { + return ( + + + + {[ + 'ExtraBold', + 'Bold', + 'SemiBold', + 'Medium', + 'Regular', + 'Light', + 'Thin', + ].map((font) => ( + + ))} + + + + + + + + + + + +
+ + + + + + + + + + + + + {children} +