Added home page.
This commit is contained in:
parent
9af245eadf
commit
8b9721bc5f
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(yarn add:*)",
|
||||
"Bash(yarn build)"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import Link from "next/link";
|
||||
import { ReactNode } from "react";
|
||||
|
||||
interface AnimatedButtonProps {
|
||||
href?: string;
|
||||
onClick?: () => void;
|
||||
children: ReactNode;
|
||||
variant?: "primary" | "secondary" | "accent" | "outline";
|
||||
size?: "sm" | "md" | "lg";
|
||||
className?: string;
|
||||
external?: boolean;
|
||||
icon?: ReactNode;
|
||||
iconPosition?: "left" | "right";
|
||||
}
|
||||
|
||||
export default function AnimatedButton({
|
||||
href,
|
||||
onClick,
|
||||
children,
|
||||
variant = "primary",
|
||||
size = "md",
|
||||
className = "",
|
||||
external = false,
|
||||
icon,
|
||||
iconPosition = "right",
|
||||
}: AnimatedButtonProps) {
|
||||
const baseStyles =
|
||||
"relative inline-flex items-center justify-center gap-2 font-medium rounded-full overflow-hidden transition-all duration-300 cursor-pointer";
|
||||
|
||||
const variants = {
|
||||
primary:
|
||||
"bg-[hsl(0,100%,40%)] text-white hover:bg-[hsl(0,100%,35%)] shadow-lg hover:shadow-xl hover:shadow-[hsl(0,100%,40%)]/25",
|
||||
secondary:
|
||||
"bg-neutral-900 text-white hover:bg-neutral-800 dark:bg-white dark:text-neutral-900 dark:hover:bg-neutral-100",
|
||||
accent:
|
||||
"bg-gradient-to-r from-emerald-600 to-emerald-500 text-white hover:from-emerald-700 hover:to-emerald-600 shadow-lg hover:shadow-xl hover:shadow-emerald-500/25",
|
||||
outline:
|
||||
"bg-transparent border-2 border-neutral-200 text-neutral-900 hover:border-[hsl(0,100%,40%)] hover:text-[hsl(0,100%,40%)] dark:border-neutral-700 dark:text-white dark:hover:border-[hsl(0,100%,50%)] dark:hover:text-[hsl(0,100%,50%)]",
|
||||
};
|
||||
|
||||
const sizes = {
|
||||
sm: "px-4 py-2 text-sm",
|
||||
md: "px-6 py-3 text-base",
|
||||
lg: "px-8 py-4 text-lg",
|
||||
};
|
||||
|
||||
const buttonContent = (
|
||||
<>
|
||||
{icon && iconPosition === "left" && (
|
||||
<motion.span
|
||||
className="inline-flex"
|
||||
whileHover={{ x: -2 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
>
|
||||
{icon}
|
||||
</motion.span>
|
||||
)}
|
||||
<span>{children}</span>
|
||||
{icon && iconPosition === "right" && (
|
||||
<motion.span
|
||||
className="inline-flex"
|
||||
whileHover={{ x: 2 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
>
|
||||
{icon}
|
||||
</motion.span>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
const motionProps = {
|
||||
whileHover: { scale: 1.02, y: -2 },
|
||||
whileTap: { scale: 0.98 },
|
||||
transition: { type: "spring" as const, stiffness: 400, damping: 17 },
|
||||
};
|
||||
|
||||
if (href) {
|
||||
if (external) {
|
||||
return (
|
||||
<motion.a
|
||||
href={href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className={`${baseStyles} ${variants[variant]} ${sizes[size]} ${className}`}
|
||||
{...motionProps}
|
||||
>
|
||||
{buttonContent}
|
||||
</motion.a>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Link href={href} passHref legacyBehavior>
|
||||
<motion.a
|
||||
className={`${baseStyles} ${variants[variant]} ${sizes[size]} ${className}`}
|
||||
{...motionProps}
|
||||
>
|
||||
{buttonContent}
|
||||
</motion.a>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<motion.button
|
||||
onClick={onClick}
|
||||
className={`${baseStyles} ${variants[variant]} ${sizes[size]} ${className}`}
|
||||
{...motionProps}
|
||||
>
|
||||
{buttonContent}
|
||||
</motion.button>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import { ReactNode } from "react";
|
||||
|
||||
interface FadeInProps {
|
||||
children: ReactNode;
|
||||
delay?: number;
|
||||
duration?: number;
|
||||
direction?: "up" | "down" | "left" | "right" | "none";
|
||||
distance?: number;
|
||||
className?: string;
|
||||
once?: boolean;
|
||||
}
|
||||
|
||||
export default function FadeIn({
|
||||
children,
|
||||
delay = 0,
|
||||
duration = 0.6,
|
||||
direction = "up",
|
||||
distance = 30,
|
||||
className = "",
|
||||
once = true,
|
||||
}: FadeInProps) {
|
||||
const directions = {
|
||||
up: { y: distance, x: 0 },
|
||||
down: { y: -distance, x: 0 },
|
||||
left: { x: distance, y: 0 },
|
||||
right: { x: -distance, y: 0 },
|
||||
none: { x: 0, y: 0 },
|
||||
};
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
initial={{
|
||||
opacity: 0,
|
||||
...directions[direction],
|
||||
}}
|
||||
whileInView={{
|
||||
opacity: 1,
|
||||
x: 0,
|
||||
y: 0,
|
||||
}}
|
||||
viewport={{ once, margin: "-50px" }}
|
||||
transition={{
|
||||
duration,
|
||||
delay,
|
||||
ease: [0.16, 1, 0.3, 1],
|
||||
}}
|
||||
className={className}
|
||||
>
|
||||
{children}
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
||||
interface StaggerContainerProps {
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
staggerDelay?: number;
|
||||
}
|
||||
|
||||
export function StaggerContainer({
|
||||
children,
|
||||
className = "",
|
||||
staggerDelay = 0.1,
|
||||
}: StaggerContainerProps) {
|
||||
return (
|
||||
<motion.div
|
||||
initial="hidden"
|
||||
whileInView="visible"
|
||||
viewport={{ once: true, margin: "-50px" }}
|
||||
variants={{
|
||||
hidden: {},
|
||||
visible: {
|
||||
transition: {
|
||||
staggerChildren: staggerDelay,
|
||||
},
|
||||
},
|
||||
}}
|
||||
className={className}
|
||||
>
|
||||
{children}
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
||||
interface StaggerItemProps {
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function StaggerItem({ children, className = "" }: StaggerItemProps) {
|
||||
return (
|
||||
<motion.div
|
||||
variants={{
|
||||
hidden: { opacity: 0, y: 30 },
|
||||
visible: {
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
transition: {
|
||||
duration: 0.6,
|
||||
ease: [0.16, 1, 0.3, 1],
|
||||
},
|
||||
},
|
||||
}}
|
||||
className={className}
|
||||
>
|
||||
{children}
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,231 @@
|
|||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { MapPinIcon, PhoneIcon, MailIcon } from "./Icons";
|
||||
|
||||
const footerLinks = {
|
||||
company: [
|
||||
{ href: "/what-we-do", label: "What We Do" },
|
||||
{ href: "/culture", label: "Our Culture" },
|
||||
{ href: "/contact", label: "Contact Us" },
|
||||
],
|
||||
products: [
|
||||
{ href: "/products", label: "All Products" },
|
||||
{ href: "/sustainability", label: "Sustainability" },
|
||||
{ href: "/state-ordinances", label: "State Ordinances" },
|
||||
],
|
||||
categories: [
|
||||
{ href: "/products#packaging", label: "Food Packaging" },
|
||||
{ href: "/products#green", label: "Green Products" },
|
||||
{ href: "/products#janitorial", label: "Janitorial" },
|
||||
],
|
||||
};
|
||||
|
||||
const territories = [
|
||||
"Colorado",
|
||||
"Washington",
|
||||
"Oregon",
|
||||
"Idaho",
|
||||
"Utah",
|
||||
"Wyoming",
|
||||
"Montana",
|
||||
"New Mexico",
|
||||
"Nebraska",
|
||||
"Arizona",
|
||||
];
|
||||
|
||||
export default function Footer() {
|
||||
return (
|
||||
<footer className="relative bg-neutral-950 text-white overflow-hidden">
|
||||
{/* Decorative gradient */}
|
||||
<div className="absolute top-0 left-0 right-0 h-px bg-gradient-to-r from-transparent via-[hsl(0,100%,40%)] to-transparent" />
|
||||
|
||||
{/* Background decoration */}
|
||||
<div className="absolute top-0 right-0 w-[600px] h-[600px] bg-[hsl(0,100%,40%)] rounded-full filter blur-[200px] opacity-5 -translate-y-1/2 translate-x-1/2" />
|
||||
|
||||
<div className="relative max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
{/* Main Footer Content */}
|
||||
<div className="py-16 lg:py-20">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-5 gap-12 lg:gap-8">
|
||||
{/* Brand Column */}
|
||||
<div className="lg:col-span-2">
|
||||
<Link href="/" className="inline-block">
|
||||
<motion.div
|
||||
whileHover={{ scale: 1.05 }}
|
||||
className="flex items-center gap-3"
|
||||
>
|
||||
<Image
|
||||
src="/assets/brand-logo.png"
|
||||
alt="TCM Sales & Marketing"
|
||||
width={50}
|
||||
height={50}
|
||||
className="w-12 h-12 object-contain brightness-0 invert"
|
||||
/>
|
||||
<div>
|
||||
<span className="font-bold text-xl">TCM</span>
|
||||
<span className="text-[hsl(0,100%,50%)] font-bold text-xl">
|
||||
{" "}
|
||||
Sales & Marketing
|
||||
</span>
|
||||
</div>
|
||||
</motion.div>
|
||||
</Link>
|
||||
|
||||
<p className="mt-6 text-neutral-400 max-w-md leading-relaxed">
|
||||
Industry trusted since 2005. Your partner for foodservice and
|
||||
janitorial solutions across the Rocky Mountain and Northwest
|
||||
regions.
|
||||
</p>
|
||||
|
||||
{/* Contact Info */}
|
||||
<div className="mt-8 space-y-4">
|
||||
<motion.a
|
||||
href="tel:303-371-2810"
|
||||
whileHover={{ x: 5 }}
|
||||
className="flex items-center gap-3 text-neutral-400 hover:text-white transition-colors group"
|
||||
>
|
||||
<span className="p-2 rounded-lg bg-white/5 group-hover:bg-[hsl(0,100%,40%)] transition-colors">
|
||||
<PhoneIcon size={18} />
|
||||
</span>
|
||||
<span>303-371-2810</span>
|
||||
</motion.a>
|
||||
|
||||
<motion.a
|
||||
href="mailto:info@tcm-sales.com"
|
||||
whileHover={{ x: 5 }}
|
||||
className="flex items-center gap-3 text-neutral-400 hover:text-white transition-colors group"
|
||||
>
|
||||
<span className="p-2 rounded-lg bg-white/5 group-hover:bg-[hsl(0,100%,40%)] transition-colors">
|
||||
<MailIcon size={18} />
|
||||
</span>
|
||||
<span>info@tcm-sales.com</span>
|
||||
</motion.a>
|
||||
|
||||
<motion.div
|
||||
whileHover={{ x: 5 }}
|
||||
className="flex items-start gap-3 text-neutral-400 group"
|
||||
>
|
||||
<span className="p-2 rounded-lg bg-white/5">
|
||||
<MapPinIcon size={18} />
|
||||
</span>
|
||||
<span className="leading-relaxed">
|
||||
12424 East Weaver Place
|
||||
<br />
|
||||
Centennial, CO 80111
|
||||
</span>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Links Columns */}
|
||||
<div>
|
||||
<h4 className="font-semibold text-white mb-4">Company</h4>
|
||||
<ul className="space-y-3">
|
||||
{footerLinks.company.map((link) => (
|
||||
<li key={link.href}>
|
||||
<Link href={link.href}>
|
||||
<motion.span
|
||||
whileHover={{ x: 5, color: "#ff4d4d" }}
|
||||
className="text-neutral-400 hover:text-white transition-colors cursor-pointer inline-block"
|
||||
>
|
||||
{link.label}
|
||||
</motion.span>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 className="font-semibold text-white mb-4">Products</h4>
|
||||
<ul className="space-y-3">
|
||||
{footerLinks.products.map((link) => (
|
||||
<li key={link.href}>
|
||||
<Link href={link.href}>
|
||||
<motion.span
|
||||
whileHover={{ x: 5, color: "#ff4d4d" }}
|
||||
className="text-neutral-400 hover:text-white transition-colors cursor-pointer inline-block"
|
||||
>
|
||||
{link.label}
|
||||
</motion.span>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 className="font-semibold text-white mb-4">Categories</h4>
|
||||
<ul className="space-y-3">
|
||||
{footerLinks.categories.map((link) => (
|
||||
<li key={link.href}>
|
||||
<Link href={link.href}>
|
||||
<motion.span
|
||||
whileHover={{ x: 5, color: "#ff4d4d" }}
|
||||
className="text-neutral-400 hover:text-white transition-colors cursor-pointer inline-block"
|
||||
>
|
||||
{link.label}
|
||||
</motion.span>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Territory Tags */}
|
||||
<div className="mt-12 pt-8 border-t border-white/10">
|
||||
<p className="text-sm text-neutral-500 mb-4">
|
||||
Proudly serving the Rocky Mountain & Northwest regions:
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{territories.map((territory, index) => (
|
||||
<motion.span
|
||||
key={territory}
|
||||
initial={{ opacity: 0, scale: 0.8 }}
|
||||
whileInView={{ opacity: 1, scale: 1 }}
|
||||
transition={{ delay: index * 0.05 }}
|
||||
whileHover={{
|
||||
scale: 1.05,
|
||||
backgroundColor: "hsl(0, 100%, 40%)",
|
||||
}}
|
||||
className="px-3 py-1 text-xs font-medium text-neutral-400 bg-white/5 rounded-full cursor-default transition-colors hover:text-white"
|
||||
>
|
||||
{territory}
|
||||
</motion.span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom Bar */}
|
||||
<div className="py-6 border-t border-white/10 flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||
<p className="text-sm text-neutral-500">
|
||||
© {new Date().getFullYear()} TCM Sales & Marketing. All rights
|
||||
reserved.
|
||||
</p>
|
||||
<div className="flex items-center gap-6">
|
||||
<Link href="/privacy">
|
||||
<motion.span
|
||||
whileHover={{ color: "#fff" }}
|
||||
className="text-sm text-neutral-500 cursor-pointer"
|
||||
>
|
||||
Privacy Policy
|
||||
</motion.span>
|
||||
</Link>
|
||||
<Link href="/terms">
|
||||
<motion.span
|
||||
whileHover={{ color: "#fff" }}
|
||||
className="text-sm text-neutral-500 cursor-pointer"
|
||||
>
|
||||
Terms of Service
|
||||
</motion.span>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,310 @@
|
|||
interface IconProps {
|
||||
className?: string;
|
||||
size?: number;
|
||||
}
|
||||
|
||||
export function ArrowRightIcon({ className = "", size = 20 }: IconProps) {
|
||||
return (
|
||||
<svg
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className={className}
|
||||
>
|
||||
<path d="M5 12h14M12 5l7 7-7 7" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function CheckIcon({ className = "", size = 20 }: IconProps) {
|
||||
return (
|
||||
<svg
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className={className}
|
||||
>
|
||||
<path d="M20 6L9 17l-5-5" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function MapPinIcon({ className = "", size = 20 }: IconProps) {
|
||||
return (
|
||||
<svg
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className={className}
|
||||
>
|
||||
<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0118 0z" />
|
||||
<circle cx="12" cy="10" r="3" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function PhoneIcon({ className = "", size = 20 }: IconProps) {
|
||||
return (
|
||||
<svg
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className={className}
|
||||
>
|
||||
<path d="M22 16.92v3a2 2 0 01-2.18 2 19.79 19.79 0 01-8.63-3.07 19.5 19.5 0 01-6-6 19.79 19.79 0 01-3.07-8.67A2 2 0 014.11 2h3a2 2 0 012 1.72 12.84 12.84 0 00.7 2.81 2 2 0 01-.45 2.11L8.09 9.91a16 16 0 006 6l1.27-1.27a2 2 0 012.11-.45 12.84 12.84 0 002.81.7A2 2 0 0122 16.92z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function MailIcon({ className = "", size = 20 }: IconProps) {
|
||||
return (
|
||||
<svg
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className={className}
|
||||
>
|
||||
<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z" />
|
||||
<polyline points="22,6 12,13 2,6" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function LeafIcon({ className = "", size = 20 }: IconProps) {
|
||||
return (
|
||||
<svg
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className={className}
|
||||
>
|
||||
<path d="M11 20A7 7 0 019.8 6.1C15.5 5 17 4.48 19 2c1 2 2 4.18 2 8 0 5.5-4.78 10-10 10z" />
|
||||
<path d="M2 21c0-3 1.85-5.36 5.08-6C9.5 14.52 12 13 13 12" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function PackageIcon({ className = "", size = 20 }: IconProps) {
|
||||
return (
|
||||
<svg
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className={className}
|
||||
>
|
||||
<path d="M16.5 9.4l-9-5.19M21 16V8a2 2 0 00-1-1.73l-7-4a2 2 0 00-2 0l-7 4A2 2 0 003 8v8a2 2 0 001 1.73l7 4a2 2 0 002 0l7-4A2 2 0 0021 16z" />
|
||||
<polyline points="3.27 6.96 12 12.01 20.73 6.96" />
|
||||
<line x1="12" y1="22.08" x2="12" y2="12" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function UsersIcon({ className = "", size = 20 }: IconProps) {
|
||||
return (
|
||||
<svg
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className={className}
|
||||
>
|
||||
<path d="M17 21v-2a4 4 0 00-4-4H5a4 4 0 00-4 4v2" />
|
||||
<circle cx="9" cy="7" r="4" />
|
||||
<path d="M23 21v-2a4 4 0 00-3-3.87M16 3.13a4 4 0 010 7.75" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function TruckIcon({ className = "", size = 20 }: IconProps) {
|
||||
return (
|
||||
<svg
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className={className}
|
||||
>
|
||||
<rect x="1" y="3" width="15" height="13" />
|
||||
<polygon points="16 8 20 8 23 11 23 16 16 16 16 8" />
|
||||
<circle cx="5.5" cy="18.5" r="2.5" />
|
||||
<circle cx="18.5" cy="18.5" r="2.5" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function TargetIcon({ className = "", size = 20 }: IconProps) {
|
||||
return (
|
||||
<svg
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className={className}
|
||||
>
|
||||
<circle cx="12" cy="12" r="10" />
|
||||
<circle cx="12" cy="12" r="6" />
|
||||
<circle cx="12" cy="12" r="2" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function HeartIcon({ className = "", size = 20 }: IconProps) {
|
||||
return (
|
||||
<svg
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className={className}
|
||||
>
|
||||
<path d="M20.84 4.61a5.5 5.5 0 00-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 00-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 000-7.78z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function StarIcon({ className = "", size = 20 }: IconProps) {
|
||||
return (
|
||||
<svg
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className={className}
|
||||
>
|
||||
<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function BuildingIcon({ className = "", size = 20 }: IconProps) {
|
||||
return (
|
||||
<svg
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className={className}
|
||||
>
|
||||
<rect x="4" y="2" width="16" height="20" rx="2" ry="2" />
|
||||
<path d="M9 22v-4h6v4M8 6h.01M16 6h.01M12 6h.01M12 10h.01M12 14h.01M16 10h.01M16 14h.01M8 10h.01M8 14h.01" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function SparklesIcon({ className = "", size = 20 }: IconProps) {
|
||||
return (
|
||||
<svg
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className={className}
|
||||
>
|
||||
<path d="M12 3l1.912 5.813a2 2 0 001.275 1.275L21 12l-5.813 1.912a2 2 0 00-1.275 1.275L12 21l-1.912-5.813a2 2 0 00-1.275-1.275L3 12l5.813-1.912a2 2 0 001.275-1.275L12 3z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function ChevronDownIcon({ className = "", size = 20 }: IconProps) {
|
||||
return (
|
||||
<svg
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className={className}
|
||||
>
|
||||
<polyline points="6 9 12 15 18 9" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function RecycleIcon({ className = "", size = 20 }: IconProps) {
|
||||
return (
|
||||
<svg
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className={className}
|
||||
>
|
||||
<path d="M7 19H4.815a1.83 1.83 0 01-1.57-.881 1.785 1.785 0 01-.004-1.784L7.196 9.5" />
|
||||
<path d="M11 19h8.203a1.83 1.83 0 001.556-.89 1.784 1.784 0 00-.001-1.78l-1.758-3.03" />
|
||||
<path d="M14 16l3 3-3 3" />
|
||||
<path d="M8.293 13.596L4.5 9.5l4.5-4" />
|
||||
<path d="M9.344 5.811l1.093-1.892A1.83 1.83 0 0112 3c.617 0 1.193.31 1.544.837l4.26 7.357" />
|
||||
<path d="M13.378 9.633L11.5 5.5 16 2" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,200 @@
|
|||
"use client";
|
||||
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { useState, useEffect } from "react";
|
||||
import AnimatedButton from "./AnimatedButton";
|
||||
|
||||
const navLinks = [
|
||||
{ href: "/", label: "Home" },
|
||||
{ href: "/what-we-do", label: "What We Do" },
|
||||
{ href: "/products", label: "Products" },
|
||||
{ href: "/sustainability", label: "Sustainability" },
|
||||
{ href: "/culture", label: "Our Culture" },
|
||||
];
|
||||
|
||||
export default function Navigation() {
|
||||
const [isScrolled, setIsScrolled] = useState(false);
|
||||
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const handleScroll = () => {
|
||||
setIsScrolled(window.scrollY > 20);
|
||||
};
|
||||
|
||||
window.addEventListener("scroll", handleScroll);
|
||||
return () => window.removeEventListener("scroll", handleScroll);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<motion.header
|
||||
initial={{ y: -100 }}
|
||||
animate={{ y: 0 }}
|
||||
transition={{ duration: 0.6, ease: [0.16, 1, 0.3, 1] }}
|
||||
className={`fixed top-0 left-0 right-0 z-50 transition-all duration-500 ${
|
||||
isScrolled
|
||||
? "bg-white/90 dark:bg-neutral-950/90 backdrop-blur-xl shadow-lg shadow-black/5"
|
||||
: "bg-transparent"
|
||||
}`}
|
||||
>
|
||||
<nav className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="flex items-center justify-between h-20">
|
||||
{/* Logo */}
|
||||
<Link href="/" className="relative z-10">
|
||||
<motion.div
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
transition={{ type: "spring", stiffness: 400, damping: 17 }}
|
||||
className="flex items-center gap-3"
|
||||
>
|
||||
<Image
|
||||
src="/assets/brand-logo.png"
|
||||
alt="TCM Sales & Marketing"
|
||||
width={50}
|
||||
height={50}
|
||||
className="w-12 h-12 object-contain"
|
||||
/>
|
||||
<div className="hidden sm:block">
|
||||
<span className="font-bold text-xl text-neutral-900 dark:text-white">
|
||||
TCM
|
||||
</span>
|
||||
<span className="text-[hsl(0,100%,40%)] font-bold text-xl">
|
||||
{" "}
|
||||
Sales
|
||||
</span>
|
||||
</div>
|
||||
</motion.div>
|
||||
</Link>
|
||||
|
||||
{/* Desktop Navigation */}
|
||||
<div className="hidden lg:flex items-center gap-1">
|
||||
{navLinks.map((link) => (
|
||||
<Link key={link.href} href={link.href}>
|
||||
<motion.span
|
||||
className="relative px-4 py-2 text-neutral-600 dark:text-neutral-300 font-medium rounded-full transition-colors hover:text-neutral-900 dark:hover:text-white cursor-pointer"
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
>
|
||||
{link.label}
|
||||
<motion.span
|
||||
className="absolute bottom-0 left-1/2 -translate-x-1/2 w-1 h-1 bg-[hsl(0,100%,40%)] rounded-full opacity-0"
|
||||
whileHover={{ opacity: 1, scale: 1.5 }}
|
||||
/>
|
||||
</motion.span>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* CTA Button */}
|
||||
<div className="hidden lg:block">
|
||||
<AnimatedButton href="/contact" size="sm">
|
||||
Contact Us
|
||||
</AnimatedButton>
|
||||
</div>
|
||||
|
||||
{/* Mobile Menu Button */}
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.1 }}
|
||||
whileTap={{ scale: 0.9 }}
|
||||
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
|
||||
className="lg:hidden relative z-10 p-2 text-neutral-900 dark:text-white"
|
||||
>
|
||||
<div className="w-6 h-5 flex flex-col justify-between">
|
||||
<motion.span
|
||||
animate={{
|
||||
rotate: isMobileMenuOpen ? 45 : 0,
|
||||
y: isMobileMenuOpen ? 8 : 0,
|
||||
}}
|
||||
className="w-full h-0.5 bg-current origin-left transition-colors"
|
||||
/>
|
||||
<motion.span
|
||||
animate={{
|
||||
opacity: isMobileMenuOpen ? 0 : 1,
|
||||
x: isMobileMenuOpen ? -20 : 0,
|
||||
}}
|
||||
className="w-full h-0.5 bg-current transition-colors"
|
||||
/>
|
||||
<motion.span
|
||||
animate={{
|
||||
rotate: isMobileMenuOpen ? -45 : 0,
|
||||
y: isMobileMenuOpen ? -8 : 0,
|
||||
}}
|
||||
className="w-full h-0.5 bg-current origin-left transition-colors"
|
||||
/>
|
||||
</div>
|
||||
</motion.button>
|
||||
</div>
|
||||
</nav>
|
||||
</motion.header>
|
||||
|
||||
{/* Mobile Menu */}
|
||||
<AnimatePresence>
|
||||
{isMobileMenuOpen && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
className="fixed inset-0 z-40 lg:hidden"
|
||||
>
|
||||
{/* Backdrop */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="absolute inset-0 bg-black/50 backdrop-blur-sm"
|
||||
onClick={() => setIsMobileMenuOpen(false)}
|
||||
/>
|
||||
|
||||
{/* Menu Panel */}
|
||||
<motion.div
|
||||
initial={{ x: "100%" }}
|
||||
animate={{ x: 0 }}
|
||||
exit={{ x: "100%" }}
|
||||
transition={{ type: "spring", damping: 25, stiffness: 200 }}
|
||||
className="absolute right-0 top-0 bottom-0 w-full max-w-sm bg-white dark:bg-neutral-950 shadow-2xl"
|
||||
>
|
||||
<div className="flex flex-col h-full pt-24 pb-8 px-6">
|
||||
<nav className="flex-1 space-y-2">
|
||||
{navLinks.map((link, index) => (
|
||||
<motion.div
|
||||
key={link.href}
|
||||
initial={{ opacity: 0, x: 20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ delay: index * 0.1 }}
|
||||
>
|
||||
<Link
|
||||
href={link.href}
|
||||
onClick={() => setIsMobileMenuOpen(false)}
|
||||
className="block py-4 text-2xl font-medium text-neutral-900 dark:text-white hover:text-[hsl(0,100%,40%)] transition-colors border-b border-neutral-100 dark:border-neutral-800"
|
||||
>
|
||||
{link.label}
|
||||
</Link>
|
||||
</motion.div>
|
||||
))}
|
||||
</nav>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.5 }}
|
||||
>
|
||||
<AnimatedButton
|
||||
href="/contact"
|
||||
size="lg"
|
||||
className="w-full justify-center"
|
||||
onClick={() => setIsMobileMenuOpen(false)}
|
||||
>
|
||||
Contact Us
|
||||
</AnimatedButton>
|
||||
</motion.div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</>
|
||||
);
|
||||
}
|
||||
432
app/globals.css
432
app/globals.css
|
|
@ -1,8 +1,75 @@
|
|||
@import "tailwindcss";
|
||||
|
||||
:root {
|
||||
/* Brand colors - TCM Red (hsl(0, 100%, 40%) as base) */
|
||||
--brand-50: hsl(0, 100%, 97%);
|
||||
--brand-100: hsl(0, 100%, 94%);
|
||||
--brand-200: hsl(0, 100%, 87%);
|
||||
--brand-300: hsl(0, 100%, 75%);
|
||||
--brand-400: hsl(0, 100%, 60%);
|
||||
--brand-500: hsl(0, 100%, 50%);
|
||||
--brand-600: hsl(0, 100%, 40%);
|
||||
--brand-700: hsl(0, 100%, 33%);
|
||||
--brand-800: hsl(0, 100%, 27%);
|
||||
--brand-900: hsl(0, 100%, 22%);
|
||||
--brand-950: hsl(0, 100%, 12%);
|
||||
|
||||
/* Accent colors - Vibrant emerald for sustainability */
|
||||
--accent-50: #ecfdf5;
|
||||
--accent-100: #d1fae5;
|
||||
--accent-200: #a7f3d0;
|
||||
--accent-300: #6ee7b7;
|
||||
--accent-400: #34d399;
|
||||
--accent-500: #10b981;
|
||||
--accent-600: #059669;
|
||||
--accent-700: #047857;
|
||||
--accent-800: #065f46;
|
||||
--accent-900: #064e3b;
|
||||
|
||||
/* Neutral colors */
|
||||
--neutral-50: #fafafa;
|
||||
--neutral-100: #f5f5f5;
|
||||
--neutral-200: #e5e5e5;
|
||||
--neutral-300: #d4d4d4;
|
||||
--neutral-400: #a3a3a3;
|
||||
--neutral-500: #737373;
|
||||
--neutral-600: #525252;
|
||||
--neutral-700: #404040;
|
||||
--neutral-800: #262626;
|
||||
--neutral-900: #171717;
|
||||
--neutral-950: #0a0a0a;
|
||||
|
||||
/* Semantic colors */
|
||||
--background: #ffffff;
|
||||
--foreground: #171717;
|
||||
--muted: #f5f5f5;
|
||||
--muted-foreground: #737373;
|
||||
--border: #e5e5e5;
|
||||
--ring: hsl(0, 100%, 40%);
|
||||
|
||||
/* Gradients */
|
||||
--gradient-brand: linear-gradient(135deg, hsl(0, 100%, 33%) 0%, hsl(0, 100%, 40%) 50%, hsl(0, 100%, 50%) 100%);
|
||||
--gradient-brand-reverse: linear-gradient(135deg, hsl(0, 100%, 50%) 0%, hsl(0, 100%, 40%) 100%);
|
||||
--gradient-accent: linear-gradient(135deg, #047857 0%, #10b981 50%, #34d399 100%);
|
||||
--gradient-dark: linear-gradient(135deg, #0a0a0a 0%, #262626 100%);
|
||||
--gradient-hero: linear-gradient(180deg, hsl(0, 50%, 98%) 0%, #ffffff 50%, hsl(0, 20%, 97%) 100%);
|
||||
--gradient-warm: linear-gradient(135deg, hsl(0, 100%, 40%) 0%, hsl(20, 100%, 50%) 100%);
|
||||
|
||||
/* Shadows */
|
||||
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
|
||||
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
|
||||
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
|
||||
--shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
|
||||
--shadow-2xl: 0 25px 50px -12px rgb(0 0 0 / 0.25);
|
||||
--shadow-glow: 0 0 40px -10px hsl(0, 100%, 40%);
|
||||
--shadow-glow-accent: 0 0 40px -10px rgb(16 185 129 / 0.5);
|
||||
--shadow-brand: 0 4px 14px 0 hsl(0 100% 40% / 0.35);
|
||||
|
||||
/* Animation timing */
|
||||
--ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
|
||||
--ease-out-quart: cubic-bezier(0.25, 1, 0.5, 1);
|
||||
--ease-in-out-circ: cubic-bezier(0.85, 0, 0.15, 1);
|
||||
--ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
|
|
@ -15,12 +82,373 @@
|
|||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--background: #0a0a0a;
|
||||
--foreground: #ededed;
|
||||
--foreground: #fafafa;
|
||||
--muted: #262626;
|
||||
--muted-foreground: #a3a3a3;
|
||||
--border: #404040;
|
||||
}
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
body {
|
||||
background: var(--background);
|
||||
color: var(--foreground);
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
font-family: var(--font-sans), system-ui, -apple-system, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* Selection styling */
|
||||
::selection {
|
||||
background-color: hsl(0, 100%, 40%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Focus styling */
|
||||
:focus-visible {
|
||||
outline: 2px solid var(--ring);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
/* Custom scrollbar */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: var(--muted);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: linear-gradient(180deg, hsl(0, 100%, 50%) 0%, hsl(0, 100%, 40%) 100%);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: linear-gradient(180deg, hsl(0, 100%, 55%) 0%, hsl(0, 100%, 45%) 100%);
|
||||
}
|
||||
|
||||
/* Utility classes for animations */
|
||||
.animate-fade-in {
|
||||
animation: fadeIn 0.6s var(--ease-out-expo) forwards;
|
||||
}
|
||||
|
||||
.animate-slide-up {
|
||||
animation: slideUp 0.6s var(--ease-out-expo) forwards;
|
||||
}
|
||||
|
||||
.animate-scale-in {
|
||||
animation: scaleIn 0.5s var(--ease-out-expo) forwards;
|
||||
}
|
||||
|
||||
.animate-float {
|
||||
animation: float 6s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.animate-pulse-glow {
|
||||
animation: pulseGlow 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes scaleIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale(0.95);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes float {
|
||||
0%, 100% {
|
||||
transform: translateY(0px);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-20px);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulseGlow {
|
||||
0%, 100% {
|
||||
box-shadow: 0 0 20px hsl(0 100% 40% / 0.3);
|
||||
}
|
||||
50% {
|
||||
box-shadow: 0 0 40px hsl(0 100% 40% / 0.6);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
0% {
|
||||
background-position: -200% 0;
|
||||
}
|
||||
100% {
|
||||
background-position: 200% 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Gradient text utility */
|
||||
.gradient-text {
|
||||
background: var(--gradient-brand);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.gradient-text-accent {
|
||||
background: var(--gradient-accent);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
/* Glass morphism effect */
|
||||
.glass {
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.glass {
|
||||
background: rgba(10, 10, 10, 0.8);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Noise texture overlay */
|
||||
.noise-overlay::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)'/%3E%3C/svg%3E");
|
||||
opacity: 0.02;
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* Animated underline for links */
|
||||
.animated-underline {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.animated-underline::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: -2px;
|
||||
left: 0;
|
||||
width: 0;
|
||||
height: 2px;
|
||||
background: var(--gradient-brand);
|
||||
transition: width 0.3s var(--ease-out-expo);
|
||||
}
|
||||
|
||||
.animated-underline:hover::after {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Shimmer effect for loading states */
|
||||
.shimmer {
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
transparent 0%,
|
||||
rgba(255, 255, 255, 0.4) 50%,
|
||||
transparent 100%
|
||||
);
|
||||
background-size: 200% 100%;
|
||||
animation: shimmer 1.5s infinite;
|
||||
}
|
||||
|
||||
/* Card hover effect */
|
||||
.card-hover {
|
||||
transition: all 0.4s var(--ease-out-expo);
|
||||
}
|
||||
|
||||
.card-hover:hover {
|
||||
transform: translateY(-8px);
|
||||
box-shadow: var(--shadow-2xl);
|
||||
}
|
||||
|
||||
/* Interactive card with border glow */
|
||||
.card-glow {
|
||||
position: relative;
|
||||
transition: all 0.4s var(--ease-out-expo);
|
||||
}
|
||||
|
||||
.card-glow::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: -2px;
|
||||
background: var(--gradient-brand);
|
||||
border-radius: inherit;
|
||||
opacity: 0;
|
||||
z-index: -1;
|
||||
transition: opacity 0.4s var(--ease-out-expo);
|
||||
}
|
||||
|
||||
.card-glow:hover::before {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.card-glow:hover {
|
||||
transform: translateY(-4px);
|
||||
}
|
||||
|
||||
/* Magnetic button effect helper */
|
||||
.magnetic {
|
||||
transition: transform 0.3s var(--ease-out-expo);
|
||||
}
|
||||
|
||||
/* Icon hover rotation */
|
||||
.icon-spin {
|
||||
transition: transform 0.5s var(--ease-spring);
|
||||
}
|
||||
|
||||
.icon-spin:hover {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
|
||||
/* Section padding utility */
|
||||
.section-padding {
|
||||
padding: 5rem 1.5rem;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.section-padding {
|
||||
padding: 7rem 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.section-padding {
|
||||
padding: 8rem 4rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Container max width */
|
||||
.container-max {
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* Decorative blob shapes */
|
||||
.blob {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
filter: blur(60px);
|
||||
opacity: 0.4;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.blob-brand {
|
||||
background: hsl(0, 100%, 70%);
|
||||
}
|
||||
|
||||
.blob-accent {
|
||||
background: hsl(150, 80%, 60%);
|
||||
}
|
||||
|
||||
/* Line decoration */
|
||||
.line-decoration {
|
||||
width: 60px;
|
||||
height: 4px;
|
||||
background: var(--gradient-brand);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
/* Stagger animation helper */
|
||||
.stagger > * {
|
||||
opacity: 0;
|
||||
animation: slideUp 0.6s var(--ease-out-expo) forwards;
|
||||
}
|
||||
|
||||
.stagger > *:nth-child(1) { animation-delay: 0.1s; }
|
||||
.stagger > *:nth-child(2) { animation-delay: 0.2s; }
|
||||
.stagger > *:nth-child(3) { animation-delay: 0.3s; }
|
||||
.stagger > *:nth-child(4) { animation-delay: 0.4s; }
|
||||
.stagger > *:nth-child(5) { animation-delay: 0.5s; }
|
||||
.stagger > *:nth-child(6) { animation-delay: 0.6s; }
|
||||
|
||||
/* Text shadow for headings */
|
||||
.text-shadow {
|
||||
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* Hover lift effect */
|
||||
.hover-lift {
|
||||
transition: transform 0.3s var(--ease-out-expo), box-shadow 0.3s var(--ease-out-expo);
|
||||
}
|
||||
|
||||
.hover-lift:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
|
||||
/* Scale on hover */
|
||||
.hover-scale {
|
||||
transition: transform 0.3s var(--ease-spring);
|
||||
}
|
||||
|
||||
.hover-scale:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
/* Rotate on hover */
|
||||
.hover-rotate {
|
||||
transition: transform 0.4s var(--ease-spring);
|
||||
}
|
||||
|
||||
.hover-rotate:hover {
|
||||
transform: rotate(3deg);
|
||||
}
|
||||
|
||||
/* Border gradient effect */
|
||||
.border-gradient {
|
||||
position: relative;
|
||||
background: var(--background);
|
||||
}
|
||||
|
||||
.border-gradient::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
padding: 2px;
|
||||
background: var(--gradient-brand);
|
||||
border-radius: inherit;
|
||||
mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
|
||||
mask-composite: exclude;
|
||||
-webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
|
||||
-webkit-mask-composite: xor;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,38 @@
|
|||
import type { Metadata } from "next";
|
||||
import { Geist, Geist_Mono } from "next/font/google";
|
||||
import { Inter, Outfit } from "next/font/google";
|
||||
import "./globals.css";
|
||||
|
||||
const geistSans = Geist({
|
||||
const inter = Inter({
|
||||
variable: "--font-geist-sans",
|
||||
subsets: ["latin"],
|
||||
display: "swap",
|
||||
});
|
||||
|
||||
const geistMono = Geist_Mono({
|
||||
variable: "--font-geist-mono",
|
||||
const outfit = Outfit({
|
||||
variable: "--font-outfit",
|
||||
subsets: ["latin"],
|
||||
display: "swap",
|
||||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Create Next App",
|
||||
description: "Generated by create next app",
|
||||
title: "TCM Sales & Marketing | Foodservice & Janitorial Experts",
|
||||
description:
|
||||
"With over 100 years of combined experience, TCM Sales & Marketing provides expert foodservice and janitorial solutions across the Rocky Mountain and Northwest regions.",
|
||||
keywords: [
|
||||
"foodservice",
|
||||
"janitorial",
|
||||
"sales",
|
||||
"marketing",
|
||||
"Rocky Mountain",
|
||||
"food packaging",
|
||||
"sustainability",
|
||||
"green products",
|
||||
],
|
||||
openGraph: {
|
||||
title: "TCM Sales & Marketing",
|
||||
description: "Your Foodservice and Janitorial Sales Experts",
|
||||
type: "website",
|
||||
},
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
|
|
@ -23,10 +41,8 @@ export default function RootLayout({
|
|||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body
|
||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||
>
|
||||
<html lang="en" className="scroll-smooth">
|
||||
<body className={`${inter.variable} ${outfit.variable} antialiased`}>
|
||||
{children}
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
812
app/page.tsx
812
app/page.tsx
|
|
@ -1,65 +1,781 @@
|
|||
"use client";
|
||||
|
||||
import { motion, useScroll, useTransform } from "framer-motion";
|
||||
import Image from "next/image";
|
||||
import { useRef } from "react";
|
||||
import Navigation from "./components/Navigation";
|
||||
import Footer from "./components/Footer";
|
||||
import AnimatedButton from "./components/AnimatedButton";
|
||||
import FadeIn, { StaggerContainer, StaggerItem } from "./components/FadeIn";
|
||||
import {
|
||||
ArrowRightIcon,
|
||||
PackageIcon,
|
||||
LeafIcon,
|
||||
UsersIcon,
|
||||
TruckIcon,
|
||||
TargetIcon,
|
||||
HeartIcon,
|
||||
SparklesIcon,
|
||||
MapPinIcon,
|
||||
CheckIcon,
|
||||
RecycleIcon,
|
||||
} from "./components/Icons";
|
||||
|
||||
const stats = [
|
||||
{ value: "100+", label: "Years Combined Experience" },
|
||||
{ value: "10", label: "States Covered" },
|
||||
{ value: "2005", label: "Industry Trusted Since" },
|
||||
{ value: "5", label: "Product Categories" },
|
||||
];
|
||||
|
||||
const productCategories = [
|
||||
{
|
||||
icon: PackageIcon,
|
||||
title: "Food Packaging",
|
||||
description:
|
||||
"Premium containers, cutlery, and packaging solutions for every foodservice need.",
|
||||
color: "from-orange-500 to-red-500",
|
||||
},
|
||||
{
|
||||
icon: LeafIcon,
|
||||
title: "Green Products",
|
||||
description:
|
||||
"Sustainable, compostable, and eco-friendly alternatives for conscious businesses.",
|
||||
color: "from-emerald-500 to-teal-500",
|
||||
},
|
||||
{
|
||||
icon: SparklesIcon,
|
||||
title: "Specialty Products",
|
||||
description:
|
||||
"Custom printed containers, wraps, and premium catering solutions.",
|
||||
color: "from-purple-500 to-pink-500",
|
||||
},
|
||||
{
|
||||
icon: TruckIcon,
|
||||
title: "Janitorial",
|
||||
description:
|
||||
"Complete range of cleaning supplies and janitorial essentials.",
|
||||
color: "from-blue-500 to-cyan-500",
|
||||
},
|
||||
];
|
||||
|
||||
const values = [
|
||||
{
|
||||
icon: HeartIcon,
|
||||
title: "Family First",
|
||||
description:
|
||||
"Building character, strengthening individuals, and nurturing families.",
|
||||
},
|
||||
{
|
||||
icon: UsersIcon,
|
||||
title: "Integrity",
|
||||
description: "Selflessness, servanthood, and confident humility in all we do.",
|
||||
},
|
||||
{
|
||||
icon: TargetIcon,
|
||||
title: "Purpose Driven",
|
||||
description: "Professional, ethical representation with symbiotic growth.",
|
||||
},
|
||||
];
|
||||
|
||||
const territories = [
|
||||
"Colorado",
|
||||
"Washington",
|
||||
"Oregon",
|
||||
"Idaho",
|
||||
"Utah",
|
||||
"Wyoming",
|
||||
"Montana",
|
||||
"New Mexico",
|
||||
"Nebraska",
|
||||
"Arizona",
|
||||
];
|
||||
|
||||
export default function Home() {
|
||||
const heroRef = useRef(null);
|
||||
const { scrollYProgress } = useScroll({
|
||||
target: heroRef,
|
||||
offset: ["start start", "end start"],
|
||||
});
|
||||
|
||||
const heroY = useTransform(scrollYProgress, [0, 1], ["0%", "30%"]);
|
||||
const heroOpacity = useTransform(scrollYProgress, [0, 0.5], [1, 0]);
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-zinc-50 font-sans dark:bg-black">
|
||||
<main className="flex min-h-screen w-full max-w-3xl flex-col items-center justify-between py-32 px-16 bg-white dark:bg-black sm:items-start">
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/next.svg"
|
||||
alt="Next.js logo"
|
||||
width={100}
|
||||
height={20}
|
||||
priority
|
||||
<main className="relative">
|
||||
<Navigation />
|
||||
|
||||
{/* Hero Section */}
|
||||
<section
|
||||
ref={heroRef}
|
||||
className="relative min-h-screen flex items-center justify-center overflow-hidden bg-gradient-to-b from-neutral-50 to-white dark:from-neutral-950 dark:to-neutral-900"
|
||||
>
|
||||
{/* Animated background elements */}
|
||||
<div className="absolute inset-0 overflow-hidden">
|
||||
<motion.div
|
||||
className="absolute top-1/4 -left-32 w-96 h-96 bg-[hsl(0,100%,70%)] rounded-full filter blur-[120px] opacity-20"
|
||||
animate={{
|
||||
scale: [1, 1.2, 1],
|
||||
x: [0, 30, 0],
|
||||
}}
|
||||
transition={{
|
||||
duration: 8,
|
||||
repeat: Infinity,
|
||||
ease: "easeInOut",
|
||||
}}
|
||||
/>
|
||||
<div className="flex flex-col items-center gap-6 text-center sm:items-start sm:text-left">
|
||||
<h1 className="max-w-xs text-3xl font-semibold leading-10 tracking-tight text-black dark:text-zinc-50">
|
||||
To get started, edit the page.tsx file.
|
||||
</h1>
|
||||
<p className="max-w-md text-lg leading-8 text-zinc-600 dark:text-zinc-400">
|
||||
Looking for a starting point or more instructions? Head over to{" "}
|
||||
<a
|
||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
className="font-medium text-zinc-950 dark:text-zinc-50"
|
||||
<motion.div
|
||||
className="absolute bottom-1/4 -right-32 w-96 h-96 bg-[hsl(0,100%,50%)] rounded-full filter blur-[120px] opacity-15"
|
||||
animate={{
|
||||
scale: [1.2, 1, 1.2],
|
||||
x: [0, -30, 0],
|
||||
}}
|
||||
transition={{
|
||||
duration: 10,
|
||||
repeat: Infinity,
|
||||
ease: "easeInOut",
|
||||
}}
|
||||
/>
|
||||
<motion.div
|
||||
className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[800px] h-[800px] bg-gradient-to-br from-[hsl(0,100%,40%)] to-[hsl(20,100%,50%)] rounded-full filter blur-[200px] opacity-5"
|
||||
animate={{
|
||||
rotate: [0, 360],
|
||||
}}
|
||||
transition={{
|
||||
duration: 60,
|
||||
repeat: Infinity,
|
||||
ease: "linear",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Grid pattern overlay */}
|
||||
<div
|
||||
className="absolute inset-0 opacity-[0.02]"
|
||||
style={{
|
||||
backgroundImage: `url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%23000000' fill-opacity='1'%3E%3Cpath d='M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E")`,
|
||||
}}
|
||||
/>
|
||||
|
||||
<motion.div
|
||||
style={{ y: heroY, opacity: heroOpacity }}
|
||||
className="relative z-10 max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 text-center"
|
||||
>
|
||||
Templates
|
||||
</a>{" "}
|
||||
or the{" "}
|
||||
<a
|
||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
className="font-medium text-zinc-950 dark:text-zinc-50"
|
||||
{/* Badge */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.2 }}
|
||||
className="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-[hsl(0,100%,40%)]/10 border border-[hsl(0,100%,40%)]/20 mb-8"
|
||||
>
|
||||
Learning
|
||||
</a>{" "}
|
||||
center.
|
||||
<span className="w-2 h-2 rounded-full bg-[hsl(0,100%,40%)] animate-pulse" />
|
||||
<span className="text-sm font-medium text-[hsl(0,100%,35%)] dark:text-[hsl(0,100%,60%)]">
|
||||
Trusted Since 2005
|
||||
</span>
|
||||
</motion.div>
|
||||
|
||||
{/* Main heading */}
|
||||
<motion.h1
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, delay: 0.3 }}
|
||||
className="text-4xl sm:text-5xl md:text-6xl lg:text-7xl font-bold tracking-tight text-neutral-900 dark:text-white mb-6"
|
||||
>
|
||||
Your{" "}
|
||||
<span className="relative inline-block">
|
||||
<span className="relative z-10 bg-gradient-to-r from-[hsl(0,100%,35%)] via-[hsl(0,100%,45%)] to-[hsl(0,100%,55%)] bg-clip-text text-transparent">
|
||||
Foodservice
|
||||
</span>
|
||||
<motion.span
|
||||
className="absolute -bottom-2 left-0 right-0 h-3 bg-[hsl(0,100%,40%)]/20 -skew-x-3"
|
||||
initial={{ scaleX: 0 }}
|
||||
animate={{ scaleX: 1 }}
|
||||
transition={{ duration: 0.6, delay: 0.8 }}
|
||||
/>
|
||||
</span>{" "}
|
||||
&<br />
|
||||
<span className="relative inline-block mt-2">
|
||||
<span className="relative z-10 bg-gradient-to-r from-[hsl(0,100%,35%)] via-[hsl(0,100%,45%)] to-[hsl(0,100%,55%)] bg-clip-text text-transparent">
|
||||
Janitorial
|
||||
</span>
|
||||
<motion.span
|
||||
className="absolute -bottom-2 left-0 right-0 h-3 bg-[hsl(0,100%,40%)]/20 skew-x-3"
|
||||
initial={{ scaleX: 0 }}
|
||||
animate={{ scaleX: 1 }}
|
||||
transition={{ duration: 0.6, delay: 1 }}
|
||||
/>
|
||||
</span>{" "}
|
||||
Experts
|
||||
</motion.h1>
|
||||
|
||||
{/* Subheading */}
|
||||
<motion.p
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, delay: 0.5 }}
|
||||
className="max-w-2xl mx-auto text-lg sm:text-xl text-neutral-600 dark:text-neutral-400 mb-10 leading-relaxed"
|
||||
>
|
||||
With over 100 years of combined experience, we deliver exceptional
|
||||
sales and marketing solutions across the Rocky Mountain and
|
||||
Northwest regions.
|
||||
</motion.p>
|
||||
|
||||
{/* CTA Buttons */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, delay: 0.7 }}
|
||||
className="flex flex-col sm:flex-row items-center justify-center gap-4"
|
||||
>
|
||||
<AnimatedButton
|
||||
href="/contact"
|
||||
size="lg"
|
||||
icon={<ArrowRightIcon size={18} />}
|
||||
>
|
||||
Get in Touch
|
||||
</AnimatedButton>
|
||||
<AnimatedButton href="/what-we-do" variant="outline" size="lg">
|
||||
Learn More
|
||||
</AnimatedButton>
|
||||
</motion.div>
|
||||
|
||||
{/* Scroll indicator */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ delay: 1.5 }}
|
||||
className="absolute bottom-8 left-1/2 -translate-x-1/2"
|
||||
>
|
||||
<motion.div
|
||||
animate={{ y: [0, 10, 0] }}
|
||||
transition={{ duration: 1.5, repeat: Infinity }}
|
||||
className="w-6 h-10 rounded-full border-2 border-neutral-300 dark:border-neutral-700 flex items-start justify-center p-2"
|
||||
>
|
||||
<motion.div
|
||||
animate={{ y: [0, 12, 0] }}
|
||||
transition={{ duration: 1.5, repeat: Infinity }}
|
||||
className="w-1.5 h-1.5 rounded-full bg-[hsl(0,100%,40%)]"
|
||||
/>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
</section>
|
||||
|
||||
{/* Stats Section */}
|
||||
<section className="relative py-20 bg-neutral-900 dark:bg-neutral-950 overflow-hidden">
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-[hsl(0,100%,40%)]/10 via-transparent to-[hsl(0,100%,40%)]/10" />
|
||||
<div className="relative max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-8 md:gap-4">
|
||||
{stats.map((stat, index) => (
|
||||
<FadeIn key={stat.label} delay={index * 0.1} className="text-center">
|
||||
<motion.div
|
||||
whileHover={{ scale: 1.05 }}
|
||||
className="group cursor-default"
|
||||
>
|
||||
<motion.span
|
||||
className="block text-4xl sm:text-5xl font-bold text-white mb-2 group-hover:text-[hsl(0,100%,60%)] transition-colors"
|
||||
initial={{ opacity: 0, scale: 0.5 }}
|
||||
whileInView={{ opacity: 1, scale: 1 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{
|
||||
type: "spring",
|
||||
stiffness: 200,
|
||||
damping: 15,
|
||||
delay: index * 0.1,
|
||||
}}
|
||||
>
|
||||
{stat.value}
|
||||
</motion.span>
|
||||
<span className="text-neutral-400 text-sm sm:text-base">
|
||||
{stat.label}
|
||||
</span>
|
||||
</motion.div>
|
||||
</FadeIn>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* About Section */}
|
||||
<section className="section-padding bg-white dark:bg-neutral-900">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="grid lg:grid-cols-2 gap-12 lg:gap-20 items-center">
|
||||
{/* Content */}
|
||||
<div>
|
||||
<FadeIn>
|
||||
<span className="inline-block px-4 py-1.5 rounded-full bg-[hsl(0,100%,40%)]/10 text-[hsl(0,100%,40%)] text-sm font-medium mb-6">
|
||||
About Us
|
||||
</span>
|
||||
</FadeIn>
|
||||
<FadeIn delay={0.1}>
|
||||
<h2 className="text-3xl sm:text-4xl lg:text-5xl font-bold text-neutral-900 dark:text-white mb-6 leading-tight">
|
||||
Driving Growth Through{" "}
|
||||
<span className="text-[hsl(0,100%,40%)]">
|
||||
Strategic Partnership
|
||||
</span>
|
||||
</h2>
|
||||
</FadeIn>
|
||||
<FadeIn delay={0.2}>
|
||||
<p className="text-lg text-neutral-600 dark:text-neutral-400 mb-6 leading-relaxed">
|
||||
We believe in “pull thru” selling, using key operators to
|
||||
direct distribution. Our integrated, end-user focused sales
|
||||
approach ensures we exceed growth goals for our manufacturers.
|
||||
</p>
|
||||
</FadeIn>
|
||||
<FadeIn delay={0.3}>
|
||||
<p className="text-lg text-neutral-600 dark:text-neutral-400 mb-8 leading-relaxed">
|
||||
We maintain continuity with our distributor's sales force,
|
||||
providing them with the tools they need to successfully sell
|
||||
our products day-to-day.
|
||||
</p>
|
||||
</FadeIn>
|
||||
<FadeIn delay={0.4}>
|
||||
<AnimatedButton
|
||||
href="/what-we-do"
|
||||
icon={<ArrowRightIcon size={18} />}
|
||||
>
|
||||
Discover Our Approach
|
||||
</AnimatedButton>
|
||||
</FadeIn>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4 text-base font-medium sm:flex-row">
|
||||
<a
|
||||
className="flex h-12 w-full items-center justify-center gap-2 rounded-full bg-foreground px-5 text-background transition-colors hover:bg-[#383838] dark:hover:bg-[#ccc] md:w-[158px]"
|
||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
|
||||
{/* Image/Visual */}
|
||||
<FadeIn direction="right" delay={0.2}>
|
||||
<div className="relative">
|
||||
<div className="relative aspect-square rounded-3xl overflow-hidden bg-gradient-to-br from-neutral-100 to-neutral-200 dark:from-neutral-800 dark:to-neutral-900">
|
||||
{/* Decorative elements */}
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
<motion.div
|
||||
className="relative w-64 h-64"
|
||||
animate={{ rotate: 360 }}
|
||||
transition={{
|
||||
duration: 60,
|
||||
repeat: Infinity,
|
||||
ease: "linear",
|
||||
}}
|
||||
>
|
||||
{[...Array(8)].map((_, i) => (
|
||||
<motion.div
|
||||
key={i}
|
||||
className="absolute w-4 h-4 rounded-full bg-[hsl(0,100%,40%)]"
|
||||
style={{
|
||||
top: "50%",
|
||||
left: "50%",
|
||||
transform: `rotate(${i * 45}deg) translateY(-120px)`,
|
||||
}}
|
||||
initial={{ scale: 0 }}
|
||||
whileInView={{ scale: 1 }}
|
||||
transition={{ delay: i * 0.1 }}
|
||||
viewport={{ once: true }}
|
||||
/>
|
||||
))}
|
||||
</motion.div>
|
||||
</div>
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
<motion.div
|
||||
whileHover={{ scale: 1.05, rotate: 5 }}
|
||||
className="bg-white dark:bg-neutral-800 p-8 rounded-2xl shadow-2xl"
|
||||
>
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/vercel.svg"
|
||||
alt="Vercel logomark"
|
||||
width={16}
|
||||
height={16}
|
||||
src="/assets/brand-logo.png"
|
||||
alt="TCM Sales"
|
||||
width={150}
|
||||
height={150}
|
||||
className="w-32 h-32 object-contain"
|
||||
/>
|
||||
Deploy Now
|
||||
</a>
|
||||
<a
|
||||
className="flex h-12 w-full items-center justify-center rounded-full border border-solid border-black/[.08] px-5 transition-colors hover:border-transparent hover:bg-black/[.04] dark:border-white/[.145] dark:hover:bg-[#1a1a1a] md:w-[158px]"
|
||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
{/* Floating badge */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: 20 }}
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
viewport={{ once: true }}
|
||||
whileHover={{ scale: 1.05, y: -5 }}
|
||||
className="absolute -bottom-6 -right-6 bg-[hsl(0,100%,40%)] text-white px-6 py-4 rounded-2xl shadow-xl"
|
||||
>
|
||||
Documentation
|
||||
</a>
|
||||
<span className="block text-3xl font-bold">20+</span>
|
||||
<span className="text-sm opacity-90">Years of Excellence</span>
|
||||
</motion.div>
|
||||
</div>
|
||||
</FadeIn>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Values Section */}
|
||||
<section className="section-padding bg-neutral-50 dark:bg-neutral-950">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="text-center mb-16">
|
||||
<FadeIn>
|
||||
<span className="inline-block px-4 py-1.5 rounded-full bg-[hsl(0,100%,40%)]/10 text-[hsl(0,100%,40%)] text-sm font-medium mb-6">
|
||||
Our Philosophy
|
||||
</span>
|
||||
</FadeIn>
|
||||
<FadeIn delay={0.1}>
|
||||
<h2 className="text-3xl sm:text-4xl lg:text-5xl font-bold text-neutral-900 dark:text-white mb-6">
|
||||
Built on{" "}
|
||||
<span className="text-[hsl(0,100%,40%)]">Strong Values</span>
|
||||
</h2>
|
||||
</FadeIn>
|
||||
<FadeIn delay={0.2}>
|
||||
<p className="max-w-2xl mx-auto text-lg text-neutral-600 dark:text-neutral-400">
|
||||
Our mission is to glorify God by being faithful stewards,
|
||||
serving our employees and families, and making a positive impact.
|
||||
</p>
|
||||
</FadeIn>
|
||||
</div>
|
||||
|
||||
<StaggerContainer className="grid md:grid-cols-3 gap-8">
|
||||
{values.map((value) => (
|
||||
<StaggerItem key={value.title}>
|
||||
<motion.div
|
||||
whileHover={{ y: -10, scale: 1.02 }}
|
||||
className="group relative bg-white dark:bg-neutral-900 p-8 rounded-3xl shadow-lg shadow-black/5 hover:shadow-xl transition-all duration-500 border border-neutral-100 dark:border-neutral-800"
|
||||
>
|
||||
<motion.div
|
||||
whileHover={{ rotate: 360, scale: 1.1 }}
|
||||
transition={{ duration: 0.5 }}
|
||||
className="w-14 h-14 rounded-2xl bg-gradient-to-br from-[hsl(0,100%,45%)] to-[hsl(0,100%,35%)] flex items-center justify-center text-white mb-6"
|
||||
>
|
||||
<value.icon size={24} />
|
||||
</motion.div>
|
||||
<h3 className="text-xl font-bold text-neutral-900 dark:text-white mb-3 group-hover:text-[hsl(0,100%,40%)] transition-colors">
|
||||
{value.title}
|
||||
</h3>
|
||||
<p className="text-neutral-600 dark:text-neutral-400 leading-relaxed">
|
||||
{value.description}
|
||||
</p>
|
||||
<motion.div
|
||||
className="absolute bottom-0 left-0 right-0 h-1 bg-gradient-to-r from-[hsl(0,100%,45%)] to-[hsl(0,100%,35%)] rounded-b-3xl origin-left"
|
||||
initial={{ scaleX: 0 }}
|
||||
whileHover={{ scaleX: 1 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
/>
|
||||
</motion.div>
|
||||
</StaggerItem>
|
||||
))}
|
||||
</StaggerContainer>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Products Section */}
|
||||
<section className="section-padding bg-white dark:bg-neutral-900">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="flex flex-col lg:flex-row lg:items-end lg:justify-between mb-16 gap-6">
|
||||
<div>
|
||||
<FadeIn>
|
||||
<span className="inline-block px-4 py-1.5 rounded-full bg-[hsl(0,100%,40%)]/10 text-[hsl(0,100%,40%)] text-sm font-medium mb-6">
|
||||
Our Products
|
||||
</span>
|
||||
</FadeIn>
|
||||
<FadeIn delay={0.1}>
|
||||
<h2 className="text-3xl sm:text-4xl lg:text-5xl font-bold text-neutral-900 dark:text-white">
|
||||
Everything You{" "}
|
||||
<span className="text-[hsl(0,100%,40%)]">Need</span>
|
||||
</h2>
|
||||
</FadeIn>
|
||||
</div>
|
||||
<FadeIn delay={0.2}>
|
||||
<AnimatedButton
|
||||
href="/products"
|
||||
variant="outline"
|
||||
icon={<ArrowRightIcon size={18} />}
|
||||
>
|
||||
View All Products
|
||||
</AnimatedButton>
|
||||
</FadeIn>
|
||||
</div>
|
||||
|
||||
<StaggerContainer className="grid sm:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
{productCategories.map((category) => (
|
||||
<StaggerItem key={category.title}>
|
||||
<motion.div
|
||||
whileHover={{ y: -10 }}
|
||||
className="group relative bg-neutral-50 dark:bg-neutral-800 p-6 rounded-3xl cursor-pointer overflow-hidden"
|
||||
>
|
||||
{/* Gradient overlay on hover */}
|
||||
<motion.div
|
||||
className={`absolute inset-0 bg-gradient-to-br ${category.color} opacity-0 group-hover:opacity-10 transition-opacity duration-500`}
|
||||
/>
|
||||
|
||||
<motion.div
|
||||
whileHover={{ scale: 1.1, rotate: 5 }}
|
||||
className={`relative w-14 h-14 rounded-2xl bg-gradient-to-br ${category.color} flex items-center justify-center text-white mb-5`}
|
||||
>
|
||||
<category.icon size={24} />
|
||||
</motion.div>
|
||||
|
||||
<h3 className="relative text-lg font-bold text-neutral-900 dark:text-white mb-2 group-hover:text-[hsl(0,100%,40%)] transition-colors">
|
||||
{category.title}
|
||||
</h3>
|
||||
<p className="relative text-sm text-neutral-600 dark:text-neutral-400 leading-relaxed">
|
||||
{category.description}
|
||||
</p>
|
||||
|
||||
<motion.div
|
||||
initial={{ x: -10, opacity: 0 }}
|
||||
whileHover={{ x: 0, opacity: 1 }}
|
||||
className="absolute bottom-6 right-6"
|
||||
>
|
||||
<ArrowRightIcon
|
||||
size={20}
|
||||
className="text-[hsl(0,100%,40%)]"
|
||||
/>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
</StaggerItem>
|
||||
))}
|
||||
</StaggerContainer>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Territory Section */}
|
||||
<section className="section-padding bg-neutral-900 dark:bg-neutral-950 text-white relative overflow-hidden">
|
||||
{/* Background map pattern */}
|
||||
<div className="absolute inset-0 opacity-5">
|
||||
<svg className="w-full h-full" viewBox="0 0 800 600">
|
||||
<path
|
||||
d="M100 200 Q 200 100 300 200 T 500 200 T 700 200"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
fill="none"
|
||||
/>
|
||||
<circle cx="150" cy="180" r="8" fill="currentColor" />
|
||||
<circle cx="300" cy="200" r="8" fill="currentColor" />
|
||||
<circle cx="500" cy="200" r="8" fill="currentColor" />
|
||||
<circle cx="650" cy="220" r="8" fill="currentColor" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div className="relative max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="grid lg:grid-cols-2 gap-12 lg:gap-20 items-center">
|
||||
<div>
|
||||
<FadeIn>
|
||||
<span className="inline-flex items-center gap-2 px-4 py-1.5 rounded-full bg-white/10 text-white/80 text-sm font-medium mb-6">
|
||||
<MapPinIcon size={16} />
|
||||
Our Coverage
|
||||
</span>
|
||||
</FadeIn>
|
||||
<FadeIn delay={0.1}>
|
||||
<h2 className="text-3xl sm:text-4xl lg:text-5xl font-bold mb-6">
|
||||
Serving the{" "}
|
||||
<span className="text-[hsl(0,100%,55%)]">Rocky Mountain</span>{" "}
|
||||
& <span className="text-[hsl(0,100%,55%)]">Northwest</span>{" "}
|
||||
Regions
|
||||
</h2>
|
||||
</FadeIn>
|
||||
<FadeIn delay={0.2}>
|
||||
<p className="text-lg text-neutral-400 mb-8 leading-relaxed">
|
||||
Our head office is in Denver, Colorado, but our dedicated team
|
||||
works across ten states to provide exceptional service and
|
||||
support.
|
||||
</p>
|
||||
</FadeIn>
|
||||
|
||||
{/* Territory grid */}
|
||||
<FadeIn delay={0.3}>
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 gap-3">
|
||||
{territories.map((territory, index) => (
|
||||
<motion.div
|
||||
key={territory}
|
||||
initial={{ opacity: 0, scale: 0.8 }}
|
||||
whileInView={{ opacity: 1, scale: 1 }}
|
||||
transition={{ delay: index * 0.05 }}
|
||||
whileHover={{
|
||||
scale: 1.05,
|
||||
backgroundColor: "hsl(0, 100%, 40%)",
|
||||
}}
|
||||
className="flex items-center gap-2 px-4 py-3 bg-white/5 rounded-xl cursor-default transition-colors"
|
||||
>
|
||||
<CheckIcon size={16} className="text-[hsl(0,100%,55%)]" />
|
||||
<span className="text-sm font-medium">{territory}</span>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</FadeIn>
|
||||
|
||||
<FadeIn delay={0.4}>
|
||||
<div className="mt-8">
|
||||
<AnimatedButton
|
||||
href="/contact"
|
||||
icon={<ArrowRightIcon size={18} />}
|
||||
>
|
||||
Contact Us
|
||||
</AnimatedButton>
|
||||
</div>
|
||||
</FadeIn>
|
||||
</div>
|
||||
|
||||
{/* Decorative Map Visual */}
|
||||
<FadeIn direction="right" delay={0.2}>
|
||||
<div className="relative aspect-square">
|
||||
<motion.div
|
||||
className="absolute inset-0 rounded-3xl bg-gradient-to-br from-[hsl(0,100%,40%)]/20 to-transparent"
|
||||
animate={{
|
||||
scale: [1, 1.05, 1],
|
||||
}}
|
||||
transition={{
|
||||
duration: 4,
|
||||
repeat: Infinity,
|
||||
ease: "easeInOut",
|
||||
}}
|
||||
/>
|
||||
<div className="absolute inset-4 rounded-2xl border border-white/10 flex items-center justify-center">
|
||||
<motion.div
|
||||
animate={{
|
||||
scale: [1, 1.1, 1],
|
||||
opacity: [0.5, 1, 0.5],
|
||||
}}
|
||||
transition={{
|
||||
duration: 2,
|
||||
repeat: Infinity,
|
||||
ease: "easeInOut",
|
||||
}}
|
||||
className="w-32 h-32 rounded-full bg-[hsl(0,100%,40%)]/30 flex items-center justify-center"
|
||||
>
|
||||
<div className="w-20 h-20 rounded-full bg-[hsl(0,100%,40%)]/50 flex items-center justify-center">
|
||||
<MapPinIcon size={32} className="text-white" />
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
{/* Floating location dots */}
|
||||
{[
|
||||
{ top: "20%", left: "30%" },
|
||||
{ top: "40%", left: "60%" },
|
||||
{ top: "60%", left: "25%" },
|
||||
{ top: "75%", left: "70%" },
|
||||
{ top: "30%", left: "75%" },
|
||||
].map((pos, i) => (
|
||||
<motion.div
|
||||
key={i}
|
||||
className="absolute w-3 h-3 rounded-full bg-[hsl(0,100%,55%)]"
|
||||
style={{ top: pos.top, left: pos.left }}
|
||||
animate={{
|
||||
scale: [1, 1.5, 1],
|
||||
opacity: [0.5, 1, 0.5],
|
||||
}}
|
||||
transition={{
|
||||
duration: 2,
|
||||
repeat: Infinity,
|
||||
ease: "easeInOut",
|
||||
delay: i * 0.3,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</FadeIn>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Sustainability CTA */}
|
||||
<section className="section-padding bg-gradient-to-br from-emerald-50 to-teal-50 dark:from-emerald-950/30 dark:to-teal-950/30 relative overflow-hidden">
|
||||
{/* Background decoration */}
|
||||
<motion.div
|
||||
className="absolute top-0 right-0 w-96 h-96 bg-emerald-400 rounded-full filter blur-[150px] opacity-20"
|
||||
animate={{
|
||||
scale: [1, 1.2, 1],
|
||||
x: [0, 50, 0],
|
||||
}}
|
||||
transition={{
|
||||
duration: 8,
|
||||
repeat: Infinity,
|
||||
ease: "easeInOut",
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="relative max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="max-w-3xl mx-auto text-center">
|
||||
<FadeIn>
|
||||
<motion.div
|
||||
whileHover={{ rotate: 360 }}
|
||||
transition={{ duration: 0.5 }}
|
||||
className="inline-flex items-center justify-center w-16 h-16 rounded-2xl bg-emerald-500 text-white mb-6"
|
||||
>
|
||||
<RecycleIcon size={32} />
|
||||
</motion.div>
|
||||
</FadeIn>
|
||||
<FadeIn delay={0.1}>
|
||||
<h2 className="text-3xl sm:text-4xl lg:text-5xl font-bold text-neutral-900 dark:text-white mb-6">
|
||||
Committed to{" "}
|
||||
<span className="text-emerald-600">Sustainability</span>
|
||||
</h2>
|
||||
</FadeIn>
|
||||
<FadeIn delay={0.2}>
|
||||
<p className="text-lg text-neutral-600 dark:text-neutral-400 mb-8 leading-relaxed">
|
||||
Go green and find the perfect sustainable product options for
|
||||
your business. We offer a comprehensive range of eco-friendly,
|
||||
compostable, and recyclable alternatives.
|
||||
</p>
|
||||
</FadeIn>
|
||||
<FadeIn delay={0.3}>
|
||||
<div className="flex flex-col sm:flex-row items-center justify-center gap-4">
|
||||
<AnimatedButton
|
||||
href="/sustainability"
|
||||
variant="accent"
|
||||
size="lg"
|
||||
icon={<LeafIcon size={18} />}
|
||||
>
|
||||
Explore Green Products
|
||||
</AnimatedButton>
|
||||
<AnimatedButton
|
||||
href="/state-ordinances"
|
||||
variant="outline"
|
||||
size="lg"
|
||||
>
|
||||
State Ordinances
|
||||
</AnimatedButton>
|
||||
</div>
|
||||
</FadeIn>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Final CTA */}
|
||||
<section className="section-padding bg-[hsl(0,100%,40%)] text-white relative overflow-hidden">
|
||||
{/* Background pattern */}
|
||||
<div className="absolute inset-0 opacity-10">
|
||||
<svg className="w-full h-full" viewBox="0 0 100 100" preserveAspectRatio="none">
|
||||
<pattern id="grid" width="10" height="10" patternUnits="userSpaceOnUse">
|
||||
<path d="M 10 0 L 0 0 0 10" fill="none" stroke="white" strokeWidth="0.5" />
|
||||
</pattern>
|
||||
<rect width="100%" height="100%" fill="url(#grid)" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div className="relative max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
|
||||
<FadeIn>
|
||||
<h2 className="text-3xl sm:text-4xl lg:text-5xl font-bold mb-6">
|
||||
Ready to Get Started?
|
||||
</h2>
|
||||
</FadeIn>
|
||||
<FadeIn delay={0.1}>
|
||||
<p className="text-xl text-white/90 mb-10 leading-relaxed">
|
||||
Let's talk about your foodservice and janitorial needs. Our team
|
||||
is here to help you find the perfect solutions.
|
||||
</p>
|
||||
</FadeIn>
|
||||
<FadeIn delay={0.2}>
|
||||
<motion.div
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
className="inline-block"
|
||||
>
|
||||
<AnimatedButton
|
||||
href="/contact"
|
||||
variant="secondary"
|
||||
size="lg"
|
||||
icon={<ArrowRightIcon size={18} />}
|
||||
>
|
||||
Contact Us Today
|
||||
</AnimatedButton>
|
||||
</motion.div>
|
||||
</FadeIn>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<Footer />
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
"lint": "eslint"
|
||||
},
|
||||
"dependencies": {
|
||||
"framer-motion": "^12.23.26",
|
||||
"next": "16.1.0",
|
||||
"react": "19.2.3",
|
||||
"react-dom": "19.2.3"
|
||||
|
|
|
|||
21
yarn.lock
21
yarn.lock
|
|
@ -1713,6 +1713,15 @@ for-each@^0.3.3, for-each@^0.3.5:
|
|||
dependencies:
|
||||
is-callable "^1.2.7"
|
||||
|
||||
framer-motion@^12.23.26:
|
||||
version "12.23.26"
|
||||
resolved "https://registry.yarnpkg.com/framer-motion/-/framer-motion-12.23.26.tgz#2a684e9b156118e1c4989d7fc9327def83480391"
|
||||
integrity sha512-cPcIhgR42xBn1Uj+PzOyheMtZ73H927+uWPDVhUMqxy8UHt6Okavb6xIz9J/phFUHUj0OncR6UvMfJTXoc/LKA==
|
||||
dependencies:
|
||||
motion-dom "^12.23.23"
|
||||
motion-utils "^12.23.6"
|
||||
tslib "^2.4.0"
|
||||
|
||||
function-bind@^1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c"
|
||||
|
|
@ -2356,6 +2365,18 @@ minimist@^1.2.0, minimist@^1.2.6:
|
|||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
|
||||
integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
|
||||
|
||||
motion-dom@^12.23.23:
|
||||
version "12.23.23"
|
||||
resolved "https://registry.yarnpkg.com/motion-dom/-/motion-dom-12.23.23.tgz#8f874333ea1a04ee3a89eb928f518b463d589e0e"
|
||||
integrity sha512-n5yolOs0TQQBRUFImrRfs/+6X4p3Q4n1dUEqt/H58Vx7OW6RF+foWEgmTVDhIWJIMXOuNNL0apKH2S16en9eiA==
|
||||
dependencies:
|
||||
motion-utils "^12.23.6"
|
||||
|
||||
motion-utils@^12.23.6:
|
||||
version "12.23.6"
|
||||
resolved "https://registry.yarnpkg.com/motion-utils/-/motion-utils-12.23.6.tgz#fafef80b4ea85122dd0d6c599a0c63d72881f312"
|
||||
integrity sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==
|
||||
|
||||
ms@^2.1.1, ms@^2.1.3:
|
||||
version "2.1.3"
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
|
||||
|
|
|
|||
Loading…
Reference in New Issue