PageSourceSearch

https://groobee.net/wp-content/themes/groobee/js/jt-strap.js?ver=1.0.3

js groobee.net collected 2026-09-24 12:28:55 UTC 44,299 bytes, 1,296 lines download raw bytes

1/*
2 * File       : js/jt-strap.js
3 * Author     : STUDIO-JT (KMS, NICO, JUN, SUMI)
4 * Guideline  : JTstyle.2.1
5 *
6 * SUMMARY:
7 * 1) JT Functions INIT
8 * 2) ON LOAD
9 * 3) ON RESIZE
10 * 4) JT Functions
11 * 5) Helpers
12 */
13
14
15
16(function($){
17
18    /* **************************************** *
19     * Global
20     * **************************************** */
21    let isAjaxLoading = false;
22
23    /* **************************************** *
24     * JT Functions INIT
25     * **************************************** */
26    // INPUT
27    JT.ui.add( selectric_init, true );
28    JT.ui.add( custom_input_file, true );
29    // JT.ui.add( custom_input_email, true );
30
31    // LAZYLOAD
32    JT.ui.add( lazyload_init, true );
33
34    // LOADMORE
35    JT.ui.add( loadmore, true );
36
37    // MODULES
38    JT.ui.add( jt_modules_search, true );
39
40    // TABS COMPONENT
41    JT.ui.add( jt_tabs, true );
42
43    // SHARE
44    JT.ui.add( share_popup_init, true );
45    JT.ui.add( share_clipboard, true );
46
47    // VIDEO
48    JT.ui.add( vimeo_play, true );
49    JT.ui.add( youtube_play, true );
50    JT.ui.add( autoplay_inview, true );
51    JT.ui.add( html_video_play, true );
52
53    // SINGLE
54    JT.ui.add( blocks_spacer, true );
55    JT.ui.add( blocks_list_counter, true );
56
57    JT.ui.add( jt_list_fullclick, true);
58
59    JT.ui.add( jt_modules_ajax, true );
60
61
62
63    /* **************************************** *
64     * ON LOAD
65     * **************************************** */
66    window.addEventListener('load', function(){
67
68        // add
69
70    });
71
72
73
74    /* **************************************** *
75     * ON RESIZE
76     * **************************************** */
77    // INITIALIZE RESIZE
78    function handleResize(){
79
80        // setTimeout to fix IOS animation on rotate issue
81        setTimeout(function(){
82
83            JT.ui.call( 'blocks_spacer' );
84
85        }, 100);
86
87    }
88
89    // Init resize on reisize
90    if( JT.browser('mobile') ) {
91        window.addEventListener('orientationchange', handleResize);
92    } else {
93        window.addEventListener('resize', handleResize);
94    }
95
96
97
98    /* **************************************** *
99     * JT Functions
100     * **************************************** */
101    /**
102     * select 커스텀 스타일을 설정합니다.
103     *
104     * @version 1.0.0
105     * @author STUDIO-JT (KMS)
106     * @see {@link http://selectric.js.org/|selectric API}
107     * @requires jquery.selectric.js
108     * @requires jt-strap.css
109     * @requires rwd-strap.css
110     *
111     * @example
112     * <div class="jt-selectric__wrap">
113     *     <select class="jt-selectric">
114     *         <option value="op1">OP1</option>
115     *         <option value="op2">OP2</option>
116     *         <option value="op3">OP3</option>
117     *     </select>
118     * </div>
119     */
120    function selectric_init() {
121
122        if( !JT.browser('mobile') ) {
123
124            document.querySelectorAll('.jt-selectric').forEach((select) => {
125
126                $(select).selectric({
127                    disableOnMobile: true,
128                    arrowButtonMarkup: '<b class="button"><i class="jt-icon"><svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M8.1,11.4L2.3,5.7l1.4-1.4l4.4,4.3l4.2-4.3l1.4,1.4L8.1,11.4z" fill="black"/></svg></i></b>',
129                    optionsItemBuilder: function(itemData) {
130                        return '<span>' + itemData.text + '</span>';
131                    },
132                    onInit: function(){
133                        document.querySelectorAll('input.selectric-input').forEach( item => item.setAttribute('readonly', true) );
134                    }
135                });
136
137            });
138
139        }
140
141    }
142
143
144
145    /**
146     * File타입 Input의 커스텀 기능을 설정합니다.
147     *
148     * @version 1.0.0
149     * @author STUDIO-JT (KMS)
150     * @requires jt-customfile.js
151     * @requires jt-strap.css
152     * @requires rwd-strap.css
153     *
154     * @example
155     * <input type="file" multiple />
156     */
157    function custom_input_file(){
158
159        document.querySelectorAll('input[type="file"]').forEach((input) => {
160
161            new JtCustomFile(input);
162
163        });
164
165    }
166
167
168
169    /**
170     * Email타입 Input의 자동완성 기능을 설정합니다.
171     *
172     * @version 1.0.0
173     * @author STUDIO-JT (KMS)
174     * @requires jt-autocomplete.js
175     * @requires jt-strap.css
176     * @requires rwd-strap.css
177     *
178     * @example
179     * <input id="email" type="email" placeholder="이메일을 입력하세요." />
180     */
181    /*
182    function custom_input_email(){
183
184        document.querySelectorAll('input[type="email"]').forEach((input) => {
185
186            new JtAutocomplete(input);
187
188        });
189
190    }
191    */
192
193
194
195    /**
196     * Image Lazyload
197     *
198     * @version 1.0.0
199     * @author STUDIO-JT (KMS)
200     * @requires jt-unveil.js
201     * @description masonry UI의 경우 jt-lazyload 컨테이너에 jt-lazyload--masonry class를 추가로 붙여서 사용하는 것을 권장합니다.
202     *
203     * @example
204     * <figure class="jt-lazyload">
205     * 	 <span class="jt-lazyload__color-preview"></span>
206     * 	 <img width="120" height="120" data-unveil="some_img_url.jpg" src="blank.gif" alt="" />
207     * 	 <noscript><img src="some_img_url.jpg" alt="" /></noscript>
208     * </figure>
209     */
210    function lazyload_init(){
211
212        // lazyload
213        $('[data-unveil]').unveil(300, function() {
214            const _this = this;
215
216            _this.addEventListener('load', function(){
217                if( _this.closest('.jt-lazyload') != null ) {
218                    _this.closest('.jt-lazyload').classList.add('jt-lazyload--loaded');
219                } else {
220                    _this.classList.add('jt-lazyload--loaded');
221                }
222            });
223        });
224
225    }
226
227
228
229    /**
230     * Ajax loadmore function
231     *
232     * @version 1.0.0
233     * @author STUDIO-JT (NICO)
234     */
235    function loadmore(){
236
237        if( !!!document.getElementById('jt-loadmore') ) return;
238
239        let isLoading = false;
240
241        document.querySelector('#jt-loadmore a').addEventListener('click', function(e){
242
243            e.preventDefault();
244
245            if( isLoading ) return;
246            isLoading = true;
247
248            const _this        = this;
249            const loadmoreBtn  = _this.parentNode;
250            const listSelector = _this.getAttribute('data-loadmore-list');
251            const list         = document.querySelector(listSelector);
252            const url          = _this.getAttribute('href');
253
254            loadmoreBtn.classList.add('jt-loadmore--loading');
255
256            fetch(url).then(( response ) => {
257                return response.text();
258            })
259            .then(( html ) => {
260
261                // DOM parser
262                const parser = new DOMParser();
263                const response = parser.parseFromString(html, 'text/html');
264
265                // Get data
266                let nextUrl = ( !!response.getElementById('jt-loadmore') ) ? response.querySelector('#jt-loadmore a').getAttribute('href') : undefined;
267                let moreItems = null;
268
269                if( response.getElementsByClassName('jt-isotope-list').length > 0 ) { // Masonry
270
271                    // isotope
272                    moreItems = response.querySelectorAll('.jt-isotope-list__item');
273
274                    // lazyload check
275                    moreItems.forEach((item) => {
276                        if( !!item.querySelector('.jt-lazyload--masonry') ){
277                            const figure     = item.querySelector('.jt-lazyload--masonry');
278                            const img        = figure.querySelector('img');
279                            const imgWidth   = img.getAttribute('width');
280                            const imgHeight  = img.getAttribute('height');
281                            const paddingTop = (imgHeight/imgWidth)*100;
282
283                            figure.style.paddingTop = paddingTop+'%';
284                            figure.classList.add('jt-lazyload--loaded');
285                            img.setAttribute('src', img.getAttribute('data-unveil'));
286                        }
287                    });
288
289                    JT.globals.isotope.insert(moreItems);
290                    imagesLoaded( document.querySelector('.jt-isotope-list'), function() {
291                        JT.globals.isotope.layout();
292                    });
293
294                } else if( response.getElementsByClassName('jt-accordion').length > 0 ) { // Accordion
295
296                    moreItems = response.querySelectorAll('.jt-accordion__item');
297
298                    gsap.set( moreItems, { autoAlpha: 0, scale: 0.9 } );
299                    moreItems.forEach( (item) => { list.appendChild( item ) });
300                    gsap.to( moreItems, { autoAlpha: 1, scale: 1, duration: .3, stagger: .1 } );
301
302                } else {
303
304                    // Nothing
305
306                }
307
308                // Update URL
309                /*
310                if ('history' in window && 'pushState' in history) {
311                    window.history.pushState(null, null, url);
312                }
313                */
314
315                // Refresh scrolltrigger offset
316                if( typeof ScrollTrigger != 'undefined' ) {
317                    ScrollTrigger.refresh();
318                }
319
320                // Remove loading class after some delay to avoid
321                setTimeout(function(){
322                    loadmoreBtn.classList.remove('jt-loadmore--loading');
323                },300);
324
325                if(nextUrl !== undefined){
326                    // Update url
327                    _this.setAttribute('href', nextUrl);
328
329                    // Update flag
330                    isLoading = false;
331                } else {
332                    _this.remove();
333                    return;
334                }
335
336            })
337            .catch(( error ) => {
338                console.error(error);
339            });
340
341        });
342
343    }
344
345
346
347    /**
348     * JT Modules 검색 기능지원
349     *
350     * @version 1.0.0
351     * @author STUDIO-JT (KMS)
352     */
353    function jt_modules_search(){
354
355        if( !document.getElementsByClassName('jt-search').length ) return;
356
357        const wrap     = document.querySelector('.jt-search__bundle');
358        const input    = document.querySelector('.jt-search__input');
359        const clearBtn = document.querySelector('.jt-search__reset');
360
361        if( input.value.length > 0 ) wrap.classList.add('jt-search--active');
362
363        // Input
364        input.addEventListener('keydown', function(){
365            wrap.classList.add('jt-search--active');
366        });
367        input.addEventListener('keyup', function(){
368            if( input.value == '' ){
369                wrap.classList.remove('jt-search--active');
370            } else {
371                wrap.classList.add('jt-search--active');
372            }
373        });
374        input.addEventListener('focusout', function(){
375            if( input.value == '' ){
376                wrap.classList.remove('jt-search--active');
377            } else {
378                wrap.classList.add('jt-search--active');
379            }
380        });
381
382        // Clear input
383        $('.jt-search__reset').on('click', function(e){
384
385            e.preventDefault();
386            e.stopPropagation();
387
388            const $input = $('.jt-search__input');
389            const $tmp = $( '<input />', { type: 'text' } );
390
391            $input.val('');
392            $input.closest( 'div' ).append( $tmp.wrap( '<div style="display:none;"></div>' ) );
393            $tmp.focus();
394            $input.focus();
395            $tmp.remove();
396
397            wrap.classList.remove('jt-search--active');
398
399        });
400
401    }
402
403
404
405    /**
406     * JT basic tabs component
407     *
408     * @version 1.0.1
409     * @author STUDIO-JT (NICO)
410     */
411    function jt_tabs(){
412
413        document.querySelectorAll('.jt-tabs').forEach((element) => {
414
415            const _this    = element;
416            const menu     = _this.querySelector('.jt-tabs__menu');
417            const menuList = menu.querySelectorAll(':scope > li');
418            const panel    = _this.querySelector('.jt-tabs__panels');
419            const itemList = panel.querySelectorAll(':scope > div');
420            const isHash   = ( _this.classList.contains('jt-tabs--hash') ) ? true : false;
421
422            // Hide tabs if not already hidden
423            itemList.forEach( ( item ) => item.style.display = 'none' );
424
425            // Init display the right tab
426            // TODO : DRY this stuff
427            if( isHash && location.hash != '#' ) {
428                if ( location.hash.length > 0 ) {
429                    const currentHash      = location.hash.replace(/#/,'');
430                    const currentItem      = document.getElementById(currentHash);
431                    const currentHashIndex = [].indexOf.call(itemList, currentItem) + 1;
432
433                    panel.querySelector(':scope > div:nth-child('+currentHashIndex+')').style.display = 'block';
434                    menu.querySelector(':scope > li:first-child').classList.remove('jt-tabs--active');
435                    menu.querySelector(':scope > li:nth-child('+currentHashIndex+')').classList.add('jt-tabs--active');
436                } else{
437                    panel.querySelector(':scope > div:first-child').style.display = 'block';
438                    menu.querySelector(':scope > li:first-child').classList.add('jt-tabs--active');
439                }
440            } else {
441                panel.querySelector(':scope > div:first-child').style.display = 'block';
442                menu.querySelector(':scope > li:first-child').classList.add('jt-tabs--active');
443            }
444
445            // Add click event
446            menuList.forEach((element) => {
447                const _that = element;
448
449                _that.addEventListener('click', function(e){
450                    e.preventDefault();
451                    e.stopPropagation();
452
453                    const link  = _that.querySelector('a');
454                    const hash  = link.getAttribute('href');
455                    const index = [].indexOf.call(menuList, _that) + 1;
456
457                    menuList.forEach( ( item ) => item.classList.remove('jt-tabs--active') );
458                    menu.querySelector(':scope > li:nth-child('+index+')').classList.add('jt-tabs--active');
459
460                    itemList.forEach( ( item ) => item.style.display = 'none' );
461                    panel.querySelector(':scope > div:nth-child('+index+')').style.display = 'block';
462
463                    // add hash
464                    if (isHash && 'history' in window && 'pushState' in history) {
465                        history.pushState(null, null, hash)
466                    }
467                });
468            });
469
470            // Listner hash change
471            // TODO DRY THIS CODE !!!
472            if (isHash && "onhashchange" in window) {
473                window.onhashchange = function locationHashChanged() {
474                    const _currentHash      = location.hash.replace(/#/,'');
475                    const _currentItem      = document.getElementById(_currentHash);
476                    const _currentHashIndex = [].indexOf.call(itemList, _currentItem) + 1;
477
478                    itemList.forEach( ( item ) => item.style.display = 'none' );
479                    panel.querySelector(':scope > div:nth-child('+_currentHashIndex+')').style.display = 'block';
480                    menuList.forEach( ( item ) => item.classList.remove('jt-tabs--active') );
481                    menu.querySelector(':scope > li:nth-child('+_currentHashIndex+')').classList.add('jt-tabs--active');
482                }
483            }
484
485        });
486
487    }
488
489
490
491    /**
492     * SNS 공유 팝업창을 생성합니다.
493     *
494     * @version 1.0.0
495     * @author STUDIO-JT (JUN)
496     */
497    function share_popup_init(){
498
499        let winPop = '';
500
501        // SNS POPUP
502        document.querySelectorAll('.jt-share__item').forEach((element) => {
503            element.addEventListener('click', function(e){
504
505                // return kakao share
506                if( element.classList.contains('jt-share--kakaotalk') || element.classList.contains('jt-share--url') ) return false;
507
508                e.preventDefault();
509
510                // OPTIONS
511                const options = {
512                    href        : element.getAttribute('href'), // 주소
513                    title       : 'socialShare',                // 타이틀
514                    width       : '600',                        // { number } 열리는 창의 가로 크기
515                    height      : '600',                        // { number } 열리는 창의 세로 크기
516                    top         : '0',                          // { number } 열리는 창의 좌표 위쪽
517                    left        : '0',                          // { number } 열리는 창의 좌표 왼쪽
518                    status      : 'no',                         // { yes | no | 1 | 0 } 상태 표시바 보이거나 숨기기
519                    fullscreen  : 'no',                         // { yes | no | 1 | 0 } 전체 창 (기본값은 no)
520                    channelmode : 'no',                         // { yes | no | 1 | 0 } 채널모드 F11 키 기능이랑 같음
521                    location    : 'no',                         // { yes | no | 1 | 0 } 주소창 (기본값은 yes)
522                    menubar     : 'no',                         // { yes | no | 1 | 0 } 메뉴바 (기본값은 yes)
523                    toolbar     : 'no',                         // { yes | no | 1 | 0 } 툴바 (기본값은 yes)
524                    resizable   : 'yes',                        // { yes | no | 1 | 0 } 창 (기본값은 yes)
525                    scrollbars  : 'yes'                         // { yes | no | 1 | 0 } 창 스크롤바 (기본값은 yes)
526                };
527
528                // ALIGN CENTER
529                const alignCenter = {
530                    top : Math.round((window.innerHeight / 2) - (options.height / 2)),
531                    left : Math.round((window.innerWidth / 2) - (options.width / 2))
532                };
533
534                // WINDOW OPEN
535                if( !winPop || (winPop && winPop.closed) ) {
536                    winPop = window.open(''+ options.href +'',''+ options.title +'','width='+ options.width +',height='+ options.height +',top='+ alignCenter.top +',left='+ alignCenter.left +',status='+ options.status +',fullscreen='+ options.fullscreen +', channelmode='+ options.channelmode+', location='+ options.location+', menubar='+ options.menubar +', toolbar='+ options.toolbar +', resizable='+ options.resizable +', scrollbars='+ options.scrollbars +'');
537                } else {
538                    winPop.location.href = options.href;
539                    winPop.focus();
540                }
541
542            });
543        });
544
545    }
546
547
548
549    /**
550     * URL 클립보드
551     *
552     * @version 1.0.0
553     * @author STUDIO-JT (KMS)
554     */
555    function share_clipboard(){
556
557        if( navigator.clipboard && document.getElementsByClassName('jt-share--url').length > 0 ){
558
559            document.querySelectorAll('.jt-share--url').forEach((item) => {
560                item.addEventListener('click', function(e){
561                    e.preventDefault();
562
563                    const _this = this;
564
565                    if ( 'share' in navigator && JT.browser('mobile') ) {
566                        const title = document.querySelector('meta[property="og:title"]').getAttribute('content');
567                        const url = document.querySelector('link[rel="canonical"]').getAttribute('href');
568
569                        navigator.share({
570                            title: title,
571                            url: url,
572                        });
573
574                        return false;
575                    } else {
576                        navigator.clipboard.writeText(_this.getAttribute('href')).then(function(){
577                            if ( !!!document.querySelector('.jt-share__tooltip') ) {
578                                let tooltip = document.createElement('div');
579                                    tooltip.setAttribute('class', 'jt-share__tooltip');
580                                    tooltip.innerHTML = `<p class="jt-typo--09">${_this.getAttribute('data-tooltip')}</p>`;
581
582                                _this.closest('.jt-share').appendChild(tooltip);
583                            }
584
585                            gsap.fromTo('.jt-share__tooltip', { autoAlpha: 0 }, { autoAlpha: 1, duration: .2, onComplete: function () { gsap.fromTo('.jt-share__tooltip', { autoAlpha: 1 }, { autoAlpha: 0, duration: .2, delay: 3, onComplete: function () { document.querySelector('.jt-share__tooltip').remove(); } }); } });
586                        });
587                    }
588                });
589            });
590
591
592        }
593
594    }
595
596
597
598    /**
599     * Vimeo custom play
600     *
601     * @version 1.0.0
602     * @author STUDIO-JT (KMS, NICO)
603     * @see {@link https://developer.vimeo.com/|API}
604     * @requires https://player.vimeo.com/api/player.js
605     */
606    function vimeo_play(){
607
608        if( !document.getElementsByClassName('jt-embed-video--vimeo').length ) return;
609
610        // play if click on the poster
611        document.querySelectorAll('.jt-embed-video--vimeo .jt-embed-video__poster').forEach((element) => {
612            const poster = element;
613            const parent = poster.closest('.jt-embed-video__inner');
614            const iframe = parent.querySelector('iframe');
615
616            poster.addEventListener('click', function(e){
617                e.preventDefault();
618
619                JT.globals.jt_vimeo_ready(function(){
620                    const video = new Vimeo.Player(iframe);
621                    gsap.set(iframe, { autoAlpha: 1 });
622                    gsap.to(poster, { autoAlpha: 0, duration: .6, onStart: function () { video.play(); } });
623                });
624            });
625        });
626
627    }
628
629
630
631    /**
632     * Youtube custom play
633     *
634     * @version 1.0.0
635     * @author STUDIO-JT (NICO)
636     * @see {@link https://developers.google.com/youtube/iframe_api_reference}
637     */
638    function youtube_play(){
639
640        if( !document.getElementsByClassName('jt-embed-video--youtube').length ) return;
641
642        const tag = document.createElement('script');
643        tag.src = 'https://www.youtube.com/iframe_api';
644
645        const firstScriptTag = document.getElementsByTagName('script')[0];
646        firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
647
648        // if youtube api ready do your stuff
649        window.onYouTubeIframeAPIReady = function() {
650
651            // play if click on the poster
652            document.querySelectorAll('.jt-embed-video--youtube .jt-embed-video__poster').forEach((element) => {
653
654                const poster = element;
655                const parent = poster.closest('.jt-embed-video__inner');
656                const iframe = parent.querySelector('iframe');
657                const iframeId = iframe.getAttribute('id');
658
659                new YT.Player(iframeId,{
660                    events: {
661                        'onReady': function(event){
662                            poster.addEventListener('click', function(e){
663                                e.preventDefault();
664
665                                event.target.playVideo();
666                                gsap.to(poster, { autoAlpha: 0, duration: .6, onComplete: function () { poster.remove(); } });
667                            });
668                        }
669                    }
670                });
671
672            });
673
674        }
675
676    }
677
678
679
680    /**
681     * Video lazyload + inview autoplay
682     *
683     * @version 1.0.0
684     * @author STUDIO-JT (KMS)
685     * @requires gsap.min.js
686     * @requires ScrollTrigger.min.js
687     * @see {@link https://greensock.com/docs/|GreenSock Docs}
688     */
689    function autoplay_inview(){
690
691        const $target = $('.jt-autoplay-inview');
692
693        $target.each(function(){
694
695            const $this = $(this);
696
697            if( $this.hasClass('jt-autoplay-inview--component') ) return; // 컴포넌트 타입 제외
698
699            ScrollTrigger.create({
700                trigger: $this,
701                start: 'top 200%',
702                once: true,
703                onEnter: function(){
704
705                    const $video  = $this.find('.jt-background-video__vod'),
706                          $poster = $this.find('.jt-background-video__poster');
707
708                    if( !$video.find('video').length ) {
709                        $video.append('<video playsinline loop muted preload><source src="'+  $video.attr('data-video') +'" type="video/mp4"></video>');
710                    }
711
712                    const vid = $video.find('video')[0];
713                    
714                    if(vid){
715
716                        if( $('html').hasClass('chrome') || $('html').hasClass('safari') ){
717
718                            const promise = vid.play();
719
720                            if( promise !== undefined ) {
721                                promise.then(function() {
722                                    if(vid.readyState !== 4){
723                                        vid.addEventListener('canplaythrough', jt_video_canplay, false);
724                                        vid.addEventListener('load', jt_video_canplay, false);
725                                        if( !$this.hasClass('jt-autoplay-inview--play') ){ vid.pause(); }
726                                    } else {
727                                        gsap.to($poster, { duration: .2, autoAlpha: 0, onComplete: function () { $poster.hide(); } });
728                                        $this.addClass('jt-autoplay--loaded');
729                                    }
730                                }).catch(function(error) {
731                                    console.warn( error );
732                                });
733                            }
734
735                        } else {
736
737                            vid.play();
738
739                            if(vid.readyState !== 4){
740                                vid.addEventListener('canplaythrough', jt_video_canplay, false);
741                                vid.addEventListener('load', jt_video_canplay, false);
742                                if( !$this.hasClass('jt-autoplay-inview--play') ){ vid.pause(); }
743                            } else {
744                                gsap.to($poster, { duration: .2, autoAlpha: 0, onComplete: function () { $poster.hide(); } });
745                                $this.addClass('jt-autoplay--loaded');
746                            }
747
748                        }
749
750                        function jt_video_canplay(){
751                            vid.removeEventListener('canplaythrough', jt_video_canplay, false);
752                            vid.removeEventListener('load', jt_video_canplay, false);
753
754                            gsap.to($poster, { duration: .2, autoAlpha: 0, onComplete: function () { $poster.hide(); } });
755                            
756                            vid.play();
757
758                            $this.addClass('jt-autoplay--loaded');
759                        }
760
761                        // autoplay trigger
762                        ScrollTrigger.create({
763                            trigger: $this,
764                            once: false,
765                            onEnter: function(){
766                                if( vid.paused && $this.hasClass('jt-autoplay--loaded') ) {
767                                    $this.removeClass('jt-autoplay-inview--paused').addClass('jt-autoplay-inview--play');
768                                    vid.play();
769                                }
770                            },
771                            onEnterBack: function(){
772                                if( vid.paused && $this.hasClass('jt-autoplay--loaded') ) {
773                                    $this.removeClass('jt-autoplay-inview--paused').addClass('jt-autoplay-inview--play');
774                                    vid.play();
775                                }
776                            },
777                            onLeave: function(){
778                                if( !vid.paused && $this.hasClass('jt-autoplay--loaded') ) {
779                                    $this.removeClass('jt-autoplay-inview--play').addClass('jt-autoplay-inview--paused');
780                                    vid.pause();
781                                }
782                            },
783                            onLeaveBack: function(){
784                                if( !vid.paused && $this.hasClass('jt-autoplay--loaded') ) {
785                                    $this.removeClass('jt-autoplay-inview--play').addClass('jt-autoplay-inview--paused');
786                                    vid.pause();
787                                }
788                            }
789                        });
790                    }
791
792                }
793            });
794
795        });
796
797    }
798
799
800
801    function html_video_play(){
802
803        if( !document.getElementsByClassName('jt-html-video').length ) return;
804
805        document.querySelectorAll('.jt-html-video').forEach((target) => {
806
807            const poster = target.getElementsByClassName('jt-html-video__poster')[0];
808            const video = target.getElementsByTagName('video')[0];
809            let initialPlay = true;
810
811            if(video){
812
813                poster.addEventListener('click', function(e){
814                    e.preventDefault();
815
816                    video.load();
817                    video.oncanplaythrough = function(){
818
819                        if( !initialPlay ) return;
820                        initialPlay = false;
821
822                        video.play();
823
824                        if( poster != null ) { gsap.to(poster, { duration: .6, autoAlpha: 0, onComplete: function () { poster.style.display == "none"; } }); }
825                    }
826                });
827            }
828
829        })
830
831    }
832
833
834
835    /**
836     * Gutenberg editor spacer block converter
837     *
838     * @version 1.0.0
839     * @author STUDIO-JT (KMS)
840     */
841    function blocks_spacer(){
842
843        document.querySelectorAll('.wp-block-spacer').forEach((space) => {
844
845            if( space.getAttribute('data-space') == null ) {
846                space.setAttribute('data-space', space.style.height);
847            }
848
849            const heightOrigin = space.getAttribute('data-space');
850            let heightConvert = heightOrigin.replace('px','');
851
852            if( JT.isScreen(860) ){
853                heightConvert = heightConvert/2;
854            }
855
856            space.style.height = heightConvert+'rem';
857
858        });
859
860    }
861
862
863
864    /**
865     * Gutenberg editor list block option support
866     *
867     * @version 1.0.0
868     * @author STUDIO-JT (KMS)
869     */
870    function blocks_list_counter(){
871
872        document.querySelectorAll('.jt-blocks ol[reversed]').forEach((list) => {
873            list.style.counterReset = 'item ' + (list.children.length + 1);
874        });
875
876    }
877
878
879
880    /**
881     * 리스트 fullclick action을 추가합니다
882     *
883     * @version 1.0.0
884     * @author STUDIO-JT (KMS)
885     */
886    function jt_list_fullclick() {
887
888        document.querySelectorAll('.js-action--full-click').forEach((item) => {
889
890            if ( item.classList.contains('feature-recommend__list-item') && JT.browser('mobile') ){
891
892                item.classList.remove('js-action--full-click');
893                return;
894
895            }
896
897            item.addEventListener('click', (e) => {
898
899                var url = item.querySelectorAll('a')[0].getAttribute('href');
900
901                if(url != undefined){
902                    window.location.href = url;
903                }
904            });
905        });
906
907    }
908
909
910
911    /* **************************************** *
912     * Helpers
913     * **************************************** */
914    /**
915     * Vimeo script on demand
916     *
917     * @version 1.0.0
918     * @author STUDIO-JT (KMS)
919     */
920    JT.globals.jt_vimeo_ready = function( callback ){
921
922        if( typeof callback != 'function' ) return;
923
924        if( typeof Vimeo == 'undefined' ){
925
926            const prior = document.getElementsByTagName('script')[0];
927
928            let script = document.createElement('script');
929            script.async = 1;
930
931            script.onload = script.onreadystatechange = function( _, isAbort ) {
932                if( isAbort || !script.readyState || /loaded|complete/.test(script.readyState) ) {
933                    script.onload = script.onreadystatechange = null;
934                    script = undefined;
935
936                    if( !isAbort ) return callback();
937                }
938            };
939
940            script.src = 'https://player.vimeo.com/api/player.js';
941            prior.parentNode.insertBefore(script, prior);
942
943        } else {
944
945            return callback();
946
947        }
948
949    }
950
951    function jt_modules_ajax(){
952
953        if( $('body').hasClass('tag') ) { return; }
954
955        // Init
956        if( !!$('.jt-category-nav-wrap').length && JT.isScreen(540) ) {
957
958            // Scroll
959            category_scroll( $('.jt-category-nav-wrap').find('.jt-category--active') );
960
961        }
962
963        $(document).on('click', '.jt-category-nav > ul > li > a', function(e){
964
965            e.preventDefault();
966
967            const $this    = $(this);
968            const url      = $this.attr('href');
969            const resource = url.indexOf('resource');
970            if( isAjaxLoading || $this.closest('li').hasClass('jt-category--active') ) return;
971
972            // Scroll
973            category_scroll( $this.closest('li') );
974
975            jt_ajax_call( 'category', url , resource );
976
977            // resource menu update
978            $('.menu-item a, small-menu a').each(function(){
979                const target = $(this).prop('search');
980
981                if ($this.prop('search') == target){
982
983                    $(this).parent('li').siblings().removeClass('current-menu-item');
984                    $(this).parent('li').addClass('current-menu-item');
985                }
986
987            })
988
989        });
990
991        // List
992        $(document).on('click', '.jt-list-ajax a', function(e){
993            e.preventDefault();
994
995            const $this    = $(this);
996            const url      = $this.attr('href');
997            if( isAjaxLoading ) return;
998
999            jt_ajax_call( 'list', url, null );
1000
1001        });
1002
1003        // Pagination
1004        $(document).on('click', '.jt-pagination a', function(e){
1005
1006            e.preventDefault();
1007
1008            const $this    = $(this);
1009            const url      = $this.attr('href');
1010            
1011            if( isAjaxLoading ) return;
1012
1013            jt_ajax_call( 'paging', url, null );
1014
1015        });
1016
1017        // Search
1018        $(document).on('submit', '.jt-search', function(e){
1019
1020            e.preventDefault();
1021
1022            const $this   = $(this);
1023
1024            const formData = new FormData(this);
1025            // if (formData.has("search") && formData.get("search").length > 100) {
1026            if ($this.find("[name='search']").val().length > 100) {
1027                alert("검색 가능한 글자 수가 초과되었습니다.(100자)");
1028                return false;
1029            }
1030
1031            const data    = $this.serialize();
1032            const url     = $this.attr('action') + '?' + data;
1033
1034            if( isAjaxLoading ) return;
1035
1036            jt_ajax_call( 'search', url, data );
1037
1038            $this.find('#jt-search__input').blur();
1039
1040        });
1041
1042        // Popstate
1043        $(window).on('popstate', function(){
1044
1045            var url    = new URL( window.location.href );
1046                params = url.searchParams;
1047
1048            jt_ajax_call('popstate', url.href, null, function(){
1049
1050                category_scroll( $('.jt-category--active') );
1051
1052                if( params.get('search') ){
1053                    $('.jt-search__bundle').addClass('jt-search--active');
1054                    $('#jt-search__input').val( params.get('search') );
1055                } else {
1056                    $('.jt-search__bundle').removeClass('jt-search--active');
1057                    $('#jt-search__input').val( '' );
1058                }
1059            });
1060
1061        });
1062
1063        // Scroll
1064        function category_scroll( $li ){
1065
1066            if( JT.isScreen(540) ) {
1067                const containerWidth = $('.jt-list-filter').width();
1068                let itemWidth = 0;
1069
1070                $('.jt-list-filter li').each(function( idx ){
1071                    const $that = $(this);
1072                    itemWidth += $that.width() + parseInt($that.css('margin-left'));
1073
1074                    if( idx == $li.index() ) {
1075                        let left = itemWidth - (containerWidth/2);
1076                        if( left < 0 ) { left = 0; }
1077
1078                        $('.jt-category-nav-wrap').stop().animate({ scrollLeft: left }, 400);
1079
1080                        return false;
1081                    }
1082                });
1083            }
1084
1085        }
1086
1087    }
1088
1089    function jt_ajax_call( type, url, data, callback ) {
1090
1091        // Update flag
1092        isAjaxLoading = true;
1093
1094        // Target
1095        const selector = '.jt-list-container';
1096        const $container = $(selector);
1097
1098        if ( data ) {
1099            gsap.to($container.find('> *:not(.jt-list-filter):not(.article__highlight)'), { opacity: 0, duration: .2 });
1100        } else {
1101            gsap.to($container.find('> *:not(.jt-list-filter)'), { opacity: 0, duration: .2 });
1102        }
1103
1104        $.get( url, data, function ( response ) {
1105
1106            var $response = $(response).find(selector);
1107
1108            // lazy image check
1109            $response.find('.jt-lazyload').each(function(){
1110                var $this = $(this);
1111                var $img = $this.find('img');
1112                $this.addClass('jt-lazyload--loaded');
1113                $img.attr('src', $img.attr('data-unveil'));
1114            });
1115
1116            $container.find('> *:not(.jt-list-filter)').remove();
1117            $container.append($response.find('> *:not(.jt-list-filter)'));
1118
1119            if( type != 'paging' ) {
1120                $container.find('.jt-category-nav').replaceWith( $response.find('.jt-category-nav') );
1121            }
1122
1123            if( type == 'list' ) {
1124                // 목록으로가기 클릭시 검색데이터 reset
1125                $container.find('.jt-search__input').val('');
1126                $container.find('.jt-search__bundle').removeClass('jt-search--active');
1127            }
1128
1129            // Update url
1130            if( type != 'popstate' ){
1131                gsap.to($container.find('> *:not(.jt-list-filter)'), {
1132                    opacity: 1,
1133                    duration: .3,
1134                    onStart: function() {
1135                        gsap.to(window, { duration: .3, scrollTo: $container.offset().top - ($('#header').outerHeight()*1.3) });
1136                        JT.ui.call( 'selectric_init' );
1137                    }
1138                });
1139
1140                if ('history' in window && 'pushState' in history) {
1141                    window.history.pushState(null, null, url);
1142                }
1143            }
1144
1145            // swiper reinit (highlight)
1146            JT.ui.call('article_highlight_slider');
1147
1148            // prevent button with comingsoon alert
1149            JT.ui.call('btn_comingsoon');
1150
1151            // callback
1152            if( typeof callback == 'function' ){
1153                callback.call();
1154            }
1155
1156            // Update flag
1157            isAjaxLoading = false;
1158
1159        } );
1160
1161    }
1162
1163
1164
1165    // 그루비 맞춤 Validation
1166    JT.globals.validation = function( element, wrapSelector, validSelector ){
1167
1168        const field     = element;
1169        const container = ( typeof wrapSelector == 'undefined' ) ? field.closest('.jt-form__data') : field.closest(wrapSelector);
1170        const validTip  = ( typeof wrapSelector == 'undefined' ) ? container.querySelector('.jt-form__valid') : container.querySelector(validSelector);
1171        const dataset   = field.dataset;
1172        let isValid     = true;
1173
1174        // Pattern 모음
1175        const langPattern       = /^(?!^\s)(?!.*\s$)[a-zA-Z가-힣\s]+$/; // 한글+영문 (앞/뒤 스페이스 불가)
1176        const numSpecialPattern = /^[0-9!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]+$/; // 숫자 + 기호
1177        const consonantPattern  = /[ㄱ-ㅎ]/; // 자음
1178        const vowelPattern      = /[ㅏ-ㅣ]/; // 모음
1179        const phonePattern      = /^0?[0-9]{1,2}-?[0-9]{3,4}-?[0-9]{4}$/; // 전화번호
1180        const emailPattern      = /^([\w-]+(?:\.[\w-]+)*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$/; // 이메일
1181
1182        if( !field.validity.valid ) { // Default
1183
1184            if( field.validity.valueMissing ) {
1185
1186                if( 'msgRequired' in dataset ) {
1187                    validTip.textContent = dataset.msgRequired;
1188                } else {
1189                    validTip.textContent = '필수항목을 입력해 주세요.';
1190                }  
1191
1192            } else if( field.validity.tooLong ) {
1193
1194                validTip.textContent = '최대 허용 길이보다 깁니다.';
1195
1196            } else if( field.validity.tooShort ) {
1197                
1198                validTip.textContent = '최소 허용 길이보다 짧습니다.';
1199
1200            }
1201
1202            // Flag
1203            container.classList.add('jt-form__data--error');
1204            isValid = false;
1205
1206        } else { // Custom Patterns
1207
1208            const fieldId   = field.getAttribute('id');
1209            const fieldVal  = field.value;
1210            let   isPattern = true;
1211
1212            if( field.hasAttribute('required') && !fieldVal.replace(/\s/g, '').length ) { // 공백제외 빈칸
1213
1214                validTip.textContent = '필수항목을 입력해 주세요.';
1215                isPattern = false;
1216
1217            } else { // 빈칸아닐 때
1218
1219                if( fieldId == 'name' ) { // 성함
1220
1221                    if( consonantPattern.test( fieldVal ) || vowelPattern.test( fieldVal ) ) {
1222                        validTip.textContent = '정확한 형식의 성함을 기재해 주세요.';
1223                        isPattern = false;
1224                    } else if( !langPattern.test( fieldVal ) ) {
1225                        validTip.textContent = '한글, 영문만 입력이 가능합니다.';
1226                        isPattern = false;
1227                    }
1228
1229                } else if( fieldId == 'company' || fieldId == 'part' || fieldId == 'position' ) { // 회사명, 부서명, 직급
1230
1231                    if( consonantPattern.test( fieldVal ) || vowelPattern.test( fieldVal ) || numSpecialPattern.test( fieldVal ) ) {
1232                        // Error type
1233                        let msgType = '';
1234
1235                        switch ( fieldId ) {
1236                            case 'part':
1237                                msgType = '부서명';
1238                                break;
1239                            case 'position':
1240                                msgType = '직급';
1241                                break;
1242                            default:
1243                                msgType = '회사명';
1244                                break;
1245                        }
1246                        
1247                        validTip.textContent = `정확한 형식의 ${msgType}을 기재해 주세요.`;
1248                        isPattern = false;
1249                    }
1250
1251                } else if( fieldId == 'phone' ) { // 전화번호
1252
1253                    if( !phonePattern.test( fieldVal ) ) {
1254                        validTip.textContent = '정확한 형식의 연락처를 기재해 주세요.';
1255                        isPattern = false;
1256                    }
1257
1258                } else if( fieldId == 'email' ) { // 이메일
1259
1260                    if( !emailPattern.test( fieldVal ) ) {
1261                        validTip.textContent = '정확한 형식의 이메일을 기재해 주세요.';
1262                        isPattern = false;
1263                    }
1264
1265                } else if( fieldId == 'site' || fieldId == 'function' ) { // 도입예정 사이트
1266
1267                    if( fieldVal.length == 1 || numSpecialPattern.test( fieldVal ) ) {
1268                        // Error type
1269                        const msgType = ( fieldId == 'site' ) ? '사이트 URL' : '기능';
1270
1271                        validTip.textContent = `정확한 ${msgType}을 기재해 주세요.`;
1272                        isPattern = false;
1273                    }
1274
1275                }
1276
1277            }
1278
1279            // Flag
1280            if( !isPattern ) { // Error
1281                container.classList.add('jt-form__data--error');
1282                isValid = false;
1283            } else { // Success
1284                validTip.textContent = '';
1285                container.classList.remove('jt-form__data--error');
1286            }
1287        
1288        }
1289
1290        return isValid;
1291
1292    }
1293
1294
1295
1296})(jQuery);

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.