527 lines
26 KiB
TypeScript
527 lines
26 KiB
TypeScript
import { useState, useEffect } from 'react';
|
||
import { useParams } from 'react-router';
|
||
import { motion } from 'motion/react';
|
||
import {
|
||
YoutubeFilled,
|
||
InstagramFilled,
|
||
FacebookFilled,
|
||
GlobeFilled,
|
||
} from '../components/icons/FilledIcons';
|
||
import {
|
||
MapPin,
|
||
Phone,
|
||
Clock,
|
||
Award,
|
||
ShieldCheck,
|
||
Star,
|
||
ExternalLink,
|
||
Users,
|
||
Video,
|
||
MessageSquare,
|
||
ChevronRight,
|
||
Camera,
|
||
BadgeCheck,
|
||
Building2,
|
||
GraduationCap,
|
||
Stethoscope,
|
||
Heart,
|
||
TrendingUp,
|
||
} from 'lucide-react';
|
||
import { fetchReportById } from '../lib/supabase';
|
||
|
||
// ─── 기본값 (DB 데이터로 덮어쓸 수 있음) ───
|
||
|
||
const CLINIC: Record<string, any> = {
|
||
name: '뷰성형외과의원',
|
||
nameEn: 'VIEW Plastic Surgery',
|
||
logo: '/assets/clients/view-clinic/logo-circle.png',
|
||
brandColor: '#7B2D8E',
|
||
established: 2005,
|
||
location: '서울 강남구 봉은사로 107 뷰성형외과 빌딩',
|
||
nearestStation: '9호선 신논현역 3번 출구 도보 1분',
|
||
phone: '02-539-1177',
|
||
hours: [
|
||
{ day: '월~목', time: '10:00 – 19:00' },
|
||
{ day: '금요일', time: '10:00 – 21:00' },
|
||
{ day: '토요일', time: '10:00 – 17:00' },
|
||
{ day: '일/공휴일', time: '휴진' },
|
||
],
|
||
specialties: ['가슴성형', '안면윤곽', '양악', '눈성형', '코성형', '지방흡입', '리프팅', '피부시술', '필러/보톡스'],
|
||
certifications: [
|
||
'수술실 CCTV 운영',
|
||
'전담 마취과 전문의 상주',
|
||
'입원실 완비',
|
||
'의료진 실명 공개',
|
||
'응급 대응 체계',
|
||
'분야별 공동 진료',
|
||
'전용 휴식 공간',
|
||
'시술 후 관리',
|
||
'야간 진료',
|
||
'여성 의사 진료',
|
||
],
|
||
mediaAppearances: ['렛미인 TV 출연', '보건복지부장관 표창 수상', '안면윤곽 대상 수상', '모티바 사용량 1위'],
|
||
websites: [
|
||
{ label: '공식 홈페이지', url: 'viewclinic.com', primary: true },
|
||
{ label: '영문 사이트', url: 'viewplasticsurgery.com', primary: false },
|
||
],
|
||
socialChannels: [
|
||
{ platform: 'YouTube', handle: '@ViewclinicKR', url: 'youtube.com/@ViewclinicKR', followers: '103K 구독자', videos: '1,064개 영상', icon: YoutubeFilled, color: '#FF0000' },
|
||
{ platform: 'Instagram KR', handle: '@viewplastic', url: 'instagram.com/viewplastic', followers: '14K 팔로워', videos: '1,409 게시물', icon: InstagramFilled, color: '#E1306C' },
|
||
{ platform: 'Instagram EN', handle: '@view_plastic_surgery', url: 'instagram.com/view_plastic_surgery', followers: '68.8K 팔로워', videos: '2,524 게시물', icon: InstagramFilled, color: '#E1306C' },
|
||
{ platform: 'Facebook', handle: 'View Plastic Surgery', url: 'facebook.com/viewclinic', followers: '88K 팔로워', videos: '', icon: FacebookFilled, color: '#1877F2' },
|
||
{ platform: '카카오톡', handle: '뷰성형외과의원', url: 'pf.kakao.com/_xbtVxjl', followers: '상담 채널', videos: '', icon: MessageSquare as any, color: '#FEE500' },
|
||
],
|
||
};
|
||
|
||
// ─── 의료진 ───
|
||
|
||
const DOCTORS: Record<string, any>[] = [
|
||
{ name: '최순우', title: '대표원장', specialty: '가슴성형', credentials: '서울대 출신, 의학박사', rating: 9.4, reviews: 1812, featured: true },
|
||
{ name: '정재현', title: '원장', specialty: '가슴성형', credentials: '성형외과 전문의', rating: 9.6, reviews: 3177, featured: false },
|
||
{ name: '김정민', title: '원장', specialty: '리프팅 · 눈성형', credentials: '성형외과 전문의', rating: 9.7, reviews: 878, featured: false },
|
||
{ name: '윤창운', title: '원장', specialty: '안면윤곽 · 양악', credentials: '성형외과 전문의', rating: 9.6, reviews: 764, featured: false },
|
||
{ name: '조진우', title: '원장', specialty: '리프팅 · 지방 · 눈코', credentials: '성형외과 전문의', rating: 9.6, reviews: 1624, featured: false },
|
||
{ name: '김도형', title: '원장', specialty: '눈성형 · 코성형', credentials: '성형외과 전문의', rating: 9.7, reviews: 191, featured: false },
|
||
];
|
||
|
||
// ─── 플랫폼별 평점 ───
|
||
|
||
const RATINGS: Record<string, any>[] = [
|
||
{ platform: '강남언니', rating: '9.5', scale: '/10', reviews: '18,961건', color: '#FF6B8A', pct: 95 },
|
||
{ platform: '네이버 플레이스', rating: '4.6', scale: '/5', reviews: '324건', color: '#03C75A', pct: 92 },
|
||
{ platform: 'Google Maps', rating: '4.3', scale: '/5', reviews: '187건', color: '#4285F4', pct: 86 },
|
||
];
|
||
|
||
// ─── 시술 가격 (강남언니 실제 데이터) ───
|
||
|
||
const PROCEDURES = [
|
||
{ name: '가슴성형 (보형물)', price: '₩2,650,000~', category: '가슴' },
|
||
{ name: '모티바 가슴성형', price: '₩11,550,000~', category: '가슴' },
|
||
{ name: '눈성형 (매몰/절개)', price: '₩440,000~', category: '눈' },
|
||
{ name: '코성형', price: '₩990,000~', category: '코' },
|
||
{ name: '안면윤곽', price: '₩3,289,000~', category: '윤곽' },
|
||
{ name: '지방흡입', price: '₩1,100,000~', category: '바디' },
|
||
{ name: '실리프팅', price: '₩1,958,000~', category: '리프팅' },
|
||
{ name: '필러', price: '₩220,000~', category: '피부' },
|
||
];
|
||
|
||
// ─── Component ───
|
||
|
||
export default function ClinicProfilePage() {
|
||
const { id } = useParams<{ id: string }>();
|
||
const [isLoading, setIsLoading] = useState(true);
|
||
const [error, setError] = useState<string | null>(null);
|
||
|
||
// Override CLINIC data with DB data when available
|
||
useEffect(() => {
|
||
if (!id) { setIsLoading(false); return; }
|
||
fetchReportById(id)
|
||
.then((row) => {
|
||
const report = row.report as Record<string, unknown>;
|
||
const clinicInfo = report.clinicInfo as Record<string, unknown> | undefined;
|
||
const scrapeData = row.scrape_data as Record<string, unknown> | undefined;
|
||
const clinic = (scrapeData?.clinic as Record<string, unknown>) || {};
|
||
|
||
if (clinicInfo?.name) CLINIC.name = clinicInfo.name as string;
|
||
if (clinicInfo?.address) CLINIC.location = clinicInfo.address as string;
|
||
if (clinicInfo?.phone) CLINIC.phone = clinicInfo.phone as string;
|
||
if (clinicInfo?.services) CLINIC.specialties = clinicInfo.services as string[];
|
||
if (clinicInfo?.doctors) {
|
||
const docs = clinicInfo.doctors as { name: string; specialty: string }[];
|
||
DOCTORS.length = 0;
|
||
docs.forEach((d) => {
|
||
DOCTORS.push({ name: d.name, title: '원장', specialty: d.specialty, credentials: '성형외과 전문의', rating: 0, reviews: 0, featured: false });
|
||
});
|
||
if (DOCTORS.length > 0) DOCTORS[0].featured = true;
|
||
}
|
||
CLINIC.nameEn = (row.clinic_name || '').includes('의원') ? '' : row.clinic_name || '';
|
||
|
||
// Update website
|
||
const domain = (() => { try { return new URL(row.url || '').hostname; } catch { return row.url || ''; } })();
|
||
CLINIC.websites = [{ label: '공식 홈페이지', url: domain, primary: true }];
|
||
|
||
// Update social from socialHandles
|
||
const handles = report.socialHandles as Record<string, string | null> | undefined;
|
||
if (handles) {
|
||
CLINIC.socialChannels = [];
|
||
if (handles.instagram) CLINIC.socialChannels.push({ platform: 'Instagram', handle: `@${handles.instagram}`, url: `instagram.com/${handles.instagram}`, followers: '-', videos: '', icon: InstagramFilled, color: '#E1306C' });
|
||
if (handles.youtube) CLINIC.socialChannels.push({ platform: 'YouTube', handle: `@${handles.youtube}`, url: `youtube.com/${handles.youtube}`, followers: '-', videos: '', icon: YoutubeFilled, color: '#FF0000' });
|
||
if (handles.facebook) CLINIC.socialChannels.push({ platform: 'Facebook', handle: handles.facebook, url: `facebook.com/${handles.facebook}`, followers: '-', videos: '', icon: FacebookFilled, color: '#1877F2' });
|
||
}
|
||
|
||
// Update ratings from channel analysis
|
||
const chAnalysis = report.channelAnalysis as Record<string, Record<string, unknown>> | undefined;
|
||
if (chAnalysis) {
|
||
RATINGS.length = 0;
|
||
if (chAnalysis.gangnamUnni?.rating) {
|
||
const guRating = chAnalysis.gangnamUnni.rating as number;
|
||
const guScale = guRating > 5 ? '/10' : '/5';
|
||
const guPct = guRating > 5 ? (guRating / 10) * 100 : (guRating / 5) * 100;
|
||
RATINGS.push({ platform: '강남언니', rating: `${guRating}`, scale: guScale, reviews: `${chAnalysis.gangnamUnni.reviews ?? '-'}건`, color: '#FF6B8A', pct: guPct });
|
||
}
|
||
if (chAnalysis.naverPlace?.rating) RATINGS.push({ platform: '네이버 플레이스', rating: `${chAnalysis.naverPlace.rating}`, scale: '/5', reviews: `${chAnalysis.naverPlace.reviews ?? '-'}건`, color: '#03C75A', pct: ((chAnalysis.naverPlace.rating as number) / 5) * 100 });
|
||
}
|
||
})
|
||
.catch((err) => setError(err.message))
|
||
.finally(() => setIsLoading(false));
|
||
}, [id]);
|
||
|
||
if (isLoading) {
|
||
return (
|
||
<div className="min-h-screen flex items-center justify-center pt-20">
|
||
<div className="flex flex-col items-center gap-4">
|
||
<div className="w-10 h-10 border-4 border-[#6C5CE7] border-t-transparent rounded-full animate-spin" />
|
||
<p className="text-slate-500 text-sm">병원 프로필을 불러오는 중...</p>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
if (error) {
|
||
return (
|
||
<div className="min-h-screen flex items-center justify-center pt-20">
|
||
<div className="text-center">
|
||
<p className="text-[#7C3A4B] font-medium mb-2">오류가 발생했습니다</p>
|
||
<p className="text-slate-500 text-sm">{error}</p>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<div className="pt-20 min-h-screen bg-slate-50">
|
||
|
||
{/* ── Hero / Clinic Header ── */}
|
||
<div className="bg-[#0A1128] pt-10 pb-16 px-6 relative overflow-hidden">
|
||
<div className="absolute top-0 right-0 w-[500px] h-[500px] rounded-full bg-[#7B2D8E]/10 blur-[120px]" />
|
||
<div className="absolute bottom-0 left-0 w-[300px] h-[300px] rounded-full bg-purple-500/5 blur-[100px]" />
|
||
|
||
<div className="max-w-5xl mx-auto relative">
|
||
{/* Breadcrumb */}
|
||
<div className="flex items-center gap-2 text-xs text-purple-300/60 mb-6">
|
||
<span>병원 검색</span>
|
||
<ChevronRight size={12} />
|
||
<span>강남구</span>
|
||
<ChevronRight size={12} />
|
||
<span className="text-purple-200">뷰성형외과</span>
|
||
</div>
|
||
|
||
<div className="flex flex-col md:flex-row items-start gap-6">
|
||
{/* Logo */}
|
||
<div className="w-20 h-20 rounded-2xl bg-white/10 backdrop-blur-sm border border-white/20 flex items-center justify-center flex-shrink-0 overflow-hidden">
|
||
<div className="w-16 h-16 rounded-xl flex items-center justify-center" style={{ backgroundColor: CLINIC.brandColor }}>
|
||
<span className="text-white font-serif font-bold text-lg">VIEW</span>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Info */}
|
||
<div className="flex-1">
|
||
<div className="flex items-center gap-3 mb-1">
|
||
<h1 className="font-serif text-2xl md:text-3xl font-bold text-white">{CLINIC.name}</h1>
|
||
<BadgeCheck size={22} className="text-[#9B8AD4]" />
|
||
</div>
|
||
<p className="text-purple-200/60 text-sm mb-4">{CLINIC.nameEn} · 개원 {CLINIC.established}년 · {new Date().getFullYear() - CLINIC.established}년차</p>
|
||
|
||
{/* Quick Stats */}
|
||
<div className="flex flex-wrap gap-3">
|
||
{[
|
||
{ icon: Star, label: '강남언니 9.5점', sub: '18,961 리뷰' },
|
||
{ icon: Users, label: `의료진 ${DOCTORS.length}명`, sub: '성형외과 전문의' },
|
||
{ icon: Video, label: 'YouTube 103K', sub: '1,064 영상' },
|
||
].map((stat) => {
|
||
const Icon = stat.icon;
|
||
return (
|
||
<div key={stat.label} className="flex items-center gap-2 px-3 py-2 rounded-xl bg-white/5 border border-white/10">
|
||
<Icon size={16} className="text-purple-300" />
|
||
<div>
|
||
<p className="text-sm font-medium text-white">{stat.label}</p>
|
||
<p className="text-xs text-purple-300/60">{stat.sub}</p>
|
||
</div>
|
||
</div>
|
||
);
|
||
})}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="max-w-5xl mx-auto px-6 -mt-6">
|
||
<div className="space-y-6">
|
||
|
||
{/* ── 기본 정보 카드 ── */}
|
||
<motion.div
|
||
initial={{ opacity: 0, y: 20 }}
|
||
animate={{ opacity: 1, y: 0 }}
|
||
className="bg-white rounded-2xl border border-slate-100 shadow-[3px_4px_12px_rgba(0,0,0,0.06)] p-6"
|
||
>
|
||
<h2 className="font-serif text-lg font-bold text-primary-900 mb-4 flex items-center gap-2">
|
||
<Building2 size={18} className="text-[#6C5CE7]" />
|
||
기본 정보
|
||
</h2>
|
||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||
<div className="space-y-3">
|
||
<div className="flex items-start gap-3">
|
||
<MapPin size={16} className="text-slate-400 mt-0.5 flex-shrink-0" />
|
||
<div>
|
||
<p className="text-sm font-medium text-primary-900">{CLINIC.location}</p>
|
||
<p className="text-xs text-slate-400">{CLINIC.nearestStation}</p>
|
||
</div>
|
||
</div>
|
||
<div className="flex items-center gap-3">
|
||
<Phone size={16} className="text-slate-400 flex-shrink-0" />
|
||
<p className="text-sm font-medium text-primary-900">{CLINIC.phone}</p>
|
||
</div>
|
||
<div className="flex items-start gap-3">
|
||
<GlobeFilled size={16} className="text-slate-400 flex-shrink-0" />
|
||
<div className="flex flex-wrap gap-2">
|
||
{CLINIC.websites.map(w => (
|
||
<span key={w.url} className={`text-sm ${w.primary ? 'text-[#6C5CE7] font-medium' : 'text-slate-400'}`}>
|
||
{w.url}
|
||
</span>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<div className="flex items-start gap-3">
|
||
<Clock size={16} className="text-slate-400 mt-0.5 flex-shrink-0" />
|
||
<div className="space-y-1">
|
||
{CLINIC.hours.map(h => (
|
||
<div key={h.day} className="flex gap-3 text-sm">
|
||
<span className="text-slate-400 w-16">{h.day}</span>
|
||
<span className={`font-medium ${h.time === '휴진' ? 'text-[#D4889A]' : 'text-primary-900'}`}>{h.time}</span>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</motion.div>
|
||
|
||
{/* ── 플랫폼별 통합 평점 ── */}
|
||
<motion.div
|
||
initial={{ opacity: 0, y: 20 }}
|
||
whileInView={{ opacity: 1, y: 0 }}
|
||
viewport={{ once: true }}
|
||
className="bg-white rounded-2xl border border-slate-100 shadow-[3px_4px_12px_rgba(0,0,0,0.06)] p-6"
|
||
>
|
||
<h2 className="font-serif text-lg font-bold text-primary-900 mb-4 flex items-center gap-2">
|
||
<Star size={18} className="text-[#6C5CE7]" />
|
||
통합 평점
|
||
</h2>
|
||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||
{RATINGS.map((r, i) => (
|
||
<motion.div
|
||
key={r.platform}
|
||
initial={{ opacity: 0, y: 10 }}
|
||
whileInView={{ opacity: 1, y: 0 }}
|
||
viewport={{ once: true }}
|
||
transition={{ delay: i * 0.1 }}
|
||
className="rounded-xl border border-slate-100 p-4"
|
||
>
|
||
<div className="flex items-center justify-between mb-3">
|
||
<span className="text-sm text-slate-500">{r.platform}</span>
|
||
<span className="text-xs text-slate-400">{r.reviews}</span>
|
||
</div>
|
||
<div className="flex items-baseline gap-1 mb-3">
|
||
<span className="text-3xl font-bold text-primary-900">{r.rating}</span>
|
||
<span className="text-sm text-slate-400">{r.scale}</span>
|
||
</div>
|
||
<div className="h-2 bg-slate-100 rounded-full overflow-hidden">
|
||
<motion.div
|
||
initial={{ width: 0 }}
|
||
whileInView={{ width: `${r.pct}%` }}
|
||
viewport={{ once: true }}
|
||
transition={{ duration: 0.8, delay: i * 0.1 + 0.2 }}
|
||
className="h-full rounded-full"
|
||
style={{ backgroundColor: r.color }}
|
||
/>
|
||
</div>
|
||
</motion.div>
|
||
))}
|
||
</div>
|
||
</motion.div>
|
||
|
||
{/* ── 의료진 ── */}
|
||
<motion.div
|
||
initial={{ opacity: 0, y: 20 }}
|
||
whileInView={{ opacity: 1, y: 0 }}
|
||
viewport={{ once: true }}
|
||
className="bg-white rounded-2xl border border-slate-100 shadow-[3px_4px_12px_rgba(0,0,0,0.06)] p-6"
|
||
>
|
||
<h2 className="font-serif text-lg font-bold text-primary-900 mb-4 flex items-center gap-2">
|
||
<Stethoscope size={18} className="text-[#6C5CE7]" />
|
||
의료진 ({DOCTORS.length}명)
|
||
</h2>
|
||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
|
||
{DOCTORS.map((doc, i) => (
|
||
<motion.div
|
||
key={doc.name}
|
||
initial={{ opacity: 0, y: 10 }}
|
||
whileInView={{ opacity: 1, y: 0 }}
|
||
viewport={{ once: true }}
|
||
transition={{ delay: i * 0.06 }}
|
||
className={`rounded-xl border p-4 ${doc.featured ? 'border-[#D5CDF5] bg-[#F3F0FF]/30' : 'border-slate-100'}`}
|
||
>
|
||
<div className="flex items-center gap-3 mb-2">
|
||
<div className="w-10 h-10 rounded-full bg-slate-100 flex items-center justify-center flex-shrink-0">
|
||
<GraduationCap size={18} className="text-slate-400" />
|
||
</div>
|
||
<div>
|
||
<div className="flex items-center gap-1.5">
|
||
<p className="font-semibold text-primary-900">{doc.name}</p>
|
||
<span className="text-xs text-slate-400">{doc.title}</span>
|
||
{doc.featured && <span className="text-xs px-1.5 py-0.5 rounded bg-[#6C5CE7] text-white font-medium">대표</span>}
|
||
</div>
|
||
<p className="text-xs text-slate-400">{doc.credentials}</p>
|
||
</div>
|
||
</div>
|
||
<div className="flex items-center justify-between mt-3 pt-3 border-t border-slate-100">
|
||
<span className="text-xs text-slate-400">{doc.specialty}</span>
|
||
<div className="flex items-center gap-2">
|
||
<span className="text-sm font-bold text-primary-900">{doc.rating}</span>
|
||
<span className="text-xs text-slate-400">/ 10</span>
|
||
<span className="text-xs text-slate-300">·</span>
|
||
<span className="text-xs text-slate-400">{doc.reviews.toLocaleString()}건</span>
|
||
</div>
|
||
</div>
|
||
</motion.div>
|
||
))}
|
||
</div>
|
||
</motion.div>
|
||
|
||
{/* ── 시술 및 가격 ── */}
|
||
<motion.div
|
||
initial={{ opacity: 0, y: 20 }}
|
||
whileInView={{ opacity: 1, y: 0 }}
|
||
viewport={{ once: true }}
|
||
className="bg-white rounded-2xl border border-slate-100 shadow-[3px_4px_12px_rgba(0,0,0,0.06)] p-6"
|
||
>
|
||
<h2 className="font-serif text-lg font-bold text-primary-900 mb-4 flex items-center gap-2">
|
||
<Heart size={18} className="text-[#6C5CE7]" />
|
||
시술 및 가격
|
||
</h2>
|
||
<p className="text-xs text-slate-400 mb-4">강남언니 기준 · 실제 가격은 상담 후 결정</p>
|
||
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
|
||
{PROCEDURES.map((proc, i) => (
|
||
<motion.div
|
||
key={proc.name}
|
||
initial={{ opacity: 0, x: -10 }}
|
||
whileInView={{ opacity: 1, x: 0 }}
|
||
viewport={{ once: true }}
|
||
transition={{ delay: i * 0.04 }}
|
||
className="flex items-center justify-between px-4 py-3 rounded-xl hover:bg-slate-50 transition-colors"
|
||
>
|
||
<div className="flex items-center gap-3">
|
||
<span className="text-xs px-2 py-0.5 rounded-full bg-[#F3F0FF] text-[#4A3A7C] font-medium">{proc.category}</span>
|
||
<span className="text-sm text-primary-900">{proc.name}</span>
|
||
</div>
|
||
<span className="text-sm font-bold text-primary-900">{proc.price}</span>
|
||
</motion.div>
|
||
))}
|
||
</div>
|
||
</motion.div>
|
||
|
||
{/* ── 인증 & 수상 ── */}
|
||
<motion.div
|
||
initial={{ opacity: 0, y: 20 }}
|
||
whileInView={{ opacity: 1, y: 0 }}
|
||
viewport={{ once: true }}
|
||
className="bg-white rounded-2xl border border-slate-100 shadow-[3px_4px_12px_rgba(0,0,0,0.06)] p-6"
|
||
>
|
||
<h2 className="font-serif text-lg font-bold text-primary-900 mb-4 flex items-center gap-2">
|
||
<ShieldCheck size={18} className="text-[#6C5CE7]" />
|
||
인증 및 수상
|
||
</h2>
|
||
<div className="grid grid-cols-2 md:grid-cols-5 gap-2 mb-6">
|
||
{CLINIC.certifications.map((cert, i) => (
|
||
<motion.div
|
||
key={cert}
|
||
initial={{ opacity: 0, scale: 0.95 }}
|
||
whileInView={{ opacity: 1, scale: 1 }}
|
||
viewport={{ once: true }}
|
||
transition={{ delay: i * 0.03 }}
|
||
className="flex items-center gap-2 px-3 py-2.5 rounded-xl bg-[#F3F0FF]/50 border border-[#D5CDF5]/50"
|
||
>
|
||
<BadgeCheck size={14} className="text-[#9B8AD4] flex-shrink-0" />
|
||
<span className="text-xs text-[#4A3A7C] font-medium">{cert}</span>
|
||
</motion.div>
|
||
))}
|
||
</div>
|
||
<div className="flex flex-wrap gap-2">
|
||
{CLINIC.mediaAppearances.map(m => (
|
||
<span key={m} className="flex items-center gap-1.5 px-3 py-1.5 rounded-full bg-[#FFF6ED] border border-[#F5E0C5] text-xs text-[#7C5C3A] font-medium">
|
||
<Award size={12} />
|
||
{m}
|
||
</span>
|
||
))}
|
||
</div>
|
||
</motion.div>
|
||
|
||
{/* ── 온라인 채널 ── */}
|
||
<motion.div
|
||
initial={{ opacity: 0, y: 20 }}
|
||
whileInView={{ opacity: 1, y: 0 }}
|
||
viewport={{ once: true }}
|
||
className="bg-white rounded-2xl border border-slate-100 shadow-[3px_4px_12px_rgba(0,0,0,0.06)] p-6"
|
||
>
|
||
<h2 className="font-serif text-lg font-bold text-primary-900 mb-4 flex items-center gap-2">
|
||
<TrendingUp size={18} className="text-[#6C5CE7]" />
|
||
온라인 채널
|
||
</h2>
|
||
<div className="space-y-3">
|
||
{CLINIC.socialChannels.map((ch, i) => {
|
||
const Icon = ch.icon;
|
||
return (
|
||
<motion.div
|
||
key={ch.platform}
|
||
initial={{ opacity: 0, x: -10 }}
|
||
whileInView={{ opacity: 1, x: 0 }}
|
||
viewport={{ once: true }}
|
||
transition={{ delay: i * 0.06 }}
|
||
className="flex items-center gap-4 px-4 py-3 rounded-xl border border-slate-100 hover:border-slate-200 transition-colors"
|
||
>
|
||
<div className="w-10 h-10 rounded-xl flex items-center justify-center flex-shrink-0" style={{ backgroundColor: `${ch.color}12` }}>
|
||
<Icon size={20} style={{ color: ch.color }} />
|
||
</div>
|
||
<div className="flex-1 min-w-0">
|
||
<div className="flex items-center gap-2">
|
||
<p className="font-medium text-primary-900 text-sm">{ch.platform}</p>
|
||
<span className="text-xs text-slate-400">{ch.handle}</span>
|
||
</div>
|
||
<p className="text-xs text-slate-400">{ch.url}</p>
|
||
</div>
|
||
<div className="text-right flex-shrink-0">
|
||
<p className="text-sm font-semibold text-primary-900">{ch.followers}</p>
|
||
{ch.videos && <p className="text-xs text-slate-400">{ch.videos}</p>}
|
||
</div>
|
||
<ExternalLink size={14} className="text-slate-300 flex-shrink-0" />
|
||
</motion.div>
|
||
);
|
||
})}
|
||
</div>
|
||
</motion.div>
|
||
|
||
{/* ── 데이터 출처 고지 ── */}
|
||
<motion.div
|
||
initial={{ opacity: 0 }}
|
||
whileInView={{ opacity: 1 }}
|
||
viewport={{ once: true }}
|
||
className="rounded-2xl bg-slate-100/50 border border-slate-200/50 p-5 mb-10"
|
||
>
|
||
<p className="text-xs text-slate-400 leading-relaxed">
|
||
<span className="font-semibold text-slate-500">데이터 출처:</span>{' '}
|
||
본 프로필은 공개된 정보를 자동 수집하여 구성되었습니다.
|
||
평점 및 리뷰 수는 강남언니, 네이버 플레이스, Google Maps에서 집계하였으며,
|
||
시술 가격은 강남언니 기준입니다. 의료진 정보는 병원 공식 홈페이지 및 강남언니에서 수집하였습니다.
|
||
마지막 업데이트: 2026-03-30 · 정보 수정 요청: contact@infinith.io
|
||
</p>
|
||
</motion.div>
|
||
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|