// ─── Demo Modal ────────────────────────────────────────────────────────────── const DemoModal = ({ sol, onClose, lang }) => { const [step, setStep] = React.useState(0); const isKo = lang === 'ko'; const screens = [ { title: isKo ? '로그인 화면' : 'Login Screen', subtitle: isKo ? `${sol.title} 관리 콘솔` : `${sol.title} Management Console`, content: (
🛡️
{sol.title}
Security Console v3.2
ID
admin@company.com
{isKo?'비밀번호':'Password'}
••••••••
setStep(1)}> {isKo?'로그인':'Sign In'} →
) }, { title: isKo ? '위협 탐지 대시보드' : 'Threat Detection Dashboard', subtitle: isKo ? '실시간 보안 이벤트 모니터링' : 'Real-time Security Event Monitoring', content: (
{[ {label:isKo?'오늘의 위협':'Today\'s Threats',val:'247',color:'#EF4444'}, {label:isKo?'차단된 공격':'Blocked Attacks',val:'246',color:'#0F9650'}, {label:isKo?'위험도':'Risk Level',val:'LOW',color:'#06B6D4'}, ].map((s,i)=>(
{s.val}
{s.label}
))}
{isKo?'🚨 스피어 피싱 탐지':'🚨 Spear Phishing Detected'}
{isKo?'발신: ceo-fake@malicious.com → 수신: finance@yourcompany.com':'From: ceo-fake@malicious.com → To: finance@yourcompany.com'}
{isKo?'악성 첨부파일 포함 — 자동 격리 완료 ✅':'Malicious attachment detected — Auto-quarantined ✅'}
✅ {isKo?'AI 엔진 자동 차단 완료 — 246/247건 처리':'AI Engine auto-blocked — 246/247 threats handled'}
) }, { title: isKo ? '접근 차단 시뮬레이션' : 'Access Block Simulation', subtitle: isKo ? '비인가 접근 시도 실시간 대응' : 'Real-time Unauthorized Access Response', content: (
⚠️ {isKo?'비인가 접근 시도 감지':'Unauthorized Access Attempt Detected'}
{[ {label:'IP', val:'185.234.218.97 (Russia)'}, {label:isKo?'대상':'Target', val:isKo?'내부 HR 시스템 /admin':'Internal HR System /admin'}, {label:isKo?'시도 횟수':'Attempts', val:'847 times in 3min'}, {label:isKo?'공격 유형':'Attack Type', val:'Brute Force + Credential Stuffing'}, ].map((r,i)=>(
{r.label} {r.val}
))}
{[ {action:isKo?'IP 즉시 차단':'IP Immediately Blocked', done:true}, {action:isKo?'관리자 알림 발송':'Admin Alert Sent', done:true}, {action:isKo?'해당 계정 임시 잠금':'Account Temporarily Locked', done:true}, {action:isKo?'포렌식 로그 저장 중...':'Saving Forensic Logs...', done:false}, ].map((a,i)=>(
{a.done?'✅':'⏳'} {a.action}
))}
) }, { title: isKo ? '로그 분석 & 리포트' : 'Log Analysis & Report', subtitle: isKo ? 'AI 기반 보안 인사이트' : 'AI-Powered Security Insights', content: (
{isKo?'이번 달 보안 요약 (AI 생성)':'Monthly Security Summary (AI Generated)'}
{[ {label:isKo?'총 탐지 이벤트':'Total Events', val:'12,847', bar:85, color:'#2563EB'}, {label:isKo?'자동 차단':'Auto-blocked', val:'12,831', bar:99, color:'#0F9650'}, {label:isKo?'수동 검토':'Manual Review', val:'16', bar:1, color:'#F59E0B'}, {label:isKo?'침해 성공':'Breaches', val:'0', bar:0, color:'#EF4444'}, ].map((r,i)=>(
{r.label} {r.val}
))}
🤖 AI {isKo?'추천 조치':'Recommended Actions'}
{isKo ?'• 러시아 IP 대역 (185.234.x.x) 전체 차단 권장\n• 임시 잠금된 계정 2건 비밀번호 재설정 필요\n• 다음 주 정기 취약점 스캔 예약됨' :'• Block entire Russian IP range (185.234.x.x)\n• 2 locked accounts need password reset\n• Weekly vulnerability scan scheduled for next week'}
) } ]; const cur = screens[step]; return (
e.target === e.currentTarget && onClose()}>
{isKo?'삼에스소프트':'3ES Soft'} — {sol.title} {isKo?'인터랙티브 데모':'Interactive Demo'}
STEP {step+1} / {screens.length}
{cur.title}
{cur.subtitle}
{cur.content}
{screens.map((_,i)=>
)}
{step < screens.length-1 ? : }
); }; // ─── Quote Modal (고도화) ──────────────────────────────────────────────────── const QuoteModal = ({ t, onClose, solTitle, lang }) => { const isKo = lang === 'ko'; const L = o => (o && (o[lang] || o.ko || o.en)) || ''; const spec = React.useMemo(() => window.quoteSpecFor(solTitle), [solTitle]); const [form, setForm] = React.useState(() => { const init = { _scale: spec.basis.def, _deploy: L(spec.deploy[0]) }; if (spec.line) init._line = L(spec.line.opts[0]); spec.fields.forEach(f => { init[f.k] = f.t === 'multi' ? [] : (f.t === 'number' ? (f.def ?? 1) : L(f.opts[0])); }); return init; }); const [sent, setSent] = React.useState(false); const [sending, setSending] = React.useState(false); const [errs, setErrs] = React.useState({}); const [submitErr, setSubmitErr] = React.useState(''); const [ticket, setTicket] = React.useState(''); const up = (k,v) => { setForm(f=>({...f,[k]:v})); if (errs[k]) setErrs(e=>{const n={...e};delete n[k];return n;}); }; const toggle = (k,v) => setForm(f=>{ const cur = f[k]||[]; return {...f, [k]: cur.includes(v) ? cur.filter(x=>x!==v) : [...cur, v]}; }); // 필수 입력 규칙 const rules = [ { k:'company', type:'text' }, { k:'name', type:'text' }, { k:'phone', type:'phone' }, { k:'email', type:'email' }, { k:'_agree', type:'agree' }, ]; const handleSubmit = async () => { const v = window.validateForm(form, rules, lang); if (!v.ok) { setErrs(v.errors); const first = document.querySelector('.modal-box .err'); if (first) first.focus({preventScroll:true}); return; } setSending(true); const detail = spec.fields.map(f => { const v = form[f.k]; return `${L(f.label)}: ${Array.isArray(v) ? (v.length?v.join(', '):'-') : (v||'-')}`; }).join('\n'); const msg = `새 견적 요청 솔루션: ${solTitle||'미선택'} 회사명: ${form.company||'-'} 담당자: ${form.name||'-'} 연락처: ${form.phone||'-'} 이메일: ${form.email||'-'} ${spec.line ? L(spec.line.label)+': '+form._line+'\n' : ''}${L(spec.basis.label)}: ${form._scale}${L(spec.basis.unit)} 도입 방식: ${form._deploy} ${detail} 환경: ${form.env||'-'} ${spec.g2b && spec.g2b.verified ? '조달 등재: '+L(spec.g2b.vendor)+' / 식별번호 '+spec.g2b.items.map(i=>i.id).join(', ') : ''} 문의: ${form.message||'-'}`; const r = await sendTelegram(msg, { consent: form._agree === true }); setSending(false); if (!r || !r.ok) { setSubmitErr(isKo ? '요청 전달에 실패했습니다. sales@3es.co.kr 또는 대표전화 031-212-3390 으로 연락 주세요.' : 'Submission failed. Please email sales@3es.co.kr or call +82-31-212-3390.'); return; } setTicket(r.ticket); setSent(true); }; return (
e.target===e.currentTarget&&onClose()}>

{t.quote.title}{solTitle?` — ${solTitle}`:''}

{sent ? (
✅
{t.quote.success}
{ticket &&
{isKo?'접수번호':'Ref.'}: {ticket}
}
{isKo?'담당자가 확인 후 영업일 기준 1일 내에 회신드립니다.':'A specialist will review and reply within one business day.'}
) : ( <> {/* 솔루션별 규모 · 구성 요건 */}
{isKo?'규모 · 구성 요건':'Scale & configuration'}
{spec.line && (
)}
up('_scale',e.target.value)}/>
{spec.fields.map(f => (
{f.t === 'select' && ( )} {f.t === 'number' && ( up(f.k,e.target.value)}/> )} {f.t === 'text' && ( up(f.k,e.target.value)}/> )} {f.t === 'multi' && (
{f.opts.map((o,i)=>{ const v=L(o); const on=(form[f.k]||[]).includes(v); return ( ); })}
)}
))}
{L(spec.note)} {isKo?'상세 금액은 요청 내용 확인 후 담당자가 산정해 회신드립니다.':'A specialist will review your request and send a tailored quote.'}
{isKo?' 표시는 필수 입력 항목입니다.':(lang==='ja'?' は必須項目です。':' Required fields.')}
up('company',e.target.value)}/>
up('name',e.target.value)}/>
up('phone',e.target.value)}/>
up('email',e.target.value)}/>
up('env',e.target.value)}/>
up('url',e.target.value)}/>
up('_agree',v)} onOpenPolicy={()=>{ onClose(); setTimeout(()=>window.__goPrivacy&&window.__goPrivacy(),0); }} purpose={isKo?'견적 요청 접수·처리 및 결과 회신':undefined} items={isKo?'회사명, 담당자명, 연락처, 이메일, 관심 솔루션, 예상 사용자 수, 문의 내용':undefined}/> {submitErr && (
{submitErr}
)}
{isKo?'제출 즉시 담당자에게 전달되며, 영업일 기준 1일 내에 회신드립니다':'Sent to our team immediately — we reply within one business day'}
)}
); }; // ─── AI ChatBot (업그레이드) ───────────────────────────────────────────────── const ChatBot = ({ t, lang, setPage }) => { const [open, setOpen] = React.useState(false); const [msgs, setMsgs] = React.useState([{ role:'bot', text: t.chat.greeting, suggestions: true }]); const [input, setInput] = React.useState(''); const [requested, setRequested] = React.useState(false); // 담당자 연결 요청 접수 여부 const [connecting, setConnecting] = React.useState(false); const [handoff, setHandoff] = React.useState(false); // 사람 상담 전환 카드 노출 const kakaoUrl = (window.SITE_CONFIG && window.SITE_CONFIG.KAKAO_CHANNEL_URL) || ''; const openHours = typeof window.isBusinessHours === 'function' ? window.isBusinessHours() : true; const bottomRef = React.useRef(null); const isKo = lang === 'ko'; const L = isKo ? { connect:'💬 담당자와 실시간 상담', connecting:'담당자 연결 중...', connected:tk=>`담당자에게 상담 요청을 전달했습니다. (접수번호 ${tk})\n바로 상담을 원하시면 아래 카카오톡 채널 또는 031-212-3390 으로 연락해 주세요.`, agent:'담당자', end:'상담 종료', ended:'상담이 종료되었습니다. 다시 궁금한 점이 있으면 언제든 문의해 주세요.', expired:'상담 세션이 만료되었습니다. 다시 연결해 주세요.', fail:'요청 전달에 실패했습니다. 카카오톡 채널 또는 031-212-3390 으로 연락해 주세요.', mode:'실시간 상담', aiMode:'AI 상담', ph:'담당자에게 보낼 메시지를 입력하세요...' } : lang === 'ja' ? { connect:'💬 担当者とリアルタイム相談', connecting:'担当者に接続中...', connected:tk=>`担当者に相談リクエストを送信しました。(受付番号 ${tk})\nお急ぎの場合は 031-212-3390 までご連絡ください。`, agent:'担当者', end:'相談終了', ended:'相談を終了しました。ご不明点があればいつでもお問い合わせください。', expired:'セッションが期限切れです。再接続してください。', fail:'リクエストの送信に失敗しました。031-212-3390 までご連絡ください。', mode:'リアルタイム相談', aiMode:'AI相談', ph:'担当者へのメッセージを入力...' } : { connect:'💬 Chat with a specialist', connecting:'Connecting...', connected:tk=>`Your request has been sent to our team. (Ref. ${tk})\nFor an immediate response, use our KakaoTalk channel or call +82-31-212-3390.`, agent:'Specialist', end:'End chat', ended:'Chat ended. Feel free to reach out anytime.', expired:'Session expired. Please reconnect.', fail:'We could not deliver your request. Please use our KakaoTalk channel or call +82-31-212-3390.', mode:'Live chat', aiMode:'AI chat', ph:'Message our specialist...' }; const KEYWORD_MAP = [ { keys:['메일','이메일','피싱','phishing','email','mail','spam'], sol:'sol_email', label:isKo?'이메일 보안':'Email Security' }, { keys:['시스템','서버','엔드포인트','랜섬','ransomware','endpoint','server','edr','mdm'], sol:'sol_system', label:isKo?'시스템 보안':'System Security' }, { keys:['네트워크','network','방화벽','firewall','vpn','ztna','nac'], sol:'sol_network', label:isKo?'네트워크 보안':'Network Security' }, { keys:['ai','인공지능','이상탐지','siem','자동','automation'], sol:'sol_ai', label:isKo?'AI 보안':'AI Security' }, ]; const detectSol = (text) => { const lower = text.toLowerCase(); for (const k of KEYWORD_MAP) { if (k.keys.some(kw => lower.includes(kw))) return k; } return null; }; React.useEffect(() => { if (bottomRef.current) { const el = bottomRef.current.parentNode; el.scrollTop = el.scrollHeight; } }, [msgs]); // 담당자 연결 요청 — 세션 중계가 아니라 1회성 알림(메일)으로 접수한다. const requestAgent = async () => { if (requested || connecting) return; setConnecting(true); setMsgs(x => [...x, { role:'bot', text:L.connecting }]); try { let ep = (window.SITE_CONFIG && window.SITE_CONFIG.CHAT_ENDPOINT) || '/api/chat-relay.php'; if (location.protocol === 'http:' && location.hostname !== 'localhost') ep = 'https://' + location.host + ep; const r = await fetch(ep, { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({ type: isKo ? '채팅 상담 요청' : 'Live chat request', product: (() => { const d = detectSol(msgs.filter(x=>x.role==='user').map(x=>x.text).join(' ')); return d ? d.label : '-'; })(), channel: isKo ? '홈페이지 채팅' : 'Website chat', inHours: openHours, history: msgs.filter(m=>m.role==='user').slice(-3).map(m=>({ role:'user', text:m.text })), }), }).then(res=>res.ok?res.json():null).catch(()=>null); if (!r || !r.ok || !r.ticket) { // 전달 실패 — 접수번호를 만들어내지 않고 대체 연락 경로를 안내한다. setMsgs(x => [...x.slice(0,-1), { role:'bot', text:L.fail, system:true }]); setHandoff(true); setConnecting(false); return; } setRequested(true); setMsgs(x => [...x.slice(0,-1), { role:'bot', text:L.connected(r.ticket), system:true }]); } catch { setMsgs(x => [...x.slice(0,-1), { role:'bot', text:L.fail, system:true }]); setHandoff(true); } setConnecting(false); }; const send = async (text) => { const userMsg = (text || input).trim(); if (!userMsg) return; setInput(''); setMsgs(m => [...m, { role:'user', text: userMsg }]); // 사람 상담 의도 감지 const HUMAN = ['사람','상담원','담당자','전화','연결','견적 상담','카톡','카카오','통화','specialist','human','agent','call me']; if (HUMAN.some(k => userMsg.toLowerCase().includes(k))) { setHandoff(true); } const detected = detectSol(userMsg); setMsgs(m => [...m, { role:'bot', text:'...' }]); // 안내형 응답 — 서버 없이도 동작한다. // window.claude 가 있는 환경(프리뷰)에서는 AI 응답을 우선 사용하고, // 없는 실제 홈페이지에서는 키워드 기반 안내로 응답한다. const guided = () => { const lower = userMsg.toLowerCase(); const has = (...k) => k.some(x => lower.includes(x)); if (has('견적','가격','비용','price','quote','cost')) return isKo ? '견적은 사용자 수·서버 대수 등 규모와 도입 방식(클라우드/구축형)에 따라 산정됩니다.\n제품 페이지의 [견적 요청]에서 구성을 입력해 주시면 담당자가 검토해 회신드립니다.' : 'Pricing depends on scale (users, servers) and deployment model. Use “Request a quote” on the product page and our team will review it.'; if (has('데모','시연','poc','demo','테스트','trial')) return isKo ? '데모와 기술 검증(PoC)을 지원합니다. 환경과 검증하고 싶은 항목을 알려주시면 일정을 조정해 드립니다.' : 'We support demos and PoCs. Tell us your environment and what you want to validate, and we will arrange a schedule.'; if (has('조달','나라장터','g2b','공공','입찰')) return isKo ? '주요 솔루션은 나라장터 디지털서비스몰에 등재되어 있어 공공기관에서 조달 구매가 가능합니다.\n필요한 품목과 수량을 알려주시면 식별번호와 함께 안내드립니다.' : 'Our main solutions are listed on the public procurement mall. Tell us the items and quantities you need.'; if (has('isms','인증','컴플라이언스','개인정보보호법','감사','compliance','audit')) return isKo ? 'ISMS-P·개인정보보호법·전자금융감독규정 대응을 지원합니다. 어떤 항목을 준비 중인지 알려주시면 필요한 구성을 제안드립니다.' : 'We support ISMS-P, privacy law and financial regulation readiness. Tell us which requirements you are preparing for.'; const d = detectSol(userMsg); if (d) return isKo ? `${d.label} 영역으로 안내드릴 수 있습니다. 아래 버튼에서 솔루션 구성을 확인하시고, 환경(사용자 수·현재 사용 제품)을 알려주시면 더 정확히 제안드립니다.` : `This falls under ${d.label}. Check the solutions below, and tell us your environment for a more precise recommendation.`; return isKo ? '문의 내용을 담당자에게 전달해 드릴 수 있습니다.\n아래에서 상담 유형을 선택하시거나, 사용 중인 환경과 고민하시는 과제를 적어주시면 맞는 솔루션을 안내드리겠습니다.' : 'We can pass this to a specialist. Pick a topic below, or describe your environment and the problem you are trying to solve.'; }; const attach = (text) => { const botMsg = { role:'bot', text }; if (detected) botMsg.link = { label: `${detected.label} ${isKo?'솔루션 보기':'Solutions'}`, page: detected.sol }; setMsgs(m=>[...m.slice(0,-1), botMsg]); }; if (window.claude && typeof window.claude.complete === 'function') { try { const systemPrompt = `You are a security consultant chatbot for 3ES Soft (삼에스소프트), a Korean B2B IT security company offering email, system, network, endpoint, data, privacy and AI security solutions. Be concise and professional, answer in the user's language, never invent prices or customer references, and end by offering to connect them with a specialist.`; const reply = await window.claude.complete({ messages:[{role:'user', content:userMsg}], system: systemPrompt }); attach(reply); return; } catch { /* 아래 안내형으로 대체 */ } } attach(guided()); }; // 카카오톡 전환 · 문의 남기기 시 내부 알림 (개인정보 없이 최소 정보만) const notifyHandoff = async (kind) => { const asked = msgs.filter(m=>m.role==='user').slice(-3).map(m=>m.text).join(' / '); try { await window.submitLead({ type: kind || '카카오톡 상담 전환', product: (detectSol(asked)||{}).label || '-', company: '(AI 상담)', name: '(미입력)', email: 'chat@3es.co.kr', message: (isKo?'AI 상담 중 사람 상담 전환 요청':'Handoff requested from AI chat') + (asked ? '\n최근 질문: ' + asked : ''), extra: { channel: kind || 'kakao', businessHours: openHours }, }); } catch {} }; const quickReplies = isKo ? ['우리 회사에 맞는 솔루션 찾기', '견적 · PoC 문의', '보안 문제 상담'] : ['Find the right solution', 'Quote / PoC inquiry', 'Security problem']; const talkLabel = isKo ? '담당자와 상담하기' : 'Talk to a specialist'; return ( <> {open && (

{isKo?'삼에스소프트 AI 보안 상담':'AI Security Chat'}

{openHours ? (isKo?'상담 가능':'Available now') : (isKo?'상담 접수 가능':'Leave a message')}
{msgs.map((m,i)=>(
{m.agentName && 🎧 {m.agentName}}
{m.text}
{m.suggestions && (
{quickReplies.map((q,qi)=>( ))}
)} {m.link && ( )}
))} {!requested && handoff && (
{isKo?'보안 전문가에게 연결해드릴까요?':'Connect with a specialist?'}
{isKo?'지금까지 문의하신 내용을 기준으로 상담을 이어갈 수 있습니다.':'We can continue from what you have asked so far.'}
{!openHours &&
{isKo?'지금은 업무시간이 아닙니다. 남겨주시면 다음 영업일에 확인 후 연락드립니다.':'Outside business hours — we will reply on the next business day.'}
} {kakaoUrl ? ( {isKo?'카카오톡으로 상담하기':'Chat on KakaoTalk'} ) : (
KAKAO_CHANNEL_URL {isKo?'설정 필요':'not configured'}
)}
)}
{!requested && !handoff && ( )}
setInput(e.target.value)} onKeyDown={e=>e.key==='Enter'&&send()}/>
)} ); }; // ─── Floating CTA Bar ──────────────────────────────────────────────────────── // 원칙: 고정 바는 한 가지 행동만 묻는다. // - 히어로를 지난 뒤에만 나타남 // - 페이지 하단 CTA 섹션이 화면에 보이면 숨김 (그 순간에는 완전한 중복) const FloatingCTA = ({ t, lang, setPage, openQuote, openDemo, solTitle, sol }) => { const [visible, setVisible] = React.useState(false); const isKo = lang === 'ko'; React.useEffect(() => { let ctaInView = false; const update = () => setVisible(window.scrollY > 400 && !ctaInView); // 하단 CTA 섹션(문의 유도 블록)을 찾아 화면에 들어오면 바를 숨긴다 const targets = () => [...document.querySelectorAll('[data-screen-label="Contact"], .ab-cta, [data-cta-block]')]; let io; if ('IntersectionObserver' in window) { io = new IntersectionObserver(entries => { ctaInView = entries.some(e => e.isIntersecting); update(); }, { rootMargin: '0px 0px -10% 0px' }); targets().forEach(el => io.observe(el)); } window.addEventListener('scroll', update, { passive: true }); update(); return () => { window.removeEventListener('scroll', update); if (io) io.disconnect(); }; }, [solTitle]); return (
); }; Object.assign(window, { DemoModal, QuoteModal, ChatBot, FloatingCTA });