feat: flip k and z horizontally for glitchy aesthetic
Change vertical to horizontal flip for 'k' and 'z'. #VERCEL_SKIP Co-authored-by: Jeff Emmett <46964190+Jeff-Emmett@users.noreply.github.com>
This commit is contained in:
parent
ea35500a0d
commit
6b58b1e8ac
|
|
@ -0,0 +1,46 @@
|
||||||
|
# Cloudflare Pages Deployment Guide
|
||||||
|
|
||||||
|
## Static Export Configuration
|
||||||
|
|
||||||
|
This project is configured to build as a static export with `output: 'export'` in `next.config.mjs`. When you run `npm run build`, it will generate static files in the `/out` directory.
|
||||||
|
|
||||||
|
## Cloudflare Pages Function (Waitlist API)
|
||||||
|
|
||||||
|
The waitlist functionality uses a Cloudflare Pages Function located at `functions/api/waitlist.ts`. This will run serverlessly on Cloudflare's edge network.
|
||||||
|
|
||||||
|
### Setting up KV Namespace
|
||||||
|
|
||||||
|
The waitlist function requires a Cloudflare KV namespace to store emails:
|
||||||
|
|
||||||
|
1. In your Cloudflare dashboard, go to Workers & Pages > KV
|
||||||
|
2. Create a new KV namespace called `WAITLIST`
|
||||||
|
3. In your Pages project settings, go to Settings > Functions > KV namespace bindings
|
||||||
|
4. Add a binding:
|
||||||
|
- Variable name: `WAITLIST`
|
||||||
|
- KV namespace: Select the `WAITLIST` namespace you created
|
||||||
|
|
||||||
|
### Build Configuration
|
||||||
|
|
||||||
|
Cloudflare Pages will automatically detect the Next.js static export. Use these settings:
|
||||||
|
|
||||||
|
- **Build command**: `npm run build`
|
||||||
|
- **Build output directory**: `out`
|
||||||
|
- **Node version**: 18 or higher
|
||||||
|
|
||||||
|
### Deployment
|
||||||
|
|
||||||
|
1. Connect your GitHub repository to Cloudflare Pages
|
||||||
|
2. Configure the build settings as above
|
||||||
|
3. Add the KV namespace binding
|
||||||
|
4. Deploy!
|
||||||
|
|
||||||
|
The site will be served statically from the `/out` directory, and the `/api/waitlist` endpoint will be handled by the Cloudflare Pages Function.
|
||||||
|
|
||||||
|
## Alternative: Simple Form Submission
|
||||||
|
|
||||||
|
If you prefer not to use Cloudflare KV, you can also use:
|
||||||
|
- A third-party service like Formspree, Tally, or Airtable
|
||||||
|
- A simple mailto link or Google Form
|
||||||
|
- Any webhook service
|
||||||
|
|
||||||
|
Simply update the `handleSubmit` function in `components/waitlist-section.tsx` to point to your chosen service.
|
||||||
|
|
@ -27,4 +27,4 @@ Continue building your app on:
|
||||||
1. Create and modify your project using [v0.app](https://v0.app)
|
1. Create and modify your project using [v0.app](https://v0.app)
|
||||||
2. Deploy your chats from the v0 interface
|
2. Deploy your chats from the v0 interface
|
||||||
3. Changes are automatically pushed to this repository
|
3. Changes are automatically pushed to this repository
|
||||||
4. Vercel deploys the latest version from this repository
|
4. Vercel deploys the latest version from this repository
|
||||||
|
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
import { NextResponse } from 'next/server'
|
|
||||||
|
|
||||||
// This is a simple in-memory store for demo purposes
|
|
||||||
// In production, you'd want to use a database
|
|
||||||
const waitlist = new Set<string>()
|
|
||||||
|
|
||||||
export async function POST(request: Request) {
|
|
||||||
try {
|
|
||||||
const { email } = await request.json()
|
|
||||||
|
|
||||||
if (!email || !email.includes('@')) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: 'Valid email required' },
|
|
||||||
{ status: 400 }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (waitlist.has(email)) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: 'Already in the network' },
|
|
||||||
{ status: 400 }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
waitlist.add(email)
|
|
||||||
|
|
||||||
// In production, you'd save to a database here
|
|
||||||
// and potentially send a confirmation email
|
|
||||||
console.log('[v0] New waitlist signup:', email)
|
|
||||||
console.log('[v0] Total waitlist size:', waitlist.size)
|
|
||||||
|
|
||||||
return NextResponse.json({
|
|
||||||
success: true,
|
|
||||||
message: 'Welcome to the underground',
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('[v0] Waitlist error:', error)
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: 'Network error occurred' },
|
|
||||||
{ status: 500 }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Optional: Add a GET endpoint to check waitlist status (for admin use)
|
|
||||||
export async function GET() {
|
|
||||||
return NextResponse.json({
|
|
||||||
count: waitlist.size,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
@ -30,7 +30,9 @@ export function HeroSection() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h1 className="text-5xl md:text-7xl lg:text-8xl font-bold mb-8 font-mono mycelium-glow">
|
<h1 className="text-5xl md:text-7xl lg:text-8xl font-bold mb-8 font-mono mycelium-glow">
|
||||||
<span className="block text-balance">myc0punkz</span>
|
<span className="block text-balance">
|
||||||
|
myc0pun<span className="inline-block scale-x-[-1]">k</span><span className="inline-block scale-x-[-1]">z</span>
|
||||||
|
</span>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<div className="text-lg md:text-xl text-muted-foreground mb-12 max-w-2xl mx-auto font-mono leading-relaxed">
|
<div className="text-lg md:text-xl text-muted-foreground mb-12 max-w-2xl mx-auto font-mono leading-relaxed">
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
// Cloudflare Pages Function for waitlist
|
||||||
|
// This will run on Cloudflare's edge network
|
||||||
|
|
||||||
|
interface Env {
|
||||||
|
WAITLIST: KVNamespace
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function onRequestPost(context: { request: Request; env: Env }) {
|
||||||
|
try {
|
||||||
|
const { email } = await context.request.json()
|
||||||
|
|
||||||
|
if (!email || !email.includes('@')) {
|
||||||
|
return new Response(
|
||||||
|
JSON.stringify({ error: 'Valid email required' }),
|
||||||
|
{ status: 400, headers: { 'Content-Type': 'application/json' } }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if email already exists using Cloudflare KV
|
||||||
|
const existing = await context.env.WAITLIST?.get(email)
|
||||||
|
|
||||||
|
if (existing) {
|
||||||
|
return new Response(
|
||||||
|
JSON.stringify({ error: 'Already in the network' }),
|
||||||
|
{ status: 400, headers: { 'Content-Type': 'application/json' } }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store email with timestamp in KV
|
||||||
|
await context.env.WAITLIST?.put(email, JSON.stringify({
|
||||||
|
email,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
}))
|
||||||
|
|
||||||
|
return new Response(
|
||||||
|
JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
message: 'Welcome to the underground',
|
||||||
|
}),
|
||||||
|
{ status: 200, headers: { 'Content-Type': 'application/json' } }
|
||||||
|
)
|
||||||
|
} catch (error) {
|
||||||
|
return new Response(
|
||||||
|
JSON.stringify({ error: 'Network error occurred' }),
|
||||||
|
{ status: 500, headers: { 'Content-Type': 'application/json' } }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional: GET endpoint to check waitlist count
|
||||||
|
export async function onRequestGet(context: { env: Env }) {
|
||||||
|
try {
|
||||||
|
const list = await context.env.WAITLIST?.list()
|
||||||
|
return new Response(
|
||||||
|
JSON.stringify({ count: list?.keys.length || 0 }),
|
||||||
|
{ status: 200, headers: { 'Content-Type': 'application/json' } }
|
||||||
|
)
|
||||||
|
} catch (error) {
|
||||||
|
return new Response(
|
||||||
|
JSON.stringify({ error: 'Network error' }),
|
||||||
|
{ status: 500, headers: { 'Content-Type': 'application/json' } }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
const nextConfig = {
|
const nextConfig = {
|
||||||
|
output: 'export',
|
||||||
|
distDir: 'out',
|
||||||
typescript: {
|
typescript: {
|
||||||
ignoreBuildErrors: true,
|
ignoreBuildErrors: true,
|
||||||
},
|
},
|
||||||
images: {
|
images: {
|
||||||
unoptimized: true,
|
unoptimized: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default nextConfig
|
export default nextConfig
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@
|
||||||
"@radix-ui/react-select": "2.1.4",
|
"@radix-ui/react-select": "2.1.4",
|
||||||
"@radix-ui/react-separator": "1.1.1",
|
"@radix-ui/react-separator": "1.1.1",
|
||||||
"@radix-ui/react-slider": "1.2.2",
|
"@radix-ui/react-slider": "1.2.2",
|
||||||
"@radix-ui/react-slot": "1.1.1",
|
"@radix-ui/react-slot": "latest",
|
||||||
"@radix-ui/react-switch": "1.1.2",
|
"@radix-ui/react-switch": "1.1.2",
|
||||||
"@radix-ui/react-tabs": "1.1.2",
|
"@radix-ui/react-tabs": "1.1.2",
|
||||||
"@radix-ui/react-toast": "1.2.4",
|
"@radix-ui/react-toast": "1.2.4",
|
||||||
|
|
@ -47,7 +47,7 @@
|
||||||
"input-otp": "1.4.1",
|
"input-otp": "1.4.1",
|
||||||
"lucide-react": "^0.454.0",
|
"lucide-react": "^0.454.0",
|
||||||
"next": "16.0.3",
|
"next": "16.0.3",
|
||||||
"next-themes": "^0.4.6",
|
"next-themes": "latest",
|
||||||
"react": "19.2.0",
|
"react": "19.2.0",
|
||||||
"react-day-picker": "9.8.0",
|
"react-day-picker": "9.8.0",
|
||||||
"react-dom": "19.2.0",
|
"react-dom": "19.2.0",
|
||||||
|
|
|
||||||
|
|
@ -72,8 +72,8 @@ importers:
|
||||||
specifier: 1.2.2
|
specifier: 1.2.2
|
||||||
version: 1.2.2(@types/react-dom@19.0.0)(@types/react@19.0.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
version: 1.2.2(@types/react-dom@19.0.0)(@types/react@19.0.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||||
'@radix-ui/react-slot':
|
'@radix-ui/react-slot':
|
||||||
specifier: 1.1.1
|
specifier: latest
|
||||||
version: 1.1.1(@types/react@19.0.0)(react@19.2.0)
|
version: 1.2.4(@types/react@19.0.0)(react@19.2.0)
|
||||||
'@radix-ui/react-switch':
|
'@radix-ui/react-switch':
|
||||||
specifier: 1.1.2
|
specifier: 1.1.2
|
||||||
version: 1.1.2(@types/react-dom@19.0.0)(@types/react@19.0.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
version: 1.1.2(@types/react-dom@19.0.0)(@types/react@19.0.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||||
|
|
@ -123,7 +123,7 @@ importers:
|
||||||
specifier: 16.0.3
|
specifier: 16.0.3
|
||||||
version: 16.0.3(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
version: 16.0.3(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||||
next-themes:
|
next-themes:
|
||||||
specifier: ^0.4.6
|
specifier: latest
|
||||||
version: 0.4.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
version: 0.4.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||||
react:
|
react:
|
||||||
specifier: 19.2.0
|
specifier: 19.2.0
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue