create-omg 0.4.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +14 -0
  2. package/dist/index.mjs +100 -0
  3. package/dist/template/AGENTS.md +49 -0
  4. package/dist/template/README.md +18 -0
  5. package/dist/template/_gitignore +24 -0
  6. package/dist/template/bun.lock +898 -0
  7. package/dist/template/eslint.config.js +23 -0
  8. package/dist/template/functions/.gitkeep +0 -0
  9. package/dist/template/index.html +20 -0
  10. package/dist/template/package.json +48 -0
  11. package/dist/template/public/favicon.svg +1 -0
  12. package/dist/template/public/icons/apple-touch-icon.png +0 -0
  13. package/dist/template/public/icons/pwa-192x192.png +0 -0
  14. package/dist/template/public/icons/pwa-512x512-maskable.png +0 -0
  15. package/dist/template/public/icons/pwa-512x512.png +0 -0
  16. package/dist/template/public/icons.svg +24 -0
  17. package/dist/template/schema.ts +5 -0
  18. package/dist/template/server/db.ts +103 -0
  19. package/dist/template/src/App.tsx +128 -0
  20. package/dist/template/src/components/ui/animated-number.tsx +102 -0
  21. package/dist/template/src/components/ui/animated-text.tsx +109 -0
  22. package/dist/template/src/components/ui/bottom-nav.tsx +131 -0
  23. package/dist/template/src/components/ui/button.tsx +84 -0
  24. package/dist/template/src/components/ui/card.tsx +70 -0
  25. package/dist/template/src/components/ui/otp-input.tsx +179 -0
  26. package/dist/template/src/components/ui/scroll-affordance.tsx +106 -0
  27. package/dist/template/src/components/ui/sound.ts +182 -0
  28. package/dist/template/src/components/ui/transitions.tsx +419 -0
  29. package/dist/template/src/components/ui/vibes-ui-styles.ts +388 -0
  30. package/dist/template/src/db.drizzle.ts +5 -0
  31. package/dist/template/src/index.css +1 -0
  32. package/dist/template/src/lib/utils.ts +6 -0
  33. package/dist/template/src/main.tsx +10 -0
  34. package/dist/template/tsconfig.app.json +30 -0
  35. package/dist/template/tsconfig.json +10 -0
  36. package/dist/template/tsconfig.node.json +24 -0
  37. package/dist/template/vite.config.ts +23 -0
  38. package/package.json +37 -0
@@ -0,0 +1,23 @@
1
+ import js from '@eslint/js'
2
+ import globals from 'globals'
3
+ import reactHooks from 'eslint-plugin-react-hooks'
4
+ import reactRefresh from 'eslint-plugin-react-refresh'
5
+ import tseslint from 'typescript-eslint'
6
+ import { defineConfig, globalIgnores } from 'eslint/config'
7
+
8
+ export default defineConfig([
9
+ globalIgnores(['dist']),
10
+ {
11
+ files: ['**/*.{ts,tsx}'],
12
+ extends: [
13
+ js.configs.recommended,
14
+ tseslint.configs.recommended,
15
+ reactHooks.configs.flat.recommended,
16
+ reactRefresh.configs.vite,
17
+ ],
18
+ languageOptions: {
19
+ ecmaVersion: 2020,
20
+ globals: globals.browser,
21
+ },
22
+ },
23
+ ])
File without changes
@@ -0,0 +1,20 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <!-- Own both color schemes so Android browsers (Chrome auto-dark, Samsung
6
+ Internet "Web page darkening") don't run their force-dark pass on top
7
+ of the app's own theme and produce a muddy double-darkened result. -->
8
+ <meta name="color-scheme" content="light dark" />
9
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
10
+ <meta
11
+ name="viewport"
12
+ content="width=device-width, initial-scale=1.0, maximum-scale=1.0, viewport-fit=cover"
13
+ />
14
+ <title>react-ts</title>
15
+ </head>
16
+ <body>
17
+ <div id="root"></div>
18
+ <script type="module" src="/src/main.tsx"></script>
19
+ </body>
20
+ </html>
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "react-ts",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "mkdir -p $HOME/.bun/bin && ln -sf $(command -v bun) $HOME/.bun/bin/node && PATH=$HOME/.bun/bin:$PATH bun ./node_modules/vite-plus/dist/bin.js dev --host 0.0.0.0",
8
+ "build": "mkdir -p $HOME/.bun/bin && ln -sf $(command -v bun) $HOME/.bun/bin/node && PATH=$HOME/.bun/bin:$PATH bun ./node_modules/vite-plus/dist/bin.js build",
9
+ "lint": "eslint .",
10
+ "preview": "vite preview"
11
+ },
12
+ "dependencies": {
13
+ "@omg-dev/ai": "^0.4.0",
14
+ "@omg-dev/auth": "^0.4.0",
15
+ "@omg-dev/db-collection": "^0.4.0",
16
+ "@omg-dev/media": "^0.4.0",
17
+ "@omg-dev/pwa": "^0.4.0",
18
+ "@omg-dev/schema": "^0.4.0",
19
+ "@omg-dev/sdk": "^0.4.0",
20
+ "@omg-dev/server": "^0.4.0",
21
+ "better-drizzle": "^0.1.1",
22
+ "clsx": "^2.1.1",
23
+ "drizzle-orm": "^0.45.2",
24
+ "lucide-react": "^1.16.0",
25
+ "react": "^19.2.4",
26
+ "react-dom": "^19.2.4",
27
+ "tailwind-merge": "^3.5.0"
28
+ },
29
+ "devDependencies": {
30
+ "@eslint/js": "^9.39.4",
31
+ "@tailwindcss/vite": "^4.1.0",
32
+ "@types/node": "^24.12.2",
33
+ "@types/react": "^19.2.14",
34
+ "@types/react-dom": "^19.2.3",
35
+ "@typescript/native": "npm:typescript@~7.0.2",
36
+ "@omg-dev/vite-plugin": "^0.4.0",
37
+ "@vitejs/plugin-react": "^6.0.1",
38
+ "eslint": "^9.39.4",
39
+ "eslint-plugin-react-hooks": "^7.0.1",
40
+ "eslint-plugin-react-refresh": "^0.5.2",
41
+ "globals": "^17.4.0",
42
+ "tailwindcss": "^4.1.0",
43
+ "typescript": "npm:@typescript/typescript6@6.0.2",
44
+ "typescript-eslint": "^8.58.0",
45
+ "vite": "^8.0.4",
46
+ "vite-plus": "0.1.21"
47
+ }
48
+ }
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="48" height="46" fill="none" viewBox="0 0 48 46"><path fill="#863bff" d="M25.946 44.938c-.664.845-2.021.375-2.021-.698V33.937a2.26 2.26 0 0 0-2.262-2.262H10.287c-.92 0-1.456-1.04-.92-1.788l7.48-10.471c1.07-1.497 0-3.578-1.842-3.578H1.237c-.92 0-1.456-1.04-.92-1.788L10.013.474c.214-.297.556-.474.92-.474h28.894c.92 0 1.456 1.04.92 1.788l-7.48 10.471c-1.07 1.498 0 3.579 1.842 3.579h11.377c.943 0 1.473 1.088.89 1.83L25.947 44.94z" style="fill:#863bff;fill:color(display-p3 .5252 .23 1);fill-opacity:1"/><mask id="a" width="48" height="46" x="0" y="0" maskUnits="userSpaceOnUse" style="mask-type:alpha"><path fill="#000" d="M25.842 44.938c-.664.844-2.021.375-2.021-.698V33.937a2.26 2.26 0 0 0-2.262-2.262H10.183c-.92 0-1.456-1.04-.92-1.788l7.48-10.471c1.07-1.498 0-3.579-1.842-3.579H1.133c-.92 0-1.456-1.04-.92-1.787L9.91.473c.214-.297.556-.474.92-.474h28.894c.92 0 1.456 1.04.92 1.788l-7.48 10.471c-1.07 1.498 0 3.578 1.842 3.578h11.377c.943 0 1.473 1.088.89 1.832L25.843 44.94z" style="fill:#000;fill-opacity:1"/></mask><g mask="url(#a)"><g filter="url(#b)"><ellipse cx="5.508" cy="14.704" fill="#ede6ff" rx="5.508" ry="14.704" style="fill:#ede6ff;fill:color(display-p3 .9275 .9033 1);fill-opacity:1" transform="matrix(.00324 1 1 -.00324 -4.47 31.516)"/></g><g filter="url(#c)"><ellipse cx="10.399" cy="29.851" fill="#ede6ff" rx="10.399" ry="29.851" style="fill:#ede6ff;fill:color(display-p3 .9275 .9033 1);fill-opacity:1" transform="matrix(.00324 1 1 -.00324 -39.328 7.883)"/></g><g filter="url(#d)"><ellipse cx="5.508" cy="30.487" fill="#7e14ff" rx="5.508" ry="30.487" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(89.814 -25.913 -14.639)scale(1 -1)"/></g><g filter="url(#e)"><ellipse cx="5.508" cy="30.599" fill="#7e14ff" rx="5.508" ry="30.599" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(89.814 -32.644 -3.334)scale(1 -1)"/></g><g filter="url(#f)"><ellipse cx="5.508" cy="30.599" fill="#7e14ff" rx="5.508" ry="30.599" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="matrix(.00324 1 1 -.00324 -34.34 30.47)"/></g><g filter="url(#g)"><ellipse cx="14.072" cy="22.078" fill="#ede6ff" rx="14.072" ry="22.078" style="fill:#ede6ff;fill:color(display-p3 .9275 .9033 1);fill-opacity:1" transform="rotate(93.35 24.506 48.493)scale(-1 1)"/></g><g filter="url(#h)"><ellipse cx="3.47" cy="21.501" fill="#7e14ff" rx="3.47" ry="21.501" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(89.009 28.708 47.59)scale(-1 1)"/></g><g filter="url(#i)"><ellipse cx="3.47" cy="21.501" fill="#7e14ff" rx="3.47" ry="21.501" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(89.009 28.708 47.59)scale(-1 1)"/></g><g filter="url(#j)"><ellipse cx=".387" cy="8.972" fill="#7e14ff" rx="4.407" ry="29.108" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(39.51 .387 8.972)"/></g><g filter="url(#k)"><ellipse cx="47.523" cy="-6.092" fill="#7e14ff" rx="4.407" ry="29.108" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(37.892 47.523 -6.092)"/></g><g filter="url(#l)"><ellipse cx="41.412" cy="6.333" fill="#47bfff" rx="5.971" ry="9.665" style="fill:#47bfff;fill:color(display-p3 .2799 .748 1);fill-opacity:1" transform="rotate(37.892 41.412 6.333)"/></g><g filter="url(#m)"><ellipse cx="-1.879" cy="38.332" fill="#7e14ff" rx="4.407" ry="29.108" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(37.892 -1.88 38.332)"/></g><g filter="url(#n)"><ellipse cx="-1.879" cy="38.332" fill="#7e14ff" rx="4.407" ry="29.108" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(37.892 -1.88 38.332)"/></g><g filter="url(#o)"><ellipse cx="35.651" cy="29.907" fill="#7e14ff" rx="4.407" ry="29.108" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(37.892 35.651 29.907)"/></g><g filter="url(#p)"><ellipse cx="38.418" cy="32.4" fill="#47bfff" rx="5.971" ry="15.297" style="fill:#47bfff;fill:color(display-p3 .2799 .748 1);fill-opacity:1" transform="rotate(37.892 38.418 32.4)"/></g></g><defs><filter id="b" width="60.045" height="41.654" x="-19.77" y="16.149" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="7.659"/></filter><filter id="c" width="90.34" height="51.437" x="-54.613" y="-7.533" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="7.659"/></filter><filter id="d" width="79.355" height="29.4" x="-49.64" y="2.03" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="e" width="79.579" height="29.4" x="-45.045" y="20.029" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="f" width="79.579" height="29.4" x="-43.513" y="21.178" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="g" width="74.749" height="58.852" x="15.756" y="-17.901" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="7.659"/></filter><filter id="h" width="61.377" height="25.362" x="23.548" y="2.284" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="i" width="61.377" height="25.362" x="23.548" y="2.284" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="j" width="56.045" height="63.649" x="-27.636" y="-22.853" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="k" width="54.814" height="64.646" x="20.116" y="-38.415" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="l" width="33.541" height="35.313" x="24.641" y="-11.323" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="m" width="54.814" height="64.646" x="-29.286" y="6.009" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="n" width="54.814" height="64.646" x="-29.286" y="6.009" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="o" width="54.814" height="64.646" x="8.244" y="-2.416" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="p" width="39.409" height="43.623" x="18.713" y="10.588" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter></defs></svg>
@@ -0,0 +1,24 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg">
2
+ <symbol id="bluesky-icon" viewBox="0 0 16 17">
3
+ <g clip-path="url(#bluesky-clip)"><path fill="#08060d" d="M7.75 7.735c-.693-1.348-2.58-3.86-4.334-5.097-1.68-1.187-2.32-.981-2.74-.79C.188 2.065.1 2.812.1 3.251s.241 3.602.398 4.13c.52 1.744 2.367 2.333 4.07 2.145-2.495.37-4.71 1.278-1.805 4.512 3.196 3.309 4.38-.71 4.987-2.746.608 2.036 1.307 5.91 4.93 2.746 2.72-2.746.747-4.143-1.747-4.512 1.702.189 3.55-.4 4.07-2.145.156-.528.397-3.691.397-4.13s-.088-1.186-.575-1.406c-.42-.19-1.06-.395-2.741.79-1.755 1.24-3.64 3.752-4.334 5.099"/></g>
4
+ <defs><clipPath id="bluesky-clip"><path fill="#fff" d="M.1.85h15.3v15.3H.1z"/></clipPath></defs>
5
+ </symbol>
6
+ <symbol id="discord-icon" viewBox="0 0 20 19">
7
+ <path fill="#08060d" d="M16.224 3.768a14.5 14.5 0 0 0-3.67-1.153c-.158.286-.343.67-.47.976a13.5 13.5 0 0 0-4.067 0c-.128-.306-.317-.69-.476-.976A14.4 14.4 0 0 0 3.868 3.77C1.546 7.28.916 10.703 1.231 14.077a14.7 14.7 0 0 0 4.5 2.306q.545-.748.965-1.587a9.5 9.5 0 0 1-1.518-.74q.191-.14.372-.293c2.927 1.369 6.107 1.369 8.999 0q.183.152.372.294-.723.437-1.52.74.418.838.963 1.588a14.6 14.6 0 0 0 4.504-2.308c.37-3.911-.63-7.302-2.644-10.309m-9.13 8.234c-.878 0-1.599-.82-1.599-1.82 0-.998.705-1.82 1.6-1.82.894 0 1.614.82 1.599 1.82.001 1-.705 1.82-1.6 1.82m5.91 0c-.878 0-1.599-.82-1.599-1.82 0-.998.705-1.82 1.6-1.82.893 0 1.614.82 1.599 1.82 0 1-.706 1.82-1.6 1.82"/>
8
+ </symbol>
9
+ <symbol id="documentation-icon" viewBox="0 0 21 20">
10
+ <path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="m15.5 13.333 1.533 1.322c.645.555.967.833.967 1.178s-.322.623-.967 1.179L15.5 18.333m-3.333-5-1.534 1.322c-.644.555-.966.833-.966 1.178s.322.623.966 1.179l1.534 1.321"/>
11
+ <path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M17.167 10.836v-4.32c0-1.41 0-2.117-.224-2.68-.359-.906-1.118-1.621-2.08-1.96-.599-.21-1.349-.21-2.848-.21-2.623 0-3.935 0-4.983.369-1.684.591-3.013 1.842-3.641 3.428C3 6.449 3 7.684 3 10.154v2.122c0 2.558 0 3.838.706 4.726q.306.383.713.671c.76.536 1.79.64 3.581.66"/>
12
+ <path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M3 10a2.78 2.78 0 0 1 2.778-2.778c.555 0 1.209.097 1.748-.047.48-.129.854-.503.982-.982.145-.54.048-1.194.048-1.749a2.78 2.78 0 0 1 2.777-2.777"/>
13
+ </symbol>
14
+ <symbol id="github-icon" viewBox="0 0 19 19">
15
+ <path fill="#08060d" fill-rule="evenodd" d="M9.356 1.85C5.05 1.85 1.57 5.356 1.57 9.694a7.84 7.84 0 0 0 5.324 7.44c.387.079.528-.168.528-.376 0-.182-.013-.805-.013-1.454-2.165.467-2.616-.935-2.616-.935-.349-.91-.864-1.143-.864-1.143-.71-.48.051-.48.051-.48.787.051 1.2.805 1.2.805.695 1.194 1.817.857 2.268.649.064-.507.27-.857.49-1.052-1.728-.182-3.545-.857-3.545-3.87 0-.857.31-1.558.8-2.104-.078-.195-.349-1 .077-2.078 0 0 .657-.208 2.14.805a7.5 7.5 0 0 1 1.946-.26c.657 0 1.328.092 1.946.26 1.483-1.013 2.14-.805 2.14-.805.426 1.078.155 1.883.078 2.078.502.546.799 1.247.799 2.104 0 3.013-1.818 3.675-3.558 3.87.284.247.528.714.528 1.454 0 1.052-.012 1.896-.012 2.156 0 .208.142.455.528.377a7.84 7.84 0 0 0 5.324-7.441c.013-4.338-3.48-7.844-7.773-7.844" clip-rule="evenodd"/>
16
+ </symbol>
17
+ <symbol id="social-icon" viewBox="0 0 20 20">
18
+ <path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M12.5 6.667a4.167 4.167 0 1 0-8.334 0 4.167 4.167 0 0 0 8.334 0"/>
19
+ <path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M2.5 16.667a5.833 5.833 0 0 1 8.75-5.053m3.837.474.513 1.035c.07.144.257.282.414.309l.93.155c.596.1.736.536.307.965l-.723.73a.64.64 0 0 0-.152.531l.207.903c.164.715-.213.991-.84.618l-.872-.52a.63.63 0 0 0-.577 0l-.872.52c-.624.373-1.003.094-.84-.618l.207-.903a.64.64 0 0 0-.152-.532l-.723-.729c-.426-.43-.289-.864.306-.964l.93-.156a.64.64 0 0 0 .412-.31l.513-1.034c.28-.562.735-.562 1.012 0"/>
20
+ </symbol>
21
+ <symbol id="x-icon" viewBox="0 0 19 19">
22
+ <path fill="#08060d" fill-rule="evenodd" d="M1.893 1.98c.052.072 1.245 1.769 2.653 3.77l2.892 4.114c.183.261.333.48.333.486s-.068.089-.152.183l-.522.593-.765.867-3.597 4.087c-.375.426-.734.834-.798.905a1 1 0 0 0-.118.148c0 .01.236.017.664.017h.663l.729-.83c.4-.457.796-.906.879-.999a692 692 0 0 0 1.794-2.038c.034-.037.301-.34.594-.675l.551-.624.345-.392a7 7 0 0 1 .34-.374c.006 0 .93 1.306 2.052 2.903l2.084 2.965.045.063h2.275c1.87 0 2.273-.003 2.266-.021-.008-.02-1.098-1.572-3.894-5.547-2.013-2.862-2.28-3.246-2.273-3.266.008-.019.282-.332 2.085-2.38l2-2.274 1.567-1.782c.022-.028-.016-.03-.65-.03h-.674l-.3.342a871 871 0 0 1-1.782 2.025c-.067.075-.405.458-.75.852a100 100 0 0 1-.803.91c-.148.172-.299.344-.99 1.127-.304.343-.32.358-.345.327-.015-.019-.904-1.282-1.976-2.808L6.365 1.85H1.8zm1.782.91 8.078 11.294c.772 1.08 1.413 1.973 1.425 1.984.016.017.241.02 1.05.017l1.03-.004-2.694-3.766L7.796 5.75 5.722 2.852l-1.039-.004-1.039-.004z" clip-rule="evenodd"/>
23
+ </symbol>
24
+ </svg>
@@ -0,0 +1,5 @@
1
+ import { defineSchema } from "@omg-dev/schema"
2
+
3
+ export default defineSchema({
4
+ collections: {},
5
+ })
@@ -0,0 +1,103 @@
1
+ import { better } from "better-drizzle"
2
+ import { drizzle } from "drizzle-orm/bun-sqlite"
3
+ import { db } from "@omg-dev/server"
4
+ import { drizzleSchema } from "../src/db.drizzle"
5
+
6
+ type BetterClient = ReturnType<typeof createBetterClient>
7
+ type ReadMethod =
8
+ | "count"
9
+ | "exists"
10
+ | "findFirst"
11
+ | "findMany"
12
+ | "findOne"
13
+ | "findUnique"
14
+ | "paginate"
15
+ type ReadOnlyDelegate<T> = Pick<T, Extract<keyof T, ReadMethod>>
16
+ type ReadOnlyBetterClient<T> = {
17
+ readonly [K in keyof T as K extends string
18
+ ? K extends `$${string}` | "repository" | "transaction"
19
+ ? never
20
+ : T[K] extends object
21
+ ? K
22
+ : never
23
+ : never]: ReadOnlyDelegate<T[K]>
24
+ }
25
+
26
+ const READ_METHODS = new Set<string>([
27
+ "count",
28
+ "exists",
29
+ "findFirst",
30
+ "findMany",
31
+ "findOne",
32
+ "findUnique",
33
+ "paginate",
34
+ ])
35
+
36
+ let drizzleClient: ReturnType<typeof createDrizzleClient> | null = null
37
+ let betterClient: ReturnType<typeof createBetterClient> | null = null
38
+ let betterReadClient: ReadOnlyBetterClient<BetterClient> | null = null
39
+
40
+ function createDrizzleClient() {
41
+ return drizzle(db.raw(), { schema: drizzleSchema })
42
+ }
43
+
44
+ function createBetterClient() {
45
+ return better(getDrizzleDb(), { schema: drizzleSchema })
46
+ }
47
+
48
+ function getDrizzleDb() {
49
+ drizzleClient ??= createDrizzleClient()
50
+ return drizzleClient
51
+ }
52
+
53
+ function getBetterDb() {
54
+ betterClient ??= createBetterClient()
55
+ return betterClient
56
+ }
57
+
58
+ function readOnlyDelegate(delegate: Record<string, unknown>, tableName: string) {
59
+ return new Proxy({}, {
60
+ get(_target, prop) {
61
+ if (typeof prop !== "string") return undefined
62
+ if (!READ_METHODS.has(prop)) {
63
+ throw new Error(
64
+ `[vibes] Better Drizzle helper is read-only. Use @omg-dev/server db or auto-CRUD for ${tableName}.${prop} mutations so auth scoping, timestamps, and live updates stay consistent.`,
65
+ )
66
+ }
67
+
68
+ const value = delegate[prop]
69
+ return typeof value === "function" ? value.bind(delegate) : value
70
+ },
71
+ })
72
+ }
73
+
74
+ function createBetterReadClient(): ReadOnlyBetterClient<BetterClient> {
75
+ const client = getBetterDb() as Record<string, unknown>
76
+ const delegates = new Map<string, object>()
77
+
78
+ return new Proxy({}, {
79
+ get(_target, prop) {
80
+ if (typeof prop !== "string") return undefined
81
+ if (prop.startsWith("$") || prop === "repository" || prop === "transaction") {
82
+ throw new Error(`[vibes] Better Drizzle helper only exposes table read delegates.`)
83
+ }
84
+
85
+ const value = client[prop]
86
+ if (!value || typeof value !== "object") return undefined
87
+
88
+ let delegate = delegates.get(prop)
89
+ if (!delegate) {
90
+ delegate = readOnlyDelegate(value as Record<string, unknown>, prop)
91
+ delegates.set(prop, delegate)
92
+ }
93
+ return delegate
94
+ },
95
+ }) as ReadOnlyBetterClient<BetterClient>
96
+ }
97
+
98
+ export function getBetterReadDb() {
99
+ betterReadClient ??= createBetterReadClient()
100
+ return betterReadClient
101
+ }
102
+
103
+ export { drizzleSchema }
@@ -0,0 +1,128 @@
1
+ import { useEffect, useState } from "react"
2
+ import {
3
+ Bot,
4
+ Lightbulb,
5
+ Database,
6
+ Home,
7
+ ImagePlus,
8
+ KeyRound,
9
+ Mic,
10
+ User,
11
+ Zap,
12
+ } from "lucide-react"
13
+ import { AnimatedNumber } from "@/components/ui/animated-number"
14
+ import { AnimatedText } from "@/components/ui/animated-text"
15
+ import { ScrollAffordance } from "@/components/ui/scroll-affordance"
16
+ import { Card } from "@/components/ui/card"
17
+ import { BottomNav } from "@/components/ui/bottom-nav"
18
+
19
+ const verbs = ["build", "ship", "iterate", "launch", "design"]
20
+
21
+ const ideas = [
22
+ {
23
+ title: "Add sign-in",
24
+ prompt: "Add sign-in with an email code",
25
+ icon: KeyRound,
26
+ },
27
+ {
28
+ title: "Save data",
29
+ prompt: "Add a posts collection and a form to create them",
30
+ icon: Database,
31
+ },
32
+ {
33
+ title: "Add AI chat",
34
+ prompt: "Add a chat box that streams from Claude",
35
+ icon: Bot,
36
+ },
37
+ {
38
+ title: "Server endpoint",
39
+ prompt: "Add an API route that returns JSON",
40
+ icon: Zap,
41
+ },
42
+ {
43
+ title: "Upload files",
44
+ prompt: "Let users upload an avatar image and show it on their profile",
45
+ icon: ImagePlus,
46
+ },
47
+ {
48
+ title: "Add voice",
49
+ prompt: "Add a button that reads the page summary aloud with generated speech",
50
+ icon: Mic,
51
+ },
52
+ ]
53
+
54
+ // The vendored bottom tab bar (src/components/ui/bottom-nav.tsx) is a
55
+ // controlled component — drive `active`/`onSelect` from state or your router.
56
+ const tabs = [
57
+ { id: "home", label: "Home", Icon: Home },
58
+ { id: "ideas", label: "Ideas", Icon: Lightbulb },
59
+ { id: "you", label: "You", Icon: User },
60
+ ] as const
61
+
62
+ function App() {
63
+ // Taste-by-default delight primitives, vendored as editable source under
64
+ // src/components/ui/. The count rolls up from 0 on first paint
65
+ // (<AnimatedNumber>), the headline verb flips on a timer (<AnimatedText>),
66
+ // and <ScrollAffordance> is a self-aware "more below" cue that shows only
67
+ // when the page overflows. All zero-dep and themed from your shadcn
68
+ // variables — open the files and restyle freely.
69
+ const [count, setCount] = useState(0)
70
+ useEffect(() => {
71
+ const id = requestAnimationFrame(() => setCount(ideas.length))
72
+ return () => cancelAnimationFrame(id)
73
+ }, [])
74
+
75
+ const [verbIndex, setVerbIndex] = useState(0)
76
+ useEffect(() => {
77
+ const id = setInterval(
78
+ () => setVerbIndex((i) => (i + 1) % verbs.length),
79
+ 2200,
80
+ )
81
+ return () => clearInterval(id)
82
+ }, [])
83
+
84
+ const [tab, setTab] = useState<(typeof tabs)[number]["id"]>("home")
85
+
86
+ return (
87
+ <main className="min-h-svh bg-background text-foreground flex flex-col items-center justify-center px-6 pt-12 pb-28">
88
+ <div className="w-full max-w-xl">
89
+ <header className="mb-8 text-center">
90
+ <h1 className="text-2xl font-semibold tracking-tight inline-flex items-baseline gap-2 flex-wrap justify-center">
91
+ <span>What do you want to</span>
92
+ <AnimatedText value={verbs[verbIndex]} className="text-primary" />
93
+ </h1>
94
+ <p className="text-sm text-muted-foreground mt-2">
95
+ <AnimatedNumber value={count} className="font-medium text-foreground" />{" "}
96
+ ways to start.
97
+ </p>
98
+ </header>
99
+ <div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
100
+ {ideas.map(({ title, prompt, icon: Icon }) => (
101
+ <Card
102
+ key={title}
103
+ className="gap-2 p-4 transition-colors hover:bg-accent/40"
104
+ >
105
+ <Icon
106
+ className="size-5 text-muted-foreground mb-1"
107
+ aria-hidden="true"
108
+ />
109
+ <h2 className="text-sm font-medium">{title}</h2>
110
+ <p className="text-xs text-muted-foreground leading-relaxed">
111
+ {prompt}
112
+ </p>
113
+ </Card>
114
+ ))}
115
+ </div>
116
+ <div className="mt-10 flex justify-center">
117
+ <ScrollAffordance label="More" />
118
+ </div>
119
+ </div>
120
+
121
+ {/* The omg home-screen bottom bar, now vendored. Swap these tabs for
122
+ your app's sections and wire onSelect to your router. */}
123
+ <BottomNav tabs={tabs} active={tab} onSelect={setTab} />
124
+ </main>
125
+ )
126
+ }
127
+
128
+ export default App
@@ -0,0 +1,102 @@
1
+ // <AnimatedNumber> — odometer rolling digits (the number half of slot-text;
2
+ // its sibling <AnimatedText> rolls arbitrary letters).
3
+ //
4
+ // Vendored, editable source (was @omg-dev/ui). Any number that changes deserves
5
+ // to *move*: counts, prices, stats. Self-contained — no Tailwind utility
6
+ // classes, no CSS import beyond the injected `.vui-*` stylesheet, no runtime
7
+ // dependency beyond React. Themes itself from your shadcn CSS variables
8
+ // (--foreground) with light/dark fallbacks and honors prefers-reduced-motion.
9
+
10
+ import { useEffect, type CSSProperties } from "react"
11
+ import { ensureStyles } from "@/components/ui/vibes-ui-styles"
12
+
13
+ const DIGITS = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
14
+
15
+ export type AnimatedNumberProps = {
16
+ /** The value to display. Changing it rolls the digits to the new number. */
17
+ value: number
18
+ /**
19
+ * Format the raw value into the string that gets rendered. Defaults to the
20
+ * host locale's grouped integer (e.g. `1,234`). Any digit in the output
21
+ * animates; everything else (separators, currency, units) stays static.
22
+ */
23
+ format?: (value: number) => string
24
+ className?: string
25
+ style?: CSSProperties
26
+ }
27
+
28
+ /**
29
+ * Rolling-digit number. Each digit is a vertical reel of 0–9 that translates
30
+ * to the target digit; CSS handles the motion, so re-rendering with a new
31
+ * `value` is all it takes to animate. Significant digits cascade in slightly
32
+ * after the small ones for an odometer feel.
33
+ */
34
+ export function AnimatedNumber({
35
+ value,
36
+ format = defaultFormat,
37
+ className = "",
38
+ style,
39
+ }: AnimatedNumberProps) {
40
+ useEffect(ensureStyles, [])
41
+
42
+ const text = format(value)
43
+ const chars = Array.from(text)
44
+ // Stagger from the least-significant character so the ripple travels left.
45
+ const lastIndex = chars.length - 1
46
+
47
+ return (
48
+ <span
49
+ className={`vui vui-num ${className}`.trim()}
50
+ style={style}
51
+ role="text"
52
+ aria-label={text}
53
+ >
54
+ {chars.map((char, i) => {
55
+ if (!isDigit(char)) {
56
+ return (
57
+ <span key={i} className="vui-num-sym" aria-hidden="true">
58
+ {char}
59
+ </span>
60
+ )
61
+ }
62
+ const digit = char.charCodeAt(0) - 48
63
+ const fromRight = lastIndex - i
64
+ return (
65
+ <Reel key={i} digit={digit} delay={Math.min(fromRight, 6) * 0.05} />
66
+ )
67
+ })}
68
+ </span>
69
+ )
70
+ }
71
+
72
+ function Reel({ digit, delay }: { digit: number; delay: number }) {
73
+ return (
74
+ <span className="vui-num-reel" aria-hidden="true">
75
+ <span
76
+ className="vui-num-track"
77
+ style={{
78
+ transform: `translateY(${-digit}em)`,
79
+ transitionDelay: `${delay}s`,
80
+ }}
81
+ >
82
+ {DIGITS.map((d) => (
83
+ <span key={d} className="vui-num-cell">
84
+ {d}
85
+ </span>
86
+ ))}
87
+ </span>
88
+ </span>
89
+ )
90
+ }
91
+
92
+ function isDigit(char: string) {
93
+ return char.length === 1 && char >= "0" && char <= "9"
94
+ }
95
+
96
+ function defaultFormat(value: number): string {
97
+ try {
98
+ return new Intl.NumberFormat().format(value)
99
+ } catch {
100
+ return String(value)
101
+ }
102
+ }
@@ -0,0 +1,109 @@
1
+ // <AnimatedText> — split-flap / slot-text rolling letters.
2
+ //
3
+ // The text half of the slot-text effect (its sibling <AnimatedNumber> rolls
4
+ // digits). Any word that changes deserves to *move*: a rotating headline verb,
5
+ // a status that flips, a name that resolves. Each character position is a
6
+ // vertical reel of glyphs that translates to the target letter — like a
7
+ // split-flap departure board. Self-contained — no Tailwind utility classes, no
8
+ // CSS import beyond the injected `.vui-*` stylesheet, no runtime dependency
9
+ // beyond React. Themes itself from your shadcn CSS variables (--foreground)
10
+ // with light/dark fallbacks and honors prefers-reduced-motion.
11
+
12
+ import { useEffect, useMemo, type CSSProperties } from "react"
13
+ import { ensureStyles } from "@/components/ui/vibes-ui-styles"
14
+
15
+ // The reel alphabet — every glyph a character can roll *through*. Space sits
16
+ // first so blanks rest at the top of the reel. Any character in `value` that
17
+ // isn't here renders static (no reel), the way <AnimatedNumber> leaves
18
+ // separators and currency symbols still.
19
+ const ALPHABET = Array.from(
20
+ " ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.,!?'-&@",
21
+ )
22
+
23
+ export type AnimatedTextProps = {
24
+ /** The text to display. Changing it rolls each letter to its new glyph. */
25
+ value: string
26
+ /**
27
+ * Override the glyphs a reel can roll through. Defaults to spaces, letters,
28
+ * digits and common punctuation. A tighter set (e.g. just `"ABC…Z"`) makes
29
+ * the roll shorter and the board feel snappier.
30
+ */
31
+ alphabet?: string
32
+ className?: string
33
+ style?: CSSProperties
34
+ }
35
+
36
+ /**
37
+ * Rolling-letter text. Each character is a vertical reel of the alphabet that
38
+ * translates to the target glyph; CSS handles the motion, so re-rendering with
39
+ * a new `value` is all it takes to animate. Characters reveal left-to-right so
40
+ * the roll reads like the word is being dealt out.
41
+ */
42
+ export function AnimatedText({
43
+ value,
44
+ alphabet,
45
+ className = "",
46
+ style,
47
+ }: AnimatedTextProps) {
48
+ useEffect(ensureStyles, [])
49
+
50
+ // Resolve the reel alphabet and a glyph→row lookup once per alphabet.
51
+ const { set, index } = useMemo(() => {
52
+ const chars = alphabet ? Array.from(alphabet) : ALPHABET
53
+ return { set: chars, index: new Map(chars.map((c, i) => [c, i])) }
54
+ }, [alphabet])
55
+
56
+ const chars = Array.from(value)
57
+
58
+ return (
59
+ <span
60
+ className={`vui vui-text ${className}`.trim()}
61
+ style={style}
62
+ role="text"
63
+ aria-label={value}
64
+ >
65
+ {chars.map((char, i) => {
66
+ const row = index.get(char)
67
+ if (row === undefined) {
68
+ return (
69
+ <span key={i} className="vui-text-sym" aria-hidden="true">
70
+ {char}
71
+ </span>
72
+ )
73
+ }
74
+ // Reveal left-to-right; cap the stagger so long strings still settle.
75
+ return (
76
+ <Reel key={i} row={row} set={set} delay={Math.min(i, 8) * 0.06} />
77
+ )
78
+ })}
79
+ </span>
80
+ )
81
+ }
82
+
83
+ function Reel({
84
+ row,
85
+ set,
86
+ delay,
87
+ }: {
88
+ row: number
89
+ set: string[]
90
+ delay: number
91
+ }) {
92
+ return (
93
+ <span className="vui-text-reel" aria-hidden="true">
94
+ <span
95
+ className="vui-text-track"
96
+ style={{
97
+ transform: `translateY(${-row}em)`,
98
+ transitionDelay: `${delay}s`,
99
+ }}
100
+ >
101
+ {set.map((c, i) => (
102
+ <span key={i} className="vui-text-cell">
103
+ {c === " " ? " " : c}
104
+ </span>
105
+ ))}
106
+ </span>
107
+ </span>
108
+ )
109
+ }