diff --git a/.clinerules/cloudflare.md b/.clinerules/cloudflare.md new file mode 100644 index 000000000..5cfa8712c --- /dev/null +++ b/.clinerules/cloudflare.md @@ -0,0 +1,43 @@ +# NextJS + Cloudflare + OpenNext Deployment + +## Setup Requirements +- Node.js 18+ and Cloudflare account required +- **@opennextjs/cloudflare** adapter mandatory (not edge runtime) +- Global Wrangler CLI: `npm install -g wrangler` +- All deployments via OpenNext adapter; no direct NextJS builds + +## Project Configuration +- **wrangler.toml**: compatibility_date ≥ "2024-09-23", nodejs_compat flag +- **package.json**: `pages:build` script runs `npx @opennextjs/cloudflare@latest` +- **next.config.js**: `output: 'standalone'`, image optimization configured +- Build output directory: `.vercel/output/static` + +## Build & Deploy Process +- Build command: `npm run pages:build` (transforms NextJS → Workers) +- Local testing: `npm run preview` (required before deploy) +- Deploy: `npm run deploy` or Cloudflare Pages Git integration +- Never deploy untested builds; preview mimics production runtime + +## Environment & Security +- Environment variables in both Cloudflare Dashboard and `wrangler.toml` +- Secrets via `wrangler secret put SECRET_NAME` (not in wrangler.toml) +- Security headers required in API routes (X-Frame-Options, CSP, etc.) +- Cache headers mandatory for API endpoints: `s-maxage=86400, stale-while-revalidate` + +## Performance & Limits +- Bundle size limits: 3MB free tier, 15MB paid +- Dynamic imports for heavy components to reduce cold starts +- Static files in `public/` directory only +- Image optimization via Cloudflare Images or custom loader + +## Database & Storage +- Cloudflare D1 binding in wrangler.toml for SQL databases +- Workers KV for key-value storage +- All DB operations via environment bindings (env.DB, env.KV) +- No direct database connections; use Cloudflare services + +## CI/CD Integration +- GitHub Actions with CLOUDFLARE_API_TOKEN secret +- Build step: `npm run pages:build` +- Deploy: `wrangler pages deploy .vercel/output/static` +- Fail builds on type/compatibility errors \ No newline at end of file diff --git a/app/page.tsx b/app/page.tsx index 09b9d3835..7d072ce82 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -6,8 +6,6 @@ import { ArtistsSection } from "@/components/artists-section" import { ServicesSection } from "@/components/services-section" import { ContactSection } from "@/components/contact-section" import { Footer } from "@/components/footer" -import { MobileBookingBar } from "@/components/mobile-booking-bar" - export default function HomePage() { return (
@@ -27,7 +25,6 @@ export default function HomePage() {
) } diff --git a/components/artists-section.tsx b/components/artists-section.tsx index 568065acc..81467fde1 100644 --- a/components/artists-section.tsx +++ b/components/artists-section.tsx @@ -23,7 +23,7 @@ export function ArtistsSection() { } }) }, - { threshold: 0.1, rootMargin: "0px 0px -50px 0px" }, + { threshold: 0.2, rootMargin: "0px 0px 0px 0px" }, ) const cards = sectionRef.current?.querySelectorAll("[data-index]") @@ -55,29 +55,30 @@ export function ArtistsSection() { const sectionTop = sectionRef.current?.offsetTop || 0 const relativeScroll = scrollY - sectionTop - leftColumnRef.current.style.transform = `translateY(${relativeScroll * -0.05}px)` + leftColumnRef.current.style.transform = `translateY(${relativeScroll * -0.025}px)` centerColumnRef.current.style.transform = `translateY(0px)` - rightColumnRef.current.style.transform = `translateY(${relativeScroll * 0.05}px)` + rightColumnRef.current.style.transform = `translateY(${relativeScroll * 0.025}px)` const leftImages = leftColumnRef.current.querySelectorAll(".artist-image") const centerImages = centerColumnRef.current.querySelectorAll(".artist-image") const rightImages = rightColumnRef.current.querySelectorAll(".artist-image") leftImages.forEach((img) => { - ;(img as HTMLElement).style.transform = `translateY(${relativeScroll * -0.02}px)` + ;(img as HTMLElement).style.transform = `translateY(${relativeScroll * -0.01}px)` }) centerImages.forEach((img) => { - ;(img as HTMLElement).style.transform = `translateY(${relativeScroll * -0.015}px)` + ;(img as HTMLElement).style.transform = `translateY(${relativeScroll * -0.0075}px)` }) rightImages.forEach((img) => { - ;(img as HTMLElement).style.transform = `translateY(${relativeScroll * -0.01}px)` + ;(img as HTMLElement).style.transform = `translateY(${relativeScroll * -0.005}px)` }) } }, [scrollY]) - const leftColumn = artists.filter((_, index) => index % 3 === 0) - const centerColumn = artists.filter((_, index) => index % 3 === 1) - const rightColumn = artists.filter((_, index) => index % 3 === 2) + // Better distribution for visual balance + const leftColumn = [artists[0], artists[3], artists[6]] // Christy, Donovan, John + const centerColumn = [artists[1], artists[4], artists[7]] // Angel, EJ, Pako + const rightColumn = [artists[2], artists[5], artists[8]] // Amari, Heather, Sole return (
@@ -112,7 +113,7 @@ export function ArtistsSection() { -
+
@@ -126,25 +127,33 @@ export function ArtistsSection() { : "opacity-0 translate-y-8" }`} style={{ - transitionDelay: `${artists.indexOf(artist) * 100}ms`, + transitionDelay: `${artists.indexOf(artist) * 50}ms`, }} > -
+
-
- {`${artist.name} -
- -
+ {/* Portfolio background - full width */} +
{`${artist.name} + {/* Darkening overlay to push background further back */} +
+
+ + {/* Artist portrait - with proper feathered mask */} +
+ {`${artist.name}
@@ -155,7 +164,7 @@ export function ArtistsSection() {
-
+

{artist.name}

{artist.specialty}

{artist.bio}

@@ -194,25 +203,33 @@ export function ArtistsSection() { : "opacity-0 translate-y-8" }`} style={{ - transitionDelay: `${artists.indexOf(artist) * 100}ms`, + transitionDelay: `${artists.indexOf(artist) * 50}ms`, }} > -
+
-
- {`${artist.name} -
- -
+ {/* Portfolio background - full width */} +
{`${artist.name} + {/* Darkening overlay to push background further back */} +
+
+ + {/* Artist portrait - with proper feathered mask */} +
+ {`${artist.name}
@@ -223,7 +240,7 @@ export function ArtistsSection() {
-
+

{artist.name}

{artist.specialty}

{artist.bio}

@@ -262,25 +279,33 @@ export function ArtistsSection() { : "opacity-0 translate-y-8" }`} style={{ - transitionDelay: `${artists.indexOf(artist) * 100}ms`, + transitionDelay: `${artists.indexOf(artist) * 50}ms`, }} > -
+
-
- {`${artist.name} -
- -
+ {/* Portfolio background - full width */} +
{`${artist.name} + {/* Darkening overlay to push background further back */} +
+
+ + {/* Artist portrait - with proper feathered mask */} +
+ {`${artist.name}
@@ -291,7 +316,7 @@ export function ArtistsSection() {
-
+

{artist.name}

{artist.specialty}

{artist.bio}

@@ -322,7 +347,7 @@ export function ArtistsSection() {
-
+

READY?

diff --git a/components/contact-modal.tsx b/components/contact-modal.tsx index 232a116ab..6c3594179 100644 --- a/components/contact-modal.tsx +++ b/components/contact-modal.tsx @@ -103,7 +103,9 @@ export function ContactModal({ children }: ContactModalProps) { return (

{children} - + + {/* Solid background overlay to block any background images */} +
Get In Touch diff --git a/components/contact-section.tsx b/components/contact-section.tsx index 47d8f30e2..44c0d05ea 100644 --- a/components/contact-section.tsx +++ b/components/contact-section.tsx @@ -36,17 +36,23 @@ export function ContactSection() { return (
+ {/* Background logo - desktop only */}
+ + {/* Mobile solid background */} +
-
-
+
+ {/* Mobile background overlay to hide logo */} +
+

Let's Talk

Ready to create something amazing?

diff --git a/components/navigation.tsx b/components/navigation.tsx index b83bf1026..66f253bdd 100644 --- a/components/navigation.tsx +++ b/components/navigation.tsx @@ -45,7 +45,7 @@ export function Navigation() { className={`fixed top-0 left-0 right-0 z-50 transition-all duration-700 ease-out ${ isScrolled ? "bg-black/95 backdrop-blur-md shadow-lg border-b border-white/10 opacity-100" - : "bg-transparent lg:opacity-0 lg:pointer-events-none opacity-100 bg-black/80 backdrop-blur-md" + : "bg-black/80 backdrop-blur-md lg:bg-transparent lg:opacity-0 lg:pointer-events-none opacity-100" }`} >
diff --git a/components/services-mobile-carousel.tsx b/components/services-mobile-carousel.tsx index f70aa6d58..e0ef9bd85 100644 --- a/components/services-mobile-carousel.tsx +++ b/components/services-mobile-carousel.tsx @@ -61,8 +61,8 @@ export function ServicesMobileCarousel() { > {services.map((service, index) => ( - -
+ +
diff --git a/components/services-mobile-only.tsx b/components/services-mobile-only.tsx new file mode 100644 index 000000000..9c3bc3283 --- /dev/null +++ b/components/services-mobile-only.tsx @@ -0,0 +1,116 @@ +"use client" + +import { Button } from "@/components/ui/button" +import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card" +import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from "@/components/ui/carousel" +import Link from "next/link" + +const services = [ + { + title: "Black & Grey Realism", + description: "Photorealistic tattoos with incredible depth and detail using black and grey shading techniques.", + features: ["Lifelike portraits", "Detailed shading", "3D effects"], + price: "Starting at $250", + }, + { + title: "Cover-ups & Blackout", + description: "Transform old tattoos into stunning new pieces with expert cover-up techniques or bold blackout designs.", + features: ["Free consultation", "Creative solutions", "Complete coverage"], + price: "Starting at $300", + }, + { + title: "Fine Line & Micro Realism", + description: "Delicate, precise linework and tiny realistic designs that showcase incredible detail.", + features: ["Single needle work", "Intricate details", "Minimalist aesthetic"], + price: "Starting at $150", + }, + { + title: "Traditional & Neo-Traditional", + description: "Bold American traditional and neo-traditional styles with vibrant colors and strong lines.", + features: ["Classic designs", "Bold color palettes", "Timeless appeal"], + price: "Starting at $200", + }, + { + title: "Anime & Watercolor", + description: "Vibrant anime characters and painterly watercolor effects that bring art to life on skin.", + features: ["Character designs", "Soft color blends", "Artistic techniques"], + price: "Starting at $250", + }, +] + +export function ServicesMobileOnly() { + return ( +
+ {/* Header */} +
+
+ Our Services +
+

Choose Your Style

+

+ From custom designs to cover-ups, we offer comprehensive tattoo services with the highest standards. +

+
+ + {/* Carousel */} +
+ + + {services.map((service, index) => ( + + + +
+ Service {String(index + 1).padStart(2, "0")} +
+ + {service.title} + + + {service.description} + +
+ + +
+ {service.features.map((feature, idx) => ( +
+ + {feature} +
+ ))} +
+ +
+ {service.price} +
+
+ + + + +
+
+ ))} +
+ +
+ + +
+
+
+
+ ) +} diff --git a/components/services-section.tsx b/components/services-section.tsx index 10e07ef51..76f711f9f 100644 --- a/components/services-section.tsx +++ b/components/services-section.tsx @@ -3,7 +3,7 @@ import { useEffect, useRef, useState } from "react" import { Button } from "@/components/ui/button" import Link from "next/link" -import { ServicesMobileCarousel } from "@/components/services-mobile-carousel" +import { ServicesMobileOnly } from "@/components/services-mobile-only" const services = [ { @@ -101,10 +101,10 @@ export function ServicesSection() {
-
+
{/* Left Side - Enhanced with split composition styling */} -
+
@@ -201,8 +201,9 @@ export function ServicesSection() {
-
+ +
) } diff --git a/data/artists.ts b/data/artists.ts index 84f090ff1..1d345acbe 100644 --- a/data/artists.ts +++ b/data/artists.ts @@ -269,7 +269,7 @@ export const artists: Artist[] = [ name: "Steven 'Sole' Cedre", title: "It has to have soul, Sole!", specialty: "Gritty Realism & Comic Art", - faceImage: "/artists/sole-cedre-portrait.jpg", + faceImage: "/artists/steven-sole-cedre.jpg", workImages: [ "/artists/sole-cedre-work-1.jpg", "/artists/sole-cedre-work-2.jpg", diff --git a/public/artists/amari-rodriguez-portrait.jpg b/public/artists/amari-rodriguez-portrait.jpg index 6fa7543d3..76dbb9119 100644 Binary files a/public/artists/amari-rodriguez-portrait.jpg and b/public/artists/amari-rodriguez-portrait.jpg differ diff --git a/public/artists/angel-andrade-portrait.jpg b/public/artists/angel-andrade-portrait.jpg index f7b91abbb..6fd2a4a26 100644 Binary files a/public/artists/angel-andrade-portrait.jpg and b/public/artists/angel-andrade-portrait.jpg differ diff --git a/public/artists/dez-portrait.jpg b/public/artists/dez-portrait.jpg new file mode 100644 index 000000000..e5802f2f6 Binary files /dev/null and b/public/artists/dez-portrait.jpg differ diff --git a/public/artists/donovan-lankford-portrait.jpg b/public/artists/donovan-lankford-portrait.jpg index 8439b9ea0..ef7cf7cf3 100644 Binary files a/public/artists/donovan-lankford-portrait.jpg and b/public/artists/donovan-lankford-portrait.jpg differ diff --git a/public/artists/donovan-lankford-portrait.png b/public/artists/donovan-lankford-portrait.png new file mode 100644 index 000000000..69eb09566 Binary files /dev/null and b/public/artists/donovan-lankford-portrait.png differ diff --git a/public/artists/ej-segoviano-portrait.jpg b/public/artists/ej-segoviano-portrait.jpg index 6fa7543d3..ee51fa9f8 100644 Binary files a/public/artists/ej-segoviano-portrait.jpg and b/public/artists/ej-segoviano-portrait.jpg differ diff --git a/public/artists/john-lapides-portrait.jpg b/public/artists/john-lapides-portrait.jpg new file mode 100644 index 000000000..94484f641 Binary files /dev/null and b/public/artists/john-lapides-portrait.jpg differ diff --git a/public/artists/steven-sole-cedre.jpg b/public/artists/steven-sole-cedre.jpg new file mode 100644 index 000000000..0971f7247 Binary files /dev/null and b/public/artists/steven-sole-cedre.jpg differ