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 bba1bab8c2
commit cd27c7b94e
42 changed files with 631 additions and 334 deletions

2
.gitignore vendored
View File

@ -159,3 +159,5 @@ supabase/.temp/
.claude/** .claude/**
.cursor/** .cursor/**
.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 *)); @custom-variant dark (&:is(.dark *));
:root { :root {
/* Updated color tokens to match United Tattoo design brief */ /* United Tattoo 2026 Design System - Color Tokens */
--background: oklch(1 0 0); /* White */
--foreground: oklch(0.145 0 0); /* Dark Slate Gray */ /* Primary Brand Colors */
--card: oklch(1 0 0); /* Light Gray */ --burnt-orange: #E67E50;
--card-foreground: oklch(0.145 0 0); /* Dark text for cards */ --terracotta: #D87850;
--popover: oklch(1 0 0); /* White */ --burnt: #b0471e;
--popover-foreground: oklch(0.145 0 0); /* Dark text */
--primary: oklch(0.205 0 0); /* Emerald-600 #059669 */ /* Secondary Colors */
--primary-foreground: oklch(0.985 0 0); /* White text on primary */ --sage-concrete: #7A8B8B;
--secondary: oklch(0.97 0 0); /* Emerald accent #10b981 */ --sage: #a28f79;
--secondary-foreground: oklch(0.205 0 0); /* White text on secondary */ --deep-olive: #4a4034;
--muted: oklch(0.97 0 0); /* Light Gray */ --moss: #6f5c49;
--muted-foreground: oklch(0.556 0 0); /* Muted text */
--accent: oklch(0.97 0 0); /* Emerald accent */ /* Neutral Colors */
--accent-foreground: oklch(0.205 0 0); /* White text on accent */ --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: oklch(0.577 0.245 27.325); /* Red for destructive actions */
--destructive-foreground: oklch(0.985 0 0); /* White text */ --destructive-foreground: oklch(0.985 0 0); /* White text */
--border: oklch(0.922 0 0); /* Light border */ --border: oklch(0.60 0.02 150 / 0.2); /* Sage concrete with opacity */
--input: oklch(0.922 0 0); /* Input background */ --input: oklch(0.97 0.015 55); /* Sand input background */
--ring: oklch(0.708 0 0); /* Focus ring */ --ring: oklch(0.67 0.06 45); /* Rose focus ring */
--chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704); /* Chart Colors - Updated to match brand */
--chart-3: oklch(0.398 0.07 227.392); --chart-1: oklch(0.63 0.08 45); /* Terracotta */
--chart-4: oklch(0.828 0.189 84.429); --chart-2: oklch(0.60 0.02 150); /* Sage concrete */
--chart-5: oklch(0.769 0.188 70.08); --chart-3: oklch(0.67 0.06 50); /* Sage */
--radius: 0.625rem; --chart-4: oklch(0.48 0.12 35); /* Burnt */
--sidebar: oklch(0.985 0 0); --chart-5: oklch(0.67 0.06 45); /* Rose */
--sidebar-foreground: oklch(0.145 0 0);
--sidebar-primary: oklch(0.205 0 0); /* Border Radius */
--sidebar-primary-foreground: oklch(0.985 0 0); --radius: 0.75rem;
--sidebar-accent: oklch(0.97 0 0);
--sidebar-accent-foreground: oklch(0.205 0 0); /* Sidebar Colors */
--sidebar-border: oklch(0.922 0 0); --sidebar: oklch(0.98 0.01 60);
--sidebar-ring: oklch(0.708 0 0); --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 { .dark {
@ -76,7 +109,7 @@
} }
@theme inline { @theme inline {
--font-sans: var(--font-source-sans); --font-sans: var(--font-grotesk);
--font-serif: var(--font-playfair); --font-serif: var(--font-playfair);
--font-mono: var(--font-geist-mono); --font-mono: var(--font-geist-mono);
--color-background: var(--background); --color-background: var(--background);
@ -121,8 +154,44 @@
* { * {
@apply border-border outline-ring/50; @apply border-border outline-ring/50;
} }
body { 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 */ /* Added Lenis smooth scrolling styles */

View File

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

View File

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

View File

@ -11,7 +11,7 @@ export function HeroSection() {
const [isVisible, setIsVisible] = useState(false) const [isVisible, setIsVisible] = useState(false)
const advancedNavAnimations = useFeatureFlag("ADVANCED_NAV_SCROLL_ANIMATIONS_ENABLED") const advancedNavAnimations = useFeatureFlag("ADVANCED_NAV_SCROLL_ANIMATIONS_ENABLED")
const reducedMotion = useReducedMotion() const reducedMotion = useReducedMotion()
// Use new parallax system with proper accessibility support // Use new parallax system with proper accessibility support
const parallax = useMultiLayerParallax(!advancedNavAnimations || reducedMotion) const parallax = useMultiLayerParallax(!advancedNavAnimations || reducedMotion)
@ -21,67 +21,107 @@ export function HeroSection() {
}, []) }, [])
return ( return (
<section <section
id="home" id="home"
className="min-h-screen flex items-center justify-center relative overflow-hidden" className="min-h-[67vh] flex items-center justify-center relative overflow-hidden bg-sage-concrete"
data-reduced-motion={reducedMotion} data-reduced-motion={reducedMotion}
> >
{/* Background Layer - Slowest parallax */} {/* Background Layer - New hero image with parallax */}
<div <div
ref={parallax.background.ref} 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={{ 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, ...parallax.background.style,
}} }}
aria-hidden="true" aria-hidden="true"
/> />
{/* Midground Layer - Overlay with subtle parallax */} {/* Midground Layer - Gradient overlay with subtle parallax */}
<div <div
ref={parallax.midground.ref} ref={parallax.midground.ref}
className="absolute inset-0 bg-black/70 will-change-transform" className="absolute inset-0 will-change-transform"
style={parallax.midground.style} style={{
background: "linear-gradient(135deg, rgba(242, 227, 208, 0.3), rgba(255, 247, 236, 0.2))",
...parallax.midground.style,
}}
aria-hidden="true" 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 <div
ref={parallax.foreground.ref} 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} style={parallax.foreground.style}
> >
<div <div
className={cn( className={cn(
"max-w-[620px] mx-auto px-8 py-12 lg:px-14 lg:py-16",
"transition-all duration-1000", "transition-all duration-1000",
isVisible ? "opacity-100 translate-y-0" : "opacity-0 translate-y-8" 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 UNITED TATTOO
</h1> </h1>
</div>
<div {/* Hero Subtitle */}
className={cn( <p
"transition-all duration-1000 delay-300", className="mb-8 font-grotesk leading-loose max-w-[54ch] mx-auto"
isVisible ? "opacity-100 translate-y-0" : "opacity-0 translate-y-8" style={{
)} fontSize: "clamp(0.95rem, 2vw, 1.3rem)",
> lineHeight: "1.65",
<p className="text-xl lg:text-2xl text-gray-200 mb-12 font-light leading-relaxed"> color: "#4a4034", // deep-olive
}}
>
Custom Tattoos in Fountain, Colorado Custom Tattoos in Fountain, Colorado
</p> </p>
</div>
<div {/* CTA Button */}
className={cn(
"transition-all duration-1000 delay-500",
isVisible ? "opacity-100 translate-y-0" : "opacity-0 translate-y-8"
)}
>
<Button <Button
size="lg" 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 Book Consultation
</Button> </Button>

View File

@ -1,256 +1,153 @@
"use client" "use client";
import { useCallback, useEffect, useState } from "react" import Link from "next/link";
import type { MouseEvent } from "react" import { usePathname } from "next/navigation";
import Link from "next/link" import { Menu, X } from "lucide-react";
import { usePathname, useRouter } from "next/navigation" import { useEffect, useState } from "react";
import { ArrowUpRight, Menu, X } from "lucide-react"
import { Button } from "@/components/ui/button"
import { import {
NavigationMenu, NavigationMenu as ShadNavigationMenu,
NavigationMenuItem, NavigationMenuItem,
NavigationMenuLink, NavigationMenuLink,
NavigationMenuList, NavigationMenuList,
} from "@/components/ui/navigation-menu" } from "@/components/ui/navigation-menu";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
type NavItem = { const NAV_ITEMS = [
href: string { name: "Artists", href: "/artists" },
label: string { name: "Your Deposit", href: "/deposit" },
id: string { name: "Aftercare", href: "/aftercare" },
isButton?: boolean { name: "Contact", href: "/contact" },
} ];
const navItems: NavItem[] = [ const CTA_LABEL = "Book";
{ 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)
export function Navigation() { export function Navigation() {
const pathname = usePathname() const pathname = usePathname();
const router = useRouter() const [isScrolled, setIsScrolled] = useState(false);
const [isOpen, setIsOpen] = useState(false) const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
const [isScrolled, setIsScrolled] = useState(false)
const [activeSection, setActiveSection] = useState(scrollTrackedIds[0] ?? "")
const scrollToSection = useCallback( useEffect(() => {
(targetId: string, options?: { href?: string; updateHistory?: boolean }) => { const handleScroll = () => setIsScrolled(window.scrollY > 16);
const target = document.getElementById(targetId)
if (!target) {
return
}
const offset = target.getBoundingClientRect().top + window.scrollY - 80 window.addEventListener("scroll", handleScroll, { passive: true });
window.scrollTo({ top: offset, behavior: "smooth" }) return () => window.removeEventListener("scroll", handleScroll);
}, []);
if (options?.href && options.updateHistory !== false) { useEffect(() => {
window.history.replaceState(null, "", options.href) 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(() => { return (
const handleScroll = () => { <nav
setIsScrolled(window.scrollY > 50) className={cn(
"fixed inset-x-0 top-0 z-50 border-b transition-all duration-500",
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",
isScrolled isScrolled
? "bg-white text-black hover:bg-gray-100 shadow-xl hover:shadow-2xl" ? "bg-sand/95 border-charcoal/10 backdrop-blur-xl shadow-[0_12px_40px_rgba(28,25,21,0.08)]"
: "border border-white/80 bg-transparent text-white shadow-none hover:bg-white/10", : "bg-sage-concrete/10 backdrop-blur-sm border-charcoal/5",
)} )}
> >
<Link href="/book" className="flex items-center gap-2"> <div className="max-w-[1600px] mx-auto px-[clamp(1.5rem,4vw,5rem)]">
<span>Book Now</span> <div className="flex items-center gap-4 py-4">
<ArrowUpRight className="h-4 w-4 transition-transform duration-300 group-hover:translate-x-0.5 group-hover:-translate-y-0.5" /> <Link href="/" className="group">
</Link> <div className="flex flex-col text-left">
</Button> <span
</div> 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 <div className="hidden lg:flex flex-1 justify-center">
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" <ShadNavigationMenu className="bg-transparent text-charcoal" viewport={false}>
onClick={handleToggleMenu} <NavigationMenuList className="gap-8">
aria-label="Toggle menu" {NAV_ITEMS.map((item) => {
> const isActive = pathname === item.href;
{isOpen ? <X size={24} /> : <Menu size={24} />} return (
</button> <NavigationMenuItem key={item.name}>
</div> <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="hidden lg:flex items-center justify-end">
<div className="lg:hidden bg-black/98 backdrop-blur-md border-t border-white/10"> <Link
<div className="px-6 py-8 space-y-5"> href="/book"
<NavigationMenu viewport={false} className="w-full"> 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"
<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",
)}
> >
<Link <span
href={item.href} aria-hidden
onClick={(event) => { className="inline-flex h-2 w-2 rounded-full bg-burnt transition-transform duration-300 group-hover:scale-125"
handleNavClick(event, item) />
handleCloseMenu() <span>{CTA_LABEL}</span>
}} </Link>
> </div>
{item.label}
</Link> <button
</NavigationMenuLink> 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"
</NavigationMenuItem> onClick={() => setMobileMenuOpen((prev) => !prev)}
) aria-label="Toggle menu"
})} aria-expanded={mobileMenuOpen}
</NavigationMenuList> >
</NavigationMenu> {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>
)} );
</div>
</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))', '3': 'hsl(var(--chart-3))',
'4': 'hsl(var(--chart-4))', '4': 'hsl(var(--chart-4))',
'5': 'hsl(var(--chart-5))' '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: { borderRadius: {
lg: 'var(--radius)', lg: 'var(--radius)',
md: 'calc(var(--radius) - 2px)', 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%'
} }
} }
}, },