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:
v0 2025-11-23 02:53:52 +00:00
parent ea35500a0d
commit 6b58b1e8ac
8 changed files with 122 additions and 59 deletions

46
CLOUDFLARE_SETUP.md Normal file
View File

@ -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.

View File

@ -27,4 +27,4 @@ Continue building your app on:
1. Create and modify your project using [v0.app](https://v0.app)
2. Deploy your chats from the v0 interface
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

View File

@ -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,
})
}

View File

@ -30,7 +30,9 @@ export function HeroSection() {
</div>
<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>
<div className="text-lg md:text-xl text-muted-foreground mb-12 max-w-2xl mx-auto font-mono leading-relaxed">

64
functions/api/waitlist.ts Normal file
View File

@ -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' } }
)
}
}

View File

@ -1,12 +1,13 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'export',
distDir: 'out',
typescript: {
ignoreBuildErrors: true,
},
images: {
unoptimized: true,
},
}
export default nextConfig
export default nextConfig

View File

@ -30,7 +30,7 @@
"@radix-ui/react-select": "2.1.4",
"@radix-ui/react-separator": "1.1.1",
"@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-tabs": "1.1.2",
"@radix-ui/react-toast": "1.2.4",
@ -47,7 +47,7 @@
"input-otp": "1.4.1",
"lucide-react": "^0.454.0",
"next": "16.0.3",
"next-themes": "^0.4.6",
"next-themes": "latest",
"react": "19.2.0",
"react-day-picker": "9.8.0",
"react-dom": "19.2.0",

View File

@ -72,8 +72,8 @@ importers:
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)
'@radix-ui/react-slot':
specifier: 1.1.1
version: 1.1.1(@types/react@19.0.0)(react@19.2.0)
specifier: latest
version: 1.2.4(@types/react@19.0.0)(react@19.2.0)
'@radix-ui/react-switch':
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)
@ -123,7 +123,7 @@ importers:
specifier: 16.0.3
version: 16.0.3(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
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)
react:
specifier: 19.2.0