duto 0.261001.0 → 0.261012.0

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 (32) hide show
  1. package/package.json +22 -26
  2. package/src/commands/templates/pages/components/html.tsx +55 -0
  3. package/src/commands/templates/pages/components/theme-select.tsx +60 -0
  4. package/src/commands/templates/pages/i18n/en.ts +6 -0
  5. package/src/commands/templates/pages/i18n/pt-br.ts +6 -0
  6. package/src/commands/templates/pages/pages/404.tsx +1 -1
  7. package/src/commands/templates/pages/pages/blog/[slug].tsx +88 -0
  8. package/src/commands/templates/pages/pages/blog/index.tsx +74 -0
  9. package/src/commands/templates/pages/pages/index.tsx +17 -7
  10. package/src/commands/templates/pages/pages/layout.tsx +5 -3
  11. package/src/commands/templates/pages/public/favicon.ico +0 -0
  12. package/src/commands/templates/pages/public/favicon.svg +1 -0
  13. package/src/commands/templates/pages/style.css +110 -7
  14. package/src/commands/templates/pages/tsconfig.json +2 -0
  15. package/src/pages/cli/commands/build.ts +1 -2
  16. package/src/pages/cli/utils.ts +8 -6
  17. package/src/pages/client/app.tsx +177 -28
  18. package/src/pages/client/link.ts +15 -41
  19. package/src/pages/client/storage.ts +1 -1
  20. package/src/pages/client/theme.ts +49 -0
  21. package/src/pages/compiler/ast.ts +13 -0
  22. package/src/pages/compiler/bun-plugin.ts +245 -0
  23. package/src/pages/compiler/constants.ts +2 -0
  24. package/src/pages/compiler/index.ts +44 -23
  25. package/src/pages/compiler/meta.ts +7 -31
  26. package/src/pages/compiler/types.ts +15 -25
  27. package/src/pages/components/link.tsx +11 -19
  28. package/src/pages/i18n/client.ts +3 -4
  29. package/src/pages/index.ts +4 -21
  30. package/src/server/app.ts +2 -1
  31. package/src/pages/client/router.ts +0 -63
  32. package/src/pages/compiler/vite-plugin.ts +0 -345
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "duto",
3
3
  "description": "All-in-one serverless framework for web artisans",
4
- "version": "0.261001.0",
4
+ "version": "0.261012.0",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
7
7
  "types": "./src/index.d.ts",
@@ -25,46 +25,42 @@
25
25
  "./length": "./src/server/utils/length.ts"
26
26
  },
27
27
  "dependencies": {
28
- "@inquirer/confirm": "^5.1.8",
29
- "@inquirer/input": "^4.1.8",
30
- "@inquirer/select": "^4.1.0",
28
+ "@inquirer/confirm": "^5.1.21",
29
+ "@inquirer/input": "^4.3.1",
30
+ "@inquirer/select": "^4.4.2",
31
31
  "nanospinner": "^1.2.2",
32
- "@hono/node-server": "^1.19.11",
33
- "@hono/standard-validator": "^0.2.2",
32
+ "@hono/node-server": "^1.19.17",
33
+ "@hono/standard-validator": "^0.2.3",
34
34
  "@hono/zod-validator": "^0.7.6",
35
- "@scalar/hono-api-reference": "^0.12.0",
35
+ "@scalar/hono-api-reference": "^0.12.11",
36
36
  "@minify-html/node": "^0.18.1",
37
- "@preact/signals": "^2.9.2",
38
- "@swc/core": "^1.15.21",
39
- "chokidar": "^3.5.2",
37
+ "@preact/signals": "^2.11.3",
38
+ "bun-plugin-tailwind": "^0.1.2",
39
+ "chokidar": "^3.6.0",
40
40
  "citty": "^0.2.2",
41
41
  "consola": "^3.4.2",
42
42
  "cripta": "0.x",
43
- "dotenv": "^16.5.0",
44
- "esbuild": "^0.25.2",
43
+ "dotenv": "^16.6.1",
44
+ "esbuild": "^0.25.12",
45
45
  "forj": "0.x",
46
- "hono": "^4.13.7",
47
- "hono-openapi": "^1.3.0",
46
+ "hono": "^4.13.13",
47
+ "hono-openapi": "^1.3.5",
48
48
  "localflare-api": "^0.5.0",
49
49
  "localflare-core": "^0.5.0",
50
50
  "miniflare": "^4.20260730.0",
51
- "pathe": "^2.0",
52
- "preact": "^10.29.7",
53
- "preact-render-to-string": "^6.6.6",
51
+ "pathe": "^2.0.3",
52
+ "preact": "^11.0.1",
53
+ "preact-render-to-string": "^6.8.0",
54
54
  "quansync": "^0.2.11",
55
55
  "t0n": "0.x",
56
- "vite": "^8.0.0",
57
- "wouter-preact": "^3.10.0",
58
- "wrangler": "^4.128.0",
59
- "zod": "^4.3.6",
56
+ "wouter-preact": "^3.13.0",
57
+ "wrangler": "^4.149.0",
58
+ "zod": "^4.6.6",
60
59
  "zod-openapi": "^5.4.6"
61
60
  },
62
61
  "devDependencies": {
63
- "@cloudflare/workers-types": "^4.20260113.0",
64
- "@minify-html/node": "^0.18.1",
65
- "@preact/preset-vite": "^2.10.5",
66
- "@tailwindcss/vite": "^4.3.1",
67
- "bun-types": "^1.4.0",
62
+ "@cloudflare/workers-types": "^4.20260702.1",
63
+ "bun-types": "^1.4.3",
68
64
  "typescript": "^5.9.3"
69
65
  },
70
66
  "resolutions": {
@@ -0,0 +1,55 @@
1
+ import { t } from 'duto'
2
+
3
+ export type Props = {
4
+ children?: preact.ComponentChildren
5
+ class?: string
6
+
7
+ locale?: string
8
+ name?: string
9
+ title: string
10
+ description?: string
11
+ }
12
+
13
+ export function Html({
14
+ children,
15
+ class: className,
16
+
17
+ locale = 'en',
18
+ name = 'Duto',
19
+ title,
20
+ description = '',
21
+ ...props
22
+ }: Props) {
23
+ const _class = [
24
+ 'bg-gradient-to-br from-zinc-200 to-zinc-150 dark:from-zinc-800 dark:to-zinc-950 text-gray-600 dark:text-gray-300',
25
+ className,
26
+ ].filter(Boolean).join(' ')
27
+
28
+ const _title = [name, t(title ?? '')].filter(Boolean).join(' › ')
29
+ const _description = t(description || title || '')
30
+
31
+ return (
32
+ <html class={_class} {...props}>
33
+ <head>
34
+ <meta charset="utf-8" />
35
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
36
+
37
+ <link rel="icon" type="image/x-icon" sizes="48x48" href="/favicon.ico" />
38
+ <link rel="icon" type="image/svg+xml" sizes="any" href="/favicon.svg" />
39
+ <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
40
+
41
+ <meta name="theme-color" content="#FFFFFF" media="(prefers-color-scheme: light)" />
42
+ <meta name="theme-color" content="#000000" media="(prefers-color-scheme: dark)" />
43
+
44
+ {/* <link rel="sitemap" href="/sitemap-index.xml" /> */}
45
+
46
+ <title dangerouslySetInnerHTML={{ __html: _title }} />
47
+ {_description && <meta name="description" content={_description} />}
48
+ </head>
49
+
50
+ <body class="flex flex-col min-h-svh max-w-screen overflow-x-clip relative z-10 ">
51
+ {children}
52
+ </body>
53
+ </html>
54
+ )
55
+ }
@@ -0,0 +1,60 @@
1
+ import { useSignal } from '@preact/signals'
2
+ import { useLayoutEffect } from 'preact/hooks'
3
+ import { t, getTheme, setTheme, onThemeChange } from 'duto'
4
+
5
+ import Select from './select'
6
+
7
+ function ThemeIcon(props: any) {
8
+ return (
9
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class={[props.class || '', props.className || ''].filter(Boolean).join(' ')}><path d="M11 17a4 4 0 0 1-8 0V5a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2Z"/><path d="M16.7 13H19a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2H7"/><path d="M 7 17h.01"/><path d="m11 8 2.3-2.3a2.4 2.4 0 0 1 3.404.004L18.6 7.6a2.4 2.4 0 0 1 .026 3.434L9.9 19.8"/></svg>
10
+ )
11
+ }
12
+
13
+ export default function ThemeSelect({ class: className = '' }) {
14
+ const options = [['auto', t`auto`], ['dark', t`dark`], ['light', t`light`]].map(([value, label]) => ({ value, label, selected: false }))
15
+ const selectedValue = useSignal(getTheme())
16
+ let unsubscribe: (() => void)[] = []
17
+ const handle = (e: any, theme?: string) => {
18
+ theme ??= getTheme()
19
+ selectedValue.value = theme
20
+ }
21
+
22
+ useLayoutEffect(() => {
23
+ unsubscribe = onThemeChange(handle)
24
+ selectedValue.value = getTheme()
25
+
26
+ window.addEventListener('popstate', handle)
27
+ return () => {
28
+ window.removeEventListener('popstate', handle)
29
+ unsubscribe.forEach(unsub => unsub?.())
30
+ }
31
+ }, [])
32
+
33
+ const handleChange = async (e: Event) => {
34
+ const target = e.target as HTMLSelectElement
35
+ if (target) {
36
+ setTheme(target.value)
37
+ selectedValue.value = target.value
38
+ }
39
+ }
40
+
41
+ const containerClasses = [
42
+ 'locale-select overflow-hidden text-ellipsis p-1 rounded-lg hover:bg-neutral-200 hover:text-zinc-900',
43
+ className,
44
+ ].filter(Boolean).join(' ')
45
+
46
+ return (
47
+ <Select
48
+ Icon={ThemeIcon}
49
+ options={options}
50
+ value={selectedValue.value}
51
+ width="6.25em"
52
+ class="theme-select outline-none text-ellipsis absolute inset-0 opacity-0 cursor-pointer"
53
+ containerClass={containerClasses}
54
+ id="theme-select"
55
+ onchange={handleChange}
56
+ width="6.25em"
57
+ id="theme-select"
58
+ />
59
+ )
60
+ }
@@ -5,6 +5,9 @@ export default {
5
5
  notfound: 'not-found',
6
6
  },
7
7
  messages: {
8
+ auto: 'System default',
9
+ dark: 'Dark theme',
10
+ light: 'Light theme',
8
11
  title: 'Welcome to Duto',
9
12
  description: 'A fast, lightweight framework that keeps things simple!',
10
13
  gotodocs: 'Go to docs',
@@ -13,5 +16,8 @@ export default {
13
16
  backhome: 'Back to homepage',
14
17
  about: 'About',
15
18
  shop: 'Shop',
19
+
20
+ blog: 'Blog',
21
+ b1: 'This page is also static, the data was loaded via',
16
22
  },
17
23
  }
@@ -5,6 +5,9 @@ export default {
5
5
  notfound: 'pagina-desconhecida',
6
6
  },
7
7
  messages: {
8
+ auto: 'Padrão do sistema',
9
+ dark: 'Escuro',
10
+ light: 'Claro',
8
11
  title: 'Bem-vindo ao Duto',
9
12
  description: 'Um framework rápido e leve que mantém as coisas simples!',
10
13
  gotodocs: 'Documentação',
@@ -13,5 +16,8 @@ export default {
13
16
  backhome: 'Ir para página inicial',
14
17
  about: 'Sobre',
15
18
  shop: 'Loja',
19
+
20
+ blog: 'Blog',
21
+ b1: 'Esta página também é estática, os dados foram carregados via',
16
22
  },
17
23
  }
@@ -3,7 +3,7 @@ import { t, Link } from 'duto'
3
3
  export default function NotFound() {
4
4
  return (
5
5
  <div class="flex flex-col items-center justify-center text-center w-full text-lg py-16">
6
- <h1 class="text-sky-gratient text-mono text-7xl lg:text-8xl">404</h1>
6
+ <h1 class="text-sky-gradient font-mono text-7xl lg:text-8xl">404</h1>
7
7
  <p class="body-large mb-2 mt-4 text-3xl lg:text-4xl">{t`notfound`}</p>
8
8
  <p class="body lg:body-large">{t`sorry404`}</p>
9
9
  <Link to="/" class="border-b border-current mt-8">{t`backhome`}</Link>
@@ -0,0 +1,88 @@
1
+
2
+ import { t, Link } from 'duto'
3
+ import type { Post, Posts } from '@/types'
4
+ import { JSJSON } from '../../../../packages/t0n/src/json'
5
+ import NotFound from '../404'
6
+
7
+ // Mock data
8
+ const ALL_POSTS: Posts = [
9
+ {
10
+ id: "hello-world",
11
+ title: "Hello World",
12
+ body:
13
+ "Welcome to my blog! This post was statically generated at build time using `getStaticProps`. When navigating here via the SPA router, `getProps` fetches the same data dynamically.",
14
+ },
15
+ {
16
+ id: "getting-started",
17
+ title: "Getting Started with SSG",
18
+ body:
19
+ "Static site generation combines the best of both worlds: fast initial loads with the flexibility of dynamic data fetching.",
20
+ },
21
+ {
22
+ id: "signals-islands",
23
+ title: "Signals & Islands",
24
+ body:
25
+ "Preact Signals give us fine-grained reactivity. Combined with the islands architecture, we can ship near-zero JS by default.",
26
+ },
27
+ ]
28
+
29
+
30
+ export { Html } from '@/components/html'
31
+
32
+ /**
33
+ * Returns one entry per post.
34
+ * Each entry MUST include the route param (`slug`) plus any page props.
35
+ * The build script uses `slug` to compute the output path.
36
+ */
37
+ export function getStaticProps() {
38
+ return ALL_POSTS.map((post) => ({
39
+ slug: post.id, // required: matches [slug] in the filename
40
+ post,
41
+ }))
42
+ }
43
+
44
+ // ── Browser-time data ─────────────────────────────────────────────────────────
45
+
46
+ /**
47
+ * Called during SPA navigation. Receives route params from wouter.
48
+ */
49
+ export async function getProps({ slug }: { slug?: string }) {
50
+ const post = (await (await fetch('https://jsonplaceholder.typicode.com/posts/'+ slug)).json())
51
+ return { post }
52
+ }
53
+
54
+ // ── Component ─────────────────────────────────────────────────────────────────
55
+
56
+ type Props = {
57
+ post?: Post | null,
58
+ slug?: string,
59
+ }
60
+
61
+ export default function BlogPost({ post, slug }: Props) {
62
+ if (!post?.title)
63
+ return <NotFound />
64
+
65
+ return (
66
+ <>
67
+ <article class="mt-6 max-w-2xl">
68
+ <div>
69
+ <Link href="/blog">
70
+ ←
71
+ <span class="ml-2">{t`blog`}</span>
72
+ </Link>
73
+ </div>
74
+ <h1 class="mt-2 text-4xl font-black text-sky-gradient">{post.title}</h1>
75
+ <p class="mt-6 leading-relaxed">{post.body}</p>
76
+ </article>
77
+
78
+ <div class="mt-12 p-4 rounded-lg text-sm bg-sky-50 text-sky-700 dark:bg-zinc-700 dark:text-white/80">
79
+ {[
80
+ ['Route', window.$duto?.route?.pattern],
81
+ ['Route path', window.$duto?.route?.path],
82
+ ['Route params', JSJSON(window?.$duto?.route?.params || {})],
83
+ ['Route search params', window?.$duto?.search?.toString()],
84
+ ].map(([label, info]) => ( <div><b>{label}:</b> {info || 'n/a'}</div>))}
85
+ </div>
86
+ </>
87
+ )
88
+ }
@@ -0,0 +1,74 @@
1
+ // 'use client'
2
+ import { t, Link } from 'duto'
3
+ import type { Posts } from '@/types'
4
+ // import { Counter } from '@/components/counter2'
5
+
6
+ type Props = {
7
+ posts?: Posts,
8
+ }
9
+
10
+ export { Html } from '@/components/html'
11
+
12
+ export async function getStaticProps() {
13
+ const posts = (await (await fetch('https://jsonplaceholder.typicode.com/posts')).json())
14
+ ?.sort(() => 0.5 - Math.random())
15
+ .slice(0, 10)
16
+
17
+ return { posts }
18
+ }
19
+
20
+ export default function Blog({ posts }: Props) {
21
+ return (
22
+ <>
23
+ <h1 class="title">Blog</h1>
24
+
25
+ <div class="highlight">
26
+ {t`b1`}&nbsp;<b><i>getStaticProps()</i></b> (SSG)
27
+ </div>
28
+
29
+ {!posts?.length && (
30
+ <p class="mt-8">No posts found.</p>
31
+ )}
32
+
33
+ <div class="mt-8 flex flex-col">
34
+ {posts?.map((post) => (
35
+ <Link to={`/blog/${post.id}`} key={post.id}
36
+ class="group block relative p-4 border-b border-gray-200 dark:border-zinc-700/70 transition-all duration-300 ease-out"
37
+ >
38
+ <div class="flex items-start gap-4">
39
+ <span class="text-sm font-mono text-gray-400 group-hover:text-sky-gradient mt-0.5 min-w-[24px]">
40
+ #{post.id}
41
+ </span>
42
+
43
+ <div class="flex-1 min-w-0">
44
+ <h2 class="text-base font-medium group-hover:text-sky-gradient">
45
+ {post.title}
46
+ </h2>
47
+ <p class="mt-1 text-sm text-gray-500 group-hover:text-gray-800 dark:group-hover:text-gray-400 line-clamp-2 pr-6">
48
+ {post.body}
49
+ </p>
50
+ </div>
51
+
52
+ <div class="absolute right-0 top-[35%] flex-shrink-0 bg-indigo-50 group-hover:bg-gray-100 dark:bg-gray-600/90 dark:group-hover:bg-gray-500 rounded-full p-1 transition-all duration-300 group-hover:translate-x-1">
53
+ <svg
54
+ class="size-5 text-gray-400 group-hover:text-sky-500 dark:group-hover:text-gray-300"
55
+ fill="none"
56
+ stroke="currentColor"
57
+ viewBox="0 0 24 24"
58
+ >
59
+ <path
60
+ strokeLinecap="round"
61
+ strokeLinejoin="round"
62
+ strokeWidth={2}
63
+ d="M9 5l7 7-7 7"
64
+ />
65
+ </svg>
66
+ </div>
67
+ </div>
68
+
69
+ </Link>
70
+ ))}
71
+ </div>
72
+ </>
73
+ )
74
+ }
@@ -1,15 +1,25 @@
1
1
  import { t, Link } from 'duto'
2
2
 
3
+ export { Html } from '@/components/html'
4
+
3
5
  export default function Index() {
4
6
  return (
5
- <div class="relative">
6
- <h1 class="title text-sky-gratient mt-10 lg:mt-2">{t`title`}</h1>
7
+ <div class="relative space-y-8">
8
+ <h1 class="title text-sky-gradient lg:mt-2">{t`title`}</h1>
7
9
  <p class="sub-title">{t`description`}</p>
8
- <div class="flex flex-row gap-8 mt-10">
9
-
10
- <Link href="https://hub.bi/docs/duto" class="group inline-flex shrink-0 items-center justify-center leading-6 font-medium select-none focus:outline-0 gap-2 px-4 py-2.5 text-base rounded-lg bg-white hover:bg-neutral-200 border border-gray-700 text-zinc-900">
11
- {t`gotodocs`}<span class="ml-0.5 inline-block transition-transform group-hover:translate-x-1">→</span>
12
- </Link>
10
+ <div class="divide-y divide-gray-300 dark:divide-zinc-700/70">
11
+ {[
12
+ ['https://hub.bi/docs/duto', t`gotodocs`],
13
+ ['/blog', t`Blog`],
14
+ ].map(([href, label]) => (
15
+ <Link
16
+ href={href}
17
+ class="group flex items-center justify-between gap-4 py-4 px-2.5"
18
+ >
19
+ <span class="font-semibold group-hover:text-sky-gradient">{label}</span>
20
+ <span class="text-lg mr-3 transition-transform group-hover:text-sky-600 dark:group-hover:text-sky-300 group-hover:translate-x-1">→</span>
21
+ </Link>
22
+ ))}
13
23
  </div>
14
24
  </div>
15
25
  )
@@ -1,15 +1,17 @@
1
1
  import type { ComponentChildren } from 'preact'
2
2
 
3
3
  import LocaleSelect from '@/components/locale-select'
4
+ import ThemeSelect from '@/components/theme-select'
4
5
 
5
6
  export default function Layout({ children }: { children: ComponentChildren }) {
6
7
  return (
7
- <div class="relative w-full max-w-full min-h-svh overflow-hidden flex flex-col flex-1 justify-center items-center bg-gradient-to-br from-zinc-800 to-zinc-950 text-gray-300 select-none">
8
+ <>
8
9
  <div class='absolute top-10 right-10'>
10
+ <ThemeSelect class='mr-5' />
9
11
  <LocaleSelect />
10
12
  </div>
11
13
 
12
- <main class="flex justify-center items-center px-6 py-16 min-h-[calc(100vh-3.75rem)]" children={children} />
14
+ <main class="flex flex-col justify-center items-center px-6 py-16 min-h-[calc(100vh-3.75rem)]" children={children} />
13
15
 
14
16
  <footer class="py-[1.1rem] relative text-base lex flex-row items-center mx-auto">
15
17
  <a href="https://hub.bi?ref=duto" class="no-underline group flex items-center justify-center" target="_blank" rel="noopener noreferrer">
@@ -19,6 +21,6 @@ export default function Layout({ children }: { children: ComponentChildren }) {
19
21
  HUB</span>
20
22
  </a>
21
23
  </footer>
22
- </div>
24
+ </>
23
25
  )
24
26
  }
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="840" height="840" viewBox="0 0 840 840" fill="none"><style>path{color:black}@media (prefers-color-scheme: dark){path{color:#c6c6c6}}</style><path fill-rule="evenodd" clip-rule="evenodd" d="M832.965 446.25C837.573 438.269 839.999 429.216 839.999 420C839.999 410.784 837.573 401.731 832.965 393.75L645.173 78.75C640.565 70.7692 633.938 64.1418 625.957 59.534C617.976 54.9262 608.923 52.5002 599.708 52.5H240.293C231.077 52.5002 222.024 54.9262 214.043 59.534C206.063 64.1418 199.435 70.7692 194.828 78.75L7.03518 393.75C2.42735 401.731 0.001544 410.784 0.001544 420C0.001544 429.216 2.42735 438.269 7.03518 446.25L194.828 761.25C199.435 769.231 206.063 775.858 214.043 780.466C222.024 785.074 231.077 787.5 240.293 787.5H599.708C608.923 787.5 617.976 785.074 625.957 780.466C633.938 775.858 640.565 769.231 645.173 761.25L832.965 446.25Z" fill="currentColor"></path></svg>
@@ -1,19 +1,122 @@
1
1
  @import "tailwindcss";
2
2
 
3
+ @custom-variant dark (&:where(.dark, .dark *));
4
+
5
+ @layer base {
6
+ * {@apply border-border}
7
+ /* body {@apply bg-background text-foreground} */
8
+ }
9
+
10
+ @utility s {
11
+ @apply w-[--value] h-[--value];
12
+ }
13
+
14
+ @variant children (& > *);
15
+
16
+
17
+ @theme {
18
+ --font-sans:
19
+ -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial,
20
+ "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", "Twemoji Mozilla",
21
+ sans-serif;
22
+ --font-mono:
23
+ ui-monospace, "SFMono-Regular", "SF Mono", Menlo, Consolas,
24
+ "Liberation Mono", monospace;
25
+ --font-serif:
26
+ Georgia, Garamond, Cambria, "Times New Roman", Times, ui-serif, serif;
27
+
28
+ --font-pixel: PixAntiqua, ui-sans-serif, system-ui, sans-serif;
29
+ /* Medium headers*/
30
+ /* sohne, "Helvetica Neue", Helvetica, Arial, sans-serif; */
31
+
32
+ --color-black: #0D0F14;
33
+ --color-white: #fff;
34
+ --color-text: #F041FF;
35
+
36
+ --color-accent: var(--color-gray-100);
37
+ /* --color-primary: #3F224D; */
38
+ --color-muted: #545864;
39
+
40
+ --color-border: var(--color-gray-200);
41
+ --color-grid: #e5e5e5;
42
+
43
+ --color-blue: #3245FF;
44
+ --color-purple: #BC52EE;
45
+ --color-purple-dark: #3F224D;
46
+ --color-red: #D83333;
47
+ --color-pink: #F041FF;
48
+ --color-pink-light: #E8C4F9;
49
+ --color-orange: #F8E42E;
50
+ --color-yellow: #FF7D54;
51
+ --color-hover: #E8C4F9;
52
+ --color-green: #4AF2C8;
53
+
54
+ --color-dark-100: #858B98;
55
+ --color-dark-300: #343841;
56
+ --color-dark-400: #18191E;
57
+ --color-dark-500: #4B4B4B;
58
+ --color-dark-600: #2C2C2C;
59
+ --color-dark-700: #11172A;
60
+ --color-dark-800: #0C0F19;
61
+ --color-dark-900: #060913;
62
+
63
+
64
+ --sky-grad-1: rgb(2, 132, 199);
65
+ --sky-grad-2: rgb(14, 165, 233);
66
+ --sky-grad-3: rgb(56, 189, 248);
67
+ }
68
+
69
+ .dark {
70
+ --color-white: #F2F6FA;
71
+
72
+ --color-accent: #17191E;
73
+ --color-primary: #BC52EE;
74
+ --color-muted: #BFC1C9;
75
+
76
+ --color-border: color-mix(in oklab, var(--color-gray-100) 10%, transparent);
77
+
78
+
79
+ --sky-grad-1: rgb(112, 209, 255);
80
+ --sky-grad-2: rgb(173, 228, 255);
81
+ --sky-grad-3: rgb(219, 243, 255);
82
+ }
83
+
84
+ /* App styles */
85
+
3
86
  html, body {
4
- @apply overscroll-none
87
+ @apply overscroll-y-none;
88
+ /* font-size-adjust: ex-height 0.5; */
89
+
90
+ /* Standardize line height across different system fonts */
91
+ /* line-height: 1.5; */
92
+ /* Consider setting letter-spacing for more consistent text blocks */
93
+ /* letter-spacing: 0.01em; */
94
+
95
+ /* Advanced font metric overrides (modern browsers only) */
96
+ /* ascent-override: 90%;
97
+ descent-override: 25%;
98
+ line-gap-override: 0%; */
5
99
  }
6
100
 
7
- .text-mono {
8
- font-family: ui-monospace,SFMono-Regular,monospace;
9
- --tw-font-weight: 300;
10
- font-weight: 300
101
+ .text-sky-gradient,
102
+ .group-hover\:text-sky-gradient:is(:where(.group):hover *),
103
+ .hover\:text-sky-gradient:hover {
104
+ @apply bg-clip-text text-transparent;
105
+ background-image: radial-gradient(
106
+ 138.06% 1036.51% at 95.25% -2.54%,
107
+ var(--sky-grad-1) 14.06%,
108
+ var(--sky-grad-2) 51.02%,
109
+ var(--sky-grad-3) 79.09%
110
+ );
11
111
  }
12
112
 
13
- .text-sky-gratient {
113
+ /* .text-sky-gradient,
114
+ .group-hover\:text-sky-gradient:is(:where(.group):hover *),
115
+ .hover\:text-sky-gradient:hover
116
+ {
14
117
  @apply bg-clip-text text-transparent;
15
118
  background-image: radial-gradient(138.06% 1036.51% at 95.25% -2.54%, rgb(112, 209, 255) 14.06%, rgb(173, 228, 255) 51.02%, rgb(219, 243, 255) 79.09%);
16
- }
119
+ } */
17
120
 
18
121
  .title {
19
122
  @apply text-4xl lg:text-[3.5rem] leading-[1.1] font-semibold tracking-tight text-balance
@@ -39,7 +39,9 @@
39
39
  "enums",
40
40
  "libs",
41
41
  "i18n",
42
+ "layouts",
42
43
  "pages",
44
+ "public",
43
45
  "tests/**/*",
44
46
  "utils",
45
47
  "i18n.ts",
@@ -1,9 +1,8 @@
1
- import { build } from 'vite'
2
1
  import { setEnv } from 't0n'
3
2
  import { command } from 't0n/cli'
4
3
  import { dim, underline } from 't0n/color'
5
4
  import { wait, error, rn, event } from 't0n/log'
6
- import { meta } from '#/compiler/vite-plugin'
5
+ import { meta, build } from '#/compiler/bun-plugin'
7
6
  import type { SummaryMetadata } from '#/compiler/types'
8
7
  import { getConfig } from '../utils'
9
8
  import { filesize } from '@/utils'
@@ -1,17 +1,16 @@
1
1
  import { IMPORT, Datte } from 't0n'
2
2
  import { join } from 'pathe'
3
3
  import { gray, green, yellow, red, cyan } from 't0n/color'
4
- import type { UserConfig } from 'vite'
5
4
 
6
5
  import { resolveExtension } from '#/compiler/file'
7
- import config from '#/compiler/vite-plugin'
6
+ import config, { type DutoOptions } from '#/compiler/bun-plugin'
8
7
  import { _root } from '@/utils'
9
8
  import { match, match404 } from '#/compiler/routes'
10
9
  import type { Metadata, PluginOptions } from '#/compiler/types'
11
- import { EXTENSIONS_LIST } from '#/compiler/constants'
10
+ import { EXTENSIONS_LIST, NF_NAMES } from '#/compiler/constants'
12
11
 
13
- export async function getConfig(name = 'config'): Promise<UserConfig & PluginOptions> {
14
- let opts = {} as UserConfig
12
+ export async function getConfig(name = 'config'): Promise<DutoOptions> {
13
+ let opts = {} as DutoOptions
15
14
  const file = resolveExtension(name)
16
15
  if (file) opts = await IMPORT(join(_root, file), 'default')
17
16
  return config(opts)
@@ -85,17 +84,20 @@ export function preview(
85
84
  res = new Response(file)
86
85
  } else {
87
86
  matched = match(path)
88
- route = matched?.name || match404(meta, path)
87
+ const r404 = match404(meta, path) || '404'
88
+ route = matched?.name || r404
89
89
  res = new Response(null, { status: 404 })
90
90
 
91
91
  for (const asset of [
92
92
  path,
93
93
  join(path, 'index'),
94
+ r404,
94
95
  'index',
95
96
  ]) {
96
97
  file = Bun.file(join(_root, dist, asset +'.html'))
97
98
 
98
99
  if (await file.exists()) {
100
+ NF_NAMES.includes(asset.split('/').pop()!) && (route = r404)
99
101
  res = new Response(file)
100
102
  break
101
103
  }