feat(design): enhance UI with new components, styles, and assets

- Introduced new design tokens and color variables in globals.css to align with the United Tattoo 2026 design system.
- Updated tailwind.config.ts to include new brand colors, background images, and box shadows for improved UI aesthetics.
- Replaced existing hero section with a new design featuring enhanced parallax effects and glassmorphism styles.
- Added multiple new images and icons to support the updated design, including navigation and hero section assets.
- Implemented a new layout structure in page.tsx, integrating new sections for immersion and identity, enhancing user engagement.

This commit significantly improves the overall design and user experience, establishing a cohesive visual identity for the application.
This commit is contained in:
Nicholai 2025-11-25 03:02:21 -07:00
parent 8d2620e0c1
commit 404136ed36
42 changed files with 631 additions and 334 deletions

2
.gitignore vendored
View File

@ -159,3 +159,5 @@ supabase/.temp/
.claude/**
.cursor/**
.cursor/
2025-11-25-where-we-left-off.txt
AGENTS.md

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

View File

@ -4,40 +4,73 @@
@custom-variant dark (&:is(.dark *));
:root {
/* Updated color tokens to match United Tattoo design brief */
--background: oklch(1 0 0); /* White */
--foreground: oklch(0.145 0 0); /* Dark Slate Gray */
--card: oklch(1 0 0); /* Light Gray */
--card-foreground: oklch(0.145 0 0); /* Dark text for cards */
--popover: oklch(1 0 0); /* White */
--popover-foreground: oklch(0.145 0 0); /* Dark text */
--primary: oklch(0.205 0 0); /* Emerald-600 #059669 */
--primary-foreground: oklch(0.985 0 0); /* White text on primary */
--secondary: oklch(0.97 0 0); /* Emerald accent #10b981 */
--secondary-foreground: oklch(0.205 0 0); /* White text on secondary */
--muted: oklch(0.97 0 0); /* Light Gray */
--muted-foreground: oklch(0.556 0 0); /* Muted text */
--accent: oklch(0.97 0 0); /* Emerald accent */
--accent-foreground: oklch(0.205 0 0); /* White text on accent */
/* United Tattoo 2026 Design System - Color Tokens */
/* Primary Brand Colors */
--burnt-orange: #E67E50;
--terracotta: #D87850;
--burnt: #b0471e;
/* Secondary Colors */
--sage-concrete: #7A8B8B;
--sage: #a28f79;
--deep-olive: #4a4034;
--moss: #6f5c49;
/* Neutral Colors */
--charcoal: #1c1915;
--ink: #241b16;
--cream: #fff7ec;
--sand: #f2e3d0;
--white: #ffffff;
/* Semantic Colors */
--rose: #e59863;
/* ShadCN UI Token Mapping */
--background: oklch(0.98 0.01 60); /* Cream background */
--foreground: oklch(0.15 0.01 40); /* Ink text */
--card: oklch(0.97 0.015 55); /* Sand card background */
--card-foreground: oklch(0.15 0.01 40); /* Ink text on cards */
--popover: oklch(0.97 0.015 55); /* Sand popover */
--popover-foreground: oklch(0.15 0.01 40); /* Ink text */
--primary: oklch(0.48 0.12 35); /* Burnt (dark orange) */
--primary-foreground: oklch(0.99 0 0); /* White text on primary */
--secondary: oklch(0.63 0.08 45); /* Terracotta */
--secondary-foreground: oklch(0.99 0 0); /* White text on secondary */
--muted: oklch(0.95 0.01 55); /* Light sand */
--muted-foreground: oklch(0.50 0.02 50); /* Moss for muted text */
--accent: oklch(0.67 0.06 45); /* Rose accent */
--accent-foreground: oklch(0.15 0.01 40); /* Ink text on accent */
--destructive: oklch(0.577 0.245 27.325); /* Red for destructive actions */
--destructive-foreground: oklch(0.985 0 0); /* White text */
--border: oklch(0.922 0 0); /* Light border */
--input: oklch(0.922 0 0); /* Input background */
--ring: oklch(0.708 0 0); /* Focus ring */
--chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704);
--chart-3: oklch(0.398 0.07 227.392);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);
--radius: 0.625rem;
--sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.145 0 0);
--sidebar-primary: oklch(0.205 0 0);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.97 0 0);
--sidebar-accent-foreground: oklch(0.205 0 0);
--sidebar-border: oklch(0.922 0 0);
--sidebar-ring: oklch(0.708 0 0);
--border: oklch(0.60 0.02 150 / 0.2); /* Sage concrete with opacity */
--input: oklch(0.97 0.015 55); /* Sand input background */
--ring: oklch(0.67 0.06 45); /* Rose focus ring */
/* Chart Colors - Updated to match brand */
--chart-1: oklch(0.63 0.08 45); /* Terracotta */
--chart-2: oklch(0.60 0.02 150); /* Sage concrete */
--chart-3: oklch(0.67 0.06 50); /* Sage */
--chart-4: oklch(0.48 0.12 35); /* Burnt */
--chart-5: oklch(0.67 0.06 45); /* Rose */
/* Border Radius */
--radius: 0.75rem;
/* Sidebar Colors */
--sidebar: oklch(0.98 0.01 60);
--sidebar-foreground: oklch(0.15 0.01 40);
--sidebar-primary: oklch(0.48 0.12 35);
--sidebar-primary-foreground: oklch(0.99 0 0);
--sidebar-accent: oklch(0.97 0.015 55);
--sidebar-accent-foreground: oklch(0.15 0.01 40);
--sidebar-border: oklch(0.60 0.02 150 / 0.2);
--sidebar-ring: oklch(0.67 0.06 45);
/* Design System Variables */
--sticky-offset: clamp(3.5rem, 8vw, 6rem);
--ambient-color: rgba(178, 109, 70, 0.4);
}
.dark {
@ -76,7 +109,7 @@
}
@theme inline {
--font-sans: var(--font-source-sans);
--font-sans: var(--font-grotesk);
--font-serif: var(--font-playfair);
--font-mono: var(--font-geist-mono);
--color-background: var(--background);
@ -121,8 +154,44 @@
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
@apply text-foreground;
/* Sun-washed plaster background with texture layers */
background: linear-gradient(180deg, #7A8B8B 0%, #9CAAA6 45%, #F2E3D0 100%);
position: relative;
font-family: var(--font-grotesk), sans-serif;
}
/* Texture overlay layer - Ambient gradients */
body::before {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: -2;
background:
radial-gradient(circle at 15% 20%, rgba(216, 120, 80, 0.15), transparent 45%),
radial-gradient(circle at 85% 5%, rgba(36, 27, 22, 0.08), transparent 55%);
opacity: 0.8;
}
/* Technical Grid Texture (Drafting Table feel) */
body::after {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: -1;
background-image: url('data:image/svg+xml;charset=UTF-8,<svg xmlns="http://www.w3.org/2000/svg" width="400" height="400" viewBox="0 0 200 200" fill="none" stroke="rgba(28, 25, 21, 0.06)" stroke-width="0.5"><path d="M0 0h200v200H0z"/><path d="M-10 20h220"/><path d="M-10 60h220"/><path d="M-10 100h220"/><path d="M-10 140h220"/><path d="M-10 180h220"/><path d="M20 -10v220"/><path d="M60 -10v220"/><path d="M100 -10v220"/><path d="M140 -10v220"/><path d="M180 -10v220"/></svg>');
mix-blend-mode: multiply;
opacity: 1;
}
/* Added Lenis smooth scrolling styles */

View File

@ -1,6 +1,6 @@
import type React from "react"
import type { Metadata } from "next"
import { Playfair_Display, Source_Sans_3 } from "next/font/google"
import { Playfair_Display, Space_Grotesk } from "next/font/google"
import { Suspense } from "react"
import Script from "next/script"
@ -15,13 +15,16 @@ const playfairDisplay = Playfair_Display({
variable: "--font-playfair",
display: "swap",
preload: true,
weight: ["400", "600"],
style: ["normal", "italic"],
})
const sourceSans = Source_Sans_3({
const spaceGrotesk = Space_Grotesk({
subsets: ["latin"],
variable: "--font-source-sans",
variable: "--font-grotesk",
display: "swap",
preload: true,
weight: ["300", "400", "500", "600", "700"],
})
export const metadata: Metadata = createMetadata({
@ -43,7 +46,7 @@ export default function RootLayout({
const organizationData = generateOrganizationJsonLd()
return (
<html lang="en" className={`${playfairDisplay.variable} ${sourceSans.variable}`}>
<html lang="en" className={`${playfairDisplay.variable} ${spaceGrotesk.variable}`}>
<head>
{/*
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

View File

@ -2,32 +2,38 @@ import { Navigation } from "@/components/navigation"
import { ScrollProgress } from "@/components/scroll-progress"
import { ScrollToSection } from "@/components/scroll-to-section"
import { LenisProvider } from "@/components/smooth-scroll-provider"
import { HeroSection } from "@/components/hero-section"
import { ArtistsSection } from "@/components/artists-section"
import { ServicesSection } from "@/components/services-section"
import { ContactSection } from "@/components/contact-section"
import { Footer } from "@/components/footer"
import { NewHero } from "@/components/united/new-hero"
import { ImmersionSection } from "@/components/united/immersion-section"
import { IdentitySection } from "@/components/united/identity-section"
import { NewArtistsSection } from "@/components/united/new-artists-section"
import { NewContactSection } from "@/components/united/new-contact-section"
export default function HomePage() {
return (
<LenisProvider>
<main className="min-h-screen">
<ScrollProgress />
<ScrollToSection />
<Navigation />
<div id="home">
<HeroSection />
</div>
<div id="artists">
<ArtistsSection />
</div>
<div id="services">
<ServicesSection />
</div>
<div id="contact">
<ContactSection />
</div>
<Footer />
</main>
</LenisProvider>
)
return (
<LenisProvider>
<main className="min-h-screen selection:bg-[var(--terracotta)] selection:text-white">
<ScrollProgress />
<ScrollToSection />
<Navigation />
<div id="home">
<NewHero />
</div>
<ImmersionSection />
<div id="about">
<IdentitySection />
</div>
<NewArtistsSection />
<NewContactSection />
<Footer />
</main>
</LenisProvider>
)
}

View File

@ -11,7 +11,7 @@ export function HeroSection() {
const [isVisible, setIsVisible] = useState(false)
const advancedNavAnimations = useFeatureFlag("ADVANCED_NAV_SCROLL_ANIMATIONS_ENABLED")
const reducedMotion = useReducedMotion()
// Use new parallax system with proper accessibility support
const parallax = useMultiLayerParallax(!advancedNavAnimations || reducedMotion)
@ -21,67 +21,107 @@ export function HeroSection() {
}, [])
return (
<section
id="home"
className="min-h-screen flex items-center justify-center relative overflow-hidden"
<section
id="home"
className="min-h-[67vh] flex items-center justify-center relative overflow-hidden bg-sage-concrete"
data-reduced-motion={reducedMotion}
>
{/* Background Layer - Slowest parallax */}
{/* Background Layer - New hero image with parallax */}
<div
ref={parallax.background.ref}
className="absolute inset-0 bg-cover bg-center bg-no-repeat will-change-transform"
className="absolute inset-0 bg-cover bg-no-repeat will-change-transform"
style={{
backgroundImage: "url(/united-logo-full.jpg)",
backgroundImage: "url(/UP1_00010_.png)",
backgroundPosition: "center 35%",
maskImage: "linear-gradient(180deg, rgba(0, 0, 0, 1) 0%, rgba(0, 0, 0, 1) 50%, rgba(0, 0, 0, 0.6) 80%, rgba(0, 0, 0, 0) 100%)",
WebkitMaskImage: "linear-gradient(180deg, rgba(0, 0, 0, 1) 0%, rgba(0, 0, 0, 1) 50%, rgba(0, 0, 0, 0.6) 80%, rgba(0, 0, 0, 0) 100%)",
...parallax.background.style,
}}
aria-hidden="true"
/>
{/* Midground Layer - Overlay with subtle parallax */}
{/* Midground Layer - Gradient overlay with subtle parallax */}
<div
ref={parallax.midground.ref}
className="absolute inset-0 bg-black/70 will-change-transform"
style={parallax.midground.style}
className="absolute inset-0 will-change-transform"
style={{
background: "linear-gradient(135deg, rgba(242, 227, 208, 0.3), rgba(255, 247, 236, 0.2))",
...parallax.midground.style,
}}
aria-hidden="true"
/>
{/* Foreground Layer - Content with slight counter-parallax */}
{/* Removed fade mask - using maskImage on background layer instead */}
{/* Foreground Layer - Glassmorphism content card with counter-parallax */}
<div
ref={parallax.foreground.ref}
className="relative z-10 text-center max-w-4xl px-8 will-change-transform"
className="relative z-10 text-center will-change-transform"
style={parallax.foreground.style}
>
<div
className={cn(
"max-w-[620px] mx-auto px-8 py-12 lg:px-14 lg:py-16",
"transition-all duration-1000",
isVisible ? "opacity-100 translate-y-0" : "opacity-0 translate-y-8"
)}
style={{
background: "rgba(242, 227, 208, 0.75)",
backdropFilter: "blur(12px) saturate(110%)",
WebkitBackdropFilter: "blur(12px) saturate(110%)",
borderRadius: "24px",
border: "1px solid rgba(36, 27, 22, 0.08)",
boxShadow: "0 20px 40px rgba(36, 27, 22, 0.15)",
}}
>
<h1 className="font-playfair text-5xl lg:text-7xl font-bold text-white mb-6 tracking-tight">
{/* Hero Title */}
<h1
className="font-playfair mb-4 tracking-tight leading-tight"
style={{
fontSize: "clamp(2.5rem, 5vw, 3.8rem)",
lineHeight: "1.1",
color: "#1c1915", // charcoal
}}
>
UNITED TATTOO
</h1>
</div>
<div
className={cn(
"transition-all duration-1000 delay-300",
isVisible ? "opacity-100 translate-y-0" : "opacity-0 translate-y-8"
)}
>
<p className="text-xl lg:text-2xl text-gray-200 mb-12 font-light leading-relaxed">
{/* Hero Subtitle */}
<p
className="mb-8 font-grotesk leading-loose max-w-[54ch] mx-auto"
style={{
fontSize: "clamp(0.95rem, 2vw, 1.3rem)",
lineHeight: "1.65",
color: "#4a4034", // deep-olive
}}
>
Custom Tattoos in Fountain, Colorado
</p>
</div>
<div
className={cn(
"transition-all duration-1000 delay-500",
isVisible ? "opacity-100 translate-y-0" : "opacity-0 translate-y-8"
)}
>
{/* CTA Button */}
<Button
size="lg"
className="bg-gray-50 text-gray-900 hover:bg-gray-100 px-8 py-4 text-lg font-medium rounded-lg w-full sm:w-auto transition-colors"
className={cn(
"px-8 py-4 text-base font-semibold",
"transition-all duration-200 ease-out",
"uppercase tracking-wider font-grotesk",
"w-full sm:w-auto"
)}
style={{
letterSpacing: "0.2em",
backgroundColor: "#b0471e",
color: "#ffffff",
borderRadius: "12px",
boxShadow: "0 10px 22px rgba(176, 71, 30, 0.25)",
}}
onMouseEnter={(e) => {
e.currentTarget.style.transform = "translateY(-1px) scale(1.03)";
e.currentTarget.style.boxShadow = "0 14px 28px rgba(176, 71, 30, 0.35)";
}}
onMouseLeave={(e) => {
e.currentTarget.style.transform = "none";
e.currentTarget.style.boxShadow = "0 10px 22px rgba(176, 71, 30, 0.25)";
}}
>
Book Consultation
</Button>

View File

@ -1,256 +1,153 @@
"use client"
"use client";
import { useCallback, useEffect, useState } from "react"
import type { MouseEvent } from "react"
import Link from "next/link"
import { usePathname, useRouter } from "next/navigation"
import { ArrowUpRight, Menu, X } from "lucide-react"
import Link from "next/link";
import { usePathname } from "next/navigation";
import { Menu, X } from "lucide-react";
import { useEffect, useState } from "react";
import { Button } from "@/components/ui/button"
import {
NavigationMenu,
NavigationMenuItem,
NavigationMenuLink,
NavigationMenuList,
} from "@/components/ui/navigation-menu"
import { cn } from "@/lib/utils"
NavigationMenu as ShadNavigationMenu,
NavigationMenuItem,
NavigationMenuLink,
NavigationMenuList,
} from "@/components/ui/navigation-menu";
import { cn } from "@/lib/utils";
type NavItem = {
href: string
label: string
id: string
isButton?: boolean
}
const NAV_ITEMS = [
{ name: "Artists", href: "/artists" },
{ name: "Your Deposit", href: "/deposit" },
{ name: "Aftercare", href: "/aftercare" },
{ name: "Contact", href: "/contact" },
];
const navItems: NavItem[] = [
{ href: "#home", label: "Home", id: "home" },
{ href: "#artists", label: "Artists", id: "artists" },
{ href: "#services", label: "Services", id: "services" },
{ href: "#contact", label: "Contact", id: "contact" },
{ href: "/book", label: "Book Now", id: "book", isButton: true },
]
const scrollTrackedIds = navItems.filter((item) => !item.isButton).map((item) => item.id)
const CTA_LABEL = "Book";
export function Navigation() {
const pathname = usePathname()
const router = useRouter()
const [isOpen, setIsOpen] = useState(false)
const [isScrolled, setIsScrolled] = useState(false)
const [activeSection, setActiveSection] = useState(scrollTrackedIds[0] ?? "")
const pathname = usePathname();
const [isScrolled, setIsScrolled] = useState(false);
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
const scrollToSection = useCallback(
(targetId: string, options?: { href?: string; updateHistory?: boolean }) => {
const target = document.getElementById(targetId)
if (!target) {
return
}
useEffect(() => {
const handleScroll = () => setIsScrolled(window.scrollY > 16);
const offset = target.getBoundingClientRect().top + window.scrollY - 80
window.scrollTo({ top: offset, behavior: "smooth" })
window.addEventListener("scroll", handleScroll, { passive: true });
return () => window.removeEventListener("scroll", handleScroll);
}, []);
if (options?.href && options.updateHistory !== false) {
window.history.replaceState(null, "", options.href)
}
useEffect(() => {
setMobileMenuOpen(false);
}, [pathname]);
setActiveSection(targetId)
},
[],
)
const navLinkClass = (isActive: boolean) =>
cn(
"group inline-flex flex-col items-center gap-1 font-semibold text-xs uppercase tracking-very-wide text-charcoal transition-colors duration-300 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-rose focus-visible:ring-offset-4 rounded-sm",
isActive ? "opacity-100" : "opacity-80 hover:opacity-100",
);
useEffect(() => {
const handleScroll = () => {
setIsScrolled(window.scrollY > 50)
const scrollPosition = window.scrollY + 100
for (const section of scrollTrackedIds) {
const element = document.getElementById(section)
if (!element) {
continue
}
const { offsetTop, offsetHeight } = element
if (scrollPosition >= offsetTop && scrollPosition < offsetTop + offsetHeight) {
setActiveSection(section)
return
}
}
}
handleScroll()
window.addEventListener("scroll", handleScroll)
return () => window.removeEventListener("scroll", handleScroll)
}, [scrollTrackedIds])
useEffect(() => {
if (pathname !== "/") {
return
}
const hash = window.location.hash.slice(1)
if (!hash) {
return
}
const frame = window.requestAnimationFrame(() => {
scrollToSection(hash, { updateHistory: false })
})
return () => window.cancelAnimationFrame(frame)
}, [pathname, scrollToSection])
const handleNavClick = (event: MouseEvent<HTMLAnchorElement>, item: NavItem) => {
if (item.isButton || !item.href.startsWith("/#")) {
return
}
if (pathname === "/") {
event.preventDefault()
const targetId = item.href.slice(2)
scrollToSection(targetId, { href: item.href })
return
}
event.preventDefault()
router.push(item.href)
}
const handleToggleMenu = () => setIsOpen((previous) => !previous)
const handleCloseMenu = () => setIsOpen(false)
return (
<nav
className={cn(
"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 backdrop-blur-none opacity-100",
)}
>
<div className="max-w-[1800px] mx-auto px-6 lg:px-10">
<div className="flex items-center justify-between h-20">
<Link
href="/"
className="flex flex-col items-start transition-all duration-500 text-white group"
>
<span className="font-bold text-2xl lg:text-3xl tracking-[0.15em] leading-none">
UNITED
</span>
<div className="flex items-center gap-2 mt-1">
<span className="h-px w-10 bg-white"></span>
<span className="text-xs lg:text-sm font-medium tracking-[0.2em] uppercase">
TATTOO
</span>
</div>
</Link>
<div className="hidden lg:flex items-center flex-1 justify-between ml-16">
<NavigationMenu viewport={false} className="flex-initial items-center bg-transparent text-white">
<NavigationMenuList className="flex items-center gap-8">
{navItems
.filter((item) => !item.isButton)
.map((item) => {
const isActive = activeSection === item.id
return (
<NavigationMenuItem key={item.id} className="min-w-max">
<NavigationMenuLink
asChild
data-active={isActive || undefined}
className={cn(
"group relative inline-flex h-auto bg-transparent px-0 py-1 text-sm font-semibold tracking-[0.15em] uppercase transition-all duration-300",
"text-white/90 hover:bg-transparent hover:text-white focus:bg-transparent focus:text-white",
isActive && "text-white",
)}
>
<Link href={item.href}>{item.label}</Link>
</NavigationMenuLink>
</NavigationMenuItem>
)
})}
</NavigationMenuList>
</NavigationMenu>
<Button
asChild
className={cn(
"px-8 py-3 text-sm font-semibold tracking-[0.1em] uppercase transition-all duration-300 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/70 focus-visible:ring-offset-0 hover:scale-105 group",
return (
<nav
className={cn(
"fixed inset-x-0 top-0 z-50 border-b transition-all duration-500",
isScrolled
? "bg-white text-black hover:bg-gray-100 shadow-xl hover:shadow-2xl"
: "border border-white/80 bg-transparent text-white shadow-none hover:bg-white/10",
)}
>
<Link href="/book" className="flex items-center gap-2">
<span>Book Now</span>
<ArrowUpRight className="h-4 w-4 transition-transform duration-300 group-hover:translate-x-0.5 group-hover:-translate-y-0.5" />
</Link>
</Button>
</div>
? "bg-sand/95 border-charcoal/10 backdrop-blur-xl shadow-[0_12px_40px_rgba(28,25,21,0.08)]"
: "bg-sage-concrete/10 backdrop-blur-sm border-charcoal/5",
)}
>
<div className="max-w-[1600px] mx-auto px-[clamp(1.5rem,4vw,5rem)]">
<div className="flex items-center gap-4 py-4">
<Link href="/" className="group">
<div className="flex flex-col text-left">
<span
className="font-playfair text-xl lg:text-[1.65rem] font-semibold leading-none tracking-tight transition-opacity duration-300 group-hover:opacity-75 text-charcoal"
>
United Tattoo
</span>
<span className="font-grotesk text-xs font-medium uppercase tracking-extra-wide text-charcoal/90 hidden sm:block">
Gallery &amp; Studio Fountain, CO
</span>
</div>
</Link>
<button
className="lg:hidden p-4 rounded-lg transition-all duration-300 text-white hover:bg-white/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/70 focus-visible:ring-offset-0"
onClick={handleToggleMenu}
aria-label="Toggle menu"
>
{isOpen ? <X size={24} /> : <Menu size={24} />}
</button>
</div>
<div className="hidden lg:flex flex-1 justify-center">
<ShadNavigationMenu className="bg-transparent text-charcoal" viewport={false}>
<NavigationMenuList className="gap-8">
{NAV_ITEMS.map((item) => {
const isActive = pathname === item.href;
return (
<NavigationMenuItem key={item.name}>
<NavigationMenuLink asChild>
<Link href={item.href} className={navLinkClass(isActive)}>
<span>{item.name}</span>
<span
aria-hidden
className={cn(
"h-px w-8 origin-left bg-burnt/70 transition-transform duration-300",
isActive ? "scale-x-100" : "scale-x-0 group-hover:scale-x-100",
)}
/>
</Link>
</NavigationMenuLink>
</NavigationMenuItem>
);
})}
</NavigationMenuList>
</ShadNavigationMenu>
</div>
{isOpen && (
<div className="lg:hidden bg-black/98 backdrop-blur-md border-t border-white/10">
<div className="px-6 py-8 space-y-5">
<NavigationMenu viewport={false} className="w-full">
<NavigationMenuList className="flex w-full flex-col space-y-3">
{navItems.map((item) => {
const isActive = !item.isButton && activeSection === item.id
if (item.isButton) {
return (
<NavigationMenuItem key={item.id} className="w-full">
<Button
asChild
className="w-full bg-white hover:bg-gray-100 text-black py-5 text-lg font-semibold tracking-[0.05em] uppercase shadow-xl mt-8"
>
<Link href={item.href} onClick={handleCloseMenu}>
{item.label}
</Link>
</Button>
</NavigationMenuItem>
)
}
return (
<NavigationMenuItem key={item.id} className="w-full">
<NavigationMenuLink
asChild
data-active={isActive || undefined}
className={cn(
"block w-full rounded-md px-4 py-4 text-lg font-semibold tracking-[0.1em] uppercase transition-all duration-300",
isActive
? "border-l-4 border-white pl-6 text-white"
: "text-white/70 hover:text-white hover:pl-5 focus:text-white focus:pl-5",
)}
<div className="hidden lg:flex items-center justify-end">
<Link
href="/book"
className="group relative inline-flex h-[44px] items-center gap-3 rounded-[12px] bg-sage-concrete px-8 text-xs font-semibold uppercase tracking-widest text-white transition-all duration-300 hover:bg-sage-concrete/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-rose focus-visible:ring-offset-2 shadow-sm"
>
<Link
href={item.href}
onClick={(event) => {
handleNavClick(event, item)
handleCloseMenu()
}}
>
{item.label}
</Link>
</NavigationMenuLink>
</NavigationMenuItem>
)
})}
</NavigationMenuList>
</NavigationMenu>
<span
aria-hidden
className="inline-flex h-2 w-2 rounded-full bg-burnt transition-transform duration-300 group-hover:scale-125"
/>
<span>{CTA_LABEL}</span>
</Link>
</div>
<button
className="lg:hidden flex h-[44px] w-[44px] items-center justify-center rounded-[12px] border border-charcoal/15 text-charcoal transition-colors duration-200 hover:border-charcoal/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-rose"
onClick={() => setMobileMenuOpen((prev) => !prev)}
aria-label="Toggle menu"
aria-expanded={mobileMenuOpen}
>
{mobileMenuOpen ? <X size={20} /> : <Menu size={20} />}
</button>
</div>
{mobileMenuOpen && (
<div className="lg:hidden border-t border-charcoal/10 pb-6">
<div className="space-y-1 pt-4">
{NAV_ITEMS.map((item) => {
const isActive = pathname === item.href;
return (
<Link
key={item.name}
href={item.href}
className={cn(
"block px-2 py-4 text-sm font-semibold uppercase tracking-[0.3em]",
isActive ? "text-charcoal" : "text-charcoal/80",
)}
>
{item.name}
</Link>
);
})}
</div>
<div className="pt-6">
<Link
href="/book"
className="flex w-full items-center justify-center rounded-[12px] bg-sage-concrete py-4 text-sm font-semibold uppercase tracking-widest text-white transition-colors hover:bg-sage-concrete/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-rose shadow-sm"
>
{CTA_LABEL}
</Link>
</div>
</div>
)}
</div>
</div>
)}
</div>
</nav>
)
</nav>
);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 585 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 566 B

View File

@ -0,0 +1,26 @@
<svg width="180" height="180" viewBox="0 0 180 180" fill="none" xmlns="http://www.w3.org/2000/svg">
<style>
@media (prefers-color-scheme: light) {
.background { fill: black; }
.foreground { fill: white; }
}
@media (prefers-color-scheme: dark) {
.background { fill: white; }
.foreground { fill: black; }
}
</style>
<g clip-path="url(#clip0_7960_43945)">
<rect class="background" width="180" height="180" rx="37" />
<g style="transform: scale(95%); transform-origin: center">
<path class="foreground"
d="M101.141 53H136.632C151.023 53 162.689 64.6662 162.689 79.0573V112.904H148.112V79.0573C148.112 78.7105 148.098 78.3662 148.072 78.0251L112.581 112.898C112.701 112.902 112.821 112.904 112.941 112.904H148.112V126.672H112.941C98.5504 126.672 86.5638 114.891 86.5638 100.5V66.7434H101.141V100.5C101.141 101.15 101.191 101.792 101.289 102.422L137.56 66.7816C137.255 66.7563 136.945 66.7434 136.632 66.7434H101.141V53Z" />
<path class="foreground"
d="M65.2926 124.136L14 66.7372H34.6355L64.7495 100.436V66.7372H80.1365V118.47C80.1365 126.278 70.4953 129.958 65.2926 124.136Z" />
</g>
</g>
<defs>
<clipPath id="clip0_7960_43945">
<rect width="180" height="180" fill="white" />
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 294 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 568 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="215" height="48" fill="none"><path fill="#000" d="M57.588 9.6h6L73.828 38h-5.2l-2.36-6.88h-11.36L52.548 38h-5.2l10.24-28.4Zm7.16 17.16-4.16-12.16-4.16 12.16h8.32Zm23.694-2.24c-.186-1.307-.706-2.32-1.56-3.04-.853-.72-1.866-1.08-3.04-1.08-1.68 0-2.986.613-3.92 1.84-.906 1.227-1.36 2.947-1.36 5.16s.454 3.933 1.36 5.16c.934 1.227 2.24 1.84 3.92 1.84 1.254 0 2.307-.373 3.16-1.12.854-.773 1.387-1.867 1.6-3.28l5.12.24c-.186 1.68-.733 3.147-1.64 4.4-.906 1.227-2.08 2.173-3.52 2.84-1.413.667-2.986 1-4.72 1-2.08 0-3.906-.453-5.48-1.36-1.546-.907-2.76-2.2-3.64-3.88-.853-1.68-1.28-3.627-1.28-5.84 0-2.24.427-4.187 1.28-5.84.88-1.68 2.094-2.973 3.64-3.88 1.574-.907 3.4-1.36 5.48-1.36 1.68 0 3.227.32 4.64.96 1.414.64 2.56 1.56 3.44 2.76.907 1.2 1.454 2.6 1.64 4.2l-5.12.28Zm11.486-7.72.12 3.4c.534-1.227 1.307-2.173 2.32-2.84 1.04-.693 2.267-1.04 3.68-1.04 1.494 0 2.76.387 3.8 1.16 1.067.747 1.827 1.813 2.28 3.2.507-1.44 1.294-2.52 2.36-3.24 1.094-.747 2.414-1.12 3.96-1.12 1.414 0 2.64.307 3.68.92s1.84 1.52 2.4 2.72c.56 1.2.84 2.667.84 4.4V38h-4.96V25.92c0-1.813-.293-3.187-.88-4.12-.56-.96-1.413-1.44-2.56-1.44-.906 0-1.68.213-2.32.64-.64.427-1.133 1.053-1.48 1.88-.32.827-.48 1.84-.48 3.04V38h-4.56V25.92c0-1.2-.133-2.213-.4-3.04-.24-.827-.626-1.453-1.16-1.88-.506-.427-1.133-.64-1.88-.64-.906 0-1.68.227-2.32.68-.64.427-1.133 1.053-1.48 1.88-.32.827-.48 1.827-.48 3V38h-4.96V16.8h4.48Zm26.723 10.6c0-2.24.427-4.187 1.28-5.84.854-1.68 2.067-2.973 3.64-3.88 1.574-.907 3.4-1.36 5.48-1.36 1.84 0 3.494.413 4.96 1.24 1.467.827 2.64 2.08 3.52 3.76.88 1.653 1.347 3.693 1.4 6.12v1.32h-15.08c.107 1.813.614 3.227 1.52 4.24.907.987 2.134 1.48 3.68 1.48.987 0 1.88-.253 2.68-.76a4.803 4.803 0 0 0 1.84-2.2l5.08.36c-.64 2.027-1.84 3.64-3.6 4.84-1.733 1.173-3.733 1.76-6 1.76-2.08 0-3.906-.453-5.48-1.36-1.573-.907-2.786-2.2-3.64-3.88-.853-1.68-1.28-3.627-1.28-5.84Zm15.16-2.04c-.213-1.733-.76-3.013-1.64-3.84-.853-.827-1.893-1.24-3.12-1.24-1.44 0-2.6.453-3.48 1.36-.88.88-1.44 2.12-1.68 3.72h9.92ZM163.139 9.6V38h-5.04V9.6h5.04Zm8.322 7.2.24 5.88-.64-.36c.32-2.053 1.094-3.56 2.32-4.52 1.254-.987 2.787-1.48 4.6-1.48 2.32 0 4.107.733 5.36 2.2 1.254 1.44 1.88 3.387 1.88 5.84V38h-4.96V25.92c0-1.253-.12-2.28-.36-3.08-.24-.8-.64-1.413-1.2-1.84-.533-.427-1.253-.64-2.16-.64-1.44 0-2.573.48-3.4 1.44-.8.933-1.2 2.307-1.2 4.12V38h-4.96V16.8h4.48Zm30.003 7.72c-.186-1.307-.706-2.32-1.56-3.04-.853-.72-1.866-1.08-3.04-1.08-1.68 0-2.986.613-3.92 1.84-.906 1.227-1.36 2.947-1.36 5.16s.454 3.933 1.36 5.16c.934 1.227 2.24 1.84 3.92 1.84 1.254 0 2.307-.373 3.16-1.12.854-.773 1.387-1.867 1.6-3.28l5.12.24c-.186 1.68-.733 3.147-1.64 4.4-.906 1.227-2.08 2.173-3.52 2.84-1.413.667-2.986 1-4.72 1-2.08 0-3.906-.453-5.48-1.36-1.546-.907-2.76-2.2-3.64-3.88-.853-1.68-1.28-3.627-1.28-5.84 0-2.24.427-4.187 1.28-5.84.88-1.68 2.094-2.973 3.64-3.88 1.574-.907 3.4-1.36 5.48-1.36 1.68 0 3.227.32 4.64.96 1.414.64 2.56 1.56 3.44 2.76.907 1.2 1.454 2.6 1.64 4.2l-5.12.28Zm11.443 8.16V38h-5.6v-5.32h5.6Z"/><path fill="#171717" fill-rule="evenodd" d="m7.839 40.783 16.03-28.054L20 6 0 40.783h7.839Zm8.214 0H40L27.99 19.894l-4.02 7.032 3.976 6.914H20.02l-3.967 6.943Z" clip-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="1200" fill="none"><rect width="1200" height="1200" fill="#EAEAEA" rx="3"/><g opacity=".5"><g opacity=".5"><path fill="#FAFAFA" d="M600.709 736.5c-75.454 0-136.621-61.167-136.621-136.62 0-75.454 61.167-136.621 136.621-136.621 75.453 0 136.62 61.167 136.62 136.621 0 75.453-61.167 136.62-136.62 136.62Z"/><path stroke="#C9C9C9" stroke-width="2.418" d="M600.709 736.5c-75.454 0-136.621-61.167-136.621-136.62 0-75.454 61.167-136.621 136.621-136.621 75.453 0 136.62 61.167 136.62 136.621 0 75.453-61.167 136.62-136.62 136.62Z"/></g><path stroke="url(#a)" stroke-width="2.418" d="M0-1.209h553.581" transform="scale(1 -1) rotate(45 1163.11 91.165)"/><path stroke="url(#b)" stroke-width="2.418" d="M404.846 598.671h391.726"/><path stroke="url(#c)" stroke-width="2.418" d="M599.5 795.742V404.017"/><path stroke="url(#d)" stroke-width="2.418" d="m795.717 796.597-391.441-391.44"/><path fill="#fff" d="M600.709 656.704c-31.384 0-56.825-25.441-56.825-56.824 0-31.384 25.441-56.825 56.825-56.825 31.383 0 56.824 25.441 56.824 56.825 0 31.383-25.441 56.824-56.824 56.824Z"/><g clip-path="url(#e)"><path fill="#666" fill-rule="evenodd" d="M616.426 586.58h-31.434v16.176l3.553-3.554.531-.531h9.068l.074-.074 8.463-8.463h2.565l7.18 7.181V586.58Zm-15.715 14.654 3.698 3.699 1.283 1.282-2.565 2.565-1.282-1.283-5.2-5.199h-6.066l-5.514 5.514-.073.073v2.876a2.418 2.418 0 0 0 2.418 2.418h26.598a2.418 2.418 0 0 0 2.418-2.418v-8.317l-8.463-8.463-7.181 7.181-.071.072Zm-19.347 5.442v4.085a6.045 6.045 0 0 0 6.046 6.045h26.598a6.044 6.044 0 0 0 6.045-6.045v-7.108l1.356-1.355-1.282-1.283-.074-.073v-17.989h-38.689v23.43l-.146.146.146.147Z" clip-rule="evenodd"/></g><path stroke="#C9C9C9" stroke-width="2.418" d="M600.709 656.704c-31.384 0-56.825-25.441-56.825-56.824 0-31.384 25.441-56.825 56.825-56.825 31.383 0 56.824 25.441 56.824 56.825 0 31.383-25.441 56.824-56.824 56.824Z"/></g><defs><linearGradient id="a" x1="554.061" x2="-.48" y1=".083" y2=".087" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="b" x1="796.912" x2="404.507" y1="599.963" y2="599.965" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="c" x1="600.792" x2="600.794" y1="403.677" y2="796.082" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="d" x1="404.85" x2="796.972" y1="403.903" y2="796.02" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><clipPath id="e"><path fill="#fff" d="M581.364 580.535h38.689v38.689h-38.689z"/></clipPath></defs></svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 KiB

View File

@ -0,0 +1,125 @@
@import 'tailwindcss';
@import 'tw-animate-css';
@custom-variant dark (&:is(.dark *));
:root {
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--card: oklch(1 0 0);
--card-foreground: oklch(0.145 0 0);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.145 0 0);
--primary: oklch(0.205 0 0);
--primary-foreground: oklch(0.985 0 0);
--secondary: oklch(0.97 0 0);
--secondary-foreground: oklch(0.205 0 0);
--muted: oklch(0.97 0 0);
--muted-foreground: oklch(0.556 0 0);
--accent: oklch(0.97 0 0);
--accent-foreground: oklch(0.205 0 0);
--destructive: oklch(0.577 0.245 27.325);
--destructive-foreground: oklch(0.577 0.245 27.325);
--border: oklch(0.922 0 0);
--input: oklch(0.922 0 0);
--ring: oklch(0.708 0 0);
--chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704);
--chart-3: oklch(0.398 0.07 227.392);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);
--radius: 0.625rem;
--sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.145 0 0);
--sidebar-primary: oklch(0.205 0 0);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.97 0 0);
--sidebar-accent-foreground: oklch(0.205 0 0);
--sidebar-border: oklch(0.922 0 0);
--sidebar-ring: oklch(0.708 0 0);
}
.dark {
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
--card: oklch(0.145 0 0);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.145 0 0);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.985 0 0);
--primary-foreground: oklch(0.205 0 0);
--secondary: oklch(0.269 0 0);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.269 0 0);
--muted-foreground: oklch(0.708 0 0);
--accent: oklch(0.269 0 0);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.396 0.141 25.723);
--destructive-foreground: oklch(0.637 0.237 25.331);
--border: oklch(0.269 0 0);
--input: oklch(0.269 0 0);
--ring: oklch(0.439 0 0);
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);
--sidebar: oklch(0.205 0 0);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.269 0 0);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(0.269 0 0);
--sidebar-ring: oklch(0.439 0 0);
}
@theme inline {
--font-sans: 'Geist', 'Geist Fallback';
--font-mono: 'Geist Mono', 'Geist Mono Fallback';
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-destructive-foreground: var(--destructive-foreground);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
--color-chart-1: var(--chart-1);
--color-chart-2: var(--chart-2);
--color-chart-3: var(--chart-3);
--color-chart-4: var(--chart-4);
--color-chart-5: var(--chart-5);
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
--color-sidebar: var(--sidebar);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);
}
@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
}
}

BIN
public/UP1_00010_.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 MiB

BIN
public/UP1_00011_.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 MiB

46
scripts/verify-changes.ts Normal file
View File

@ -0,0 +1,46 @@
import { chromium } from 'playwright';
(async () => {
try {
console.log('Connecting to browser...');
const browser = await chromium.connectOverCDP('http://localhost:9222');
const defaultContext = browser.contexts()[0];
const page = defaultContext ? defaultContext.pages()[0] : await browser.newPage();
if (!page) {
throw new Error('No page found');
}
console.log('Navigating to home...');
await page.goto('http://localhost:3000', { waitUntil: 'networkidle' });
// Desktop Screenshot
console.log('Taking desktop screenshot...');
await page.setViewportSize({ width: 1600, height: 1200 });
// Wait a sec for resizing
await page.waitForTimeout(1000);
await page.screenshot({ path: '.playwright-mcp/08-navigation-desktop-refined.png' });
// Mobile Screenshot
console.log('Taking mobile screenshot...');
await page.setViewportSize({ width: 390, height: 844 });
await page.waitForTimeout(1000);
// Click Menu
const menuButton = page.locator('button[aria-label="Toggle menu"]');
if (await menuButton.isVisible()) {
await menuButton.click();
// Wait for transition
await page.waitForTimeout(1000);
await page.screenshot({ path: '.playwright-mcp/09-navigation-mobile-refined.png' });
} else {
console.error('Menu button not found!');
}
console.log('Done.');
await browser.close();
} catch (error) {
console.error('Error:', error);
process.exit(1);
}
})();

View File

@ -49,12 +49,93 @@ const config: Config = {
'3': 'hsl(var(--chart-3))',
'4': 'hsl(var(--chart-4))',
'5': 'hsl(var(--chart-5))'
}
},
// United Tattoo 2026 Brand Colors
'burnt-orange': '#E67E50',
terracotta: '#D87850',
burnt: '#b0471e',
'sage-concrete': '#7A8B8B',
sage: '#a28f79',
'deep-olive': '#4a4034',
moss: '#6f5c49',
charcoal: '#1c1915',
ink: '#241b16',
cream: '#fff7ec',
sand: '#f2e3d0',
rose: '#e59863'
},
backgroundImage: {
'gradient-radial': 'radial-gradient(circle, var(--tw-gradient-stops))',
'hero-bg': 'linear-gradient(180deg, #7A8B8B 0%, #9CAAA6 45%, #F2E3D0 100%)',
'hero-overlay': 'linear-gradient(135deg, rgba(242, 227, 208, 0.95), rgba(255, 247, 236, 0.9))',
'button-gradient': 'linear-gradient(90deg, #b0471e, #d26a32)',
'burnt-to-rose': 'linear-gradient(90deg, #b0471e, #e59863)',
'card-gradient': 'linear-gradient(135deg, rgba(242, 227, 208, 0.95), rgba(255, 247, 236, 0.9))'
},
boxShadow: {
'sm': '0 4px 12px rgba(31, 27, 23, 0.08)',
'md': '0 12px 28px rgba(31, 27, 23, 0.1)',
'lg': '0 14px 24px rgba(31, 27, 23, 0.18)',
'xl': '0 20px 35px rgba(31, 27, 23, 0.1)',
'2xl': '0 20px 40px rgba(31, 27, 23, 0.2)',
'3xl': '0 25px 40px rgba(31, 27, 23, 0.08)',
'4xl': '0 32px 60px rgba(31, 27, 23, 0.2)',
'5xl': '0 35px 55px rgba(31, 27, 23, 0.18)',
'6xl': '0 40px 70px rgba(31, 27, 23, 0.25)',
'filmic': '0 40px 70px rgba(31, 27, 23, 0.25)',
'button-primary': '0 10px 22px rgba(186, 75, 47, 0.25)',
'button-primary-hover': '0 12px 24px rgba(186, 75, 47, 0.3)',
'button-secondary': '0 10px 22px rgba(216, 120, 80, 0.25)',
'button-secondary-hover': '0 8px 16px rgba(216, 120, 80, 0.3)'
},
borderRadius: {
lg: 'var(--radius)',
md: 'calc(var(--radius) - 2px)',
sm: 'calc(var(--radius) - 4px)'
sm: 'calc(var(--radius) - 4px)',
'xl': '20px',
'2xl': '22px',
'3xl': '24px',
'4xl': '28px',
'5xl': '32px'
},
fontFamily: {
playfair: ['var(--font-playfair)', 'serif'],
grotesk: ['var(--font-grotesk)', 'sans-serif']
},
fontSize: {
'xs': '0.7rem',
'sm': '0.75rem',
'base': '0.95rem',
'md': '1rem',
'lg': '1.1rem',
'xl': '1.2rem',
'2xl': '1.9rem',
'3xl': '2.4rem',
'4xl': '2.5rem',
'5xl': '3rem',
'6xl': '3.8rem'
},
letterSpacing: {
'tighter': '-0.02em',
'wide': '0.05em',
'wider': '0.2em',
'widest': '0.25em',
'very-wide': '0.3em',
'extra-wide': '0.35em'
},
lineHeight: {
'tight': '1.1',
'snug': '1.15',
'normal': '1.5',
'relaxed': '1.6',
'loose': '1.65',
'very-loose': '1.7'
},
backdropBlur: {
'hero': '12px'
},
backdropSaturate: {
'hero': '110%'
}
}
},