refactor: unify service color system into single source of truth
Some checks failed
Pull Request Checks / Code Quality & Security (push) Has been cancelled
Security Audit / Security Vulnerability Audit (push) Has been cancelled
Security Audit / Dependency Review (push) Has been cancelled
Security Audit / CodeQL Security Analysis (push) Has been cancelled
Security Audit / Check Outdated Dependencies (push) Has been cancelled
Some checks failed
Pull Request Checks / Code Quality & Security (push) Has been cancelled
Security Audit / Security Vulnerability Audit (push) Has been cancelled
Security Audit / Dependency Review (push) Has been cancelled
Security Audit / CodeQL Security Analysis (push) Has been cancelled
Security Audit / Check Outdated Dependencies (push) Has been cancelled
Consolidate 3 separate color systems (ServiceCardAccentColor, CarouselAccent, serviceAccents) into one canonical SERVICE_COLORS map at shared/constants/service-colors.ts. Palette: internet=blue, sim=emerald, vpn=violet, onsite=amber, all business=slate. Removes 3 unused colors (indigo, cyan, rose) that added visual noise without aiding recognition. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
eac5739779
commit
174ffd8fe3
@ -17,7 +17,7 @@ const meta: Meta<typeof ServiceCard> = {
|
|||||||
},
|
},
|
||||||
accentColor: {
|
accentColor: {
|
||||||
control: "select",
|
control: "select",
|
||||||
options: ["blue", "green", "purple", "orange", "cyan", "pink", "amber", "rose"],
|
options: ["blue", "emerald", "violet", "amber", "slate"],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -107,26 +107,26 @@ export const BentoGrid: Story = {
|
|||||||
icon={<DevicePhoneMobileIcon className="h-6 w-6" />}
|
icon={<DevicePhoneMobileIcon className="h-6 w-6" />}
|
||||||
title="Mobile"
|
title="Mobile"
|
||||||
description="SIM & eSIM plans"
|
description="SIM & eSIM plans"
|
||||||
accentColor="green"
|
accentColor="emerald"
|
||||||
/>
|
/>
|
||||||
<ServiceCard
|
<ServiceCard
|
||||||
variant="bento-md"
|
variant="bento-md"
|
||||||
icon={<ShieldCheckIcon className="h-6 w-6" />}
|
icon={<ShieldCheckIcon className="h-6 w-6" />}
|
||||||
title="VPN"
|
title="VPN"
|
||||||
description="Secure browsing"
|
description="Secure browsing"
|
||||||
accentColor="purple"
|
accentColor="violet"
|
||||||
/>
|
/>
|
||||||
<ServiceCard
|
<ServiceCard
|
||||||
variant="bento-sm"
|
variant="bento-sm"
|
||||||
icon={<ServerIcon className="h-5 w-5" />}
|
icon={<ServerIcon className="h-5 w-5" />}
|
||||||
title="Hosting"
|
title="Hosting"
|
||||||
accentColor="orange"
|
accentColor="amber"
|
||||||
/>
|
/>
|
||||||
<ServiceCard
|
<ServiceCard
|
||||||
variant="bento-sm"
|
variant="bento-sm"
|
||||||
icon={<WifiIcon className="h-5 w-5" />}
|
icon={<WifiIcon className="h-5 w-5" />}
|
||||||
title="WiFi Router"
|
title="WiFi Router"
|
||||||
accentColor="cyan"
|
accentColor="slate"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
@ -135,17 +135,15 @@ export const BentoGrid: Story = {
|
|||||||
export const AccentColors: Story = {
|
export const AccentColors: Story = {
|
||||||
render: () => (
|
render: () => (
|
||||||
<div className="grid grid-cols-2 gap-4 w-[600px]">
|
<div className="grid grid-cols-2 gap-4 w-[600px]">
|
||||||
{(["blue", "green", "purple", "orange", "cyan", "pink", "amber", "rose"] as const).map(
|
{(["blue", "emerald", "violet", "amber", "slate"] as const).map(color => (
|
||||||
color => (
|
<ServiceCard
|
||||||
<ServiceCard
|
key={color}
|
||||||
key={color}
|
icon={<WifiIcon className="h-6 w-6" />}
|
||||||
icon={<WifiIcon className="h-6 w-6" />}
|
title={color.charAt(0).toUpperCase() + color.slice(1)}
|
||||||
title={color.charAt(0).toUpperCase() + color.slice(1)}
|
description={`${color} accent`}
|
||||||
description={`${color} accent`}
|
accentColor={color}
|
||||||
accentColor={color}
|
/>
|
||||||
/>
|
))}
|
||||||
)
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|||||||
@ -7,15 +7,7 @@ import React from "react";
|
|||||||
/**
|
/**
|
||||||
* Accent color options for ServiceCard
|
* Accent color options for ServiceCard
|
||||||
*/
|
*/
|
||||||
export type ServiceCardAccentColor =
|
export type ServiceCardAccentColor = "blue" | "emerald" | "violet" | "amber" | "slate";
|
||||||
| "blue"
|
|
||||||
| "green"
|
|
||||||
| "purple"
|
|
||||||
| "orange"
|
|
||||||
| "cyan"
|
|
||||||
| "pink"
|
|
||||||
| "amber"
|
|
||||||
| "rose";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Variant options for ServiceCard
|
* Variant options for ServiceCard
|
||||||
@ -86,45 +78,21 @@ const accentColorStyles: Record<
|
|||||||
hoverBorder: "hover:border-blue-500/40",
|
hoverBorder: "hover:border-blue-500/40",
|
||||||
cardBg: "from-blue-500/10 via-card to-card",
|
cardBg: "from-blue-500/10 via-card to-card",
|
||||||
},
|
},
|
||||||
green: {
|
emerald: {
|
||||||
bg: "bg-green-500/10",
|
bg: "bg-emerald-500/10",
|
||||||
text: "text-green-600 dark:text-green-400",
|
text: "text-emerald-600 dark:text-emerald-400",
|
||||||
border: "border-green-500/20",
|
border: "border-emerald-500/20",
|
||||||
gradient: "from-green-500/30 to-transparent",
|
gradient: "from-emerald-500/30 to-transparent",
|
||||||
hoverBorder: "hover:border-green-500/40",
|
hoverBorder: "hover:border-emerald-500/40",
|
||||||
cardBg: "from-green-500/10 via-card to-card",
|
cardBg: "from-emerald-500/10 via-card to-card",
|
||||||
},
|
},
|
||||||
purple: {
|
violet: {
|
||||||
bg: "bg-purple-500/10",
|
bg: "bg-violet-500/10",
|
||||||
text: "text-purple-600 dark:text-purple-400",
|
text: "text-violet-600 dark:text-violet-400",
|
||||||
border: "border-purple-500/20",
|
border: "border-violet-500/20",
|
||||||
gradient: "from-purple-500/30 to-transparent",
|
gradient: "from-violet-500/30 to-transparent",
|
||||||
hoverBorder: "hover:border-purple-500/40",
|
hoverBorder: "hover:border-violet-500/40",
|
||||||
cardBg: "from-purple-500/10 via-card to-card",
|
cardBg: "from-violet-500/10 via-card to-card",
|
||||||
},
|
|
||||||
orange: {
|
|
||||||
bg: "bg-orange-500/10",
|
|
||||||
text: "text-orange-600 dark:text-orange-400",
|
|
||||||
border: "border-orange-500/20",
|
|
||||||
gradient: "from-orange-500/30 to-transparent",
|
|
||||||
hoverBorder: "hover:border-orange-500/40",
|
|
||||||
cardBg: "from-orange-500/10 via-card to-card",
|
|
||||||
},
|
|
||||||
cyan: {
|
|
||||||
bg: "bg-cyan-500/10",
|
|
||||||
text: "text-cyan-600 dark:text-cyan-400",
|
|
||||||
border: "border-cyan-500/20",
|
|
||||||
gradient: "from-cyan-500/30 to-transparent",
|
|
||||||
hoverBorder: "hover:border-cyan-500/40",
|
|
||||||
cardBg: "from-cyan-500/10 via-card to-card",
|
|
||||||
},
|
|
||||||
pink: {
|
|
||||||
bg: "bg-pink-500/10",
|
|
||||||
text: "text-pink-600 dark:text-pink-400",
|
|
||||||
border: "border-pink-500/20",
|
|
||||||
gradient: "from-pink-500/30 to-transparent",
|
|
||||||
hoverBorder: "hover:border-pink-500/40",
|
|
||||||
cardBg: "from-pink-500/10 via-card to-card",
|
|
||||||
},
|
},
|
||||||
amber: {
|
amber: {
|
||||||
bg: "bg-amber-500/10",
|
bg: "bg-amber-500/10",
|
||||||
@ -134,13 +102,13 @@ const accentColorStyles: Record<
|
|||||||
hoverBorder: "hover:border-amber-500/40",
|
hoverBorder: "hover:border-amber-500/40",
|
||||||
cardBg: "from-amber-500/10 via-card to-card",
|
cardBg: "from-amber-500/10 via-card to-card",
|
||||||
},
|
},
|
||||||
rose: {
|
slate: {
|
||||||
bg: "bg-rose-500/10",
|
bg: "bg-slate-500/10",
|
||||||
text: "text-rose-600 dark:text-rose-400",
|
text: "text-slate-600 dark:text-slate-400",
|
||||||
border: "border-rose-500/20",
|
border: "border-slate-500/20",
|
||||||
gradient: "from-rose-500/30 to-transparent",
|
gradient: "from-slate-500/30 to-transparent",
|
||||||
hoverBorder: "hover:border-rose-500/40",
|
hoverBorder: "hover:border-slate-500/40",
|
||||||
cardBg: "from-rose-500/10 via-card to-card",
|
cardBg: "from-slate-500/10 via-card to-card",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,13 +1,243 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useRef } from "react";
|
import { useRef } from "react";
|
||||||
import { LayoutGroup, motion, useInView } from "framer-motion";
|
import { LayoutGroup, motion, useInView, useScroll, useTransform } from "framer-motion";
|
||||||
import { ArrowRight } from "lucide-react";
|
import { ArrowRight } from "lucide-react";
|
||||||
import { Button } from "@/components/atoms/button";
|
import { Button } from "@/components/atoms/button";
|
||||||
import TextRotate from "@/components/fancy/text/text-rotate";
|
import TextRotate from "@/components/fancy/text/text-rotate";
|
||||||
|
|
||||||
const SERVICE_WORDS = ["Internet", "Phone Plans", "VPN", "IT Support", "Business"];
|
const SERVICE_WORDS = ["Internet", "Phone Plans", "VPN", "IT Support", "Business"];
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
/* Placeholder silhouettes — replace with final illustrations later */
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
function HouseSilhouette({ className }: { className?: string }) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 200 220"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
className={className}
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
{/* Roof */}
|
||||||
|
<polygon points="100,10 10,100 190,100" className="fill-primary/80" />
|
||||||
|
{/* Chimney */}
|
||||||
|
<rect x="145" y="40" width="20" height="45" rx="2" className="fill-primary/60" />
|
||||||
|
{/* Body */}
|
||||||
|
<rect x="30" y="100" width="140" height="110" rx="4" className="fill-primary/70" />
|
||||||
|
{/* Door */}
|
||||||
|
<rect x="80" y="140" width="40" height="70" rx="3" className="fill-background" />
|
||||||
|
<circle cx="112" cy="178" r="3" className="fill-primary/50" />
|
||||||
|
{/* Window left */}
|
||||||
|
<rect x="45" y="115" width="28" height="28" rx="2" className="fill-info-bg" />
|
||||||
|
<line x1="59" y1="115" x2="59" y2="143" className="stroke-primary/30" strokeWidth="2" />
|
||||||
|
<line x1="45" y1="129" x2="73" y2="129" className="stroke-primary/30" strokeWidth="2" />
|
||||||
|
{/* Window right */}
|
||||||
|
<rect x="127" y="115" width="28" height="28" rx="2" className="fill-info-bg" />
|
||||||
|
<line x1="141" y1="115" x2="141" y2="143" className="stroke-primary/30" strokeWidth="2" />
|
||||||
|
<line x1="127" y1="129" x2="155" y2="129" className="stroke-primary/30" strokeWidth="2" />
|
||||||
|
{/* WiFi signal on roof */}
|
||||||
|
<path
|
||||||
|
d="M100 30 Q100 20 108 16"
|
||||||
|
className="stroke-info"
|
||||||
|
strokeWidth="2.5"
|
||||||
|
strokeLinecap="round"
|
||||||
|
fill="none"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M100 30 Q100 14 114 8"
|
||||||
|
className="stroke-info"
|
||||||
|
strokeWidth="2.5"
|
||||||
|
strokeLinecap="round"
|
||||||
|
fill="none"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M100 30 Q100 8 120 0"
|
||||||
|
className="stroke-info"
|
||||||
|
strokeWidth="2.5"
|
||||||
|
strokeLinecap="round"
|
||||||
|
fill="none"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function PersonWithPhoneSilhouette({ className }: { className?: string }) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 120 240"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
className={className}
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
{/* Head */}
|
||||||
|
<circle cx="60" cy="30" r="24" className="fill-primary/75" />
|
||||||
|
{/* Body */}
|
||||||
|
<path
|
||||||
|
d="M36 60 C36 54 84 54 84 60 L88 160 C88 164 32 164 32 160 Z"
|
||||||
|
className="fill-primary/65"
|
||||||
|
/>
|
||||||
|
{/* Left arm (down) */}
|
||||||
|
<path d="M36 70 L14 130 L22 134 L40 80" className="fill-primary/55" />
|
||||||
|
{/* Right arm (holding phone up to ear) */}
|
||||||
|
<path d="M84 70 L96 60 L100 40 L92 38 L88 56 L80 66" className="fill-primary/55" />
|
||||||
|
{/* Phone */}
|
||||||
|
<rect x="90" y="28" width="14" height="24" rx="3" className="fill-foreground/80" />
|
||||||
|
<rect x="92" y="32" width="10" height="16" rx="1" className="fill-info/40" />
|
||||||
|
{/* Legs */}
|
||||||
|
<path d="M44 158 L38 230 L52 230 L54 158" className="fill-primary/60" />
|
||||||
|
<path d="M66 158 L68 230 L82 230 L76 158" className="fill-primary/60" />
|
||||||
|
{/* Shoes */}
|
||||||
|
<ellipse cx="45" cy="232" rx="14" ry="6" className="fill-primary/80" />
|
||||||
|
<ellipse cx="75" cy="232" rx="14" ry="6" className="fill-primary/80" />
|
||||||
|
{/* Signal waves from phone */}
|
||||||
|
<path
|
||||||
|
d="M104 30 Q112 26 112 18"
|
||||||
|
className="stroke-info"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
fill="none"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M106 30 Q118 24 118 12"
|
||||||
|
className="stroke-info"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
fill="none"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function PersonWithRouterSilhouette({ className }: { className?: string }) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 140 240"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
className={className}
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
{/* Head */}
|
||||||
|
<circle cx="70" cy="30" r="24" className="fill-primary/75" />
|
||||||
|
{/* Body */}
|
||||||
|
<path
|
||||||
|
d="M46 60 C46 54 94 54 94 60 L98 160 C98 164 42 164 42 160 Z"
|
||||||
|
className="fill-primary/65"
|
||||||
|
/>
|
||||||
|
{/* Left arm (holding router) */}
|
||||||
|
<path d="M46 70 L18 110 L22 116 L46 82" className="fill-primary/55" />
|
||||||
|
{/* Right arm */}
|
||||||
|
<path d="M94 70 L116 130 L108 134 L90 80" className="fill-primary/55" />
|
||||||
|
{/* Router box */}
|
||||||
|
<rect x="2" y="100" width="44" height="16" rx="3" className="fill-foreground/80" />
|
||||||
|
{/* Router antenna 1 */}
|
||||||
|
<line
|
||||||
|
x1="12"
|
||||||
|
y1="100"
|
||||||
|
x2="8"
|
||||||
|
y2="78"
|
||||||
|
className="stroke-foreground/70"
|
||||||
|
strokeWidth="2.5"
|
||||||
|
strokeLinecap="round"
|
||||||
|
/>
|
||||||
|
<circle cx="8" cy="76" r="3" className="fill-info" />
|
||||||
|
{/* Router antenna 2 */}
|
||||||
|
<line
|
||||||
|
x1="36"
|
||||||
|
y1="100"
|
||||||
|
x2="40"
|
||||||
|
y2="78"
|
||||||
|
className="stroke-foreground/70"
|
||||||
|
strokeWidth="2.5"
|
||||||
|
strokeLinecap="round"
|
||||||
|
/>
|
||||||
|
<circle cx="40" cy="76" r="3" className="fill-info" />
|
||||||
|
{/* Router LEDs */}
|
||||||
|
<circle cx="14" cy="108" r="2" className="fill-green-400" />
|
||||||
|
<circle cx="22" cy="108" r="2" className="fill-green-400" />
|
||||||
|
<circle cx="30" cy="108" r="2" className="fill-info" />
|
||||||
|
{/* WiFi waves from router */}
|
||||||
|
<path
|
||||||
|
d="M24 76 Q24 66 16 60"
|
||||||
|
className="stroke-info"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
fill="none"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M24 76 Q24 60 12 50"
|
||||||
|
className="stroke-info"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
fill="none"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M24 76 Q24 54 8 40"
|
||||||
|
className="stroke-info"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
fill="none"
|
||||||
|
/>
|
||||||
|
{/* Legs */}
|
||||||
|
<path d="M54 158 L48 230 L62 230 L64 158" className="fill-primary/60" />
|
||||||
|
<path d="M76 158 L78 230 L92 230 L86 158" className="fill-primary/60" />
|
||||||
|
{/* Shoes */}
|
||||||
|
<ellipse cx="55" cy="232" rx="14" ry="6" className="fill-primary/80" />
|
||||||
|
<ellipse cx="85" cy="232" rx="14" ry="6" className="fill-primary/80" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
/* Floating wrapper — ambient bob + scroll parallax */
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
function FloatingIllustration({
|
||||||
|
children,
|
||||||
|
floatDelay,
|
||||||
|
floatDuration,
|
||||||
|
floatDistance,
|
||||||
|
parallaxRange,
|
||||||
|
scrollProgress,
|
||||||
|
className,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
floatDelay: number;
|
||||||
|
floatDuration: number;
|
||||||
|
floatDistance: number;
|
||||||
|
parallaxRange: [number, number];
|
||||||
|
scrollProgress: import("framer-motion").MotionValue<number>;
|
||||||
|
className?: string;
|
||||||
|
}) {
|
||||||
|
const parallaxY = useTransform(scrollProgress, [0, 1], parallaxRange);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
className={className}
|
||||||
|
style={{ y: parallaxY }}
|
||||||
|
animate={{ y: [0, -floatDistance, 0] }}
|
||||||
|
transition={{
|
||||||
|
y: {
|
||||||
|
duration: floatDuration,
|
||||||
|
repeat: Infinity,
|
||||||
|
ease: "easeInOut",
|
||||||
|
delay: floatDelay,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</motion.div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
/* Hero Section */
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
interface HeroSectionProps {
|
interface HeroSectionProps {
|
||||||
heroCTARef: React.RefObject<HTMLDivElement | null>;
|
heroCTARef: React.RefObject<HTMLDivElement | null>;
|
||||||
}
|
}
|
||||||
@ -16,6 +246,11 @@ export function HeroSection({ heroCTARef }: HeroSectionProps) {
|
|||||||
const heroRef = useRef<HTMLDivElement>(null);
|
const heroRef = useRef<HTMLDivElement>(null);
|
||||||
const heroInView = useInView(heroRef, { once: true, amount: 0.1 });
|
const heroInView = useInView(heroRef, { once: true, amount: 0.1 });
|
||||||
|
|
||||||
|
const { scrollYProgress } = useScroll({
|
||||||
|
target: heroRef,
|
||||||
|
offset: ["start start", "end start"],
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<motion.div
|
<motion.div
|
||||||
ref={heroRef}
|
ref={heroRef}
|
||||||
@ -47,51 +282,111 @@ export function HeroSection({ heroCTARef }: HeroSectionProps) {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="relative mx-auto max-w-3xl px-6 sm:px-10 lg:px-14 text-center">
|
{/* Split Layout Container */}
|
||||||
<LayoutGroup>
|
<div className="relative mx-auto max-w-7xl w-full px-6 sm:px-10 lg:px-14 flex flex-col lg:flex-row items-center gap-10 lg:gap-16">
|
||||||
<h1 className="text-4xl sm:text-5xl lg:text-6xl font-extrabold leading-tight text-foreground font-heading">
|
{/* Left — Text Content */}
|
||||||
<span className="block">Seamless IT Solutions</span>
|
<div className="flex-1 text-center lg:text-left">
|
||||||
<motion.span
|
<LayoutGroup>
|
||||||
className="flex items-center justify-center gap-2 sm:gap-3 mt-2"
|
<h1 className="text-4xl sm:text-5xl lg:text-6xl font-extrabold leading-tight text-foreground font-heading">
|
||||||
layout
|
<span className="block">Seamless IT Solutions</span>
|
||||||
transition={{ type: "spring", damping: 30, stiffness: 400 }}
|
<motion.span
|
||||||
>
|
className="flex items-center justify-center lg:justify-start gap-2 sm:gap-3 mt-2"
|
||||||
<span className="text-foreground">for</span>
|
layout
|
||||||
<TextRotate
|
|
||||||
texts={SERVICE_WORDS}
|
|
||||||
mainClassName="text-white px-3 sm:px-4 bg-primary overflow-hidden py-1 sm:py-1.5 justify-center rounded-lg"
|
|
||||||
staggerFrom="last"
|
|
||||||
initial={{ y: "100%" }}
|
|
||||||
animate={{ y: 0 }}
|
|
||||||
exit={{ y: "-120%" }}
|
|
||||||
staggerDuration={0.025}
|
|
||||||
splitLevelClassName="overflow-hidden pb-0.5 sm:pb-1"
|
|
||||||
transition={{ type: "spring", damping: 30, stiffness: 400 }}
|
transition={{ type: "spring", damping: 30, stiffness: 400 }}
|
||||||
rotationInterval={2500}
|
>
|
||||||
/>
|
<span className="text-foreground">for</span>
|
||||||
</motion.span>
|
<TextRotate
|
||||||
</h1>
|
texts={SERVICE_WORDS}
|
||||||
</LayoutGroup>
|
mainClassName="text-white px-3 sm:px-4 bg-primary overflow-hidden py-1 sm:py-1.5 justify-center rounded-lg"
|
||||||
<p className="mt-6 text-base sm:text-lg text-muted-foreground leading-relaxed font-semibold max-w-2xl mx-auto">
|
staggerFrom="last"
|
||||||
From connectivity to communication, we handle the complexity so you can focus on what
|
initial={{ y: "100%" }}
|
||||||
matters — with dedicated English support across Japan.
|
animate={{ y: 0 }}
|
||||||
</p>
|
exit={{ y: "-120%" }}
|
||||||
<div
|
staggerDuration={0.025}
|
||||||
ref={heroCTARef}
|
splitLevelClassName="overflow-hidden pb-0.5 sm:pb-1"
|
||||||
className="mt-8 flex flex-col sm:flex-row items-center justify-center gap-3 sm:gap-4"
|
transition={{ type: "spring", damping: 30, stiffness: 400 }}
|
||||||
>
|
rotationInterval={2500}
|
||||||
<Button
|
/>
|
||||||
as="a"
|
</motion.span>
|
||||||
href="/services"
|
</h1>
|
||||||
variant="pill"
|
</LayoutGroup>
|
||||||
size="lg"
|
<p className="mt-6 text-base sm:text-lg text-muted-foreground leading-relaxed font-semibold max-w-2xl mx-auto lg:mx-0">
|
||||||
rightIcon={<ArrowRight className="h-5 w-5" />}
|
From connectivity to communication, we handle the complexity so you can focus on what
|
||||||
|
matters — with dedicated English support across Japan.
|
||||||
|
</p>
|
||||||
|
<div
|
||||||
|
ref={heroCTARef}
|
||||||
|
className="mt-8 flex flex-col sm:flex-row items-center justify-center lg:justify-start gap-3 sm:gap-4"
|
||||||
>
|
>
|
||||||
Find Your Plan
|
<Button
|
||||||
</Button>
|
as="a"
|
||||||
<Button as="a" href="#contact" variant="pillOutline" size="lg">
|
href="/services"
|
||||||
Talk to Us
|
variant="pill"
|
||||||
</Button>
|
size="lg"
|
||||||
|
rightIcon={<ArrowRight className="h-5 w-5" />}
|
||||||
|
>
|
||||||
|
Find Your Plan
|
||||||
|
</Button>
|
||||||
|
<Button as="a" href="#contact" variant="pillOutline" size="lg">
|
||||||
|
Talk to Us
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Right — Floating Illustrations */}
|
||||||
|
<div className="flex-1 relative min-h-[300px] sm:min-h-[360px] lg:min-h-[420px] w-full max-w-lg lg:max-w-none">
|
||||||
|
{/* House — center back, largest */}
|
||||||
|
<FloatingIllustration
|
||||||
|
floatDelay={0}
|
||||||
|
floatDuration={5}
|
||||||
|
floatDistance={8}
|
||||||
|
parallaxRange={[0, -30]}
|
||||||
|
scrollProgress={scrollYProgress}
|
||||||
|
className="absolute left-1/2 -translate-x-1/2 bottom-0 w-40 sm:w-48 lg:w-56 drop-shadow-lg"
|
||||||
|
>
|
||||||
|
<HouseSilhouette />
|
||||||
|
</FloatingIllustration>
|
||||||
|
|
||||||
|
{/* Person with phone — left front */}
|
||||||
|
<FloatingIllustration
|
||||||
|
floatDelay={0.8}
|
||||||
|
floatDuration={4.2}
|
||||||
|
floatDistance={12}
|
||||||
|
parallaxRange={[0, -50]}
|
||||||
|
scrollProgress={scrollYProgress}
|
||||||
|
className="absolute left-2 sm:left-4 lg:left-0 bottom-0 w-20 sm:w-24 lg:w-28 drop-shadow-md"
|
||||||
|
>
|
||||||
|
<PersonWithPhoneSilhouette />
|
||||||
|
</FloatingIllustration>
|
||||||
|
|
||||||
|
{/* Person with router — right front */}
|
||||||
|
<FloatingIllustration
|
||||||
|
floatDelay={1.5}
|
||||||
|
floatDuration={4.8}
|
||||||
|
floatDistance={10}
|
||||||
|
parallaxRange={[0, -40]}
|
||||||
|
scrollProgress={scrollYProgress}
|
||||||
|
className="absolute right-2 sm:right-4 lg:right-0 bottom-0 w-24 sm:w-28 lg:w-32 drop-shadow-md"
|
||||||
|
>
|
||||||
|
<PersonWithRouterSilhouette />
|
||||||
|
</FloatingIllustration>
|
||||||
|
|
||||||
|
{/* Decorative floating dots */}
|
||||||
|
<motion.div
|
||||||
|
className="absolute top-8 left-10 w-3 h-3 rounded-full bg-primary/30"
|
||||||
|
animate={{ y: [0, -10, 0], opacity: [0.3, 0.6, 0.3] }}
|
||||||
|
transition={{ duration: 3, repeat: Infinity, ease: "easeInOut" }}
|
||||||
|
/>
|
||||||
|
<motion.div
|
||||||
|
className="absolute top-16 right-16 w-2 h-2 rounded-full bg-info/40"
|
||||||
|
animate={{ y: [0, -8, 0], opacity: [0.4, 0.7, 0.4] }}
|
||||||
|
transition={{ duration: 3.5, repeat: Infinity, ease: "easeInOut", delay: 1 }}
|
||||||
|
/>
|
||||||
|
<motion.div
|
||||||
|
className="absolute top-4 right-1/3 w-2.5 h-2.5 rounded-full bg-primary/20"
|
||||||
|
animate={{ y: [0, -12, 0], opacity: [0.2, 0.5, 0.2] }}
|
||||||
|
transition={{ duration: 4, repeat: Infinity, ease: "easeInOut", delay: 0.5 }}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|||||||
@ -13,96 +13,25 @@ import {
|
|||||||
type ConversionServiceCard,
|
type ConversionServiceCard,
|
||||||
type CarouselAccent,
|
type CarouselAccent,
|
||||||
} from "@/features/landing-page/data";
|
} from "@/features/landing-page/data";
|
||||||
|
import { SERVICE_COLORS, type ServiceColorId } from "@/shared/constants/service-colors";
|
||||||
|
|
||||||
type Tab = "personal" | "business";
|
type Tab = "personal" | "business";
|
||||||
|
|
||||||
/* ─── Accent color system ─── */
|
/* ─── Accent color system (derived from canonical SERVICE_COLORS) ─── */
|
||||||
|
|
||||||
interface AccentStyles {
|
/** Map carousel accent names to canonical service IDs */
|
||||||
iconBg: string;
|
const ACCENT_SERVICE: Record<CarouselAccent, ServiceColorId> = {
|
||||||
iconText: string;
|
blue: "internet",
|
||||||
ctaBg: string;
|
emerald: "sim",
|
||||||
dotBg: string;
|
violet: "vpn",
|
||||||
border: string;
|
amber: "onsite",
|
||||||
glowFrom: string;
|
slate: "business",
|
||||||
cssVar: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ACCENTS: Record<CarouselAccent, AccentStyles> = {
|
|
||||||
blue: {
|
|
||||||
iconBg: "bg-blue-500/12",
|
|
||||||
iconText: "text-blue-600",
|
|
||||||
ctaBg: "bg-blue-600 hover:bg-blue-700",
|
|
||||||
dotBg: "bg-blue-600",
|
|
||||||
border: "border-blue-500/20",
|
|
||||||
glowFrom: "from-blue-500/5",
|
|
||||||
cssVar: "var(--color-blue-500)",
|
|
||||||
},
|
|
||||||
emerald: {
|
|
||||||
iconBg: "bg-emerald-500/12",
|
|
||||||
iconText: "text-emerald-600",
|
|
||||||
ctaBg: "bg-emerald-600 hover:bg-emerald-700",
|
|
||||||
dotBg: "bg-emerald-600",
|
|
||||||
border: "border-emerald-500/20",
|
|
||||||
glowFrom: "from-emerald-500/5",
|
|
||||||
cssVar: "var(--color-emerald-500)",
|
|
||||||
},
|
|
||||||
violet: {
|
|
||||||
iconBg: "bg-violet-500/12",
|
|
||||||
iconText: "text-violet-600",
|
|
||||||
ctaBg: "bg-violet-600 hover:bg-violet-700",
|
|
||||||
dotBg: "bg-violet-600",
|
|
||||||
border: "border-violet-500/20",
|
|
||||||
glowFrom: "from-violet-500/5",
|
|
||||||
cssVar: "var(--color-violet-500)",
|
|
||||||
},
|
|
||||||
amber: {
|
|
||||||
iconBg: "bg-amber-500/12",
|
|
||||||
iconText: "text-amber-600",
|
|
||||||
ctaBg: "bg-amber-600 hover:bg-amber-700",
|
|
||||||
dotBg: "bg-amber-600",
|
|
||||||
border: "border-amber-500/20",
|
|
||||||
glowFrom: "from-amber-500/5",
|
|
||||||
cssVar: "var(--color-amber-500)",
|
|
||||||
},
|
|
||||||
indigo: {
|
|
||||||
iconBg: "bg-indigo-500/12",
|
|
||||||
iconText: "text-indigo-600",
|
|
||||||
ctaBg: "bg-indigo-600 hover:bg-indigo-700",
|
|
||||||
dotBg: "bg-indigo-600",
|
|
||||||
border: "border-indigo-500/20",
|
|
||||||
glowFrom: "from-indigo-500/5",
|
|
||||||
cssVar: "var(--color-indigo-500)",
|
|
||||||
},
|
|
||||||
cyan: {
|
|
||||||
iconBg: "bg-cyan-500/12",
|
|
||||||
iconText: "text-cyan-600",
|
|
||||||
ctaBg: "bg-cyan-600 hover:bg-cyan-700",
|
|
||||||
dotBg: "bg-cyan-600",
|
|
||||||
border: "border-cyan-500/20",
|
|
||||||
glowFrom: "from-cyan-500/5",
|
|
||||||
cssVar: "var(--color-cyan-500)",
|
|
||||||
},
|
|
||||||
rose: {
|
|
||||||
iconBg: "bg-rose-500/12",
|
|
||||||
iconText: "text-rose-600",
|
|
||||||
ctaBg: "bg-rose-600 hover:bg-rose-700",
|
|
||||||
dotBg: "bg-rose-600",
|
|
||||||
border: "border-rose-500/20",
|
|
||||||
glowFrom: "from-rose-500/5",
|
|
||||||
cssVar: "var(--color-rose-500)",
|
|
||||||
},
|
|
||||||
slate: {
|
|
||||||
iconBg: "bg-slate-500/12",
|
|
||||||
iconText: "text-slate-600",
|
|
||||||
ctaBg: "bg-slate-600 hover:bg-slate-700",
|
|
||||||
dotBg: "bg-slate-600",
|
|
||||||
border: "border-slate-500/20",
|
|
||||||
glowFrom: "from-slate-500/5",
|
|
||||||
cssVar: "var(--color-slate-500)",
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function getAccent(accent: CarouselAccent) {
|
||||||
|
return SERVICE_COLORS[ACCENT_SERVICE[accent]];
|
||||||
|
}
|
||||||
|
|
||||||
/* ─── Framer Motion variants ─── */
|
/* ─── Framer Motion variants ─── */
|
||||||
|
|
||||||
const tabContentVariants = {
|
const tabContentVariants = {
|
||||||
@ -126,7 +55,7 @@ const ServiceCard = memo(function ServiceCard({
|
|||||||
card: ConversionServiceCard;
|
card: ConversionServiceCard;
|
||||||
wasDragging: () => boolean;
|
wasDragging: () => boolean;
|
||||||
}) {
|
}) {
|
||||||
const a = ACCENTS[card.accent];
|
const a = getAccent(card.accent);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
@ -312,7 +241,7 @@ function CarouselNav({
|
|||||||
</Button>
|
</Button>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{cards.map((card, i) => {
|
{cards.map((card, i) => {
|
||||||
const styles = ACCENTS[card.accent];
|
const styles = getAccent(card.accent);
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
key={`${card.title}-${i}`}
|
key={`${card.title}-${i}`}
|
||||||
|
|||||||
@ -17,15 +17,7 @@ import type { ServiceCardAccentColor } from "@/components/molecules";
|
|||||||
|
|
||||||
export type ServiceCategory = "personal" | "business";
|
export type ServiceCategory = "personal" | "business";
|
||||||
|
|
||||||
export type CarouselAccent =
|
export type CarouselAccent = "blue" | "emerald" | "violet" | "amber" | "slate";
|
||||||
| "blue"
|
|
||||||
| "emerald"
|
|
||||||
| "violet"
|
|
||||||
| "amber"
|
|
||||||
| "indigo"
|
|
||||||
| "cyan"
|
|
||||||
| "rose"
|
|
||||||
| "slate";
|
|
||||||
|
|
||||||
export interface ServiceItem {
|
export interface ServiceItem {
|
||||||
title: string;
|
title: string;
|
||||||
@ -172,7 +164,7 @@ export const businessConversionCards: ConversionServiceCard[] = [
|
|||||||
keyBenefit: "Onsite & remote support",
|
keyBenefit: "Onsite & remote support",
|
||||||
description:
|
description:
|
||||||
"Dedicated English-speaking IT team for your business. Onsite and remote support whenever you need it.",
|
"Dedicated English-speaking IT team for your business. Onsite and remote support whenever you need it.",
|
||||||
accent: "amber",
|
accent: "slate",
|
||||||
icon: <Wrench className={ICON_CLASS_MD} />,
|
icon: <Wrench className={ICON_CLASS_MD} />,
|
||||||
href: HREF_ONSITE,
|
href: HREF_ONSITE,
|
||||||
ctaLabel: CTA_GET_QUOTE,
|
ctaLabel: CTA_GET_QUOTE,
|
||||||
@ -183,7 +175,7 @@ export const businessConversionCards: ConversionServiceCard[] = [
|
|||||||
keyBenefit: "Enterprise-grade connectivity",
|
keyBenefit: "Enterprise-grade connectivity",
|
||||||
description:
|
description:
|
||||||
"Enterprise-grade connectivity with guaranteed bandwidth and SLA. Built for businesses that can't afford downtime.",
|
"Enterprise-grade connectivity with guaranteed bandwidth and SLA. Built for businesses that can't afford downtime.",
|
||||||
accent: "indigo",
|
accent: "slate",
|
||||||
icon: <Building2 className={ICON_CLASS_MD} />,
|
icon: <Building2 className={ICON_CLASS_MD} />,
|
||||||
href: HREF_BUSINESS,
|
href: HREF_BUSINESS,
|
||||||
ctaLabel: CTA_GET_QUOTE,
|
ctaLabel: CTA_GET_QUOTE,
|
||||||
@ -194,7 +186,7 @@ export const businessConversionCards: ConversionServiceCard[] = [
|
|||||||
keyBenefit: "Secure, reliable infrastructure",
|
keyBenefit: "Secure, reliable infrastructure",
|
||||||
description:
|
description:
|
||||||
"Secure, reliable hosting infrastructure in Japan. Colocation and managed services for your critical systems.",
|
"Secure, reliable hosting infrastructure in Japan. Colocation and managed services for your critical systems.",
|
||||||
accent: "cyan",
|
accent: "slate",
|
||||||
icon: <Shield className={ICON_CLASS_MD} />,
|
icon: <Shield className={ICON_CLASS_MD} />,
|
||||||
href: HREF_BUSINESS,
|
href: HREF_BUSINESS,
|
||||||
ctaLabel: CTA_GET_QUOTE,
|
ctaLabel: CTA_GET_QUOTE,
|
||||||
@ -205,7 +197,7 @@ export const businessConversionCards: ConversionServiceCard[] = [
|
|||||||
keyBenefit: "Construction & maintenance",
|
keyBenefit: "Construction & maintenance",
|
||||||
description:
|
description:
|
||||||
"Professional website construction and ongoing maintenance. Bilingual design that connects with your audience.",
|
"Professional website construction and ongoing maintenance. Bilingual design that connects with your audience.",
|
||||||
accent: "rose",
|
accent: "slate",
|
||||||
icon: <Code className={ICON_CLASS_MD} />,
|
icon: <Code className={ICON_CLASS_MD} />,
|
||||||
href: HREF_BUSINESS,
|
href: HREF_BUSINESS,
|
||||||
ctaLabel: CTA_GET_QUOTE,
|
ctaLabel: CTA_GET_QUOTE,
|
||||||
@ -282,9 +274,10 @@ export type LandingServiceItem = (typeof services)[number] & {
|
|||||||
|
|
||||||
const landingAccentMap: Record<string, ServiceCardAccentColor> = {
|
const landingAccentMap: Record<string, ServiceCardAccentColor> = {
|
||||||
"/services/internet": "blue",
|
"/services/internet": "blue",
|
||||||
"/services/sim": "green",
|
"/services/sim": "emerald",
|
||||||
"/services/vpn": "purple",
|
"/services/vpn": "violet",
|
||||||
"/services/business": "orange",
|
"/services/business": "slate",
|
||||||
|
"/services/onsite": "amber",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const landingServices: LandingServiceItem[] = services
|
export const landingServices: LandingServiceItem[] = services
|
||||||
|
|||||||
@ -48,26 +48,26 @@ const services = [
|
|||||||
description:
|
description:
|
||||||
"SIM cards on Japan's best network. Foreign credit cards accepted, no hanko required.",
|
"SIM cards on Japan's best network. Foreign credit cards accepted, no hanko required.",
|
||||||
icon: <Smartphone />,
|
icon: <Smartphone />,
|
||||||
accentColor: "green" as const,
|
accentColor: "emerald" as const,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Business Solutions",
|
title: "Business Solutions",
|
||||||
description:
|
description:
|
||||||
"Enterprise IT for international companies. Dedicated internet, office networks, and data centers.",
|
"Enterprise IT for international companies. Dedicated internet, office networks, and data centers.",
|
||||||
icon: <Building2 />,
|
icon: <Building2 />,
|
||||||
accentColor: "purple" as const,
|
accentColor: "slate" as const,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "VPN",
|
title: "VPN",
|
||||||
description: "Stream your favorite shows from home. Pre-configured router for US/UK content.",
|
description: "Stream your favorite shows from home. Pre-configured router for US/UK content.",
|
||||||
icon: <Lock />,
|
icon: <Lock />,
|
||||||
accentColor: "orange" as const,
|
accentColor: "violet" as const,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Onsite Support",
|
title: "Onsite Support",
|
||||||
description: "English-speaking technicians at your door for setup and troubleshooting.",
|
description: "English-speaking technicians at your door for setup and troubleshooting.",
|
||||||
icon: <Wrench />,
|
icon: <Wrench />,
|
||||||
accentColor: "cyan" as const,
|
accentColor: "amber" as const,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@ -23,9 +23,9 @@ interface ServicesOverviewContentProps {
|
|||||||
|
|
||||||
const serviceAccents = {
|
const serviceAccents = {
|
||||||
internet: {
|
internet: {
|
||||||
icon: "bg-sky-500/10 text-sky-600",
|
icon: "bg-blue-500/10 text-blue-600",
|
||||||
badge: "bg-sky-500/10 text-sky-700 border-sky-500/20",
|
badge: "bg-blue-500/10 text-blue-700 border-blue-500/20",
|
||||||
stripe: "from-sky-500 to-blue-600",
|
stripe: "from-blue-500 to-blue-600",
|
||||||
},
|
},
|
||||||
sim: {
|
sim: {
|
||||||
icon: "bg-emerald-500/10 text-emerald-600",
|
icon: "bg-emerald-500/10 text-emerald-600",
|
||||||
|
|||||||
@ -9,3 +9,4 @@ export {
|
|||||||
formatJapanesePostalCode,
|
formatJapanesePostalCode,
|
||||||
type PrefectureOption,
|
type PrefectureOption,
|
||||||
} from "./japan-prefectures";
|
} from "./japan-prefectures";
|
||||||
|
export { SERVICE_COLORS, type ServiceColorId, type ServiceColorTokens } from "./service-colors";
|
||||||
|
|||||||
109
apps/portal/src/shared/constants/service-colors.ts
Normal file
109
apps/portal/src/shared/constants/service-colors.ts
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
/**
|
||||||
|
* Canonical service color map — single source of truth.
|
||||||
|
*
|
||||||
|
* Every place that needs a service-specific color should reference
|
||||||
|
* this map by service ID. This ensures consistent colors across
|
||||||
|
* the carousel, service cards, overview pages, and dashboard.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type ServiceColorId = "internet" | "sim" | "vpn" | "onsite" | "business";
|
||||||
|
|
||||||
|
export interface ServiceColorTokens {
|
||||||
|
/** Icon background: e.g. "bg-blue-500/10" */
|
||||||
|
iconBg: string;
|
||||||
|
/** Icon/text color: e.g. "text-blue-600" */
|
||||||
|
iconText: string;
|
||||||
|
/** Dark mode text: e.g. "dark:text-blue-400" */
|
||||||
|
iconTextDark: string;
|
||||||
|
/** Border: e.g. "border-blue-500/20" */
|
||||||
|
border: string;
|
||||||
|
/** Hover border: e.g. "hover:border-blue-500/40" */
|
||||||
|
hoverBorder: string;
|
||||||
|
/** Badge: e.g. "bg-blue-500/10 text-blue-700 border-blue-500/20" */
|
||||||
|
badge: string;
|
||||||
|
/** Gradient stripe: e.g. "from-blue-500 to-blue-600" */
|
||||||
|
stripe: string;
|
||||||
|
/** CTA button: e.g. "bg-blue-600 hover:bg-blue-700" */
|
||||||
|
ctaBg: string;
|
||||||
|
/** Dot indicator: e.g. "bg-blue-600" */
|
||||||
|
dotBg: string;
|
||||||
|
/** Glow gradient: e.g. "from-blue-500/5" */
|
||||||
|
glowFrom: string;
|
||||||
|
/** Card background gradient: e.g. "from-blue-500/10 via-card to-card" */
|
||||||
|
cardBg: string;
|
||||||
|
/** CSS variable for dynamic usage: e.g. "var(--color-blue-500)" */
|
||||||
|
cssVar: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SERVICE_COLORS: Record<ServiceColorId, ServiceColorTokens> = {
|
||||||
|
internet: {
|
||||||
|
iconBg: "bg-blue-500/10",
|
||||||
|
iconText: "text-blue-600",
|
||||||
|
iconTextDark: "dark:text-blue-400",
|
||||||
|
border: "border-blue-500/20",
|
||||||
|
hoverBorder: "hover:border-blue-500/40",
|
||||||
|
badge: "bg-blue-500/10 text-blue-700 border-blue-500/20",
|
||||||
|
stripe: "from-blue-500 to-blue-600",
|
||||||
|
ctaBg: "bg-blue-600 hover:bg-blue-700",
|
||||||
|
dotBg: "bg-blue-600",
|
||||||
|
glowFrom: "from-blue-500/5",
|
||||||
|
cardBg: "from-blue-500/10 via-card to-card",
|
||||||
|
cssVar: "var(--color-blue-500)",
|
||||||
|
},
|
||||||
|
sim: {
|
||||||
|
iconBg: "bg-emerald-500/10",
|
||||||
|
iconText: "text-emerald-600",
|
||||||
|
iconTextDark: "dark:text-emerald-400",
|
||||||
|
border: "border-emerald-500/20",
|
||||||
|
hoverBorder: "hover:border-emerald-500/40",
|
||||||
|
badge: "bg-emerald-500/10 text-emerald-700 border-emerald-500/20",
|
||||||
|
stripe: "from-emerald-500 to-teal-600",
|
||||||
|
ctaBg: "bg-emerald-600 hover:bg-emerald-700",
|
||||||
|
dotBg: "bg-emerald-600",
|
||||||
|
glowFrom: "from-emerald-500/5",
|
||||||
|
cardBg: "from-emerald-500/10 via-card to-card",
|
||||||
|
cssVar: "var(--color-emerald-500)",
|
||||||
|
},
|
||||||
|
vpn: {
|
||||||
|
iconBg: "bg-violet-500/10",
|
||||||
|
iconText: "text-violet-600",
|
||||||
|
iconTextDark: "dark:text-violet-400",
|
||||||
|
border: "border-violet-500/20",
|
||||||
|
hoverBorder: "hover:border-violet-500/40",
|
||||||
|
badge: "bg-violet-500/10 text-violet-700 border-violet-500/20",
|
||||||
|
stripe: "from-violet-500 to-purple-600",
|
||||||
|
ctaBg: "bg-violet-600 hover:bg-violet-700",
|
||||||
|
dotBg: "bg-violet-600",
|
||||||
|
glowFrom: "from-violet-500/5",
|
||||||
|
cardBg: "from-violet-500/10 via-card to-card",
|
||||||
|
cssVar: "var(--color-violet-500)",
|
||||||
|
},
|
||||||
|
onsite: {
|
||||||
|
iconBg: "bg-amber-500/10",
|
||||||
|
iconText: "text-amber-600",
|
||||||
|
iconTextDark: "dark:text-amber-400",
|
||||||
|
border: "border-amber-500/20",
|
||||||
|
hoverBorder: "hover:border-amber-500/40",
|
||||||
|
badge: "bg-amber-500/10 text-amber-700 border-amber-500/20",
|
||||||
|
stripe: "from-amber-500 to-orange-600",
|
||||||
|
ctaBg: "bg-amber-600 hover:bg-amber-700",
|
||||||
|
dotBg: "bg-amber-600",
|
||||||
|
glowFrom: "from-amber-500/5",
|
||||||
|
cardBg: "from-amber-500/10 via-card to-card",
|
||||||
|
cssVar: "var(--color-amber-500)",
|
||||||
|
},
|
||||||
|
business: {
|
||||||
|
iconBg: "bg-slate-500/10",
|
||||||
|
iconText: "text-slate-600",
|
||||||
|
iconTextDark: "dark:text-slate-400",
|
||||||
|
border: "border-slate-500/20",
|
||||||
|
hoverBorder: "hover:border-slate-500/40",
|
||||||
|
badge: "bg-slate-500/10 text-slate-700 border-slate-500/20",
|
||||||
|
stripe: "from-slate-500 to-slate-700",
|
||||||
|
ctaBg: "bg-slate-600 hover:bg-slate-700",
|
||||||
|
dotBg: "bg-slate-600",
|
||||||
|
glowFrom: "from-slate-500/5",
|
||||||
|
cardBg: "from-slate-500/10 via-card to-card",
|
||||||
|
cssVar: "var(--color-slate-500)",
|
||||||
|
},
|
||||||
|
};
|
||||||
Loading…
x
Reference in New Issue
Block a user