diff --git a/app/globals.css b/app/globals.css index 096ff6aff..6c59e13e1 100644 --- a/app/globals.css +++ b/app/globals.css @@ -300,4 +300,18 @@ .font-playfair { font-family: var(--font-playfair); } + + .scrollbar-hide { + -ms-overflow-style: none; + scrollbar-width: none; + } + + .scrollbar-hide::-webkit-scrollbar { + display: none; + } + + .perspective-1000 { + perspective: 1000px; + transform-style: preserve-3d; + } } diff --git a/components/artists-section.tsx b/components/artists-section.tsx index f7e91c54d..39c7f0923 100644 --- a/components/artists-section.tsx +++ b/components/artists-section.tsx @@ -14,7 +14,7 @@ type ArtistGridSet = { items: PublicArtist[] } -const GRID_SIZE = 16 +const GRID_SIZE = 8 const GRID_INTERVAL = 12000 export function ArtistsSection() { @@ -48,6 +48,7 @@ export function ArtistsSection() { const [gridSets, setGridSets] = useState([]) const [activeSetIndex, setActiveSetIndex] = useState(0) const [previousSetIndex, setPreviousSetIndex] = useState(null) + const [centerIndex, setCenterIndex] = useState(0) artistsRef.current = artists gridSetsRef.current = gridSets @@ -117,23 +118,45 @@ export function ArtistsSection() { const next = prev + 1 if (next >= gridSetsRef.current.length) { regenerateSets() + setCenterIndex(0) return 0 } + setCenterIndex(0) return next }) }, [regenerateSets]) + const rotateCarousel = useCallback((direction: 'next' | 'prev') => { + if (gridSetsRef.current.length === 0) return + + const currentSet = gridSetsRef.current[activeSetRef.current] + if (!currentSet) return + + setCenterIndex((prev) => { + const nextIndex = direction === 'next' + ? (prev + 1) % currentSet.items.length + : (prev - 1 + currentSet.items.length) % currentSet.items.length + + // If we've looped back to start on 'next', advance to next set + if (direction === 'next' && nextIndex === 0 && prev === currentSet.items.length - 1) { + setTimeout(() => advanceSet(), 0) + } + + return nextIndex + }) + }, [advanceSet]) + useEffect(() => { if (gridSets.length === 0) { return } const interval = window.setInterval(() => { - advanceSet() + rotateCarousel('next') }, GRID_INTERVAL) return () => window.clearInterval(interval) - }, [advanceSet, gridSets.length]) + }, [rotateCarousel, gridSets.length]) const displayIndices = useMemo(() => { const indices = new Set() @@ -145,15 +168,20 @@ export function ArtistsSection() { }, [activeSetIndex, previousSetIndex]) const getArtistImage = (artist: PublicArtist) => { - const candidate = (artist as any).faceImage || artist.workImages?.[0] - if (candidate) { - return candidate + // Try faceImage from static data first + if ((artist as any).faceImage) { + return (artist as any).faceImage } + // Fall back to first work image + if (artist.workImages && artist.workImages.length > 0) { + return artist.workImages[0] + } + // Final fallback return "/placeholder.svg" } return ( -
+
-
-
-
+
+
+
Resident & Guest Artists

- A Collective of Story-Driven Tattoo Artists + Artists Who Know What They're Doing

- United Tattoo is home to cover-up virtuosos, illustrative explorers, anime specialists, and fine line minimalists. - Every artist curates their chair with intention—offering custom narratives, flash experiments, and collaborative pieces - that evolve with you. + Cover-up specialists, illustrative work, anime, and fine line. Each artist brings years of experience and their own style. + Custom work and flash drops.

-
-
-

What to Expect

-

Consultation-first Process

-

- Artist pairing • Mood-boards • Aftercare guides • CalDAV-synced scheduling -

-
-
-

Specialties

-

Layered Stylescapes

-

- Black & grey realism • Neo-traditional color • Bold cover-ups • Fine line botanicals -

-
-
- -
+
-
-
-
) } diff --git a/components/footer.tsx b/components/footer.tsx index fc8a7ef50..ec6ca8455 100644 --- a/components/footer.tsx +++ b/components/footer.tsx @@ -6,207 +6,205 @@ import { ArrowUp } from "lucide-react" import { Button } from "@/components/ui/button" export function Footer() { - const [showScrollTop, setShowScrollTop] = useState(false) + const [showScrollTop, setShowScrollTop] = useState(false) - useEffect(() => { - const handleScroll = () => { - const scrolled = window.scrollY - const threshold = window.innerHeight * 0.5 - setShowScrollTop(scrolled > threshold) + useEffect(() => { + const handleScroll = () => { + const scrolled = window.scrollY + const threshold = window.innerHeight * 0.5 + setShowScrollTop(scrolled > threshold) + } + + window.addEventListener("scroll", handleScroll) + handleScroll() + return () => window.removeEventListener("scroll", handleScroll) + }, []) + + const scrollToTop = () => { + window.scrollTo({ top: 0, behavior: "smooth" }) } - window.addEventListener("scroll", handleScroll) - handleScroll() - return () => window.removeEventListener("scroll", handleScroll) - }, []) + return ( + <> + - const scrollToTop = () => { - window.scrollTo({ top: 0, behavior: "smooth" }) - } +
+
+
+
+
+ Services +
+
    + {[ + { name: "TRADITIONAL", count: "" }, + { name: "REALISM", count: "" }, + { name: "BLACKWORK", count: "" }, + { name: "FINE LINE", count: "" }, + { name: "ILLUSTRATION", count: "" }, + { name: "ANIME", count: "" }, + ].map((service, index) => ( +
  • + + {service.name} + {service.count && {service.count}} + +
  • + ))} +
+
- return ( - <> - +
+
+ Artists +
+
    + {[ + { name: "CHRISTY_LUMBERG", count: "" }, + { name: "STEVEN_SOLE", count: "" }, + { name: "DONOVAN_L", count: "" }, + { name: "VIEW_ALL", count: "" }, + ].map((artist, index) => ( +
  • + + {artist.name} + {artist.count && {artist.count}} + +
  • + ))} +
+
-
-
-
-
-
- Services -
-
    - {[ - { name: "TRADITIONAL", count: "" }, - { name: "REALISM", count: "" }, - { name: "BLACKWORK", count: "" }, - { name: "FINE LINE", count: "" }, - { name: "WATERCOLOR", count: "" }, - { name: "COVER-UPS", count: "" }, - { name: "ANIME", count: "" }, - ].map((service, index) => ( -
  • - - {service.name} - {service.count && {service.count}} - -
  • - ))} -
-
+
+
+ © L3 INVESTMENTS LLC 2025 — All Rights Reserved +
+
+

5160 Fontaine Blvd

+

Fountain, CO 80817

+ + (719) 698-9004 + +
+
-
-
- Artists -
-
    - {[ - { name: "CHRISTY_LUMBERG", count: "" }, - { name: "STEVEN_SOLE", count: "" }, - { name: "DONOVAN_L", count: "" }, - { name: "VIEW_ALL", count: "" }, - ].map((artist, index) => ( -
  • - - {artist.name} - {artist.count && {artist.count}} - -
  • - ))} -
-
+
+ {/* Legal */} +
+
+ Legal +
+
    +
  • + + AFTERCARE + +
  • +
  • + + DEPOSIT POLICY + +
  • +
  • + + TERMS OF SERVICE + +
  • +
  • + + PRIVACY POLICY + +
  • +
  • + + WAIVER + +
  • +
+
-
-
- © UNITED.TATTOO LLC 2025 — All Rights Reserved -
-
-

5160 Fontaine Blvd

-

Fountain, CO 80817

- - (719) 698-9004 - -
-
+ {/* Social */} +
+
+ Social +
+
    +
  • + + INSTAGRAM + +
  • +
  • + + FACEBOOK + +
  • +
  • + + TIKTOK + +
  • +
+
-
- {/* Legal */} -
-
- Legal + {/* Contact */} +
+
+ Contact +
+ + INFO@UNITED-TATTOO.COM + +
+
+
+ +
+
+
+
-
    -
  • - - AFTERCARE - -
  • -
  • - - DEPOSIT POLICY - -
  • -
  • - - TERMS OF SERVICE - -
  • -
  • - - PRIVACY POLICY - -
  • -
  • - - WAIVER - -
  • -
-
- - {/* Social */} -
-
- Social -
-
    -
  • - - INSTAGRAM - -
  • -
  • - - FACEBOOK - -
  • -
  • - - TIKTOK - -
  • -
-
- - {/* Contact */} -
-
- Contact -
- - INFO@UNITED-TATTOO.COM - -
-
-
- -
-
-
-
-
-
- - ) +
+ + ) } diff --git a/components/hero-section.tsx b/components/hero-section.tsx index c587f95ad..65879e45a 100644 --- a/components/hero-section.tsx +++ b/components/hero-section.tsx @@ -10,166 +10,133 @@ import { useMultiLayerParallax, useReducedMotion } from "@/hooks/use-parallax" import { cn } from "@/lib/utils" export function HeroSection() { - const [isVisible, setIsVisible] = useState(false) - const advancedNavAnimations = useFeatureFlag("ADVANCED_NAV_SCROLL_ANIMATIONS_ENABLED") - const reducedMotion = useReducedMotion() + const [isVisible, setIsVisible] = useState(false) + const advancedNavAnimations = useFeatureFlag("ADVANCED_NAV_SCROLL_ANIMATIONS_ENABLED") + const reducedMotion = useReducedMotion() - const parallax = useMultiLayerParallax(!advancedNavAnimations || reducedMotion) + const parallax = useMultiLayerParallax(!advancedNavAnimations || reducedMotion) - useEffect(() => { - const timer = setTimeout(() => setIsVisible(true), 240) - return () => clearTimeout(timer) - }, []) + useEffect(() => { + const timer = setTimeout(() => setIsVisible(true), 240) + return () => clearTimeout(timer) + }, []) - return ( -
-
+ ) } diff --git a/components/navigation.tsx b/components/navigation.tsx index 22820d38b..93adfc893 100644 --- a/components/navigation.tsx +++ b/components/navigation.tsx @@ -4,7 +4,7 @@ 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 { Menu, X } from "lucide-react" import { Button } from "@/components/ui/button" import { @@ -122,33 +122,22 @@ export function Navigation() { return (