PageSourceSearch

https://bluecvs.com/assets/js/web2-experience.js?v=1788850974

js bluecvs.com collected 2026-10-02 00:15:40 UTC 15,762 bytes, 247 lines download raw bytes

1(() => {
2    'use strict';
3    // Runtime copy comes from the page's JSON block; the Korean literals stay as the fallback.
4    const BC_I18N = (() => { try { return JSON.parse((document.getElementById('w2-i18n') || document.getElementById('hp-i18n')).textContent); } catch { return {}; } })();
5    const bcT = (key, fallback) => (typeof BC_I18N[key] === 'string' ? BC_I18N[key] : fallback);
6    const root = document.querySelector('.web2-page');
7    if (!root) return;
8    const reduced = matchMedia('(prefers-reduced-motion: reduce)');
9    const clamp = (value, min = 0, max = 1) => Math.max(min, Math.min(max, value));
10
11    const sculpture = window.BlueCanvasSculpture;
12    document.querySelectorAll('.w2-sculpture,.w2-mini-sculpture').forEach(host=>{
13        try { sculpture(host); } catch { /* Static artwork remains if GPU access is unavailable. */ }
14    });
15
16    // One canvas travels from its original hero slot to the discovery system.
17    // Its layout size stays constant during scroll: only the composited wrapper moves.
18    function discoveryJourney() {
19        const host=root.querySelector('.w2-sculpture');
20        const canvas=host.querySelector('canvas');
21        const origin=host.querySelector('.w2-sculpture-stage');
22        const art=root.querySelector('.w2-search-art');
23        const field=art.querySelector('.w2-discovery-field');
24        const rows=[...root.querySelectorAll('.w2-search-rows>div')];
25        const contact=root.querySelector('.w2-cta');
26        const words=['Be found.','Be the answer.','Be understood.'];
27        const captions=['SEARCH ENGINE<br>OPTIMIZATION','ANSWER ENGINE<br>OPTIMIZATION','GENERATIVE ENGINE<br>OPTIMIZATION'];
28        const sections=[...root.querySelectorAll('main>section')];
29        const float=document.createElement('div');
30        float.className='w2-journey';
31        const badge=document.createElement('div');
32        badge.className='w2-journey-badge';
33        badge.innerHTML='<span aria-hidden="true">01 / 03</span><button type="button" aria-label="'+bcT('rotatePause','자동 회전 멈추기').replace(/"/g,'&quot;')+'" aria-pressed="false">Ⅱ</button>';
34        const stop=badge.querySelector('button');
35        const originalPause=host.querySelector('.w2-sculpture-pause');
36        const searchPause=stop.cloneNode(true);
37        searchPause.className='w2-discovery-pause';
38        art.removeAttribute('aria-hidden');
39        [...art.children].forEach(child=>child.setAttribute('aria-hidden','true'));
40        art.append(searchPause);
41        root.append(float,badge);
42        let attached=false,queued=0,active=-1,manual=null,lastPhase=-1;
43        let width=1,height=1,originX=0,originY=0,fieldX=0,fieldY=0,fieldSize=1,end=0,searchY=0,searchEnd=0;
44        let anchors=[];
45        const smooth=value=>{const t=clamp(value);return t*t*(3-2*t);};
46        const mix=(a,b,t)=>a+(b-a)*t;
47        rows.forEach((row,i)=>{
48            const label=row.querySelector('b');
49            const button=document.createElement('button');
50            button.type='button';button.className='w2-discovery-select';
51            button.textContent=label.textContent;
52            button.setAttribute('aria-label',label.textContent+' '+bcT('visualSuffix','비주얼 보기'));
53            button.setAttribute('aria-pressed','false');
54            label.replaceWith(button);
55            button.addEventListener('click',()=>{manual=i;select(i);schedule();});
56            row.addEventListener('pointerenter',event=>{if(event.pointerType==='mouse'){manual=i;select(i);schedule();}});
57        });
58        function select(index) {
59            if(active===index)return;
60            active=index;art.dataset.discovery=String(index);
61            rows.forEach((row,i)=>{
62                row.classList.toggle('is-active',i===index);
63                row.querySelector('button').setAttribute('aria-pressed',String(i===index));
64            });
65            art.querySelector('.w2-discovery-count').textContent='0'+(index+1)+' / 03';
66            art.querySelector('.w2-discovery-word').textContent=words[index];
67            art.querySelector('.w2-discovery-caption').innerHTML=captions[index];
68        }
69        function measure() {
70            const a=origin.getBoundingClientRect(),b=field.getBoundingClientRect();
71            width=a.width;height=a.height;originX=a.left;originY=a.top+scrollY;
72            fieldX=b.left+b.width/2;fieldY=b.top+scrollY+b.height/2;fieldSize=b.width;
73            const search=art.parentElement.getBoundingClientRect();
74            searchY=search.top+scrollY;searchEnd=search.bottom+scrollY;
75            end=contact.getBoundingClientRect().top+scrollY;
76            anchors=sections.map(section=>section.getBoundingClientRect().top+scrollY);
77            float.style.width=width+'px';float.style.height=height+'px';
78            schedule();
79        }
80        function syncPause() {
81            const paused=originalPause.getAttribute('aria-pressed')==='true';
82            [stop,searchPause].forEach(button=>{
83                button.setAttribute('aria-pressed',String(paused));button.textContent=paused?'▷':'Ⅱ';
84                button.setAttribute('aria-label',paused?bcT('rotatePlay','자동 회전 시작하기'):bcT('rotatePause','자동 회전 멈추기'));
85            });
86            art.classList.toggle('is-paused',paused);schedule();
87        }
88        stop.addEventListener('click',()=>originalPause.click());
89        searchPause.addEventListener('click',()=>originalPause.click());
90        new MutationObserver(syncPause).observe(originalPause,{attributes:true,attributeFilter:['aria-pressed']});
91        function render() {
92            queued=0;
93            const enabled=!reduced.matches&&host.classList.contains('is-ready');
94            if(enabled&&!attached){float.append(canvas);attached=true;}
95            if(!enabled&&attached){origin.prepend(canvas);attached=false;}
96            float.hidden=!enabled;
97            const y=scrollY,vh=innerHeight,mobile=innerWidth<=600;
98            const fieldTop=fieldY-y-fieldSize/2;
99            const inView=fieldTop<vh&&fieldTop+fieldSize>84;
100            art.classList.toggle('is-inview',inView&&!document.hidden&&enabled);
101            searchPause.hidden=!enabled;
102            const searchProgress=clamp((vh*.78-fieldTop)/Math.max(1,fieldSize+vh*.25));
103            art.style.setProperty('--discovery-turn',(searchProgress*110).toFixed(2)+'deg');
104            if(searchY-y>vh||searchEnd-y<84)manual=null;
105            select(manual===null?Math.min(2,Math.floor(searchProgress*3)):manual);
106            if(!enabled){
107                badge.hidden=true;art.classList.remove('has-journey');
108                host.dispatchEvent(new CustomEvent('w2-journey',{detail:{active:true,turn:0,material:0}}));
109                return;
110            }
111            const departure=smooth((y-Math.max(0,originY-180))/Math.max(220,height*.85));
112            const smallHeight=mobile?66:106;
113            const smallScale=smallHeight/height;
114            const edgeX=innerWidth-width*smallScale/2-(mobile?0:3);
115            const edgeY=mobile?135:Math.min(vh*.24,215);
116            let cx=mix(originX+width/2,edgeX,departure);
117            let cy=mix(originY-y+height/2,edgeY,departure);
118            let scale=mix(1,smallScale,departure);
119            // Dock while the field is safely below the header; leave before it scrolls away.
120            const dock=smooth((vh*.94-fieldTop)/Math.max(120,vh*.23)) * smooth((fieldTop+fieldSize*.62-105)/Math.max(100,fieldSize*.28));
121            cx=mix(cx,fieldX,dock);cy=mix(cy,fieldY-y,dock);
122            scale=mix(scale,fieldSize*.53/height,dock);
123            const fade=1-smooth((y+vh*.65-end)/(vh*.35));
124            float.style.transform='translate3d('+(cx-width*scale/2).toFixed(2)+'px,'+(cy-height*scale/2).toFixed(2)+'px,0) scale('+scale.toFixed(5)+')';
125            float.style.opacity=String(fade);
126            float.classList.toggle('is-travelling',departure>.01);
127            canvas.tabIndex=departure>.01?-1:0;
128            if(departure>.01)canvas.setAttribute('aria-hidden','true');else canvas.removeAttribute('aria-hidden');
129            badge.hidden=departure<.98||dock>.1||fade<.1;
130            badge.style.left=(edgeX-(mobile?30:41))+'px';
131            badge.style.top=(edgeY+smallHeight/2+3)+'px';
132            art.classList.toggle('has-journey',dock>.8);
133            let section=0;anchors.forEach((top,i)=>{if(y+vh*.45>=top)section=i;});
134            const phase=dock>.45?100+active:departure<.05?-1:section;
135            const state={active:fade>.01,turn:y*.00055,material:dock*(active/2)};
136            if(phase!==lastPhase){
137                if(phase!==-1)state.shape=dock>.45?active:section%3;
138                lastPhase=phase;
139            }
140            badge.querySelector('span').textContent=String(section+1).padStart(2,'0')+' / '+String(sections.length).padStart(2,'0');
141            host.dispatchEvent(new CustomEvent('w2-journey',{detail:state}));
142        }
143        function schedule(){if(!queued)queued=requestAnimationFrame(render);}
144        addEventListener('scroll',schedule,{passive:true});
145        addEventListener('resize',measure,{passive:true});
146        addEventListener('pageshow',measure);
147        document.addEventListener('visibilitychange',schedule);
148        reduced.addEventListener('change',()=>{lastPhase=-1;measure();});
149        new MutationObserver(schedule).observe(host,{attributes:true,attributeFilter:['class']});
150        if('ResizeObserver'in window){const observer=new ResizeObserver(measure);observer.observe(root.querySelector('main'));observer.observe(origin);}
151        if(document.fonts)document.fonts.ready.then(measure);
152        select(0);measure();syncPause();
153    }
154    discoveryJourney();
155
156    if ('IntersectionObserver' in window) {
157        const inView = new Set();
158        const immersive = new IntersectionObserver(entries => {
159            entries.forEach(entry => { if(entry.isIntersecting)inView.add(entry.target);else inView.delete(entry.target); });
160            root.classList.toggle('w2-in-experiment',inView.size>0);
161        });
162        document.querySelectorAll('.w2-hero,.w2-showcase,.w2-depth,.w2-playground').forEach(el=>immersive.observe(el));
163    }
164
165    const depth=document.querySelector('.w2-depth');
166    const depthStage=depth.querySelector('.w2-depth-sticky');
167    const captions=[...depth.querySelectorAll('[data-depth-copy]')];
168    const count=depth.querySelector('.w2-depth-count');
169    let scrollFrame=0;
170    function updateDepth() {
171        scrollFrame=0;
172        const bounds=depth.getBoundingClientRect();
173        if(bounds.top>innerHeight||bounds.bottom<0)return;
174        const available=depth.offsetHeight-depthStage.offsetHeight;
175        const p=reduced.matches?.5:clamp((84-bounds.top)/Math.max(1,available));
176        const open=reduced.matches?0:Math.pow(Math.sin(Math.PI*clamp(p/.88)),.85);
177        const detail=reduced.matches?0:clamp((p-.08)/.16)*(1-clamp((p-.64)/.2));
178        depthStage.style.setProperty('--depth-detail',detail.toFixed(4));
179        depthStage.style.setProperty('--depth-resolved',reduced.matches?'1':clamp((p-.84)/.1).toFixed(4));
180        depthStage.style.setProperty('--depth-flow',(p*-420).toFixed(1));
181        depthStage.style.setProperty('--depth-open',open.toFixed(4));depthStage.style.setProperty('--depth-progress',p.toFixed(4));
182        const active=p<.22?0:p<.76?1:2;
183        captions.forEach((item,i)=>{item.classList.toggle('is-active',i===active);item.setAttribute('aria-hidden',String(i!==active));});
184        count.textContent='0'+(active+1)+' — 03';
185    }
186    function onScroll(){if(!scrollFrame)scrollFrame=requestAnimationFrame(updateDepth);}
187    addEventListener('scroll',onScroll,{passive:true});addEventListener('resize',onScroll,{passive:true});reduced.addEventListener('change',onScroll);updateDepth();
188
189    const range=document.querySelector('#w2-width');
190    const output=document.querySelector('.w2-width-control output');
191    const liveStage=document.querySelector('.w2-live-stage');
192    const frame=document.querySelector('.w2-live-frame');
193    const mini=document.querySelector('.w2-mini-site');
194    const devices=[...document.querySelectorAll('[data-w2-device]')];
195    const theme=document.querySelector('.w2-theme-switch');
196    root.classList.add('w2-play-ready');
197    function setWidth() {
198        const width=Math.round(320+780*Number(range.value)/100);
199        const zoom=Math.min(1,liveStage.clientWidth/width);
200        frame.style.width=width+'px';frame.style.transform='translateX(-50%) scale('+zoom+')';
201        liveStage.style.height=Math.ceil(frame.offsetHeight*zoom+64)+'px';
202        output.value=width+'px';range.setAttribute('aria-valuetext',width+bcT('pixels','픽셀'));
203        devices.forEach(button=>button.setAttribute('aria-pressed',String(Number(button.dataset.w2Device)===Number(range.value))));
204        mini.querySelector('.w2-mini-sculpture').dispatchEvent(new Event('w2-preview-resize'));
205    }
206    // Brief control cues on first arrival. The preview remains under user control.
207    const playground=document.querySelector('.w2-playground');
208    const controls=playground.querySelector('.w2-play-controls');
209    let cueDone=false,cueTimer=0;
210    function clearCue() {
211        clearTimeout(cueTimer);cueTimer=0;
212        controls.classList.remove('is-cueing');
213    }
214    function dismissCue() { cueDone=true;clearCue(); }
215    controls.addEventListener('pointerdown',dismissCue,{capture:true});
216    controls.addEventListener('focusin',dismissCue);
217    controls.addEventListener('input',dismissCue,{capture:true});
218    mini.addEventListener('pointerdown',dismissCue,{capture:true});
219    if('IntersectionObserver'in window)new IntersectionObserver(entries=>{
220        if(entries[0].isIntersecting&&!cueDone&&!document.hidden) {
221            cueDone=true;controls.classList.add('is-cueing');
222            cueTimer=setTimeout(clearCue,3200);
223        } else if(!entries[0].isIntersecting)clearCue();
224    },{threshold:.9,rootMargin:'-100px 0px -12% 0px'}).observe(controls);
225    reduced.addEventListener('change',clearCue);
226    document.addEventListener('visibilitychange',()=>{if(document.hidden)clearCue();});
227    range.disabled=false;theme.disabled=false;
228    devices.forEach(button=>{button.disabled=false;button.addEventListener('click',()=>{range.value=button.dataset.w2Device;setWidth();});});
229    range.addEventListener('input',setWidth);
230    theme.addEventListener('click',()=>{
231        const dark=mini.classList.toggle('is-dark');theme.setAttribute('aria-pressed',String(dark));theme.innerHTML='<span aria-hidden="true">◐</span> '+(dark?bcT('lightTheme','라이트 테마'):bcT('darkTheme','다크 테마'));
232        mini.querySelector('.w2-mini-sculpture').dispatchEvent(new Event('w2-material-change'));
233    });
234    if('ResizeObserver'in window){const resize=new ResizeObserver(setWidth);resize.observe(liveStage);resize.observe(frame);}else addEventListener('resize',setWidth,{passive:true});
235    setWidth();
236
237    const fine=matchMedia('(hover: hover) and (pointer: fine)');
238    document.querySelectorAll('.w2-work-image').forEach(card=>{
239        let pending=0,point=null;
240        card.addEventListener('pointermove',event=>{
241            if(!fine.matches||reduced.matches)return;
242            point={x:event.clientX,y:event.clientY};
243            if(pending)return;
244            pending=requestAnimationFrame(()=>{pending=0;const rect=card.getBoundingClientRect();card.style.setProperty('--shine-x',point.x-rect.left+'px');card.style.setProperty('--shine-y',point.y-rect.top+'px');});
245        },{passive:true});
246    });
247})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.