create-strayl-landing 1.0.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.
- package/dist/index.js +63 -0
- package/dist/index.js.map +1 -0
- package/package.json +39 -0
- package/template/components.json +21 -0
- package/template/gitignore +11 -0
- package/template/package.json +38 -0
- package/template/public/favicon.ico +0 -0
- package/template/public/logo-dark.webp +0 -0
- package/template/public/logo-light.webp +0 -0
- package/template/src/components/theme-provider.tsx +14 -0
- package/template/src/components/themed-logo.tsx +44 -0
- package/template/src/components/ui/button.tsx +73 -0
- package/template/src/lib/utils.ts +6 -0
- package/template/src/routeTree.gen.ts +68 -0
- package/template/src/router.tsx +14 -0
- package/template/src/routes/__root.tsx +69 -0
- package/template/src/routes/index.tsx +26 -0
- package/template/src/styles.css +121 -0
- package/template/tsconfig.json +24 -0
- package/template/vite.config.ts +27 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
// src/index.ts
|
|
5
|
+
import { Command } from "commander";
|
|
6
|
+
import pc from "picocolors";
|
|
7
|
+
import { cpSync, existsSync, readFileSync, renameSync, writeFileSync } from "fs";
|
|
8
|
+
import { resolve, dirname, join } from "path";
|
|
9
|
+
import { fileURLToPath } from "url";
|
|
10
|
+
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
var templateDir = resolve(__dirname, "..", "template");
|
|
12
|
+
var program = new Command();
|
|
13
|
+
program.name("create-strayl-landing").description("Scaffold a new Strayl landing page").requiredOption("--name <name>", "Name of the new project").action((opts) => {
|
|
14
|
+
const name = opts.name.trim();
|
|
15
|
+
if (!name) {
|
|
16
|
+
console.error(pc.red("Error: --name must not be empty."));
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
if (!/^[a-zA-Z0-9_@\/-][a-zA-Z0-9_.\-@\/]*$/.test(name)) {
|
|
20
|
+
console.error(
|
|
21
|
+
pc.red(
|
|
22
|
+
"Error: Invalid project name. Use only alphanumeric characters, hyphens, underscores, dots, and slashes."
|
|
23
|
+
)
|
|
24
|
+
);
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
const targetDir = resolve(process.cwd(), name);
|
|
28
|
+
if (existsSync(targetDir)) {
|
|
29
|
+
console.error(
|
|
30
|
+
pc.red(`Error: Directory "${name}" already exists.`)
|
|
31
|
+
);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
if (!existsSync(templateDir)) {
|
|
35
|
+
console.error(
|
|
36
|
+
pc.red("Error: Template directory not found. The package may be corrupted.")
|
|
37
|
+
);
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
console.log(pc.cyan(`Creating project "${name}"...`));
|
|
41
|
+
cpSync(templateDir, targetDir, { recursive: true });
|
|
42
|
+
const gitignorePath = join(targetDir, "gitignore");
|
|
43
|
+
if (existsSync(gitignorePath)) {
|
|
44
|
+
renameSync(gitignorePath, join(targetDir, ".gitignore"));
|
|
45
|
+
}
|
|
46
|
+
const pkgJsonPath = join(targetDir, "package.json");
|
|
47
|
+
if (existsSync(pkgJsonPath)) {
|
|
48
|
+
const pkgJson = JSON.parse(readFileSync(pkgJsonPath, "utf-8"));
|
|
49
|
+
pkgJson.name = name;
|
|
50
|
+
delete pkgJson.private;
|
|
51
|
+
writeFileSync(pkgJsonPath, JSON.stringify(pkgJson, null, 2) + "\n");
|
|
52
|
+
}
|
|
53
|
+
console.log(pc.green(`
|
|
54
|
+
Project "${name}" created successfully!
|
|
55
|
+
`));
|
|
56
|
+
console.log("Next steps:");
|
|
57
|
+
console.log(pc.cyan(` cd ${name}`));
|
|
58
|
+
console.log(pc.cyan(" npm install"));
|
|
59
|
+
console.log(pc.cyan(" npm run dev"));
|
|
60
|
+
console.log();
|
|
61
|
+
});
|
|
62
|
+
program.parse();
|
|
63
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { Command } from \"commander\";\nimport pc from \"picocolors\";\nimport { cpSync, existsSync, readFileSync, renameSync, writeFileSync } from \"node:fs\";\nimport { resolve, dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst templateDir = resolve(__dirname, \"..\", \"template\");\n\nconst program = new Command();\n\nprogram\n .name(\"create-strayl-landing\")\n .description(\"Scaffold a new Strayl landing page\")\n .requiredOption(\"--name <name>\", \"Name of the new project\")\n .action((opts: { name: string }) => {\n const name = opts.name.trim();\n\n if (!name) {\n console.error(pc.red(\"Error: --name must not be empty.\"));\n process.exit(1);\n }\n\n if (!/^[a-zA-Z0-9_@\\/-][a-zA-Z0-9_.\\-@\\/]*$/.test(name)) {\n console.error(\n pc.red(\n \"Error: Invalid project name. Use only alphanumeric characters, hyphens, underscores, dots, and slashes.\"\n )\n );\n process.exit(1);\n }\n\n const targetDir = resolve(process.cwd(), name);\n\n if (existsSync(targetDir)) {\n console.error(\n pc.red(`Error: Directory \"${name}\" already exists.`)\n );\n process.exit(1);\n }\n\n if (!existsSync(templateDir)) {\n console.error(\n pc.red(\"Error: Template directory not found. The package may be corrupted.\")\n );\n process.exit(1);\n }\n\n console.log(pc.cyan(`Creating project \"${name}\"...`));\n\n cpSync(templateDir, targetDir, { recursive: true });\n\n // npm publish strips .gitignore, so we ship it as \"gitignore\" and rename here\n const gitignorePath = join(targetDir, \"gitignore\");\n if (existsSync(gitignorePath)) {\n renameSync(gitignorePath, join(targetDir, \".gitignore\"));\n }\n\n const pkgJsonPath = join(targetDir, \"package.json\");\n if (existsSync(pkgJsonPath)) {\n const pkgJson = JSON.parse(readFileSync(pkgJsonPath, \"utf-8\"));\n pkgJson.name = name;\n delete pkgJson.private;\n writeFileSync(pkgJsonPath, JSON.stringify(pkgJson, null, 2) + \"\\n\");\n }\n\n console.log(pc.green(`\\nProject \"${name}\" created successfully!\\n`));\n console.log(\"Next steps:\");\n console.log(pc.cyan(` cd ${name}`));\n console.log(pc.cyan(\" npm install\"));\n console.log(pc.cyan(\" npm run dev\"));\n console.log();\n });\n\nprogram.parse();\n"],"mappings":";;;;AAAA,SAAS,eAAe;AACxB,OAAO,QAAQ;AACf,SAAS,QAAQ,YAAY,cAAc,YAAY,qBAAqB;AAC5E,SAAS,SAAS,SAAS,YAAY;AACvC,SAAS,qBAAqB;AAE9B,IAAM,YAAY,QAAQ,cAAc,YAAY,GAAG,CAAC;AACxD,IAAM,cAAc,QAAQ,WAAW,MAAM,UAAU;AAEvD,IAAM,UAAU,IAAI,QAAQ;AAE5B,QACG,KAAK,uBAAuB,EAC5B,YAAY,oCAAoC,EAChD,eAAe,iBAAiB,yBAAyB,EACzD,OAAO,CAAC,SAA2B;AAClC,QAAM,OAAO,KAAK,KAAK,KAAK;AAE5B,MAAI,CAAC,MAAM;AACT,YAAQ,MAAM,GAAG,IAAI,kCAAkC,CAAC;AACxD,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,MAAI,CAAC,wCAAwC,KAAK,IAAI,GAAG;AACvD,YAAQ;AAAA,MACN,GAAG;AAAA,QACD;AAAA,MACF;AAAA,IACF;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,YAAY,QAAQ,QAAQ,IAAI,GAAG,IAAI;AAE7C,MAAI,WAAW,SAAS,GAAG;AACzB,YAAQ;AAAA,MACN,GAAG,IAAI,qBAAqB,IAAI,mBAAmB;AAAA,IACrD;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,MAAI,CAAC,WAAW,WAAW,GAAG;AAC5B,YAAQ;AAAA,MACN,GAAG,IAAI,oEAAoE;AAAA,IAC7E;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,UAAQ,IAAI,GAAG,KAAK,qBAAqB,IAAI,MAAM,CAAC;AAEpD,SAAO,aAAa,WAAW,EAAE,WAAW,KAAK,CAAC;AAGlD,QAAM,gBAAgB,KAAK,WAAW,WAAW;AACjD,MAAI,WAAW,aAAa,GAAG;AAC7B,eAAW,eAAe,KAAK,WAAW,YAAY,CAAC;AAAA,EACzD;AAEA,QAAM,cAAc,KAAK,WAAW,cAAc;AAClD,MAAI,WAAW,WAAW,GAAG;AAC3B,UAAM,UAAU,KAAK,MAAM,aAAa,aAAa,OAAO,CAAC;AAC7D,YAAQ,OAAO;AACf,WAAO,QAAQ;AACf,kBAAc,aAAa,KAAK,UAAU,SAAS,MAAM,CAAC,IAAI,IAAI;AAAA,EACpE;AAEA,UAAQ,IAAI,GAAG,MAAM;AAAA,WAAc,IAAI;AAAA,CAA2B,CAAC;AACnE,UAAQ,IAAI,aAAa;AACzB,UAAQ,IAAI,GAAG,KAAK,QAAQ,IAAI,EAAE,CAAC;AACnC,UAAQ,IAAI,GAAG,KAAK,eAAe,CAAC;AACpC,UAAQ,IAAI,GAAG,KAAK,eAAe,CAAC;AACpC,UAAQ,IAAI;AACd,CAAC;AAEH,QAAQ,MAAM;","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-strayl-landing",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Scaffold a new Strayl landing page",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"create-strayl-landing": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"template"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsup",
|
|
15
|
+
"dev": "tsup --watch",
|
|
16
|
+
"typecheck": "tsc --noEmit",
|
|
17
|
+
"prepublishOnly": "npm run build"
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"commander": "^12.1.0",
|
|
21
|
+
"picocolors": "^1.1.1"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@types/node": "^22.10.0",
|
|
25
|
+
"tsup": "^8.3.5",
|
|
26
|
+
"typescript": "^5.7.2"
|
|
27
|
+
},
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=18"
|
|
30
|
+
},
|
|
31
|
+
"keywords": [
|
|
32
|
+
"strayl",
|
|
33
|
+
"create",
|
|
34
|
+
"landing",
|
|
35
|
+
"scaffold"
|
|
36
|
+
],
|
|
37
|
+
"author": "Strayl Inc",
|
|
38
|
+
"license": "MIT"
|
|
39
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema.json",
|
|
3
|
+
"style": "new-york",
|
|
4
|
+
"rsc": false,
|
|
5
|
+
"tsx": true,
|
|
6
|
+
"tailwind": {
|
|
7
|
+
"config": "",
|
|
8
|
+
"css": "src/styles.css",
|
|
9
|
+
"baseColor": "neutral",
|
|
10
|
+
"cssVariables": true,
|
|
11
|
+
"prefix": ""
|
|
12
|
+
},
|
|
13
|
+
"iconLibrary": "lucide",
|
|
14
|
+
"aliases": {
|
|
15
|
+
"components": "@/components",
|
|
16
|
+
"utils": "@/lib/utils",
|
|
17
|
+
"ui": "@/components/ui",
|
|
18
|
+
"lib": "@/lib",
|
|
19
|
+
"hooks": "@/hooks"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "strayl-landing",
|
|
3
|
+
"private": true,
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "vite dev --port 3000",
|
|
7
|
+
"build": "vite build",
|
|
8
|
+
"preview": "vite preview"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@base-ui/react": "^1.1.0",
|
|
12
|
+
"@fontsource-variable/bitcount-prop-single": "^5.2.2",
|
|
13
|
+
"@radix-ui/react-slot": "^1.1.1",
|
|
14
|
+
"@tailwindcss/vite": "^4.0.6",
|
|
15
|
+
"@tanstack/react-router": "^1.132.0",
|
|
16
|
+
"@tanstack/react-start": "^1.132.0",
|
|
17
|
+
"@tanstack/router-plugin": "^1.132.0",
|
|
18
|
+
"class-variance-authority": "^0.7.1",
|
|
19
|
+
"clsx": "^2.1.1",
|
|
20
|
+
"lucide-react": "^0.561.0",
|
|
21
|
+
"next-themes": "^0.4.6",
|
|
22
|
+
"nitro": "npm:nitro-nightly@latest",
|
|
23
|
+
"react": "^19.2.0",
|
|
24
|
+
"react-dom": "^19.2.0",
|
|
25
|
+
"tailwind-merge": "^2.6.0",
|
|
26
|
+
"tailwindcss": "^4.0.6",
|
|
27
|
+
"tw-animate-css": "^1.4.0",
|
|
28
|
+
"vite-tsconfig-paths": "^6.0.2"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@types/node": "^22.10.2",
|
|
32
|
+
"@types/react": "^19.2.0",
|
|
33
|
+
"@types/react-dom": "^19.2.0",
|
|
34
|
+
"@vitejs/plugin-react": "^5.0.4",
|
|
35
|
+
"typescript": "^5.7.2",
|
|
36
|
+
"vite": "^7.1.7"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ThemeProvider as NextThemesProvider } from "next-themes"
|
|
2
|
+
|
|
3
|
+
export function ThemeProvider({ children }: { children: React.ReactNode }) {
|
|
4
|
+
return (
|
|
5
|
+
<NextThemesProvider
|
|
6
|
+
attribute="class"
|
|
7
|
+
defaultTheme="system"
|
|
8
|
+
enableSystem
|
|
9
|
+
disableTransitionOnChange
|
|
10
|
+
>
|
|
11
|
+
{children}
|
|
12
|
+
</NextThemesProvider>
|
|
13
|
+
)
|
|
14
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { useTheme } from "next-themes"
|
|
2
|
+
import { useEffect, useState } from "react"
|
|
3
|
+
|
|
4
|
+
interface ThemedLogoProps {
|
|
5
|
+
width?: number
|
|
6
|
+
height?: number
|
|
7
|
+
className?: string
|
|
8
|
+
alt?: string
|
|
9
|
+
inverted?: boolean
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function ThemedLogo({
|
|
13
|
+
width = 24,
|
|
14
|
+
height = 24,
|
|
15
|
+
className = "",
|
|
16
|
+
alt = "Strayl Logo",
|
|
17
|
+
inverted = false,
|
|
18
|
+
}: ThemedLogoProps) {
|
|
19
|
+
const { resolvedTheme } = useTheme()
|
|
20
|
+
const [mounted, setMounted] = useState(false)
|
|
21
|
+
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
setMounted(true)
|
|
24
|
+
}, [])
|
|
25
|
+
|
|
26
|
+
if (!mounted) {
|
|
27
|
+
return <div style={{ width, height }} className={className} />
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const isDark = resolvedTheme === "dark"
|
|
31
|
+
const logoSrc = inverted
|
|
32
|
+
? (isDark ? "/logo-dark.webp" : "/logo-light.webp")
|
|
33
|
+
: (isDark ? "/logo-light.webp" : "/logo-dark.webp")
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<img
|
|
37
|
+
src={logoSrc}
|
|
38
|
+
alt={alt}
|
|
39
|
+
width={width}
|
|
40
|
+
height={height}
|
|
41
|
+
className={className}
|
|
42
|
+
/>
|
|
43
|
+
)
|
|
44
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { mergeProps } from "@base-ui/react/merge-props";
|
|
4
|
+
import { useRender } from "@base-ui/react/use-render";
|
|
5
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
6
|
+
import type * as React from "react";
|
|
7
|
+
|
|
8
|
+
import { cn } from "@/lib/utils";
|
|
9
|
+
|
|
10
|
+
const buttonVariants = cva(
|
|
11
|
+
"[&_svg]:-mx-0.5 relative inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-lg border font-medium text-base outline-none transition-shadow before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] pointer-coarse:after:absolute pointer-coarse:after:size-full pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-64 sm:text-sm [&_svg:not([class*='opacity-'])]:opacity-80 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
|
12
|
+
{
|
|
13
|
+
defaultVariants: {
|
|
14
|
+
size: "default",
|
|
15
|
+
variant: "default",
|
|
16
|
+
},
|
|
17
|
+
variants: {
|
|
18
|
+
size: {
|
|
19
|
+
default: "h-9 px-[calc(--spacing(3)-1px)] sm:h-8",
|
|
20
|
+
icon: "size-9 sm:size-8",
|
|
21
|
+
"icon-lg": "size-10 sm:size-9",
|
|
22
|
+
"icon-sm": "size-8 sm:size-7",
|
|
23
|
+
"icon-xl":
|
|
24
|
+
"size-11 sm:size-10 [&_svg:not([class*='size-'])]:size-5 sm:[&_svg:not([class*='size-'])]:size-4.5",
|
|
25
|
+
"icon-xs":
|
|
26
|
+
"size-7 rounded-md before:rounded-[calc(var(--radius-md)-1px)] sm:size-6 not-in-data-[slot=input-group]:[&_svg:not([class*='size-'])]:size-4 sm:not-in-data-[slot=input-group]:[&_svg:not([class*='size-'])]:size-3.5",
|
|
27
|
+
lg: "h-10 px-[calc(--spacing(3.5)-1px)] sm:h-9",
|
|
28
|
+
sm: "h-8 gap-1.5 px-[calc(--spacing(2.5)-1px)] sm:h-7",
|
|
29
|
+
xl: "h-11 px-[calc(--spacing(4)-1px)] text-lg sm:h-10 sm:text-base [&_svg:not([class*='size-'])]:size-5 sm:[&_svg:not([class*='size-'])]:size-4.5",
|
|
30
|
+
xs: "h-7 gap-1 rounded-md px-[calc(--spacing(2)-1px)] text-sm before:rounded-[calc(var(--radius-md)-1px)] sm:h-6 sm:text-xs [&_svg:not([class*='size-'])]:size-4 sm:[&_svg:not([class*='size-'])]:size-3.5",
|
|
31
|
+
},
|
|
32
|
+
variant: {
|
|
33
|
+
default:
|
|
34
|
+
"not-disabled:inset-shadow-[0_1px_--theme(--color-white/16%)] border-primary bg-primary text-primary-foreground shadow-primary/24 shadow-xs [:active,[data-pressed]]:inset-shadow-[0_1px_--theme(--color-black/8%)] [:disabled,:active,[data-pressed]]:shadow-none [:hover,[data-pressed]]:bg-primary/90",
|
|
35
|
+
destructive:
|
|
36
|
+
"not-disabled:inset-shadow-[0_1px_--theme(--color-white/16%)] border-destructive bg-destructive text-white shadow-destructive/24 shadow-xs [:active,[data-pressed]]:inset-shadow-[0_1px_--theme(--color-black/8%)] [:disabled,:active,[data-pressed]]:shadow-none [:hover,[data-pressed]]:bg-destructive/90",
|
|
37
|
+
"destructive-outline":
|
|
38
|
+
"border-input bg-transparent not-dark:bg-clip-padding text-destructive-foreground shadow-xs/5 not-disabled:not-active:not-data-pressed:before:shadow-[0_1px_--theme(--color-black/6%)] dark:bg-input/32 dark:not-disabled:before:shadow-[0_-1px_--theme(--color-white/2%)] dark:not-disabled:not-active:not-data-pressed:before:shadow-[0_-1px_--theme(--color-white/6%)] [:disabled,:active,[data-pressed]]:shadow-none [:hover,[data-pressed]]:border-destructive/32 [:hover,[data-pressed]]:bg-destructive/4",
|
|
39
|
+
ghost:
|
|
40
|
+
"border-transparent text-foreground data-pressed:bg-accent [:hover,[data-pressed]]:bg-accent",
|
|
41
|
+
link: "border-transparent underline-offset-4 [:hover,[data-pressed]]:underline",
|
|
42
|
+
outline:
|
|
43
|
+
"border-input bg-background not-dark:bg-clip-padding text-foreground shadow-xs/5 not-disabled:not-active:not-data-pressed:before:shadow-[0_1px_--theme(--color-black/6%)] dark:bg-input/32 dark:not-disabled:before:shadow-[0_-1px_--theme(--color-white/2%)] dark:not-disabled:not-active:not-data-pressed:before:shadow-[0_-1px_--theme(--color-white/6%)] [:disabled,:active,[data-pressed]]:shadow-none [:hover,[data-pressed]]:bg-accent/50 dark:[:hover,[data-pressed]]:bg-input/64",
|
|
44
|
+
secondary:
|
|
45
|
+
"border-transparent bg-secondary text-secondary-foreground [:active,[data-pressed]]:bg-secondary/80 [:hover,[data-pressed]]:bg-secondary/90",
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
interface ButtonProps extends useRender.ComponentProps<"button"> {
|
|
52
|
+
variant?: VariantProps<typeof buttonVariants>["variant"];
|
|
53
|
+
size?: VariantProps<typeof buttonVariants>["size"];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function Button({ className, variant, size, render, ...props }: ButtonProps) {
|
|
57
|
+
const typeValue: React.ButtonHTMLAttributes<HTMLButtonElement>["type"] =
|
|
58
|
+
render ? undefined : "button";
|
|
59
|
+
|
|
60
|
+
const defaultProps = {
|
|
61
|
+
className: cn(buttonVariants({ className, size, variant })),
|
|
62
|
+
"data-slot": "button",
|
|
63
|
+
type: typeValue,
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
return useRender({
|
|
67
|
+
defaultTagName: "button",
|
|
68
|
+
props: mergeProps<"button">(defaultProps, props),
|
|
69
|
+
render,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export { Button, buttonVariants };
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
|
|
3
|
+
// @ts-nocheck
|
|
4
|
+
|
|
5
|
+
// noinspection JSUnusedGlobalSymbols
|
|
6
|
+
|
|
7
|
+
// This file was automatically generated by TanStack Router.
|
|
8
|
+
// You should NOT make any changes in this file as it will be overwritten.
|
|
9
|
+
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
|
|
10
|
+
|
|
11
|
+
import { Route as rootRouteImport } from './routes/__root'
|
|
12
|
+
import { Route as IndexRouteImport } from './routes/index'
|
|
13
|
+
|
|
14
|
+
const IndexRoute = IndexRouteImport.update({
|
|
15
|
+
id: '/',
|
|
16
|
+
path: '/',
|
|
17
|
+
getParentRoute: () => rootRouteImport,
|
|
18
|
+
} as any)
|
|
19
|
+
|
|
20
|
+
export interface FileRoutesByFullPath {
|
|
21
|
+
'/': typeof IndexRoute
|
|
22
|
+
}
|
|
23
|
+
export interface FileRoutesByTo {
|
|
24
|
+
'/': typeof IndexRoute
|
|
25
|
+
}
|
|
26
|
+
export interface FileRoutesById {
|
|
27
|
+
__root__: typeof rootRouteImport
|
|
28
|
+
'/': typeof IndexRoute
|
|
29
|
+
}
|
|
30
|
+
export interface FileRouteTypes {
|
|
31
|
+
fileRoutesByFullPath: FileRoutesByFullPath
|
|
32
|
+
fullPaths: '/'
|
|
33
|
+
fileRoutesByTo: FileRoutesByTo
|
|
34
|
+
to: '/'
|
|
35
|
+
id: '__root__' | '/'
|
|
36
|
+
fileRoutesById: FileRoutesById
|
|
37
|
+
}
|
|
38
|
+
export interface RootRouteChildren {
|
|
39
|
+
IndexRoute: typeof IndexRoute
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
declare module '@tanstack/react-router' {
|
|
43
|
+
interface FileRoutesByPath {
|
|
44
|
+
'/': {
|
|
45
|
+
id: '/'
|
|
46
|
+
path: '/'
|
|
47
|
+
fullPath: '/'
|
|
48
|
+
preLoaderRoute: typeof IndexRouteImport
|
|
49
|
+
parentRoute: typeof rootRouteImport
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const rootRouteChildren: RootRouteChildren = {
|
|
55
|
+
IndexRoute: IndexRoute,
|
|
56
|
+
}
|
|
57
|
+
export const routeTree = rootRouteImport
|
|
58
|
+
._addFileChildren(rootRouteChildren)
|
|
59
|
+
._addFileTypes<FileRouteTypes>()
|
|
60
|
+
|
|
61
|
+
import type { getRouter } from './router.tsx'
|
|
62
|
+
import type { createStart } from '@tanstack/react-start'
|
|
63
|
+
declare module '@tanstack/react-start' {
|
|
64
|
+
interface Register {
|
|
65
|
+
ssr: true
|
|
66
|
+
router: Awaited<ReturnType<typeof getRouter>>
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { createRouter } from '@tanstack/react-router'
|
|
2
|
+
|
|
3
|
+
import { routeTree } from './routeTree.gen'
|
|
4
|
+
|
|
5
|
+
export const getRouter = () => {
|
|
6
|
+
const router = createRouter({
|
|
7
|
+
routeTree,
|
|
8
|
+
context: {},
|
|
9
|
+
scrollRestoration: true,
|
|
10
|
+
defaultPreloadStaleTime: 0,
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
return router
|
|
14
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { HeadContent, Scripts, createRootRoute } from '@tanstack/react-router'
|
|
2
|
+
|
|
3
|
+
import '@fontsource-variable/bitcount-prop-single'
|
|
4
|
+
|
|
5
|
+
import { ThemeProvider } from '../components/theme-provider'
|
|
6
|
+
|
|
7
|
+
import appCss from '../styles.css?url'
|
|
8
|
+
|
|
9
|
+
export const Route = createRootRoute({
|
|
10
|
+
head: () => ({
|
|
11
|
+
meta: [
|
|
12
|
+
{
|
|
13
|
+
charSet: 'utf-8',
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
name: 'viewport',
|
|
17
|
+
content: 'width=device-width, initial-scale=1',
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
title: 'Strayl',
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
name: 'description',
|
|
24
|
+
content: 'Build end to end',
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
links: [
|
|
28
|
+
{
|
|
29
|
+
rel: 'stylesheet',
|
|
30
|
+
href: appCss,
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
rel: 'icon',
|
|
34
|
+
href: '/favicon.ico',
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
rel: 'preconnect',
|
|
38
|
+
href: 'https://fonts.googleapis.com',
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
rel: 'preconnect',
|
|
42
|
+
href: 'https://fonts.gstatic.com',
|
|
43
|
+
crossOrigin: 'anonymous',
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
rel: 'stylesheet',
|
|
47
|
+
href: 'https://fonts.googleapis.com/css2?family=Geist:wght@100..900&family=Geist+Mono:wght@100..900&display=swap',
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
}),
|
|
51
|
+
|
|
52
|
+
shellComponent: RootDocument,
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
function RootDocument({ children }: { children: React.ReactNode }) {
|
|
56
|
+
return (
|
|
57
|
+
<html lang="en" suppressHydrationWarning>
|
|
58
|
+
<head>
|
|
59
|
+
<HeadContent />
|
|
60
|
+
</head>
|
|
61
|
+
<body>
|
|
62
|
+
<ThemeProvider>
|
|
63
|
+
{children}
|
|
64
|
+
</ThemeProvider>
|
|
65
|
+
<Scripts />
|
|
66
|
+
</body>
|
|
67
|
+
</html>
|
|
68
|
+
)
|
|
69
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { createFileRoute } from '@tanstack/react-router'
|
|
2
|
+
import { Button } from '@/components/ui/button'
|
|
3
|
+
import { ThemedLogo } from '@/components/themed-logo'
|
|
4
|
+
|
|
5
|
+
export const Route = createFileRoute('/')({ component: App })
|
|
6
|
+
|
|
7
|
+
function App() {
|
|
8
|
+
return (
|
|
9
|
+
<div className="min-h-screen bg-background flex items-center justify-center px-6">
|
|
10
|
+
<div className="text-center max-w-2xl">
|
|
11
|
+
<h1 className="text-6xl md:text-8xl font-normal text-foreground font-[family-name:var(--font-bitcount)]">
|
|
12
|
+
STRAYL
|
|
13
|
+
</h1>
|
|
14
|
+
<p className="text-lg md:text-xl text-muted-foreground mt-4">
|
|
15
|
+
Landing page template
|
|
16
|
+
</p>
|
|
17
|
+
<div className="mt-8">
|
|
18
|
+
<Button size="lg" render={<a href="https://strayl.dev" target="_blank" rel="noopener noreferrer" />}>
|
|
19
|
+
<ThemedLogo width={20} height={20} inverted />
|
|
20
|
+
Get Started
|
|
21
|
+
</Button>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
2
|
+
@import "tw-animate-css";
|
|
3
|
+
|
|
4
|
+
@custom-variant dark (&:is(.dark *));
|
|
5
|
+
@variant dark (&:where(.dark, .dark *));
|
|
6
|
+
|
|
7
|
+
@theme inline {
|
|
8
|
+
/* Color mappings */
|
|
9
|
+
--color-background: var(--background);
|
|
10
|
+
--color-foreground: var(--foreground);
|
|
11
|
+
--color-card: var(--card);
|
|
12
|
+
--color-card-foreground: var(--card-foreground);
|
|
13
|
+
--color-primary: var(--primary);
|
|
14
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
15
|
+
--color-secondary: var(--secondary);
|
|
16
|
+
--color-secondary-foreground: var(--secondary-foreground);
|
|
17
|
+
--color-muted: var(--muted);
|
|
18
|
+
--color-muted-foreground: var(--muted-foreground);
|
|
19
|
+
--color-accent: var(--accent);
|
|
20
|
+
--color-accent-foreground: var(--accent-foreground);
|
|
21
|
+
--color-destructive: var(--destructive);
|
|
22
|
+
--color-destructive-foreground: var(--destructive-foreground);
|
|
23
|
+
--color-border: var(--border);
|
|
24
|
+
--color-input: var(--input);
|
|
25
|
+
--color-ring: var(--ring);
|
|
26
|
+
--color-popover: var(--popover);
|
|
27
|
+
--color-popover-foreground: var(--popover-foreground);
|
|
28
|
+
|
|
29
|
+
/* Semantic colors */
|
|
30
|
+
--color-info: var(--info);
|
|
31
|
+
--color-info-foreground: var(--info-foreground);
|
|
32
|
+
--color-success: var(--success);
|
|
33
|
+
--color-success-foreground: var(--success-foreground);
|
|
34
|
+
--color-warning: var(--warning);
|
|
35
|
+
--color-warning-foreground: var(--warning-foreground);
|
|
36
|
+
|
|
37
|
+
/* Fonts — Geist via Google Fonts, Bitcount via fontsource */
|
|
38
|
+
--font-sans: "Geist", ui-sans-serif, system-ui, sans-serif;
|
|
39
|
+
--font-mono: "Geist Mono", ui-monospace, monospace;
|
|
40
|
+
--font-bitcount: "Bitcount Prop Single Variable", sans-serif;
|
|
41
|
+
|
|
42
|
+
/* Radius */
|
|
43
|
+
--radius-sm: calc(var(--radius) - 4px);
|
|
44
|
+
--radius-md: calc(var(--radius) - 2px);
|
|
45
|
+
--radius-lg: var(--radius);
|
|
46
|
+
--radius-xl: calc(var(--radius) + 4px);
|
|
47
|
+
--animate-skeleton: skeleton 2s -1s infinite linear;
|
|
48
|
+
@keyframes skeleton {
|
|
49
|
+
to {
|
|
50
|
+
background-position: -200% 0;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* Light theme (default) */
|
|
56
|
+
:root {
|
|
57
|
+
--radius: 0.625rem;
|
|
58
|
+
--background: #f5f5f5;
|
|
59
|
+
--foreground: #262626;
|
|
60
|
+
--card: #ffffff;
|
|
61
|
+
--card-foreground: #262626;
|
|
62
|
+
--popover: #ffffff;
|
|
63
|
+
--popover-foreground: #262626;
|
|
64
|
+
--primary: #262626;
|
|
65
|
+
--primary-foreground: #fafafa;
|
|
66
|
+
--secondary: rgba(0, 0, 0, 0.04);
|
|
67
|
+
--secondary-foreground: #262626;
|
|
68
|
+
--muted: rgba(0, 0, 0, 0.04);
|
|
69
|
+
--muted-foreground: #737373;
|
|
70
|
+
--accent: rgba(0, 0, 0, 0.04);
|
|
71
|
+
--accent-foreground: #262626;
|
|
72
|
+
--destructive: #ef4444;
|
|
73
|
+
--destructive-foreground: #b91c1c;
|
|
74
|
+
--border: rgba(0, 0, 0, 0.08);
|
|
75
|
+
--input: rgba(0, 0, 0, 0.1);
|
|
76
|
+
--ring: #a3a3a3;
|
|
77
|
+
--info: #3b82f6;
|
|
78
|
+
--info-foreground: #1d4ed8;
|
|
79
|
+
--success: #10b981;
|
|
80
|
+
--success-foreground: #047857;
|
|
81
|
+
--warning: #f59e0b;
|
|
82
|
+
--warning-foreground: #b45309;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/* Dark theme */
|
|
86
|
+
.dark {
|
|
87
|
+
--background: #171717;
|
|
88
|
+
--foreground: #f5f5f5;
|
|
89
|
+
--card: #262626;
|
|
90
|
+
--card-foreground: #f5f5f5;
|
|
91
|
+
--popover: #262626;
|
|
92
|
+
--popover-foreground: #f5f5f5;
|
|
93
|
+
--primary: #f5f5f5;
|
|
94
|
+
--primary-foreground: #262626;
|
|
95
|
+
--secondary: rgba(255, 255, 255, 0.04);
|
|
96
|
+
--secondary-foreground: #f5f5f5;
|
|
97
|
+
--muted: rgba(255, 255, 255, 0.04);
|
|
98
|
+
--muted-foreground: #a3a3a3;
|
|
99
|
+
--accent: rgba(255, 255, 255, 0.04);
|
|
100
|
+
--accent-foreground: #f5f5f5;
|
|
101
|
+
--destructive: #f87171;
|
|
102
|
+
--destructive-foreground: #fca5a5;
|
|
103
|
+
--border: rgba(255, 255, 255, 0.06);
|
|
104
|
+
--input: rgba(255, 255, 255, 0.08);
|
|
105
|
+
--ring: #737373;
|
|
106
|
+
--info: #3b82f6;
|
|
107
|
+
--info-foreground: #60a5fa;
|
|
108
|
+
--success: #10b981;
|
|
109
|
+
--success-foreground: #34d399;
|
|
110
|
+
--warning: #f59e0b;
|
|
111
|
+
--warning-foreground: #fbbf24;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
@layer base {
|
|
115
|
+
* {
|
|
116
|
+
@apply border-border outline-ring/50;
|
|
117
|
+
}
|
|
118
|
+
body {
|
|
119
|
+
@apply bg-background text-foreground font-sans antialiased;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"include": ["**/*.ts", "**/*.tsx"],
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"target": "ES2022",
|
|
5
|
+
"jsx": "react-jsx",
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
8
|
+
"types": ["vite/client"],
|
|
9
|
+
"moduleResolution": "bundler",
|
|
10
|
+
"allowImportingTsExtensions": true,
|
|
11
|
+
"verbatimModuleSyntax": false,
|
|
12
|
+
"noEmit": true,
|
|
13
|
+
"skipLibCheck": true,
|
|
14
|
+
"strict": true,
|
|
15
|
+
"noUnusedLocals": true,
|
|
16
|
+
"noUnusedParameters": true,
|
|
17
|
+
"noFallthroughCasesInSwitch": true,
|
|
18
|
+
"noUncheckedSideEffectImports": true,
|
|
19
|
+
"baseUrl": ".",
|
|
20
|
+
"paths": {
|
|
21
|
+
"@/*": ["./src/*"]
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { defineConfig } from 'vite'
|
|
2
|
+
import { tanstackStart } from '@tanstack/react-start/plugin/vite'
|
|
3
|
+
import viteReact from '@vitejs/plugin-react'
|
|
4
|
+
import viteTsConfigPaths from 'vite-tsconfig-paths'
|
|
5
|
+
import { fileURLToPath, URL } from 'url'
|
|
6
|
+
|
|
7
|
+
import tailwindcss from '@tailwindcss/vite'
|
|
8
|
+
import { nitro } from 'nitro/vite'
|
|
9
|
+
|
|
10
|
+
const config = defineConfig({
|
|
11
|
+
resolve: {
|
|
12
|
+
alias: {
|
|
13
|
+
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
plugins: [
|
|
17
|
+
nitro(),
|
|
18
|
+
viteTsConfigPaths({
|
|
19
|
+
projects: ['./tsconfig.json'],
|
|
20
|
+
}),
|
|
21
|
+
tailwindcss(),
|
|
22
|
+
tanstackStart(),
|
|
23
|
+
viteReact(),
|
|
24
|
+
],
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
export default config
|