/* ---------- header ---------- */ const header=document.getElementById('header'); addEventListener('scroll',()=>header.classList.toggle('on',scrollY>10),{passive:true}); /* ---------- marquee ---------- */ ['marquee','marquee2'].forEach(id=>{ const mq=document.getElementById(id); if(mq)mq.innerHTML+=mq.innerHTML+mq.innerHTML; }); /* ---------- reveal ---------- */ const io=new IntersectionObserver(es=>{ es.forEach(e=>{if(e.isIntersecting){e.target.classList.add('show');io.unobserve(e.target);}}); },{threshold:.12}); document.querySelectorAll('.reveal').forEach(el=>io.observe(el)); /* ---------- count-up ---------- */ const fmt=n=>n.toLocaleString('ko-KR'); const cio=new IntersectionObserver(es=>{ es.forEach(e=>{ const el=e.target; if(!e.isIntersecting){el.dataset.done='';return;} if(el.dataset.done==='1')return; el.dataset.done='1'; const target=+el.dataset.count; const dur=2200,t0=performance.now(); const tick=t=>{ const p=Math.min((t-t0)/dur,1),ease=1-Math.pow(1-p,3); el.textContent=fmt(Math.round(target*ease)); if(p<1)requestAnimationFrame(tick); }; requestAnimationFrame(tick); }); },{threshold:.25}); document.querySelectorAll('[data-count]').forEach(el=>cio.observe(el)); /* ---------- rolling shots ---------- */ document.querySelectorAll('.rot').forEach(rot=>{ const items=[...rot.children]; if(items.length<2)return; let i=0; setInterval(()=>{ items[i].classList.remove('on'); i=(i+1)%items.length; items[i].classList.add('on'); },3600); }); /* ---------- giant parallax ---------- */ const giants=[...document.querySelectorAll('[data-parallax]')]; let ticking=false; function parallax(){ const vh=innerHeight; giants.forEach(el=>{ const r=el.getBoundingClientRect(); if(r.bottom<-vh||r.top>vh*2)return; const center=r.top+r.height/2-vh/2; const f=+el.dataset.parallax; el.style.transform=(el.closest('.sol-giant')?`translateX(calc(-50% + ${(-center*f).toFixed(1)}px))`:`translateX(${(-center*f).toFixed(1)}px)`); }); ticking=false; } addEventListener('scroll',()=>{if(!ticking){requestAnimationFrame(parallax);ticking=true}},{passive:true}); parallax(); /* ---------- halftone ---------- */ function buildHalftone(el){ const size=+el.dataset.size||420; const dotColor=el.dataset.dot||'#111318'; const step=13,R=size/2; const ns='http://www.w3.org/2000/svg'; const svg=document.createElementNS(ns,'svg'); svg.setAttribute('viewBox',`0 0 ${size} ${size}`); svg.setAttribute('width','100%');svg.setAttribute('height','100%'); svg.classList.add('ht'); for(let y=step/2;y1)continue; const base=(1-Math.pow(d,1.6)); const r=Math.max(0,base*step*.42+(Math.random()-.5)*.5); if(r<.35)continue; const c=document.createElementNS(ns,'circle'); c.setAttribute('cx',x);c.setAttribute('cy',y);c.setAttribute('r',r.toFixed(2)); c.setAttribute('fill',dotColor); c.setAttribute('opacity',(0.25+base*.75).toFixed(2)); svg.appendChild(c); } } el.style.aspectRatio='1'; el.appendChild(svg); } document.querySelectorAll('[data-halftone]').forEach(buildHalftone); /* ---------- wave line ---------- */ (function(){ const el=document.getElementById('waveLine'); if(!el)return; const text=el.textContent; el.textContent=''; let idx=0; for(const ch of text){ if(ch===' '){el.appendChild(document.createTextNode(' '));continue;} const s=document.createElement('span'); s.className='wv';s.style.setProperty('--i',idx++); s.textContent=ch; el.appendChild(s); } })(); /* ---------- AI briefing: typewriter with risk highlights ---------- */ (function(){ const el=document.getElementById('aiBrief'); if(!el)return; const SEG=[ {t:'7월 15일 브리핑입니다.',c:'b'},{t:'\n'}, {t:'총 자산 1,782건 · 전수조사 진행률 '},{t:'82%',c:'hl'},{t:'.'},{t:'\n'}, {t:'⚠️ 에이전트 임의 삭제 시도 1건',c:'risk'},{t:' — 개발2팀 PC, 확인이 필요합니다.'},{t:'\n'}, {t:'⚠️ 고사양 장비 2대가 창고에서 잠자는 중',c:'risk'},{t:' — 저사양 장비로 업무 중인 팀에 재배치를 제안합니다.'},{t:'\n'}, {t:'만료 임박 라이선스 '},{t:'2건',c:'hl'},{t:', 지난달 대비 미사용 자산 '},{t:'9건 감소',c:'hl'},{t:'.'} ]; let started=false; function type(){ if(started)return;started=true; let si=0,ci=0,cur=null; const cursor=document.createElement('span'); cursor.className='ai-cursor';cursor.textContent='▌'; el.appendChild(cursor); function step(){ if(si>=SEG.length){ // 잠시 유지 후 다시 타이핑 (항상 살아있는 브리핑) setTimeout(()=>{ el.textContent='';el.appendChild(cursor); si=0;ci=0;cur=null;setTimeout(step,600); },5200); return; } const s=SEG[si]; if(s.t==='\n'){el.insertBefore(document.createElement('br'),cursor);si++;ci=0;setTimeout(step,180);return} if(!cur){ cur=s.c==='b'?document.createElement('b'):document.createElement('span'); if(s.c&&s.c!=='b')cur.className=s.c; el.insertBefore(cur,cursor); } cur.textContent=s.t.slice(0,++ci); if(ci>=s.t.length){si++;ci=0;cur=null;setTimeout(step,s.c==='risk'?260:120);return} setTimeout(step,s.c==='risk'?34:20); } step(); } new IntersectionObserver((es,o)=>{ es.forEach(e=>{if(e.isIntersecting){type();o.disconnect();}}); },{threshold:.4}).observe(el); })(); /* ---------- progress fills grow on view ---------- */ (function(){ const fills=[...document.querySelectorAll('.dbar .fl,.gaugeline .g-fill,.pinv .pg .fl')]; fills.forEach(el=>{ el.dataset.tw=el.style.width; el.style.width='0'; el.style.transition='width 1.5s cubic-bezier(.22,1,.36,1) .2s'; }); const fio=new IntersectionObserver(es=>{ es.forEach(e=>{ if(!e.isIntersecting)return; e.target.style.width=e.target.dataset.tw; fio.unobserve(e.target); }); },{threshold:.4}); fills.forEach(el=>fio.observe(el)); })(); /* ---------- sticky CTA (show after hero, hide near CTA/footer) ---------- */ (function(){ const bar=document.getElementById('stickyCta'); const cta=document.getElementById('contact'); addEventListener('scroll',()=>{ const past=scrollY>innerHeight*.9; const nearEnd=cta.getBoundingClientRect().top{ // v in 만원 if(v>=10000){const e=v/10000;return (e%1?e.toFixed(1):e)+'억 원'} return v.toLocaleString('ko-KR')+'만 원'; }; function calc(){ const a=+rA.value,e=+rE.value,s=+rS.value; document.getElementById('vAsset').textContent=a.toLocaleString('ko-KR'); document.getElementById('vEmp').textContent=e.toLocaleString('ko-KR'); document.getElementById('vSw').textContent=won(s); // 재물조사: 수기 약 4분/자산 vs 샘큐 30초/자산, 연 2회 const saveMin=(a*4-a*0.5)*2; const saveH=Math.round(saveMin/60); document.getElementById('oAudit').textContent='약 '+saveH.toLocaleString('ko-KR')+'시간'; // SW 절감: 15~30% document.getElementById('oSw').textContent=won(Math.round(s*.15))+' ~ '+won(Math.round(s*.3)); // 반복 업무: 신청·지급·실사 셀프화 → 담당자 업무 약 70% 감소 표기 document.getElementById('oWork').textContent='약 70% ↓'; } [rA,rE,rS].forEach(r=>r.addEventListener('input',calc)); calc(); })(); /* ---------- smooth anchors (JS only, no global smooth) ---------- */ document.querySelectorAll('a[href^="#"]').forEach(a=>{ a.addEventListener('click',e=>{ const id=a.getAttribute('href'); if(id.length<2)return; const t=document.querySelector(id); if(!t)return; e.preventDefault(); t.scrollIntoView({behavior:'smooth',block:'start'}); }); }); /* ---------- hero particles: scatter → converge sphere ---------- */ (function(){ const cv=document.getElementById('heroCanvas'); const ctx=cv.getContext('2d'); let W,H,DPR; function resize(){ W=cv.clientWidth;H=cv.clientHeight; DPR=Math.min(devicePixelRatio||1,1.25); const budget=1600000; if(W*H*DPR*DPR>budget)DPR=Math.sqrt(budget/(W*H)); cv.width=W*DPR;cv.height=H*DPR; ctx.setTransform(DPR,0,0,DPR,0,0); } resize();addEventListener('resize',resize); const N=440,pts=[]; const GA=Math.PI*(3-Math.sqrt(5)); const ICONS=['💻','🖥️','⚙️','🪑','📄','📐','⌨️','🖱️','🖨️','📱','🔑','💾']; // 실제 SW 브랜드 로고 파티클 (CAD·어도비·피그마·클로드·GPT·제미나이·한글…) const LOGOS=['adobe','autodesk','figma','claude','openai','googlegemini','han','slack','notion']; const LIMG=LOGOS.map(n=>{ if(n==='han')return null; const im=new Image();im.crossOrigin='anonymous'; im.src='https://cdn.simpleicons.org/'+n; return im; }); for(let i=0;i=0)links.push([i,best]); } let mx=0,my=0; addEventListener('mousemove',e=>{ mx=(e.clientX/innerWidth-.5)*2; my=(e.clientY/innerHeight-.5)*2; },{passive:true}); const easeIO=p=>p<.5?4*p*p*p:1-Math.pow(-2*p+2,3)/2; const START_DELAY=600,CONVERGE=2800; const AB_START=CONVERGE*1.25+700, AB_DUR=1000; // 흡수 시작·지속 (빠르게) const wl=document.getElementById('waveLine'); let textT=null,blueDone=false,rippleT=null,allDone=false; let emitT=null,dashOn=false; // 피날레: 점 분출 → 대시보드 조립 let t0=null; let rotY=0,rotX=.35; let lastT=0; function frame(t){ if(!running)return; if(allDone){ctx.clearRect(0,0,W,H);return} if(t-lastT<33){requestAnimationFrame(frame);return} lastT=t; if(t0===null)t0=t; /* ---- 피날레: 글자에서 점 분출 → 우측 대시보드 위치로 블록 쌓기 ---- */ if(emitT!==null){ ctx.clearRect(0,0,W,H); const dEl=document.getElementById('heroDash'); const TOTAL=4200; // 천천히, 계속 쌓이며 const em=Math.min(1,(t-emitT)/TOTAL); // 모래 수위: 아래→위로 드러나는 클립 if(dEl&&!dashOn){ dEl.style.clipPath=`inset(${((1-em)*100).toFixed(1)}% 0 0 0)`; } let alive=false; for(let i=0;i=1){ dashOn=true; if(dEl){dEl.style.clipPath='none';dEl.classList.add('on');heroDashFx();} } if(!alive&&dashOn){allDone=true;ctx.clearRect(0,0,W,H);return} requestAnimationFrame(frame);return; } rotY+=.0026; rotX+=((.35+my*.12+Math.sin(t*.0004)*.07)-rotX)*.04; ctx.clearRect(0,0,W,H); const R=Math.min(W*.26,H*.38); const cx=W*.7,cy=H*.5; const cosY=Math.cos(rotY+mx*.1),sinY=Math.sin(rotY+mx*.1); const cosX=Math.cos(rotX),sinX=Math.sin(rotX); const breath=1+Math.sin(t*.0006)*.02; const elapsed=t-t0-START_DELAY; /* 흡수 진행도: 구 → SAMQ AI 글자 */ const abG=easeIO(Math.max(0,Math.min(1,(elapsed-AB_START)/(AB_DUR+700)))); if(abG>0&&!textT&&wl){ const cvr=cv.getBoundingClientRect(),wlr=wl.getBoundingClientRect(); textT={x:wlr.left+wlr.width/2-cvr.left,y:wlr.top+wlr.height/2-cvr.top}; } if(abG>.55&&!blueDone&&wl){wl.classList.add('inCore');blueDone=true;rippleT=t;} const proj=new Array(N); for(let i=0;i0){ try{ctx.drawImage(im,px-s/2,py-s/2,s,s);}catch(e){} }else{ ctx.font=`800 ${(s*.9).toFixed(1)}px Pretendard,sans-serif`; ctx.textAlign='center';ctx.textBaseline='middle'; ctx.fillStyle='rgba(43,71,255,.8)'; ctx.fillText(nm[0].toUpperCase(),px,py); } ctx.globalAlpha=1; continue; } if(p.icon){ // tiny asset icons (노트북·모니터·장비·의자·문서·캐드…) const fs=(8+depth*10)*(0.75+g*.35)*(1-ab*.6); ctx.font=`${fs.toFixed(1)}px sans-serif`; ctx.textAlign='center';ctx.textBaseline='middle'; ctx.globalAlpha=Math.min(1,alpha*2.4); ctx.fillText(p.icon,px,py); ctx.globalAlpha=1; continue; } const r=((0.5+depth*1.9)*g+(0.5+tw*.8)*(1-g))*tw; ctx.fillStyle=p.accent ?`rgba(43,71,255,${(alpha*1.15).toFixed(3)})` :`rgba(17,19,24,${alpha.toFixed(3)})`; ctx.beginPath();ctx.arc(px,py,r,0,6.2832);ctx.fill(); } const gAll=easeIO(Math.max(0,Math.min(1,(elapsed-CONVERGE*.6)/CONVERGE))); const keep=1-abG; // 흡수될수록 구의 흔적은 사라짐 if(gAll>0&&keep>.02){ ctx.strokeStyle=`rgba(17,19,24,${(.05*gAll*keep).toFixed(3)})`; ctx.beginPath();ctx.ellipse(cx,cy,R*1.18,R*.42,-.4,0,6.2832);ctx.stroke(); const pulse=1+Math.sin(t*.0028)*.07; const gr=ctx.createRadialGradient(cx,cy,0,cx,cy,R*.62*pulse); gr.addColorStop(0,`rgba(77,125,255,${(.30*gAll*keep).toFixed(3)})`); gr.addColorStop(.55,`rgba(77,125,255,${(.10*gAll*keep).toFixed(3)})`); gr.addColorStop(1,'rgba(77,125,255,0)'); ctx.fillStyle=gr; ctx.beginPath();ctx.arc(cx,cy,R*.62*pulse,0,6.2832);ctx.fill(); } /* ---- 흡수 완료: SAMQ AI 글자에서 푸른 파동 1~2회 후 종료 ---- */ if(rippleT&&textT){ const rp=(t-rippleT)/1300; for(let k=0;k<2;k++){ const pr=rp-k*.55; if(pr>0&&pr<1){ const ra=40+pr*230; const al=(1-pr)*.34; ctx.strokeStyle=`rgba(43,71,255,${al.toFixed(3)})`; ctx.lineWidth=2*(1-pr)+.5; ctx.beginPath();ctx.arc(textT.x,textT.y,ra,0,6.2832);ctx.stroke(); } } ctx.lineWidth=1; if(rp>1.35&&abG>=1&&emitT===null){ // 분출 목표 좌표 = 대시보드 영역 격자 const dEl=document.getElementById('heroDash'); const cvr=cv.getBoundingClientRect(); let r={left:W*.56,top:H*.2,width:W*.36,height:H*.6}; if(dEl){ const dr=dEl.getBoundingClientRect(); r={left:dr.left-cvr.left,top:dr.top-cvr.top,width:dr.width,height:dr.height}; } for(let i=0;i{ es.forEach(e=>{ const was=running; running=e.isIntersecting; if(running&&!was)requestAnimationFrame(frame); }); }).observe(cv); if(matchMedia('(prefers-reduced-motion: reduce)').matches){running=false;t0=1;} requestAnimationFrame(frame); /* 대시보드 라이브 효과: 빈 값들이 채워짐 (카운트업·게이지·타이핑·제안 등장) */ function heroDashFx(){ const cnt=(id,target,dur)=>{ const e=document.getElementById(id);if(!e)return; const d=dur||1600,s=performance.now(); const tick=tt=>{ const p=Math.min((tt-s)/d,1),ez=1-Math.pow(1-p,3); e.textContent=Math.round(target*ez).toLocaleString('ko-KR'); if(p<1)requestAnimationFrame(tick); }; requestAnimationFrame(tick); }; const pct=(barId,pctId,target)=>{ const b=document.getElementById(barId);if(b)b.style.width=target+'%'; const e=document.getElementById(pctId);if(!e)return; const s=performance.now(); const tick=tt=>{const p=Math.min((tt-s)/1600,1);e.textContent=(target*(1-Math.pow(1-p,3))).toFixed(target<10?1:0)+'%';if(p<1)requestAnimationFrame(tick)}; requestAnimationFrame(tick); }; // 자산 현황 setTimeout(()=>{cnt('hdN1',782);cnt('hdN2',2,700);cnt('hdN3',782);cnt('hdN4',90);},350); setTimeout(()=>{pct('hdU','hdUp',64);},800); setTimeout(()=>{pct('hdG','hdGp',82);},1150); // 총자산 및 구매비용 setTimeout(()=>{cnt('hdM1',57726101,1900);cnt('hdM2',57076101,1900);cnt('hdM3',650000,1500);},600); setTimeout(()=>{ const b=document.getElementById('hdB');if(b)b.style.width='85.7%'; cnt('hdBp',86,1400); },1300); // AI 갱신 제안 — 하나씩 도착 setTimeout(()=>{const s=document.getElementById('hdS1');if(s)s.classList.add('in');},900); setTimeout(()=>{const s=document.getElementById('hdS2');if(s)s.classList.add('in');},1600); // AI 가이드 타자 효과 (반복) const ty=document.getElementById('hdType'); if(ty){ const TXT='오늘 만료 임박 2건이 있어요. 미확인 자산은 3층 창고에 집중 — 재고 실사를 이어서 진행할까요?'; let i=0; function tp(){ ty.textContent=TXT.slice(0,++i); if(i{i=0;ty.textContent='';setTimeout(tp,500);},4600);} } setTimeout(tp,700); } } /* reduced-motion: 애니메이션 없이 바로 파란 상태로 */ if(matchMedia('(prefers-reduced-motion: reduce)').matches&&wl){ wl.classList.add('inCore'); const dEl=document.getElementById('heroDash'); if(dEl){dEl.classList.add('on');heroDashFx();} } })();