@brust/create-app 2.0.0-alpha.3 → 2.0.0-alpha.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.
- package/README.md +2 -1
- package/package.json +6 -2
- package/src/args.ts +25 -3
- package/src/cli.ts +24 -6
- package/src/shadcn.ts +41 -0
- package/src/tailwind.ts +10 -0
- package/templates/blank/README.md.tmpl +4 -4
- package/templates/blank/_gitignore +1 -0
- package/templates/blank/app.css +32 -0
- package/templates/blank/components/Counter.tsx +5 -1
- package/templates/blank/components/Layout.tsx +5 -4
- package/templates/blank/package.json.tmpl +7 -4
- package/templates/blank/pages/HomePage.tsx +29 -5
- package/templates/blank/public/favicon.svg +1 -1
- package/templates/pokedex/README.md.tmpl +7 -9
- package/templates/pokedex/{styles/app.css → app.css} +0 -1
- package/templates/pokedex/components/AppLayout.tsx +1 -1
- package/templates/pokedex/components/TeamBuilder.tsx +2 -1
- package/templates/pokedex/components/UnifiedIsland.tsx +2 -1
- package/templates/pokedex/package.json.tmpl +7 -4
- package/templates/pokedex/pages/NotFoundPage.tsx +3 -1
- package/templates/blank/public/app.css +0 -14
- package/templates/pokedex/public/app.css +0 -3
package/README.md
CHANGED
|
@@ -4,6 +4,7 @@ Scaffold a [brust](https://github.com/AssetsArt/brust) v2 app.
|
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
6
|
bun create @brust/app my-app # --template blank (default) | pokedex
|
|
7
|
+
bun create @brust/app my-app --shadcn=button,card,dialog # also installs and runs `brust shadcn init` (needs @brust/core 2.0.0-alpha.11+)
|
|
7
8
|
cd my-app && bun install
|
|
8
9
|
bun run dev # http://127.0.0.1:1337
|
|
9
10
|
```
|
|
@@ -17,4 +18,4 @@ bun run dev # http://127.0.0.1:1337
|
|
|
17
18
|
|
|
18
19
|
`bun create` rejects `-t` and `-y`; use the long flags (the short ones work with `bunx @brust/create-app`).
|
|
19
20
|
|
|
20
|
-
Full guide: [docs/create-app.md](https://github.com/AssetsArt/brust/blob/
|
|
21
|
+
Full guide: [docs/create-app.md](https://github.com/AssetsArt/brust/blob/main/docs/create-app.md).
|
package/package.json
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brust/create-app",
|
|
3
|
-
"version": "2.0.0-alpha.
|
|
3
|
+
"version": "2.0.0-alpha.30",
|
|
4
4
|
"description": "Scaffold a brust v2 app — bun create @brust/app <name>",
|
|
5
5
|
"license": "MIT",
|
|
6
|
+
"homepage": "https://brust.dev",
|
|
6
7
|
"repository": {
|
|
7
8
|
"type": "git",
|
|
8
9
|
"url": "git+https://github.com/AssetsArt/brust.git",
|
|
9
10
|
"directory": "packages/create-app"
|
|
10
11
|
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/AssetsArt/brust/issues"
|
|
14
|
+
},
|
|
11
15
|
"type": "module",
|
|
12
16
|
"bin": {
|
|
13
17
|
"create-app": "./bin/create-app.ts"
|
|
@@ -20,7 +24,7 @@
|
|
|
20
24
|
"typecheck": "bun check"
|
|
21
25
|
},
|
|
22
26
|
"devDependencies": {
|
|
23
|
-
"@types/bun": "^1.4.
|
|
27
|
+
"@types/bun": "^1.4.4",
|
|
24
28
|
"typescript": "^7.0.2"
|
|
25
29
|
},
|
|
26
30
|
"publishConfig": {
|
package/src/args.ts
CHANGED
|
@@ -4,8 +4,11 @@ import { isAbsolute, resolve } from 'node:path'
|
|
|
4
4
|
|
|
5
5
|
const NAME_RE = /^[a-z0-9][a-z0-9_-]*$/
|
|
6
6
|
const MAX_NAME_LEN = 50
|
|
7
|
+
// The same rule as `brust shadcn init` (R41.8): a component is a registry name or an https URL, checked before anything is written.
|
|
8
|
+
const COMPONENT_RE = /^[a-z0-9][a-z0-9-]*$/
|
|
9
|
+
const REGISTRY_URL_RE = /^https:\/\/[^\s,]+$/
|
|
7
10
|
|
|
8
|
-
export const USAGE = 'usage: bun create @brust/app <name> [--template blank|pokedex] [--dir <path>] [--yes] [--brust-dep <spec>]'
|
|
11
|
+
export const USAGE = 'usage: bun create @brust/app <name> [--template blank|pokedex] [--tailwind] [--dir <path>] [--yes] [--brust-dep <spec>] [--shadcn[=button,card,...]] [--version]'
|
|
9
12
|
|
|
10
13
|
export interface ParsedArgs {
|
|
11
14
|
projectName: string
|
|
@@ -13,7 +16,13 @@ export interface ParsedArgs {
|
|
|
13
16
|
template?: string
|
|
14
17
|
yes: boolean
|
|
15
18
|
brustDep?: string
|
|
19
|
+
/** `--tailwind` (L21): accepted and ignored since L28: the blank template uses Tailwind CSS v4 by default. */
|
|
20
|
+
tailwind: boolean
|
|
16
21
|
help: boolean
|
|
22
|
+
/** `--shadcn[=a,b]` (F181): after scaffolding, install and run `brust shadcn init` with these components (`[]` = set up only). */
|
|
23
|
+
shadcn?: string[]
|
|
24
|
+
/** `--version` (F131): print the bare version and exit; no short form (see the note above). */
|
|
25
|
+
version: boolean
|
|
17
26
|
}
|
|
18
27
|
|
|
19
28
|
export function parseArgs(args: string[]): ParsedArgs {
|
|
@@ -22,7 +31,10 @@ export function parseArgs(args: string[]): ParsedArgs {
|
|
|
22
31
|
let template: string | undefined
|
|
23
32
|
let brustDep: string | undefined
|
|
24
33
|
let yes = false
|
|
34
|
+
let tailwind = false
|
|
25
35
|
let help = false
|
|
36
|
+
let version = false
|
|
37
|
+
let shadcn: string[] | undefined
|
|
26
38
|
const need = (flag: string, v: string | undefined): string => {
|
|
27
39
|
if (!v) throw new Error(`create-app: ${flag} requires a value`)
|
|
28
40
|
return v
|
|
@@ -32,6 +44,7 @@ export function parseArgs(args: string[]): ParsedArgs {
|
|
|
32
44
|
const a = args[i]!
|
|
33
45
|
if (a === '--') continue
|
|
34
46
|
if (a === '--help' || a === '-h') help = true
|
|
47
|
+
else if (a === '--version') version = true
|
|
35
48
|
else if (a === '--dir') dir = need('--dir', args[++i])
|
|
36
49
|
else if (a.startsWith('--dir=')) dir = need('--dir', a.slice('--dir='.length))
|
|
37
50
|
else if (a === '--template' || a === '-t') template = need('--template', args[++i])
|
|
@@ -39,12 +52,21 @@ export function parseArgs(args: string[]): ParsedArgs {
|
|
|
39
52
|
else if (a === '--brust-dep') brustDep = need('--brust-dep', args[++i])
|
|
40
53
|
else if (a.startsWith('--brust-dep=')) brustDep = need('--brust-dep', a.slice('--brust-dep='.length))
|
|
41
54
|
else if (a === '--yes' || a === '-y') yes = true
|
|
55
|
+
else if (a === '--tailwind') tailwind = true
|
|
56
|
+
else if (a === '--shadcn') shadcn = shadcn ?? []
|
|
57
|
+
else if (a.startsWith('--shadcn=')) {
|
|
58
|
+
const names = a.slice('--shadcn='.length).split(',').filter(Boolean)
|
|
59
|
+
for (const c of names) {
|
|
60
|
+
if (!COMPONENT_RE.test(c) && !REGISTRY_URL_RE.test(c)) throw new Error(`create-app: --shadcn component "${c}" must match ^[a-z0-9][a-z0-9-]*$ or be an https:// registry URL (without commas)`)
|
|
61
|
+
}
|
|
62
|
+
shadcn = [...(shadcn ?? []), ...names]
|
|
63
|
+
}
|
|
42
64
|
else if (a.startsWith('-')) throw new Error(`create-app: unknown flag "${a}"`)
|
|
43
65
|
else if (name === undefined) name = a
|
|
44
66
|
else throw new Error(`create-app: unexpected positional argument "${a}"`)
|
|
45
67
|
}
|
|
46
68
|
|
|
47
|
-
if (help) return { projectName: name ?? '', targetDir: '', template, yes, brustDep, help }
|
|
69
|
+
if (help || version) return { projectName: name ?? '', targetDir: '', template, yes, brustDep, tailwind, help, version }
|
|
48
70
|
if (!name) throw new Error(`create-app: missing project name. ${USAGE}`)
|
|
49
71
|
if (name.length > MAX_NAME_LEN) throw new Error(`create-app: project name too long (max ${MAX_NAME_LEN} chars)`)
|
|
50
72
|
if (!NAME_RE.test(name)) {
|
|
@@ -52,5 +74,5 @@ export function parseArgs(args: string[]): ParsedArgs {
|
|
|
52
74
|
}
|
|
53
75
|
const cwd = process.cwd()
|
|
54
76
|
const targetDir = dir ? (isAbsolute(dir) ? dir : resolve(cwd, dir)) : resolve(cwd, name)
|
|
55
|
-
return { projectName: name, targetDir, template, yes, brustDep, help }
|
|
77
|
+
return { projectName: name, targetDir, template, yes, brustDep, tailwind, help, version, ...(shadcn && { shadcn }) }
|
|
56
78
|
}
|
package/src/cli.ts
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
// `create-app` main — a port of 0.1.x runNew (runtime/cli/new.ts:221-294) with the v2 dependency resolution.
|
|
2
|
-
import { existsSync } from 'node:fs'
|
|
2
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
3
3
|
import { readdir, rm } from 'node:fs/promises'
|
|
4
4
|
import { copyTemplate } from './copy.ts'
|
|
5
5
|
import { parseArgs, USAGE } from './args.ts'
|
|
6
6
|
import { applyOverrides, monorepoRoot, resolveBrustDep } from './dep.ts'
|
|
7
|
+
import { TAILWIND_DEFAULT_NOTE } from './tailwind.ts'
|
|
8
|
+
import { runShadcn, shq, spawnRunner } from './shadcn.ts'
|
|
7
9
|
import { selectTemplate } from './templates.ts'
|
|
8
10
|
|
|
11
|
+
// F131: read at run time from the package.json that `bun publish` ships, so release-bump.ts stays the only place versions change.
|
|
12
|
+
const ownVersion = (): string => (JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')) as { version?: string }).version ?? '0.0.0'
|
|
13
|
+
|
|
9
14
|
const fail = (msg: string): never => {
|
|
10
15
|
console.error(msg)
|
|
11
16
|
process.exit(1)
|
|
@@ -19,6 +24,11 @@ export async function main(argv: string[]): Promise<void> {
|
|
|
19
24
|
console.log(USAGE)
|
|
20
25
|
return
|
|
21
26
|
}
|
|
27
|
+
if (parsed.version) {
|
|
28
|
+
console.log(ownVersion())
|
|
29
|
+
return
|
|
30
|
+
}
|
|
31
|
+
console.log(`@brust/create-app ${ownVersion()}\n`)
|
|
22
32
|
const { projectName, targetDir } = parsed
|
|
23
33
|
|
|
24
34
|
const targetExisted = existsSync(targetDir)
|
|
@@ -26,6 +36,8 @@ export async function main(argv: string[]): Promise<void> {
|
|
|
26
36
|
|
|
27
37
|
let tmpl: ReturnType<typeof selectTemplate>
|
|
28
38
|
try { tmpl = selectTemplate({ explicit: parsed.template, yes: parsed.yes, isTTY: Boolean(process.stdin.isTTY) }) } catch (e) { return fail(msg(e)) }
|
|
39
|
+
// L28 R29.1: Tailwind is the blank template's default; the flag stays accepted so older instructions still work.
|
|
40
|
+
if (parsed.tailwind) console.log(TAILWIND_DEFAULT_NOTE)
|
|
29
41
|
const dep = resolveBrustDep({ explicit: parsed.brustDep, targetDir })
|
|
30
42
|
|
|
31
43
|
try {
|
|
@@ -44,15 +56,21 @@ export async function main(argv: string[]): Promise<void> {
|
|
|
44
56
|
if (!targetExisted) await rm(targetDir, { recursive: true, force: true }).catch(() => {})
|
|
45
57
|
return fail(`create-app: failed to scaffold (${msg(e)})`)
|
|
46
58
|
}
|
|
47
|
-
|
|
59
|
+
if (parsed.shadcn) {
|
|
60
|
+
const code = await runShadcn({ targetDir, components: parsed.shadcn, workspace: dep.kind === 'workspace', run: spawnRunner, out: (l) => console.log(l) })
|
|
61
|
+
if (code !== 0) process.exit(code)
|
|
62
|
+
printNextSteps(projectName, targetDir, tmpl.name, dep.spec, dep.kind === 'workspace', true)
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
printNextSteps(projectName, targetDir, tmpl.name, dep.spec, dep.kind === 'workspace')
|
|
48
66
|
}
|
|
49
67
|
|
|
50
|
-
function printNextSteps(name: string, targetDir: string, template: string, workspace: boolean): void {
|
|
68
|
+
function printNextSteps(name: string, targetDir: string, template: string, spec: string, workspace: boolean, installed = false): void {
|
|
51
69
|
const cwd = process.cwd()
|
|
52
70
|
const displayPath = targetDir.startsWith(`${cwd}/`) ? `./${targetDir.slice(cwd.length + 1)}` : targetDir
|
|
53
|
-
console.log(`Created ${name} at ${targetDir} (template: ${template})\n`)
|
|
71
|
+
console.log(`Created ${name} at ${targetDir} (template: ${template}, @brust/core ${spec})\n`)
|
|
54
72
|
console.log('Next:')
|
|
55
|
-
console.log(` cd ${displayPath}`)
|
|
56
|
-
console.log(workspace ? ' bun install # run it at the brust repo root: this app is a workspace member' : ' bun install')
|
|
73
|
+
console.log(` cd ${shq(displayPath)}`)
|
|
74
|
+
if (!installed) console.log(workspace ? ' bun install # run it at the brust repo root: this app is a workspace member' : ' bun install')
|
|
57
75
|
console.log(' bun run dev')
|
|
58
76
|
}
|
package/src/shadcn.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// `--shadcn[=a,b]` (F181): after the scaffold, install the project and run its own `brust shadcn init`.
|
|
2
|
+
// Every child is an argv array (no shell). The project's own `node_modules/.bin/brust` is run by path, never `bunx brust`,
|
|
3
|
+
// which could fetch an unrelated package named `brust` from the registry when the local one is missing.
|
|
4
|
+
import { join } from 'node:path'
|
|
5
|
+
|
|
6
|
+
/** Runs `argv` in `cwd` with the terminal attached; resolves to the exit code. */
|
|
7
|
+
export type Runner = (argv: string[], cwd: string) => Promise<number>
|
|
8
|
+
|
|
9
|
+
export const spawnRunner: Runner = async (argv, cwd) => {
|
|
10
|
+
try {
|
|
11
|
+
const p = Bun.spawn(argv, { cwd, stdin: 'inherit', stdout: 'inherit', stderr: 'inherit' })
|
|
12
|
+
return await p.exited
|
|
13
|
+
} catch {
|
|
14
|
+
return 127
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** POSIX single-quoting: nothing inside expands when the printed command is pasted into a shell. */
|
|
19
|
+
export const shq = (s: string): string => (/^[A-Za-z0-9_@%+=:,./-]+$/.test(s) ? s : `'${s.replace(/'/g, `'\\''`)}'`)
|
|
20
|
+
|
|
21
|
+
/** Returns 0 when done, else the failing step's exit code (the project is left in place; `lines` says what to run). */
|
|
22
|
+
export async function runShadcn(o: { targetDir: string; components: string[]; workspace: boolean; run: Runner; out: (line: string) => void }): Promise<number> {
|
|
23
|
+
const { targetDir, components, out } = o
|
|
24
|
+
const by = `cd ${shq(targetDir)} && bun install && node_modules/.bin/brust shadcn init${components.map((c) => ` ${shq(c)}`).join('')}`
|
|
25
|
+
if (o.workspace) {
|
|
26
|
+
out('--shadcn: this app is a workspace member of a brust checkout; run `bun install` at the repo root, then in the app:')
|
|
27
|
+
out(` node_modules/.bin/brust shadcn init${components.map((c) => ` ${shq(c)}`).join('')}`)
|
|
28
|
+
return 0
|
|
29
|
+
}
|
|
30
|
+
out('\n--shadcn: installing, then running `brust shadcn init` (needs @brust/core 2.0.0-alpha.11 or later)')
|
|
31
|
+
const install = await o.run(['bun', 'install'], targetDir)
|
|
32
|
+
if (install !== 0) {
|
|
33
|
+
out(`create-app: bun install failed (exit ${install}); the project is created. Finish with: ${by}`)
|
|
34
|
+
return install
|
|
35
|
+
}
|
|
36
|
+
const init = await o.run([join(targetDir, 'node_modules/.bin/brust'), 'shadcn', 'init', ...components], targetDir)
|
|
37
|
+
if (init !== 0) {
|
|
38
|
+
out(`create-app: brust shadcn init failed (exit ${init}); the project is created. Run again: cd ${shq(targetDir)} && node_modules/.bin/brust shadcn init${components.map((c) => ` ${shq(c)}`).join('')}`)
|
|
39
|
+
}
|
|
40
|
+
return init
|
|
41
|
+
}
|
package/src/tailwind.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Tailwind CSS v4 in the blank template (gaps round L21, made the default by L28 R29.1): the template ships `app.css`
|
|
2
|
+
// (`@import "tailwindcss"` + `@theme` tokens), the layout imports it (brust builds it and links it in <head>, L19), and
|
|
3
|
+
// the pinned dev dependencies below are in its package.json (R21.1: the app's own; L32 R33.2: the compiler, its Node API
|
|
4
|
+
// and the scanner, one exact version, so the build compiles with the tailwindcss the app pinned). `--tailwind` no longer
|
|
5
|
+
// changes anything; it prints TAILWIND_DEFAULT_NOTE.
|
|
6
|
+
|
|
7
|
+
/** The exact versions brust's own tests build with (the blank template's package.json pins the same). */
|
|
8
|
+
export const TAILWIND_DEV_DEPS = { '@tailwindcss/node': '4.3.3', '@tailwindcss/oxide': '4.3.3', tailwindcss: '4.3.3' }
|
|
9
|
+
|
|
10
|
+
export const TAILWIND_DEFAULT_NOTE = 'create-app: Tailwind CSS v4 is the default; the --tailwind flag does nothing'
|
|
@@ -6,10 +6,10 @@ A [brust](https://github.com/AssetsArt/brust) app: React components compiled ahe
|
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
8
|
bun install
|
|
9
|
-
bun run dev # brust
|
|
9
|
+
bun run dev # brust dev routes.tsx (hot reload) -> http://127.0.0.1:1337
|
|
10
10
|
```
|
|
11
11
|
|
|
12
|
-
Scripts: `dev` (
|
|
12
|
+
Scripts: `dev` (`brust dev`, with hot reload), `build`, `start`, `typecheck`, `dev:cf` (build for Cloudflare Workers and run `wrangler dev`), `deploy:cf` (build and `wrangler deploy`).
|
|
13
13
|
|
|
14
14
|
## Layout
|
|
15
15
|
|
|
@@ -39,5 +39,5 @@ const { data } = await api.hello.get({ query: { name: 'ash' } })
|
|
|
39
39
|
|
|
40
40
|
## Docs
|
|
41
41
|
|
|
42
|
-
- Actions: https://github.com/AssetsArt/brust/blob/
|
|
43
|
-
- The unified store: https://github.com/AssetsArt/brust/blob/
|
|
42
|
+
- Actions: https://github.com/AssetsArt/brust/blob/main/docs/actions.md
|
|
43
|
+
- The unified store: https://github.com/AssetsArt/brust/blob/main/docs/store.md
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
2
|
+
|
|
3
|
+
/* Tailwind CSS v4, CSS-first: no tailwind.config. The tokens below are the brust docs palette; each one becomes a
|
|
4
|
+
utility (bg-paper, text-ink, border-rule, ...). A *.module.css that uses @apply starts with
|
|
5
|
+
@reference "../app.css"; (see brust docs/css.md). */
|
|
6
|
+
@theme {
|
|
7
|
+
--color-paper: oklch(94% 0.035 78);
|
|
8
|
+
--color-sheet: oklch(97% 0.02 82);
|
|
9
|
+
--color-ink: oklch(18% 0.035 82);
|
|
10
|
+
--color-ink-2: oklch(36% 0.045 80);
|
|
11
|
+
--color-rule: oklch(76% 0.05 77);
|
|
12
|
+
--color-amber: oklch(68% 0.145 74);
|
|
13
|
+
--color-amber-ink: oklch(46% 0.12 62);
|
|
14
|
+
--font-sans: ui-rounded, "SF Pro Rounded", system-ui, sans-serif;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/* Dark mode follows the system: the same tokens, darker values, so every utility switches with them. */
|
|
18
|
+
@media (prefers-color-scheme: dark) {
|
|
19
|
+
:root {
|
|
20
|
+
--color-paper: oklch(20% 0.028 185);
|
|
21
|
+
--color-sheet: oklch(24% 0.032 182);
|
|
22
|
+
--color-ink: oklch(91% 0.02 82);
|
|
23
|
+
--color-ink-2: oklch(77% 0.03 82);
|
|
24
|
+
--color-rule: oklch(36% 0.032 180);
|
|
25
|
+
--color-amber: oklch(74% 0.12 78);
|
|
26
|
+
--color-amber-ink: oklch(81% 0.11 80);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
:root {
|
|
31
|
+
color-scheme: light dark;
|
|
32
|
+
}
|
|
@@ -4,7 +4,11 @@ import { useState } from 'react'
|
|
|
4
4
|
export default function Counter() {
|
|
5
5
|
const [count, setCount] = useState(0)
|
|
6
6
|
return (
|
|
7
|
-
<button
|
|
7
|
+
<button
|
|
8
|
+
type="button"
|
|
9
|
+
className="cursor-pointer rounded-[0.6rem] border border-amber-ink bg-amber px-[1.1rem] py-2 font-semibold text-[oklch(18%_0.035_82)] hover:brightness-106 focus-visible:outline-3 focus-visible:outline-offset-3 focus-visible:outline-amber-ink"
|
|
10
|
+
onClick={() => setCount(count + 1)}
|
|
11
|
+
>
|
|
8
12
|
count is {count}
|
|
9
13
|
</button>
|
|
10
14
|
)
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
// The document root: a plain <html> with <Outlet/> for the page.
|
|
1
|
+
// The document root: a plain <html> with <Outlet/> for the page. app.css (Tailwind CSS v4) is imported here; brust
|
|
2
|
+
// builds it into one hashed file and links it in <head>. Static files are served from /public.
|
|
2
3
|
import { Outlet } from '@brust/core/routes'
|
|
4
|
+
import '../app.css'
|
|
3
5
|
|
|
4
6
|
export default function Layout() {
|
|
5
7
|
return (
|
|
@@ -9,10 +11,9 @@ export default function Layout() {
|
|
|
9
11
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
10
12
|
<title>brust app</title>
|
|
11
13
|
<link rel="icon" href="/public/favicon.svg" />
|
|
12
|
-
<link rel="stylesheet" href="/public/app.css" />
|
|
13
14
|
</head>
|
|
14
|
-
<body>
|
|
15
|
-
<main>
|
|
15
|
+
<body className="bg-paper font-sans text-ink leading-[1.55]">
|
|
16
|
+
<main className="mx-auto box-border max-w-2xl px-4 py-16">
|
|
16
17
|
<Outlet />
|
|
17
18
|
</main>
|
|
18
19
|
</body>
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
|
-
"dev": "brust
|
|
7
|
+
"dev": "brust dev routes.tsx",
|
|
8
8
|
"build": "brust build routes.tsx",
|
|
9
9
|
"start": "brust start",
|
|
10
10
|
"typecheck": "bun check",
|
|
@@ -13,14 +13,17 @@
|
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
15
|
"@brust/core": __BRUST_DEP__,
|
|
16
|
-
"@elysia/eden": "2.0.0-
|
|
17
|
-
"elysia": "2.0.0-
|
|
16
|
+
"@elysia/eden": "2.0.0-rc.0",
|
|
17
|
+
"elysia": "2.0.0-rc.0",
|
|
18
18
|
"react": "^19.2.0",
|
|
19
19
|
"react-dom": "^19.2.0"
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
|
-
"@
|
|
22
|
+
"@tailwindcss/node": "4.3.3",
|
|
23
|
+
"@tailwindcss/oxide": "4.3.3",
|
|
24
|
+
"@types/bun": "^1.4.4",
|
|
23
25
|
"@types/react": "^19.2.0",
|
|
26
|
+
"tailwindcss": "4.3.3",
|
|
24
27
|
"typescript": "^7.0.2",
|
|
25
28
|
"wrangler": "^4.149.0"
|
|
26
29
|
}
|
|
@@ -1,11 +1,35 @@
|
|
|
1
1
|
import Counter from '../components/Counter'
|
|
2
2
|
|
|
3
|
+
const link = 'font-semibold text-amber-ink underline underline-offset-[0.2em] hover:decoration-2 focus-visible:rounded focus-visible:outline-3 focus-visible:outline-offset-3 focus-visible:outline-amber-ink'
|
|
4
|
+
|
|
3
5
|
export default function HomePage() {
|
|
4
6
|
return (
|
|
5
|
-
|
|
6
|
-
<
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
<>
|
|
8
|
+
<header className="flex items-center gap-[0.9rem]">
|
|
9
|
+
<svg className="flex-none" viewBox="0 0 32 32" width="44" height="44" aria-hidden="true">
|
|
10
|
+
<rect width="32" height="32" rx="8" fill="#062323" />
|
|
11
|
+
<path d="M7 19a9 9 0 0 1 18 0z" fill="#f2a516" />
|
|
12
|
+
<rect x="5" y="20.5" width="22" height="2.2" rx="1.1" fill="#f2a516" />
|
|
13
|
+
<rect x="10" y="24.6" width="12" height="2" rx="1" fill="#f2a516" opacity=".55" />
|
|
14
|
+
</svg>
|
|
15
|
+
<h1 className="m-0 text-4xl leading-[1.1] font-bold tracking-[-0.01em]">brust app</h1>
|
|
16
|
+
</header>
|
|
17
|
+
<p className="mt-4 mb-8 text-lg text-ink-2">Edit pages/HomePage.tsx and save. The page reloads.</p>
|
|
18
|
+
<section className="rounded-2xl border border-rule bg-sheet p-6">
|
|
19
|
+
<Counter />
|
|
20
|
+
<p className="mt-4 mb-0 text-[0.9375rem] text-ink-2">useState only, so this button is native: a signal in the browser, no React shipped.</p>
|
|
21
|
+
</section>
|
|
22
|
+
<nav className="mt-8 flex flex-wrap gap-x-6 gap-y-2" aria-label="Learn more">
|
|
23
|
+
<a className={link} href="https://brust.dev">
|
|
24
|
+
Docs
|
|
25
|
+
</a>
|
|
26
|
+
<a className={link} href="https://brust.dev/docs/routing">
|
|
27
|
+
Routes
|
|
28
|
+
</a>
|
|
29
|
+
<a className={link} href="https://brust.dev/docs/actions">
|
|
30
|
+
Actions
|
|
31
|
+
</a>
|
|
32
|
+
</nav>
|
|
33
|
+
</>
|
|
10
34
|
)
|
|
11
35
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><rect width="32" height="32" rx="8" fill="#062323"/><path d="M7 19a9 9 0 0 1 18 0z" fill="#f2a516"/><rect x="5" y="20.5" width="22" height="2.2" rx="1.1" fill="#f2a516"/><rect x="10" y="24.6" width="12" height="2" rx="1" fill="#f2a516" opacity=".55"/></svg>
|
|
@@ -7,7 +7,7 @@ React islands, and a persistent team built with Elysia actions + SQLite and one
|
|
|
7
7
|
|
|
8
8
|
```bash
|
|
9
9
|
bun install
|
|
10
|
-
bun run dev # brust
|
|
10
|
+
bun run dev # brust dev routes.tsx (hot reload) -> http://127.0.0.1:1337
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
Scripts: `dev` (build then start), `build`, `start`, `typecheck`, `snapshot` (needs network; rewrites `data/pokedex.json`).
|
|
@@ -45,15 +45,13 @@ then `bun run deploy:cf`.
|
|
|
45
45
|
Preview note: a team change refreshes cached team pages only in the isolate that served the action; other isolates catch up
|
|
46
46
|
within the route `ttl` (60 s for `/pokemon/*`, 1 h for `/type-chart`) until the cross-isolate invalidation lands.
|
|
47
47
|
|
|
48
|
-
##
|
|
48
|
+
## Styling
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
(Tailwind must resolve from the input's directory: install `tailwindcss` in the project first.)
|
|
50
|
+
`app.css` (Tailwind CSS v4, CSS-first: no `tailwind.config`) is imported by `components/AppLayout.tsx` and by the 404 page, which renders its
|
|
51
|
+
own `<html>`. Edit `app.css`; brust builds it into one minified, content-hashed file and links it in `<head>`. There is no prebuilt
|
|
52
|
+
stylesheet and no `<link>` to write. `brust dev` swaps the stylesheet in place, without a reload.
|
|
55
53
|
|
|
56
54
|
## Docs
|
|
57
55
|
|
|
58
|
-
- Actions: https://github.com/AssetsArt/brust/blob/
|
|
59
|
-
- The unified store: https://github.com/AssetsArt/brust/blob/
|
|
56
|
+
- Actions: https://github.com/AssetsArt/brust/blob/main/docs/actions.md
|
|
57
|
+
- The unified store: https://github.com/AssetsArt/brust/blob/main/docs/store.md
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// components/AppLayout.tsx — the document (S9): plain <html>, <Outlet/> for the leaf, TeamBuilder as a react child.
|
|
2
2
|
import { Outlet } from '@brust/core/routes'
|
|
3
3
|
import type { NavItem } from '../lib/types'
|
|
4
|
+
import '../app.css'
|
|
4
5
|
import NavLink from './NavLink'
|
|
5
6
|
import TeamBuilder from './TeamBuilder'
|
|
6
7
|
import ThemeToggle from './ThemeToggle'
|
|
@@ -13,7 +14,6 @@ export default function AppLayout(props: { title: string; mode: 'dark' | 'light'
|
|
|
13
14
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
14
15
|
<title>{props.title}</title>
|
|
15
16
|
<link rel="icon" href="/public/favicon.svg" />
|
|
16
|
-
<link rel="stylesheet" href="/public/app.css" />
|
|
17
17
|
</head>
|
|
18
18
|
<body className="min-h-screen bg-slate-50 text-slate-900 dark:bg-slate-950 dark:text-slate-100">
|
|
19
19
|
<header className="sticky top-0 z-50 border-b border-slate-200 bg-white/80 dark:border-slate-800 dark:bg-slate-950/80">
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// components/TeamBuilder.tsx — react tier on purpose (useReducer for `open`): SSR + idle hydration (S12). The roster is the
|
|
2
2
|
// unified `team` store: the SSR scope is seeded from the snapshot, so the first client snapshot equals the server paint (D8).
|
|
3
3
|
import { useStore } from '@brust/core/store/react'
|
|
4
|
-
import { useReducer } from 'react'
|
|
4
|
+
import { useReducer, useTransition } from 'react'
|
|
5
5
|
import { removeMember } from '../lib/team-client'
|
|
6
6
|
import { team } from '../stores/team'
|
|
7
7
|
|
|
@@ -9,6 +9,7 @@ const flip = (open: boolean): boolean => !open
|
|
|
9
9
|
|
|
10
10
|
export default function TeamBuilder() {
|
|
11
11
|
const { members, max } = useStore(team)
|
|
12
|
+
useTransition()
|
|
12
13
|
const [open, toggle] = useReducer(flip, false)
|
|
13
14
|
return (
|
|
14
15
|
<div className="fixed bottom-5 right-5 z-[200]">
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
// components/UnifiedIsland.tsx — React island (SSR + hydration). `useStore` alone compiles native, so the "clicks from
|
|
2
2
|
// React" tally (useReducer) is what puts this on the react tier (D10 asks for an island).
|
|
3
3
|
import { useStore } from '@brust/core/store/react'
|
|
4
|
-
import { useReducer } from 'react'
|
|
4
|
+
import { useReducer, useTransition } from 'react'
|
|
5
5
|
import { unified } from '../stores/unified'
|
|
6
6
|
|
|
7
7
|
const tick = (n: number): number => n + 1
|
|
8
8
|
|
|
9
9
|
export default function UnifiedIsland() {
|
|
10
10
|
const { count } = useStore(unified)
|
|
11
|
+
useTransition()
|
|
11
12
|
const [mine, tally] = useReducer(tick, 0)
|
|
12
13
|
return (
|
|
13
14
|
<div className="flex flex-col items-center gap-4">
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
|
-
"dev": "brust
|
|
7
|
+
"dev": "brust dev routes.tsx",
|
|
8
8
|
"build": "brust build routes.tsx",
|
|
9
9
|
"dev:cf": "wrangler d1 migrations apply TEAM_DB --local --persist-to .wrangler/state && brust dev routes.tsx --target cloudflare",
|
|
10
10
|
"deploy:cf": "brust build routes.tsx --target cloudflare && wrangler deploy -c dist/cf/wrangler.jsonc",
|
|
@@ -16,14 +16,17 @@
|
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"@brust/core": __BRUST_DEP__,
|
|
19
|
-
"@elysia/eden": "2.0.0-
|
|
20
|
-
"elysia": "2.0.0-
|
|
19
|
+
"@elysia/eden": "2.0.0-rc.0",
|
|
20
|
+
"elysia": "2.0.0-rc.0",
|
|
21
21
|
"react": "^19.2.0",
|
|
22
22
|
"react-dom": "^19.2.0"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
|
-
"@
|
|
25
|
+
"@tailwindcss/node": "4.3.3",
|
|
26
|
+
"@tailwindcss/oxide": "4.3.3",
|
|
27
|
+
"@types/bun": "^1.4.4",
|
|
26
28
|
"@types/react": "^19.2.0",
|
|
29
|
+
"tailwindcss": "4.3.3",
|
|
27
30
|
"typescript": "^7.0.2",
|
|
28
31
|
"wrangler": "^4.149.0"
|
|
29
32
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import '../app.css'
|
|
2
|
+
|
|
1
3
|
export default function NotFoundPage() {
|
|
2
4
|
return (
|
|
3
|
-
<html lang="en" data-mode="dark"><head><meta charSet="utf-8" /><title>Not found · PokéDex</title
|
|
5
|
+
<html lang="en" data-mode="dark"><head><meta charSet="utf-8" /><title>Not found · PokéDex</title></head>
|
|
4
6
|
<body className="min-h-screen bg-slate-50 dark:bg-slate-950"><main className="mx-auto max-w-md py-16 text-center"><div className="text-6xl font-black text-brand-500">404</div><h1 className="mt-4 text-2xl font-extrabold">Nothing here</h1><a href="/" className="mt-6 inline-block rounded-xl bg-brand-500 px-5 py-2.5 text-sm font-semibold text-white no-underline">Back home</a></main></body>
|
|
5
7
|
</html>
|
|
6
8
|
)
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
:root { color-scheme: light dark; font-family: system-ui, sans-serif; }
|
|
2
|
-
body { margin: 0; }
|
|
3
|
-
main { max-width: 40rem; margin: 4rem auto; padding: 0 1.5rem; line-height: 1.5; }
|
|
4
|
-
h1 { font-size: 2rem; margin: 0 0 0.5rem; }
|
|
5
|
-
button {
|
|
6
|
-
cursor: pointer;
|
|
7
|
-
font: inherit;
|
|
8
|
-
padding: 0.5rem 1rem;
|
|
9
|
-
border: 1px solid currentColor;
|
|
10
|
-
border-radius: 0.5rem;
|
|
11
|
-
background: transparent;
|
|
12
|
-
color: inherit;
|
|
13
|
-
}
|
|
14
|
-
button:hover { opacity: 0.8; }
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
/* generated: bunx @tailwindcss/cli@4 -i styles/app.css -o public/app.css --minify — do not edit */
|
|
2
|
-
/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */
|
|
3
|
-
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-border-style:solid;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1;--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0}}}@layer theme{:root,:host{--font-sans:-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-emerald-500:oklch(69.6% .17 162.48);--color-indigo-700:oklch(45.7% .24 277.023);--color-rose-400:oklch(71.2% .194 13.428);--color-rose-500:oklch(64.5% .246 16.439);--color-slate-50:oklch(98.4% .003 247.858);--color-slate-100:oklch(96.8% .007 247.896);--color-slate-200:oklch(92.9% .013 255.508);--color-slate-300:oklch(86.9% .022 252.894);--color-slate-400:oklch(70.4% .04 256.788);--color-slate-500:oklch(55.4% .046 257.417);--color-slate-600:oklch(44.6% .043 257.281);--color-slate-700:oklch(37.2% .044 257.287);--color-slate-800:oklch(27.9% .041 260.031);--color-slate-900:oklch(20.8% .042 265.755);--color-slate-950:oklch(12.9% .042 264.695);--color-white:#fff;--spacing:.25rem;--container-xs:20rem;--container-md:28rem;--container-xl:36rem;--container-2xl:42rem;--container-6xl:72rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25 / 1.875);--text-4xl:2.25rem;--text-4xl--line-height:calc(2.5 / 2.25);--text-5xl:3rem;--text-5xl--line-height:1;--text-6xl:3.75rem;--text-6xl--line-height:1;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--font-weight-extrabold:800;--font-weight-black:900;--tracking-tighter:-.05em;--tracking-tight:-.025em;--tracking-wide:.025em;--tracking-wider:.05em;--leading-relaxed:1.625;--radius-lg:.5rem;--radius-xl:.75rem;--radius-2xl:1rem;--radius-3xl:1.5rem;--drop-shadow-xl:0 9px 7px #0000001a;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--color-brand-50:oklch(97% .02 264);--color-brand-500:oklch(55% .2 264);--color-brand-600:oklch(49% .2 264);--color-brand-700:oklch(43% .19 264)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring:where(:not(iframe)){outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.fixed{position:fixed}.relative{position:relative}.static{position:static}.sticky{position:sticky}.top-0{top:0}.right-5{right:calc(var(--spacing) * 5)}.bottom-5{bottom:calc(var(--spacing) * 5)}.left-0{left:0}.z-10{z-index:10}.z-20{z-index:20}.z-50{z-index:50}.z-\[200\]{z-index:200}.mx-auto{margin-inline:auto}.mt-1{margin-top:var(--spacing)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-3{margin-top:calc(var(--spacing) * 3)}.mt-4{margin-top:calc(var(--spacing) * 4)}.mt-5{margin-top:calc(var(--spacing) * 5)}.mt-6{margin-top:calc(var(--spacing) * 6)}.mt-8{margin-top:calc(var(--spacing) * 8)}.mr-2{margin-right:calc(var(--spacing) * 2)}.mb-1{margin-bottom:var(--spacing)}.mb-3{margin-bottom:calc(var(--spacing) * 3)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.mb-5{margin-bottom:calc(var(--spacing) * 5)}.mb-6{margin-bottom:calc(var(--spacing) * 6)}.mb-8{margin-bottom:calc(var(--spacing) * 8)}.ml-auto{margin-left:auto}.block{display:block}.contents{display:contents}.flex{display:flex}.grid{display:grid}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.aspect-square{aspect-ratio:1}.h-2\.5{height:calc(var(--spacing) * 2.5)}.h-5{height:calc(var(--spacing) * 5)}.h-6{height:calc(var(--spacing) * 6)}.h-7{height:calc(var(--spacing) * 7)}.h-8{height:calc(var(--spacing) * 8)}.h-10{height:calc(var(--spacing) * 10)}.h-16{height:calc(var(--spacing) * 16)}.h-20{height:calc(var(--spacing) * 20)}.h-24{height:calc(var(--spacing) * 24)}.h-28{height:calc(var(--spacing) * 28)}.h-56{height:calc(var(--spacing) * 56)}.h-full{height:100%}.min-h-screen{min-height:100vh}.w-5{width:calc(var(--spacing) * 5)}.w-6{width:calc(var(--spacing) * 6)}.w-7{width:calc(var(--spacing) * 7)}.w-8{width:calc(var(--spacing) * 8)}.w-20{width:calc(var(--spacing) * 20)}.w-24{width:calc(var(--spacing) * 24)}.w-28{width:calc(var(--spacing) * 28)}.w-56{width:calc(var(--spacing) * 56)}.w-80{width:calc(var(--spacing) * 80)}.w-full{width:100%}.max-w-2xl{max-width:var(--container-2xl)}.max-w-6xl{max-width:var(--container-6xl)}.max-w-md{max-width:var(--container-md)}.max-w-xl{max-width:var(--container-xl)}.min-w-0{min-width:0}.min-w-36{min-width:calc(var(--spacing) * 36)}.flex-1{flex:1}.shrink-0{flex-shrink:0}.grow{flex-grow:1}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-cols-\[2\.75rem_repeat\(18\,minmax\(1\.75rem\,1fr\)\)\]{grid-template-columns:2.75rem repeat(18,minmax(1.75rem,1fr))}.grid-cols-\[3rem_2\.5rem_1fr\]{grid-template-columns:3rem 2.5rem 1fr}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.place-items-center{place-items:center}.items-center{align-items:center}.items-end{align-items:flex-end}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.gap-1{gap:var(--spacing)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-2\.5{gap:calc(var(--spacing) * 2.5)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}.gap-6{gap:calc(var(--spacing) * 6)}.gap-px{gap:1px}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 3) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-6>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 6) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-10>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 10) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 10) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-16>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 16) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 16) * calc(1 - var(--tw-space-y-reverse)))}.self-start{align-self:flex-start}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:var(--radius-2xl)}.rounded-3xl{border-radius:var(--radius-3xl)}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-xl{border-radius:var(--radius-xl)}.rounded-l-xl{border-top-left-radius:var(--radius-xl);border-bottom-left-radius:var(--radius-xl)}.rounded-r-xl{border-top-right-radius:var(--radius-xl);border-bottom-right-radius:var(--radius-xl)}.border{border-style:var(--tw-border-style);border-width:1px}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-brand-500{border-color:var(--color-brand-500)}.border-slate-100{border-color:var(--color-slate-100)}.border-slate-200{border-color:var(--color-slate-200)}.border-slate-300{border-color:var(--color-slate-300)}.bg-brand-50{background-color:var(--color-brand-50)}.bg-brand-50\/60{background-color:#f0f5ff99}@supports (color:color-mix(in lab, red, red)){.bg-brand-50\/60{background-color:color-mix(in oklab, var(--color-brand-50) 60%, transparent)}}.bg-brand-500{background-color:var(--color-brand-500)}.bg-emerald-500{background-color:var(--color-emerald-500)}.bg-rose-400{background-color:var(--color-rose-400)}.bg-slate-50{background-color:var(--color-slate-50)}.bg-slate-100{background-color:var(--color-slate-100)}.bg-slate-200{background-color:var(--color-slate-200)}.bg-slate-800{background-color:var(--color-slate-800)}.bg-transparent{background-color:#0000}.bg-white{background-color:var(--color-white)}.bg-white\/15{background-color:#ffffff26}@supports (color:color-mix(in lab, red, red)){.bg-white\/15{background-color:color-mix(in oklab, var(--color-white) 15%, transparent)}}.bg-white\/25{background-color:#ffffff40}@supports (color:color-mix(in lab, red, red)){.bg-white\/25{background-color:color-mix(in oklab, var(--color-white) 25%, transparent)}}.bg-white\/80{background-color:#fffc}@supports (color:color-mix(in lab, red, red)){.bg-white\/80{background-color:color-mix(in oklab, var(--color-white) 80%, transparent)}}.bg-white\/95{background-color:#fffffff2}@supports (color:color-mix(in lab, red, red)){.bg-white\/95{background-color:color-mix(in oklab, var(--color-white) 95%, transparent)}}.bg-gradient-to-br{--tw-gradient-position:to bottom right in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.from-brand-500{--tw-gradient-from:var(--color-brand-500);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.via-brand-600{--tw-gradient-via:var(--color-brand-600);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.to-indigo-700{--tw-gradient-to:var(--color-indigo-700);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.object-contain{object-fit:contain}.p-1{padding:var(--spacing)}.p-2{padding:calc(var(--spacing) * 2)}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.p-5{padding:calc(var(--spacing) * 5)}.p-6{padding:calc(var(--spacing) * 6)}.p-8{padding:calc(var(--spacing) * 8)}.px-1{padding-inline:var(--spacing)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-3\.5{padding-inline:calc(var(--spacing) * 3.5)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-5{padding-inline:calc(var(--spacing) * 5)}.px-6{padding-inline:calc(var(--spacing) * 6)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:var(--spacing)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-2\.5{padding-block:calc(var(--spacing) * 2.5)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-4{padding-block:calc(var(--spacing) * 4)}.py-5{padding-block:calc(var(--spacing) * 5)}.py-6{padding-block:calc(var(--spacing) * 6)}.py-7{padding-block:calc(var(--spacing) * 7)}.py-8{padding-block:calc(var(--spacing) * 8)}.py-16{padding-block:calc(var(--spacing) * 16)}.pr-3{padding-right:calc(var(--spacing) * 3)}.pb-2{padding-bottom:calc(var(--spacing) * 2)}.pl-1{padding-left:var(--spacing)}.text-center{text-align:center}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.text-6xl{font-size:var(--text-6xl);line-height:var(--tw-leading,var(--text-6xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[9px\]{font-size:9px}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.leading-none{--tw-leading:1;line-height:1}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.font-black{--tw-font-weight:var(--font-weight-black);font-weight:var(--font-weight-black)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-extrabold{--tw-font-weight:var(--font-weight-extrabold);font-weight:var(--font-weight-extrabold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.tracking-tighter{--tw-tracking:var(--tracking-tighter);letter-spacing:var(--tracking-tighter)}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.text-brand-500{color:var(--color-brand-500)}.text-brand-600{color:var(--color-brand-600)}.text-brand-700{color:var(--color-brand-700)}.text-slate-100{color:var(--color-slate-100)}.text-slate-300{color:var(--color-slate-300)}.text-slate-400{color:var(--color-slate-400)}.text-slate-500{color:var(--color-slate-500)}.text-slate-600{color:var(--color-slate-600)}.text-slate-700{color:var(--color-slate-700)}.text-slate-800{color:var(--color-slate-800)}.text-slate-900{color:var(--color-slate-900)}.text-white{color:var(--color-white)}.text-white\/85{color:#ffffffd9}@supports (color:color-mix(in lab, red, red)){.text-white\/85{color:color-mix(in oklab, var(--color-white) 85%, transparent)}}.text-white\/90{color:#ffffffe6}@supports (color:color-mix(in lab, red, red)){.text-white\/90{color:color-mix(in oklab, var(--color-white) 90%, transparent)}}.uppercase{text-transform:uppercase}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}.no-underline{text-decoration-line:none}.shadow-2xl{--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,#00000040);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a), 0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.ring,.ring-1{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.ring-slate-200{--tw-ring-color:var(--color-slate-200)}.ring-white\/20{--tw-ring-color:#fff3}@supports (color:color-mix(in lab, red, red)){.ring-white\/20{--tw-ring-color:color-mix(in oklab, var(--color-white) 20%, transparent)}}.ring-white\/30{--tw-ring-color:#ffffff4d}@supports (color:color-mix(in lab, red, red)){.ring-white\/30{--tw-ring-color:color-mix(in oklab, var(--color-white) 30%, transparent)}}.drop-shadow-xl{--tw-drop-shadow-size:drop-shadow(0 9px 7px var(--tw-drop-shadow-color,#0000001a));--tw-drop-shadow:drop-shadow(var(--drop-shadow-xl));filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}@media (hover:hover){.group-hover\:scale-110:is(:where(.group):hover *){--tw-scale-x:110%;--tw-scale-y:110%;--tw-scale-z:110%;scale:var(--tw-scale-x) var(--tw-scale-y)}.hover\:relative:hover{position:relative}.hover\:z-10:hover{z-index:10}.hover\:-translate-y-0\.5:hover{--tw-translate-y:calc(var(--spacing) * -.5);translate:var(--tw-translate-x) var(--tw-translate-y)}.hover\:border-brand-500\/50:hover{border-color:#3266e480}@supports (color:color-mix(in lab, red, red)){.hover\:border-brand-500\/50:hover{border-color:color-mix(in oklab, var(--color-brand-500) 50%, transparent)}}.hover\:border-slate-500:hover{border-color:var(--color-slate-500)}.hover\:bg-brand-600:hover{background-color:var(--color-brand-600)}.hover\:bg-slate-100:hover{background-color:var(--color-slate-100)}.hover\:bg-white\/25:hover{background-color:#ffffff40}@supports (color:color-mix(in lab, red, red)){.hover\:bg-white\/25:hover{background-color:color-mix(in oklab, var(--color-white) 25%, transparent)}}.hover\:text-slate-600:hover{color:var(--color-slate-600)}.hover\:underline:hover{text-decoration-line:underline}.hover\:opacity-90:hover{opacity:.9}.hover\:shadow-md:hover{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a), 0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.hover\:ring-2:hover{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.hover\:ring-brand-500:hover{--tw-ring-color:var(--color-brand-500)}.hover\:ring-inset:hover{--tw-ring-inset:inset}}.disabled\:opacity-50:disabled{opacity:.5}@media (min-width:40rem){.sm\:max-w-xs{max-width:var(--container-xs)}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.sm\:grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.sm\:flex-row{flex-direction:row}.sm\:items-center{align-items:center}.sm\:justify-between{justify-content:space-between}.sm\:px-12{padding-inline:calc(var(--spacing) * 12)}.sm\:text-5xl{font-size:var(--text-5xl);line-height:var(--tw-leading,var(--text-5xl--line-height))}.sm\:text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}}@media (min-width:48rem){.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}@media (min-width:64rem){.lg\:col-span-2{grid-column:span 2/span 2}.lg\:col-span-3{grid-column:span 3/span 3}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:grid-cols-5{grid-template-columns:repeat(5,minmax(0,1fr))}.lg\:grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}}.dark\:border-brand-500:where([data-mode=dark],[data-mode=dark] *){border-color:var(--color-brand-500)}.dark\:border-slate-600:where([data-mode=dark],[data-mode=dark] *){border-color:var(--color-slate-600)}.dark\:border-slate-700:where([data-mode=dark],[data-mode=dark] *){border-color:var(--color-slate-700)}.dark\:border-slate-800:where([data-mode=dark],[data-mode=dark] *){border-color:var(--color-slate-800)}.dark\:bg-brand-500\/10:where([data-mode=dark],[data-mode=dark] *){background-color:#3266e41a}@supports (color:color-mix(in lab, red, red)){.dark\:bg-brand-500\/10:where([data-mode=dark],[data-mode=dark] *){background-color:color-mix(in oklab, var(--color-brand-500) 10%, transparent)}}.dark\:bg-brand-600\/20:where([data-mode=dark],[data-mode=dark] *){background-color:#2253cf33}@supports (color:color-mix(in lab, red, red)){.dark\:bg-brand-600\/20:where([data-mode=dark],[data-mode=dark] *){background-color:color-mix(in oklab, var(--color-brand-600) 20%, transparent)}}.dark\:bg-rose-500:where([data-mode=dark],[data-mode=dark] *){background-color:var(--color-rose-500)}.dark\:bg-slate-700:where([data-mode=dark],[data-mode=dark] *){background-color:var(--color-slate-700)}.dark\:bg-slate-800:where([data-mode=dark],[data-mode=dark] *){background-color:var(--color-slate-800)}.dark\:bg-slate-800\/60:where([data-mode=dark],[data-mode=dark] *){background-color:#1d293d99}@supports (color:color-mix(in lab, red, red)){.dark\:bg-slate-800\/60:where([data-mode=dark],[data-mode=dark] *){background-color:color-mix(in oklab, var(--color-slate-800) 60%, transparent)}}.dark\:bg-slate-900:where([data-mode=dark],[data-mode=dark] *){background-color:var(--color-slate-900)}.dark\:bg-slate-900\/90:where([data-mode=dark],[data-mode=dark] *){background-color:#0f172be6}@supports (color:color-mix(in lab, red, red)){.dark\:bg-slate-900\/90:where([data-mode=dark],[data-mode=dark] *){background-color:color-mix(in oklab, var(--color-slate-900) 90%, transparent)}}.dark\:bg-slate-950:where([data-mode=dark],[data-mode=dark] *){background-color:var(--color-slate-950)}.dark\:bg-slate-950\/80:where([data-mode=dark],[data-mode=dark] *){background-color:#020618cc}@supports (color:color-mix(in lab, red, red)){.dark\:bg-slate-950\/80:where([data-mode=dark],[data-mode=dark] *){background-color:color-mix(in oklab, var(--color-slate-950) 80%, transparent)}}.dark\:text-brand-50:where([data-mode=dark],[data-mode=dark] *){color:var(--color-brand-50)}.dark\:text-slate-100:where([data-mode=dark],[data-mode=dark] *){color:var(--color-slate-100)}.dark\:text-slate-200:where([data-mode=dark],[data-mode=dark] *){color:var(--color-slate-200)}.dark\:text-slate-300:where([data-mode=dark],[data-mode=dark] *){color:var(--color-slate-300)}.dark\:text-slate-400:where([data-mode=dark],[data-mode=dark] *){color:var(--color-slate-400)}.dark\:text-slate-600:where([data-mode=dark],[data-mode=dark] *){color:var(--color-slate-600)}.dark\:text-white:where([data-mode=dark],[data-mode=dark] *){color:var(--color-white)}.dark\:ring-slate-700:where([data-mode=dark],[data-mode=dark] *){--tw-ring-color:var(--color-slate-700)}@media (hover:hover){.dark\:hover\:bg-slate-800:where([data-mode=dark],[data-mode=dark] *):hover{background-color:var(--color-slate-800)}.dark\:hover\:text-slate-200:where([data-mode=dark],[data-mode=dark] *):hover{color:var(--color-slate-200)}}}button{cursor:pointer}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-gradient-position{syntax:"*";inherits:false}@property --tw-gradient-from{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-via{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-to{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-stops{syntax:"*";inherits:false}@property --tw-gradient-via-stops{syntax:"*";inherits:false}@property --tw-gradient-from-position{syntax:"<length-percentage>";inherits:false;initial-value:0%}@property --tw-gradient-via-position{syntax:"<length-percentage>";inherits:false;initial-value:50%}@property --tw-gradient-to-position{syntax:"<length-percentage>";inherits:false;initial-value:100%}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}
|