style: enhance layout and design of service components

- Updated ServicesPage layout by removing unnecessary padding for a cleaner appearance.
- Added a "Contact" link to the PublicShell navigation for improved accessibility.
- Refined AboutUsView by restructuring service and value data for better clarity and organization.
- Improved styling consistency across various service components, including ServiceCTA, ServiceFAQ, and ServiceHighlights, by adjusting spacing and typography.
- Enhanced InternetOfferingCard and PublicOfferingCard with updated tier styles and visual hierarchy for better user experience.
This commit is contained in:
barsa 2026-03-04 18:57:30 +09:00
parent d5294dc580
commit 48eb8c8725
16 changed files with 1197 additions and 1270 deletions

View File

@ -22,7 +22,7 @@ export const metadata: Metadata = {
export default function ServicesPage() { export default function ServicesPage() {
return ( return (
<div className="max-w-6xl mx-auto px-4 pt-8"> <div className="max-w-6xl mx-auto">
<ServicesOverviewContent basePath="/services" /> <ServicesOverviewContent basePath="/services" />
</div> </div>
); );

View File

@ -261,6 +261,12 @@ export function PublicShell({ children }: PublicShellProps) {
> >
Support Support
</Link> </Link>
<Link
href="/contact"
className="inline-flex items-center px-3 py-2 rounded-md hover:text-foreground transition-colors"
>
Contact
</Link>
</nav> </nav>
{/* Spacer for mobile only - takes center column when nav is hidden */} {/* Spacer for mobile only - takes center column when nav is hidden */}
@ -396,6 +402,13 @@ export function PublicShell({ children }: PublicShellProps) {
> >
Support Support
</Link> </Link>
<Link
href="/contact"
onClick={closeMobileMenu}
className="flex items-center px-3 py-3.5 rounded-xl text-base font-semibold text-foreground hover:bg-muted/50 active:bg-muted/70 transition-colors"
>
Contact
</Link>
</div> </div>
</div> </div>

View File

@ -1,7 +1,5 @@
"use client";
import Image from "next/image"; import Image from "next/image";
import { useCallback, useEffect, useRef, useState } from "react"; import type { LucideIcon } from "lucide-react";
import { import {
Wifi, Wifi,
Smartphone, Smartphone,
@ -13,349 +11,342 @@ import {
Lightbulb, Lightbulb,
Globe, Globe,
Shield, Shield,
Quote, MapPin,
Phone,
Users,
Calendar,
Banknote,
BriefcaseBusiness,
} from "lucide-react"; } from "lucide-react";
/** /* ─── Data ─── */
* AboutUsView - Corporate profile and company information
*
* Displays company background, corporate data, business activities,
* and mission statement for Assist Solutions.
*/
export function AboutUsView() {
const values = [
{
text: "Make technology accessible for everyone, regardless of language barriers.",
icon: <Heart className="h-6 w-6" />,
color: "bg-rose-50 text-rose-500 border-rose-100",
},
{
text: "Save our customers time by handling Japanese bureaucracy and paperwork for them.",
icon: <Clock3 className="h-6 w-6" />,
color: "bg-amber-50 text-amber-500 border-amber-100",
},
{
text: "Stay current with the latest technology to provide the best solutions for our clients.",
icon: <Lightbulb className="h-6 w-6" />,
color: "bg-sky-50 text-sky-500 border-sky-100",
},
{
text: "Be a bridge between Japan's tech infrastructure and its international community.",
icon: <Globe className="h-6 w-6" />,
color: "bg-emerald-50 text-emerald-500 border-emerald-100",
},
{
text: "Operate with transparency and integrity in all our customer relationships.",
icon: <Shield className="h-6 w-6" />,
color: "bg-violet-50 text-violet-500 border-violet-100",
},
];
const services = [ const services: { title: string; description: string; icon: LucideIcon }[] = [
{ {
title: "Internet Plans", title: "Internet Plans",
description: description:
"High-speed NTT fiber with English support. We handle the Japanese paperwork so you don't have to.", "High-speed NTT fiber with English support. We handle the Japanese paperwork so you don't have to.",
icon: <Wifi className="h-7 w-7 text-primary" />, icon: Wifi,
}, },
{ {
title: "Phone Plans", title: "Phone Plans",
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 className="h-7 w-7 text-primary" />, icon: Smartphone,
}, },
{ {
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 className="h-7 w-7 text-primary" />, icon: Building2,
}, },
{ {
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 className="h-7 w-7 text-primary" />, icon: Lock,
}, },
{ {
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 className="h-7 w-7 text-primary" />, icon: Wrench,
}, },
]; ];
const carouselRef = useRef<HTMLDivElement>(null); const values: {
const [scrollAmount, setScrollAmount] = useState(0); title: string;
text: string;
icon: LucideIcon;
accent: string;
}[] = [
{
title: "Accessibility",
text: "Make technology accessible for everyone, regardless of language barriers.",
icon: Heart,
accent: "text-rose-500 bg-rose-500/10",
},
{
title: "Time-Saving",
text: "Save our customers time by handling Japanese bureaucracy and paperwork for them.",
icon: Clock3,
accent: "text-amber-500 bg-amber-500/10",
},
{
title: "Innovation",
text: "Stay current with the latest technology to provide the best solutions for our clients.",
icon: Lightbulb,
accent: "text-sky-500 bg-sky-500/10",
},
{
title: "Connection",
text: "Be a bridge between Japan's tech infrastructure and its international community.",
icon: Globe,
accent: "text-emerald-500 bg-emerald-500/10",
},
{
title: "Integrity",
text: "Operate with transparency and integrity in all our customer relationships.",
icon: Shield,
accent: "text-violet-500 bg-violet-500/10",
},
];
const computeScrollAmount = useCallback(() => { const corporateDetails: { label: string; value: string; icon: LucideIcon }[] = [
const container = carouselRef.current; {
if (!container) return; label: "Representative Director",
const card = container.querySelector<HTMLElement>("[data-business-card]"); value: "Daisuke Nagakawa",
if (!card) return; icon: Users,
const gap = },
Number.parseFloat(getComputedStyle(container).columnGap || "0") || {
Number.parseFloat(getComputedStyle(container).gap || "0") || label: "Employees",
24; value: "21 Staff Members (as of March 31st, 2025)",
setScrollAmount(card.clientWidth + gap); icon: BriefcaseBusiness,
}, []); },
{ label: "Established", value: "March 8, 2002", icon: Calendar },
{ label: "Paid in Capital", value: "40,000,000 JPY", icon: Banknote },
];
const scrollServices = useCallback( const businessHours = [
(direction: 1 | -1) => { { team: "Customer Support Team", hours: "Mon \u2013 Fri 9:30AM \u2013 6:00PM" },
const container = carouselRef.current; { team: "In-office Tech Support Team", hours: "Mon \u2013 Fri 9:30AM \u2013 6:00PM" },
if (!container) return; { team: "Onsite Tech Support Team", hours: "Mon \u2013 Sat 10:00AM \u2013 9:00PM" },
const amount = scrollAmount || container.clientWidth; ];
container.scrollBy({ left: direction * amount, behavior: "smooth" });
},
[scrollAmount]
);
useEffect(() => { /* ─── Full-width section wrapper ─── */
computeScrollAmount();
window.addEventListener("resize", computeScrollAmount);
return () => window.removeEventListener("resize", computeScrollAmount);
}, [computeScrollAmount]);
const SECTION_BASE = "relative left-1/2 right-1/2 w-screen -translate-x-1/2";
const HERO_GLOW_STYLE = {
background: "radial-gradient(circle, oklch(0.7 0.12 234.4 / 0.35), transparent 70%)",
} as const;
/* ─── Section components ─── */
function HeroSection() {
return ( return (
<div className="space-y-0"> <section className={`${SECTION_BASE} overflow-hidden bg-surface-sunken`}>
{/* Hero with geometric pattern */} <div
<section className="relative left-1/2 right-1/2 w-screen -translate-x-1/2 bg-gradient-to-br from-slate-50 to-sky-50/30 py-12 sm:py-16 overflow-hidden"> className="pointer-events-none absolute -top-1/3 right-0 h-[600px] w-[600px] opacity-30"
{/* Dot grid pattern */} style={HERO_GLOW_STYLE}
<div />
className="absolute inset-0 opacity-[0.4]" <div className="relative mx-auto max-w-6xl px-6 py-16 sm:px-8 sm:py-20 lg:py-24">
style={{ <div className="grid grid-cols-1 items-center gap-12 lg:grid-cols-2">
backgroundImage: `radial-gradient(circle, #0ea5e9 1px, transparent 1px)`, <div className="cp-stagger-children space-y-6">
backgroundSize: "24px 24px", <span className="inline-block rounded-full bg-primary/10 px-4 py-1.5 text-sm font-semibold tracking-wide text-primary">
}} Since 2002
/> </span>
{/* Subtle gradient overlay for depth */} <h1 className="text-display-lg font-extrabold leading-[1.1] tracking-tight font-display text-foreground">
<div className="absolute inset-0 bg-gradient-to-t from-white/80 via-transparent to-white/40" /> Your Trusted IT Partner <span className="cp-gradient-text">in Japan</span>
<div className="relative max-w-6xl mx-auto px-6 sm:px-8"> </h1>
<div className="grid grid-cols-1 lg:grid-cols-[1.05fr_minmax(0,0.95fr)] gap-10 items-center"> <div className="max-w-lg space-y-4 text-base leading-relaxed text-muted-foreground sm:text-lg">
<div className="space-y-5"> <p>
<h1 className="text-4xl sm:text-5xl font-extrabold text-primary leading-tight tracking-tight"> Assist Solutions has been the go-to IT partner for expats and international
About Us businesses in Japan for over two decades. We understand the unique challenges of
</h1> living and working where language barriers can make simple tasks difficult.
<div className="space-y-4 text-muted-foreground leading-relaxed text-base sm:text-lg"> </p>
<p> <p>
Since 2002, Assist Solutions has been the trusted IT partner for expats and Our bilingual team provides internet, mobile, VPN, and tech support with full
international businesses in Japan. We understand the unique challenges of living English service. No Japanese required we handle everything from contracts to
and working in a country where language barriers can make simple tasks difficult. installation.
</p>
<p>
Our bilingual team provides internet, mobile, VPN, and tech support services with
full English support. No Japanese required. We handle everything from contracts to
installation coordination, so you can focus on enjoying life in Japan.
</p>
</div>
</div>
<div className="relative h-full min-h-[420px]">
<Image
src="/assets/images/About us.png"
alt="Assist Solutions team in Tokyo"
fill
priority
className="object-contain drop-shadow-lg"
sizes="(max-width: 1024px) 100vw, 45vw"
/>
</div>
</div>
</div>
{/* Gradient fade to Business Solutions section */}
<div className="absolute bottom-0 left-0 right-0 h-24 bg-gradient-to-b from-transparent to-[#e8f5ff] pointer-events-none z-10" />
</section>
{/* Business Solutions Carousel */}
<section className="relative left-1/2 right-1/2 w-screen -translate-x-1/2 bg-[#e8f5ff] py-10">
{/* Gradient fade to next section */}
<div className="absolute bottom-0 left-0 right-0 h-24 bg-gradient-to-b from-transparent to-white pointer-events-none" />
<div className="mx-auto max-w-6xl px-6 sm:px-10">
<div className="flex items-center justify-between mb-6">
<h2 className="text-2xl sm:text-3xl font-bold text-foreground">Business</h2>
</div>
<div className="relative">
<div
ref={carouselRef}
className="flex gap-6 overflow-x-auto scroll-smooth pb-6 pr-20"
style={{ scrollbarWidth: "none" }}
>
{services.map((service, idx) => (
<article
key={`${service.title}-${idx}`}
data-business-card
className="flex-shrink-0 w-[240px] rounded-3xl bg-white px-6 py-7 shadow-md border border-white/60"
>
<div className="mb-4">{service.icon}</div>
<h3 className="text-lg font-semibold text-foreground mb-2">{service.title}</h3>
<p className="text-sm text-muted-foreground leading-relaxed">
{service.description}
</p>
</article>
))}
</div>
<div className="absolute bottom-0 right-2 flex gap-3">
<button
type="button"
onClick={() => scrollServices(-1)}
className="h-9 w-9 rounded-full border border-black/15 bg-white text-foreground shadow-sm hover:bg-white/90"
aria-label="Scroll business left"
>
</button>
<button
type="button"
onClick={() => scrollServices(1)}
className="h-9 w-9 rounded-full border border-black/15 bg-white text-foreground shadow-sm hover:bg-white/90"
aria-label="Scroll business right"
>
</button>
</div>
</div>
</div>
</section>
{/* Our Values Section */}
<section className="relative left-1/2 right-1/2 w-screen -translate-x-1/2 bg-white py-12 sm:py-14">
{/* Gradient fade to next section */}
<div className="absolute bottom-0 left-0 right-0 h-24 bg-gradient-to-b from-transparent to-[#f7f7f7] pointer-events-none" />
<div className="max-w-6xl mx-auto px-6 sm:px-8 space-y-8">
<div className="text-center max-w-2xl mx-auto">
<h2 className="text-2xl sm:text-3xl font-bold text-primary mb-3">Our Values</h2>
<p className="text-muted-foreground leading-relaxed">
These principles guide how we serve customers, support our community, and advance our
craft every day.
</p>
</div>
{/* Values Grid - 3 on top, 2 centered on bottom */}
<div className="space-y-4">
{/* Top row - 3 cards */}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
{values.slice(0, 3).map((value, index) => (
<div
key={index}
className="group relative bg-white rounded-2xl border border-border/60 p-6 shadow-sm hover:shadow-md hover:-translate-y-1 transition-all duration-300"
>
{/* Icon */}
<div
className={`inline-flex items-center justify-center w-12 h-12 rounded-xl border mb-4 ${value.color}`}
>
{value.icon}
</div>
{/* Quote mark decoration */}
<Quote className="absolute top-4 right-4 h-8 w-8 text-muted-foreground/10 rotate-180" />
{/* Text */}
<p className="text-foreground font-medium leading-relaxed">{value.text}</p>
</div>
))}
</div>
{/* Bottom row - 2 cards centered */}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 max-w-2xl mx-auto lg:max-w-none lg:grid-cols-2 lg:px-[16.666%]">
{values.slice(3).map((value, index) => (
<div
key={index + 3}
className="group relative bg-white rounded-2xl border border-border/60 p-6 shadow-sm hover:shadow-md hover:-translate-y-1 transition-all duration-300"
>
{/* Icon */}
<div
className={`inline-flex items-center justify-center w-12 h-12 rounded-xl border mb-4 ${value.color}`}
>
{value.icon}
</div>
{/* Quote mark decoration */}
<Quote className="absolute top-4 right-4 h-8 w-8 text-muted-foreground/10 rotate-180" />
{/* Text */}
<p className="text-foreground font-medium leading-relaxed">{value.text}</p>
</div>
))}
</div>
</div>
</div>
</section>
{/* Corporate Data Section */}
<section className="relative left-1/2 right-1/2 w-screen -translate-x-1/2 bg-[#f7f7f7] py-12 pb-16">
<div className="max-w-6xl mx-auto px-6 sm:px-8 space-y-3">
{/* Row 1: headings same level */}
<div className="grid grid-cols-1 lg:grid-cols-[1.2fr_0.8fr] gap-x-10 gap-y-2 items-start">
<h2 className="text-2xl font-bold text-foreground">Corporate Data</h2>
<div>
<h3 className="text-xl font-bold text-foreground mb-2">Address</h3>
<p className="text-muted-foreground font-semibold leading-relaxed">
3F Azabu Maruka Bldg., 3-8-2 Higashi Azabu,
<br />
Minato-ku, Tokyo 106-0044
<br />
Tel: 03-3560-1006 Fax: 03-3560-1007
</p> </p>
</div> </div>
</div> </div>
<div className="relative mx-auto h-[380px] w-full max-w-md lg:h-[440px] lg:max-w-none">
{/* Row 2: corporate data list | map (no stretch, no extra space below left column) */} <Image
<div className="grid grid-cols-1 lg:grid-cols-[1.2fr_0.8fr] gap-x-10 gap-y-2 items-start"> src="/assets/images/About us.png"
<div className="space-y-2"> alt="Assist Solutions team in Tokyo"
<div> fill
<h3 className="text-xl font-semibold text-foreground flex items-center gap-2"> priority
<span className="h-6 w-1 rounded-full bg-primary" /> className="object-contain drop-shadow-lg"
Representative Director sizes="(max-width: 1024px) 90vw, 45vw"
</h3> />
<p className="text-muted-foreground font-semibold mt-0.5">Daisuke Nagakawa</p>
</div>
<div>
<h3 className="text-xl font-semibold text-foreground flex items-center gap-2">
<span className="h-6 w-1 rounded-full bg-primary" />
Employees
</h3>
<p className="text-muted-foreground font-semibold mt-0.5">
21 Staff Members (as of March 31st, 2025)
</p>
</div>
<div>
<h3 className="text-xl font-semibold text-foreground flex items-center gap-2">
<span className="h-6 w-1 rounded-full bg-primary" />
Established
</h3>
<p className="text-muted-foreground font-semibold mt-0.5">March 8, 2002</p>
</div>
<div>
<h3 className="text-xl font-semibold text-foreground flex items-center gap-2">
<span className="h-6 w-1 rounded-full bg-primary" />
Paid in Capital
</h3>
<p className="text-muted-foreground font-semibold mt-0.5">40,000,000 JPY</p>
</div>
<div>
<h3 className="text-xl font-semibold text-foreground flex items-center gap-2">
<span className="h-6 w-1 rounded-full bg-primary" />
Business Hours
</h3>
<div className="text-muted-foreground font-semibold mt-0.5 space-y-0.5">
<p>Mon - Fri 9:30AM - 6:00PM Customer Support Team</p>
<p>Mon - Fri 9:30AM - 6:00PM In-office Tech Support Team</p>
<p>Mon - Sat 10:00AM - 9:00PM Onsite Tech Support Team</p>
</div>
</div>
</div>
<div className="rounded-2xl overflow-hidden w-full min-h-[320px]">
<iframe
title="Assist Solutions Corp Map"
src="https://www.google.com/maps?q=Assist+Solutions+Corp,+3-8-2+Higashi+Azabu,+Minato-ku,+Tokyo&output=embed"
className="w-full h-[320px] block"
loading="lazy"
allowFullScreen
referrerPolicy="no-referrer-when-downgrade"
/>
</div>
</div> </div>
</div> </div>
</section> </div>
</section>
);
}
function ServicesSection() {
return (
<section className={`${SECTION_BASE} bg-background py-16 sm:py-20`}>
<div className="mx-auto max-w-6xl px-6 sm:px-8">
<div className="cp-stagger-children mb-10 max-w-xl">
<h2 className="text-display-sm font-bold font-display text-foreground">What We Do</h2>
<p className="mt-3 leading-relaxed text-muted-foreground">
End-to-end IT services designed for the international community in Japan all in
English.
</p>
</div>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
{services.map(service => {
const Icon = service.icon;
return (
<div
key={service.title}
className="group cp-card-hover-lift rounded-2xl border border-border/60 bg-card p-6 hover:border-primary/20"
>
<div className="mb-4 inline-flex h-11 w-11 items-center justify-center rounded-xl bg-primary/10 text-primary transition-colors group-hover:bg-primary group-hover:text-primary-foreground">
<Icon className="h-5 w-5" />
</div>
<h3 className="text-lg font-semibold text-foreground">{service.title}</h3>
<p className="mt-2 text-sm leading-relaxed text-muted-foreground">
{service.description}
</p>
</div>
);
})}
</div>
</div>
</section>
);
}
function ValuesSection() {
return (
<section className={`${SECTION_BASE} bg-surface-sunken py-16 sm:py-20`}>
<div className="mx-auto max-w-6xl px-6 sm:px-8">
<div className="cp-stagger-children mb-10 text-center">
<h2 className="text-display-sm font-bold font-display text-foreground">Our Values</h2>
<p className="mx-auto mt-3 max-w-lg leading-relaxed text-muted-foreground">
These principles guide how we serve customers, support our community, and advance our
craft every day.
</p>
</div>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-5">
{values.map(value => {
const Icon = value.icon;
return (
<div
key={value.title}
className="group cp-card-hover-lift rounded-2xl border border-border/40 bg-card p-5"
>
<div
className={`mb-3 inline-flex h-10 w-10 items-center justify-center rounded-lg ${value.accent}`}
>
<Icon className="h-5 w-5" />
</div>
<h3 className="mb-1.5 text-sm font-bold uppercase tracking-wide text-foreground/80">
{value.title}
</h3>
<p className="text-sm leading-relaxed text-muted-foreground">{value.text}</p>
</div>
);
})}
</div>
</div>
</section>
);
}
function CorporateSection() {
return (
<section className={`${SECTION_BASE} bg-background py-16 sm:py-20`}>
<div className="mx-auto max-w-6xl px-6 sm:px-8">
<div className="cp-stagger-children mb-10">
<h2 className="text-display-sm font-bold font-display text-foreground">Corporate Data</h2>
</div>
<div className="grid grid-cols-1 gap-10 lg:grid-cols-5">
<CorporateDetails />
<CorporateMap />
</div>
</div>
</section>
);
}
function CorporateDetails() {
return (
<div className="space-y-8 lg:col-span-3">
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
{corporateDetails.map(item => {
const Icon = item.icon;
return (
<div
key={item.label}
className="flex items-start gap-3 rounded-xl border border-border/40 bg-card p-4"
>
<div className="mt-0.5 flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-primary/8 text-primary">
<Icon className="h-4 w-4" />
</div>
<div className="min-w-0">
<p className="text-xs font-medium uppercase tracking-wider text-muted-foreground">
{item.label}
</p>
<p className="mt-0.5 font-semibold text-foreground">{item.value}</p>
</div>
</div>
);
})}
</div>
<div className="rounded-xl border border-border/40 bg-card p-5">
<h3 className="mb-3 text-sm font-bold uppercase tracking-wider text-foreground/80">
Business Hours
</h3>
<div className="space-y-2">
{businessHours.map(item => (
<div key={item.team} className="flex items-center justify-between gap-4 text-sm">
<span className="text-muted-foreground">{item.team}</span>
<span className="shrink-0 font-medium text-foreground">{item.hours}</span>
</div>
))}
</div>
</div>
<div className="flex items-start gap-3">
<MapPin className="mt-1 h-5 w-5 shrink-0 text-primary" />
<div>
<p className="font-semibold text-foreground">
3F Azabu Maruka Bldg., 3-8-2 Higashi Azabu
</p>
<p className="text-muted-foreground">Minato-ku, Tokyo 106-0044</p>
<p className="mt-1 flex items-center gap-1.5 text-sm text-muted-foreground">
<Phone className="h-3.5 w-3.5" />
<a href="tel:03-3560-1006" className="transition-colors hover:text-primary">
03-3560-1006
</a>
</p>
</div>
</div>
</div> </div>
); );
} }
export default AboutUsView; function CorporateMap() {
return (
<div className="lg:col-span-2">
<div className="sticky top-24 overflow-hidden rounded-2xl border border-border/40 shadow-sm">
<iframe
title="Assist Solutions Corp Map"
src="https://www.google.com/maps?q=Assist+Solutions+Corp,+3-8-2+Higashi+Azabu,+Minato-ku,+Tokyo&output=embed"
className="block h-[360px] w-full lg:h-[420px]"
loading="lazy"
allowFullScreen
referrerPolicy="no-referrer-when-downgrade"
/>
</div>
</div>
);
}
/* ─── Main view ─── */
/**
* AboutUsView - Corporate profile and company information
*
* Displays company background, services, values, and corporate data
* for Assist Solutions.
*/
export function AboutUsView() {
return (
<div>
<HeroSection />
<ServicesSection />
<ValuesSection />
<CorporateSection />
</div>
);
}

View File

@ -17,10 +17,8 @@ export interface HowItWorksProps {
} }
/** /**
* HowItWorks - Visual step-by-step process component * HowItWorks - Visual step-by-step process.
* * Horizontal on desktop, vertical stack on mobile.
* Displays a numbered process with icons and descriptions.
* Horizontal layout on desktop, vertical stack on mobile.
*/ */
export function HowItWorks({ export function HowItWorks({
title = "How It Works", title = "How It Works",
@ -29,50 +27,46 @@ export function HowItWorks({
className, className,
}: HowItWorksProps) { }: HowItWorksProps) {
return ( return (
<section className={cn("py-8", className)}> <section className={cn("py-6", className)}>
{/* Section Header */} {/* Header */}
<div className="text-center mb-8"> <div className="text-center mb-8">
<p className="text-sm font-semibold text-primary uppercase tracking-wider mb-2"> <p className="text-xs font-semibold text-primary uppercase tracking-wider mb-1.5">
{eyebrow} {eyebrow}
</p> </p>
<h2 className="text-2xl sm:text-3xl font-display font-semibold leading-tight tracking-tight text-foreground"> <h2 className="text-xl sm:text-2xl font-bold leading-tight tracking-tight text-foreground">
{title} {title}
</h2> </h2>
</div> </div>
{/* Steps Container */} {/* Steps */}
<div className="relative"> <div className="relative">
{/* Connection line - visible on md+ */} {/* Connection line — desktop */}
<div <div
className="hidden md:block absolute top-8 left-[10%] right-[10%] h-0.5 bg-gradient-to-r from-transparent via-border to-transparent" className="hidden md:block absolute top-8 left-[12%] right-[12%] h-px bg-border"
aria-hidden="true" aria-hidden="true"
/> />
{/* Steps Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 md:gap-4"> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 md:gap-4">
{steps.map((step, index) => ( {steps.map((step, index) => (
<div key={index} className="relative flex flex-col items-center text-center group"> <div key={index} className="relative flex flex-col items-center text-center group">
{/* Step Number Badge */} {/* Icon */}
<div className="relative mb-4"> <div className="relative mb-3">
{/* Icon Container */} <div className="w-14 h-14 rounded-xl bg-card border border-border shadow-sm flex items-center justify-center text-primary group-hover:border-primary/30 group-hover:shadow-md transition-all duration-200">
<div className="w-16 h-16 rounded-2xl bg-card border border-border shadow-sm flex items-center justify-center text-primary group-hover:border-primary/40 group-hover:shadow-md transition-all duration-[var(--cp-duration-normal)]">
{step.icon} {step.icon}
</div> </div>
{/* Number Badge */} <div className="absolute -top-1.5 -right-1.5 w-5 h-5 rounded-full bg-primary text-primary-foreground text-[10px] font-bold flex items-center justify-center">
<div className="absolute -top-2 -right-2 w-6 h-6 rounded-full bg-primary text-primary-foreground text-xs font-bold flex items-center justify-center shadow-sm">
{index + 1} {index + 1}
</div> </div>
</div> </div>
{/* Content */} <h3 className="text-sm font-semibold text-foreground mb-1">{step.title}</h3>
<h3 className="text-base font-semibold text-foreground mb-1.5">{step.title}</h3> <p className="text-xs text-muted-foreground leading-relaxed max-w-[180px]">
<p className="text-sm text-muted-foreground leading-relaxed max-w-[200px]">
{step.description} {step.description}
</p> </p>
{/* Vertical connector for mobile - between steps */} {/* Vertical connector — mobile */}
{index < steps.length - 1 && ( {index < steps.length - 1 && (
<div className="md:hidden w-0.5 h-6 bg-border mt-4" aria-hidden="true" /> <div className="md:hidden w-px h-5 bg-border mt-3" aria-hidden="true" />
)} )}
</div> </div>
))} ))}

View File

@ -25,10 +25,7 @@ export interface ServiceCTAProps {
} }
/** /**
* ServiceCTA - Reusable call-to-action section * ServiceCTA - Call-to-action section with decorative background.
*
* Gradient background CTA with decorative elements.
* Used at the bottom of service pages to drive conversion.
*/ */
export function ServiceCTA({ export function ServiceCTA({
eyebrow = "Get started in minutes", eyebrow = "Get started in minutes",
@ -40,38 +37,32 @@ export function ServiceCTA({
className, className,
}: ServiceCTAProps) { }: ServiceCTAProps) {
return ( return (
<section className={cn("relative text-center py-12 rounded-2xl overflow-hidden", className)}> <section className={cn("relative text-center py-10 rounded-2xl overflow-hidden", className)}>
{/* Background */} {/* Background */}
<div className="absolute inset-0 bg-gradient-to-br from-blue-500/10 via-primary/5 to-purple-500/10" /> <div className="absolute inset-0 bg-gradient-to-br from-primary/5 via-transparent to-primary/8" />
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_center,_color-mix(in_oklch,var(--color-indigo-500)_10%,transparent),_transparent_70%)]" />
{/* Decorative rings */} {/* Refined dot pattern */}
<div <div
className="absolute top-4 left-8 w-20 h-20 rounded-full border border-primary/10 cp-float hidden sm:block" className="absolute inset-0 pointer-events-none opacity-30"
aria-hidden="true" style={{
/> backgroundImage: `radial-gradient(circle at center, color-mix(in oklch, var(--primary) 12%, transparent) 0.6px, transparent 0.6px)`,
<div backgroundSize: "20px 20px",
className="absolute bottom-4 right-8 w-16 h-16 rounded-full border border-blue-500/10 cp-float-delayed hidden sm:block" }}
aria-hidden="true"
/> />
{/* Content */} {/* Content */}
<div className="relative"> <div className="relative">
{/* Eyebrow */} <div className="inline-flex items-center gap-1.5 text-xs font-medium text-primary mb-3 px-2.5 py-1 rounded-full bg-primary/10 border border-primary/15">
<div className="inline-flex items-center gap-1.5 text-xs font-medium text-primary mb-4 px-3 py-1.5 rounded-full bg-primary/10 border border-primary/20">
{eyebrowIcon} {eyebrowIcon}
{eyebrow} {eyebrow}
</div> </div>
{/* Headline */} <h2 className="text-xl sm:text-2xl font-bold leading-tight tracking-tight text-foreground mb-2">
<h2 className="text-2xl sm:text-3xl font-display font-semibold leading-tight tracking-tight text-foreground mb-3">
{headline} {headline}
</h2> </h2>
{/* Description */} <p className="text-sm text-muted-foreground mb-5 max-w-md mx-auto">{description}</p>
<p className="text-base text-muted-foreground mb-6 max-w-md mx-auto">{description}</p>
{/* Actions */}
<div className="flex flex-col sm:flex-row items-center justify-center gap-3"> <div className="flex flex-col sm:flex-row items-center justify-center gap-3">
{primaryAction.onClick ? ( {primaryAction.onClick ? (
<Button <Button

View File

@ -1,7 +1,7 @@
"use client"; "use client";
import { useState } from "react"; import { useState } from "react";
import { ChevronDown, ChevronUp } from "lucide-react"; import { ChevronDown } from "lucide-react";
import { cn } from "@/shared/utils/cn"; import { cn } from "@/shared/utils/cn";
export interface FAQItem { export interface FAQItem {
@ -17,9 +17,6 @@ export interface ServiceFAQProps {
defaultOpenIndex?: number | null; defaultOpenIndex?: number | null;
} }
/**
* Individual FAQ item with expand/collapse
*/
function FAQItemComponent({ function FAQItemComponent({
question, question,
answer, answer,
@ -32,19 +29,22 @@ function FAQItemComponent({
onToggle: () => void; onToggle: () => void;
}) { }) {
return ( return (
<div className="border-b border-border last:border-b-0"> <div className="border-b border-border/60 last:border-b-0">
<button <button
type="button" type="button"
onClick={onToggle} onClick={onToggle}
className="w-full py-4 flex items-start justify-between gap-3 text-left hover:text-primary transition-colors" className="w-full py-4 flex items-start justify-between gap-3 text-left group"
aria-expanded={isOpen} aria-expanded={isOpen}
> >
<span className="text-sm font-medium text-foreground">{question}</span> <span className="text-sm font-medium text-foreground group-hover:text-primary transition-colors">
{isOpen ? ( {question}
<ChevronUp className="h-4 w-4 text-muted-foreground flex-shrink-0 mt-0.5" /> </span>
) : ( <ChevronDown
<ChevronDown className="h-4 w-4 text-muted-foreground flex-shrink-0 mt-0.5" /> className={cn(
)} "h-4 w-4 text-muted-foreground flex-shrink-0 mt-0.5 transition-transform duration-200",
isOpen && "rotate-180"
)}
/>
</button> </button>
<div <div
className={cn( className={cn(
@ -61,10 +61,7 @@ function FAQItemComponent({
} }
/** /**
* ServiceFAQ - Reusable FAQ accordion section * ServiceFAQ - FAQ accordion section.
*
* Displays frequently asked questions with collapsible answers.
* Used at the bottom of service pages to address common concerns.
*/ */
export function ServiceFAQ({ export function ServiceFAQ({
title = "Frequently Asked Questions", title = "Frequently Asked Questions",
@ -78,19 +75,19 @@ export function ServiceFAQ({
if (items.length === 0) return null; if (items.length === 0) return null;
return ( return (
<section className={cn("py-8", className)}> <section className={cn("py-6", className)}>
{/* Section Header */} {/* Header */}
<div className="text-center mb-6"> <div className="text-center mb-6">
<p className="text-sm font-semibold text-primary uppercase tracking-wider mb-2"> <p className="text-xs font-semibold text-primary uppercase tracking-wider mb-1.5">
{eyebrow} {eyebrow}
</p> </p>
<h2 className="text-2xl sm:text-3xl font-display font-semibold leading-tight tracking-tight text-foreground"> <h2 className="text-xl sm:text-2xl font-bold leading-tight tracking-tight text-foreground">
{title} {title}
</h2> </h2>
</div> </div>
{/* FAQ Container */} {/* FAQ list */}
<div className="bg-card border border-border rounded-2xl px-5 shadow-sm"> <div className="bg-card border border-border rounded-xl px-5 shadow-sm">
{items.map((item, index) => ( {items.map((item, index) => (
<FAQItemComponent <FAQItemComponent
key={index} key={index}

View File

@ -1,7 +1,6 @@
"use client"; "use client";
import { useRef, useState, useEffect } from "react"; import { useRef, useState, useEffect } from "react";
import { CheckCircle } from "lucide-react";
import { cn } from "@/shared/utils"; import { cn } from "@/shared/utils";
export interface HighlightFeature { export interface HighlightFeature {
@ -18,16 +17,15 @@ interface ServiceHighlightsProps {
function HighlightItem({ icon, title, description, highlight }: HighlightFeature) { function HighlightItem({ icon, title, description, highlight }: HighlightFeature) {
return ( return (
<div className="group relative flex items-start gap-3.5 p-4 rounded-xl border border-border/40 hover:bg-muted/40 transition-colors duration-200"> <div className="group relative flex items-start gap-3 p-4 rounded-xl bg-card border border-border/60 hover:border-primary/30 hover:shadow-sm transition-all duration-200">
<div className="flex h-9 w-9 items-center justify-center rounded-lg bg-primary/10 text-primary flex-shrink-0 mt-0.5"> <div className="flex h-9 w-9 items-center justify-center rounded-lg bg-primary/8 text-primary flex-shrink-0 mt-0.5">
{icon} {icon}
</div> </div>
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-1"> <div className="flex items-center gap-2 mb-0.5">
<h3 className="font-semibold text-foreground text-sm">{title}</h3> <h3 className="font-semibold text-foreground text-sm">{title}</h3>
{highlight && ( {highlight && (
<span className="inline-flex items-center gap-1 py-0.5 px-2 rounded-full bg-primary/10 text-[10px] font-semibold text-primary whitespace-nowrap"> <span className="inline-flex py-0.5 px-1.5 rounded-md bg-primary/8 text-[10px] font-semibold text-primary whitespace-nowrap">
<CheckCircle className="h-3 w-3" />
{highlight} {highlight}
</span> </span>
)} )}
@ -38,26 +36,20 @@ function HighlightItem({ icon, title, description, highlight }: HighlightFeature
); );
} }
/**
* Mobile Carousel Item - Compact card for horizontal scrolling
*/
function MobileCarouselItem({ icon, title, description, highlight }: HighlightFeature) { function MobileCarouselItem({ icon, title, description, highlight }: HighlightFeature) {
return ( return (
<div className="flex-shrink-0 w-[280px] snap-center"> <div className="flex-shrink-0 w-[260px] snap-center">
<div className="h-full p-4 rounded-xl bg-gradient-to-br from-muted/40 to-muted/20 border border-border/40 shadow-sm"> <div className="h-full p-4 rounded-xl bg-card border border-border/60 shadow-sm">
{/* Top row: Icon + Highlight badge */}
<div className="flex items-center justify-between gap-2 mb-3"> <div className="flex items-center justify-between gap-2 mb-3">
<div className="flex h-9 w-9 items-center justify-center rounded-lg bg-primary/10 text-primary flex-shrink-0"> <div className="flex h-9 w-9 items-center justify-center rounded-lg bg-primary/8 text-primary flex-shrink-0">
{icon} {icon}
</div> </div>
{highlight && ( {highlight && (
<span className="inline-flex items-center gap-1 py-0.5 px-2 rounded-full bg-primary/10 text-[10px] font-bold text-primary uppercase tracking-wide"> <span className="inline-flex py-0.5 px-1.5 rounded-md bg-primary/8 text-[10px] font-bold text-primary uppercase tracking-wide">
{highlight} {highlight}
</span> </span>
)} )}
</div> </div>
{/* Content */}
<h3 className="font-semibold text-foreground text-sm mb-1">{title}</h3> <h3 className="font-semibold text-foreground text-sm mb-1">{title}</h3>
<p className="text-xs text-muted-foreground leading-relaxed line-clamp-3">{description}</p> <p className="text-xs text-muted-foreground leading-relaxed line-clamp-3">{description}</p>
</div> </div>
@ -66,24 +58,20 @@ function MobileCarouselItem({ icon, title, description, highlight }: HighlightFe
} }
/** /**
* ServiceHighlights * ServiceHighlights - Grid-based feature highlights.
* * Mobile: horizontal scrolling carousel. Desktop: 3-column grid.
* A clean, grid-based layout for displaying service features/highlights.
* On mobile: horizontal scrolling carousel with snap points.
* On desktop: grid layout.
*/ */
export function ServiceHighlights({ features, className = "" }: ServiceHighlightsProps) { export function ServiceHighlights({ features, className = "" }: ServiceHighlightsProps) {
const scrollContainerRef = useRef<HTMLDivElement>(null); const scrollContainerRef = useRef<HTMLDivElement>(null);
const [activeIndex, setActiveIndex] = useState(0); const [activeIndex, setActiveIndex] = useState(0);
// Track scroll position to update active dot indicator
useEffect(() => { useEffect(() => {
const container = scrollContainerRef.current; const container = scrollContainerRef.current;
if (!container) return; if (!container) return;
const handleScroll = () => { const handleScroll = () => {
const scrollLeft = container.scrollLeft; const scrollLeft = container.scrollLeft;
const itemWidth = 280 + 12; // card width + gap const itemWidth = 260 + 12;
const newIndex = Math.min(Math.round(scrollLeft / itemWidth), features.length - 1); const newIndex = Math.min(Math.round(scrollLeft / itemWidth), features.length - 1);
setActiveIndex(prev => (prev === newIndex ? prev : newIndex)); setActiveIndex(prev => (prev === newIndex ? prev : newIndex));
}; };
@ -92,36 +80,29 @@ export function ServiceHighlights({ features, className = "" }: ServiceHighlight
return () => container.removeEventListener("scroll", handleScroll); return () => container.removeEventListener("scroll", handleScroll);
}, [features.length]); }, [features.length]);
// Scroll to specific item when dot is clicked
const scrollToIndex = (index: number) => { const scrollToIndex = (index: number) => {
const container = scrollContainerRef.current; const container = scrollContainerRef.current;
if (!container) return; if (!container) return;
const itemWidth = 260 + 12;
const itemWidth = 280 + 12; // card width + gap container.scrollTo({ left: index * itemWidth, behavior: "smooth" });
container.scrollTo({
left: index * itemWidth,
behavior: "smooth",
});
}; };
return ( return (
<> <>
{/* Mobile: Horizontal scrolling carousel */} {/* Mobile: Horizontal carousel */}
<div className={cn("md:hidden", className)}> <div className={cn("md:hidden", className)}>
{/* Scroll container */}
<div <div
ref={scrollContainerRef} ref={scrollContainerRef}
className="flex gap-3 overflow-x-auto pb-4 -mx-4 px-4 snap-x snap-mandatory scrollbar-hide touch-pan-x" className="flex gap-3 overflow-x-auto pb-3 -mx-4 px-4 snap-x snap-mandatory scrollbar-hide touch-pan-x"
> >
{features.map((feature, index) => ( {features.map((feature, index) => (
<MobileCarouselItem key={index} {...feature} /> <MobileCarouselItem key={index} {...feature} />
))} ))}
{/* End spacer for last item visibility */}
<div className="flex-shrink-0 w-1" aria-hidden="true" /> <div className="flex-shrink-0 w-1" aria-hidden="true" />
</div> </div>
{/* Dot indicators */} {/* Dot indicators */}
<div className="flex justify-center gap-1.5 mt-2"> <div className="flex justify-center gap-1.5 mt-1.5">
{features.map((_, index) => ( {features.map((_, index) => (
<button <button
key={index} key={index}
@ -131,20 +112,15 @@ export function ServiceHighlights({ features, className = "" }: ServiceHighlight
className={cn( className={cn(
"h-1.5 rounded-full transition-all duration-300", "h-1.5 rounded-full transition-all duration-300",
activeIndex === index activeIndex === index
? "w-6 bg-primary" ? "w-5 bg-primary"
: "w-1.5 bg-muted-foreground/30 hover:bg-muted-foreground/50" : "w-1.5 bg-muted-foreground/25 hover:bg-muted-foreground/40"
)} )}
/> />
))} ))}
</div> </div>
{/* Swipe hint - only show initially */}
<p className="text-[10px] text-muted-foreground/60 text-center mt-2">
Swipe to explore features
</p>
</div> </div>
{/* Desktop: Grid layout */} {/* Desktop: Grid */}
<div className={cn("hidden md:grid md:grid-cols-2 lg:grid-cols-3 gap-3", className)}> <div className={cn("hidden md:grid md:grid-cols-2 lg:grid-cols-3 gap-3", className)}>
{features.map((feature, index) => ( {features.map((feature, index) => (
<HighlightItem key={index} {...feature} /> <HighlightItem key={index} {...feature} />

View File

@ -38,16 +38,16 @@ export function ServicesHero({
return ( return (
<div <div
className={cn( className={cn(
"flex flex-col gap-3 mb-10", "flex flex-col gap-2",
alignmentMap[align], alignmentMap[align],
className, className,
align === "center" ? "mx-auto max-w-3xl" : "" align === "center" ? "mx-auto max-w-2xl" : ""
)} )}
> >
{eyebrow ? ( {eyebrow ? (
<div <div
className={cn( className={cn(
"text-sm font-semibold text-primary uppercase tracking-wider", "text-sm font-semibold text-primary uppercase tracking-wider mb-1",
animationClasses animationClasses
)} )}
style={animated ? { animationDelay: "0ms" } : undefined} style={animated ? { animationDelay: "0ms" } : undefined}
@ -57,7 +57,7 @@ export function ServicesHero({
) : null} ) : null}
<h1 <h1
className={cn( className={cn(
"text-3xl sm:text-4xl lg:text-5xl text-foreground leading-tight font-extrabold", "text-2xl sm:text-3xl lg:text-4xl text-foreground leading-tight font-bold tracking-tight",
displayFont && "font-display", displayFont && "font-display",
animationClasses animationClasses
)} )}
@ -67,7 +67,7 @@ export function ServicesHero({
</h1> </h1>
<p <p
className={cn( className={cn(
"text-base md:text-lg text-muted-foreground leading-relaxed max-w-xl", "text-sm md:text-base text-muted-foreground leading-relaxed max-w-lg",
align === "center" && "mx-auto", align === "center" && "mx-auto",
animationClasses animationClasses
)} )}
@ -77,7 +77,7 @@ export function ServicesHero({
</p> </p>
{children ? ( {children ? (
<div <div
className={cn("mt-2 w-full", animationClasses)} className={cn("mt-1 w-full", animationClasses)}
style={animated ? { animationDelay: "150ms" } : undefined} style={animated ? { animationDelay: "150ms" } : undefined}
> >
{children} {children}

View File

@ -7,12 +7,8 @@ import {
ShieldCheck, ShieldCheck,
ArrowRight, ArrowRight,
Phone, Phone,
CheckCircle2,
Globe,
Headphones,
Building2, Building2,
Wrench, Wrench,
Zap,
Check, Check,
} from "lucide-react"; } from "lucide-react";
@ -25,10 +21,37 @@ interface ServicesOverviewContentProps {
showCta?: boolean; showCta?: boolean;
} }
// Service data with enhanced information const serviceAccents = {
internet: {
icon: "bg-sky-500/10 text-sky-600",
badge: "bg-sky-500/10 text-sky-700 border-sky-500/20",
stripe: "from-sky-500 to-blue-600",
},
sim: {
icon: "bg-emerald-500/10 text-emerald-600",
badge: "bg-emerald-500/10 text-emerald-700 border-emerald-500/20",
stripe: "from-emerald-500 to-teal-600",
},
vpn: {
icon: "bg-violet-500/10 text-violet-600",
badge: "bg-violet-500/10 text-violet-700 border-violet-500/20",
stripe: "from-violet-500 to-purple-600",
},
business: {
icon: "bg-slate-500/10 text-slate-600",
badge: "bg-slate-500/10 text-slate-700 border-slate-500/20",
stripe: "from-slate-500 to-slate-700",
},
onsite: {
icon: "bg-amber-500/10 text-amber-600",
badge: "bg-amber-500/10 text-amber-700 border-amber-500/20",
stripe: "from-amber-500 to-orange-600",
},
} as const;
const services = [ const services = [
{ {
id: "internet", id: "internet" as const,
icon: Wifi, icon: Wifi,
title: "Internet", title: "Internet",
subtitle: "Fiber Optic", subtitle: "Fiber Optic",
@ -40,7 +63,7 @@ const services = [
useBasePath: true, useBasePath: true,
}, },
{ {
id: "sim", id: "sim" as const,
icon: Smartphone, icon: Smartphone,
title: "SIM & eSIM", title: "SIM & eSIM",
subtitle: "Mobile Data", subtitle: "Mobile Data",
@ -53,7 +76,7 @@ const services = [
useBasePath: true, useBasePath: true,
}, },
{ {
id: "vpn", id: "vpn" as const,
icon: ShieldCheck, icon: ShieldCheck,
title: "VPN Router", title: "VPN Router",
subtitle: "Streaming Access", subtitle: "Streaming Access",
@ -65,7 +88,7 @@ const services = [
useBasePath: true, useBasePath: true,
}, },
{ {
id: "business", id: "business" as const,
icon: Building2, icon: Building2,
title: "Business", title: "Business",
subtitle: "Enterprise IT", subtitle: "Enterprise IT",
@ -76,7 +99,7 @@ const services = [
fixedPath: "/services/business", fixedPath: "/services/business",
}, },
{ {
id: "onsite", id: "onsite" as const,
icon: Wrench, icon: Wrench,
title: "Onsite Support", title: "Onsite Support",
subtitle: "Tech Assistance", subtitle: "Tech Assistance",
@ -89,10 +112,10 @@ const services = [
]; ];
/** /**
* ServicesOverviewContent - Enhanced services overview with rich visual design. * ServicesOverviewContent - Services overview with clean, modern card design.
* *
* Features full-width sections, gradient backgrounds, and polished card treatments * Uses color-coded accents per service type and a refined layout
* matching the website's landing page aesthetic. * with proper spacing and visual hierarchy.
*/ */
export function ServicesOverviewContent({ export function ServicesOverviewContent({
basePath, basePath,
@ -100,249 +123,221 @@ export function ServicesOverviewContent({
showCta = true, showCta = true,
}: ServicesOverviewContentProps) { }: ServicesOverviewContentProps) {
return ( return (
<div className="relative"> <div>
{/* Hero Section with gradient background */} {/* Hero Section */}
{showHero && ( {showHero && (
<section className="relative left-1/2 right-1/2 w-screen -translate-x-1/2 overflow-hidden bg-gradient-to-br from-slate-50 via-white to-sky-50/80 pt-8 pb-16"> <section className="relative overflow-hidden pt-8 pb-12">
{/* Dot grid pattern */} {/* Refined dot grid */}
<div <div
className="absolute inset-0 pointer-events-none" className="absolute inset-0 pointer-events-none opacity-40"
style={{ style={{
backgroundImage: `radial-gradient(circle at center, color-mix(in oklch, var(--primary) 12%, transparent) 1px, transparent 1px)`, backgroundImage: `radial-gradient(circle at center, color-mix(in oklch, var(--primary) 10%, transparent) 0.8px, transparent 0.8px)`,
backgroundSize: "24px 24px", backgroundSize: "28px 28px",
}} }}
/> />
{/* Gradient orb accent */} {/* Gradient orb — top right, subtle */}
<div <div
className="absolute -top-32 -right-32 w-96 h-96 rounded-full pointer-events-none opacity-60" className="absolute -top-20 -right-20 w-80 h-80 rounded-full pointer-events-none opacity-50"
style={{ style={{
background: background:
"radial-gradient(circle, color-mix(in oklch, var(--info) 30%, transparent) 0%, transparent 70%)", "radial-gradient(circle, color-mix(in oklch, var(--info) 20%, transparent) 0%, transparent 65%)",
}} }}
/> />
<div className="relative max-w-6xl mx-auto px-4 sm:px-6 lg:px-8"> {/* Gradient orb — bottom left, complementary */}
{/* Eyebrow badge */} <div
<div className="flex justify-center mb-6 animate-in fade-in slide-in-from-bottom-4 duration-500"> className="absolute -bottom-16 -left-16 w-64 h-64 rounded-full pointer-events-none opacity-30"
<span className="inline-flex items-center gap-2 rounded-full bg-white/80 backdrop-blur-sm border border-primary/20 px-4 py-2 text-sm text-primary font-medium shadow-sm"> style={{
<CheckCircle2 className="h-4 w-4" /> background:
Full English Support "radial-gradient(circle, color-mix(in oklch, var(--primary) 18%, transparent) 0%, transparent 65%)",
</span> }}
</div> />
{/* Main heading */} <div className="relative max-w-5xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
<p className="text-sm font-semibold text-primary uppercase tracking-wider mb-3 animate-in fade-in slide-in-from-bottom-4 duration-500">
Full English Support
</p>
<h1 <h1
className="text-4xl sm:text-5xl lg:text-6xl font-extrabold text-center text-foreground mb-6 tracking-tight animate-in fade-in slide-in-from-bottom-4 duration-500" className="text-3xl sm:text-4xl lg:text-5xl font-bold text-foreground tracking-tight animate-in fade-in slide-in-from-bottom-4 duration-500"
style={{ animationDelay: "100ms" }} style={{ animationDelay: "80ms" }}
> >
Our Services Our Services
</h1> </h1>
{/* Description */}
<p <p
className="text-lg sm:text-xl text-muted-foreground text-center max-w-2xl mx-auto mb-10 leading-relaxed animate-in fade-in slide-in-from-bottom-4 duration-500" className="text-base sm:text-lg text-muted-foreground mt-3 max-w-xl mx-auto leading-relaxed animate-in fade-in slide-in-from-bottom-4 duration-500"
style={{ animationDelay: "200ms" }} style={{ animationDelay: "160ms" }}
> >
Connectivity and support solutions designed for Japan&apos;s international community. Connectivity and support solutions designed for Japan&apos;s international community.
One provider for all your needs.
</p> </p>
{/* Value propositions */}
<div
className="flex flex-wrap justify-center gap-4 sm:gap-8 animate-in fade-in slide-in-from-bottom-4 duration-500"
style={{ animationDelay: "300ms" }}
>
<div className="flex items-center gap-2 px-4 py-2 rounded-full bg-white/60 backdrop-blur-sm border border-border/40">
<Globe className="h-4 w-4 text-primary" />
<span className="text-sm font-medium text-foreground">
One provider, all services
</span>
</div>
<div className="flex items-center gap-2 px-4 py-2 rounded-full bg-white/60 backdrop-blur-sm border border-border/40">
<Headphones className="h-4 w-4 text-primary" />
<span className="text-sm font-medium text-foreground">English support</span>
</div>
<div className="flex items-center gap-2 px-4 py-2 rounded-full bg-white/60 backdrop-blur-sm border border-border/40">
<Zap className="h-4 w-4 text-primary" />
<span className="text-sm font-medium text-foreground">Fast activation</span>
</div>
</div>
</div> </div>
{/* Gradient fade to services section */}
<div className="absolute bottom-0 left-0 right-0 h-24 bg-gradient-to-b from-transparent to-[#f7f7f7] pointer-events-none" />
</section> </section>
)} )}
{/* Services Section */} {/* Services Grid */}
<section <section className={showHero ? "" : "pt-2"}>
className={`relative left-1/2 right-1/2 w-screen -translate-x-1/2 bg-[#f7f7f7] ${showHero ? "pt-8" : "pt-12"} pb-16`} <div className="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8">
> {/* Featured Services — Internet & SIM */}
{/* Subtle pattern overlay */} <div className="grid grid-cols-1 lg:grid-cols-2 gap-4 mb-4">
<div
className="absolute inset-0 pointer-events-none opacity-30"
style={{
backgroundImage: `radial-gradient(circle at center, color-mix(in oklch, var(--muted-foreground) 8%, transparent) 1px, transparent 1px)`,
backgroundSize: "32px 32px",
}}
/>
<div className="relative max-w-6xl mx-auto px-4 sm:px-6 lg:px-8">
{/* Section header */}
<div className="flex items-center justify-between mb-8">
<h2 className="text-2xl sm:text-3xl font-bold text-foreground">Choose Your Service</h2>
<div className="hidden sm:flex items-center gap-2 text-sm text-muted-foreground">
<CheckCircle2 className="h-4 w-4 text-primary" />
<span>No hidden fees</span>
</div>
</div>
{/* Featured services - Top row (Internet & SIM) */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
{services.slice(0, 2).map((service, index) => { {services.slice(0, 2).map((service, index) => {
const Icon = service.icon; const Icon = service.icon;
const href = service.useBasePath ? `${basePath}/${service.id}` : service.fixedPath; const href = service.useBasePath ? `${basePath}/${service.id}` : service.fixedPath;
const accent = serviceAccents[service.id];
return ( return (
<Link <Link
key={service.id} key={service.id}
href={href!} href={href!}
className="group relative overflow-hidden rounded-2xl bg-white border border-border/60 shadow-md p-6 sm:p-8 transition-all duration-300 hover:-translate-y-1 hover:shadow-lg animate-in fade-in slide-in-from-bottom-8 duration-700" className="group relative overflow-hidden rounded-2xl bg-card border border-border shadow-sm hover:shadow-md transition-all duration-300 animate-in fade-in slide-in-from-bottom-6 duration-600"
style={{ animationDelay: `${index * 100}ms` }} style={{ animationDelay: `${index * 80}ms` }}
> >
{/* Badge */} {/* Top accent stripe */}
{service.badge && ( <div className={`h-1 w-full bg-gradient-to-r ${accent.stripe}`} />
<div className="absolute top-4 right-4 sm:top-6 sm:right-6">
<span className="inline-flex items-center rounded-full bg-primary px-3 py-1 text-xs font-bold text-white uppercase tracking-wide shadow-sm"> <div className="p-6">
{service.badge} {/* Badge */}
</span> {service.badge && (
<div className="absolute top-5 right-5">
<span
className={`inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold ${accent.badge}`}
>
{service.badge}
</span>
</div>
)}
{/* Icon + subtitle */}
<div
className={`w-11 h-11 rounded-xl ${accent.icon} flex items-center justify-center mb-4`}
>
<Icon className="h-5.5 w-5.5" />
</div> </div>
)}
{/* Icon */} <p className="text-[11px] font-semibold uppercase tracking-wider text-muted-foreground mb-1">
<div className="w-14 h-14 sm:w-16 sm:h-16 rounded-2xl bg-primary/10 flex items-center justify-center mb-5 transition-transform duration-300 group-hover:scale-105"> {service.subtitle}
<Icon className="h-7 w-7 sm:h-8 sm:w-8 text-primary" /> </p>
</div>
{/* Subtitle */} <h3 className="text-xl font-bold text-foreground mb-2">{service.title}</h3>
<span className="text-xs font-semibold uppercase tracking-wider text-muted-foreground mb-1 block">
{service.subtitle}
</span>
{/* Title */} <p className="text-sm text-muted-foreground leading-relaxed mb-4">
<h3 className="text-xl sm:text-2xl font-bold text-foreground mb-3"> {service.description}
{service.title} </p>
</h3>
{/* Description */} {/* Price + Features row */}
<p className="text-muted-foreground leading-relaxed mb-5"> <div className="flex items-end justify-between gap-4">
{service.description} <div className="flex flex-wrap gap-1.5">
</p> {service.features.map(feature => (
<span
key={feature}
className="inline-flex items-center gap-1 rounded-md bg-muted/60 px-2 py-1 text-[11px] font-medium text-foreground/80"
>
<Check className="h-3 w-3 text-primary/70" />
{feature}
</span>
))}
</div>
{/* Features */} {service.price && (
<div className="flex flex-wrap gap-2 mb-5"> <div className="text-right flex-shrink-0">
{service.features.map(feature => ( <span className="text-lg font-bold text-foreground">{service.price}</span>
<span <span className="text-xs text-muted-foreground">{service.priceUnit}</span>
key={feature} </div>
className="inline-flex items-center gap-1.5 rounded-full bg-muted px-3 py-1.5 text-xs font-medium text-foreground" )}
> </div>
<Check className="h-3 w-3 text-primary" />
{feature}
</span>
))}
</div>
{/* CTA */} {/* Hover CTA */}
<div className="flex items-center gap-2 text-primary font-semibold group-hover:gap-3 transition-all duration-300"> <div className="flex items-center gap-1.5 text-primary font-medium text-sm mt-4 group-hover:gap-2.5 transition-all duration-300">
<span>View Plans</span> <span>View Plans</span>
<ArrowRight className="h-4 w-4 transition-transform group-hover:translate-x-1" /> <ArrowRight className="h-3.5 w-3.5 transition-transform group-hover:translate-x-0.5" />
</div>
</div> </div>
</Link> </Link>
); );
})} })}
</div> </div>
{/* Secondary services - Bottom row */} {/* Secondary Services — VPN, Business, Onsite */}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-5"> <div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
{services.slice(2).map((service, index) => { {services.slice(2).map((service, index) => {
const Icon = service.icon; const Icon = service.icon;
const href = service.useBasePath ? `${basePath}/${service.id}` : service.fixedPath; const href = service.useBasePath ? `${basePath}/${service.id}` : service.fixedPath;
const accent = serviceAccents[service.id];
return ( return (
<Link <Link
key={service.id} key={service.id}
href={href!} href={href!}
className="group relative overflow-hidden rounded-2xl bg-white border border-border/60 shadow-sm p-5 sm:p-6 transition-all duration-300 hover:-translate-y-1 hover:shadow-md animate-in fade-in slide-in-from-bottom-8 duration-700" className="group relative overflow-hidden rounded-xl bg-card border border-border shadow-sm hover:shadow-md transition-all duration-300 animate-in fade-in slide-in-from-bottom-6 duration-600"
style={{ animationDelay: `${(index + 2) * 100}ms` }} style={{ animationDelay: `${(index + 2) * 80}ms` }}
> >
<div className="flex items-start gap-4"> {/* Left accent stripe */}
{/* Icon */} <div
<div className="w-12 h-12 rounded-xl bg-primary/10 flex items-center justify-center flex-shrink-0 transition-transform duration-300 group-hover:scale-105"> className={`absolute left-0 top-0 bottom-0 w-1 bg-gradient-to-b ${accent.stripe}`}
<Icon className="h-6 w-6 text-primary" /> />
</div>
<div className="flex-1 min-w-0"> <div className="p-5 pl-6">
{/* Subtitle */} <div className="flex items-start gap-3.5">
<span className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground mb-0.5 block"> <div
{service.subtitle} className={`w-10 h-10 rounded-lg ${accent.icon} flex items-center justify-center flex-shrink-0`}
</span> >
<Icon className="h-5 w-5" />
</div>
<div className="flex-1 min-w-0">
<p className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground mb-0.5">
{service.subtitle}
</p>
<h3 className="text-base font-bold text-foreground mb-1.5">
{service.title}
</h3>
<p className="text-xs text-muted-foreground leading-relaxed line-clamp-2 mb-3">
{service.description}
</p>
{/* Title */} {/* Feature pills */}
<h3 className="text-lg font-bold text-foreground mb-2">{service.title}</h3> <div className="flex flex-wrap gap-1">
{service.features.map(feature => (
{/* Description */} <span
<p className="text-sm text-muted-foreground leading-relaxed mb-3 line-clamp-2"> key={feature}
{service.description} className="inline-flex items-center rounded bg-muted/50 px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground"
</p> >
{feature}
{/* Features as pills */} </span>
<div className="flex flex-wrap gap-1.5"> ))}
{service.features.slice(0, 2).map(feature => ( </div>
<span
key={feature}
className="inline-flex items-center rounded-full bg-muted px-2 py-0.5 text-[10px] font-medium text-foreground"
>
{feature}
</span>
))}
</div> </div>
</div> </div>
</div>
{/* Hover arrow indicator */} {/* Hover arrow */}
<div className="absolute bottom-4 right-4 opacity-0 group-hover:opacity-100 transition-opacity duration-300"> <div className="absolute bottom-4 right-4 opacity-0 group-hover:opacity-100 transition-opacity duration-200">
<ArrowRight className="h-4 w-4 text-primary" /> <ArrowRight className="h-4 w-4 text-primary" />
</div>
</div> </div>
</Link> </Link>
); );
})} })}
</div> </div>
</div> </div>
{/* Gradient fade to CTA section */}
<div className="absolute bottom-0 left-0 right-0 h-16 bg-gradient-to-b from-transparent to-white pointer-events-none" />
</section> </section>
{/* CTA Section */} {/* CTA Section */}
{showCta && ( {showCta && (
<section className="relative left-1/2 right-1/2 w-screen -translate-x-1/2 bg-white py-12 sm:py-16"> <section className="py-14">
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 text-center"> <div className="max-w-3xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
<h2 className="text-2xl sm:text-3xl font-bold text-foreground mb-4"> <h2 className="text-xl sm:text-2xl font-bold text-foreground mb-3">
Need help choosing? Need help choosing?
</h2> </h2>
<p className="text-muted-foreground mb-8 max-w-lg mx-auto leading-relaxed"> <p className="text-sm text-muted-foreground mb-6 max-w-md mx-auto leading-relaxed">
Our bilingual team is ready to help you find the perfect solution for your needs. Get Our bilingual team can help you find the right solution. Get personalized
personalized recommendations in English. recommendations in English.
</p> </p>
<div className="flex flex-col sm:flex-row items-center justify-center gap-4"> <div className="flex flex-col sm:flex-row items-center justify-center gap-3">
<Link <Link
href="/contact" href="/contact"
className="inline-flex items-center gap-2 rounded-full bg-primary px-6 py-3 font-semibold text-white hover:bg-primary/90 transition-colors shadow-md" className="inline-flex items-center gap-2 rounded-full bg-primary px-5 py-2.5 text-sm font-semibold text-white hover:bg-primary/90 transition-colors shadow-sm"
> >
Get in Touch Get in Touch
<ArrowRight className="h-4 w-4" /> <ArrowRight className="h-3.5 w-3.5" />
</Link> </Link>
<a <a
href="tel:0120660470" href="tel:0120660470"

View File

@ -1,6 +1,6 @@
"use client"; "use client";
import { Home, Building2, Zap } from "lucide-react"; import { Home, Building2, Check } from "lucide-react";
import { Button } from "@/components/atoms/button"; import { Button } from "@/components/atoms/button";
import { CardBadge } from "@/features/services/components/base/CardBadge"; import { CardBadge } from "@/features/services/components/base/CardBadge";
import { cn } from "@/shared/utils"; import { cn } from "@/shared/utils";
@ -35,16 +35,22 @@ interface InternetOfferingCardProps {
const tierStyles = { const tierStyles = {
Silver: { Silver: {
card: "border-muted-foreground/20 bg-card", card: "border-border/60 bg-card",
accent: "text-muted-foreground", accent: "text-muted-foreground",
stripe: "bg-gradient-to-r from-slate-400 to-slate-500",
badge: "bg-slate-100 text-slate-600",
}, },
Gold: { Gold: {
card: "border-warning/30 bg-warning-soft/20", card: "border-warning/25 bg-warning-soft/10",
accent: "text-warning", accent: "text-warning",
stripe: "bg-gradient-to-r from-amber-400 to-yellow-500",
badge: "bg-amber-50 text-amber-700",
}, },
Platinum: { Platinum: {
card: "border-primary/30 bg-info-soft/20", card: "border-primary/25 bg-info-soft/10",
accent: "text-primary", accent: "text-primary",
stripe: "bg-gradient-to-r from-sky-500 to-blue-600",
badge: "bg-sky-50 text-sky-700",
}, },
} as const; } as const;
@ -71,38 +77,50 @@ export function InternetOfferingCard({
return ( return (
<div <div
className={cn( className={cn(
"rounded-xl border bg-card shadow-[var(--cp-shadow-1)] overflow-hidden", "rounded-xl border bg-card overflow-hidden",
isPremium ? "border-primary/30" : "border-border" isPremium ? "border-primary/25 shadow-sm" : "border-border/60"
)} )}
> >
{/* Header - Always visible */} {/* Top accent stripe */}
<div
className={cn(
"h-0.5 w-full",
isPremium
? "bg-gradient-to-r from-sky-500 to-blue-600"
: "bg-gradient-to-r from-sky-400 to-blue-500"
)}
/>
{/* Header */}
<div className="w-full p-4 flex items-start justify-between gap-3 text-left"> <div className="w-full p-4 flex items-start justify-between gap-3 text-left">
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
<div <div
className={cn( className={cn(
"flex h-10 w-10 items-center justify-center rounded-lg border flex-shrink-0", "flex h-9 w-9 items-center justify-center rounded-lg flex-shrink-0",
iconType === "home" iconType === "home"
? "bg-info-soft/50 text-info border-info/20" ? "bg-sky-500/10 text-sky-600"
: "bg-success-soft/50 text-success border-success/20" : "bg-emerald-500/10 text-emerald-600"
)} )}
> >
<Icon className="h-5 w-5" /> <Icon className="h-4.5 w-4.5" />
</div> </div>
<div className="space-y-1"> <div className="space-y-0.5">
<div className="flex flex-wrap items-center gap-2"> <div className="flex flex-wrap items-center gap-1.5">
<h3 className="text-base font-bold text-foreground">{title}</h3> <h3 className="text-sm font-bold text-foreground">{title}</h3>
<CardBadge text={speedBadge} variant={isPremium ? "new" : "default"} size="sm" /> <CardBadge text={speedBadge} variant={isPremium ? "new" : "default"} size="sm" />
{isPremium && <span className="text-xs text-muted-foreground">(select areas)</span>} {isPremium && (
<span className="text-[10px] text-muted-foreground">(select areas)</span>
)}
</div> </div>
<p className="text-sm text-muted-foreground">{description}</p> <p className="text-xs text-muted-foreground">{description}</p>
<div className="flex items-baseline gap-1 pt-0.5"> <div className="flex items-baseline gap-0.5 pt-0.5">
<span className="text-xs text-muted-foreground">From</span> <span className="text-[10px] text-muted-foreground">From</span>
<span className="text-lg font-bold text-foreground"> <span className="text-base font-bold text-foreground">
¥{startingPrice.toLocaleString()} ¥{startingPrice.toLocaleString()}
</span> </span>
<span className="text-sm text-muted-foreground">/mo</span> <span className="text-xs text-muted-foreground">/mo</span>
<span className="text-xs text-muted-foreground ml-1.5"> <span className="text-[10px] text-muted-foreground ml-1">
+ ¥{setupFee.toLocaleString()} setup + ¥{setupFee.toLocaleString()} setup
</span> </span>
</div> </div>
@ -110,91 +128,95 @@ export function InternetOfferingCard({
</div> </div>
</div> </div>
{/* Tiers - Always expanded */} {/* Tiers */}
<div className="border-t border-border px-4 py-4 bg-muted/10"> <div className="border-t border-border/60 px-4 py-4 bg-muted/5">
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3"> <div className="grid grid-cols-1 sm:grid-cols-3 gap-3">
{tiers.map(tier => ( {tiers.map(tier => {
<div const style = tierStyles[tier.tier];
key={tier.tier} return (
className={cn( <div
"rounded-lg border p-3 transition-all flex flex-col", key={tier.tier}
tierStyles[tier.tier].card, className={cn(
tier.recommended && "ring-1 ring-warning/30" "rounded-lg border p-3 transition-all flex flex-col relative overflow-hidden",
)} style.card,
> tier.recommended && "ring-1 ring-warning/25"
{/* Header */}
<div className="flex items-center gap-2 mb-2">
<span className={cn("font-semibold text-sm", tierStyles[tier.tier].accent)}>
{tier.tier}
</span>
{tier.recommended && (
<CardBadge text="Recommended" variant="recommended" size="xs" />
)} )}
</div> >
{/* Tier accent bar */}
<div className={cn("absolute top-0 left-0 right-0 h-px", style.stripe)} />
{/* Price */} {/* Tier header */}
{!previewMode && ( <div className="flex items-center gap-1.5 mb-2">
<div className="mb-2"> <span className={cn("font-semibold text-xs", style.accent)}>{tier.tier}</span>
<div className="flex items-baseline gap-0.5 flex-wrap"> {tier.recommended && (
<span className="text-xl font-bold text-foreground"> <CardBadge text="Recommended" variant="recommended" size="xs" />
¥{tier.monthlyPrice.toLocaleString()}
</span>
<span className="text-xs text-muted-foreground">/mo</span>
{tier.pricingNote && (
<span className="text-[10px] text-warning ml-1">{tier.pricingNote}</span>
)}
</div>
</div>
)}
{/* Description */}
<p className="text-xs text-muted-foreground mb-2">{tier.description}</p>
{/* Features */}
<ul className="space-y-1 flex-grow mb-3">
{tier.features.slice(0, 3).map((feature, index) => (
<li key={index} className="flex items-start gap-1.5 text-xs">
<Zap className="h-3 w-3 text-success flex-shrink-0 mt-0.5" />
<span className="text-muted-foreground leading-relaxed">{feature}</span>
</li>
))}
</ul>
{/* Button */}
{previewMode ? (
<div className="mt-auto pt-2 border-t border-border/50">
<p className="text-[10px] text-muted-foreground text-center">
See pricing after verification
</p>
</div>
) : disabled ? (
<div className="mt-auto">
<Button variant="outline" size="sm" className="w-full" disabled>
Unavailable
</Button>
{disabledReason && (
<p className="text-[10px] text-muted-foreground text-center mt-1.5">
{disabledReason}
</p>
)} )}
</div> </div>
) : (
<Button {/* Price */}
as="a" {!previewMode && (
href={resolveTierHref(ctaPath, tier.planSku)} <div className="mb-2">
variant={tier.recommended ? "default" : "outline"} <div className="flex items-baseline gap-0.5 flex-wrap">
size="sm" <span className="text-lg font-bold text-foreground">
className="w-full mt-auto" ¥{tier.monthlyPrice.toLocaleString()}
> </span>
Select <span className="text-[10px] text-muted-foreground">/mo</span>
</Button> {tier.pricingNote && (
)} <span className="text-[10px] text-warning ml-1">{tier.pricingNote}</span>
</div> )}
))} </div>
</div>
)}
{/* Description */}
<p className="text-[11px] text-muted-foreground mb-2">{tier.description}</p>
{/* Features */}
<ul className="space-y-1 flex-grow mb-3">
{tier.features.slice(0, 3).map((feature, index) => (
<li key={index} className="flex items-start gap-1.5 text-[11px]">
<Check className="h-3 w-3 text-primary/60 flex-shrink-0 mt-0.5" />
<span className="text-muted-foreground leading-relaxed">{feature}</span>
</li>
))}
</ul>
{/* Button */}
{previewMode ? (
<div className="mt-auto pt-2 border-t border-border/40">
<p className="text-[10px] text-muted-foreground text-center">
See pricing after verification
</p>
</div>
) : disabled ? (
<div className="mt-auto">
<Button variant="outline" size="sm" className="w-full" disabled>
Unavailable
</Button>
{disabledReason && (
<p className="text-[10px] text-muted-foreground text-center mt-1">
{disabledReason}
</p>
)}
</div>
) : (
<Button
as="a"
href={resolveTierHref(ctaPath, tier.planSku)}
variant={tier.recommended ? "default" : "outline"}
size="sm"
className="w-full mt-auto"
>
Select
</Button>
)}
</div>
);
})}
</div> </div>
{/* Footer */} {/* Footer */}
<p className="text-xs text-muted-foreground text-center mt-3 pt-3 border-t border-border/50"> <p className="text-[11px] text-muted-foreground text-center mt-3 pt-2.5 border-t border-border/40">
+ ¥{setupFee.toLocaleString()} one-time installation (or 12/24-month installment) + ¥{setupFee.toLocaleString()} one-time installation (or 12/24-month installment)
</p> </p>
</div> </div>

View File

@ -1,7 +1,7 @@
"use client"; "use client";
import { useState } from "react"; import { useState } from "react";
import { ChevronDown, ChevronUp, Home, Building2, Info, X, Sparkles } from "lucide-react"; import { ChevronDown, ChevronUp, Home, Building2, Info, X, Sparkles, Check } from "lucide-react";
import { Button } from "@/components/atoms/button"; import { Button } from "@/components/atoms/button";
import { CardBadge } from "@/features/services/components/base/CardBadge"; import { CardBadge } from "@/features/services/components/base/CardBadge";
import { cn } from "@/shared/utils"; import { cn } from "@/shared/utils";
@ -38,29 +38,29 @@ interface PublicOfferingCardProps {
const tierStyles = { const tierStyles = {
Silver: { Silver: {
card: "border-gray-200 bg-white border-l-4 border-l-gray-400", card: "border-border/60 bg-card",
accent: "text-gray-600", accent: "text-muted-foreground",
leftBorder: "border-l-slate-400",
}, },
Gold: { Gold: {
card: "border-gray-200 bg-white border-l-4 border-l-amber-500", card: "border-border/60 bg-card",
accent: "text-amber-600", accent: "text-amber-600",
leftBorder: "border-l-amber-500",
}, },
Platinum: { Platinum: {
card: "border-gray-200 bg-white border-l-4 border-l-primary", card: "border-border/60 bg-card",
accent: "text-primary", accent: "text-primary",
leftBorder: "border-l-primary",
}, },
} as const; } as const;
/**
* Info panel explaining apartment connection types
*/
function ConnectionTypeInfo({ onClose }: { onClose: () => void }) { function ConnectionTypeInfo({ onClose }: { onClose: () => void }) {
return ( return (
<div className="bg-info-soft/50 border border-info/20 rounded-lg p-4 mb-4"> <div className="bg-info-soft/30 border border-info/15 rounded-lg p-3.5 mb-4">
<div className="flex items-start justify-between gap-3 mb-3"> <div className="flex items-start justify-between gap-3 mb-2.5">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Info className="h-5 w-5 text-info flex-shrink-0" /> <Info className="h-4 w-4 text-info flex-shrink-0" />
<h4 className="font-semibold text-sm text-foreground"> <h4 className="font-semibold text-xs text-foreground">
Why does speed vary by building? Why does speed vary by building?
</h4> </h4>
</div> </div>
@ -69,15 +69,15 @@ function ConnectionTypeInfo({ onClose }: { onClose: () => void }) {
onClick={onClose} onClick={onClose}
className="text-muted-foreground hover:text-foreground transition-colors" className="text-muted-foreground hover:text-foreground transition-colors"
> >
<X className="h-4 w-4" /> <X className="h-3.5 w-3.5" />
</button> </button>
</div> </div>
<div className="space-y-3 text-xs text-muted-foreground"> <div className="space-y-2.5 text-[11px] text-muted-foreground">
<p> <p>
Apartment buildings in Japan have different fiber infrastructure installed by NTT. Your Apartment buildings in Japan have different fiber infrastructure installed by NTT. Your
available speed depends on what your building supports: available speed depends on what your building supports:
</p> </p>
<div className="grid gap-2"> <div className="grid gap-1.5">
<div className="flex gap-2"> <div className="flex gap-2">
<span className="font-semibold text-foreground whitespace-nowrap">FTTH (1Gbps)</span> <span className="font-semibold text-foreground whitespace-nowrap">FTTH (1Gbps)</span>
<span> <span>
@ -97,7 +97,7 @@ function ConnectionTypeInfo({ onClose }: { onClose: () => void }) {
</span> </span>
</div> </div>
</div> </div>
<p className="text-foreground font-medium pt-1"> <p className="text-foreground font-medium pt-0.5">
Good news: All types have the same monthly price (¥4,800~). We&apos;ll check what&apos;s Good news: All types have the same monthly price (¥4,800~). We&apos;ll check what&apos;s
available at your address. available at your address.
</p> </p>
@ -107,8 +107,8 @@ function ConnectionTypeInfo({ onClose }: { onClose: () => void }) {
} }
/** /**
* Public-facing offering card that shows pricing inline * Public-facing offering card that shows pricing inline.
* No modals - all information is visible or expandable within the card * No modals all information is visible or expandable within the card.
*/ */
export function PublicOfferingCard({ export function PublicOfferingCard({
title, title,
@ -134,49 +134,61 @@ export function PublicOfferingCard({
return ( return (
<div <div
className={cn( className={cn(
"rounded-xl border bg-card shadow-[var(--cp-shadow-1)] overflow-hidden transition-all duration-300 hover:shadow-lg hover:-translate-y-0.5", "rounded-xl border bg-card overflow-hidden transition-all duration-300",
isExpanded ? "shadow-[var(--cp-shadow-2)] ring-1 ring-primary/20" : "", isExpanded ? "shadow-md ring-1 ring-primary/15" : "hover:shadow-sm",
isPremium ? "border-primary/30" : "border-border" isPremium ? "border-primary/25" : "border-border/60"
)} )}
> >
{/* Header - Always visible */} {/* Top accent stripe */}
<div
className={cn(
"h-0.5 w-full",
isPremium
? "bg-gradient-to-r from-sky-500 to-blue-600"
: "bg-gradient-to-r from-sky-400 to-blue-500"
)}
/>
{/* Header */}
<button <button
type="button" type="button"
onClick={() => setIsExpanded(!isExpanded)} onClick={() => setIsExpanded(!isExpanded)}
className="w-full p-4 flex items-start justify-between gap-3 text-left hover:bg-muted/20 transition-colors" className="w-full p-4 flex items-start justify-between gap-3 text-left hover:bg-muted/10 transition-colors"
> >
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
<div <div
className={cn( className={cn(
"flex h-10 w-10 items-center justify-center rounded-lg border flex-shrink-0", "flex h-9 w-9 items-center justify-center rounded-lg flex-shrink-0",
iconType === "home" iconType === "home"
? "bg-info-soft/50 text-info border-info/20" ? "bg-sky-500/10 text-sky-600"
: "bg-success-soft/50 text-success border-success/20" : "bg-emerald-500/10 text-emerald-600"
)} )}
> >
<Icon className="h-5 w-5" /> <Icon className="h-4.5 w-4.5" />
</div> </div>
<div className="space-y-1"> <div className="space-y-0.5">
<div className="flex flex-wrap items-center gap-2"> <div className="flex flex-wrap items-center gap-1.5">
<h3 className="text-base font-bold text-foreground">{title}</h3> <h3 className="text-sm font-bold text-foreground">{title}</h3>
<CardBadge text={speedBadge} variant={isPremium ? "new" : "default"} size="sm" /> <CardBadge text={speedBadge} variant={isPremium ? "new" : "default"} size="sm" />
{isPremium && <span className="text-xs text-muted-foreground">(select areas)</span>} {isPremium && (
<span className="text-[10px] text-muted-foreground">(select areas)</span>
)}
</div> </div>
<p className="text-sm text-muted-foreground">{description}</p> <p className="text-xs text-muted-foreground">{description}</p>
<div className="flex items-baseline gap-1 pt-0.5"> <div className="flex items-baseline gap-0.5 pt-0.5">
<span className="text-xs text-muted-foreground">From</span> <span className="text-[10px] text-muted-foreground">From</span>
<span className="text-lg font-bold text-foreground"> <span className="text-base font-bold text-foreground">
¥{startingPrice.toLocaleString()} ¥{startingPrice.toLocaleString()}
{maxPrice && maxPrice > startingPrice && `~${maxPrice.toLocaleString()}`} {maxPrice && maxPrice > startingPrice && `~${maxPrice.toLocaleString()}`}
</span> </span>
<span className="text-sm text-muted-foreground">/mo</span> <span className="text-xs text-muted-foreground">/mo</span>
</div> </div>
</div> </div>
</div> </div>
<div className="flex items-center gap-1.5 flex-shrink-0 mt-1"> <div className="flex items-center gap-1 flex-shrink-0 mt-1">
<span className="text-xs text-muted-foreground hidden sm:inline"> <span className="text-[10px] text-muted-foreground hidden sm:inline">
{isExpanded ? "Hide" : "View tiers"} {isExpanded ? "Hide" : "View tiers"}
</span> </span>
{isExpanded ? ( {isExpanded ? (
@ -187,104 +199,95 @@ export function PublicOfferingCard({
</div> </div>
</button> </button>
{/* Expanded content - Tier pricing shown inline */} {/* Expanded: Tier pricing */}
{isExpanded && ( {isExpanded && (
<div className="border-t border-border px-4 py-4 bg-muted/10"> <div className="border-t border-border/60 px-4 py-4 bg-muted/5">
{/* Connection type info button (for Apartment) */}
{showConnectionInfo && !showInfo && ( {showConnectionInfo && !showInfo && (
<button <button
type="button" type="button"
onClick={() => setShowInfo(true)} onClick={() => setShowInfo(true)}
className="flex items-center gap-1.5 text-xs text-info hover:text-info/80 transition-colors mb-3" className="flex items-center gap-1.5 text-[11px] text-info hover:text-info/80 transition-colors mb-3"
> >
<Info className="h-4 w-4" /> <Info className="h-3.5 w-3.5" />
<span>Why does speed vary by building?</span> <span>Why does speed vary by building?</span>
</button> </button>
)} )}
{/* Connection type info panel */}
{showConnectionInfo && showInfo && ( {showConnectionInfo && showInfo && (
<ConnectionTypeInfo onClose={() => setShowInfo(false)} /> <ConnectionTypeInfo onClose={() => setShowInfo(false)} />
)} )}
{/* Tier cards - 3 columns on desktop */} {/* Tier cards */}
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3 mb-4"> <div className="grid grid-cols-1 sm:grid-cols-3 gap-3 mb-4">
{tiers.map(tier => ( {tiers.map(tier => {
<div const style = tierStyles[tier.tier];
key={tier.tier} return (
className={cn(
"rounded-lg border p-3 transition-all duration-200 flex flex-col relative",
tierStyles[tier.tier].card
)}
>
{/* Popular Badge for Gold */}
{tier.tier === "Gold" && (
<div className="absolute -top-2.5 left-1/2 -translate-x-1/2">
<span className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full bg-amber-500 text-white text-[10px] font-semibold shadow-sm">
<Sparkles className="h-2.5 w-2.5" />
Popular
</span>
</div>
)}
{/* Header */}
<div <div
className={cn("flex items-center gap-2 mb-2", tier.tier === "Gold" ? "mt-1" : "")} key={tier.tier}
className={cn(
"rounded-lg border border-l-4 p-3 transition-all duration-200 flex flex-col relative",
style.card,
style.leftBorder
)}
> >
<span className={cn("font-semibold text-sm", tierStyles[tier.tier].accent)}> {tier.tier === "Gold" && (
{tier.tier} <div className="absolute -top-2.5 left-1/2 -translate-x-1/2">
</span> <span className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full bg-amber-500 text-white text-[10px] font-semibold shadow-sm">
</div> <Sparkles className="h-2.5 w-2.5" />
Popular
{/* Price - Always visible */}
<div className="mb-2">
<div className="flex items-baseline gap-0.5 flex-wrap">
<span className="text-xl font-bold text-foreground">
¥{tier.monthlyPrice.toLocaleString()}
{tier.maxMonthlyPrice &&
tier.maxMonthlyPrice > tier.monthlyPrice &&
`~${tier.maxMonthlyPrice.toLocaleString()}`}
</span>
<span className="text-xs text-muted-foreground">/mo</span>
{tier.pricingNote && (
<span
className={`text-[10px] ml-1 ${tier.tier === "Platinum" ? "text-primary" : "text-amber-600"}`}
>
{tier.pricingNote}
</span> </span>
</div>
)}
<div
className={cn(
"flex items-center gap-2 mb-2",
tier.tier === "Gold" ? "mt-1" : ""
)} )}
>
<span className={cn("font-semibold text-xs", style.accent)}>{tier.tier}</span>
</div> </div>
<div className="mb-2">
<div className="flex items-baseline gap-0.5 flex-wrap">
<span className="text-lg font-bold text-foreground">
¥{tier.monthlyPrice.toLocaleString()}
{tier.maxMonthlyPrice &&
tier.maxMonthlyPrice > tier.monthlyPrice &&
`~${tier.maxMonthlyPrice.toLocaleString()}`}
</span>
<span className="text-[10px] text-muted-foreground">/mo</span>
{tier.pricingNote && (
<span
className={cn(
"text-[10px] ml-1",
tier.tier === "Platinum" ? "text-primary" : "text-amber-600"
)}
>
{tier.pricingNote}
</span>
)}
</div>
</div>
<p className="text-[11px] text-muted-foreground mb-2">{tier.description}</p>
<ul className="space-y-1 flex-grow">
{tier.features.slice(0, 3).map((feature, index) => (
<li key={index} className="flex items-start gap-1.5 text-[11px]">
<Check className="h-3 w-3 text-primary/60 flex-shrink-0 mt-0.5" />
<span className="text-muted-foreground leading-relaxed">{feature}</span>
</li>
))}
</ul>
</div> </div>
);
{/* Description */} })}
<p className="text-xs text-muted-foreground mb-2">{tier.description}</p>
{/* Features */}
<ul className="space-y-1 flex-grow">
{tier.features.slice(0, 3).map((feature, index) => (
<li key={index} className="flex items-start gap-1.5 text-xs">
<svg
className="h-3 w-3 text-gray-500 flex-shrink-0 mt-0.5"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fillRule="evenodd"
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
clipRule="evenodd"
/>
</svg>
<span className="text-muted-foreground leading-relaxed">{feature}</span>
</li>
))}
</ul>
</div>
))}
</div> </div>
{/* Footer with setup fee and CTA */} {/* Footer */}
<div className="flex flex-col sm:flex-row items-stretch sm:items-center gap-3 pt-3 border-t border-border/50"> <div className="flex flex-col sm:flex-row items-stretch sm:items-center gap-3 pt-2.5 border-t border-border/40">
<p className="text-xs text-muted-foreground flex-1"> <p className="text-[11px] text-muted-foreground flex-1">
<span className="font-semibold text-foreground"> <span className="font-semibold text-foreground">
+ ¥{setupFee.toLocaleString()} one-time setup + ¥{setupFee.toLocaleString()} one-time setup
</span>{" "} </span>{" "}

View File

@ -1,101 +1,31 @@
"use client"; "use client";
import { Signal, FileCheck, Send, CheckCircle } from "lucide-react"; import { Signal, FileCheck, Send, CheckCircle } from "lucide-react";
import { HowItWorks, type HowItWorksStep } from "@/features/services/components/base/HowItWorks";
interface StepProps { const simSteps: HowItWorksStep[] = [
number: number; {
icon: React.ReactNode; icon: <Signal className="h-6 w-6" />,
title: string; title: "Choose Plan",
description: string; description: "Select your data and voice options",
} },
{
function Step({ number, icon, title, description }: StepProps) { icon: <FileCheck className="h-6 w-6" />,
return ( title: "Create Account",
<div className="flex flex-col items-center text-center flex-1 min-w-0"> description: "Sign up with email verification",
{/* Icon with number badge */} },
<div className="relative mb-4"> {
<div className="flex h-16 w-16 items-center justify-center rounded-xl bg-gray-50 border border-gray-200 text-primary shadow-sm"> icon: <Send className="h-6 w-6" />,
{icon} title: "Place Order",
</div> description: "Configure SIM type and pay",
{/* Number badge */} },
<div className="absolute -top-1 -right-1 flex h-6 w-6 items-center justify-center rounded-full bg-primary text-white text-xs font-bold shadow-sm"> {
{number} icon: <CheckCircle className="h-6 w-6" />,
</div> title: "Get Connected",
</div> description: "Receive SIM and activate",
},
{/* Content */} ];
<h4 className="font-semibold text-foreground mb-2">{title}</h4>
<p className="text-sm text-muted-foreground leading-relaxed max-w-[180px]">{description}</p>
</div>
);
}
export function SimHowItWorksSection() { export function SimHowItWorksSection() {
const steps = [ return <HowItWorks steps={simSteps} eyebrow="Getting Started" title="How It Works" />;
{
icon: <Signal className="h-6 w-6" />,
title: "Choose Plan",
description: "Select your data and voice options",
},
{
icon: <FileCheck className="h-6 w-6" />,
title: "Create Account",
description: "Sign up with email verification",
},
{
icon: <Send className="h-6 w-6" />,
title: "Place Order",
description: "Configure SIM type and pay",
},
{
icon: <CheckCircle className="h-6 w-6" />,
title: "Get Connected",
description: "Receive SIM and activate",
},
];
return (
<section className="bg-card rounded-xl border border-border shadow-[var(--cp-shadow-1)] p-8 mb-8">
{/* Header */}
<div className="text-center mb-8">
<span className="text-sm font-semibold text-primary uppercase tracking-wider">
Getting Started
</span>
<h3 className="text-2xl font-bold text-foreground mt-1">How It Works</h3>
</div>
{/* Steps with connecting line */}
<div className="relative">
{/* Connecting line - hidden on mobile */}
<div className="hidden md:block absolute top-8 left-[12%] right-[12%] h-0.5 bg-gray-200" />
{/* Curved path SVG for visual connection - hidden on mobile */}
<svg
className="hidden md:block absolute top-[30px] left-0 right-0 w-full h-4 pointer-events-none"
preserveAspectRatio="none"
>
<path
d="M 12% 8 Q 30% 8, 37.5% 8 Q 45% 8, 50% 8 Q 55% 8, 62.5% 8 Q 70% 8, 88% 8"
fill="none"
stroke="#e5e7eb"
strokeWidth="2"
strokeDasharray="6 4"
/>
</svg>
{/* Steps grid */}
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-8 relative z-10">
{steps.map((step, index) => (
<Step
key={index}
number={index + 1}
icon={step.icon}
title={step.title}
description={step.description}
/>
))}
</div>
</div>
</section>
);
} }

View File

@ -33,6 +33,7 @@ import {
type HighlightFeature, type HighlightFeature,
} from "@/features/services/components/base/ServiceHighlights"; } from "@/features/services/components/base/ServiceHighlights";
import { SimHowItWorksSection } from "@/features/services/components/sim/SimHowItWorksSection"; import { SimHowItWorksSection } from "@/features/services/components/sim/SimHowItWorksSection";
import { cn } from "@/shared/utils";
export type SimPlansTab = "data-voice" | "data-only" | "voice-only"; export type SimPlansTab = "data-voice" | "data-only" | "voice-only";
@ -56,24 +57,30 @@ function CollapsibleSection({
const [isOpen, setIsOpen] = useState(defaultOpen); const [isOpen, setIsOpen] = useState(defaultOpen);
return ( return (
<div className="border border-border rounded-xl overflow-hidden bg-card"> <div className="border border-border/60 rounded-xl overflow-hidden bg-card">
<button <button
type="button" type="button"
onClick={() => setIsOpen(!isOpen)} onClick={() => setIsOpen(!isOpen)}
className="w-full flex items-center justify-between p-4 text-left hover:bg-muted/50 transition-colors" className="w-full flex items-center justify-between p-4 text-left hover:bg-muted/30 transition-colors"
> >
<div className="flex items-center gap-3"> <div className="flex items-center gap-2.5">
<Icon className="w-5 h-5 text-primary" /> <Icon className="w-4 h-4 text-primary" />
<span className="font-medium text-foreground">{title}</span> <span className="text-sm font-medium text-foreground">{title}</span>
</div> </div>
<ChevronDown <ChevronDown
className={`w-5 h-5 text-muted-foreground transition-transform duration-200 ${isOpen ? "rotate-180" : ""}`} className={cn(
"w-4 h-4 text-muted-foreground transition-transform duration-200",
isOpen && "rotate-180"
)}
/> />
</button> </button>
<div <div
className={`overflow-hidden transition-all duration-300 ${isOpen ? "max-h-[2000px]" : "max-h-0"}`} className={cn(
"overflow-hidden transition-all duration-300",
isOpen ? "max-h-[2000px]" : "max-h-0"
)}
> >
<div className="p-4 pt-0 border-t border-border">{children}</div> <div className="p-4 pt-0 border-t border-border/60">{children}</div>
</div> </div>
</div> </div>
); );
@ -92,53 +99,68 @@ function SimPlanCardCompact({
return ( return (
<div <div
className={`group relative bg-card rounded-2xl p-5 transition-all duration-200 ${ className={cn(
"group relative bg-card rounded-xl overflow-hidden transition-all duration-200",
isFamily isFamily
? "border-2 border-success/50 hover:border-success hover:shadow-[var(--cp-shadow-2)]" ? "border border-success/40 hover:border-success hover:shadow-md"
: "border border-border hover:border-primary/50 hover:shadow-[var(--cp-shadow-2)]" : "border border-border hover:border-primary/40 hover:shadow-md"
}`}
>
{isFamily && (
<div className="absolute -top-3 left-4 flex items-center gap-1.5 px-2.5 py-1 rounded-full bg-success text-success-foreground text-xs font-medium">
<Users className="w-3.5 h-3.5" />
Family Discount
</div>
)} )}
>
{/* Top accent stripe */}
<div
className={cn(
"h-0.5 w-full",
isFamily
? "bg-gradient-to-r from-emerald-500 to-teal-500"
: "bg-gradient-to-r from-sky-500 to-blue-500"
)}
/>
<div className="flex items-center justify-between mb-4 mt-1"> <div className="p-4">
<div className="flex items-center gap-2"> {isFamily && (
<div className="flex items-center gap-1.5 mb-3">
<Users className="w-3.5 h-3.5 text-success" />
<span className="text-[10px] font-semibold text-success uppercase tracking-wide">
Family Discount
</span>
</div>
)}
{/* Data size — the hero of each card */}
<div className="flex items-center gap-2 mb-3">
<div <div
className={`w-10 h-10 rounded-xl flex items-center justify-center ${ className={cn(
isFamily ? "bg-success/10" : "bg-primary/10" "w-9 h-9 rounded-lg flex items-center justify-center",
}`} isFamily ? "bg-success/10" : "bg-primary/8"
)}
> >
<Signal className={`w-5 h-5 ${isFamily ? "text-success" : "text-primary"}`} /> <Signal className={cn("w-4 h-4", isFamily ? "text-success" : "text-primary")} />
</div> </div>
<span className="text-lg font-bold text-foreground">{plan.simDataSize}</span> <span className="text-lg font-bold text-foreground">{plan.simDataSize}</span>
</div> </div>
{/* Pricing */}
<div className="mb-3">
<CardPricing monthlyPrice={displayPrice} size="sm" alignment="left" />
{isFamily && (
<div className="text-[10px] text-success font-medium mt-0.5">Discounted price</div>
)}
</div>
{/* Plan name */}
<p className="text-xs text-muted-foreground mb-4 line-clamp-2">{plan.name}</p>
{/* CTA */}
<Button
className="w-full"
variant="outline"
size="sm"
onClick={() => onSelect(plan.sku)}
rightIcon={<ArrowRight className="w-3.5 h-3.5" />}
>
Select Plan
</Button>
</div> </div>
<div className="mb-4">
<CardPricing monthlyPrice={displayPrice} size="sm" alignment="left" />
{isFamily && (
<div className="text-xs text-success font-medium mt-1">Discounted price applied</div>
)}
</div>
<p className="text-sm text-muted-foreground mb-5 line-clamp-2">{plan.name}</p>
<Button
className={`w-full ${
isFamily
? "group-hover:bg-success group-hover:text-success-foreground"
: "group-hover:bg-primary group-hover:text-primary-foreground"
}`}
variant="outline"
onClick={() => onSelect(plan.sku)}
rightIcon={<ArrowRight className="w-4 h-4" />}
>
Select Plan
</Button>
</div> </div>
); );
} }
@ -206,29 +228,23 @@ export function SimPlansContent({
if (isLoading) { if (isLoading) {
return ( return (
<div className="max-w-6xl mx-auto px-4 pb-16 pt-8"> <div className="max-w-5xl mx-auto px-4 pb-16 pt-6">
<ServicesBackLink href={servicesBasePath} label="Back to Services" /> <ServicesBackLink href={servicesBasePath} label="Back to Services" />
<div className="text-center mb-12 pt-8"> <div className="text-center mb-10 pt-6">
<Skeleton className="h-10 w-80 mx-auto mb-4" /> <Skeleton className="h-8 w-72 mx-auto mb-3" />
<Skeleton className="h-6 w-96 max-w-full mx-auto" /> <Skeleton className="h-5 w-96 max-w-full mx-auto" />
</div> </div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-5"> <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
{Array.from({ length: 4 }).map((_, i) => ( {Array.from({ length: 4 }).map((_, i) => (
<div key={i} className="bg-card rounded-2xl border border-border p-5"> <div key={i} className="bg-card rounded-xl border border-border p-4">
<div className="flex items-center justify-between mb-6 mt-1"> <div className="flex items-center gap-2 mb-4">
<div className="flex items-center gap-2"> <Skeleton className="h-9 w-9 rounded-lg" />
<Skeleton className="h-10 w-10 rounded-xl" /> <Skeleton className="h-5 w-14" />
<Skeleton className="h-6 w-16" />
</div>
</div> </div>
<div className="mb-6 space-y-2"> <Skeleton className="h-7 w-28 mb-3" />
<Skeleton className="h-8 w-32" /> <Skeleton className="h-4 w-full mb-2" />
</div> <Skeleton className="h-4 w-3/4 mb-4" />
<div className="space-y-2 mb-6"> <Skeleton className="h-9 w-full rounded-md" />
<Skeleton className="h-4 w-full" />
<Skeleton className="h-4 w-3/4" />
</div>
<Skeleton className="h-10 w-full rounded-md" />
</div> </div>
))} ))}
</div> </div>
@ -239,9 +255,9 @@ export function SimPlansContent({
if (error) { if (error) {
const errorMessage = error instanceof Error ? error.message : "An unexpected error occurred"; const errorMessage = error instanceof Error ? error.message : "An unexpected error occurred";
return ( return (
<div className="max-w-6xl mx-auto px-4 pb-16 pt-8"> <div className="max-w-5xl mx-auto px-4 pb-16 pt-6">
<ServicesBackLink href={servicesBasePath} label="Back to Services" /> <ServicesBackLink href={servicesBasePath} label="Back to Services" />
<div className="rounded-xl bg-destructive/10 border border-destructive/20 p-8 text-center mt-8"> <div className="rounded-xl bg-destructive/10 border border-destructive/20 p-8 text-center mt-6">
<div className="text-destructive font-medium text-lg mb-2">Failed to load SIM plans</div> <div className="text-destructive font-medium text-lg mb-2">Failed to load SIM plans</div>
<div className="text-destructive/80 text-sm mb-6">{errorMessage}</div> <div className="text-destructive/80 text-sm mb-6">{errorMessage}</div>
<Button as="a" href={servicesBasePath} leftIcon={<ArrowLeft className="w-4 h-4" />}> <Button as="a" href={servicesBasePath} leftIcon={<ArrowLeft className="w-4 h-4" />}>
@ -280,91 +296,85 @@ export function SimPlansContent({
const { regularPlans, familyPlans } = getCurrentPlans(); const { regularPlans, familyPlans } = getCurrentPlans();
return ( return (
<div className="max-w-6xl mx-auto py-8 pb-16 space-y-12"> <div className="max-w-5xl mx-auto py-6 pb-16 space-y-8">
<ServicesBackLink href={servicesBasePath} label="Back to Services" className="mb-0" /> <ServicesBackLink href={servicesBasePath} label="Back to Services" className="mb-0" />
<ServicesHero <ServicesHero
title="SIM Cards for Foreigners in Japan" title="SIM Cards for Foreigners in Japan"
description="No hanko, no complicated Japanese forms. Get a SIM card with your foreign credit card and English support. eSIM for instant delivery or physical SIM shipped to your door." description="No hanko, no complicated Japanese forms. Get a SIM card with your foreign credit card and English support. eSIM for instant delivery or physical SIM shipped to your door."
eyebrow={ eyebrow={
<div className="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-primary/10 border border-primary/20"> <div className="inline-flex items-center gap-2 px-3 py-1.5 rounded-full bg-primary/8 border border-primary/15">
<Sparkles className="w-4 h-4 text-primary" /> <Sparkles className="w-3.5 h-3.5 text-primary" />
<span className="text-sm font-medium text-primary">Powered by NTT DOCOMO</span> <span className="text-xs font-medium text-primary">Powered by NTT DOCOMO</span>
</div> </div>
} }
/> />
{variant === "account" && hasExistingSim && ( {variant === "account" && hasExistingSim && (
<div className="space-y-4 mb-8"> <AlertBanner variant="success" title="Family Discount Available">
<AlertBanner variant="success" title="Family Discount Available"> <p className="text-sm">
<p className="text-sm"> You already have a SIM subscription. Discounted pricing is automatically shown for
You already have a SIM subscription. Discounted pricing is automatically shown for additional lines.
additional lines. </p>
</p> </AlertBanner>
</AlertBanner>
</div>
)} )}
<ServiceHighlights features={simFeatures} className="mb-12" /> <ServiceHighlights features={simFeatures} />
<div className="flex justify-center mb-8"> {/* Tab Switcher */}
<div className="inline-flex rounded-xl bg-muted p-1 border border-border"> <div className="flex justify-center">
<button <div className="inline-flex rounded-lg bg-muted/60 p-0.5 border border-border/60">
type="button" {[
onClick={() => onTabChange("data-voice")} {
className={`flex items-center gap-2 px-5 py-2.5 rounded-lg text-sm font-medium transition-all ${ key: "data-voice" as const,
activeTab === "data-voice" icon: Phone,
? "bg-card text-foreground shadow-sm" label: "Data + Voice",
: "text-muted-foreground hover:text-foreground" shortLabel: "All-in",
}`} count: plansByType.DataSmsVoice.length,
> },
<Phone className="h-4 w-4" /> {
<span className="hidden sm:inline">Data + Voice</span> key: "data-only" as const,
<span className="sm:hidden">All-in</span> icon: Globe,
<span className="text-xs px-1.5 py-0.5 rounded-full bg-primary/10 text-primary"> label: "Data Only",
{plansByType.DataSmsVoice.length} shortLabel: "Data",
</span> count: plansByType.DataOnly.length,
</button> },
<button {
type="button" key: "voice-only" as const,
onClick={() => onTabChange("data-only")} icon: Check,
className={`flex items-center gap-2 px-5 py-2.5 rounded-lg text-sm font-medium transition-all ${ label: "Voice Only",
activeTab === "data-only" shortLabel: "Voice",
? "bg-card text-foreground shadow-sm" count: plansByType.VoiceOnly.length,
: "text-muted-foreground hover:text-foreground" },
}`} ].map(tab => (
> <button
<Globe className="h-4 w-4" /> key={tab.key}
<span className="hidden sm:inline">Data Only</span> type="button"
<span className="sm:hidden">Data</span> onClick={() => onTabChange(tab.key)}
<span className="text-xs px-1.5 py-0.5 rounded-full bg-primary/10 text-primary"> className={cn(
{plansByType.DataOnly.length} "flex items-center gap-1.5 px-4 py-2 rounded-md text-sm font-medium transition-all",
</span> activeTab === tab.key
</button> ? "bg-card text-foreground shadow-sm"
<button : "text-muted-foreground hover:text-foreground"
type="button" )}
onClick={() => onTabChange("voice-only")} >
className={`flex items-center gap-2 px-5 py-2.5 rounded-lg text-sm font-medium transition-all ${ <tab.icon className="h-3.5 w-3.5" />
activeTab === "voice-only" <span className="hidden sm:inline">{tab.label}</span>
? "bg-card text-foreground shadow-sm" <span className="sm:hidden">{tab.shortLabel}</span>
: "text-muted-foreground hover:text-foreground" <span className="text-[10px] px-1.5 py-0.5 rounded-full bg-primary/8 text-primary font-semibold">
}`} {tab.count}
> </span>
<Check className="h-4 w-4" /> </button>
<span className="hidden sm:inline">Voice Only</span> ))}
<span className="sm:hidden">Voice</span>
<span className="text-xs px-1.5 py-0.5 rounded-full bg-primary/10 text-primary">
{plansByType.VoiceOnly.length}
</span>
</button>
</div> </div>
</div> </div>
<div id="plans" className="min-h-[300px]"> {/* Plans Grid */}
<div id="plans" className="min-h-[280px]">
{regularPlans.length > 0 || familyPlans.length > 0 ? ( {regularPlans.length > 0 || familyPlans.length > 0 ? (
<div className="space-y-8 animate-in fade-in duration-300"> <div className="space-y-8 animate-in fade-in duration-300">
{regularPlans.length > 0 && ( {regularPlans.length > 0 && (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-5"> <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
{regularPlans.map(plan => ( {regularPlans.map(plan => (
<SimPlanCardCompact key={plan.id} plan={plan} onSelect={onSelectPlan} /> <SimPlanCardCompact key={plan.id} plan={plan} onSelect={onSelectPlan} />
))} ))}
@ -373,11 +383,11 @@ export function SimPlansContent({
{variant === "account" && hasExistingSim && familyPlans.length > 0 && ( {variant === "account" && hasExistingSim && familyPlans.length > 0 && (
<div> <div>
<div className="flex items-center gap-2 mb-4"> <div className="flex items-center gap-2 mb-3">
<Users className="h-5 w-5 text-success" /> <Users className="h-4 w-4 text-success" />
<h3 className="text-lg font-semibold text-foreground">Family Discount Plans</h3> <h3 className="text-sm font-semibold text-foreground">Family Discount Plans</h3>
</div> </div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-5"> <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
{familyPlans.map(plan => ( {familyPlans.map(plan => (
<SimPlanCardCompact <SimPlanCardCompact
key={plan.id} key={plan.id}
@ -391,23 +401,22 @@ export function SimPlansContent({
)} )}
</div> </div>
) : ( ) : (
<div className="text-center py-16 text-muted-foreground"> <div className="text-center py-12 text-muted-foreground text-sm">
No plans available in this category. No plans available in this category.
</div> </div>
)} )}
</div> </div>
{/* Device Compatibility Section - below plans */} {/* Device Compatibility */}
<DeviceCompatibility /> <DeviceCompatibility />
{/* How It Works Section */} {/* How It Works */}
<div className="mt-12"> <SimHowItWorksSection />
<SimHowItWorksSection />
</div>
<div className="mt-8 space-y-4"> {/* Collapsible Info Sections */}
<div className="space-y-3">
<CollapsibleSection title="Calling & SMS Rates" icon={Phone}> <CollapsibleSection title="Calling & SMS Rates" icon={Phone}>
<div className="space-y-6 pt-4"> <div className="space-y-5 pt-4">
<div> <div>
<h4 className="text-sm font-medium text-foreground mb-3 flex items-center gap-2"> <h4 className="text-sm font-medium text-foreground mb-3 flex items-center gap-2">
<span className="w-5 h-3 rounded-sm bg-[#BC002D] relative overflow-hidden flex items-center justify-center"> <span className="w-5 h-3 rounded-sm bg-[#BC002D] relative overflow-hidden flex items-center justify-center">
@ -415,31 +424,33 @@ export function SimPlansContent({
</span> </span>
Domestic (Japan) Domestic (Japan)
</h4> </h4>
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-3">
<div className="bg-muted/50 rounded-lg p-4 border border-border"> <div className="bg-muted/30 rounded-lg p-3 border border-border/60">
<div className="text-sm text-muted-foreground mb-1">Voice Calls</div> <div className="text-xs text-muted-foreground mb-1">Voice Calls</div>
<div className="text-xl font-bold text-foreground"> <div className="text-lg font-bold text-foreground">
¥10<span className="text-sm font-normal text-muted-foreground">/30 sec</span> ¥10<span className="text-xs font-normal text-muted-foreground">/30 sec</span>
</div> </div>
</div> </div>
<div className="bg-muted/50 rounded-lg p-4 border border-border"> <div className="bg-muted/30 rounded-lg p-3 border border-border/60">
<div className="text-sm text-muted-foreground mb-1">SMS</div> <div className="text-xs text-muted-foreground mb-1">SMS</div>
<div className="text-xl font-bold text-foreground"> <div className="text-lg font-bold text-foreground">
¥3<span className="text-sm font-normal text-muted-foreground">/message</span> ¥3<span className="text-xs font-normal text-muted-foreground">/message</span>
</div> </div>
</div> </div>
</div> </div>
<p className="text-xs text-muted-foreground mt-2"> <p className="text-[11px] text-muted-foreground mt-2">
Incoming calls and SMS are free. Pay-per-use charges billed 5-6 weeks after usage. Incoming calls and SMS are free. Pay-per-use charges billed 5-6 weeks after usage.
</p> </p>
</div> </div>
<div className="p-4 bg-success/5 border border-success/20 rounded-lg"> <div className="p-3 bg-success/5 border border-success/15 rounded-lg">
<div className="flex items-start gap-3"> <div className="flex items-start gap-2.5">
<Phone className="w-5 h-5 text-success mt-0.5" /> <Phone className="w-4 h-4 text-success mt-0.5" />
<div> <div>
<h4 className="font-medium text-foreground">Unlimited Domestic Calling</h4> <h4 className="text-sm font-medium text-foreground">
<p className="text-sm text-muted-foreground"> Unlimited Domestic Calling
</h4>
<p className="text-xs text-muted-foreground">
Add unlimited domestic calls for{" "} Add unlimited domestic calls for{" "}
<span className="font-semibold text-success">¥3,000/month</span> (available at <span className="font-semibold text-success">¥3,000/month</span> (available at
checkout) checkout)
@ -448,7 +459,7 @@ export function SimPlansContent({
</div> </div>
</div> </div>
<div className="text-sm text-muted-foreground"> <div className="text-xs text-muted-foreground">
<p> <p>
International calling rates vary by country (¥31-148/30 sec). See{" "} International calling rates vary by country (¥31-148/30 sec). See{" "}
<a <a
@ -466,60 +477,64 @@ export function SimPlansContent({
</CollapsibleSection> </CollapsibleSection>
<CollapsibleSection title="Fees & Discounts" icon={CircleDollarSign}> <CollapsibleSection title="Fees & Discounts" icon={CircleDollarSign}>
<div className="space-y-6 pt-4"> <div className="space-y-5 pt-4">
<div> <div>
<h4 className="text-sm font-medium text-foreground mb-3">One-time Fees</h4> <h4 className="text-xs font-medium text-foreground mb-2.5">One-time Fees</h4>
<div className="space-y-3"> <div className="space-y-2">
<div className="flex items-center justify-between py-2 border-b border-border"> <div className="flex items-center justify-between py-1.5 border-b border-border/60">
<span className="text-muted-foreground">Activation Fee</span> <span className="text-xs text-muted-foreground">Activation Fee</span>
<span className="font-medium text-foreground">¥1,500</span> <span className="text-sm font-medium text-foreground">¥1,500</span>
</div> </div>
<div className="flex items-center justify-between py-2 border-b border-border"> <div className="flex items-center justify-between py-1.5 border-b border-border/60">
<span className="text-muted-foreground">SIM Replacement (lost/damaged)</span> <span className="text-xs text-muted-foreground">
<span className="font-medium text-foreground">¥1,500</span> SIM Replacement (lost/damaged)
</span>
<span className="text-sm font-medium text-foreground">¥1,500</span>
</div> </div>
<div className="flex items-center justify-between py-2"> <div className="flex items-center justify-between py-1.5">
<span className="text-muted-foreground">eSIM Re-download</span> <span className="text-xs text-muted-foreground">eSIM Re-download</span>
<span className="font-medium text-foreground">¥1,500</span> <span className="text-sm font-medium text-foreground">¥1,500</span>
</div> </div>
</div> </div>
</div> </div>
<div className="p-4 bg-success/5 border border-success/20 rounded-lg"> <div className="p-3 bg-success/5 border border-success/15 rounded-lg">
<h4 className="font-medium text-foreground mb-2">Family Discount</h4> <h4 className="text-sm font-medium text-foreground mb-1">Family Discount</h4>
<p className="text-sm text-muted-foreground"> <p className="text-xs text-muted-foreground">
<span className="font-semibold text-success">¥300/month off</span> per additional <span className="font-semibold text-success">¥300/month off</span> per additional
Voice SIM on your account Voice SIM on your account
</p> </p>
</div> </div>
<p className="text-xs text-muted-foreground">All prices exclude 10% consumption tax.</p> <p className="text-[11px] text-muted-foreground">
All prices exclude 10% consumption tax.
</p>
</div> </div>
</CollapsibleSection> </CollapsibleSection>
<CollapsibleSection title="Important Information & Terms" icon={Info}> <CollapsibleSection title="Important Information & Terms" icon={Info}>
<div className="space-y-6 pt-4 text-sm"> <div className="space-y-5 pt-4 text-sm">
<div> <div>
<h4 className="font-medium text-foreground mb-3 flex items-center gap-2"> <h4 className="text-xs font-medium text-foreground mb-2.5 flex items-center gap-1.5">
<TriangleAlert className="w-4 h-4 text-warning" /> <TriangleAlert className="w-3.5 h-3.5 text-warning" />
Important Notices Important Notices
</h4> </h4>
<ul className="space-y-2 text-muted-foreground"> <ul className="space-y-1.5 text-muted-foreground">
<li className="flex items-start gap-2"> <li className="flex items-start gap-2 text-xs">
<span className="text-foreground"></span> <span className="text-foreground/60 mt-0.5">&#8226;</span>
<span> <span>
ID verification with official documents (name, date of birth, address, photo) is ID verification with official documents (name, date of birth, address, photo) is
required during checkout. required during checkout.
</span> </span>
</li> </li>
<li className="flex items-start gap-2"> <li className="flex items-start gap-2 text-xs">
<span className="text-foreground"></span> <span className="text-foreground/60 mt-0.5">&#8226;</span>
<span> <span>
A compatible unlocked device is required. Check compatibility on our website. A compatible unlocked device is required. Check compatibility on our website.
</span> </span>
</li> </li>
<li className="flex items-start gap-2"> <li className="flex items-start gap-2 text-xs">
<span className="text-foreground"></span> <span className="text-foreground/60 mt-0.5">&#8226;</span>
<span> <span>
Service may not be available in areas with weak signal. See{" "} Service may not be available in areas with weak signal. See{" "}
<a <a
@ -533,14 +548,14 @@ export function SimPlansContent({
. .
</span> </span>
</li> </li>
<li className="flex items-start gap-2"> <li className="flex items-start gap-2 text-xs">
<span className="text-foreground"></span> <span className="text-foreground/60 mt-0.5">&#8226;</span>
<span> <span>
SIM is activated as 4G by default. 5G can be requested via your account portal. SIM is activated as 4G by default. 5G can be requested via your account portal.
</span> </span>
</li> </li>
<li className="flex items-start gap-2"> <li className="flex items-start gap-2 text-xs">
<span className="text-foreground"></span> <span className="text-foreground/60 mt-0.5">&#8226;</span>
<span> <span>
International data roaming is not available. Voice/SMS roaming can be enabled International data roaming is not available. Voice/SMS roaming can be enabled
upon request (¥50,000/month limit). upon request (¥50,000/month limit).
@ -550,32 +565,32 @@ export function SimPlansContent({
</div> </div>
<div> <div>
<h4 className="font-medium text-foreground mb-3">Contract Terms</h4> <h4 className="text-xs font-medium text-foreground mb-2.5">Contract Terms</h4>
<ul className="space-y-2 text-muted-foreground"> <ul className="space-y-1.5 text-muted-foreground">
<li className="flex items-start gap-2"> <li className="flex items-start gap-2 text-xs">
<span className="text-foreground"></span> <span className="text-foreground/60 mt-0.5">&#8226;</span>
<span> <span>
<strong className="text-foreground">Minimum contract:</strong> 3 full billing <strong className="text-foreground">Minimum contract:</strong> 3 full billing
months. First month (sign-up to end of month) is free and doesn&apos;t count. months. First month (sign-up to end of month) is free and doesn&apos;t count.
</span> </span>
</li> </li>
<li className="flex items-start gap-2"> <li className="flex items-start gap-2 text-xs">
<span className="text-foreground"></span> <span className="text-foreground/60 mt-0.5">&#8226;</span>
<span> <span>
<strong className="text-foreground">Billing cycle:</strong> 1st to end of month. <strong className="text-foreground">Billing cycle:</strong> 1st to end of month.
Regular billing starts the 1st of the following month after sign-up. Regular billing starts the 1st of the following month after sign-up.
</span> </span>
</li> </li>
<li className="flex items-start gap-2"> <li className="flex items-start gap-2 text-xs">
<span className="text-foreground"></span> <span className="text-foreground/60 mt-0.5">&#8226;</span>
<span> <span>
<strong className="text-foreground">Cancellation:</strong> Can be requested <strong className="text-foreground">Cancellation:</strong> Can be requested
after 3rd month via cancellation form. Monthly fee is incurred in full for after 3rd month via cancellation form. Monthly fee is incurred in full for
cancellation month. cancellation month.
</span> </span>
</li> </li>
<li className="flex items-start gap-2"> <li className="flex items-start gap-2 text-xs">
<span className="text-foreground"></span> <span className="text-foreground/60 mt-0.5">&#8226;</span>
<span> <span>
<strong className="text-foreground">SIM return:</strong> SIM card must be <strong className="text-foreground">SIM return:</strong> SIM card must be
returned after service termination. returned after service termination.
@ -585,18 +600,18 @@ export function SimPlansContent({
</div> </div>
<div> <div>
<h4 className="font-medium text-foreground mb-3">Additional Options</h4> <h4 className="text-xs font-medium text-foreground mb-2.5">Additional Options</h4>
<ul className="space-y-2 text-muted-foreground"> <ul className="space-y-1.5 text-muted-foreground">
<li className="flex items-start gap-2"> <li className="flex items-start gap-2 text-xs">
<span className="text-foreground"></span> <span className="text-foreground/60 mt-0.5">&#8226;</span>
<span>Call waiting and voice mail available as separate paid options.</span> <span>Call waiting and voice mail available as separate paid options.</span>
</li> </li>
<li className="flex items-start gap-2"> <li className="flex items-start gap-2 text-xs">
<span className="text-foreground"></span> <span className="text-foreground/60 mt-0.5">&#8226;</span>
<span>Data plan changes are free and take effect next billing month.</span> <span>Data plan changes are free and take effect next billing month.</span>
</li> </li>
<li className="flex items-start gap-2"> <li className="flex items-start gap-2 text-xs">
<span className="text-foreground"></span> <span className="text-foreground/60 mt-0.5">&#8226;</span>
<span> <span>
Voice plan changes require new SIM issuance and standard policies apply. Voice plan changes require new SIM issuance and standard policies apply.
</span> </span>
@ -604,8 +619,8 @@ export function SimPlansContent({
</ul> </ul>
</div> </div>
<div className="p-4 bg-muted/50 rounded-lg"> <div className="p-3 bg-muted/30 rounded-lg">
<p className="text-xs text-muted-foreground"> <p className="text-[11px] text-muted-foreground">
Payment is by credit card only. Data service is not suitable for activities Payment is by credit card only. Data service is not suitable for activities
requiring continuous large data transfers. See full Terms of Service for complete requiring continuous large data transfers. See full Terms of Service for complete
details. details.
@ -616,11 +631,16 @@ export function SimPlansContent({
</div> </div>
{/* FAQ Section */} {/* FAQ Section */}
<div className="mt-12 mb-8"> <div>
<h2 className="text-2xl font-bold text-foreground mb-6 text-center"> <div className="text-center mb-5">
Frequently Asked Questions <p className="text-xs font-semibold text-primary uppercase tracking-wider mb-1.5">
</h2> Common Questions
<div className="space-y-4 max-w-3xl mx-auto"> </p>
<h2 className="text-xl sm:text-2xl font-bold leading-tight tracking-tight text-foreground">
Frequently Asked Questions
</h2>
</div>
<div className="space-y-2 max-w-3xl mx-auto">
<FaqItem <FaqItem
question="What is the service contract period?" question="What is the service contract period?"
answer="The minimum service requirement period is activation month + 3 billing months. After this period, the service will switch to a monthly service and you will be able to cancel at the end of each month." answer="The minimum service requirement period is activation month + 3 billing months. After this period, the service will switch to a monthly service and you will be able to cancel at the end of each month."
@ -660,7 +680,8 @@ export function SimPlansContent({
</div> </div>
</div> </div>
<div className="mt-8 text-center text-sm text-muted-foreground"> {/* Footer */}
<div className="text-center text-xs text-muted-foreground">
<p> <p>
All prices exclude 10% consumption tax.{" "} All prices exclude 10% consumption tax.{" "}
<a href="#" className="text-primary hover:underline"> <a href="#" className="text-primary hover:underline">
@ -676,19 +697,24 @@ function FaqItem({ question, answer }: { question: string; answer: React.ReactNo
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
return ( return (
<div className="border border-border rounded-xl overflow-hidden bg-card"> <div className="border border-border/60 rounded-xl overflow-hidden bg-card">
<button <button
type="button" type="button"
onClick={() => setIsOpen(!isOpen)} onClick={() => setIsOpen(!isOpen)}
className="w-full flex items-start justify-between gap-3 p-4 text-left hover:bg-muted/50 transition-colors" className="w-full flex items-start justify-between gap-3 p-4 text-left group"
> >
<span className="font-medium text-foreground">{question}</span> <span className="text-sm font-medium text-foreground group-hover:text-primary transition-colors">
{question}
</span>
<ChevronDown <ChevronDown
className={`w-5 h-5 text-muted-foreground flex-shrink-0 mt-0.5 transition-transform duration-200 ${isOpen ? "rotate-180" : ""}`} className={cn(
"w-4 h-4 text-muted-foreground flex-shrink-0 mt-0.5 transition-transform duration-200",
isOpen && "rotate-180"
)}
/> />
</button> </button>
{isOpen && ( {isOpen && (
<div className="px-4 pb-4 text-sm text-muted-foreground leading-relaxed">{answer}</div> <div className="px-4 pb-4 text-xs text-muted-foreground leading-relaxed">{answer}</div>
)} )}
</div> </div>
); );

View File

@ -18,65 +18,58 @@ const vpnFeatures = [
export function VpnPlanCard({ plan }: VpnPlanCardProps) { export function VpnPlanCard({ plan }: VpnPlanCardProps) {
return ( return (
<AnimatedCard className="p-6 border border-border hover:border-primary/40 transition-all duration-300 hover:shadow-lg flex flex-col h-full bg-card"> <AnimatedCard className="overflow-hidden border border-border hover:border-primary/30 transition-all duration-300 hover:shadow-md flex flex-col h-full bg-card">
{/* Header with icon and name */} {/* Top accent stripe */}
<div className="flex items-start justify-between mb-4"> <div className="h-0.5 w-full bg-gradient-to-r from-violet-500 to-purple-600" />
<div className="flex items-start gap-3">
<div className="p-2.5 bg-primary/10 rounded-xl"> <div className="p-5 flex flex-col h-full">
<Globe className="h-6 w-6 text-primary" /> {/* Header */}
</div> <div className="flex items-start justify-between mb-4">
<div className="flex-1"> <div className="flex items-start gap-3">
<h3 className="text-lg font-bold text-foreground">{plan.name}</h3> <div className="p-2 bg-violet-500/10 rounded-lg">
<span className="text-sm text-muted-foreground">International</span> <Globe className="h-5 w-5 text-violet-600" />
</div>
<div>
<h3 className="text-base font-bold text-foreground">{plan.name}</h3>
<span className="text-xs text-muted-foreground">International</span>
</div>
</div> </div>
</div> </div>
<div className="p-1.5 rounded-full border border-primary/30">
<Check className="h-4 w-4 text-primary" /> {/* Pricing */}
<div className="mb-4">
<div className="flex items-baseline gap-0.5">
<span className="text-xs text-primary font-medium">¥</span>
<span className="text-2xl font-bold text-foreground">
{(plan.monthlyPrice ?? 0).toLocaleString()}
</span>
<span className="text-xs text-muted-foreground">/month</span>
</div>
<p className="text-[11px] text-muted-foreground mt-0.5">Router rental included</p>
</div> </div>
</div>
{/* Pricing */} {/* Features */}
<div className="mb-4"> <ul className="space-y-2 mb-5 flex-grow">
<div className="flex items-baseline gap-1"> {vpnFeatures.map((feature, index) => (
<span className="text-sm text-primary">¥</span> <li key={index} className="flex items-start gap-2 text-xs">
<span className="text-3xl font-bold text-foreground"> <Check className="h-3.5 w-3.5 text-primary/60 flex-shrink-0 mt-0.5" />
{(plan.monthlyPrice ?? 0).toLocaleString()} <span className="text-muted-foreground">{feature}</span>
</span> </li>
<span className="text-sm text-muted-foreground">/month</span> ))}
</ul>
{/* CTA */}
<div className="mt-auto">
<Button
as="a"
href={`/order?type=vpn&planSku=${encodeURIComponent(plan.sku)}`}
className="w-full"
size="sm"
rightIcon={<ArrowRight className="w-3.5 h-3.5" />}
>
Select {plan.name}
</Button>
</div> </div>
<p className="text-xs text-muted-foreground mt-1">Router rental included</p>
</div>
{/* Features list */}
<ul className="space-y-2 mb-6 flex-grow">
{vpnFeatures.map((feature, index) => (
<li key={index} className="flex items-start gap-2 text-sm">
<svg
className="h-4 w-4 text-muted-foreground flex-shrink-0 mt-0.5"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fillRule="evenodd"
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
clipRule="evenodd"
/>
</svg>
<span className="text-muted-foreground">{feature}</span>
</li>
))}
</ul>
{/* Action Button */}
<div className="mt-auto">
<Button
as="a"
href={`/order?type=vpn&planSku=${encodeURIComponent(plan.sku)}`}
className="w-full"
rightIcon={<ArrowRight className="w-4 h-4" />}
>
Select {plan.name}
</Button>
</div> </div>
</AnimatedCard> </AnimatedCard>
); );

View File

@ -14,7 +14,6 @@ import { ServiceFAQ, type FAQItem } from "@/features/services/components/base/Se
import { VpnPlanCard } from "./VpnPlanCard"; import { VpnPlanCard } from "./VpnPlanCard";
import { VPN_FEATURES } from "@/features/services/utils"; import { VPN_FEATURES } from "@/features/services/utils";
// Steps for HowItWorks
const vpnSteps: HowItWorksStep[] = [ const vpnSteps: HowItWorksStep[] = [
{ {
icon: <CreditCard className="h-6 w-6" />, icon: <CreditCard className="h-6 w-6" />,
@ -38,7 +37,6 @@ const vpnSteps: HowItWorksStep[] = [
}, },
]; ];
// FAQ items for VPN
const vpnFaqItems: FAQItem[] = [ const vpnFaqItems: FAQItem[] = [
{ {
question: "Which streaming services can I access?", question: "Which streaming services can I access?",
@ -86,32 +84,29 @@ export function VpnPlansContent({
if (isLoading) { if (isLoading) {
return ( return (
<div className="max-w-6xl mx-auto px-4 pb-16 pt-8"> <div className="max-w-5xl mx-auto px-4 pb-16 pt-6">
<ServicesBackLink href={servicesBasePath} label="Back to Services" /> <ServicesBackLink href={servicesBasePath} label="Back to Services" />
<div className="text-center mb-12 pt-8"> <div className="text-center mb-10 pt-6">
<Skeleton className="h-10 w-80 mx-auto mb-4" /> <Skeleton className="h-8 w-72 mx-auto mb-3" />
<Skeleton className="h-6 w-96 max-w-full mx-auto" /> <Skeleton className="h-5 w-96 max-w-full mx-auto" />
</div> </div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 max-w-4xl mx-auto"> <div className="grid grid-cols-1 md:grid-cols-2 gap-5 max-w-3xl mx-auto">
{Array.from({ length: 2 }).map((_, i) => ( {Array.from({ length: 2 }).map((_, i) => (
<div key={i} className="bg-card rounded-2xl border border-border p-6"> <div key={i} className="bg-card rounded-xl border border-border p-5">
<div className="flex items-start gap-4 mb-5"> <div className="flex items-start gap-3 mb-4">
<Skeleton className="h-14 w-14 rounded-xl" /> <Skeleton className="h-10 w-10 rounded-lg" />
<div className="flex-1"> <div className="flex-1">
<Skeleton className="h-6 w-32 mb-2" /> <Skeleton className="h-5 w-28 mb-1.5" />
<Skeleton className="h-4 w-24" /> <Skeleton className="h-3.5 w-20" />
</div> </div>
</div> </div>
<div className="mb-5 space-y-2"> <Skeleton className="h-7 w-32 mb-4" />
<Skeleton className="h-8 w-36" /> <div className="space-y-2 mb-5">
<Skeleton className="h-4 w-28" />
</div>
<div className="space-y-2 mb-6">
{Array.from({ length: 4 }).map((_, j) => ( {Array.from({ length: 4 }).map((_, j) => (
<Skeleton key={j} className="h-4 w-full" /> <Skeleton key={j} className="h-3.5 w-full" />
))} ))}
</div> </div>
<Skeleton className="h-10 w-full rounded-md" /> <Skeleton className="h-9 w-full rounded-md" />
</div> </div>
))} ))}
</div> </div>
@ -122,9 +117,9 @@ export function VpnPlansContent({
if (error) { if (error) {
const errorMessage = error instanceof Error ? error.message : "An unexpected error occurred"; const errorMessage = error instanceof Error ? error.message : "An unexpected error occurred";
return ( return (
<div className="max-w-6xl mx-auto px-4 pb-16 pt-8"> <div className="max-w-5xl mx-auto px-4 pb-16 pt-6">
<ServicesBackLink href={servicesBasePath} label="Back to Services" /> <ServicesBackLink href={servicesBasePath} label="Back to Services" />
<div className="rounded-xl bg-destructive/10 border border-destructive/20 p-8 text-center mt-8"> <div className="rounded-xl bg-destructive/10 border border-destructive/20 p-8 text-center mt-6">
<div className="text-destructive font-medium text-lg mb-2">Failed to load VPN plans</div> <div className="text-destructive font-medium text-lg mb-2">Failed to load VPN plans</div>
<div className="text-destructive/80 text-sm mb-6">{errorMessage}</div> <div className="text-destructive/80 text-sm mb-6">{errorMessage}</div>
<Button as="a" href={servicesBasePath} leftIcon={<ArrowLeft className="w-4 h-4" />}> <Button as="a" href={servicesBasePath} leftIcon={<ArrowLeft className="w-4 h-4" />}>
@ -139,41 +134,40 @@ export function VpnPlansContent({
<div className="space-y-8 pb-16"> <div className="space-y-8 pb-16">
<ServicesBackLink href={servicesBasePath} label="Back to Services" /> <ServicesBackLink href={servicesBasePath} label="Back to Services" />
{/* Hero Section */} {/* Hero */}
<div className="text-center py-6"> <div className="text-center py-4">
<div <div
className="animate-in fade-in slide-in-from-bottom-4 duration-500" className="animate-in fade-in slide-in-from-bottom-4 duration-500"
style={{ animationDelay: "0ms" }} style={{ animationDelay: "0ms" }}
> >
<span className="inline-flex items-center gap-2 rounded-full bg-purple-500/10 border border-purple-500/20 px-4 py-1.5 text-sm text-purple-600 dark:text-purple-400 font-medium mb-4"> <span className="inline-flex items-center gap-1.5 rounded-full bg-violet-500/10 border border-violet-500/15 px-3 py-1 text-xs text-violet-600 dark:text-violet-400 font-medium mb-3">
<ShieldCheck className="h-4 w-4" /> <ShieldCheck className="h-3.5 w-3.5" />
VPN Router Service VPN Router Service
</span> </span>
</div> </div>
<h1 <h1
className="text-2xl md:text-4xl font-display font-bold leading-tight text-foreground animate-in fade-in slide-in-from-bottom-6 duration-700" className="text-2xl md:text-3xl font-bold leading-tight tracking-tight text-foreground animate-in fade-in slide-in-from-bottom-6 duration-700"
style={{ animationDelay: "100ms" }} style={{ animationDelay: "100ms" }}
> >
Stream Content from Abroad Stream Content from Abroad
</h1> </h1>
<p <p
className="text-lg text-muted-foreground mt-3 max-w-xl mx-auto leading-relaxed animate-in fade-in slide-in-from-bottom-8 duration-700" className="text-sm text-muted-foreground mt-2 max-w-lg mx-auto leading-relaxed animate-in fade-in slide-in-from-bottom-8 duration-700"
style={{ animationDelay: "200ms" }} style={{ animationDelay: "200ms" }}
> >
Access US and UK streaming services using a pre-configured VPN router. No technical setup Access US and UK streaming services using a pre-configured VPN router. No technical setup
required. required.
</p> </p>
{/* Order info banner - public variant only */}
{variant === "public" && ( {variant === "public" && (
<div <div
className="inline-flex mt-6 animate-in fade-in slide-in-from-bottom-8 duration-700" className="inline-flex mt-4 animate-in fade-in slide-in-from-bottom-8 duration-700"
style={{ animationDelay: "300ms" }} style={{ animationDelay: "300ms" }}
> >
<div className="bg-success-soft border border-success/25 rounded-xl px-4 py-3"> <div className="bg-success-soft border border-success/20 rounded-lg px-3 py-2">
<div className="flex items-center gap-2 justify-center"> <div className="flex items-center gap-1.5 justify-center">
<Zap className="h-4 w-4 text-success flex-shrink-0" /> <Zap className="h-3.5 w-3.5 text-success flex-shrink-0" />
<p className="text-sm text-foreground"> <p className="text-xs text-foreground">
<span className="font-medium">Order today</span> <span className="font-medium">Order today</span>
<span className="text-muted-foreground"> <span className="text-muted-foreground">
{" "} {" "}
@ -186,47 +180,45 @@ export function VpnPlansContent({
)} )}
</div> </div>
{/* Service Highlights */} {/* Highlights */}
<section> <ServiceHighlights features={VPN_FEATURES} />
<ServiceHighlights features={VPN_FEATURES} />
</section>
{/* Plans Section */} {/* Plans */}
{plans.length > 0 ? ( {plans.length > 0 ? (
<section <section
id="plans" id="plans"
className="animate-in fade-in slide-in-from-bottom-8 duration-700" className="animate-in fade-in slide-in-from-bottom-8 duration-700"
style={{ animationDelay: "500ms" }} style={{ animationDelay: "500ms" }}
> >
<div className="text-center mb-6"> <div className="text-center mb-5">
<p className="text-sm font-semibold text-primary uppercase tracking-wider mb-2"> <p className="text-xs font-semibold text-primary uppercase tracking-wider mb-1.5">
Choose Your Region Choose Your Region
</p> </p>
<h2 className="text-2xl sm:text-3xl font-display font-semibold leading-tight tracking-tight text-foreground"> <h2 className="text-xl sm:text-2xl font-bold leading-tight tracking-tight text-foreground">
Available Plans Available Plans
</h2> </h2>
<p className="text-sm text-muted-foreground mt-2"> <p className="text-xs text-muted-foreground mt-1.5">
Select one region per router rental Select one region per router rental
</p> </p>
</div> </div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 max-w-4xl mx-auto"> <div className="grid grid-cols-1 md:grid-cols-2 gap-5 max-w-3xl mx-auto">
{plans.map(plan => ( {plans.map(plan => (
<VpnPlanCard key={plan.id} plan={plan} /> <VpnPlanCard key={plan.id} plan={plan} />
))} ))}
</div> </div>
{activationFees.length > 0 && ( {activationFees.length > 0 && (
<AlertBanner variant="info" className="mt-6 max-w-4xl mx-auto" title="Activation Fee"> <AlertBanner variant="info" className="mt-5 max-w-3xl mx-auto" title="Activation Fee">
A one-time activation fee of ¥3,000 applies per router rental. Tax (10%) not included. A one-time activation fee of ¥3,000 applies per router rental. Tax (10%) not included.
</AlertBanner> </AlertBanner>
)} )}
</section> </section>
) : ( ) : (
<div className="text-center py-12"> <div className="text-center py-10">
<ShieldCheck className="h-12 w-12 text-muted-foreground mx-auto mb-4" /> <ShieldCheck className="h-10 w-10 text-muted-foreground mx-auto mb-3" />
<h3 className="text-lg font-medium text-foreground mb-2">No VPN Plans Available</h3> <h3 className="text-base font-medium text-foreground mb-1.5">No VPN Plans Available</h3>
<p className="text-muted-foreground mb-6"> <p className="text-sm text-muted-foreground mb-5">
We couldn&apos;t find any VPN plans available at this time. We couldn&apos;t find any VPN plans available at this time.
</p> </p>
<ServicesBackLink <ServicesBackLink
@ -241,7 +233,7 @@ export function VpnPlansContent({
{/* How It Works */} {/* How It Works */}
<HowItWorks steps={vpnSteps} eyebrow="Simple Setup" title="How It Works" /> <HowItWorks steps={vpnSteps} eyebrow="Simple Setup" title="How It Works" />
{/* CTA Section - public variant only */} {/* CTA — public only */}
{variant === "public" && ( {variant === "public" && (
<ServiceCTA <ServiceCTA
eyebrow="Get started today" eyebrow="Get started today"
@ -258,7 +250,7 @@ export function VpnPlansContent({
/> />
)} )}
{/* FAQ Section */} {/* FAQ */}
<ServiceFAQ <ServiceFAQ
items={vpnFaqItems} items={vpnFaqItems}
eyebrow="Common Questions" eyebrow="Common Questions"
@ -275,8 +267,8 @@ export function VpnPlansContent({
</p> </p>
</AlertBanner> </AlertBanner>
{/* Footer Note */} {/* Footer */}
<div className="text-center text-sm text-muted-foreground"> <div className="text-center text-xs text-muted-foreground">
<p>All prices exclude 10% consumption tax.</p> <p>All prices exclude 10% consumption tax.</p>
</div> </div>
</div> </div>

View File

@ -122,6 +122,24 @@ export function PublicSupportView() {
</p> </p>
</div> </div>
{/* Existing Customer Banner */}
<div className="mb-12 max-w-2xl mx-auto">
<div className="flex flex-col sm:flex-row items-center justify-between gap-4 rounded-2xl border border-primary/20 bg-primary/5 px-6 py-5">
<div className="text-center sm:text-left">
<p className="font-semibold text-foreground">Already have an account?</p>
<p className="text-sm text-muted-foreground">
Sign in to view your tickets and get faster support.
</p>
</div>
<Link
href="/auth/login"
className="inline-flex items-center justify-center rounded-full bg-primary px-5 py-2.5 text-sm font-semibold text-primary-foreground shadow hover:bg-primary/90 transition-colors whitespace-nowrap"
>
Sign in
</Link>
</div>
</div>
{/* Knowledge Base Categories */} {/* Knowledge Base Categories */}
<section className="mb-16" aria-labelledby="kb-heading"> <section className="mb-16" aria-labelledby="kb-heading">
<h2 id="kb-heading" className="text-2xl font-bold text-foreground mb-6 text-center"> <h2 id="kb-heading" className="text-2xl font-bold text-foreground mb-6 text-center">
@ -262,20 +280,6 @@ export function PublicSupportView() {
<ContactForm /> <ContactForm />
</div> </div>
</section> </section>
{/* Existing Customer CTA */}
<div className="text-center pt-8 border-t border-border/60">
<p className="text-muted-foreground">
Already have an account?{" "}
<Link
href="/auth/login"
className="font-semibold text-primary hover:text-primary/80 hover:underline transition-colors"
>
Sign in
</Link>{" "}
to access your dashboard and support tickets.
</p>
</div>
</div> </div>
); );
} }