-
-
- Deploy now
-
-
- Read our docs
-
+
+
+
+
+
+ Zero-Knowledge Encrypted Code Sharing
+
+
+ Share code snippets securely with client-side encryption. Your code,
+ your privacy.
+
+
+
+ Current theme:{" "}
+
+ system/light/dark
+
+
+
+ Click the theme toggle button in the header to switch between
+ light and dark modes.
+
+
-
);
}
diff --git a/components.json b/components.json
new file mode 100644
index 0000000..4ee62ee
--- /dev/null
+++ b/components.json
@@ -0,0 +1,21 @@
+{
+ "$schema": "https://ui.shadcn.com/schema.json",
+ "style": "new-york",
+ "rsc": true,
+ "tsx": true,
+ "tailwind": {
+ "config": "",
+ "css": "app/globals.css",
+ "baseColor": "neutral",
+ "cssVariables": true,
+ "prefix": ""
+ },
+ "aliases": {
+ "components": "@/components",
+ "utils": "@/lib/utils",
+ "ui": "@/components/ui",
+ "lib": "@/lib",
+ "hooks": "@/hooks"
+ },
+ "iconLibrary": "lucide"
+}
diff --git a/components/theme-provider.tsx b/components/theme-provider.tsx
new file mode 100644
index 0000000..189a2b1
--- /dev/null
+++ b/components/theme-provider.tsx
@@ -0,0 +1,11 @@
+"use client";
+
+import * as React from "react";
+import { ThemeProvider as NextThemesProvider } from "next-themes";
+
+export function ThemeProvider({
+ children,
+ ...props
+}: React.ComponentProps
) {
+ return {children};
+}
diff --git a/components/theme-toggle.tsx b/components/theme-toggle.tsx
new file mode 100644
index 0000000..7ed8189
--- /dev/null
+++ b/components/theme-toggle.tsx
@@ -0,0 +1,23 @@
+"use client";
+
+import * as React from "react";
+import { Moon, Sun } from "lucide-react";
+import { useTheme } from "next-themes";
+
+import { Button } from "@/components/ui/button";
+
+export function ThemeToggle() {
+ const { setTheme, theme } = useTheme();
+
+ return (
+
+ );
+}
diff --git a/components/ui/button.tsx b/components/ui/button.tsx
new file mode 100644
index 0000000..66ab90e
--- /dev/null
+++ b/components/ui/button.tsx
@@ -0,0 +1,59 @@
+import * as React from "react";
+import { Slot } from "@radix-ui/react-slot";
+import { cva, type VariantProps } from "class-variance-authority";
+
+import { cn } from "@/lib/utils";
+
+const buttonVariants = cva(
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
+ {
+ variants: {
+ variant: {
+ default:
+ "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
+ destructive:
+ "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
+ outline:
+ "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
+ secondary:
+ "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
+ ghost:
+ "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
+ link: "text-primary underline-offset-4 hover:underline",
+ },
+ size: {
+ default: "h-9 px-4 py-2 has-[>svg]:px-3",
+ sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
+ lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
+ icon: "size-9",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "default",
+ },
+ }
+);
+
+function Button({
+ className,
+ variant,
+ size,
+ asChild = false,
+ ...props
+}: React.ComponentProps<"button"> &
+ VariantProps & {
+ asChild?: boolean;
+ }) {
+ const Comp = asChild ? Slot : "button";
+
+ return (
+
+ );
+}
+
+export { Button, buttonVariants };
diff --git a/docs/TODO.md b/docs/TODO.md
index 588f3e5..05184b3 100644
--- a/docs/TODO.md
+++ b/docs/TODO.md
@@ -21,19 +21,19 @@ This document tracks the implementation progress of GhostPaste. Check off tasks
- [x] Install @cloudflare/next-on-pages for Workers deployment - [#6](https://github.com/nullcoder/ghostpaste/issues/6)
- [x] Install wrangler for local development - [#6](https://github.com/nullcoder/ghostpaste/issues/6)
-- [ ] Install and configure shadcn/ui (using official CLI) - [#8](https://github.com/nullcoder/ghostpaste/issues/8)
+- [x] Install and configure shadcn/ui (using official CLI) - [#8](https://github.com/nullcoder/ghostpaste/issues/8)
- [ ] Install CodeMirror 6 and language modes - [#9](https://github.com/nullcoder/ghostpaste/issues/9)
- [ ] Install nanoid for ID generation - [#9](https://github.com/nullcoder/ghostpaste/issues/9)
- [x] Install @cloudflare/workers-types for type definitions - [#9](https://github.com/nullcoder/ghostpaste/issues/9)
- [ ] Install development dependencies (vitest, @testing-library/react) - [#10](https://github.com/nullcoder/ghostpaste/issues/10)
-- [ ] Install next-themes for theme management - [#8](https://github.com/nullcoder/ghostpaste/issues/8)
+- [x] Install next-themes for theme management - [#8](https://github.com/nullcoder/ghostpaste/issues/8)
### Project Structure
- [ ] Create folder structure (`app/`, `components/`, `lib/`, `types/`) - [#11](https://github.com/nullcoder/ghostpaste/issues/11)
- [ ] Set up app router pages structure - [#11](https://github.com/nullcoder/ghostpaste/issues/11)
-- [ ] Create base layout with theme provider - [#8](https://github.com/nullcoder/ghostpaste/issues/8)
-- [ ] Set up global styles and CSS variables - [#8](https://github.com/nullcoder/ghostpaste/issues/8)
+- [x] Create base layout with theme provider - [#8](https://github.com/nullcoder/ghostpaste/issues/8)
+- [x] Set up global styles and CSS variables - [#8](https://github.com/nullcoder/ghostpaste/issues/8)
- [ ] Verify Cloudflare R2 setup - [#12](https://github.com/nullcoder/ghostpaste/issues/12)
## 🔧 Phase 2: Core Infrastructure
diff --git a/lib/utils.ts b/lib/utils.ts
new file mode 100644
index 0000000..a5ef193
--- /dev/null
+++ b/lib/utils.ts
@@ -0,0 +1,6 @@
+import { clsx, type ClassValue } from "clsx";
+import { twMerge } from "tailwind-merge";
+
+export function cn(...inputs: ClassValue[]) {
+ return twMerge(clsx(inputs));
+}
diff --git a/package-lock.json b/package-lock.json
index 2ecbad2..d51bae6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,9 +8,15 @@
"name": "ghostpaste",
"version": "0.1.0",
"dependencies": {
+ "@radix-ui/react-slot": "^1.2.3",
+ "class-variance-authority": "^0.7.1",
+ "clsx": "^2.1.1",
+ "lucide-react": "^0.513.0",
"next": "15.3.3",
+ "next-themes": "^0.4.6",
"react": "^19.0.0",
- "react-dom": "^19.0.0"
+ "react-dom": "^19.0.0",
+ "tailwind-merge": "^3.3.0"
},
"devDependencies": {
"@cloudflare/workers-types": "^4.20250605.0",
@@ -28,6 +34,7 @@
"prettier": "^3.5.3",
"prettier-plugin-tailwindcss": "^0.6.12",
"tailwindcss": "^4",
+ "tw-animate-css": "^1.3.4",
"typescript": "^5",
"wrangler": "^4.19.1"
}
@@ -9825,6 +9832,39 @@
"wrangler": "^4.14.0"
}
},
+ "node_modules/@radix-ui/react-compose-refs": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
+ "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-slot": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
+ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@rtsao/scc": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
@@ -12809,6 +12849,18 @@
"node": ">=18"
}
},
+ "node_modules/class-variance-authority": {
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz",
+ "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "clsx": "^2.1.1"
+ },
+ "funding": {
+ "url": "https://polar.sh/cva"
+ }
+ },
"node_modules/cli-cursor": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz",
@@ -12902,6 +12954,15 @@
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
"license": "MIT"
},
+ "node_modules/clsx": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
+ "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/color": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
@@ -16104,6 +16165,15 @@
"node": "20 || >=22"
}
},
+ "node_modules/lucide-react": {
+ "version": "0.513.0",
+ "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.513.0.tgz",
+ "integrity": "sha512-CJZKq2g8Y8yN4Aq002GahSXbG2JpFv9kXwyiOAMvUBv7pxeOFHUWKB0mO7MiY4ZVFCV4aNjv2BJFq/z3DgKPQg==",
+ "license": "ISC",
+ "peerDependencies": {
+ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
"node_modules/magic-string": {
"version": "0.30.17",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
@@ -16454,6 +16524,16 @@
}
}
},
+ "node_modules/next-themes": {
+ "version": "0.4.6",
+ "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz",
+ "integrity": "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc"
+ }
+ },
"node_modules/next/node_modules/postcss": {
"version": "8.4.31",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
@@ -18151,6 +18231,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/tailwind-merge": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.3.0.tgz",
+ "integrity": "sha512-fyW/pEfcQSiigd5SNn0nApUOxx0zB/dm6UDU/rEwc2c3sX2smWUNbapHv+QRqLGVp9GWX3THIa7MUGPo+YkDzQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/dcastil"
+ }
+ },
"node_modules/tailwindcss": {
"version": "4.1.8",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.8.tgz",
@@ -18319,6 +18409,16 @@
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD"
},
+ "node_modules/tw-animate-css": {
+ "version": "1.3.4",
+ "resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.3.4.tgz",
+ "integrity": "sha512-dd1Ht6/YQHcNbq0znIT6dG8uhO7Ce+VIIhZUhjsryXsMPJQz3bZg7Q2eNzLwipb25bRZslGb2myio5mScd1TFg==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/Wombosvideo"
+ }
+ },
"node_modules/type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
diff --git a/package.json b/package.json
index 230fb64..6078528 100644
--- a/package.json
+++ b/package.json
@@ -17,9 +17,15 @@
"prepare": "husky"
},
"dependencies": {
+ "@radix-ui/react-slot": "^1.2.3",
+ "class-variance-authority": "^0.7.1",
+ "clsx": "^2.1.1",
+ "lucide-react": "^0.513.0",
"next": "15.3.3",
+ "next-themes": "^0.4.6",
"react": "^19.0.0",
- "react-dom": "^19.0.0"
+ "react-dom": "^19.0.0",
+ "tailwind-merge": "^3.3.0"
},
"devDependencies": {
"@cloudflare/workers-types": "^4.20250605.0",
@@ -37,6 +43,7 @@
"prettier": "^3.5.3",
"prettier-plugin-tailwindcss": "^0.6.12",
"tailwindcss": "^4",
+ "tw-animate-css": "^1.3.4",
"typescript": "^5",
"wrangler": "^4.19.1"
},