feat: GA4 전환 이벤트 추적 + 전 페이지 스크롤 리빌 애니메이션

- lib/gtag.ts: GA4 이벤트 유틸리티 (trackCTAClick, trackToolDemo, trackDownload, trackOutboundClick)
- ContactModal/ContactForm: 공용 trackEvent로 리팩토링 + generate_lead 이벤트
- 홈/tools/automation/prompt/website: CTA 클릭 이벤트 추적 추가
- 홈/freelance/ai-kit: IntersectionObserver 스크롤 리빌 애니메이션 신규 추가
- automation/prompt: GA4 trackCTAClick 적용

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-03 07:34:17 +09:00
parent c7bf0253e3
commit 5d2fd4be1f
10 changed files with 230 additions and 60 deletions

View File

@@ -1,8 +1,9 @@
'use client';
import { useState } from 'react';
import { useState, useEffect, useRef } from 'react';
import Link from 'next/link';
import ContactModal from './components/ContactModal';
import { trackCTAClick } from '../lib/gtag';
/* ═══════════════════════════════════════════════════
쟁승메이드 홈페이지 — 리뉴얼 v2
@@ -169,11 +170,50 @@ const SERVICE_LIST = [
},
];
function useScrollReveal() {
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
const el = ref.current;
if (!el) return;
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add('is-visible');
observer.unobserve(entry.target);
}
});
},
{ threshold: 0.1, rootMargin: '0px 0px -40px 0px' }
);
el.querySelectorAll('.reveal').forEach((child) => observer.observe(child));
return () => observer.disconnect();
}, []);
return ref;
}
export default function Home() {
const [modalOpen, setModalOpen] = useState(false);
const containerRef = useScrollReveal();
return (
<div className="min-h-full">
<div className="min-h-full" ref={containerRef}>
<style>{`
.reveal {
opacity: 0;
transform: translateY(1.5rem);
transition: opacity 0.7s cubic-bezier(0.16, 1, 0.3, 1),
transform 0.7s cubic-bezier(0.16, 1, 0.3, 1);
}
.reveal.is-visible {
opacity: 1;
transform: translateY(0);
}
.reveal-d1 { transition-delay: 80ms; }
.reveal-d2 { transition-delay: 160ms; }
.reveal-d3 { transition-delay: 240ms; }
.reveal-d4 { transition-delay: 320ms; }
`}</style>
<ContactModal
isOpen={modalOpen}
onClose={() => setModalOpen(false)}
@@ -248,7 +288,7 @@ export default function Home() {
{/* CTA */}
<div className="flex flex-wrap gap-3 mb-14">
<button
onClick={() => setModalOpen(true)}
onClick={() => { trackCTAClick('무료 상담 신청', '/'); setModalOpen(true); }}
className="inline-flex items-center gap-2 bg-[#1a56db] hover:bg-[#1e4fc2] text-white px-8 py-4 rounded-xl font-bold text-sm transition-colors"
>
@@ -292,7 +332,7 @@ export default function Home() {
══════════════════════════════════════ */}
<section className="bg-white px-6 py-16 lg:px-14 lg:py-20">
<div className="max-w-5xl mx-auto">
<div className="flex items-end justify-between mb-10 flex-wrap gap-4">
<div className="reveal flex items-end justify-between mb-10 flex-wrap gap-4">
<div>
<p className="font-mono text-xs text-[#1a56db]/60 tracking-widest uppercase mb-2">
Client Pain Points
@@ -310,10 +350,10 @@ export default function Home() {
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
{PAIN_POINTS.map((p) => (
{PAIN_POINTS.map((p, i) => (
<div
key={p.problem}
className="border border-[#e2e8f0] rounded-2xl p-6 hover:border-[#cbd5e1] hover:shadow-sm transition-all bg-white"
className={`reveal reveal-d${i + 1} border border-[#e2e8f0] rounded-2xl p-6 hover:border-[#cbd5e1] hover:shadow-sm transition-all bg-white`}
>
<div className="flex items-start gap-4">
<div className={`w-9 h-9 rounded-lg flex items-center justify-center flex-shrink-0 ${p.color}`}>
@@ -338,10 +378,10 @@ export default function Home() {
══════════════════════════════════════ */}
<section className="bg-[#04102b] px-6 py-16 lg:px-14 lg:py-20">
<div className="max-w-5xl mx-auto">
<p className="font-mono text-xs text-[#5ba4ff]/40 tracking-widest uppercase mb-3">
<p className="reveal font-mono text-xs text-[#5ba4ff]/40 tracking-widest uppercase mb-3">
About
</p>
<div className="grid lg:grid-cols-2 gap-10 lg:gap-16 items-start">
<div className="reveal grid lg:grid-cols-2 gap-10 lg:gap-16 items-start">
{/* 좌측: 스토리 */}
<div>
<h2
@@ -432,7 +472,7 @@ export default function Home() {
══════════════════════════════════════ */}
<section className="bg-[#f8faff] px-6 py-16 lg:px-14 lg:py-20">
<div className="max-w-5xl mx-auto">
<div className="mb-10">
<div className="reveal mb-10">
<p className="font-mono text-xs text-[#1a56db]/60 tracking-widest uppercase mb-2">
Guarantee
</p>
@@ -444,7 +484,7 @@ export default function Home() {
</h2>
</div>
<div className="space-y-px">
<div className="reveal space-y-px">
{PROMISES.map((p, i) => (
<div
key={p.number}
@@ -481,7 +521,7 @@ export default function Home() {
══════════════════════════════════════ */}
<section className="bg-[#04102b] px-6 py-16 lg:px-14 lg:py-20">
<div className="max-w-5xl mx-auto">
<div className="mb-10">
<div className="reveal mb-10">
<p className="font-mono text-xs text-[#5ba4ff]/40 tracking-widest uppercase mb-2">
Live Portfolio
</p>
@@ -497,11 +537,11 @@ export default function Home() {
</div>
<div className="grid md:grid-cols-3 gap-4">
{LIVE_SERVICES.map((s) => (
{LIVE_SERVICES.map((s, i) => (
<Link
key={s.name}
href={s.url}
className="group relative flex flex-col border border-white/8 hover:border-white/20 rounded-2xl p-6 transition-all hover:bg-white/3"
className={`reveal reveal-d${i + 1} group relative flex flex-col border border-white/8 hover:border-white/20 rounded-2xl p-6 transition-all hover:bg-white/3`}
>
{/* 상단 */}
<div className="flex items-center justify-between mb-4">
@@ -551,7 +591,7 @@ export default function Home() {
══════════════════════════════════════ */}
<section className="bg-white px-6 py-16 lg:px-14 lg:py-20">
<div className="max-w-5xl mx-auto">
<div className="flex items-end justify-between flex-wrap gap-4 mb-8">
<div className="reveal flex items-end justify-between flex-wrap gap-4 mb-8">
<div>
<p className="font-mono text-xs text-[#1a56db]/60 tracking-widest uppercase mb-2">Services</p>
<h2 className="text-2xl md:text-3xl font-extrabold text-[#04102b]" style={{ wordBreak: 'keep-all' }}>
@@ -566,7 +606,7 @@ export default function Home() {
</button>
</div>
<div className="divide-y divide-[#f1f5f9]">
<div className="reveal divide-y divide-[#f1f5f9]">
{SERVICE_LIST.map((s) => (
<Link
key={s.href}
@@ -617,7 +657,7 @@ export default function Home() {
<section className="bg-[#04102b] px-6 py-20 lg:px-14">
<div className="max-w-5xl mx-auto">
{/* 무료 이벤트 배너 */}
<div className="border border-white/8 rounded-2xl p-6 md:p-8 mb-10 flex flex-col md:flex-row items-start md:items-center justify-between gap-6">
<div className="reveal border border-white/8 rounded-2xl p-6 md:p-8 mb-10 flex flex-col md:flex-row items-start md:items-center justify-between gap-6">
<div>
<div className="inline-flex items-center gap-2 bg-red-500/15 border border-red-500/20 text-red-400 text-xs font-bold px-3 py-1 rounded-full mb-3">
<span className="w-1.5 h-1.5 rounded-full bg-red-400 animate-pulse" />
@@ -639,7 +679,7 @@ export default function Home() {
</div>
{/* 메인 CTA */}
<div className="text-center">
<div className="reveal text-center">
<p className="font-mono text-xs text-[#5ba4ff]/40 tracking-widest uppercase mb-4">Get Started</p>
<h2
className="text-3xl md:text-5xl font-extrabold text-white mb-4 leading-tight"