PageSourceSearch

https://hcroh.org/js/site_shop/site_shop.js?1790832311

js hcroh.org collected 2026-10-01 15:34:09 UTC 662,870 bytes, 14,554 lines download raw bytes

1/**
2 * site_shop 상수 값
3 * @type {{WIDTH_MOBILE: number, SEEMORE_HEIGHT: {PC: number, MOBILE: number}, PROD_TYPE: {DIGITAL: string, SUBSCRIBE: string, NORMAL: string}, TAB_TYPE: {RETURN: string, QNA: string, REVIEW: string, DETAIL: string}}}
4 */
5const SHOP_CONST = {
6    /** shopping default */
7    WIDTH_MOBILE: 768,
8    PROD_TYPE: {
9        NORMAL: 'normal',
10        DIGITAL: 'digital',
11        SUBSCRIBE: 'subscribe'
12    },
13    TAB_TYPE: {
14        DETAIL: 'detail',
15        QNA: 'qna',
16        REVIEW: 'review',
17        RETURN: 'return'
18    },
19    SEEMORE_HEIGHT: {
20        PC: 1050,
21        MOBILE: 550
22    },
23    /** cart */
24    CART_TYPE_NORMAL: 'normal',
25    CART_TYPE_REGULARLY: 'regularly'
26};
27
28/**
29 * 사은품 MFE(magnet-shell)가 DOM에 적재되어 있는지 검사한다.
30 * 사은품 미사용 사이트는 PHP단에서 magnet-shell 자체를 렌더하지 않으므로,
31 * 이 검사가 false 이면 사은품 관련 처리를 스킵해도 안전하다.
32 * @returns {boolean}
33 */
34const isFreebieMfeMounted = () => {
35    return !!document.querySelector('magnet-shell[data-manifest-url*="fo-prod-detail-freebie"]');
36};
37
38/**
39 * 로그인 페이지로 리다이렉트 (코드 7 처리용) - 전역 함수
40 * @returns {void}
41 */
42function redirectToLoginPage() {
43    var back_url_base64 = window.location.href; // 전체 URL 사용
44
45    // 로그인 팝업 사용 여부에 따른 분기
46    if (typeof cm_data !== 'undefined' && cm_data.use_login_popup === 'Y') {
47        if (typeof window.SITE_MEMBER !== 'undefined' && typeof window.SITE_MEMBER.openLogin === 'function') {
48            window.SITE_MEMBER.openLogin(cm_data.back_url, cm_data.back_url_unit_type, 'order', cm_data.use_login_popup);
49        } else {
50            // 팝업 함수가 없으면 리다이렉트로 폴백
51            window.location.href = '/login?back_url=' + encodeURIComponent(btoa(back_url_base64));
52        }
53    } else {
54        window.location.href = '/login?back_url=' + encodeURIComponent(btoa(back_url_base64));
55    }
56}
57
58// __bs_imweb localStorage 의 landingUrl 을 imweb-landing-url 헤더 값으로 변환한다.
59// 정규식은 HTTP 헤더 인젝션 방어, @direct 는 attribution 정책상 미송신.
60function getBsLandingUrlHeaderValue() {
61    try {
62        const raw = localStorage.getItem('__bs_imweb');
63        if (!raw) return '';
64        const data = JSON.parse(raw);
65        if (!data || typeof data.landingUrl !== 'string' || !data.landingUrl) return '';
66        const stripped = data.landingUrl.replace(/[\r\n\x00-\x1F\x7F]/g, '');
67        if (!stripped || stripped.indexOf('@direct') === 0) return '';
68        return stripped;
69    } catch (e) {
70        return '';
71    }
72}
73
74var SITE_SHOP_DETAIL = function () {
75    var $body;
76    var $selected_options;
77    var $prod_detail;
78    var $prod_detail_content_mobile;
79    var $prod_detail_content_pc;
80    var $prod_detail_content_tab_mobile;
81    var $prod_detail_return;
82    var $prod_detail_return_mobile;
83    var $first_photo_review_wrap;
84    var $prod_image_list;
85    var $prod_image_list_rolling;
86    var $review_image_list;
87    var $review_image_list_rolling;
88    var $review_summary_wrap;
89    var $review_summary_wrap_mobile;
90    var $prod_goods_form;
91    var $add_cart_alarm;
92    // 장바구니 담기 후 무료 배송 안내 모달 ($_init_deliv_price_flexable_key > 0인 케이스에만 DOM에 존재)
93    var $add_cart_alarm_with_free_ship_notify;
94    var $add_cart_alarm_with_product_recommendation;
95    var $confirm_order_with_cart_alarm;
96    var $deliv_visit_wrap;
97    var $options;
98		var $prod_additional;
99		var $prod_additional_options;
100		var $prod_additional_options_default;
101    var $prod_deliv_setting;
102    var _initDetailDone = false;
103    var _pendingSalesToolPreviewMessages = [];
104    var _handleSalesToolPreviewMessage;
105    var _isFreeShipNotifySalesToolPreview = false;
106    var _isWaitingFreeShipNotifySdkForAddCartAlarm = false;
107    var _isWaitingFreeShipNotifyPreviewReadyToNotify = false;
108    var _freeShipNotifyPreviewReadyObserver = null;
109    var _freeShipNotifyPreviewReadyCallbacks = [];
110    var _hasFreeShipNotifyInlineMagnet = false;
111    // HTML sanitize: script 태그, 이벤트 핸들러 제거 (IMOPS-13023)
112    var _sanitizeHtml = function (html) {
113        if (html === null || html === undefined) return html;
114        var div = document.createElement('div');
115        div.innerHTML = html;
116        var scripts = div.querySelectorAll('script');
117        for (var i = 0; i < scripts.length; i++) {
118            scripts[i].parentNode.removeChild(scripts[i]);
119        }
120        var dangerousTags = div.querySelectorAll('svg, math, iframe, object, embed, form');
121        for (var t = 0; t < dangerousTags.length; t++) {
122            dangerousTags[t].parentNode.removeChild(dangerousTags[t]);
123        }
124        var allEls = div.querySelectorAll('*');
125        for (var j = 0; j < allEls.length; j++) {
126            var attrs = allEls[j].attributes;
127            for (var k = attrs.length - 1; k >= 0; k--) {
128                var attrName = attrs[k].name.toLowerCase();
129                var attrVal = (attrs[k].value || '').replace(/\s/g, '').toLowerCase();
130                if (attrName.indexOf('on') === 0) {
131                    allEls[j].removeAttribute(attrs[k].name);
132                } else if ((attrName === 'href' || attrName === 'src' || attrName === 'action') &&
133                           (attrVal.indexOf('javascript:') === 0 || attrVal.indexOf('data:text/html') === 0 || attrVal.indexOf('vbscript:') === 0)) {
134                    allEls[j].removeAttribute(attrs[k].name);
135                } else if (attrName === 'srcdoc' || attrName === 'xlink:href') {
136                    allEls[j].removeAttribute(attrs[k].name);
137                }
138            }
139        }
140        return div.innerHTML;
141    };
142		var $wish_buttons;
143		var selected_prod_additionals = [];
144    var selected_options = []; /** [ {options:[{option_code:, value_code:, value_name:}], price:, count:, require:} ] */
145    var selected_prod_additional_options = []; /** [ {options:[{option_code:, value_code:, value_name:}], price:, count:, require:} ] */
146    var selected_require_options = []; /** [ { value_type:SELECT(선택형)/INPUT(입력형), option_code:, value_code:, value_name:  } ] */
147    var selected_prod_additional_require_options = []; /** [ { value_type:SELECT(선택형)/INPUT(입력형), option_code:, value_code:, value_name:  } ] */
148    var free_ship_notify_prod_additional_regular_price_map = {};
149		var current_select_additional_prod_idx = 0; /** 최근 선택한 추가상품 idx(추가상품 UI 영역에 표시하기 위해 사용) **/
150    var require_option_count = 0;
151    var prod_additional_require_option_count = {};
152    var require_input_option_count = 0;
153    var prod_additional_require_input_option_count = {};
154    var current_prod_idx = 0;
155		var current_prod_code = 0;
156    var prod_stock_use = false;
157    var prod_stock = 0;
158    var prod_stock_unlimit = false;
159    var prod_price = 0;
160    var FREE_SHIP_NOTIFY_DELIV_PRICE_COST_TYPE_AFTER = 'after_sale';
161    var free_ship_notify_deliv_price_cost_type = 'before_sale';
162    var free_ship_notify_prod_regular_price = 0;
163    var order_count = 0;
164    /** 구매할 수량 (옵션이 없는 경우에만)(*/
165    /** 크리에이터 전용 혜택가 데이터 — PHP 에서 setCreatorDiscountData 로 주입. null 이면 비활성 */
166    var creator_discount_data = null;
167    var creator_discount_max_reached = false;
168    var creator_discount_max_toast_timer = null;
169    var creator_discount_max_toast_text = '최대 할인 금액에 도달했어요';
170    var isComplete = false;
171    var current_content_tab = '';
172    /** detail,review,qna*/
173    var isUseNpMobile = false;
174    /** 모바일 네이버페이 사용 유무*/
175    var $deliv_type = '';
176    var $deliv_pay_type = '';
177    /** 배송 국가 (옵션 select 커스텀 변경) */
178    var $deliv_country = '';
179    /** 선택된 배송 템플릿 코드 (OMS), 프런트 UI에서 설정 예정 */
180    var selected_shipping_template_code = '';
181
182    /* 구매 수량 체크를 위한 변수 */
183    var max_prod_quantity = 0;
184    var max_member_quantity = 0;
185    var maximum_purchase_quantity_type = 'order';
186    var optional_limit = 0;
187    var optional_limit_type = 'relative';
188    var prod_name = '';
189
190    var total_price_localize_text = '';		// 총금액 다국어 코드
191
192    var add_order_progress_check = false;
193
194    var use_lazy_load = true; //레이지 로드 사용여부
195
196    var tab_type = 'Y'; // pc 버전 탭 타입
197
198    var prod_type = SHOP_CONST.PROD_TYPE.NORMAL;
199
200    // 상품 상세페이지 내 기획전 위젯의 cart 버튼을 클릭하여 모달을 띄우는 동작에서
201    // 모달이 닫힐 시 설정 값의 초기화를 위해 변수를 설정
202    var prod_idx_org = 0;
203		var prod_code_org = 0;
204    var prod_price_org = 0;
205    var require_option_count_org = 0;
206    var use_lazy_load_org = true;
207    var tab_type_org = 'Y';
208    var is_site_page_org = true;
209    var prod_type_org = SHOP_CONST.PROD_TYPE.NORMAL;
210
211    var is_view_price = true;
212    var paging_type = 'st00';
213    var paging_default_type = 'st00';
214    var paging_active_type = 'st00';
215    var cm_data = {};
216    var section_code;
217
218    var prod_edit_time = '';
219    var prod_deliv_hash = '';
220    var shop_view_style = '';
221
222    var handle_loadDelivSetting = 0;
223    var prod_option_touching = false;
224
225    var first_tab = '';
226    var use_tab_list = [];
227
228    var window_width = 1920;
229    var is_mobile_width = false;
230
231    var default_options = {
232        prod_idx: 0,
233        prod_price: 0,
234        require_option_count: 0,
235        require_input_option_count: 0,
236		use_image_optimizer_on_product_detail_markup: true,
237        shop_use_full_load: false,
238        is_site_page: false,
239        prod_type: SHOP_CONST.PROD_TYPE.NORMAL,
240        is_prod_detail_page: false,
241        is_price_view_permission: false,
242        cm_style: '{}',
243        section_code: '',
244        exist_color_option: false,
245        is_exist_color_option_images: false,
246        first_tab: '',
247        can_add_order: true,
248        add_order_permission_message: '',
249        add_order_permission_type: '',
250        initial_is_guest: false,
251        shipping_template_code: '',
252        free_ship_notify_deliv_price_cost_type: 'before_sale',
253        free_ship_notify_prod_regular_price: 0
254    };
255    var options = {};
256    var analyticsEventName = 'imweb:shop:detail-analytics';
257
258    var dispatchShopAnalyticsEvent = function (type, payload) {
259        try {
260            if (!document || typeof document.dispatchEvent !== 'function') return;
261
262            var event = null;
263            if (typeof CustomEvent === 'function') {
264                event = new CustomEvent(analyticsEventName, {
265                    detail: {
266                        type: type,
267                        payload: payload || {}
268                    }
269                });
270            } else if (document.createEvent) {
271                event = document.createEvent('CustomEvent');
272                event.initCustomEvent(
273                    analyticsEventName,
274                    false,
275                    false,
276                    {
277                        type: type,
278                        payload: payload || {}
279                    }
280                );
281            }
282
283            if (event) {
284                document.dispatchEvent(event);
285            }
286        } catch (e) {
287            return;
288        }
289    };
290
291    var safeSetProdDetailAnalyticsTargets = function (body, pcTarget, mobileTarget, isMobileGetter) {
292        try {
293            dispatchShopAnalyticsEvent('setTargets', {
294                body: body,
295                pcTarget: pcTarget,
296                mobileTarget: mobileTarget,
297                isMobileGetter: isMobileGetter
298            });
299        } catch (e) {
300            return;
301        }
302    };
303
304    var safeScheduleProdDetailBodyExpandedHeightUpdate = function () {
305        try {
306            dispatchShopAnalyticsEvent('scheduleBodyHeightUpdate');
307        } catch (e) {
308            return;
309        }
310    };
311
312    // redirectToLoginPage 함수는 전역 스코프로 이동됨
313
314    function ensureAddOrderPermission() {
315        if (options.can_add_order !== false) {
316            return true;
317        }
318
319        var wasGuestInitially = options.initial_is_guest === true;
320        var isGuestNow = typeof window !== 'undefined' && window.IS_GUEST === true;
321        if (wasGuestInitially && !isGuestNow) {
322            return true;
323        }
324
325        var message = options.add_order_permission_message;
326        if (!message) {
327            if (typeof LOCALIZE !== 'undefined') {
328                if (options.add_order_permission_type === 'member' && typeof LOCALIZE.설명_회원만구매가능1 === 'function') {
329                    message = LOCALIZE.설명_회원만구매가능1();
330                } else if (options.add_order_permission_type === 'group' && typeof LOCALIZE.설명_그룹만구매가능1 === 'function') {
331                    message = LOCALIZE.설명_그룹만구매가능1();
332                } else if (options.add_order_permission_type === 'admin' && typeof LOCALIZE.설명_소유자만구매가능1 === 'function') {
333                    message = LOCALIZE.설명_소유자만구매가능1();
334                } else if (typeof LOCALIZE.설명_회원만구매가능1 === 'function') {
335                    message = LOCALIZE.설명_회원만구매가능1();
336                }
337            }
338
339        }
340
341        if (!message) {
342            message = 'Purchase not permitted.';
343        }
344
345        alert(message);
346        return false;
347    }
348
349    var is_init_detail = false;
350    var last_refund_data;
351    //결제 타입 (일반, 정기구독)
352    var cart_type;
353
354	var review_page = 1;
355	var qna_page = 1;
356
357	let use_cdn_optimized = false;
358	let animatedWebpSupport = null;
359
360	const isProblematicAnimatedWebpUserAgent = (userAgent) => {
361		if (!userAgent) {
362			return false;
363		}
364
365		return /(iPad|iPhone|iPod)/i.test(userAgent) || (/Macintosh/i.test(userAgent) && /Mobile/i.test(userAgent));
366	};
367
368	// animated WebP 사용 여부는 페이지당 한 번만 판별한다.
369	const canUseAnimatedWebp = () => {
370		if (animatedWebpSupport !== null) {
371			return animatedWebpSupport;
372		}
373
374		let userAgent = '';
375		try {
376			userAgent = navigator.userAgent || '';
377		} catch (e) {
378			userAgent = '';
379		}
380
381		animatedWebpSupport = !isProblematicAnimatedWebpUserAgent(userAgent);
382
383		return animatedWebpSupport;
384	};
385
386
387	const setImageWidthHeightBeforeLoad = async (node) => {
388		console.time('loadTemplate')
389		const styleFragment = window.document.createElement('style');
390		styleFragment.innerHTML = `@keyframes lazyload {0% {opacity: 0;} 50% {opacity: 0.1;} 100% {opacity: 0;}}`
391		node.prepend(styleFragment);
392
393		const ORIGINAL_WIDTH_HEADER_KEY = 'x-amz-meta-original-width';
394		const ORIGINAL_HEIGHT_HEADER_KEY = 'x-amz-meta-original-height';
395		const imgElements = node.querySelectorAll('img') || [];
396		const timeoutLimit = 1000000;
397		const checkOnceSupport = checkerlistenerOption('once');
398		const loadingImageJobs = Array.prototype.map.call(imgElements, (async (imgElement) => {
399			let abortTimer = null;
400			try {
401				const srcOriginal = new URL(imgElement.src);
402				const srcDefault = new URL(window.CDN_UPLOAD_URL);
403				if (srcOriginal.host !== srcDefault.host || !srcOriginal.pathname.startsWith(srcDefault.pathname)) {
404					throw new Error();
405				}
406				// WARN: srcResized 값이 콜백 내에서 계속 변경되므로(searchParams.set) 주의
407				const srcResized = new URL(srcOriginal);
408				srcResized.host = new URL(window.CDN_OPTIMIZED_URL).host;
409				// 본문이 아니라 x-amz-meta-original-* 헤더만 쓰는 HEAD 프로브라 가장 작은 프리셋을 요청한다.
410				// 종전에는 0 을 보냈고 imgo 가 최근접 프리셋인 100 으로 스냅해 같은 객체를 내려줬다.
411				// imgo 가 w=0 을 "원본 해상도 유지"로 바꾸므로, 스냅에 기대지 않고 100 을 명시한다.
412				srcResized.searchParams.set('w', 100);
413
414				const signal = typeof AbortSignal.timeout === 'function'
415					? AbortSignal.timeout(timeoutLimit)
416					: new AbortController()
417
418				if (typeof signal.abort === 'function') {
419					abortTimer = setTimeout(signal.abort, timeoutLimit);
420				}
421				const imgRes = await fetch(srcResized, { method: 'HEAD',  signal });
422
423				const checkResized = imgRes.headers.has(ORIGINAL_WIDTH_HEADER_KEY)
424					&& imgRes.headers.has(ORIGINAL_HEIGHT_HEADER_KEY);
425				if (
426					!imgRes.ok || !checkResized
427				) {
428					throw new Error();
429				}
430
431				const meta = {
432					originalWidth: imgRes.headers.get(ORIGINAL_WIDTH_HEADER_KEY),
433					originalHeight: imgRes.headers.get(ORIGINAL_HEIGHT_HEADER_KEY),
434				}
435
436				const imgKey = srcResized.pathname.replace(/[^a-z\d]/g, '_');
437				imgElement.dataset[imgKey] = "";
438				const imgSelector = `img[data-${imgKey}]`;
439
440				const styleFragment = window.document.createElement('style');
441				styleFragment.innerHTML = `${imgSelector} {opacity:0;transition:opacity 60ms ease-out;} ${imgSelector}.loaded {opacity:1;}`;
442				imgElement.parentNode.prepend(styleFragment);
443				srcResized.searchParams.set('w', 1920);
444
445				if (srcResized.pathname.endsWith('.gif')) {
446					const useAnimatedWebp = canUseAnimatedWebp();
447					srcResized.searchParams.set('f', useAnimatedWebp ? 'webp' : 'gif');
448				}
449				imgElement.src = srcResized.href;
450				imgElement.style.objectFit = 'cover';
451				imgElement.style.maxWidth = '100%';
452				imgElement.style.height = 'auto';
453				imgElement.width = (imgElement.width || meta.originalWidth);
454				imgElement.height = (imgElement.height || meta.originalHeight);
455				imgElement.sizes = '100vw';
456				imgElement.srcset = [1536, 1280, 1080, 828, 768, 640, 576, 368]
457					.map((w) => {
458						srcResized.searchParams.set('w', w);
459						return `${srcResized.href} ${w}w`;
460					}).join(', ');
461				imgElement.loading = 'lazy';
462
463				imgElement.addEventListener('error', () => {
464					imgElement.src = imgElement.dataset.original || srcOriginal.href;
465					imgElement.classList.add('loaded');
466					imgElement.style.removeProperty('objectFit');
467					imgElement.style.removeProperty('maxWidth');
468					imgElement.style.removeProperty('height');
469					imgElement.removeAttribute('width');
470					imgElement.removeAttribute('height');
471					imgElement.removeAttribute('sizes');
472					imgElement.removeAttribute('srcset');
473					imgElement.removeAttribute('loading');
474				});
475
476				if (checkOnceSupport) {
477					imgElement.addEventListener('load', () => imgElement.classList.add('loaded'), { once: true });
478				} else {
479					imgElement.onload = () => imgElement.classList.add('loaded');
480				}
481			} catch (e) {
482				// 비-CDN 호스트·리사이즈 헤더 없음은 throw new Error() 로 표현한 "최적화 대상 아님" 의도 분기(빈 메시지)다.
483				// 이런 정상 폴백은 debug 로 낮추고, fetch/네트워크 등 진짜 예외는 error 로 남겨 운영 가시성을 유지한다.
484				if (e instanceof Error && !e.message) {
485					console.debug(e);
486				} else {
487					console.error(e);
488				}
489				// TypeError, FetchError, AbortError, TimeoutError ... 어떤 에러든 원본 이미지로 대체
490				imgElement.src = imgElement.dataset.original || imgElement.src;
491				imgElement.classList.add('loaded');
492				imgElement.removeAttribute('height');
493			} finally {
494				if (abortTimer) {
495					clearTimeout(abortTimer);
496				}
497			}
498		}));
499
500		for ( const job of loadingImageJobs ) {
501			await job;
502		}
503		console.timeEnd('loadTemplate')
504		return node;
505	}
506
507    /*
508    option 샘플
509    initDetail({
510        prod_idx: ,
511        prod_price: ,
512        require_option_count: ,
513		use_image_optimizer_on_product_detail_markup: ,
514        shop_use_full_load: ,
515        shop_view_tab_display: ,
516        is_site_page: ,
517        prod_type: ,
518        is_prod_detail_page: ,
519        is_price_view_permission: ,
520        cm_style: ,
521        section_code:
522        exist_color_option:
523        is_empty_color_option_images:
524        is_prod_wish_by_member,
525        wish_count,
526        is_use_wish_btn,
527        is_show_wish_count,
528        is_soldout,
529        is_possible_use_gift
530    });
531    */
532    var initDetail = function (_option) {
533        options = $.extend({}, default_options, _option);
534
535	    if(typeof options.shop_view_tab_display === 'undefined') {
536		    options.shop_view_tab_display = options.shop_pc_tab_type_one_page === 'Y' ? 'Y' : 'N';
537	    }
538	    if(typeof options.shop_tab_fixed === 'undefined') {
539		    options.shop_tab_fixed = options.shop_tab_type_unfixed !== 'Y' ? 'Y' : 'N';
540	    }
541
542        var prodIdx = options.prod_idx;
543				var prodCode = options.prod_code;
544        var price = options.prod_price;
545        var requireOptionCnt = options.require_option_count;
546        var requireInputOptionCnt = options.require_input_option_count;
547        use_lazy_load = options.use_image_optimizer_on_product_detail_markup;
548        tab_type = options.shop_view_tab_display;
549        var is_site_page = options.is_site_page;
550        var _prod_type = options.prod_type;
551        var is_prod_detail_page = options.is_prod_detail_page;
552        var view_price = options.is_price_view_permission;
553        var _cm_data = options.cm_style;
554        section_code = options.section_code;
555        prod_edit_time = options.prod_edit_time;
556        prod_deliv_hash = options.prod_deliv_hash;
557        shop_view_style = options.shop_view_style;
558        first_tab = options.first_tab;
559        use_tab_list = options.use_tab_list;
560        selected_shipping_template_code = options.shipping_template_code || '';
561
562        window_width = window.innerWidth;
563        is_mobile_width = (window_width < SHOP_CONST.WIDTH_MOBILE);
564		use_cdn_optimized = options.use_cdn_optimized;
565
566        $body = $('body');
567
568        if (is_prod_detail_page) {
569            var $target_modal = $('.modal_prod_detail_from_shopping_list');
570            $prod_image_list = $target_modal.find('#prod_image_list');
571            $prod_image_list_rolling = $prod_image_list.find('div.owl-carousel');
572            $prod_goods_form = $target_modal.find('#prod_goods_form');
573            $selected_options = $target_modal.find('#prod_selected_options');
574            $options = $target_modal.find('#prod_options');
575            $prod_additional = $target_modal.find('#prod_additional');
576            $prod_additional_options = $target_modal.find('#prod_additional_options');
577            $prod_additional_options_default = $prod_additional_options; // 기본 위치 저장
578            $prod_deliv_setting = $target_modal.find('#prod_deliv_setting');
579            $prod_detail = $target_modal.find('#prod_detail');
580            $prod_detail_content_pc = $prod_detail.find('._prod_detail_detail_lazy_load');
581            $prod_detail_content_mobile = $target_modal.find('._prod_detail_detail_lazy_load_mobile');
582            $prod_detail_content_tab_mobile = $target_modal.find('.categorize-mobile .site_prod_nav_wrap');
583            $add_cart_alarm = $target_modal.find('#shop_detail_add_cart_alarm');
584            $add_cart_alarm_with_free_ship_notify = $target_modal.find('#shop_detail_add_cart_alarm_with_free_ship_notify');
585            $add_cart_alarm_with_product_recommendation = $target_modal.find('#shop_detail_add_cart_alarm_with_product_recommendation');
586            $confirm_order_with_cart_alarm = $target_modal.find('#shop_detail_confirm_order_with_cart_alarm');
587            if ($add_cart_alarm.length === 0) {
588                $add_cart_alarm = $('#shop_detail_add_cart_alarm');
589            }
590            if ($add_cart_alarm_with_free_ship_notify.length === 0) {
591                $add_cart_alarm_with_free_ship_notify = $('#shop_detail_add_cart_alarm_with_free_ship_notify');
592            }
593            if ($add_cart_alarm_with_product_recommendation.length === 0) {
594                $add_cart_alarm_with_product_recommendation = $('#shop_detail_add_cart_alarm_with_product_recommendation');
595            }
596            if ($confirm_order_with_cart_alarm.length === 0) {
597                $confirm_order_with_cart_alarm = $('#shop_detail_confirm_order_with_cart_alarm');
598            }
599            $deliv_visit_wrap = $target_modal.find('#deliv_visit_wrap');
600	        $wish_buttons = $target_modal.find('._wish_button');
601        } else {
602            $prod_image_list = $('#prod_image_list');
603            $prod_image_list_rolling = $('#prod_image_list').find('div.owl-carousel');
604            $prod_goods_form = $('#prod_goods_form');
605            $selected_options = $('#prod_selected_options');
606            $options = $('#prod_options');
607            $prod_additional = $('#prod_additional');
608	          $prod_additional_options = $('#prod_additional_options');
609            $prod_additional_options_default = $prod_additional_options; // 기본 위치 저장
610            $prod_deliv_setting = $('#prod_deliv_setting');
611            $prod_detail = $('#prod_detail');
612            $prod_detail_content_pc = $prod_detail.find('._prod_detail_detail_lazy_load');
613            $prod_detail_content_mobile = $('._prod_detail_detail_lazy_load_mobile');
614            $prod_detail_content_tab_mobile = $('.categorize-mobile .site_prod_nav_wrap');
615            $add_cart_alarm = $('#shop_detail_add_cart_alarm');
616            $add_cart_alarm_with_free_ship_notify = $('#shop_detail_add_cart_alarm_with_free_ship_notify');
617            $add_cart_alarm_with_product_recommendation = $('#shop_detail_add_cart_alarm_with_product_recommendation');
618            $confirm_order_with_cart_alarm = $('#shop_detail_confirm_order_with_cart_alarm');
619            $deliv_visit_wrap = $('#deliv_visit_wrap');
620	        $wish_buttons = $('._wish_button');
621        }
622
623        $prod_detail_return = $("#prod_detail_return_body");
624        $prod_detail_return_mobile = $("#prod_detail_return_body_mobile");
625
626        // 서버 렌더링된 초기 데이터 읽기 (data 속성)
627        var $item_detail_wrap = $prod_detail.find('._item_detail_wrap');
628        if ($item_detail_wrap.length && $item_detail_wrap.data('deliv-type')) {
629            $deliv_type = $item_detail_wrap.data('deliv-type');
630            $deliv_pay_type = $item_detail_wrap.data('deliv-pay-type') || '';
631            $deliv_country = $item_detail_wrap.data('deliv-country') || '';
632            $('._free_ship_notify_detail_magnet_wrap').toggleClass('tw-hidden', $('._deliv_country_selector').length > 0 && $deliv_country !== 'KR');
633            $('.btn-popover').popover();
634            $('.html-popover').popover({html: true});
635            // 환불 데이터 → 반품/교환 탭 렌더링
636            if ($item_detail_wrap.data('refund')) {
637                last_refund_data = $item_detail_wrap.data('refund');
638                setDetailReturnHtml();
639            }
640        }
641
642        $first_photo_review_wrap = $prod_detail.find('._first_photo_review_wrap');
643        $review_summary_wrap = $prod_detail.find('._review_summary_wrap');
644        $review_summary_wrap_mobile = $prod_detail.find('._review_summary_wrap_mobile');
645        safeSetProdDetailAnalyticsTargets(
646            $body,
647            $prod_detail_content_pc,
648            $prod_detail_content_mobile,
649            function () {
650                return is_mobile_width;
651            }
652        );
653        if (options.only_regularly) {
654            if (!$prod_detail.hasClass('shop-style-b')) selectCartType('regularly');
655        }
656
657
658        is_view_price = view_price;
659        current_prod_idx = prodIdx;
660        current_prod_code = prodCode;
661        prod_price = price;
662        free_ship_notify_deliv_price_cost_type = options.free_ship_notify_deliv_price_cost_type || 'before_sale';
663        free_ship_notify_prod_regular_price = getFreeShipNotifyRegularPrice(options.free_ship_notify_prod_regular_price, prod_price);
664        require_option_count = parseInt(requireOptionCnt);
665        require_input_option_count = parseInt(requireInputOptionCnt);
666	      selected_prod_additionals = [];
667        selected_options = [];
668	      selected_prod_additional_options = [];
669	      free_ship_notify_prod_additional_regular_price_map = {};
670        selected_require_options = [];
671	      selected_prod_additional_require_options = [];
672	      order_count = 0;
673				prod_additional_require_option_count = {};
674				prod_additional_require_input_option_count = {};
675	    prod_additional_period_discount_data = {};
676	    prod_additional_period_dc_type = {};
677	    current_select_additional_prod_idx = 0;
678        isComplete = true;
679        //isUseNpMobile = use_np_mobile;
680        cm_data = JSON.parse(_cm_data);
681
682        // 옵션 없는 상품(require_option_count == 0)은 사용자가 옵션/수량을 건드리기 전까지
683        // updateSelectedOptions 가 호출되지 않아 무료배송 안내 MFE 가 초기 가격을 받지 못한다.
684        // DOM 갱신은 필요 없으므로 SDK dispatch 만 분리 호출하여 첫 렌더부터 무료배송 기준 금액이 반영되도록 한다.
685        if (require_option_count == 0) {
686            notifyFreeShipNotifySdk(getFreeShipNotifyInitialTotal());
687        }
688
689		// 그러니까 이 값들은, 상품 상세페이지 내 기획전 위젯의 cart 버튼을 클릭하여 모달을 띄우는 동작에서
690		// 모달이 닫힐 시 설정 값의 초기화를 위해 사용하는 것이다.
691        if (!is_prod_detail_page) {
692            prod_idx_org = current_prod_idx;
693						prod_code_org = current_prod_code;
694            prod_price_org = prod_price;
695            require_option_count_org = require_option_count;
696            use_lazy_load_org = use_lazy_load;
697            tab_type_org = tab_type;
698            is_site_page_org = is_site_page;
699            prod_type_org = _prod_type;
700        }
701
702        if (options.is_use_wish_btn) {
703            toggleWishButtons($wish_buttons, options.wish_count, options.is_show_wish_count, options.is_prod_wish_by_member);
704        }
705
706        /* 하나의 페이지에 모든 탭 열람일 경우 상품 상세설명 HTML 세팅 */
707        // html설정
708        var is_shop_view_tab_display = (options.shop_view_tab_display == 'Y');
709
710        if (is_shop_view_tab_display) {
711            let detail
711_html = null;
712            let $seemore_wrap = null;
713
714			if (use_cdn_optimized) {
715				if (is_mobile_width) {
716				Promise.resolve(loadTemplate('prodDetailMobile', $prod_detail_content_mobile, use_lazy_load ? setImageWidthHeightBeforeLoad : null))
717					.then(function() {
718						safeScheduleProdDetailBodyExpandedHeightUpdate();
719					});
720			} else {
721				Promise.resolve(loadTemplate('prodDetailPC', $prod_detail_content_pc, use_lazy_load ? setImageWidthHeightBeforeLoad : null))
722					.then(function() {
723						safeScheduleProdDetailBodyExpandedHeightUpdate();
724					});
725			}
726		} else {
727			detail_html = IMWEB_TEMPLATE.loadSimple("prodDetailPC");
728			$prod_detail_content_pc.html(detail_html);
729			detail_html = IMWEB_TEMPLATE.loadSimple("prodDetailMobile");
730			$prod_detail_content_mobile.html(detail_html);
731			safeScheduleProdDetailBodyExpandedHeightUpdate();
732		}
733
734            $seemore_wrap = $body.find('._seemore_wrap');
735
736            if ($seemore_wrap.length > 0) {
737                $seemore_wrap.show();
738                $prod_detail_content_pc.toggleClass('hide_seemore', false);
739	            $prod_detail_content_mobile.toggleClass('hide_seemore', false);
740            }
741        }
742
743		if (!use_cdn_optimized) {
744
745			runLazyload();
746		}
747
748        prod_type = _prod_type;
749
750        var hash_temp = location.hash.split('!/');
751
752	    setTimeout(() => {
753		    switch(hash_temp[0]){
754			    case '#prod_detail_detail':
755				    is_init_detail = true;
756				    if(is_mobile_width) SITE_SHOP_DETAIL.changeContentTab(SHOP_CONST.TAB_TYPE.DETAIL);
757				    else SITE_SHOP_DETAIL.changeContentPCTab(SHOP_CONST.TAB_TYPE.DETAIL);
758				    break;
759			    case '#prod_detail_review':
760				    is_init_detail = true;
761				    if(is_mobile_width) SITE_SHOP_DETAIL.changeContentTab(SHOP_CONST.TAB_TYPE.REVIEW);
762				    else SITE_SHOP_DETAIL.changeContentPCTab(SHOP_CONST.TAB_TYPE.REVIEW);
763				    if(hash_temp[1]){
764					    SITE_SHOP_DETAIL.viewReviewDetail(hash_temp[1], 1, 'N', 'Y');
765				    }
766				    break;
767			    case '#prod_detail_qna':
768				    is_init_detail = true;
769				    if(is_mobile_width) SITE_SHOP_DETAIL.changeContentTab(SHOP_CONST.TAB_TYPE.QNA);
770				    else SITE_SHOP_DETAIL.changeContentPCTab(SHOP_CONST.TAB_TYPE.QNA);
771				    if(hash_temp[1]){
772					    SITE_SHOP_DETAIL.viewQnaDetail(hash_temp[1], 1, 'Y');
773				    }
774				    break;
775			    case '#prod_detail_return':
776				    is_init_detail = true;
777				    if(is_mobile_width) SITE_SHOP_DETAIL.changeContentTab(SHOP_CONST.TAB_TYPE.RETURN);
778				    else SITE_SHOP_DETAIL.changeContentPCTab(SHOP_CONST.TAB_TYPE.RETURN);
779				    break;
780			    default:
781
782				    let target_tab = options.first_tab === "prod_detail"
783						    ? SHOP_CONST.TAB_TYPE.DETAIL
784						    : options.first_tab === "prod_review"
785								    ? SHOP_CONST.TAB_TYPE.REVIEW
786								    : options.first_tab === "prod_qna"
787										    ? SHOP_CONST.TAB_TYPE.QNA
788										    : options.first_tab === "prod_return"
789												    ? SHOP_CONST.TAB_TYPE.RETURN
790												    : "";
791
792				    if(options.shop_view_tab_display === "N"){
793					    if(is_mobile_width) {
794						    SITE_SHOP_DETAIL.changeContentTab(target_tab, review_page, qna_page, false, false);
795					    } else {
796						    SITE_SHOP_DETAIL.changeContentPCTab(target_tab, review_page, qna_page, false, false);
797					    }
798				    }
799
800				    checkSeemore(target_tab);
801				    is_init_detail = true;
802				    break;
803		    }
804
805		    /* 크리마 리뷰 사용 중일 시 위젯 호출 */
806		    if(document.querySelector(".crema-product-reviews")){
807			    if(typeof crema !== 'undefined' && typeof crema.run === 'function'){
808				    crema.run();
809			    }
810		    }
811	    }, 200)
812
813        if (cm_data.shop_view_buy_item_tooltip === 'Y') {
814            $('.btn_tooltip[data-toggle="tooltip"]').tooltip({
815                delay: { show: 500, hide: 1000000 }
816            });
817            var toggle_regularly = $("input[name='add_cart_type']");
818            var $tooltip = $('.btn_tooltip[data-toggle="tooltip"]');
819            var window_height = $(window).height();
820            toggle_regularly.off('change').on('change', function () {
821                $tooltip.css({ 'opacity': '0' });
822                if ($(this).prop('checked')) {
823                    if ($tooltip.length > 0) {
824                        var window_height = $(window).height();
825                        $.each($tooltip, function (k, v) {
826                            var tooltip_y = v.getBoundingClientRect().y;
827                            if (tooltip_y >= 0 && tooltip_y < window_height) {
828                                $tooltip.css({ 'opacity': '1' });
829                                $(v).tooltip('show');
830                            }
831                        });
832                    }
833                }
834            });
835            if ($tooltip.length > 0) {
836                $.each($tooltip, function (k, v) {
837                    var tooltip_y = v.getBoundingClientRect().y;
838                    if (tooltip_y >= 0 && tooltip_y < window_height) {
839                        // 툴팁이 화면 내에 보이지 않을 때 show하면 위치가 어긋나게 됨
840                        setTimeout(function () {
841                            $(v).tooltip('show');
842                        }, 100);
843                    }
844
845                    $(window).scroll(function () {
846                        if ($(v).next('.tooltip:visible').length === 0) {
847                            var tooltip_y = v.getBoundingClientRect().y;
848                            if (tooltip_y > 0 && tooltip_y < (window_height - $(v).height())) {
849                                $(v).tooltip('show');
850                            }
851                        }
852                    });
853                });
854            }
855        }
856
857        setTimesale();
858        if (!is_prod_detail_page) {
859            setImgZoom(30, 800, section_code);
860
861            if ($first_photo_review_wrap.length > 0) {
862                getFirstPhotoReview(1);
863            }
864        }
865
866        /* resize 시 window_width 재계산 */
867        let is_resizing = false;
868        window.addEventListener('resize', function () {
869            if (!is_resizing) {
870                is_resizing = true;
871                setTimeout(function () {
872                    window_width = window.innerWidth;
873					let was_mobile_width = is_mobile_width;
874                    is_mobile_width = (window_width < SHOP_CONST.WIDTH_MOBILE);
875					if(is_mobile_width != was_mobile_width){
876						if(is_mobile_width){
877							changeContentTab(current_content_tab);
878						}else{
879							/* 동영상 전체화면 시 PC 너비로 인지되는데 탭을 새로 그리면서 동영상이 다시 불러와지는 문제가 있어 예외처리 */
880							if (!is_shop_view_tab_display && current_content_tab === SHOP_CONST.TAB_TYPE.REVIEW) {
881								changeContentPCTab(current_content_tab);
882							}
883						}
884						// 상단 조치로 인해 lazyload가 다시 실행되지 않아서 추가
885						setTabHistory(current_content_tab);
886						runLazyload();
887					}
888                    is_resizing = false;
889                }, 100);
890            }
891        });
892
893        /* debounce로 변경 */
894		// const handleResize = _.debounce(function () {
895		// 	window_width = window.innerWidth;
896		// 	let was_mobile_width = is_mobile_width;
897		// 	is_mobile_width = (window_width < SHOP_CONST.WIDTH_MOBILE /* 768w */);
898		// 	if(is_mobile_width != was_mobile_width){
899		// 		if(is_mobile_width){
900		// 			changeContentTab(current_content_tab);
901		// 		}else{
902		// 			/* 동영상 전체화면 시 PC 너비로 인지되는데 탭을 새로 그리면서 동영상이 다시 불러와지는 문제가 있어 예외처리 */
903		// 			if (!is_shop_view_tab_display && current_content_tab === SHOP_CONST.TAB_TYPE.REVIEW) {
904		// 				changeContentPCTab(current_content_tab);
905		// 			}
906		// 		}
907		// 		// 상단 조치로 인해 lazyload가 다시 실행되지 않아서 추가
908		// 		setTabHistory(current_content_tab);
909		// 		// runLazyload();
910		// 	}
911        // }, 100)
912
913        // window.addEventListener('resize', handleResize);
914
915        _hasFreeShipNotifyInlineMagnet = !!document.querySelector('.foFreeShipNotifyMagnet');
916
917        _initDetailDone = true;
918        if (isInSalesToolPreview && typeof _handleSalesToolPreviewMessage === 'function') {
919            while (_pendingSalesToolPreviewMessages.length > 0) {
920                _handleSalesToolPreviewMessage(_pendingSalesToolPreviewMessages.shift());
921            }
922        }
923        notifySalesToolPreviewReady();
924    };
925
926    var initProdStock = function (stock_use, stock, stock_un_limit) {
927        prod_stock_use = stock_use;
928        prod_stock = stock;
929        prod_stock_unlimit = stock_un_limit;
930    };
931
932    var runLazyload = function () {
933        if (!use_lazy_load)
934            return false;
935
936        var $seemore_wrap = $body.find('._seemore_wrap');
937        // [TFR-1179] 한 페이지에 전체 노출 + 순차적 로드 + 상세정보 펼쳐보기 미사용 시 순차적 로드에 의해 다른 탭 스크롤이 밀리는 현상 방지를 위해 마지막 이미지는 레이지 로드 제외
938        let is_except_last_lazy_load = options.shop_view_tab_display === 'Y' && $seemore_wrap.length == 0;
939		if(is_mobile_width){
940            let lazy_load_selector = 'img';
941            if(is_except_last_lazy_load){
942                let last_img = $prod_detail_content_mobile.find('img:last');
943                last_img.attr('src', last_img.attr('data-original')).removeAttr('height');
944                lazy_load_selector = 'img:not(:last)';
945            }
946			$prod_detail_content_mobile.find(lazy_load_selector).lazyload({		/* 상품 상세페이지 lazy load 적용, 기본 /img/no-image.png 는 한번만 불러옴 */
947				placeholder: NO_IMAGE_URL,
948				threshold: 100,
949				effect: "fadeIn",
950				effect_speed: 50, // fadeIn 시간 단축(기본 400ms→50ms)
951				load: function () {
952					$(this).removeAttr('height');
953					if ($seemore_wrap.length > 0) {
954						// 레이지로드 이후 no-image보다 로딩된 이미지가 작아 펼쳐보기 높이 제한에 걸리지 않게 되면 펼쳐보기 해제
955						setTimeout(function () {
956							if ($prod_detail_content_mobile.outerHeight() < SHOP_CONST.SEEMORE_HEIGHT.MOBILE) {
957								$prod_detail_content_mobile.toggleClass('hide_seemore', true);
958								checkSeemore(current_content_tab);
959							}
960						safeScheduleProdDetailBodyExpandedHeightUpdate();
961						}, 100);
962					}
963				}
964			});
965		}else{
966            let lazy_load_selector = 'img';
967            if(is_except_last_lazy_load){
968                let last_img = $prod_detail_content_pc.find('img:last');
969                last_img.attr('src', last_img.attr('data-original')).removeAttr('height');
970                lazy_load_selector = 'img:not(:last)';
971            }
972			$prod_detail_content_pc.find(lazy_load_selector).lazyload({		/* 상품 상세페이지 lazy load 적용, 기본 /img/no-image.png 는 한번만 불러옴 */
973				placeholder: NO_IMAGE_URL,
974				threshold: 100,
975				effect: "fadeIn",
976				effect_speed: 50, // fadeIn 시간 단축(기본 400ms→50ms)
977				load: function () {
978					$(this).removeAttr('height');
979					if ($seemore_wrap.length > 0) {
980						// 레이지로드 이후 no-image보다 로딩된 이미지가 작아 펼쳐보기 높이 제한에 걸리지 않게 되면 펼쳐보기 해제
981						setTimeout(function () {
982							if ($prod_detail_content_pc.outerHeight() < SHOP_CONST.SEEMORE_HEIGHT.PC) {
983								$prod_detail_content_pc.toggleClass('hide_seemore', true);
984								checkSeemore(current_content_tab);
985							}
986						safeScheduleProdDetailBodyExpandedHeightUpdate();
987						}, 100);
988					}
989				}
990			});
991		}
992    };
993
994    var setImgZoom = function (margin, max_width, section_code) {
995        if ($('.shop_view .xzoom').length > 0) {
996            // 메인 이미지에 object-fit 크롭이 적용된 경우(원본 경로 서빙) xzoom 전환 페이드 고스트가
997            // 크롭 없이 fill 로 복제되어 왜곡되므로, 이 경우에만 fadeTrans 를 비활성화한다.
998            // 기본값 fill(기존 사이트)에서는 fadeTrans: true 로 기존 크로스페이드 동작 유지.
999            var is_cover_fit_main = ($('.shop_view .xzoom').css('object-fit') || 'fill') !== 'fill';
1000            var shop_view_xzoom = $('.shop_view .xzoom, .shop_view .xzoom-gallery').xzoom({ Xoffset: margin, openOnSmall: false, defaultScale: 0, scroll: false, custom: true, hover: true, fadeTrans: !is_cover_fit_main });
1001            if (typeof shop_view_xzoom != 'undefined') {
1002                // 연관상품 장바구니 확인 후 닫을 시 재실행되면서 오류나는 문제 방지
1003                shop_view_xzoom.eventmove = function (element) {
1004                    var $xzoom_preview = $('.shop_view .xzoom-preview');
1005                    $xzoom_preview.addClass(section_code);
1006                    var $xzoom_preview_img = $xzoom_preview.find('img');
1007                    var $xzoom = $('.shop_view .xzoom');
1008                    var xzoom_width = $(window).width() - ($xzoom.offset().left + $xzoom.outerWidth() + margin * 2);
1009                    if (xzoom_width > max_width) {
1010                        xzoom_width = max_width;
1011                    }
1012                    var xzoom_height = xzoom_width;
1013                    var xzoom_preview_img_width = $xzoom_preview_img.outerWidth();
1014                    var xzoom_preview_img_height = $xzoom_preview_img.outerHeight();
1015                    if (xzoom_preview_img_width < xzoom_width || xzoom_preview_img_height < xzoom_height) {
1016                        shop_view_xzoom.closezoom();
1017                    } else {
1018                        $xzoom_preview.outerWidth(xzoom_width);
1019                        $xzoom_preview.outerHeight(xzoom_height);
1020                        element.bind('mousemove', shop_view_xzoom.movezoom);
1021                    }
1022                    $xzoom_preview.mouseover(shop_view_xzoom.closezoom);
1023                };
1024            }
1025        }
1026    };
1027
1028    var setTimesale = function () {
1029        var $doz_timesale_wrap = $('#prod_goods_form ._doz_timesale_wrap');
1030        if ($doz_timesale_wrap.length > 0) {
1031            $doz_timesale_wrap.each(function () {
1032                var $that = $(this);
1033                var $doz_timesale = $that.find('._doz_timesale');
1034                var start_time = ($that.find('._doz_timesale').attr('data-start-time') * 1000);
1035                var timesale_interval = setInterval(function () {
1036                    var remain_ms = ($doz_timesale.attr('data-end-time') * 1000) - start_time;
1037                    if (remain_ms > 0) {
1038                        var remain_d = Math.floor(remain_ms / 86400000);
1039                        var remain_h = Math.floor((remain_ms % 86400000) / 3600000);
1040                        var remain_m = Math.floor((remain_ms % 3600000) / 60000);
1041                        var remain_s = Math.floor((remain_ms % 60000) / 1000);
1042
1043                        var remain_hh = remain_h < 10 ? '0' + remain_h : '' + remain_h;
1044                        var remain_mm = remain_m < 10 ? '0' + remain_m : '' + remain_m;
1045                        var remain_ss = remain_s < 10 ? '0' + remain_s : '' + remain_s;
1046
1047                        if (remain_d >= 1) {
1048	                        $doz_timesale.html(getLocalizeString('설명_상세페이지타임세일종료까지n1일n2시n3분n4초남음', [remain_d, remain_hh, remain_mm, remain_ss], "<label class='text-bold text-brand'>타임세일</span> 종료까지 <strong>%1일 %1:%2:%3</strong> 남음"));
1049                        } else {
1050                            $doz_timesale.html(getLocalizeString('설명_상세페이지타임세일종료까지n1시n2분n3초남음', [remain_hh, remain_mm, remain_ss], "<label class='text-bold text-brand'>타임세일</span> 종료까지 <strong>%1:%2:%3</strong> 남음"));
1051                        }
1052                        start_time = start_time + 1000;
1053                    } else {
1054                        /* 타임세일 종료 */
1055                        clearInterval(timesale_interval);
1056                        $that.remove();
1057                    }
1058                }, 1000);
1059            });
1060        }
1061    };
1062
1063    /**
1064     * 다국어코드를 백엔드에서 호출하여 자바스크립트에 저장하는 함수
1065     * initDetail 피라미터 개수가 다른 히스토리를 모르겠어서 일단 따로 만들었음
1066     * @param code
1067     */
1068    var initLocalize = function (code) {
1069        total_price_localize_text = code;
1070    };
1071
1072    /**
1073     * 특정상품 위시리스트 추가 처리
1074     * @param prod_code
1075     */
1076    var addProdWish = function (prod_code, back_url) {
1077        var back_url = typeof back_url == 'undefined' ? '' : back_url;
1078        $.ajax({
1079            type: 'POST',
1080            data: { 'prod_code': prod_code },
1081            url: ('/shop/add_prod_wish.cm'),
1082            dataType: 'json',
1083            success: function (res) {
1084                if (res.msg == 'SUCCESS') {
1085                    if (res.res == 'add') {
1086                        if (typeof FB_PIXEL != 'undefined') FB_PIXEL.AddToWishlist();
1087                        if (typeof CHANNEL_PLUGIN != "undefined") CHANNEL_PLUGIN.AddToWishlist();
1088	                    if (typeof NP_NDA != 'undefined') NP_NDA.AddToWishList(prod_code);
1089                        if (typeof TIKTOK_PIXEL != 'undefined') {
1090                            // TIKTOK_PIXEL.track('AddToWishlist', {
1091                            //     contents: [
1092                            //         {
1093                            //             content_id: options.prod_idx,
1094                            //             content_name: res.prod_name,
1095                            //             quantity: res.wish_cnt,
1096                            //             price: options.prod_price
1097                            //         },
1098                            //     ],
1099                            //     content_type: 'product',
1100                            //     value: options.prod_price,
1101                            //     currency: res.currency,
1102                            // });
1103                        }
1104
1105                        if ($wish_buttons) {
1106                            toggleWishButtons($wish_buttons, res.wish_cnt, options.is_show_wish_count, true);
1107                        }
1108                        if ($(".wish-icon-" + prod_code).length > 0) {
1109                            $(".wish-icon-" + prod_code).removeClass('im-ico-like');
1110                            $(".wish-icon-" + prod_code).addClass('im-ico-liked');
1111                        }
1112                    } else if (res.res == 'delete') {
1113                        if ($wish_buttons) {
1114                            toggleWishButtons($wish_buttons, res.wish_cnt, options.is_show_wish_count, false);
1115                        }
1116                        if ($(".wish-icon-" + prod_code).length > 0) {
1117                            $(".wish-icon-" + prod_code).removeClass('im-ico-liked');
1118                            $(".wish-icon-" + prod_code).addClass('im-ico-like');
1119                        }
1120                        if (typeof CHANNEL_PLUGIN != "undefined") CHANNEL_PLUGIN.addCountUserProfileAttr('wishCount', -1);
1121                    }
1122                    if ($(".wish-text-" + prod_code).length > 0) {
1123                        $(".wish-text-" + prod_code).text(res.wish_cnt);
1124                    }
1125                } else if (res.msg === 'NON_MEMBERS') {
1126                    SITE_MEMBER.openLogin(back_url);
1127                } else {
1128                    alert(res.msg);
1129                }
1130            }
1131        });
1132    };
1133    /**
1134     * 특정상품 위시정보 가져오기 (내 위시여부,위시갯수)
1135     * @param prod_code
1136     */
1137    var getProdWish = function (prod_code) {
1138        $.ajax({
1139            type: 'POST',
1140            data: { 'prod_code': prod_code },
1141            url: ('/shop/get_prod_wish.cm'),
1142            dataType: 'json',
1143            success: function (res) {
1144                if (res.msg == 'SUCCESS') {
1145                } else
1146                    alert(res.msg);
1147            }
1148        });
1149    };
1150    /**
1151     * 두개의 옵션 데이터가 같은지 확인 (순서 무관)
1152     * @param options1 [ {option_code: option_value} ]
1153     * @param options2 [ {option_code: option_value} ]
1154     */
1155    var isSameOptionList = function (options1, options2) {
1156        if (options1.length == options2.length) {
1157            var isSame = true;
1158            var exist = false;
1159            $.each(options1, function (no, data) {
1160                exist = false;
1161                $.each(options2, function (no2, data2) {
1162                    if (data.option_code == data2.option_code) {
1163                        if (data.value_code != '') {
1164                            if (data.value_code == data2.value_code) {
1165                                exist = true;
1166                                return false;
1167                            }
1168                        } else {
1169                            if (data.value_name == data2.value_name) {
1170                                exist = true;
1171                                return false;
1172                            }
1173                        }
1174                    }
1175                });
1176                if (!exist) {
1177                    isSame = false;
1178                    return false;
1179                }
1180            });
1181            return isSame;
1182        } else {
1183            return false;
1184        }
1185    };
1186
1187    /**
1188     * 상품 이미지 롤링 시작
1189     */
1190    var startProdImageRolling = function (autoWidth) {
1191
1192        var items = $prod_image_list.find('._item');
1193        var is_dots = false;
1194        is_dots = items.length > 1 ? true : false;
1195
1196        if (is_dots) {
1197            switch (cm_data.paging_style_type) {
1198                case 'st00':
1199                    switch (cm_data.paging_default_style_type) {
1200                        case 'st00':
1201                            $prod_image_list_rolling.toggleClass('paging_type_dot paging_type_dot01', true);
1202                            break;
1203                        case 'st01':
1204                            $prod_image_list_rolling.toggleClass('paging_type_dot paging_type_dot02', true);
1205                            break;
1206                    }
1207                    break;
1208                case 'st01':
1209                    $prod_image_list_rolling.toggleClass('paging_type_big_dot', true);
1210                    break;
1211                case 'st02':
1212                    $prod_image_list_rolling.toggleClass('paging_type_line', true);
1213                    break;
1214                case 'st03':
1215                    switch (cm_data.paging_active_style_type) {
1216                        case 'st00':
1217                            $prod_image_list_rolling.toggleClass('paging_type_count paging_type_count01', true);
1218                            break;
1219                        case 'st01':
1220                            $prod_image_list_rolling.toggleClass('paging_type_count paging_type_count02', true);
1221                            break;
1222                        case 'st02':
1223                            $prod_image_list_rolling.toggleClass('paging_type_count paging_type_count03', true);
1224                            break;
1225                    }
1226                    break;
1227            }
1228        }
1229
1230        if (autoWidth) {
1231            $prod_image_list_rolling.owlCarousel({
1232                dots: is_dots,
1233                navigation: true,
1234                slideSpeed: 300,
1235                paginationSpeed: 400,
1236                singleItem: true,
1237                animateOut: 'fadeOut',
1238                items: 1,
1239                autoHeight: true,
1240                autoWidth: true,
1241                onInitialized: function () {
1242                    var owl_item_list = $prod_image_list.find('.owl-item');
1243                    var first_url = $(items[0]).find('img').attr('src');
1244                    var max_width = 750;
1245                    // TODO: is_prod_detail_page가 명확하지 않아 장바구니 임시조치
1246                    // shopping_list에서 들어오면 400으로 잡아야 함
1247                    if ($('.modal_prod_detail_from_shopping_list').length > 0) {
1248                        var max_width = 400;
1249                    } else if ($prod_detail.width() < max_width) {
1250                        max_width = $prod_detail.width();
1251                    }
1252                    $.each(owl_item_list, function (key, value) {
1253                        var $value = $(value);
1254                        var image_url = $value.find('img').attr('src');
1255                        var image = new Image();
1256                        image.onload = function () {
1257                            var that = this;
1258                            // owl initialized 이후 내부적으로 resized를 하면서 크기가 어긋나는 경우가 있어 timeout을 둠
1259                            setTimeout(function () {
1260                                var org_width = that.width;
1261                                var org_height = that.height;
1262                                var width = org_width > max_width ? max_width : org_width;
1263                                var height = width * (org_height / org_width);
1264                                const new_width = getComputedStyle($prod_image_list[0]).margin == '0px -15px' ? width + 30 : width;
1265                                const new_height = getComputedStyle($prod_image_list[0]).margin == '0px -15px' ? height - 30 : height;
1266                                $value.css({ 'width': new_width + 'px', 'height': new_height + 'px' });
1267                                if (that.src === first_url) {
1268                                    // 첫 이미지 크기 세팅
1269                                    $prod_image_list.find('.owl-carousel').css({ 'width': `${new_width}px`, 'height': `${new_height}px` });
1270                                    $prod_image_list.find('.owl-height').css({ 'height': `${new_height}px` });
1271                                }
1272                            }, 5);
1273                        };
1274                        image.src = image_url;
1275                    });
1276                },
1277                onChanged: function () {
1278                    var owl = $prod_image_list_rolling.data('owlCarousel');
1279                    var current = 0;
1280                    if (typeof owl !== "undefined") current = owl._current;
1281                    var li_list = $prod_image_list.find('li.owl-dot');
1282                    li_list.find('a').removeClass('active');
1283                    li_list.eq(current).find('a').addClass('active');
1284                    if (items.length > 1) {
1285                        $prod_goods_form.find('header').css('margin-top', 0);
1286                    }
1287                    // 현재 이미지 너비로 슬라이드 너비 수정
1288                    var current_width = $(items[current]).find('img').css('width');
1289                    $prod_image_list.find('.owl-carousel').css({ 'width': current_width, 'height': '' });
1290                }
1291            });
1292        } else {
1293            $prod_image_list_rolling.owlCarousel({
1294                dots: true,
1295                navigation: true,
1296                slideSpeed: 300,
1297                paginationSpeed: 400,
1298                singleItem: true,
1299                animateOut: 'fadeOut',
1300                items: 1,
1301                autoHeight: false,
1302                onInitialized: function () {
1303                    var owl_item_list = $prod_image_list.find('.owl-item');
1304                    var total = owl_item_list.length;
1305                    var html = '';
1306                    html += '<span class="_numbering_variable">1</span> <span class="_numbering_total">/ ' + total + '</span>';
1307                    if (total <= 1) {
1308                        $("._carousel_count_numbering").hide();
1309                    }
1310                    else {
1311                        $("._carousel_count_numbering").append(html);
1312                    }
1313                },
1314                onChanged: function () {
1315                    var owl = $prod_image_list_rolling.data('owlCarousel');
1316                    var current = 0;
1317                    if (typeof owl !== "undefined") current = owl._current;
1318                    var now = parseInt(current);
1319                    var html = '';
1320                    html += '<span class="_numbering_variable">' + (now + 1) + '</span>';
1321                    $("._numbering_variable").html(html);
1322                }
1323            });
1324        }
1325    };
1326
1327    /**
1328     * 상품 이미지 롤링 특정 위치 이동
1329     * @param no
1330     */
1331    function changeProdImageRolling(no) {
1332        var owl = $prod_image_list_rolling.data('owlCarousel');
1333        if (typeof owl !== "undefined") {
1334            owl.to(no);
1335
1336            var option_img_list = $prod_image_list.find('._color_option_img');
1337            if (option_img_list.length) {
1338                $prod_image_list.attr('data-preview-type', 'prod');
1339                option_img_list.attr('data-visible', 'false');
1340            }
1341        }
1342    }
1343
1344    const loadProductDetail = function (code, idx, menu_code, current_url) {
1345        $.ajax({
1346            type: 'POST',
1347            data: { 'idx': idx, 'menu_code': menu_code, current_url },
1348            url: ('/shop/load_product_detail.cm'),
1349            dataType: 'json',
1350            cache: false,
1351            success: function (result) {
1352                if (result.msg === 'SUCCESS') {
1353                    $('.shop_detail_wrap_csr_' + code).html(result.html);
1354
1355		                function injectHTMLWithScripts(html, target = document.body) {
1356			                const wrapper = document.createElement('div');
1357			                wrapper.innerHTML = html;
1358
1359			                // 먼저 script를 제외한 나머지 노드 삽입
1360			                const scripts = wrapper.querySelectorAll('script');
1361			                scripts.forEach(script => script.remove()); // 미리 제거
1362
1363			                // 나머지 HTML 삽입
1364			                Array.from(wrapper.childNodes).forEach(node => target.appendChild(node));
1365
1366			                // script 수동 실행
1367			                scripts.forEach(oldScript => {
1368				                const newScript = document.createElement('script');
1369
1370				                // 복사: src / type / defer / async 등
1371				                for (const attr of oldScript.attributes) {
1372					                newScript.setAttribute(attr.name, attr.value);
1373				                }
1374
1375				                // inline script일 경우 textContent 복사
1376				                if (!oldScript.src) {
1377					                newScript.textContent = oldScript.textContent;
1378				                }
1379
1380				                // 실행
1381				                document.body.appendChild(newScript);
1382			                });
1383		                }
1384
1385										if (result.script){
1386											injectHTMLWithScripts(result.script);
1387										}
1388
1389
1390	                // 최근 본 상품, refresh
1391										if (window.RECENT_PRODUCT && typeof window.RECENT_PRODUCT.refresh === 'function') {
1392											window.RECENT_PRODUCT.refresh();
1393										}
1394
1395										// 상세페이지 CSR 로드 완료
1396										document.dispatchEvent(new CustomEvent('productDetailLoaded', {detail: {idx}}));
1397								}
1398            },
1399            error: function (req, status, error) {
1400                document.write(req.responseText);
1401            }
1402        });
1403    };
1404
1405    const loadBookingDetail = function (code, idx, day, endDay, current_url) {
1406        $.ajax({
1407            type: 'POST',
1408            data: { 'idx': idx, 'day': day, 'endDay': endDay, current_url, 'widgetCode': code },
1409            url: ('/shop/load_booking_detail.cm'),
1410            dataType: 'json',
1411            cache: false,
1412            success: function (result) {
1413                if (result.msg === 'SUCCESS') {
1414                    $('.book_detail_wrap_csr_' + code).html(result.output);
1415                }
1416            }
1417        });
1418    };
1419
1420    const loadCode = function (code) {
1421        $.ajax({
1422            type: 'POST',
1423            data: { 'code': code },
1424            url: ('/shop/load_code.cm'),
1425            dataType: 'json',
1426            cache: false,
1427            success: function (result) {
1428                if (result.msg === 'SUCCESS') {
1429                    // DOM에 콘텐츠를 추가하는 함수
1430                    const appendContentToDOM = function() {
1431                        $('.code_wrap_csr_' + code).html(result.output);
1432
1433                        // script 태그를 찾아서 실행
1434                        const $container = $('.code_wrap_csr_' + code);
1435                        $container.find('script').each(function() {
1436                            const scriptContent = $(this).html();
1437                            if (scriptContent) {
1438                                try {
1439                                    // script 내용 실행
1440                                    setTimeout(function() {
1441                                        eval(scriptContent);
1442                                    }, 0);
1443                                } catch (e) {
1444                                    console.error('Script execution error:', e);
1445                                }
1446                            }
1447                        });
1448                    };
1449
1450                    // 다른 CSR 위젯이 로드되었는지 확인
1451                    const checkInterval = setInterval(function () {
1452                        const target_now = $('[class^="shop_detail_wrap_csr_"], [class^="book_detail_wrap_csr_"]');
1453                        const target = target_now.length > 0 ? target_now.first() : null;
1454
1455
1456                        if (!target) {
1457                            clearInterval(checkInterval); // 감지 종료
1458                            appendContentToDOM();
1459                            return;
1460                        }
1461
1462
1463                        if (target.is(':empty')) {
1464                            return;
1465                        }
1466
1467                        // prod_detail 요소가 있는지 확인
1468                        if (!target.find('#prod_detail').length) {
1469														return;
1470                        }
1471
1472                        clearInterval(checkInterval); // 감지 종료
1473                        appendContentToDOM();
1474                    }, 100); // 100ms 간격으로 검사
1475                }
1476            }
1477        });
1478    };
1479
1480    /**
1481     * 무료배송 안내 모달의 추가옵션 영역(#prod_free_ship_notify_additional_options)이
1482     * 현재 활성 상태인지 확인.
1483     * - PC cocoaDialog: 모달이 열려있어야(`.modal.in`) 활성으로 인정
1484     * - 모바일 imSheet: 닫힐 때 DOM에서 element 자체가 제거되므로 존재만으로 활성으로 간주
1485     * 모달이 닫혔지만 DOM에 잔존한 경우(PC에서 `in` 클래스만 빠진 상태) 일반 옵션 흐름과
1486     * 섞이지 않도록 분기 조건으로 사용한다.
1487     */
1488    var isFreeShipNotifyAdditionalActive = function () {
1489        var $el = $('#prod_free_ship_notify_additional_options');
1490        if (!$el.length) return false;
1491        var $modal = $el.closest('.modal');
1492        return !$modal.length || $modal.hasClass('in');
1493    };
1494
1495    /**
1496     * 장바구니 무료배송 안내 모달 전용 옵션 로더.
1497     * 기본 #prod_options 컨테이너가 없는 환경(cart 페이지 모달)에서 load_prod_additional_option.cm을 호출해
1498     * 모달 전용 컨테이너(#prod_free_ship_notify_additional_options)의 HTML을 갱신한다.
1499     * 최초 렌더와 동일한 템플릿을 재사용하여 스타일 일관성 유지 (loadProdAdditionalOptions 패턴 참조).
1500     */
1501    var loadOptionForFreeShipNotify = function (prod_idx) {
1502        if (_isFreeShipNotifyTemplatePreview) return;
1503        var $freeShipContainer = $('#prod_free_ship_notify_additional_options');
1504        if (!$freeShipContainer.length) return;
1505
1506        $.ajax({
1507            type: 'POST',
1508            data: {
1509                'prod_idx': prod_idx,
1510                'selected_require_options': selected_require_options,
1511                '__': prod_edit_time,
1512                'is_additional': true,
1513                'context': 'cart'
1514            },
1515            url: '/shop/load_prod_additional_option.cm',
1516            dataType: 'json',
1517            cache: false,
1518            success: function (result) {
1519                if (result.msg === 'SUCCESS') {
1520                    $freeShipContainer.html(result.option_html);
1521                } else {
1522                    alert(result.msg);
1523                }
1524            }
1525        });
1526    };
1527
1528    var loadOption = function (type, prod_idx) {
1529        if (_isFreeShipNotifyTemplatePreview) return;
1530        // 장바구니 무료배송 안내 모달이 열려있는 cart 컨텍스트에서는 모달 전용 로더로 위임
1531        if (type === 'cart' && isFreeShipNotifyAdditionalActive()) {
1532            return loadOptionForFreeShipNotify(prod_idx);
1533        }
1534
1535        $.ajax({
1536            type: 'POST',
1537            data: { 'type': type, 'prod_idx': prod_idx, 'selected_require_options': selected_require_options, '__': prod_edit_time },
1538            url: ('/shop/load_option.cm'),
1539            dataType: 'json',
1540            cache: false,
1541            success: function (result) {
1542                if (result.msg == 'SUCCESS') {
1543                    max_prod_quantity = result.max_prod_quantity;
1544                    max_member_quantity = result.max_member_quantity;
1545                    maximum_purchase_quantity_type = result.maximum_purchase_quantity_type;
1546                    optional_limit = result.optional_limit;
1547                    optional_limit_type = result.optional_limit_type;
1548                    if (result.prod_name) prod_name = result.prod_name;
1549                    $options.html(result.option_html);
1550
1551                    // 색상 옵션이 있고 + 색상 옵션의 이미지까지 있다면
1552                    if (options.exist_color_option && options.is_exist_color_option_images) {
1553                        if ($prod_image_list.find('.xzoom').length) {
1554                            // xzoom 사용
1555                            $options.find('label[data-opttype]').hover(function () {
1556                                var $_that = $(this);
1557                                var $_target_wrap = $("#btn_owl_" + $_that.attr('data-optcode'));
1558                                if (!$_target_wrap.length) {
1559                                    $_target_wrap = $("#btn_owl_0");
1560                                }
1561                                $_target_wrap.find('a').trigger('click');
1562                            });
1563                        } else {
1564                            // xzoom 미사용2
1565                            var $_color_option_img = $prod_image_list.find('._color_option_img');
1566                            if ($_color_option_img.length) {
1567                                (function () {
1568                                    var hover_evnets = (IS_MOBILE ? 'touchstart touchend' : 'mouseover');
1569                                    $options.find('label[data-opttype] ._bg').off(hover_evnets).on(hover_evnets, function (e) {
1570                                        // mouse over
1571                                        var $_that = $(this);
1572                                        var opt_code = $_that.attr('data-optcode');
1573                                        var $_target = $_color_option_img.filter('[data-optcode="' + opt_code + '"]');
1574
1575                                        $_color_option_img.attr('data-visible', 'false');
1576
1577                                        if ($_target.length) {
1578                                            if ($_target.attr('src').length == 0) {
1579                                                $_target.attr('src', $_target.attr('data-src'));
1580                                                // 이미지 load 이벤트
1581                                                $_target.on('load', function () {
1582                                                    var _width = this.naturalWi
1582dth;
1583                                                    var _height = this.naturalHeight;
1584
1585                                                    if (_width == _height) {
1586                                                        var _wrap_width = $prod_image_list.width();
1587                                                        var _wrap_height = $prod_image_list.height();
1588
1589                                                        if (_wrap_width == _wrap_height) {
1590                                                            $(this).attr('data-size-type', 'A');
1591                                                        } else if (_wrap_width > _wrap_height) {
1592                                                            $(this).attr('data-size-type', 'B');
1593                                                        } else {
1594                                                            $(this).attr('data-size-type', 'C');
1595                                                        }
1596                                                    } else if (_width > _height) {
1597                                                        $(this).attr('data-size-type', 'B');
1598                                                    } else {
1599                                                        $(this).attr('data-size-type', 'C');
1600                                                    }
1601                                                });
1602                                            }
1603                                            $prod_image_list.attr('data-preview-type', 'option');
1604                                            $_target.attr('data-visible', 'true');
1605                                        }
1606
1607                                        switch (e.type) {
1608                                            case 'touchstart':
1609                                                prod_option_touching = true;
1610                                                break;
1611                                            case 'touchend':
1612                                                prod_option_touching = false;
1613                                                break;
1614                                        }
1615                                    });
1616
1617                                    $options.find('label[data-opttype] ._bg').off('mouseout').on('mouseout', function (e) {
1618                                        // mouse out
1619                                        var $_that = $(this);
1620                                        var opt_code = $_that.attr('data-optcode');
1621                                        var $_target = $_color_option_img.filter('[data-optcode="' + opt_code + '"]');
1622
1623                                        $prod_image_list.attr('data-preview-type', 'prod');
1624
1625                                        if ($_target.length) {
1626                                            $_target.attr('data-visible', 'false');
1627                                        }
1628                                    });
1629
1630                                    if (IS_MOBILE) {
1631                                        // 모바일일 경우 터치이벤트 추가
1632                                        var $_body = $("#doz_body");
1633                                        if (!$_body.hasClass('_bind_touchstart_evt')) {
1634                                            $_body.on('touchstart', function (e) {
1635                                                if (!prod_option_touching) {
1636                                                    $prod_image_list.attr('data-preview-type', 'prod');
1637                                                    if ($_color_option_img.length) {
1638                                                        $_color_option_img.attr('data-visible', 'false');
1639                                                    }
1640                                                }
1641                                            }).addClass('_bind_touchstart_evt');
1642                                        }
1643                                    }
1644                                })();
1645                            }
1646                        }
1647                    }
1648
1649                    if (IS_MOBILE) {
1650                        addEventMobileOptionInput();
1651                    }
1652
1653                } else {
1654                    alert(result.msg);
1655                }
1656            }
1657        });
1658
1659				if($prod_additional.length > 0){
1660					loadProdAdditional(prod_idx);
1661				}
1662    };
1663
1664		var loadProdAdditional = function (prod_idx) {
1665			if (_isFreeShipNotifyTemplatePreview) return;
1666			if(is_mobile_width){
1667				$.ajax({
1668					type : 'POST',
1669					data : {'prod_idx' : prod_idx, 'selected_additional_prod_idx' : current_select_additional_prod_idx, '__' : prod_edit_time},
1670					url : (
1671							'/shop/load_prod_additional_mobile.cm'
1672					),
1673					dataType : 'json',
1674					cache : false,
1675					success : function(result){
1676						if(result.msg == 'SUCCESS'){
1677							// 추가 상품
1678							if(!result.prod_additional.prod_list.length) return;
1679							$prod_additional.html(result.prod_additional_sheet_contents);
1680							// $prod_additional.html(result.prod_additional.html);
1681							// $prod_additional.find('#btnProdAdditionalSelect').off('click').on('click', function(e){
1682							// 	e.stopPropagation();
1683							// 	imSheet.open({
1684							// 		id: 'prod_additional_sheet',
1685							// 		html: result.prod_additional_sheet_contents,
1686							// 		backdrop: 'rgba(0, 0, 0, 0.15)',
1687							// 		zIndex: 17001
1688							// 	});
1689							// });
1690						}else{
1691							alert(result.msg);
1692						}
1693					}
1694				});
1695			}else{
1696				$.ajax({
1697					type: 'POST',
1698					data: {'prod_idx': prod_idx, 'selected_additional_prod_idx': current_select_additional_prod_idx, '__': prod_edit_time },
1699					url: ('/shop/load_prod_additional.cm'),
1700					dataType: 'json',
1701					cache: false,
1702					success: function (result) {
1703						if (result.msg == 'SUCCESS') {
1704							// 추가 상품
1705							if(!result.prod_additional.prod_list.length) return;
1706							$prod_additional.html(result.prod_additional.html);
1707						} else {
1708							alert(result.msg);
1709						}
1710					}
1711				});
1712			}
1713
1714    };
1715
1716		var loadProdAdditionalOptions = function (product_idx, cb) {
1717			if (_isFreeShipNotifyTemplatePreview) return;
1718			if(is_mobile_width){
1719				$.ajax({
1720					type: 'POST',
1721					data: {'prod_idx': product_idx, 'selected_require_options': selected_prod_additional_require_options, '__': prod_edit_time, 'is_additional': true },
1722					url: ('/shop/load_prod_additional_option_mobile.cm'),
1723					dataType: 'json',
1724					cache: false,
1725					success: function (result) {
1726						if (result.msg == 'SUCCESS') {
1727							prod_additional_require_option_count = {
1728								...prod_additional_require_option_count,
1729								...result.require_option_count};
1730							prod_additional_require_input_option_count = {
1731								...prod_additional_require_input_option_count,
1732								...result.require_input_option_count
1733							};
1734							if(isFreeShipNotifyAdditionalActive()){
1735								$('#prod_free_ship_notify_additional_options').html(result.option_html);
1736							}else{
1737								// 모바일에서는 드롭다운이 동작하지 않도록 클릭 이벤트 대치
1738								$prod_additional_options.html(result.option_html).find('._form_parent:not(.disabled) ._form_select_wrap > a, ._requireInputOption').each(function(e){
1739									let $that = $(this);
1740									$that.on('click', function(e){
1741										e.stopPropagation();
1742										loadProdAdditionalOptionRequire(product_idx, $that.attr('data-option-code'));
1743									});
1744								});
1745
1746								var full_input = false;
1747								var $form = $prod_additional_options.find('._form_parent');
1748								// 첫번째 선택 후 창을 닫았을때
1749								$form.each(function (index) {
1750
1751									var is_input = $(this).children().hasClass('option_box_wrap');
1752
1753									if (is_input) {
1754										var input_items = $(this).find('._requireInputOption');
1755										input_items.each(function (index) {
1756											if ($(this).val() != '') {
1757												if ((index + 1) == input_items.length) {
1758													full_input = true;
1759												}
1760											} else {
1761												return false;
1762											}
1763										});
1764
1765									} else {
1766										var prev_is_selected = $(this).prev().find('.dropdown-item').hasClass('selected');
1767										var is_selected = $(this).find('.dropdown-item').hasClass('selected');
1768										// 이전 필수 드롭다운이 선택 되어 있을 경위
1769										if (prev_is_selected || full_input) {
1770											$(this).toggleClass('disabled', false);
1771
1772											full_input = false;
1773
1774											/* 드롭다운 활성화는 loadProdAdditionalOptionRequire의 시트 호출로 대체 */
1775										} else if (index === 0) {
1776											// 첫 드롭다운은 일단 true로 표시
1777											$(this).toggleClass('disabled', false);
1778										} else {
1779											$(this).toggleClass('disabled', true);
1780										}
1781									}
1782								});
1783
1784								if (typeof prod_additional_require_option_count[product_idx] == 'undefined' || selected_prod_additional_require_options.length == prod_additional_require_option_count[product_idx]){
1785									/* 필수 옵션이 모두 선택되었을 경우 시트를 닫음 */
1786									imSheet.close('prod_additional_sheet');
1787								}else{
1788									loadProdAdditionalOptionRequire(product_idx);
1789								}
1790							}
1791
1792							updateSelectedOptions('prod');
1793						} else {
1794							alert(result.msg);
1795						}
1796					}
1797				});
1798			}else{
1799				$.ajax({
1800					type: 'POST',
1801					data: {'prod_idx': product_idx, 'selected_require_options': selected_prod_additional_require_options, '__': prod_edit_time},
1802					url: ('/shop/load_prod_additional_option.cm'),
1803					dataType: 'json',
1804					cache: false,
1805					success: function (result) {
1806						if (result.msg == 'SUCCESS') {
1807							prod_additional_require_option_count = {
1808								...prod_additional_require_option_count,
1809								...result.require_option_count};
1810							prod_additional_require_input_option_count = {
1811								...prod_additional_require_input_option_count,
1812								...result.require_input_option_count
1813							};
1814							if(isFreeShipNotifyAdditionalActive()){
1815								$('#prod_free_ship_notify_additional_options').html(result.option_html);
1816							}else{
1817								$prod_additional_options.html(result.option_html);
1818							}
1819							// PC 인라인 컨테이너 사용 후, 다른 상품 선택 시 기존 인라인 영역 정리
1820							try{
1821								if($prod_additional_options.hasClass('_prod_additional_inline_options')){
1822									$('._prod_additional_inline_options').not($prod_additional_options).remove();
1823								}
1824							}catch(e){}
1825
1826							updateSelectedOptions('prod');
1827						} else {
1828							alert(result.msg);
1829						}
1830					}
1831				});
1832			}
1833		}
1834
1835	/**
1836	 * 모바일 추가 상품 선택 시 다음 필수 옵션에 대한 html을 시트에 출력
1837	 * @param product_idx
1838	 * @param option_code
1839	 */
1840		var loadProdAdditionalOptionRequire = function(product_idx, option_code = null) {
1841			if (_isFreeShipNotifyTemplatePreview) return;
1842			$.ajax({
1843				type: 'POST',
1844				data: {'prod_idx': product_idx, 'option_code': option_code, 'selected_require_options': selected_prod_additional_require_options, '__': prod_edit_time, 'is_additional': true },
1845				url: ('/shop/load_prod_additional_option_require.cm'),
1846				dataType: 'json',
1847				cache: false,
1848				success: function (result) {
1849					if (result.msg == 'SUCCESS') {
1850						prod_additional_require_option_count = {
1851							...prod_additional_require_option_count,
1852							...result.require_option_count};
1853						prod_additional_require_input_option_count = {
1854							...prod_additional_require_input_option_count,
1855							...result.require_input_option_count
1856						};
1857
1858						// 현재 시트의 내용을 받아온 옵션(html)으로 변경(시트를 닫고 새로 열어줌)
1859						imSheet.close('prod_additional_sheet', () => {
1860							imSheet.open({
1861								id: 'prod_additional_sheet',
1862								html: result.prod_additional_sheet_contents,
1863								backdrop: 'rgba(0, 0, 0, 0.15)',
1864								zIndex: 17001
1865							});
1866						});
1867					} else {
1868						alert(result.msg);
1869					}
1870				}
1871			});
1872		}
1873
1874		/**
1875		 * 모바일 추가 상품 선택 옵션에 대한 html을 시트에 출력
1876		 * @param product_idx
1877		 * @param option_code
1878		 */
1879		var loadProdAdditionalOptionOptional = function(product_idx, option_code) {
1880			if (_isFreeShipNotifyTemplatePreview) return;
1881			$.ajax({
1882				type: 'POST',
1883				data: {'prod_idx': product_idx, 'option_code': option_code, '__': prod_edit_time, 'is_additional': true },
1884				url: ('/shop/load_prod_additional_option_optional.cm'),
1885				dataType: 'json',
1886				cache: false,
1887				success: function (result) {
1888					if (result.msg == 'SUCCESS') {
1889						// 현재 시트의 내용을 받아온 옵션(html)으로 변경(시트를 닫고 새로 열어줌)
1890						imSheet.close('prod_additional_sheet', () => {
1891							imSheet.open({
1892								id: 'prod_additional_sheet',
1893								html: result.prod_additional_sheet_contents,
1894								backdrop: 'rgba(0, 0, 0, 0.15)',
1895								zIndex: 17001
1896							});
1897						});
1898					} else {
1899						alert(result.msg);
1900					}
1901				}
1902			});
1903		}
1904
1905		/**
1906		 * 추가 상품 선택 시 선택 옵션에 대한 html을 반환
1907		 * @param product_idx
1908		 * @param option_code
1909		 */
1910		var loadProdAdditionalOptionalList = function() {
1911			if (_isFreeShipNotifyTemplatePreview) return;
1912			$('._prod_additional_option_optional').each((k, v) => {
1913				let $that = $(v);
1914				let product_idx = $that.attr('data-prod-idx');
1915				if($that.attr('data-prod-idx') > 0){
1916					$.ajax({
1917						type: 'POST',
1918						data: {'prod_idx': $that.attr('data-prod-idx'), '__': prod_edit_time, 'is_additional': true },
1919						url: ('/shop/load_prod_additional_optional_list.cm'),
1920						dataType: 'json',
1921						cache: false,
1922						success: function (result) {
1923							if (result.msg == 'SUCCESS') {
1924								if(is_mobile_width){
1925									$that.html(result.option_html).find('._form_select_wrap > a').each(function(e){
1926										let $form = $(this);
1927										$form.on('click', function(e){
1928											e.stopPropagation();
1929											loadProdAdditionalOptionOptional(product_idx, $form.attr('data-option-code'));
1930										});
1931									});
1932								}else{
1933									$that.html(result.option_html);
1934								}
1935							}
1936						}
1937					});
1938				}
1939			});
1940		}
1941
1942    var loadDelivSetting = _.debounce(function (prod_idx, change_country, deliv_type, deliv_pay_type) {
1943        $deliv_country = change_country;
1944        $deliv_pay_type = deliv_pay_type;
1945            var is_design_mode = (location.pathname.indexOf('/admin/design') != -1);
1946            $.ajax({
1947                type: 'GET',
1948                data: {
1949                    'prod_idx': prod_idx,
1950                    'change_country': change_country,
1951                    'deliv_type': deliv_type,
1952                    'deliv_pay_type': deliv_pay_type,
1953                    '__': MEMBER_HASH
1954                },
1955                url: (is_mobile_width ? '/shop/load_deliv_setting.cm' : '/shop/load_deliv_setting_pc.cm'),
1956                dataType: 'json',
1957                cache: !is_design_mode,
1958                async: false,
1959                success: function (result) {
1960                    if (result.msg == 'SUCCESS') {
1961                        $prod_deliv_setting.html(result.deliv_html);
1962                        if (window.IMWEB_SHOP_PAYMENT_METHOD_BENEFIT_SDK && typeof window.IMWEB_SHOP_PAYMENT_METHOD_BENEFIT_SDK.hydrate === 'function') {
1963                            window.IMWEB_SHOP_PAYMENT_METHOD_BENEFIT_SDK.hydrate($prod_deliv_setting.get(0));
1964                        }
1965                        if (is_mobile_width) {
1966                            $prod_detail.find('._today_arrival_wrap').html(result.today_arrival_html);
1967                        }
1968
1969                        /* 환불 정보 입력 */
1970                        last_refund_data = result.refund;
1971                        setDetailReturnHtml();
1972
1973                        // 표시항목 주입: sanitize 후 주입 (IMOPS-13023)
1974                        if ($prod_detail && $prod_detail.find('._item_detail_wrap').length > 0) {
1975                            $prod_detail.find('._item_detail_wrap').html(_sanitizeHtml(result.item_detail_html));
1976                            // sanitize로 script가 제거되어 popover 초기화가 누락되므로 재초기화 (IMOPS-13118)
1977                            $prod_detail.find('._item_detail_wrap .btn-popover').popover();
1978                            $prod_detail.find('._item_detail_wrap .html-popover').popover({html: true});
1979                        }
1980
1981                        if (IS_MOBILE) {
1982                            addEventMobileOptionInput();
1983                        }
1984
1985                        if (typeof makeNaverPayBtn !== 'undefined') {
1986														try{
1987															// v2 는 show/hide idempotent 호출이라 항상 재호출(국가 노출 갱신). v1 은 기존대로 최초 1회만.
1988															if(window.__npayButtonV2 || !document.getElementById('naverPayWrap').innerHTML){
1989																makeNaverPayBtn('naverPayWrap', (
1990																		change_country == 'KR' || change_country == 'none' || change_country == ''
1991																));
1992															}
1993														} catch(e) {
1994
1995														}
1996                        }
1997
1998                        if (typeof kakaoCheckout != 'undefined' && typeof makeTalkPayButton != 'undefined') {
1999                            var _target_container_id = 'create-kakao-checkout-button';
2000                            _target_container_id = document.getElementById(_target_container_id) ? _target_container_id : 'create-kakao-checkout-button-mobile';
2001                            if (!document.getElementById(_target_container_id).innerHTML) {
2002                                makeTalkPayButton(_target_container_id, (change_country == 'KR' || change_country == 'none' || change_country == ''));
2003                            }
2004                        }
2005
2006                    } else {
2007                        alert(result.msg);
2008                    }
2009                }
2010            });
2011    }, 100, { leading: true, trailing: false});
2012
2013    /**
2014     * 통합 상품정보 AJAX 호출 (item_detail_html + deliv_html 분리 응답)
2015     * Feature flag ON 시 DetailItemMake/loadDelivSetting 대신 사용
2016     */
2017    const loadItemInfo = ({ prod_idx, change_country, deliv_type, deliv_pay_type } = {}) => {
2018        const is_design_mode = location.pathname.includes('/admin/design');
2019        $deliv_country = change_country;
2020        $deliv_pay_type = deliv_pay_type;
2021        const isKrOrEmpty = change_country === 'KR' || change_country === 'none' || change_country === '';
2022        $.ajax({
2023            type: 'GET',
2024            data: {
2025                prod_idx,
2026                change_country,
2027                deliv_type,
2028                deliv_pay_type,
2029                '__': MEMBER_HASH
2030            },
2031            url: '/shop/prod_detail_item_info.cm',
2032            dataType: 'json',
2033            cache: !is_design_mode,
2034            success(result) {
2035                if (result.msg !== 'SUCCESS') return;
2036
2037                if (shop_view_style === 'b' && !IS_MOBILE) {
2038                    // style_b PC: 상품정보 → _item_detail_wrap, 배송정보 → 바텀시트(#prod_deliv_setting)
2039                    var $item_detail_wrap = $prod_detail && $prod_detail.find('._item_detail_wrap');
2040                    if ($item_detail_wrap && $item_detail_wrap.length > 0) {
2041                        $item_detail_wrap.html(result.item_detail_html);
2042                    }
2043                    $prod_deliv_setting.html(result.deliv_html);
2044                    if (window.IMWEB_SHOP_PAYMENT_METHOD_BENEFIT_SDK && typeof window.IMWEB_SHOP_PAYMENT_METHOD_BENEFIT_SDK.hydrate === 'function') {
2045                        if ($item_detail_wrap && $item_detail_wrap.length > 0) {
2046                            window.IMWEB_SHOP_PAYMENT_METHOD_BENEFIT_SDK.hydrate($item_detail_wrap.get(0));
2047                        }
2048                        window.IMWEB_SHOP_PAYMENT_METHOD_BENEFIT_SDK.hydrate($prod_deliv_setting.get(0));
2049                    }
2050                } else {
2051                    // style_a + style_b 모바일: 상품정보 + 배송정보 합쳐서 _item_detail_wrap에 주입
2052                    if ($prod_detail && $prod_detail.find('._item_detail_wrap').length > 0) {
2053                        var $item_detail_wrap = $prod_detail.find('._item_detail_wrap');
2054                        $item_detail_wrap.html(result.item_detail_html + result.deliv_html);
2055                        if (window.IMWEB_SHOP_PAYMENT_METHOD_BENEFIT_SDK && typeof window.IMWEB_SHOP_PAYMENT_METHOD_BENEFIT_SDK.hydrate === 'function') {
2056                            window.IMWEB_SHOP_PAYMENT_METHOD_BENEFIT_SDK.hydrate($item_detail_wrap.get(0));
2057                        }
2058                    }
2059                }
2060
2061                // 배송 타입 업데이트
2062                if (result.deliv_type) SITE_SHOP_DETAIL.addDelivType(result.deliv_type);
2063                if (result.deliv_pay_type) SITE_SHOP_DETAIL.addDelivPayType(result.deliv_pay_type);
2064
2065                var hasFreeShipNotifyTargetResult = typeof result.free_ship_notify_delivery_target_product === 'boolean';
2066                var isFreeShipNotifyDeliveryTarget = hasFreeShipNotifyTargetResult
2067                    ? result.free_ship_notify_delivery_target_product
2068                    : change_country === 'KR';
2069                $('._free_ship_notify_detail_magnet_wrap').toggleClass(
2070                    'tw-hidden',
2071                    hasFreeShipNotifyTargetResult ? !isFreeShipNotifyDeliveryTarget : ($('._deliv_country_selector').length > 0 && change_country !== 'KR')
2072                );
2073
2074                if (typeof result.deliv_price_flexable_key === 'number') {
2075                    var newThreshold = isFreeShipNotifyDeliveryTarget ? result.deliv_price_flexable_key : 0;
2076                    var newThresholdStr = String(newThreshold);
2077                    $('#foFreeShipNotifyProdDetailMagnet').attr('data-init-deliv-price-flexable-key', newThresholdStr);
2078                    $('#shop_detail_add_cart_alarm_with_free_ship_notify').attr('data-init-deliv-price-flexable-key', newThresholdStr);
2079                    window.dispatchEvent(new CustomEvent('imweb:freeShipNotify:freeShippingThreshold:update', {
2080                        detail: { threshold: newThreshold }
2081                    }));
2082                }
2083
2084                // 환불 정보
2085                last_refund_data = result.refund;
2086                setDetailReturnHtml();
2087
2088                // popover 초기화
2089                $('.btn-popover').popover();
2090                $('.html-popover').popover({html: true});
2091
2092                // 네이버페이 버튼 — v2 에서만 재렌더(show/hide). v1 은 기존 no-op 유지(플래그 미설정).
2093                if (window.__npayButtonV2 && typeof makeNaverPayBtn !== 'undefined') {
2094                    try { makeNaverPayBtn('naverPayWrap', isKrOrEmpty); } catch(e) {}
2095                }
2096
2097                // 카카오페이 버튼
2098                if (typeof kakaoCheckout !== 'undefined' && typeof makeTalkPayButton !== 'undefined') {
2099                    try { makeTalkPayButton('create-kakao-checkout-button', isKrOrEmpty); } catch(e) {}
2100                }
2101            }
2102        });
2103    };
2104
2105    /**
2106     * 선택형 필수 옵션 선택
2107     * @param type cart (장바구니변경), prod(상품상세)
2108     * @param option_code
2109     * @param value_code
2110     */
2111    var selectRequireOption = function (type, prod_idx, option_code, value_code, value_name, success) {
2112        var data = { 'value_type': 'SELECT', 'option_code': option_code, 'value_code': value_code, 'value_name': value_name };
2113        var no = findSelectedRequireOption(option_code);
2114        if (no == -1) {
2115            if (value_code == '') return;
2116            selected_require_options.push(data);
2117            /** 처음 선택된 옵션인 경우 새로 추가*/
2118        } else {
2119            if (value_code == '') {
2120                /** 옵션 삭제 */
2121                selected_require_options.splice(no, (s
2121elected_require_options.length - no));
2122            } else {
2123                selected_require_options[no] = data;
2124                /** 이미 선택된 옵션인 경우 기존 값 교체 */
2125                if (no < selected_require_options.length - 1) {
2126                    selected_require_options.splice(no + 1, (selected_require_options.length - (no + 1)));
2127                }
2128            }
2129        }
2130        if (selected_require_options.length == require_option_count) {
2131            if (isFreeShipNotifyAdditionalActive()) {
2132                // 무료 배송 안내 모달에서는 추가하기 버튼을 눌러야 반영되므로
2133                // selectOption/selected_require_options reset을 생략하고 현재 선택 상태를 유지한 채 화면만 갱신한다.
2134                // (selectProdAdditionalRequireOption의 무료배송 모달 분기와 동일 패턴)
2135                loadOption(type, prod_idx);
2136                if (typeof success === 'function') success();
2137            } else {
2138                /** 필수 옵션이 모두 선택되었을 경우*/
2139                selectOption(
2140                    prod_idx,
2141                    {
2142                        options: selected_require_options,
2143                        require: true,
2144                        count: 1
2145                    },
2146                    function () {
2147                        selected_require_options = [];
2148                        loadOption(type, prod_idx);
2149                        success();
2150                    },
2151                    function (msg) {
2152                        alert(msg);
2153                    }
2154                );
2155            }
2156        } else {
2157            /** 필수옵션 선택이 아직 끝나지 않았을 경우 옵션 재로드 */
2158            loadOption(type, prod_idx);
2159        }
2160    };
2161
2162    /**
2163     * 입력형 필수 옵션 변경시 처리
2164     * @param type cart (장바구니변경), prod(상품상세)
2165     */
2166    var changeRequireInputOption = function (type, prod_idx, option_code, msg, success) {
2167        var data = { 'value_type': 'INPUT', 'option_code': option_code, 'value_code': '', 'value_name': msg };
2168        var no = findSelectedRequireOption(option_code);
2169        if (no == -1) {
2170            if (msg == '') return;
2171            selected_require_options.push(data);
2172            /** 처음 입력한 옵션인 경우 새로 추가*/
2173        } else {
2174            if (msg == '') {
2175                /** 입력형 옵션 삭제 */
2176                selected_require_options.splice(no, (selected_require_options.length - no));
2177            } else {
2178                /** 기존값 교체 */
2179                selected_require_options[no] = data;
2180            }
2181        }
2182        if (selected_require_options.length == require_option_count) {
2183            if (isFreeShipNotifyAdditionalActive()) {
2184                // 무료 배송 안내 모달에서는 추가하기 버튼을 눌러야 반영되므로
2185                // selectOption/selected_require_options reset을 생략하고 현재 선택 상태를 유지한 채 화면만 갱신한다.
2186                loadOption(type, prod_idx);
2187                if (typeof success === 'function') success();
2188            } else {
2189                /** 필수 옵션이 모두 선택되었을 경우*/
2190                selectOption(
2191                    prod_idx,
2192                    {
2193                        options: selected_require_options,
2194                        require: true,
2195                        count: 1
2196                    },
2197                    function () {
2198                        selected_require_options = [];
2199                        loadOption(type, prod_idx);
2200                        success();
2201                    },
2202                    function (msg) {
2203                        alert(msg);
2204                    }
2205                );
2206            }
2207        } else {
2208            // 웹가속기A(edge HTML cache) 사이트는 서버 IS_MOBILE이 항상 false라(UA가 CloudFront로 대체됨)
2209            // 모바일 바텀시트의 순차 필수옵션 unlock 재렌더가 죽는다. 뷰포트 기준 판정을 함께 사용한다.
2210            if ((IS_MOBILE || is_mobile_width) && selected_require_options.length == require_input_option_count) loadOption(type, prod_idx);
2211        }
2212    };
2213
2214		/**
2215     * 입력형 필수 옵션 변경시 처리
2216     * @param type cart (장바구니변경), prod(상품상세)
2217     */
2218    var changeProdAdditionalRequireInputOption = function (type, prod_idx, option_code, msg, success) {
2219        var data = { 'value_type': 'INPUT', 'option_code': option_code, 'value_code': '', 'value_name': msg };
2220        var no = findSelectedRequireOption(option_code);
2221        if (no == -1) {
2222            if (msg == '') return;
2223            selected_prod_additional_require_options.push(data);
2224            /** 처음 입력한 옵션인 경우 새로 추가*/
2225        } else {
2226            if (msg == '') {
2227                /** 입력형 옵션 삭제 */
2228                selected_prod_additional_require_options.splice(no, (selected_prod_additional_require_options.length - no));
2229            } else {
2230                /** 기존값 교체 */
2231                selected_prod_additional_require_options[no] = data;
2232            }
2233        }
2234        if (selected_prod_additional_require_options.length == prod_additional_require_option_count[prod_idx]) {
2235            if (isFreeShipNotifyAdditionalActive()) {
2236                loadProdAdditionalOptions(prod_idx);
2237                if (typeof success === 'function') success();
2238            } else {
2239                /** 필수 옵션이 없거나 모두 선택되었을 경우*/
2240                selectProdAdditionalOption(prod_idx, selected_prod_additional_require_options, true, 1, function () {
2241		                if(is_mobile_width){
2242			                imSheet.close('prod_additional_sheet');
2243		                }
2244	                  current_select_additional_prod_idx = 0;
2245                    selected_prod_additional_require_options = [];
2246	                  $prod_additional_options.html('');
2247                        $('#prod_additional_drop_menu_content').text(getLocalizeString('타이틀_추가상품', '', '추가 상품'));  // PC
2248                        // 인라인 컨테이너 초기화 및 원래 위치로 복구
2249                        try{ $('._prod_additional_inline_options').remove(); $prod_additional_options = $prod_additional_options_default; }catch(e){}
2250	                  $('#btnProdAdditionalSelect').text(getLocalizeString('타이틀_추가상품', '', '추가 상품'));  // 모바일
2251                    success();
2252                }, function (msg) {
2253                    alert(msg);
2254                });
2255            }
2256        } else {
2257	        loadProdAdditionalOptions(prod_idx);
2258        }
2259    };
2260
2261    /**
2262     * 옵션 선택
2263     * @param options [ {value_type: SELECT/INPUT, option_code:, value_code:, value_name:} ]
2264     */
2265    var selectOption = function (prod_idx, {options, require, count, idx = 0, skip_quantity_validation = false}, success, failed) {
2266        var btn_buy_onclick_temp = $('._btn_buy').attr('onclick');
2267        var btn_regularly_onclick_temp = $('._btn_regularly').attr('onclick');
2268        var btn_cart_onclick_temp = $('._btn_cart').attr('onclick');
2269        var btn_regularly_cart_onclick_temp = $('._btn_cart.im-regularly').attr('onclick');
2270        $('._btn_buy').attr('onclick', 'event.cancelBubble=true');
2271        $('._btn_cart').attr('onclick', 'event.cancelBubble=true');
2272        $.ajax({
2273            type: 'POST',
2274            data: {
2275                'prod_idx': prod_idx,
2276                'options': options,
2277                'require': (require ? 'Y' : 'N'),
2278                'count': count,
2279                'idx' : idx,
2280                'skip_quantity_validation': skip_quantity_validation ? true : false
2281            },
2282            url: ('/shop/select_option.cm'),
2283            dataType: 'json',
2284            cache: false,
2285            async: false,
2286            success: function (result) {
2287                if (result.msg == 'SUCCESS') {
2288                    prod_price = result.prod_price;
2289
2290                    var no = findSelectedOption(options);
2291                    if (no == -1) {
2292                        /* 상품 구매수량 체크 (장바구니 로드 시 검증 건너뛰기) */
2293                        if (skip_quantity_validation) {
2294                            selected_options.push(result.selected_option);
2295                            success();
2296                        } else {
2297                            var total_selected_count = getProdTotalQuantity(maximum_purchase_quantity_type);
2298
2299                            var new_total = total_selected_count + count;
2300                            if (max_prod_quantity == 0 || new_total <= max_prod_quantity) {
2301                                selected_options.push(result.selected_option);
2302                                /** 처음 선택된 옵션인 경우 새로 추가*/
2303                                success();
2304                            } else {
2305                                failed(LOCALIZE.설명_최대N개만구매가능한상품입니다(max_prod_quantity));
2306                            }
2307                        }
2308                    } else {
2309                        failed(LOCALIZE.설명_이미선택된옵션입니다());
2310                    }
2311                } else {
2312                    failed(result.msg);
2313                }
2314                $('._btn_buy').attr('onclick', btn_buy_onclick_temp);
2315                $('._btn_regularly').attr('onclick', btn_regularly_onclick_temp);
2316                $('._btn_cart').attr('onclick', btn_cart_onclick_temp);
2317                $('._btn_cart.im-regularly').attr('onclick', btn_regularly_cart_onclick_temp);
2318            }
2319        });
2320    };
2321
2322		/**
2323     * 옵션 선택
2324     * @param options [ {value_type: SELECT/INPUT, option_code:, value_code:, value_name:} ]
2325     */
2326    var selectProdAdditionalOption = function (prod_idx, options, require, count, success, failed) {
2327        //$('.npay_btn_pay').attr('onclick', 'event.cancelBubble=true');
2328        var btn_buy_onclick_temp = $('._btn_buy').attr('onclick');
2329        var btn_regularly_onclick_temp = $('._btn_regularly').attr('onclick');
2330        var btn_cart_onclick_temp = $('._btn_cart').attr('onclick');
2331        var btn_regularly_cart_onclick_temp = $('._btn_cart.im-regularly').attr('onclick');
2332        $('._btn_buy').attr('onclick', 'event.cancelBubble=true');
2333        $('._btn_cart').attr('onclick', 'event.cancelBubble=true');
2334        $.ajax({
2335            type: 'POST',
2336            data: { 'prod_idx': prod_idx, 'options': options, 'require': (require ? 'Y' : 'N'), 'count': count },
2337            url: ('/shop/select_option.cm'),
2338            dataType: 'json',
2339            cache: false,
2340            async: false,
2341            success: function (result) {
2342                if (result.msg == 'SUCCESS') {
2343                    var no = findSelectedProdAdditionalOption(options);
2344										var prod_name = result.prod_name;
2345                    if (no == -1) {
2346                        /* 상품 구매수량 체크 */
2347	                    var selected_prod_data = selected_prod_additionals.find(v=>v.idx===prod_idx);
2348                        var can_add = true;
2349                        var error_msg = '';
2350
2351                        // 추가상품 자체의 제한만 확인한다.
2352                        var total_selected_count = getProdAdditionalTotalQuantity(selected_prod_data.maximum_purchase_quantity_type, prod_idx);
2353                        var new_total = total_selected_count + count;
2354
2355                        if (selected_prod_data.maximum_purchase_quantity > 0 && new_total > selected_prod_data.maximum_purchase_quantity) {
2356                            can_add = false;
2357                            error_msg = LOCALIZE.설명_최대N개만구매가능한상품입니다(selected_prod_data.maximum_purchase_quantity);
2358                        }
2359
2360                        if (can_add) {
2361	                        selected_prod_additional_options.push({prod_idx, prod_name, ...result.selected_option});
2362                            /** 처음 선택된 옵션인 경우 새로 추가*/
2363                            success();
2364                        } else {
2365                            failed(error_msg);
2366                        }
2367                    } else {
2368                        failed(LOCALIZE.설명_이미선택된옵션입니다());
2369                    }
2370                } else {
2371                    failed(result.msg);
2372                }
2373                $('._btn_buy').attr('onclick', btn_buy_onclick_temp);
2374                $('._btn_regularly').attr('onclick', btn_regularly_onclick_temp);
2375                $('._btn_cart').attr('onclick', btn_cart_onclick_temp);
2376                $('._btn_cart.im-regularly').attr('onclick', btn_regularly_cart_onclick_temp);
2377            }
2378        });
2379    };
2380
2381		var checkRequireOption = function() {
2382			var main_require_option_count = parseInt(require_option_count) || 0;
2383			if (main_require_option_count <= 0) return true;
2384
2385			var has_selected_require_option = false;
2386			$.each(selected_options, function (no, data) {
2387				if (data.require == true) {
2388					has_selected_require_option = true;
2389					return false;
2390				}
2391			});
2392
2393			return has_selected_require_option;
2394		};
2395
2396    var getProdTotalQuantity = function (maximum_purchase_quantity_type) {
2397        /* 본상품 구매수량 체크: 추가상품은 별도 상품이므로 포함하지 않는다. */
2398        var total_selected_count = 0;
2399        if (maximum_purchase_quantity_type === 'order') {
2400            // 옵션이 없는 경우의 본품 수량 포함
2401            total_selected_count += order_count;
2402        }
2403        $.each(selected_options, function (idx, obj) {
2404            if (maximum_purchase_quantity_type === 'order') {
2405                total_selected_count += obj.count;
2406            }
2407        });
2408        return total_selected_count;
2409    };
2410
2411    var getProdRequireTotalQuantity = function () {
2412        /* 필수 옵션 구매수량 체크 */
2413        var total_selected_count = 0;
2414        $.each(selected_options, function (idx, obj) {
2415            if (obj.require) total_selected_count += obj.count;
2416        });
2417        return total_selected_count;
2418    };
2419
2420		var getProdAdditionalTotalQuantity = function (maximum_purchase_quantity_type, prod_idx) {
2421        /* 추가상품 구매수량 체크: 같은 추가상품의 옵션만 합산한다. */
2422        var total_selected_count = 0;
2423        $.each(selected_prod_additional_options, function (idx, obj) {
2424            if (maximum_purchase_quantity_type === 'order' && obj.prod_idx === prod_idx) {
2425                total_selected_count += obj.count;
2426            }
2427        });
2428        return total_selected_count;
2429    };
2430
2431    var getProdAdditionalRequireTotalQuantity = function () {
2432        /* 필수 옵션 구매수량 체크 */
2433        var total_selected_count = 0;
2434        $.each(selected_prod_additional_options, function (idx, obj) {
2435            if (obj.require) total_selected_count += obj.count;
2436        });
2437        return total_selected_count;
2438    };
2439
2440    /**
2441     * 선택된 옵션 삭제
2442     * @param optName
2443     * @param success
2444     */
2445    var removeSelectedOption = function (optNo, success) {
2446        if (optNo <= (selected_options.length - 1)) {
2447            selected_options.splice(optNo, 1);
2448            success();
2449        }
2450    };
2451
2452	/**
2453	 * 선택된 추가 상품 삭제
2454	 * @param optNo
2455	 * @param success
2456	 */
2457	var removeSelectedProdAdditional = function (prodIdx, success) {
2458		if (selected_prod_additionals.length) {
2459			selected_prod_additionals = selected_prod_additionals.filter(data => data.idx !== prodIdx);
2460			/* 추가 상품의 하위 옵션도 같이 삭제 */
2461			selected_prod_additional_options = selected_prod_additional_options.filter(data => data.prod_idx !== prodIdx);
2462
2463			success();
2464		}
2465	};
2466
2467	/**
2468     * 선택된 추가 상품 옵션 삭제
2469     * @param optNo
2470     * @param success
2471     */
2472    var removeSelectedProdAdditionalOption = function (optNo, success) {
2473        if (optNo <= (selected_prod_additional_options.length - 1)) {
2474            var deleted = selected_prod_additional_options.splice(optNo, 1)[0];
2475            if (deleted.require) {
2476                var hasRemainingRequiredOption = selected_prod_additional_options.some(function (option) {
2477                    return option.prod_idx === deleted.prod_idx && option.require;
2478                });
2479                if (!hasRemainingRequiredOption) {
2480                    removeSelectedProdAdditional(deleted.prod_idx, function () {});
2481                }
2482            }
2483            success();
2484        }
2485    };
2486
2487    /**
2488     * 해당 옵션이 현재 선택되어있는지 확인
2489     * @param options [{option_code:, value_code:, value_name:}]
2490     * */
2491    var findSelectedOption = function (options) {
2492        var found_no = -1;
2493        $.each(selected_options, function (no, data) {
2494            if (isSameOptionList(data.options, options)) {
2495                found_no = no;
2496                return false;
2497            }
2498        });
2499        return found_no;
2500    };
2501
2502		/**
2503     * 해당 옵션이 현재 선택되어있는지 확인
2504     * @param options [{option_code:, value_code:, value_name:}]
2505     * */
2506    var findSelectedProdAdditionalOption = function (options) {
2507        var found_no = -1;
2508        $.each(selected_prod_additional_options, function (no, data) {
2509	        if(data.prod_idx != current_select_additional_prod_idx){
2510		        return true; // 현재 선택 중인 추가 상품이 아닌 상품의 옵션은 체크하지 않고 continue 처리
2511	        }
2512	        if (isSameOptionList(data.options, options)) {
2513		        found_no = no;
2514		        return false;
2515	        }
2516        });
2517        return found_no;
2518    };
2519
2520    /**
2521     * 해당 필수 옵션이 현재 선택되어있는지 확인
2522     * @param optName
2523     */
2524    var findSelectedRequireOption = function (option_code) {
2525        var foundNo = -1;
2526        $.each(selected_require_options, function (no, data) {
2527            if (data.option_code == option_code) {
2528                foundNo = no;
2529                return false;
2530            }
2531        });
2532        return foundNo;
2533    };
2534
2535	/**
2536	 * 해당 필수 옵션이 현재 선택되어있는지 확인
2537	 * @param optName
2538	 */
2539	var findSelectedProdAdditionalRequireOption = function (option_code) {
2540		var foundNo = -1;
2541		$.each(selected_prod_additional_require_options, function (no, data) {
2542			if (data.option_code == option_code) {
2543				foundNo = no;
2544				return false;
2545			}
2546		});
2547		return foundNo;
2548	};
2549
2550    /**
2551     * 옵션 수량 및 재고 체크하는 기능
2552     * @param optNo
2553     * @param cnt
2554     * @returns {*}
2555     */
2556    var checkOptionCount = function (optNo, cnt) {
2557        // 그냥 재고 체크
2558        if (selected_options[optNo].use_stock && !selected_options[optNo].stock_un_limit) {
2559            if (cnt > selected_options[optNo].stock) {
2560                selected_options[optNo].count = selected_options[optNo].stock;
2561                return LOCALIZE.설명_현재재고부족으로N개이상구매할수없습니다(selected_options[optNo].stock + 1);
2562            }
2563        }
2564
2565
2566
2567        if (selected_options[optNo].require) { // 필수 옵션
2568            if (prod_type === SHOP_CONST.PROD_TYPE.DIGITAL) {
2569                // 만약 디지털 상품일 경우 수량변경이 제한된다.
2570                selected_options[optNo].count = 1;
2571                return LOCALIZE.설명_디지털상품은수량을변경하실수없습니다();
2572            }
2573        } else { // 선택 옵션
2574            if (parseInt(selected_options[optNo].price) == 0) {
2575                // 가격이 0원일 경우... 0원 선택옵션 구매 시 최대 수량 제한 체크를 한다.
2576                switch (optional_limit_type) {
2577                    case 'limit':
2578                        if (cnt > optional_limit) {
2579                            selected_options[optNo].count = optional_limit;
2580                            return LOCALIZE.설명_해당선택옵션은최대N개까지구매가능합니다(optional_limit);
2581                        }
2582                        break;
2583                    case 'unique':
2584                        if (cnt > 1) {
2585                            selected_options[optNo].count = 1;
2586                            return LOCALIZE.설명_해당선택옵션은최대N개까지구매가능합니다(1);
2587                        }
2588                        break;
2589                    case 'relative':
2590                        var _order_cnt = order_count;
2591                        if (_order_cnt == 0) {
2592                            $.each(selected_options, function (no, data) { if (data.require) { _order_cnt += data.count; } });
2593                        }
2594
2595                        if (cnt > _order_cnt) {
2596                            selected_options[optNo].count = _order_cnt;
2597                            return LOCALIZE.설명_0원상품갯수제한();
2598                        }
2599                        break;
2600                }
2601            }
2602        }
2603
2604        return true;
2605    };
2606
2607		/**
2608     * 옵션 수량 및 재고 체크하는 기능
2609     * @param optNo
2610     * @param cnt
2611     * @returns {*}
2612     */
2613    var checkProdAdditionalOptionCount = function (optNo, cnt) {
2614			var prod_idx = selected_prod_additional_options[optNo].prod_idx;
2615			var selected_prod_data = selected_prod_additionals.find(v=>v.idx===prod_idx);
2616        // 그냥 재고 체크
2617        if (selected_prod_additional_options[optNo].use_stock && !selected_prod_additional_options[optNo].stock_un_limit) {
2618            if (cnt > selected_prod_additional_options[optNo].stock) {
2619                selected_prod_additional_options[optNo].count = selected_prod_additional_options[optNo].stock;
2620                return LOCALIZE.설명_현재재고부족으로N개이상구매할수없습니다(selected_prod_additional_options[optNo].stock + 1);
2621            }
2622        }
2623
2624
2625
2626        if (selected_prod_additional_options[optNo].require) { // 필수 옵션
2627            if (prod_type === SHOP_CONST.PROD_TYPE.DIGITAL) {
2628                // 만약 디지털 상품일 경우 수량변경이 제한된다.
2629                selected_prod_additional_options[optNo].count = 1;
2630                return LOCALIZE.설명_디지털상품은수량을변경하실수없습니다();
2631            }
2632        } else { // 선택 옵션
2633            if (parseInt(selected_prod_additional_options[optNo].price) == 0) {
2634                // 가격이 0원일 경우... 0원 선택옵션 구매 시 최대 수량 제한 체크를 한다.
2635                switch (selected_prod_data.optional_limit_type) {
2636                    case 'limit':
2637                        if (cnt > selected_prod_data.optional_limit) {
2638                            selected_prod_additional_options[optNo].count = selected_prod_data.optional_limit;
2639                            return LOCALIZE.설명_해당선택옵션은최대N개까지구매가능합니다(selected_prod_data.optional_limit);
2640                        }
2641                        break;
2642                    case 'unique':
2643                        if (cnt > 1) {
2644                            selected_prod_additional_options[optNo].count = 1;
2645                            return LOCALIZE.설명_해당선택옵션은최대N개까지구매가능합니다(1);
2646                        }
2647                        break;
2648                    case 'relative':
2649                        var _order_cnt = selected_prod_data.count;
2650                        if (_order_cnt == 0) {
2651                            $.each(selected_prod_additional_options, function (no, data) { if (data.require) { _order_cnt += data.count; } });
2652                        }
2653
2654                        if (cnt > _order_cnt) {
2655                            selected_prod_additional_options[optNo].count = _order_cnt;
2656                            return LOCALIZE.설명_0원상품갯수제한();
2657                        }
2658                        break;
2659                }
2660            }
2661        }
2662
2663        return true;
2664    };
2665
2666    var increaseOptionCount = function (optNo, success) {
2667        var $curCount = $('#prdOption' + optNo).find('input._count');
2668        var curCount = $curCount.val();
2669        if (isNaN(curCount))
2670            curCount = 1;
2671        else
2672            curCount = parseInt(curCount) + 1;
2673
2674        var optional_result = checkOptionCount(optNo, curCount);
2675        if (optional_result === true) {
2676            selected_options[optNo].count = curCount;
2677        } else {
2678            alert(optional_result);
2679        }
2680
2681
2682        var is_success = true;
2683        //1회당 구매 가능한 수량 체크
2684        if (max_prod_quantity > 0) {
2685            var _selected_count = 0;
2686            if (maximum_purchase_quantity_type === 'order') {//주문단위 기준인경우
2687                // 본품 수량 포함
2688                _selected_count += order_count;
2689                // 필수/선택 옵션 수량 포함
2690                $.each(selected_options, function (idx, obj) {
2691                    _selected_count += obj.count;
2692                });
2693            } else if (maximum_purchase_quantity_type !== 'order') { // 옵션별 수량 체크인 경우
2694                switch (maximum_purchase_quantity_type) {
2695                    // required_option 타입인 경우 필수옵션만 카운트
2696                    case 'required_option':
2697                        var $option = $('#prdOption' + optNo);
2698                        if ($option.hasClass('_selected_require_option')) {
2699                            _selected_count = selected_options[optNo].count;
2700                        }
2701                        break;
2702                    case 'optional_option':
2703                        var $option = $('#prdOption' + optNo);
2704                        if (!$option.hasClass('_selected_require_option')) {
2705                            _selected_count = selected_options[optNo].count;
2706                        }
2707                        break;
2708                    case 'prod':
2709                    default:
2710                        _selected_count = selected_options[optNo].count;
2711                        break;
2712                }
2713            }
2714            if (_selected_count > max_prod_quantity) {
2715                selected_options[optNo].count -= 1;
2716                is_success = false;
2717                alert(LOCALIZE.설명_최대구매수량(max_prod_quantity));
2718            }
2719        }
2720
2721        // 회원당 최대 구매수량 검증 (1회 구매 제한이 옵션별 타입이면 1인 제한 미적용 - 스펙)
2722        if (is_success && max_member_quantity > 0 && maximum_purchase_quantity_type === 'order') {
2723            var _member_count = order_count;
2724            $.each(selected_options, function (idx, obj) {
2725                _member_count += obj.count;
2726            });
2727            if (_member_count > max_member_quantity) {
2728                selected_options[optNo].count -= 1;
2729                is_success = false;
2730                alert(LOCALIZE.설명_회원당최대구매수량상품명포함(prod_name, max_member_quantity));
2731            }
2732        }
2733
2734        if (is_success) success();
2735    };
2736
2737    var decreaseOptionCount = function (optNo, success) {
2738        var $curCount = $('#prdOption' + optNo).find('input._count');
2739        var curCount = $curCount.val();
2740        if (isNaN(curCount))
2741            curCount = 1;
2742        else
2743            curCount = parseInt(curCount) - 1;
2744        if (curCount < 1) curCount = 1;
2745
2746        var optional_result = checkOptionCount(optNo, curCount);
2747        if (optional_result === true) {
2748            selected_options[optNo].count = curCount;
2749        } else {
2750            alert(optional_result);
2751        }
2752        success();
2753    };
2754
2755    var increaseProdAdditionalOptionCount = function (optNo, success) {
2756        var $curCount = $('#prdAdditionalOption' + optNo).find('input._count');
2757        var curCount = $curCount.val();
2758				var prod_idx = selected_prod_additional_options[optNo].prod_idx;
2759				var selected_prod_data = selected_prod_additionals.find(v=>v.idx===prod_idx);
2760        if (isNaN(curCount))
2761            curCount = 1;
2762        else
2763            curCount = parseInt(curCount) + 1;
2764
2765        var optional_result = checkProdAdditionalOptionCount(optNo, curCount);
2766        if (optional_result === true) {
2767            selected_prod_additional_options[optNo].count = curCount;
2768        } else {
2769            alert(optional_result);
2770        }
2771
2772        var is_success = true;
2773        //1회당 구매 가능한 수량 체크
2774        if (selected_prod_data.maximum_purchase_quantity > 0) {
2775            var _selected_count = 0;
2776            if (selected_prod_data.maximum_purchase_quantity_type === 'order') {//주문단위 기준인경우
2777                _selected_count = getProdAdditionalTotalQuantity(selected_prod_data.maximum_purchase_quantity_type, prod_idx);
2778            } else if (selected_prod_data.maximum_purchase_quantity_type !== 'order') {
2779                switch (selected_prod_data.maximum_purchase_quantity_type) {
2780                    case 'required_option':
2781                        var $prodAddtionalOption = $('#prdAdditionalOption' + optNo);
2782                        if ($prodAddtionalOption.hasClass('_selected_require_option')) {
2783                            _selected_count = selected_prod_additional_options[optNo].count;
2784                        }
2785                        break;
2786                    case 'optional_option':
2787                        var $prodAddtionalOption = $('#prdAdditionalOption' + optNo);
2788                        if (!$prodAddtionalOption.hasClass('_selected_require_option')) {
2789                            _selected_count = selected_prod_additional_options[optNo].count;
2790                        }
2791                        break;
2792                    case 'prod':
2793                    default:
2794                        _selected_count = selected_prod_additional_options[optNo].count;
2795                        break;
2796                }
2797            }
2798            if (_selected_count > selected_prod_data.maximum_purchase_quantity) {
2799                selected_prod_additional_options[optNo].count -= 1;
2800                is_success = false;
2801                alert(LOCALIZE.설명_최대구매수량(selected_prod_data.maximum_purchase_quantity));
2802            }
2803        }
2804
2805        if (is_success) success();
2806    };
2807
2808    var decreaseProdAdditionalOptionCount = function (optNo, success) {
2809        var $curCount = $('#prdAdditionalOption' + optNo).find('input._count');
2810        var curCount = $curCount.val();
2811        if (isNaN(curCount))
2812            curCount = 1;
2813        else
2814            curCount = parseInt(curCount) - 1;
2815        if (curCount < 1) curCount = 1;
2816
2817        var optional_result = checkProdAdditionalOptionCount(optNo, curCount);
2818        if (optional_result === true) {
2819            selected_prod_additional_options[optNo].count = curCount;
2820        } else {
2821            alert(optional_result);
2822        }
2823        success();
2824    };
2825
2826    const changeProdAdditionalOptionCount = function (optNo, toBeCount, success) {
2827        toBeCount = parseInt(toBeCount, 10);
2828        if (isNaN(toBeCount) || toBeCount < 1) toBeCount = 1;
2829
2830        const optionalResult = checkProdAdditionalOptionCount(optNo, toBeCount);
2831        if (optionalResult === true) {
2832            selected_prod_additional_options[optNo].count = toBeCount;
2833        } else {
2834            alert(optionalResult);
2835        }
2836
2837        const prodIdx = selected_prod_additional_options[optNo].prod_idx;
2838        const selectedProdData = selected_prod_additionals.find(v => v.idx === prodIdx);
2839
2840        //1회당 구매 가능한 수량 체크
2841        if (!selectedProdData || selectedProdData?.maximum_purchase_quantity === undefined) {
2842            return;
2843        }
2844
2845        if (selectedProdData.maximum_purchase_quantity > 0) {
2846            var shouldCheck = false;
2847            switch (selectedProdData.maximum_purchase_quantity_type) {
2848                case 'required_option':
2849                    var $prodAddtionalOption = $('#prdAdditionalOption' + optNo);
2850                    shouldCheck = $prodAddtionalOption.hasClass('_selected_require_option');
2851                    break;
2852                case 'optional_option':
2853                    var $prodAddtionalOption = $('#prdAdditionalOption' + optNo);
2854                    shouldCheck = !$prodAddtionalOption.hasClass('_selected_require_option');
2855                    break;
2856                case 'order':
2857                    // order 타입은 전체 합산 로직 필요
2858                    break;
2859                case 'prod':
2860                default:
2861                    shouldCheck = true;
2862                    break;
2863            }
2864
2865            if (shouldCheck && toBeCount > selectedProdData.maximum_purchase_quantity) {
2866                selected_prod_additional_options[optNo].count = selectedProdData.maximum_purchase_quantity;
2867                alert(LOCALIZE.설명_최대구매수량(selectedProdData.maximum_purchase_quantity));
2868            }
2869        }
2870
2871        if (typeof success === 'function') success();
2872    };
2873
2874    var changeOptionCount = function (optNo, optCount, success) {
2875        var prevCount = selected_options[optNo].count;
2876        optCount = parseInt(optCount);
2877        if (isNaN(optCount)) optCount = 1;
2878        if (optCount < 1) optCount = 1;
2879
2880        var optional_result = checkOptionCount(optNo, optCount);
2881        if (optional_result === true) {
2882            selected_options[optNo].count = optCount;
2883        } else {
2884            alert(optional_result);
2885        }
2886
2887        var is_success = true;
2888        //1회당 구매 가능한 수량 체크
2889        if (max_prod_quantity > 0) {
2890            var _selected_count = 0;
2891            if (maximum_purchase_quantity_type === 'order') {//주문단위 기준인경우
2892                // 본품 수량 포함
2893                _selected_count += order_count;
2894                // 필수/선택 옵션 수량 포함
2895                $.each(selected_options, function (idx, obj) {
2896                    _selected_count += obj.count;
2897                });
2898            } else if (maximum_purchase_quantity_type !== 'order') { // 옵션별 수량 체크인 경우
2899                switch (maximum_purchase_quantity_type) {
2900                    // required_option 타입인 경우 필수옵션만 카운트
2901                    case 'required_option':
2902                        var $option = $('#prdOption' + optNo);
2903                        if ($option.hasClass('_selected_require_option')) {
2904                            _selected_count = selected_options[optNo].count;
2905                        }
2906                        break;
2907                    case 'optional_option':
2908                        var $option = $('#prdOption' + optNo);
2909                        if (!$option.hasClass('_selected_require_option')) {
2910                            _selected_count = selected_options[optNo].count;
2911                        }
2912                        break;
2913                    case 'prod':
2914                    default:
2915                        _selected_count = selected_options[optNo].count;
2916                        break;
2917                }
2918            }
2919            if (_selected_count > max_prod_quantity) {
2920                selected_options[optNo].count = prevCount;
2921                is_success = false;
2922                alert(LOCALIZE.설명_최대구매수량(max_prod_quantity));
2923            }
2924        }
2925
2926        // 회원당 최대 구매수량 검증 (1회 구매 제한이 옵션별 타입이면 1인 제한 미적용 - 스펙)
2927        if (is_success && max_member_quantity > 0 && maximum_purchase_quantity_type === 'order') {
2928            var _member_count = order_count;
2929            $.each(selected_options, function (idx, obj) {
2930                _member_count += obj.count;
2931            });
2932            if (_member_count > max_member_quantity) {
2933                selected_options[optNo].count = prevCount;
2934                is_success = false;
2935                alert(LOCALIZE.설명_회원당최대구매수량상품명포함(prod_name, max_member_quantity));
2936            }
2937        }
2938
2939        if (is_success) success();
2940    };
2941
2942    var checkProdStock = function (cnt) {
2943        // 그냥 재고 체크
2944        if (prod_stock_use && !prod_stock_unlimit) {
2945            if (cnt > prod_stock) {
2946                order_count = prod_stock;
2947                return LOCALIZE.설명_현재재고부족으로N개이상구매할수없습니다(prod_stock + 1);
2948            }
2949        }
2950
2951        return true;
2952    };
2953
2954    var increaseOrderCount = function (type, success) {
2955        var o = $prod_detail.find('input._order_count_' + type);
2956        var curCount = o.val();
2957        if (isNaN(curCount))
2958            curCount = 1;
2959        else
2960            curCount = parseInt(curCount) + 1;
2961
2962        var checkProdStockResult = checkProdStock(curCount);
2963        if (checkProdStockResult === true) {
2964            order_count = curCount;
2965        } else {
2966            alert(checkProdStockResult);
2967        }
2968
2969        var is_success = true;
2970        //1회당 구매 가능한 수량 체크
2971        if (max_prod_quantity > 0 && maximum_purchase_quantity_type !== 'optional_option') {
2972            var _selected_count = order_count;
2973
2974            // order 타입이면 본상품의 필수/선택 옵션도 포함하여 검증
2975            if (maximum_purchase_quantity_type === 'order') {
2976                $.each(selected_options, function (idx, obj) {
2977                    _selected_count += obj.count;
2978                });
2979            }
2980
2981            if (_selected_count > max_prod_quantity) {
2982                order_count -= 1;
2983                is_success = false;
2984                alert(LOCALIZE.설명_최대구매수량(max_prod_quantity));
2985            }
2986        }
2987        o.val(order_count);
2988
2989        if (is_success) success();
2990    };
2991
2992    var decreaseOrderCount = function (type, success) {
2993        var o = $prod_detail.find('input._order_count_' + type);
2994        var curCount = o.val();
2995        if (isNaN(curCount))
2996            curCount = 1;
2997        else
2998            curCount = parseInt(curCount) - 1;
2999
3000        if (curCount < 1) curCount = 1;
3001
3002        var checkProdStockResult = checkProdStock(curCount);
3003        if (checkProdStockResult === true) {
3004            order_count = curCount;
3005        } else {
3006            alert(checkProdStockResult);
3007        }
3008
3009        o.val(order_count);
3010        success();
3011    };
3012
3013    var changeOrderCount = function (type, count, success, is_alert) {
3014        if (is_alert == void 0) is_alert = true;
3015        var prevCount = order_count;
3016        if (isNaN(count))
3017            count = 1;
3018        else
3019            count = parseInt(count);
3020        if (count < 1) count = 1;
3021
3022        var checkProdStockResult = checkProdStock(count);
3023        if (checkProdStockResult === true) {
3024            order_count = count;
3025        } else {
3026            if (is_alert) alert(checkProdStockResult);
3027        }
3028
3029        var is_success = true;
3030        //1회당 구매 가능한 수량 체크
3031        if (max_prod_quantity > 0 && maximum_purchase_quantity_type !== 'optional_option') {
3032            var _selected_count = order_count;
3033
3034            // order 타입이면 본상품의 필수/선택 옵션도 포함하여 검증
3035            if (maximum_purchase_quantity_type === 'order') {
3036                $.each(selected_options, function (idx, obj) {
3037                    _selected_count += obj.count;
3038                });
3039            }
3040
3041            if (_selected_count > max_prod_quantity) {
3042                order_count = prevCount;
3043                is_success = false;
3044                if (is_alert) alert(LOCALIZE.설명_최대구매수량(max_prod_quantity));
3045            }
3046        }
3047
3048        $prod_detail.find("input._order_count_" + type).val(order_count);
3049        if (is_success) success();
3050    };
3051
3052	var increaseProdAdditionalOrderCount = function (no, type, success) {
3053		var selected_prod_data = selected_prod_additionals[no];
3054		var o = $prod_detail.find(`._area_count[data-no=${no}] input._prod_additional_order_count_${type}`);
3055		var curCount = o.val();
3056		if (isNaN(curCount))
3057			curCount = 1;
3058		else
3059			curCount = parseInt(curCount) + 1;
3060
3061		var checkProdStockResult = checkProdAdditionalStock(no, curCount);
3062		if (checkProdStockResult === true) {
3063			selected_prod_data.count = curCount;
3064		} else {
3065			alert(checkProdStockResult);
3066		}
3067
3068		var is_success = true;
3069
3070		// 추가상품 자체의 제한을 확인한다.
3071		if (is_success && selected_prod_data.maximum_purchase_quantity > 0 && selected_prod_data.maximum_purchase_quantity_type !== 'optional_option') {
3072			if (selected_prod_data.count > selected_prod_data.maximum_purchase_quantity) {
3073				selected_prod_data.count -= 1;
3074				is_success = false;
3075				alert(LOCALIZE.설명_최대구매수량(selected_prod_data.maximum_purchase_quantity));
3076			}
3077		}
3078		o.val(selected_prod_data.count);
3079
3080		if (is_success) success();
3081	};
3082
3083	var decreaseProdAdditionalOrderCount = function (no, type, success) {
3084		var selected_prod_data = selected_prod_additionals[no];
3085		var o = $prod_detail.find(`._area_count[data-no=${no}] input._prod_additional_order_count_${type}`);
3086		var curCount = o.val();
3087		if (isNaN(curCount))
3088			curCount = 1;
3089		else
3090			curCount = parseInt(curCount) - 1;
3091
3092		if (curCount < 1) curCount = 1;
3093
3094		var checkProdStockResult = checkProdAdditionalStock(no, curCount);
3095		if (checkProdStockResult === true) {
3096			selected_prod_data.count = curCount;
3097		} else {
3098			alert(checkProdStockResult);
3099		}
3100
3101		o.val(selected_prod_data.count);
3102		success();
3103	};
3104
3105	var changeProdAdditionalOrderCount = function (no, type, count, success, is_alert) {
3106		var selected_prod_data = selected_prod_additionals[no];
3107		var prevCount = selected_prod_data.count;
3108		if (is_alert == void 0) is_alert = true;
3109		if (isNaN(count))
3110			count = 1;
3111		else
3112			count = parseInt(count);
3113		if (count < 1) count = 1;
3114
3115		var checkProdStockResult = checkProdAdditionalStock(no, count);
3116		if (checkProdStockResult === true) {
3117			selected_prod_data.count = count;
3118		} else {
3119			if (is_alert) alert(checkProdStockResult);
3120		}
3121
3122		var is_success = true;
3123
3124		// 추가상품 자체의 제한을 확인한다.
3125		if (is_success && selected_prod_data.maximum_purchase_quantity > 0 && selected_prod_data.maximum_purchase_quantity_type !== 'optional_option') {
3126			if (selected_prod_data.count > selected_prod_data.maximum_purchase_quantity) {
3127				selected_prod_data.count = prevCount;
3128				is_success = false;
3129				if (is_alert) alert(LOCALIZE.설명_최대구매수량(selected_prod_data.maximum_purchase_quantity));
3130			}
3131		}
3132
3133		$prod_detail.find(`._area_count[data-no=${no}] input._prod_additional_order_count_${type}`).val(selected_prod_data.count);
3134		if (is_success) success();
3135	};
3136
3137	var checkProdAdditionalStock = function (no, cnt) {
3138		var selected_prod_data = selected_prod_additionals[no];
3139		// 그냥 재고 체크
3140		if (selected_prod_data.stock_use && !selected_prod_data.stock_unlimit) {
3141			if (cnt > selected_prod_data.stock) {
3142				selected_prod_data.count = selected_prod_data.stock;
3143				return LOCALIZE.설명_현재재고부족으로N개이상구매할수없습니다(selected_prod_data.stock + 1);
3144			}
3145		}
3146
3147		return true;
3148	};
3149
3150    var selectCartType = function (type) {
3151        if (type === 'regularly') {
3152            $prod_detail.addClass('detail-regularly');
3153            // 정기구독 선택 시 수량별 할인 배너/혜택 숨김 (IMIO-7132)
3154            $prod_detail.find('._quantity_discount_banner_wrap').hide();
3155            $prod_detail.find('._quantity_discount_banner_mobile').hide();
3156            $prod_detail.find('._quantity_discount_benefits').hide();
3157        } else {
3158            $prod_detail.removeClass('detail-regularly');
3159            // 1회구매 복귀 시 수량별 할인 배너/혜택 복원
3160            $prod_detail.find('._quantity_discount_banner_mobile').show();
3161            $prod_detail.find('._quantity_discount_benefits').show();
3162            // 배너는 옵션 선택 여부에 따라 updateSelectedOptions 에서 toggle 됨
3163        }
3164        updateSelectedOptions('prod');
3165    };
3166
3167    var getCreatorDiscountMaxToastTarget = function () {
3168        var $scope = options.is_prod_detail_page ? $('.modal_prod_detail_from_shopping_list') : $(document);
3169        var $target = $scope.find('.buy_btns.mobile:visible .cart_btn:visible').first();
3170        if (!$target.length) {
3171            $target = $scope.find('.buy_btns.mobile .cart_btn').first();
3172        }
3173        if (!$target.length) {
3174            $target = $scope.find('._mobile_action_btn_modal_wrap:visible, ._mobile_action_btn_wrap:visible, .buy_btns.mobile:visible').first();
3175        }
3176        if (!$target.length) {
3177            $target = $scope.find('._mobile_action_btn_modal_wrap, ._mobile_action_btn_wrap, .buy_btns.mobile').first();
3178        }
3179        return $target;
3180    };
3181
3182    var getCreatorDiscountMaxToast = function () {
3183        var $toast = $('body').children('._creator_max_discount_toast');
3184        if (!$toast.length) {
3185            $toast = $('<div class="_creator_max_discount_toast" role="status" aria-live="polite"><i class="btl bt-exclamation-triangle" aria-hidden="true"></i><span></span></div>');
3186            $('body').append($toast);
3187        }
3188        return $toast;
3189    };
3190
3191    var positionCreatorDiscountMaxToast = function ($toast, $target) {
3192        if (!$toast || !$toast.length || !$target || !$target.length) return;
3193
3194        var rect = $target.get(0).getBoundingClientRect();
3195        var bottom = Math.max(window.innerHeight - rect.top + 16, 16);
3196        var center = rect.left + (rect.width / 2);
3197
3198        $toast.css({
3199            bottom: bottom + 'px',
3200            left: center + 'px'
3201        });
3202    };
3203
3204    var hideCreatorDiscountMaxToast = function () {
3205        if (creator_discount_max_toast_timer) {
3206            clearTimeout(creator_discount_max_toast_timer);
3207            creator_discount_max_toast_timer = null;
3208        }
3209        $('body').children('._creator_max_discount_toast').removeClass('is-visible');
3210    };
3211
3212    var showCreatorDiscountMaxToast = function () {
3213        var $target = getCreatorDiscountMaxToastTarget();
3214        if (!$target.length) return;
3215
3216        var $toast = getCreatorDiscountMaxToast();
3217        positionCreatorDiscountMaxToast($toast, $target);
3218
3219        $toast.find('span').text(creator_discount_max_toast_text);
3220        $toast.removeClass('is-visible');
3221        var nextFrame = window.requestAnimationFrame || function (callback) { return setTimeout(callback, 0); };
3222        nextFrame(function () {
3223            positionCreatorDiscountMaxToast($toast, $target);
3224            $toast.addClass('is-visible');
3225        });
3226
3227        if (creator_discount_max_toast_timer) {
3228            clearTimeout(creator_discount_max_toast_timer);
3229        }
3230        creator_discount_max_toast_timer = setTimeout(function () {
3231            $toast.removeClass('is-visible');
3232            creator_discount_max_toast_timer = null;
3233        }, 2200);
3234    };
3235
3236    var parseFreeShipNotifyPrice = function (price) {
3237        var parsed = parseFloat(price);
3238        return isNaN(parsed) ? 0 : parsed;
3239    };
3240
3241    var getFreeShipNotifyRegularPrice = function (regular_price, sale_price) {
3242        var regularPrice = parseFreeShipNotifyPrice(regular_price);
3243        var salePrice = parseFreeShipNotifyPrice(sale_price);
3244        return regularPrice > 0 ? regularPrice : salePrice;
3245    };
3246
3247    var getFreeShipNotifyMainRegularPrice = function () {
3248        return getFreeShipNotifyRegularPrice(free_ship_notify_prod_regular_price, prod_price);
3249    };
3250
3251    var setFreeShipNotifyProdAdditionalRegularPrice = function (prod_idx, regular_price) {
3252        if (!prod_idx) return;
3253        free_ship_notify_prod_additional_regular_price_map[prod_idx] = parseFreeShipNotifyPrice(regular_price);
3254    };
3255
3256    var getFreeShipNotifyProdAdditionalRegularPrice = function (prod_idx, sale_price) {
3257        return getFreeShipNotifyRegularPrice(free_ship_notify_prod_additional_regular_price_map[prod_idx], sale_price);
3258    };
3259
3260    var isFreeShipNotifyAfterSale = function () {
3261        return free_ship_notify_deliv_price_cost_type === FREE_SHIP_NOTIFY_DELIV_PRICE_COST_TYPE_AFTER;
3262    };
3263
3264    var calcFreeShipNotifyRequiredOptionPrice = function (option_price) {
3265        return parseFreeShipNotifyPrice(option_price);
3266    };
3267
3268    var calcFreeShipNotifyCreatorDiscountRaw = function (total_base, total_count) {
3269        if (!creator_discount_data) return 0;
3270        var raw = 0;
3271        if (creator_discount_data.type === 'PDT01') {
3272            raw = (parseInt(total_count, 10) || 0) * (parseInt(creator_discount_data.discount_amount, 10) || 0);
3273        } else if (creator_discount_data.type === 'PDT02') {
3274            var pct = parseFloat(creator_discount_data.percent) || 0;
3275            if (pct <= 0) return 0;
3276            raw = Math.floor((parseFreeShipNotifyPrice(total_base) * pct) / 100);
3277        }
3278        return raw;
3279    };
3280
3281    var calcFreeShipNotifyCreatorDiscount = function (total_base, total_count) {
3282        if (!creator_discount_data) return 0;
3283        var raw = calcFreeShipNotifyCreatorDiscountRaw(total_base, total_count);
3284        var cap = parseFreeShipNotifyPrice(creator_discount_data.max_price);
3285        return cap > 0 ? Math.min(raw, cap) : raw;
3286    };
3287
3288    var calcFreeShipNotifyQuantityDiscount = function (base_amount, total_count) {
3289        if (creator_discount_data || !$selected_options || !$selected_options.length) return 0;
3290
3291        var raw = $selected_options.attr('data-qd-tiers');
3292        if (!raw) return 0;
3293
3294        var tiers = null;
3295        try {
3296            tiers = JSON.parse(raw);
3297        } catch (e) {
3298            return 0;
3299        }
3300        if (!tiers || !tiers.length) return 0;
3301
3302        var matched = tiers
3303            .filter(function (tier) { return total_count >= tier.min_quantity; })
3304            .sort(function (a, b) { return b.min_quantity - a.min_quantity; })[0];
3305        if (!matched) return 0;
3306
3307        var cap = Math.max(parseFreeShipNotifyPrice(base_amount), 0);
3308        if (matched.method === 'percentage') {
3309            return Math.min(Math.ceil(cap * matched.value / 100), cap);
3310        }
3311        return Math.min(parseInt(matched.value, 10) || 0, cap);
3312    };
3313
3314    var getFreeShipNotifyInitialTotal = function () {
3315        if (!isFreeShipNotifyAfterSale()) {
3316            return getFreeShipNotifyMainRegularPrice();
3317        }
3318
3319        var totalPrice = parseFreeShipNotifyPrice(prod_price);
3320        var periodDiscountPrice = 0;
3321        var creatorUsesPeriodDiscountTarget = creator_discount_data && creator_discount_data.reference_source === 'period_discount';
3322
3323        if (!creator_discount_data || creatorUsesPeriodDiscountTarget) {
3324            periodDiscountPrice = calcPeriodDiscount(prod_price, 1);
3325            if (periodDiscountPrice > totalPrice) periodDiscountPrice = totalPrice;
3326            totalPrice -= periodDiscountPrice;
3327        }
3328
3329        var isRegularlyProd = $prod_detail && $prod_detail.hasClass('detail-regularly');
3330        var quantityDiscountPrice = isRegularlyProd ? 0 : calcFreeShipNotifyQuantityDiscount(totalPrice, 1);
3331        totalPrice -= quantityDiscountPrice;
3332
3333        if (is_main_product_membership_discount) {
3334            var membershipDiscountPrice = calcMembershipDiscount(totalPrice);
3335            if (membershipDiscountPrice > totalPrice) membershipDiscountPrice = totalPrice;
3336            totalPrice -= membershipDiscountPrice;
3337        }
3338
3339        if (creator_discount_data) {
3340            var creatorDiscountBase = creatorUsesPeriodDiscountTarget ? totalPrice : parseFreeShipNotifyPrice(prod_price);
3341            var creatorDiscountPrice = calcFreeShipNotifyCreatorDiscount(creatorDiscountBase, 1);
3342            if (creatorDiscountPrice > totalPrice) creatorDiscountPrice = totalPrice;
3343            totalPrice -= creatorDiscountPrice;
3344        }
3345
3346        return Math.max(totalPrice, 0);
3347    };
3348
3349    var refreshFreeShipNotifyInitialTotal = function () {
3350        if (require_option_count == 0 && selected_options.length == 0 && selected_prod_additionals.length == 0 && selected_prod_additional_options.length == 0 && order_count == 0) {
3351            notifyFreeShipNotifySdk(getFreeShipNotifyInitialTotal());
3352        }
3353    };
3354
3355    /**
3356     * 무료배송 안내 SDK 에 현재 합계와 필수옵션 완료 상태를 전달.
3357     * updateSelectedOptions 의 SDK dispatch 부분만 분리하여, DOM 갱신이 필요 없는
3358     * 진입 경로(예: initDetail 의 옵션 없는 상품 초기 가격 반영)에서도 재사용한다.
3359     * SDK 미로드 환경에서는 no-op.
3360     * @param {number} total_price - SDK 에 전달할 무료배송 기준 합계
3361     */
3362    var notifyFreeShipNotifySdk = function (total_price) {
3363        if (typeof window.IMWEB_SHOP_FREE_SHIP_NOTIFY_SDK === 'undefined') return;
3364        window.IMWEB_SHOP_FREE_SHIP_NOTIFY_SDK.updateSelectedOptionsTotal(total_price);
3365        // REQUIRE_OPTION 스타일에서 인라인 마그넷 표시 토글.
3366        // selectRequireOption은 필수 옵션이 모두 채워지면 selectOption 호출 후 selected_require_options를 []로 리셋하고
3367        // 확정된 옵션 조합을 selected_options에 push하므로, 진짜 "완료" 신호는 selected_options.length > 0이다.
3368        // 필수 옵션이 없는 상품(require_option_count == 0)은 항상 true.
3369        var isRequireOptionCompleted = (require_option_count == 0) || (selected_options.length > 0);
3370        window.IMWEB_SHOP_FREE_SHIP_NOTIFY_SDK.setRequireOptionCompleted(isRequireOptionCompleted);
3371    };
3372
3373    /**
3374     * 옵션선택화면갱신
3375     * type : prod (상품상세화면용), cart (장바구니 수량 변경용)
3376     */
3377    var updateSelectedOptions = function (type) {
3378        var html = '';
3379        var total_price_html = '';
3380
3381        var total_price = 0;
3382        var total_count = 0;
3383        var period_discount_price = 0;
3384        var show_period_discount = false;
3385				var membership_discount_not_involved_price = 0;  // 등급할인을 사용하지 않는 상품의 가격(등급할인 계산 과정에서 제외됨)
3386        var free_ship_notify_is_after_sale = isFreeShipNotifyAfterSale();
3387        var free_ship_notify_total_price = 0;
3388
3389        // 수량별 할인 데이터 (data-attribute에서 읽기)
3390        const _quantityDiscountTiers = (() => {
3391            const raw = $selected_options.attr('data-qd-tiers');
3392            if (!raw) return null;
3393            try { return JSON.parse(raw); } catch { return null; }
3394        })();
3395        const _quantityDiscountExcludeOptional = $selected_options.attr('data-qd-exclude-optional') === 'true';
3396        let _quantityDiscountRequireOptionCount = 0; // 필수 옵션 수량 합계
3397        let _quantityDiscountBaseRaw = 0; // 본 상품 raw 매출 합계 (기간할인 미적용, 추가상품 제외) - 수량별 할인 base
3398        // 크리에이터 전용 혜택가 — 본품 + 필수 옵션(require) 합산 가격에 한 번 할인 적용
3399        // 정책 B(필수옵션 포함, 선택옵션 제외) / Y(N건 모두 적용) / M(cap 합산 한 번)
3400        //   - PDT01: discount_amount × totalCount (Y), cap 합산 (M)
3401        //   - PDT02: floor(totalBase × percent / 100) (B+Y 자동 누적), cap 합산 (M)
3402        let _creator_discount_base = 0;  // 본품 + 필수옵션 타겟 가격 합산
3403        let _creator_discount_count = 0; // 본품 + 필수옵션 수량 합산
3404        let _prod_additional_creator_discount_price = 0; // 추가상품별 크리에이터 할인 row 누적액
3405        const _creator_uses_period_discount_target = creator_discount_data && creator_discount_data.reference_source === 'period_discount';
3406        const calcCreatorDiscountRaw = (totalBase, totalCount) => {
3407            if (!creator_discount_data) return 0;
3408            var raw = 0;
3409            if (creator_discount_data.type === 'PDT01') {
3410                raw = (parseInt(totalCount, 10) || 0) * (parseInt(creator_discount_data.discount_amount, 10) || 0);
3411            } else if (creator_discount_data.type === 'PDT02') {
3412                var pct = parseFloat(creator_discount_data.percent) || 0;
3413                if (pct <= 0) return 0;
3414                raw = Math.floor((parseInt(totalBase, 10) || 0) * pct / 100);
3415            }
3416            return raw;
3417        };
3418        const calcCreatorDiscount = (totalBase, totalCount) => {
3419            if (!creator_discount_data) return 0;
3420            var raw = calcCreatorDiscountRaw(totalBase, totalCount);
3421            var cap = parseInt(creator_discount_data.max_price, 10) || 0;
3422            return (cap > 0) ? Math.min(raw, cap) : raw;
3423        };
3424        const calcAdditionalCreatorDiscount = (additionalCreatorData, totalBase, totalCount) => {
3425            if (!additionalCreatorData) return 0;
3426            var raw = 0;
3427            if (additionalCreatorData.type === 'PDT01') {
3428                raw = (parseInt(totalCount, 10) || 0) * (parseInt(additionalCreatorData.discount_amount, 10) || 0);
3429            } else if (additionalCreatorData.type === 'PDT02') {
3430                var pct = parseFloat(additionalCreatorData.percent) || 0;
3431                if (pct <= 0) return 0;
3432                raw = Math.floor((parseInt(totalBase, 10) || 0) * pct / 100);
3433            }
3434            var cap = parseInt(additionalCreatorData.max_price, 10) || 0;
3435            return (cap > 0) ? Math.min(raw, cap) : raw;
3436        };
3437
3438        // 크리에이터 혜택가 — DOM insert 후 본품/옵션 행 가격을 합산 cap 비례 분배해 갱신
3439        // [data-creator-row] 셀렉터로 본품(book)/필수옵션(option) 가격 노드 모두 수집 → 비례 분배
3440        const _applyCreatorDiscountDistribution = ($container) => {
3441            if (!creator_discount_data) return;
3442            if (!$container || !$container.length) return;
3443            var $rows = $container.find('[data-creator-row]');
3444            if (!$rows.length) return;
3445            var totalRaw = 0;
3446            $rows.each(function () {
3447                totalRaw += parseInt($(this).attr('data-creator-raw'), 10) || 0;
3448            });
3449            if (totalRaw <= 0) return;
3450            var totalDiscount = calcCreatorDiscount(_creator_discount_base, _creator_discount_count);
3451            if (totalDiscount <= 0) return;
3452            $rows.each(function () {
3453                var $row = $(this);
3454                var raw = parseInt($row.attr('data-creator-raw'), 10) || 0;
3455                if (raw <= 0) return;
3456                var share = Math.floor(totalDiscount * raw / totalRaw);
3457                var finalPrice = raw - share;
3458                $row.text(LOCALIZE.getCurrencyFormat(finalPrice));
3459            });
3460        };
3461
3462        const _calcQuantityDiscount = (baseAmount, totalCount) => {
3463            // 크리에이터 전용 혜택가 활성 시 MSA 수량별 할인(MFE 프로모션) 무시 (정책 #2)
3464            if (creator_discount_data) return 0;
3465            if (!_quantityDiscountTiers) return 0;
3466            const matched = _quantityDiscountTiers
3467                .filter(t => totalCount >= t.min_quantity)
3468                .sort((a, b) => b.min_quantity - a.min_quantity)[0];
3469            if (!matched) return 0;
3470            const cap = Math.max(baseAmount, 0);
3471            if (matched.method === 'percentage') {
3472                // 서버 OMS 식과 동일: ceil((row 매출 - 기간할인) × 비율)
3473                return Math.min(Math.ceil(cap * matched.value / 100), cap);
3474            }
3475            // fixed_amount, fixed_price: value는 총 할인금액 (수량 무관)
3476            // 품목 금액 초과 방지 (결제페이지/OMS 와 동일한 클램핑, IMIO-7184)
3477            return Math.min(parseInt(matched.value, 10) || 0, cap);
3478        };
3479	      var is_use_prod_additional = selected_prod_additionals.length > 0 || selected_prod_additional_options.length > 0;
3480				/* 본 상품 계산 */
3481        if (require_option_count == 0) {
3482					let section_total_price = 0;
3483
3484            // 본품 행 표시 = 타겟 기준가 × 수량. 크리에이터 할인은 합계(total_price)에서만 차감.
3485            if (typeof USE_OMS !== 'undefined' && USE_OMS === true){
3486                if (creator_discount_data && !_creator_uses_period_discount_target) {
3487                    period_discount_price = 0;
3488                } else {
3489                    period_discount_price = calcPeriodDiscount(prod_price, 1);
3490							if(period_discount_price > prod_price){
3491								period_discount_price = prod_price;
3492							}
3493							period_discount_price *= order_count;
3494						}
3495                section_total_price = order_count * prod_price;
3496                section_total_price = section_total_price - period_discount_price;
3497                show_period_discount = true;
3498            } else {
3499                section_total_price = order_count * prod_price;
3500                if (creator_discount_data && !_creator_uses_period_discount_target) {
3501                    period_discount_price = 0;
3502                } else {
3503							period_discount_price = calcPeriodDiscount(prod_price, order_count);
3504							if (period_discount_price > section_total_price) period_discount_price = section_total_price;
3505						}
3506						section_total_price = section_total_price - period_discount_price;
3507						show_period_discount = true;
3508					}
3509
3510            // 필수옵션이 없을 때 기본 상품 수량 조절 html 출력
3511            if (prod_type === SHOP_CONST.PROD_TYPE.NORMAL) {
3512                // 디지털, 이용권 상품은 수량 변경 할 필요가 없으므로 출력하지 않는다
3513                switch (type) {
3514                    case 'prod':
3515                        if (!is_view_price) break;
3516                        // 상품 상세페이지
3517                        html += '<div class="opt_block no-border order_quantity_area" style="height: auto; "> ';
3518                        html += '	<div class="area_tit holder">';
3519                        html += '		<span class="option_title inline-blocked" style="margin-bottom: 7px">' + LOCALIZE.타이틀_수량() + '</span> ';
3520                        html += '	</div> ';
3521                        html += '	<div class="area_count holder">';
3522                        html += '		<div class="option_btn_wrap" style="top:0;"> ';
3523                        html += '			<div class="option_btn_tools" style="float: none;"> ';
3524                        html += '				<a href="javascript:;" onclick="SITE_SHOP_DETAIL.decreaseOrderCount(\'mobile\', function(){SITE_SHOP_DETAIL.updateSelectedOptions(\'' + type + '\')})"><i class="btl bt-minus" aria-hidden="true"></i><span class="sr-only">minus</span></a>';
3525                        html += '				<input type="text" title="number" value="' + order_count + '" class="form-control _order_count_mobile" onchange="SITE_SHOP_DETAIL.changeOrderCount(\'mobile\', $(this).val(), function(){SITE_SHOP_DETAIL.updateSelectedOptions(\'' + type + '\')})"> ';
3526                        html += '				<a href="javascript:;" onclick="SITE_SHOP_DETAIL.increaseOrderCount(\'mobile\', function(){SITE_SHOP_DETAIL.updateSelectedOptions(\'' + type + '\')})"><i class="btl bt-plus" aria-hidden="true"></i><span class="sr-only">plus</span></a>';
3527                        html += '			</div> ';
3528                        // 본품 가격은 위에서 _bookCreatorTotal 로 자체 cap 처리됨 → data-creator-row 미부착 (옵션만 비례 분배)
3529                        html += '			<div class="area_price absolute absolute_right absolute_middle"><span>' + LOCALIZE.getCurrencyFormat(section_total_price) + '</span></div>';
3530                        html += '		</div> ';
3531                        html += '	</div> ';
3532                        html += '</div> ';
3533                        break;
3534                    case 'cart':
3535                        // 장바구니
3536                        html += '<div class="opt_block no-border" style="height: auto; "> ';
3537                        html += '	<div class="col-control row"> ';
3538                        html += '		<div class="col-xs-12"><span class="option_title text-bold inline-blocked" style="margin-bottom: 7px">' + LOCALIZE.타이틀_수량() + '</span></div> ';
3539                        html += '		<div class="col-xs-12 option_btn_wrap" style="top:0;"> ';
3540                        html += '			<div class="option_btn_tools" style="float: none;"> ';
3541                        html += '				<a href="javascript:;" onclick="SITE_SHOP_CART.changeCartDecrease(\'mobile\')"><i class="btl bt-minus" aria-hidden="true"></i><span class="sr-only">minus</span></a>';
3542                        html += '				<input type="text" title="number" value="' + order_count + '" class="form-control _order_count_mobile" onchange="SITE_SHOP_CART.changeCartOrderCount(\'mobile\', $(this).val())"> ';
3543                        html += '				<a href="javascript:;" onclick="SITE_SHOP_CART.changeCartIncrease(\'mobile\')"><i class="btl bt-plus"></i><span class="sr-only">plus</span></a>';
3544                        html += '			</div> ';
3545                        html += '		</div> ';
3546                        html += '	</div> ';
3547                        html += '</div> ';
3548                        break;
3549                }
3550            }
3551						/* 본 상품 합계에 반영 */
3552	          total_count += order_count;
3553            total_price += section_total_price;
3554            if (!free_ship_notify_is_after_sale) {
3555                free_ship_notify_total_price += order_count * getFreeShipNotifyMainRegularPrice();
3556            }
3557            // 수량별 할인 base에 본품 raw 매출 누적
3558            _quantityDiscountBaseRaw += order_count * prod_price;
3559            // 본품 타겟 가격도 합산 cap base 에 누적 (creator 활성 시 합계에서 한 번 차감)
3560            if (creator_discount_data) {
3561                _creator_discount_base += _creator_uses_period_discount_target ? section_total_price : order_count * prod_price;
3562                _creator_discount_count += order_count;
3563            }
3564	          if(!is_main_product_membership_discount){
3565		          membership_discount_not_involved_price += section_total_price;
3566	          }
3567        }
3568
3569				/* 본 상품 옵션 계산 */
3570		    var require_class = '';
3571		    var quantity_changeable = false;	// 상품 옵션부분 디지털, 이용권 상품 및 선택옵션 구분 - 수량 변경 가능
3572	      let option_total_count = 0;
3573	      var option_total_price = 0;
3574		    var option_price = 0;
3575
3576        var prod_option_html = { "Y": "", "N": "" };
3577        var option_html = '';
3578        $.each(selected_options, function (no, data) {
3579            option_price = parseFloat(data.price || 0) * parseFloat(data.count);
3580            // 크리에이터 활성 시 옵션 단가는 타겟 기준가 그대로 — 합산 cap 후 비례 분배 후처리에서 갱신
3581							let option_period_discount_price = 0;
3582            if (data.require == true && (!creator_discount_data || _creator_uses_period_discount_target)){
3583
3584							if(typeof USE_OMS !== 'undefined' && USE_OMS === true){
3585		            option_period_discount_price = calcPeriodDiscount(data.price, 1);
3586		            if(option_period_discount_price > data.price){
3587			            option_period_discount_price = data.price;
3588		            }
3589		            option_period_discount_price *= data.count;
3590	            }else{
3591		            option_period_discount_price = calcPeriodDiscount(data.price, data.count);
3592		            if(option_period_discount_price > option_price){
3593			            option_period_discount_price = option_price;
3594		            }
3595	            }
3596							period_discount_price += option_period_discount_price;
3597							if (data.option_mix_type == 'MIX' || period_dc_type == 'percent') {
3598									show_period_discount = true;
3599									option_price = option_price - option_period_discount_price;
3600							}
3601            }
3602            option_total_count += parseInt(data.count);
3603            if (data.require) _quantityDiscountRequireOptionCount += parseInt(data.count);
3604            // 수량별 할인 base에 옵션 raw 매출 누적 (선택옵션 제외 설정 시 필수 옵션만)
3605            if (!_quantityDiscountExcludeOptional || data.require) {
3606                _quantityDiscountBaseRaw += parseFloat(data.price || 0) * parseFloat(data.count);
3607            }
3608            // 크리에이터 할인 base 에 필수 옵션 타겟 매출만 누적
3609            if (creator_discount_data && data.require) {
3610                _creator_discount_base += _creator_uses_period_discount_target ? option_price : parseFloat(data.price || 0) * parseFloat(data.count);
3611                _creator_discount_count += parseInt(data.count);
3612            }
3613            option_total_price += option_price;
3614            if (!free_ship_notify_is_after_sale) {
3615                var free_ship_notify_option_unit_price = parseFreeShipNotifyPrice(data.price);
3616                if (data.require) {
3617                    free_ship_notify_option_unit_price = calcFreeShipNotifyRequiredOptionPrice(free_ship_notify_option_unit_price, prod_price, free_ship_notify_prod_regular_price);
3618                }
3619                free_ship_notify_total_price += free_ship_notify_option_unit_price * parseFreeShipNotifyPrice(data.count);
3620            }
3621
3622            var option_data_html = [];
3623            $.each(data.options, function (no2, data2) {
3624                option_data_html.push(RemoveTag(data2.option_name + ': ' + data2.value_name));
3625            });
3626            quantity_changeable = !(prod_type !== SHOP_CONST.PROD_TYPE.NORMAL && data.require == true);
3627            require_class = (data.require ? '_selected_require_option ' : '');
3628
3629
3630            // 한 개마다 초기화
3631            option_html = '';
3632            if (type == 'prod') {
3633                // 상품 상세페이지
3634                option_html += '<div class="' + require_class + (no == 0 ? '' : ' middle ') + 'opt_block" id="prdOption' + no + '">';
3635                option_html += '	<div class="full-width opt_product_area">';
3636                option_html += '		<div class="area_tit holder">';
3637                option_html += '		 	<span class="body_font_color_80">' + option_data_html.join(' / ') + '</span>';
3638                option_html += '			<a class="text-18 absolute absolute_right absolute_middle" href="javascript:;
3638" onclick="SITE_SHOP_DETAIL.removeSelectedOption(' + no + ',\'prod\')"><i class="btl bt-times-circle"></i></a>';
3639                option_html += '		</div>';
3640                option_html += '		<div class="area_count holder">';
3641                option_html += '			<div class="option_btn_wrap">';
3642                if (quantity_changeable) {
3643                    option_html += '			<div class="option_btn_tools">';
3644                    option_html += '				<a href="javascript:;" onclick="SITE_SHOP_DETAIL.decreaseOptionCount(' + no + ',\'prod\')"> <i class="btl bt-minus" aria-hidden="true"></i> </a>';
3645                    option_html += '				<input type="text" value="' + data.count + '" class="form-control count _count" onchange="SITE_SHOP_DETAIL.changeOptionCount(' + no + ', $(this).val(),\'prod\')" />';
3646                    option_html += '				<a href="javascript:;" onclick="SITE_SHOP_DETAIL.increaseOptionCount(' + no + ',\'prod\')"> <i class="btl bt-plus"></i> </a>';
3647                    option_html += '			</div>';
3648                }
3649                option_html += '			</div>';
3650                // 크리에이터 활성 + 필수 옵션이면 타겟 가격 정보를 data attr 로 노출 (비례 분배 후처리용)
3651                var _optionCreatorRaw = _creator_uses_period_discount_target ? option_price : parseFloat(data.price || 0) * parseFloat(data.count);
3652                var _optionCreatorAttr = (creator_discount_data && data.require) ? ' data-creator-row="option" data-creator-raw="' + _optionCreatorRaw + '"' : '';
3653                if (quantity_changeable) {
3654									option_html += '			<div class="area_price absolute absolute_right absolute_middle"><span' + _optionCreatorAttr + '>' + LOCALIZE.getCurrencyFormat(option_price) + '</span></div>';
3655                } else {
3656									option_html += '<div class="tw-flex tw-justify-between"><div>' + LOCALIZE.타이틀_수량() + ' ' + LOCALIZE.설명_n개띄어쓰기없음(1) + '</div><div' + _optionCreatorAttr + '>' + LOCALIZE.getCurrencyFormat(option_price) + '</div></div>';
3657                }
3658                option_html += '		</div>';
3659                option_html += '	</div>';
3660                option_html += '</div>';
3661            } else {
3662                // 장바구니
3663                option_html += '<div class="' + require_class + (no == 0 ? '' : ' middle ') + 'opt_block" id="prdOption' + no + '">';
3664                option_html += '	<div class="full-width opt_product_area">';
3665                option_html += '		<div class="area_tit holder">';
3666                option_html += '			<span class="body_font_color_80">' + option_data_html.join(' / ') + '</span>';
3667                option_html += '			<a href="javascript:;" class="text-18 absolute absolute_right absolute_middle" onclick="SITE_SHOP_CART.changeCartItemRemove(' + no + ')"><i class="btl bt-times-circle"></i> </a>';
3668                option_html += '		</div>';
3669                option_html += '		<div class="area_count holder">';
3670                option_html += '			<div class="option_btn_wrap">';
3671                if (quantity_changeable) {
3672                    option_html += '				<div class="option_btn_tools">';
3673                    option_html += '					<a href="javascript:;" onclick="SITE_SHOP_CART.changeCartItemDecrease(' + no + ')"> <i class="btl bt-minus" aria-hidden="true"></i> </a>';
3674                    option_html += '					<input type="text" value="' + data.count + '" class="form-control count _count" onchange="SITE_SHOP_CART.changeCartItemCount(' + no + ', $(this).val())" />';
3675                    option_html += '					<a href="javascript:;" onclick="SITE_SHOP_CART.changeCartItemIncrease(' + no + ')"> <i class="btl bt-plus"></i> </a>';
3676                    option_html += '				</div>';
3677                }
3678                option_html += '			</div>';
3679                option_html += '			<div class="area_price absolute absolute_right absolute_middle">';
3680                // 크리에이터 활성 + 필수 옵션이면 raw 정보를 data attr 로 노출 (cart 분기에서도 동일)
3681                var _optionCreatorAttr = (creator_discount_data && data.require) ? ' data-creator-row="option" data-creator-raw="' + (parseFloat(data.price || 0) * parseFloat(data.count)) + '"' : '';
3682                option_html += '				<span' + _optionCreatorAttr + '>' + LOCALIZE.getCurrencyFormat(option_price) + '</span>';
3683                option_html += '			</div>';
3684                option_html += '		</div>';
3685                option_html += '	</div>';
3686                option_html += '</div>';
3687            }
3688
3689            // 기존처럼 옵션 구분 없이 선택 순 출력으로 사용하려면 prod_option_html 사용하는 부분 지우고 html += option_html 하면 됨
3690            prod_option_html[((data.require) ? "Y" : "N")] += option_html;
3691        });
3692
3693        html += prod_option_html["Y"];
3694        html += prod_option_html["N"];
3695
3696	     /* 본 상품 옵션 합계에 반영 */
3697	      total_count += option_total_count;
3698        total_price += option_total_price;
3699		    if(!is_main_product_membership_discount){
3700			    membership_discount_not_involved_price += option_total_price;
3701		    }
3702
3703				/* 추가상품 계산 */
3704				if(is_use_prod_additional){
3705
3706					$.each(selected_prod_additionals, function (no, data) {
3707						let prod_additional_total_count = 0;
3708						var prod_additional_total_price = 0;
3709						var prod_additional_require_option_count_by_data_idx = prod_additional_require_option_count[data.idx] || 0;
3710						if (prod_additional_require_option_count_by_data_idx == 0) {
3711
3712								prod_additional_total_count = parseInt(data.count);
3713								prod_additional_total_price = parseInt(data.count) * parseFloat(data.price);
3714								var prod_additional_creator_data = data.creator_discount_data || null;
3715								var prod_additional_creator_uses_period = prod_additional_creator_data && prod_additional_creator_data.reference_source === 'period_discount';
3716								var prod_additional_period_discount_price = 0;
3717								if (!prod_additional_creator_data || prod_additional_creator_uses_period) {
3718									prod_additional_period_discount_price = calcProdAdditionalPeriodDiscount(data.idx, data.price, data.count);
3719									if (prod_additional_period_discount_price > prod_additional_total_price) prod_additional_period_discount_price = prod_additional_total_price;
3720									// 추가상품의 경우 상품 할인 금액에 별도로 할인가를 포함하지 않고 항상 옵션가에 포함시킴
3721									prod_additional_total_price = prod_additional_total_price - prod_additional_period_discount_price;
3722								}
3723								if (prod_additional_creator_data) {
3724									var prod_additional_creator_discount_price = calcAdditionalCreatorDiscount(prod_additional_creator_data, prod_additional_total_price, data.count);
3725									if (prod_additional_creator_discount_price > prod_additional_total_price) prod_additional_creator_discount_price = prod_additional_total_price;
3726									// 추가상품 행에는 판매가/기간할인가 기준 금액을 담고, 크리에이터 할인은 하단 할인 row 에 누적한다.
3727									_prod_additional_creator_discount_price += prod_additional_creator_discount_price;
3728								}
3729								if (!free_ship_notify_is_after_sale) {
3730									free_ship_notify_total_price += parseInt(data.count) * getFreeShipNotifyProdAdditionalRegularPrice(data.idx, data.price);
3731								}
3732
3733							if(is_view_price){ // @TODO 본상품과 추가상품 가격없는 상품 분기 처리 필요
3734								// 상품 상세페이지
3735								html += '<div class="opt_block no-border order_quantity_area" style="height: auto; "> ';
3736								html += '	<div class="area_tit holder">';
3737								html += '		<div class="tw-flex tw-flex-col tw-gap-[12px]">';
3738								html += '     <span class="body_font_color_80 tw-font-bold">' + getLocalizeString('타이틀_추가상품', '', '추가 상품') + '</span>';
3739								html += '		 	<span class="body_font_color_80">' + data.name + '</span>';
3740								html += `		 	<div class="_prod_additional_option_optional goods-select-prod-additional" data-prod-idx="${data.idx}"></div>`;
3741								html += '		</div>';
3742								html += '		<a class="text-18 absolute absolute_right tw-top-0" href="javascript:;" onclick="SITE_SHOP_DETAIL.removeSelectedProdAdditional(' + data.idx + ', \'prod\')"><i class="btl bt-times-circle"></i></a> ';
3743								html += '	</div> ';
3744								html += '	<div class="area_count holder _area_count" data-no="' + no + '">';
3745								html += '		<div class="option_btn_wrap" style="top:0;"> ';
3746								if(data.prod_type === SHOP_CONST.PROD_TYPE.NORMAL){
3747									html += '			<div class="option_btn_tools" style="float: none;"> ';
3748									html += '				<a href="javascript:;" onclick="SITE_SHOP_DETAIL.decreaseProdAdditionalOrderCount(' + no + ', \'mobile\', function(){SITE_SHOP_DETAIL.updateSelectedOptions(\'' + type + '\')})"><i class="btl bt-minus" aria-hidden="true"></i><span class="sr-only">minus</span></a>';
3749									html += '				<input type="text" title="number" value="' + data.count + '" class="form-control _prod_additional_order_count_mobile" onchange="SITE_SHOP_DETAIL.changeProdAdditionalOrderCount(' + no + ', \'mobile\', $(this).val(), function(){SITE_SHOP_DETAIL.updateSelectedOptions(\'' + type + '\')})"> ';
3750									html += '				<a href="javascript:;" onclick="SITE_SHOP_DETAIL.increaseProdAdditionalOrderCount(' + no + ', \'mobile\', function(){SITE_SHOP_DETAIL.updateSelectedOptions(\'' + type + '\')})"><i class="btl bt-plus" aria-hidden="true"></i><span class="sr-only">plus</span></a>';
3751									html += '			</div> ';
3752								}
3753								html += ' </div>';
3754								if(data.prod_type === SHOP_CONST.PROD_TYPE.NORMAL){
3755									html += '			<div class="area_price absolute absolute_right absolute_middle"><span>' + LOCALIZE.getCurrencyFormat(prod_additional_total_price) + '</span></div>';
3756								}else{
3757									html += '<div class="tw-flex tw-justify-between"><div>' + LOCALIZE.타이틀_수량() + ' ' + LOCALIZE.설명_n개띄어쓰기없음(1) + '</div><div>' + LOCALIZE.getCurrencyFormat(prod_additional_total_price) + '</div></div>';
3758								}
3759								html += '	</div> ';
3760								html += '</div> ';
3761							}
3762
3763								if(!data.membership_discount || prod_additional_creator_data){
3764									membership_discount_not_involved_price += prod_additional_total_price;
3765								}
3766						}
3767
3768						/* 추가상품 합계에 반영 */
3769						total_count += prod_additional_total_count;
3770						total_price += prod_additional_total_price;
3771					});
3772
3773
3774					/* 추가상품 옵션 계산 */
3775					require_class = '';
3776					quantity_changeable = false;	// 상품 옵션부분 디지털, 이용권 상품 및 선택옵션 구분 - 수량 변경 가능
3777					option_total_count = 0;
3778					option_total_price = 0;
3779
3780					prod_option_html = { "Y": "", "N": "" };
3781					option_html = '';
3782					$.each(selected_prod_additional_options, function (no, data) {
3783						let prod_data = selected_prod_additionals.find(v=>v.idx===data.prod_idx);
3784						if(typeof prod_data === 'undefined') return true; // prod_data가 확인되지 않을 경우 continue 처리
3785							let option_price = parseFloat(data.price || 0) * parseFloat(data.count);
3786							var option_period_discount_price = 0;
3787							var prod_additional_option_creator_data = prod_data.creator_discount_data || null;
3788							var prod_additional_option_creator_uses_period = prod_additional_option_creator_data && prod_additional_option_creator_data.reference_source === 'period_discount';
3789							if (data.require && (!prod_additional_option_creator_data || prod_additional_option_creator_uses_period)) {
3790								option_period_discount_price = calcProdAdditionalPeriodDiscount(data.prod_idx, data.price, data.count);
3791								if (option_period_discount_price > option_price) {
3792									option_period_discount_price = option_price;
3793								}
3794
3795								// 추가상품 옵션의 경우 상품 할인 금액에 별도로 할인가를 포함하지 않고 항상 옵션가에 포함시킴
3796								option_price = option_price - option_period_discount_price;
3797							}
3798							if (data.require && prod_additional_option_creator_data) {
3799								var prod_additional_option_creator_discount_price = calcAdditionalCreatorDiscount(prod_additional_option_creator_data, option_price, data.count);
3800								if (prod_additional_option_creator_discount_price > option_price) prod_additional_option_creator_discount_price = option_price;
3801								// 추가상품 옵션 행에는 판매가/기간할인가 기준 금액을 담고, 크리에이터 할인은 하단 할인 row 에 누적한다.
3802								_prod_additional_creator_discount_price += prod_additional_option_creator_discount_price;
3803							}
3804							option_total_count += parseInt(data.count);
3805							option_total_price += option_price;
3806							if (!free_ship_notify_is_after_sale) {
3807								var prod_additional_regular_price = getFreeShipNotifyProdAdditionalRegularPrice(prod_data.idx, prod_data.price);
3808								var free_ship_notify_prod_additional_option_unit_price = parseFreeShipNotifyPrice(data.price);
3809								if (data.require) {
3810									free_ship_notify_prod_additional_option_unit_price = calcFreeShipNotifyRequiredOptionPrice(free_ship_notify_prod_additional_option_unit_price, prod_data.price, prod_additional_regular_price);
3811								}
3812								free_ship_notify_total_price += free_ship_notify_prod_additional_option_unit_price * parseFreeShipNotifyPrice(data.count);
3813							}
3814
3815							if(!prod_data.membership_discount || (data.require && prod_additional_option_creator_data)){
3816								membership_discount_not_involved_price += option_price;
3817							}
3818
3819						var option_data_html = [];
3820						$.each(data.options, function (no2, data2) {
3821							option_data_html.push(RemoveTag(data2.option_name + ': ' + data2.value_name));
3822						});
3823						quantity_changeable = !(prod_data.prod_type !== SHOP_CONST.PROD_TYPE.NORMAL && data.require == true);
3824						require_class = (data.require ? '_selected_require_option ' : '');
3825
3826						// 한 개마다 초기화
3827						option_html = '';
3828						// 상품 상세페이지
3829						option_html += '<div class="' + require_class + (no == 0 ? '' : ' middle ') + 'opt_block" id="prdAdditionalOption' + no + '">';
3830						option_html += '	<div class="full-width opt_product_area">';
3831						option_html += '		<div class="area_tit holder">';
3832						option_html += '		 	<div class="tw-flex tw-flex-col tw-gap-[12px]">';
3833						option_html += '        <span class="body_font_color_80 tw-font-bold">' + getLocalizeString('타이틀_추가상품', '', '추가 상품') + '</span>';
3834						option_html += '		 	  <span class="body_font_color_80">' + prod_data.name + ' ' + option_data_html.join(' / ') + '</span>';
3835						if (data.require){
3836							option_html += `		 		<div class="_prod_additional_option_optional goods-select-prod-additional" data-prod-idx="${data.prod_idx}"></div>`;
3837						}
3838						option_html += '		 	</div>';
3839						option_html += '			<a class="text-18 absolute absolute_right tw-top-0" href="javascript:;" onclick="SITE_SHOP_DETAIL.removeSelectedProdAdditionalOption(' + no + ',\'prod\')"><i class="btl bt-times-circle"></i></a>';
3840						option_html += '		</div>';
3841						option_html += '		<div class="area_count holder">';
3842						option_html += '			<div class="option_btn_wrap">';
3843						if (quantity_changeable) {
3844							option_html += '			<div class="option_btn_tools">';
3845							option_html += '				<a href="javascript:;" onclick="SITE_SHOP_DETAIL.decreaseProdAdditionalOptionCount(' + no + ')"> <i class="btl bt-minus" aria-hidden="true"></i> </a>';
3846							option_html += '				<input type="text" value="' + data.count + '" class="form-control count _count" onchange="SITE_SHOP_DETAIL.changeProdAdditionalOptionCount(' + no + ', $(this).val())" />';
3847							option_html += '				<a href="javascript:;" onclick="SITE_SHOP_DETAIL.increaseProdAdditionalOptionCount(' + no + ')"> <i class="btl bt-plus"></i> </a>';
3848							option_html += '			</div>';
3849						}
3850						option_html += '			</div>';
3851						if (quantity_changeable) {
3852							option_html += '			<div class="area_price absolute absolute_right absolute_middle"><span>' + LOCALIZE.getCurrencyFormat(option_price) + '</span></div>';
3853						} else {
3854							option_html += '<div class="tw-flex tw-justify-between"><div>' + LOCALIZE.타이틀_수량() + ' ' + LOCALIZE.설명_n개띄어쓰기없음(1) + '</div><div>' + LOCALIZE.getCurrencyFormat(option_price) + '</div></div>';
3855						}
3856						option_html += '		</div>';
3857						option_html += '	</div>';
3858						option_html += '</div>';
3859
3860						// 기존처럼 옵션 구분 없이 선택 순 출력으로 사용하려면 prod_option_html 사용하는 부분 지우고 html += option_html 하면 됨
3861						prod_option_html[((data.require) ? "Y" : "N")] += option_html;
3862					});
3863
3864					html += prod_option_html["Y"];
3865					html += prod_option_html["N"];
3866
3867					total_count += option_total_count;
3868					total_price += option_total_price;
3869				}
3870
3871				/* 전체 내역 표시 */
3872
3873        // 선택된 옵션이 없으면 출력하지 않음
3874        if (total_count > 0) {
3875            // 하단에 결제 예상 총 금액 표기
3876            switch (type) {
3877                case 'prod':
3878                    // 상품 상세페이지
3879                    if (!is_view_price) break;
3880
3881                    const is_mobile_view = $(window).width() < 768;
3882                    const left_text_class = is_mobile_view ? '' : 'text-left';
3883                    var total_price_class = is_mobile_view ? 'non_member ' : '';
3884                    const discount_item_style = is_mobile_view ? 'padding-bottom: 4px;' : 'padding: 0 0 16px 0';
3885
3886                    // 할인 항목 HTML 생성 헬퍼
3887                    const discountRowHtml = (label, price, hide_row = false) => {
3888                        if (price <= 0) return '';
3889                        total_price -= price;
3890                        // 행을 숨겨도 결제 예상 금액의 할인 차감은 유지한다.
3891                        if (hide_row) return '';
3892                        return `<p class="text-right"><span class="body_font_color_70 ${left_text_class}" style="${discount_item_style}">${label}</span><span class="period_discount_price" style="${discount_item_style}">${LOCALIZE.getCurrencyFormat(price)}</span></p>`;
3893                    };
3894
3895                    // 수량별 할인 계산 (정기구독 시 미적용: IMIO-7132)
3896                    const _isRegularlyProd = $prod_detail.hasClass('detail-regularly');
3897                    const _quantityDiscountCount = _quantityDiscountExcludeOptional ? (order_count + _quantityDiscountRequireOptionCount) : total_count;
3898                    const _quantityDiscountBase = _quantityDiscountBaseRaw - period_discount_price;
3899                    const quantity_discount_price = _isRegularlyProd ? 0 : _calcQuantityDiscount(_quantityDiscountBase, _quantityDiscountCount);
3900
3901                    // 등급 할인 계산 (기간할인 + 수량별 할인 차감 후 금액 기준)
3902                    let membership_discount_target_price = total_price;
3903                    if (membership_discount_not_involved_price > 0) {
3904                        membership_discount_target_price -= membership_discount_not_involved_price;
3905                    }
3906                    if (period_discount_price > 0 && show_period_discount === false) {
3907                        membership_discount_target_price -= period_discount_price;
3908                    }
3909                    membership_discount_target_price -= quantity_discount_price;
3910                    const membership_discount_price = calcMembershipDiscount(membership_discount_target_price);
3911
3912                    // 크리에이터 전용 혜택가 — 본품/옵션 단가는 정상가로 누적되어 있음.
3913                    // 합산 raw(_creator_discount_base) 에 정책 M(합산 cap) 정확 적용 후 합계에서 차감.
3914                    // 본품/옵션 행 표시는 DOM insert 후 _applyCreatorDiscountDistribution() 후처리에서 비례 분배 갱신.
3915                    let creator_discount_price = 0;
3916                    let creator_discount_raw_price = 0;
3917                    let is_creator_discount_max_reached_now = false;
3918                    if (creator_discount_data) {
3919                        creator_discount_raw_price = calcCreatorDiscountRaw(_creator_discount_base, _creator_discount_count);
3920                        creator_discount_price = calcCreatorDiscount(_creator_discount_base, _creator_discount_count);
3921                        if (creator_discount_price > total_price) creator_discount_price = total_price;
3922                        const creator_discount_max_price = parseInt(creator_discount_data.max_price, 10) || 0;
3923                        is_creator_discount_max_reached_now = creator_discount_max_price > 0 && creator_discount_raw_price >= creator_discount_max_price;
3924                    }
3925                    creator_discount_price += _prod_additional_creator_discount_price;
3926                    if (creator_discount_price > total_price) creator_discount_price = total_price;
3927
3928                    if (is_mobile_view) {
3929                        if (is_creator_discount_max_reached_now && !creator_discount_max_reached) {
3930                            showCreatorDiscountMaxToast();
3931                        } else if (!is_creator_discount_max_reached_now) {
3932                            hideCreatorDiscountMaxToast();
3933                        }
3934                        creator_discount_max_reached = is_creator_discount_max_reached_now;
3935                    } else {
3936                        creator_discount_max_reached = is_creator_discount_max_reached_now;
3937                        hideCreatorDiscountMaxToast();
3938                    }
3939
3940                    // 표기 순서: 상품 할인 → 수량별 할인 → 등급 할인 → 크리에이터 할인
3941                    const period_discount_html = (period_discount_price > 0 && show_period_discount === false)
3942                        ? discountRowHtml(LOCALIZE.설명_상품할인금액(), period_discount_price) : '';
3943                    const quantity_discount_html = discountRowHtml('수량별 할인', quantity_discount_price);
3944                    const membership_discount_html = discountRowHtml(LOCALIZE.설명_회원등급할인(), membership_discount_price);
3945                    const hide_creator_discount_row = creator_discount_data
3946                        && creator_discount_data.hide_calculator_row === true;
3947                    const creator_discount_html = discountRowHtml(
3948                        '크리에이터 할인',
3949                        creator_discount_price,
3950                        hide_creator_discount_row
3951                    );
3952
3953                    const discount_style = is_mobile_view ? 'padding-bottom: 4px;' : 'padding: 16px 0';
3954                    const has_visible_creator_discount = creator_discount_price > 0 && !hide_creator_discount_row;
3955                    const hasDiscount = period_discount_price > 0
3956                        || membership_discount_price > 0
3957                        || quantity_discount_price > 0
3958                        || has_visible_creator_discount;
3959
3960                    total_price = Math.max(total_price, 0);
3961                    total_price_html += `<div class="opt_block total bottom">`;
3962                    total_price_html += period_discount_html + quantity_discount_html + membership_discount_html + creator_discount_html;
3963                    if (!is_mobile_view && hasDiscount) {
3964                        total_price_html += `<p style="border-bottom: 1px solid rgba(0, 0, 0, 0.1); position: absolute; width: 100%; display: block;"></p>`;
3965                    }
3966                    total_price_html += `<p class="no-margin text-right"><span class="body_font_color_70 ${left_text_class}" style="${discount_style}">${LOCALIZE.설명_결제예상금액임시(total_count)}</span><span class="total_price ${total_price_class}" style="${discount_style}">${LOCALIZE.getCurrencyFormat(total_price)}</span></p></div>`;
3967
3968                    const $footer_price_wrap = $prod_detail.find('._footer_price_wrap');
3969                    if ($footer_price_wrap.length) {
3970                        $footer_price_wrap.find('._pay_label').text(LOCALIZE.설명_결제예상금액임시(total_count)).show();
3971                        $footer_price_wrap.find('._pay_number').text(LOCALIZE.getCurrencyFormat(total_price)).show();
3972                        $footer_price_wrap.find('._pay_org_label').hide();
3973                        $footer_price_wrap.find('._pay_org_number').hide();
3974                    }
3975                    break;
3976                case 'cart':
3977                    // 장바구니
3978                    if (period_discount_price > 0 && show_period_discount == false) {
3979                        // 상품 할인금액
3980                        total_price_html += '<div class="opt_block total bottom">';
3981                        total_price_html += '	<div class="col-xs-4 vertical-middle"> <span class="body_font_color_70 text-13">' + LOCALIZE.설명_상품할인금액() + '</span> </div>';
3982                        total_price_html += '	<div class="col-xs-8 vertical-middle">';
3983                        total_price_html += '		<p class="align_r body_font_color_50 text-13">' + total_price_localize_text + ' <span class="text-brand vertical-middle">' + LOCALIZE.getCurrencyFormat(period_discount_price) + '</span></p>';
3984                        total_price_html += '	</div>';
3985                        total_price_html += '</div>';
3986                        total_price = (total_price - period_discount_price);
3987                    }
3988
3989                    total_price_html += '<div class="opt_block total bottom">';
3990                    total_price_html += '	<div class="col-xs-4 vertical-middle"> <span class="body_font_color_70 text-13">' + LOCALIZE.타이틀_총수량(total_count) + '</span> </div>';
3991                    total_price_html += '	<div class="col-xs-8 vertical-middle">';
3992                    total_price_html += '		<p class="align_r body_font_color_50 text-13">' + total_price_localize_text + ' <span class="text-brand vertical-middle">' + LOCALIZE.getCurrencyFormat(total_price) + '</span></p>';
3993                    total_price_html += '	</div>';
3994                    total_price_html += '</div>';
3995                    break;
3996            }
3997        } else {
3998            if (type === 'prod') {
3999                creator_discount_max_reached = false;
4000                hideCreatorDiscountMaxToast();
4001                if ($prod_detail.find('._footer_price_wrap').length > 0) {
4002                    // 선택된 옵션이 없을 경우 총 상품금액이 아닌 판매가 출력
4003                    var $footer_price_wrap = $prod_detail.find('._footer_price_wrap');
4004                    $footer_price_wrap.find('._pay_org_label').show();
4005                    $footer_price_wrap.find('._pay_org_number').show();
4006                    $footer_price_wrap.find('._pay_label').hide();
4007                    $footer_price_wrap.find('._pay_number').hide();
4008                }
4009            }
4010        }
4011
4012				// 무료배송 안내 SDK 에 무료배송 기준 합계 / 필수옵션 완료 상태 전달
4013				if (free_ship_notify_is_after_sale) {
4014					free_ship_notify_total_price = total_price;
4015				}
4016				notifyFreeShipNotifySdk(Math.max(free_ship_notify_total_price, 0));
4017				//
4018
4019        html += total_price_html;
4020        $selected_options.html(html);
4021
4022        // 크리에이터 혜택가 — 본품/옵션 행은 정상가 표시. 할인은 합계(total_price) 에서만 차감 완료.
4023        // (이전 비례 분배 후처리는 폐기. _applyCreatorDiscountDistribution 미호출)
4024
4025        // 수량별 할인 배너: 옵션 선택 시 표시, 선택 해제 시 숨김 (정기구독 시 미노출: IMIO-7132)
4026        if (type === 'prod') {
4027            var _isRegularly = $prod_detail.hasClass('detail-regularly');
4028            $prod_detail.find('._quantity_discount_banner_wrap').toggle(total_count > 0 && !_isRegularly);
4029
4030            if (_hasFreeShipNotifyInlineMagnet
4031                && typeof window.IMWEB_SHOP_FREE_SHIP_NOTIFY_SDK !== 'undefined'
4032                && typeof window.IMWEB_SHOP_FREE_SHIP_NOTIFY_SDK.syncInlineMagnetItemCountsFromAdditionals === 'function') {
4033                var inlineMagnetSyncList = selected_prod_additionals.map(function (additional) {
4034                    var requireOptionCount = prod_additional_require_option_count[additional.idx] || 0;
4035                    var optionLines = selected_prod_additional_options.filter(function (o) {
4036                        return o.prod_idx === additional.idx;
4037                    });
4038                    var optionLinesCount = optionLines.reduce(function (sum, o) { return sum + (o.count || 0); }, 0);
4039                    var totalCount = requireOptionCount > 0
4040                        ? optionLinesCount
4041                        : (optionLines.length > 0 ? optionLinesCount : (additional.count || 0));
4042                    return { idx: additional.idx, count: totalCount };
4043                });
4044                window.IMWEB_SHOP_FREE_SHIP_NOTIFY_SDK.syncInlineMagnetItemCountsFromAdditionals(inlineMagnetSyncList);
4045            }
4046        }
4047
4048				loadProdAdditionalOptionalList();
4049    };
4050
4051    /**
4052     * 옵션 글자 제한 체크 (네이버페이 주문형이 활성화되어 있는 상품의 경우 글자 제한)
4053     * @param target
4054     * @param limit
4055     */
4056    const checkOptionLength = function (target, limit) {
4057        const input_option_length_tooltip = target.parentNode.querySelector('._input_option_length_tooltip');
4058        if (input_option_length_tooltip) {
4059            if (target.value.length >= limit) {
4060                input_option_length_tooltip.classList.remove('tw-hidden');
4061            } else {
4062                input_option_length_tooltip.classList.add('tw-hidden');
4063            }
4064        }
4065    };
4066
4067    var membership_discount_data = {};
4068		var is_main_product_membership_discount = false;
4069    var setMembershipSaleData = function (data, _is_main_product_membership_discount) {
4070        if (typeof data != 'undefined') {
4071            membership_discount_data = data;
4072	          is_main_product_membership_discount = _is_main_product_membership_discount;
4073            refreshFreeShipNotifyInitialTotal();
4074        }
4075    };
4076
4077    var calcMembershipDiscount = function (price) {
4078        var sale_price = 0;
4079
4080        // 크리에이터 전용 혜택가 활성 시 회원그룹(등급) 할인 무시 (정책 #1)
4081        if (creator_discount_data) {
4082            return sale_price;
4083        }
4084
4085        if ($.isEmptyObject(membership_discount_data)) {
4086            return sale_price;
4087        }
4088
4089        if (price >= membership_discount_data['minimum']) {
4090            sale_price = membership_discount_data['amount'];
4091            if (membership_discount_data['type'] == 'percent') {
4092                sale_price = (price * membership_discount_data['amount']) / 100;
4093                if (sale_price > membership_discount_data['maximum']) {
4094                    sale_price = membership_discount_data['maximum'];
4095                }
4096            }
4097        }
4098
4099        if (typeof USE_OMS !== 'undefined' && USE_OMS === true) {
4100            const decimalCount = LOCALIZE.getCurrencyDecimalCount();
4101            if (decimalCount === 0) {
4102                return Math.ceil(sale_price > price ? price : sale_price);
4103            }
4104
4105            const pow = Math.pow(10, decimalCount);
4106            return Math.ceil((sale_price > price ? price : sale_price) * pow) / pow;
4107        } else {
4108            return sale_price > price ? price : sale_price;
4109        }
4110    };
4111
4112    var period_discount_data = {};
4113    var setPeriodDiscountData = function (flag, data, group_list) {
4114        if (typeof data != 'undefined' && data != null) {
4115            period_discount_data = data;
4116            period_discount_data['switch'] = flag;
4117            period_discount_data['group_list'] = group_list;
4118            refreshFreeShipNotifyInitialTotal();
4119        }
4120    };
4121
4122    var period_dc_type;
4123    var calcPeriodDiscount = function (price, count) {
4124        var sale_price = 0;
4125        var dc_price;
4126        if ($.isEmptyObject(period_discount_data)) {
4127            return sale_price;
4128        }
4129        if (period_discount_data['switch'] === false || period_discount_data['switch'] == 'false') return sale_price;
4130        var group_list = period_discount_data['group_list'];
4131
4132        for (var target in period_discount_data['target']) {
4133            var discount_data = period_discount_data['target'][target];
4134
4135            // dc_amount 검증 및 정규화
4136            var dcAmount;
4137            if (typeof discount_data['dc_amount'] === 'string') {
4138                var amountStr = discount_data['dc_amount'];
4139                // EUR 통화는 점을 천단위 구분자로, 쉼표를 소수점으로 사용
4140                if (typeof LOCALIZE !== 'undefined' && LOCALIZE.getCurrency() === 'EUR') {
4141                    amountStr = amountStr.replace(/\./g, '').replace(',', '.');
4142                } else {
4143                    amountStr = amountStr.replace(/,/g, '');
4144                }
4145                dcAmount = parseFloat(amountStr);
4146            } else if (typeof discount_data['dc_amount'] === 'number') {
4147                dcAmount = discount_data['dc_amount'];
4148            } else {
4149                continue; // 유효하지 않은 타입인 경우 다음 항목으로
4150            }
4151
4152            if (Number.isNaN(dcAmount) || dcAmount < 0) {
4153                continue; // NaN이거나 음수인 경우 다음 항목으로
4154            }
4155
4156            // 정규화된 값을 다시 할당
4157            discount_data['dc_amount'] = dcAmount;
4158
4159            if (IS_GUEST) {
4160                if (discount_data['group_type'] != 'guest') continue;
4161            } else {
4162                if (discount_data['group_type'] == 'group' && group_list.indexOf(discount_data['group_code']) == -1) continue;
4163            }
4164            if (discount_data['dc_type'] == 'percent') {
4165                if (discount_data['dc_amount'] > 100) discount_data['dc_amount'] = 100;
4166
4167                if (typeof USE_OMS !== 'undefined' && USE_OMS === true){
4168                  const decimalCount = LOCALIZE.getCurrencyDecimalCount();
4169	                if (decimalCount === 0) {
4170                      dc_price = Math.ceil(( price * discount_data['dc_amount'] ) / 100 * count );
4171                  } else {
4172										const pow = Math.pow(10, decimalCount);
4173                    dc_price = Math.ceil(( ( price * discount_data['dc_amount'] ) / 100 * count ) * pow) / pow;
4174	                }
4175                } else {
4176	                dc_price = (price * discount_data['dc_amount']) / 100 * count;
4177                }
4178            } else {
4179                dc_price = discount_data['dc_amount'] * count;
4180            }
4181            if (sale_price < dc_price) {
4182                sale_price = dc_price;
4183                period_dc_type = discount_data['dc_type'];
4184            }
4185        }
4186
4187        return sale_price;
4188    };
4189
4190	var prod_additional_period_discount_data = {};
4191	var setProdAdditionalPeriodDiscountData = function (prod_idx, flag, data, group_list) {
4192		if (typeof data != 'undefined' && data != null) {
4193			prod_additional_period_discount_data[prod_idx] = data;
4194			prod_additional_period_discount_data[prod_idx]['switch'] = flag;
4195			prod_additional_period_discount_data[prod_idx]['group_list'] = group_list;
4196		}
4197	};
4198
4199	var prod_additional_period_dc_type = {};
4200	var calcProdAdditionalPeriodDiscount = function (prod_idx, price, count) {
4201		var sale_price = 0;
4202		var dc_price;
4203		if ($.isEmptyObject(prod_additional_period_discount_data[prod_idx])) {
4204			return sale_price;
4205		}
4206		if (prod_additional_period_discount_data[prod_idx]['switch'] === false || prod_additional_period_discount_data[prod_idx]['switch'] == 'false') return sale_price;
4207		var group_list = prod_additional_period_discount_data[prod_idx]['group_list'];
4208
4209		for (var target in prod_additional_period_discount_data[prod_idx]['target']) {
4210			var discount_data = prod_additional_period_discount_data[prod_idx]['target'][target];
4211
4212            // dc_amount 검증 및 정규화
4213            var dcAmount;
4214            if (typeof discount_data['dc_amount'] === 'string') {
4215                var amountStr = discount_data['dc_amount'];
4216                // EUR 통화는 점을 천단위 구분자로, 쉼표를 소수점으로 사용
4217                if (typeof LOCALIZE !== 'undefined' && LOCALIZE.getCurrency() === 'EUR') {
4218                    amountStr = amountStr.replace(/\./g, '').replace(',', '.');
4219                } else {
4220                    amountStr = amountStr.replace(/,/g, '');
4221                }
4222                dcAmount = parseFloat(amountStr);
4223            } else if (typeof discount_data['dc_amount'] === 'number') {
4224                dcAmount = discount_data['dc_amount'];
4225            } else {
4226                continue; // 유효하지 않은 타입인 경우 다음 항목으로
4227            }
4228
4229            if (Number.isNaN(dcAmount) || dcAmount < 0) {
4230                continue; // NaN이거나 음수인 경우 다음 항목으로
4231            }
4232
4233            // 정규화된 값을 다시 할당
4234            discount_data['dc_amount'] = dcAmount;
4235
4236			if (IS_GUEST) {
4237				if (discount_data['group_type'] != 'guest') continue;
4238			} else {
4239				if (discount_data['group_type'] == 'group' && group_list.indexOf(discount_data['group_code']) == -1) continue;
4240			}
4241			if (discount_data['dc_type'] == 'percent') {
4242				if (discount_data['dc_amount'] > 100) discount_data['dc_amount'] = 100;
4243				dc_price = (price * discount_data['dc_amount']) / 100 * count;
4244			} else {
4245				dc_price = discount_data['dc_amount'] * count;
4246			}
4247			if (sale_price < dc_price) {
4248				sale_price = dc_price;
4249				prod_additional_period_dc_type[prod_idx] = discount_data['dc_type'];
4250			}
4251		}
4252
4253		return sale_price;
4254	};
4255
4256    /**
4257     * 장바구니에 추가
4258     */
4259    var addCart = async function (callback, options = {}) {
4260        // 배송 국가 선택 유효성 검사
4261        if ($('._deliv_country_selector').length > 0 && (!$deliv_country || $deliv_country === 'none')) {
4262            $('._deliv_country_selector').addClass('warning_select');
4263            alert(typeof LOCALIZE.설명_배송받을국가를선택해주세요 === 'function' ? LOCALIZE.설명_배송받을국가를선택해주세요() : '배송받을 국가를 선택해주세요.');
4264            return;
4265        }
4266
4267        const skipShippingValidation = options.skipShippingValidation || false;
4268        if (!skipShippingValidation) {
4269            const verified = await SHIPPING_SERVICE.verify();
4270            if (!verified) {
4271                return;
4272            }
4273        }
4274
4275        cart_type = $('input[name=add_cart_type]:checked').val();
4276        $.ajax({
4277            type: 'POST',
4278            data: {
4279                'prodIdx': current_prod_idx,
4280                'options': selected_options,
4281                'orderCount': order_count,
4282                'deliv_type': $deliv_type,
4283                'deliv_pay_type': $deliv_pay_type,
4284                'deliv_country': $deliv_country,
4285                'cart_type': cart_type,
4286                'prod_additional_list': selected_prod_additionals,
4287                'prod_additional_option_list': selected_prod_additional_options,
4288                'shipping_template_code': selected_shipping_template_code
4289            },
4290            url: ('/shop/add_cart.cm'),
4291            dataType: 'json',
4292            success: function (result) {
4293                if (result.msg == 'SUCCESS') {
4294                    if (typeof NP_LOG != 'undefined') NP_LOG.AddToCart();
4295                    if (typeof NP_NDA != 'undefined') NP_NDA.AddToCart(result.prod_code);
4296                    if (typeof CRITEO != 'undefined') CRITEO.AddToCart(result.prod_id, result.total_price);
4297                    if (Object.prototype.hasOwnProperty.call(result, 'meta_compatibility')) {
4298                        // 구 provider에는 송신을 위임하지 않는다. 대상 응답이 잘못되어도 미송신을 유지한다.
4299                        try {
4300                            if (typeof FB_PIXEL !== 'undefined' && FB_PIXEL && typeof FB_PIXEL.AddToCartDryRun === 'function') {
4301                                FB_PIXEL.AddToCartDryRun(result.prod_id, result.prod_name, result.cart_price, result.currency, result.prod_count, result.total_price, result.fb_event_id, result.fb_external_id, result.meta_compatibility);
4302                            } else if (typeof console !== 'undefined' && typeof console.info === 'function') {
4303                                console.info('[FS-3585][META_EVENT_DRY_RUN]', {feature_key: 'FS-3585', mode: 'dry_run', provider: 'meta', event_name: 'AddToCart', disposition: 'provider_unsupported'});
4304                            }
4305                        } catch (e) { /* 관측 오류와 무관하게 후속 UI 및 다른 광고 채널을 계속 실행한다. */ }
4306                    } else {
4307                        if (typeof FB_PIXEL != 'undefined') FB_PIXEL.AddToCart(result.prod_id, result.prod_name, result.cart_price, result.currency, result.prod_count, result.total_price, result.fb_event_id, result.fb_external_id);
4308                    }
4309                    if (typeof ACE_COUNTER != 'undefined') ACE_COUNTER.AddToCart(result.prod_id, result.prod_name, result.prod_count);
4310                    if (typeof CHANNEL_PLUGIN != 'undefined') CHANNEL_PLUGIN.AddToCart(result.prod_id, result.prod_count, result.total_price, result.currency);
4311                    if (typeof ACE_COUNTER_PARTNER != 'undefined') ACE_COUNTER_PARTNER.AddToCart(result.prod_id, result.prod_count, result.check_quantity);
4312                    if (typeof AW_PRODUCT != 'undefined') AW_PRODUCT(result.prod_count);
4313                    if (typeof AM_PRODUCT != 'undefined') AM_PRODUCT(result.prod_count);
4314                    if (typeof TIKTOK_PIXEL != 'undefined') {
4315                        TIKTOK_PIXEL.track('AddToCart', {
4316                            contents: [
4317                                {
4318                                    content_id: result.prod_id,
4319                                    content_name: result.prod_name,
4320                                    quantity: result.prod_count,
4321                                    price: result.cart_price
4322                                },
4323                            ],
4324                            event_id: result.tiktok_event_id,
4325                            content_type: 'product',
4326                            value: result.total_price,
4327                            currency: result.currency,
4328                        });
4329                    }
4330                    if (typeof CRM_ONSITE != 'undefined') {
4331		                    CRM_ONSITE.track({
4332			                    name: 'ADD_TO_CART',
4333			                    properties: {
4334				                    productCode: result.prod_code,
4335				                    categoryCodes: result.category_codes || []
4336			                    }
4337		                    });
4338                    }
4339                    // BO(마케팅 > 고급 전환추적)에서 설정한 커스텀 추적 코드 주입.
4340                    // append 시 인라인 스크립트가 동기 실행되므로, 잘못된 코드가 저장된 경우
4341                    // 예외가 밖으로 전파되어 뱃지 갱신·담기 완료 알림 등 후속 처리가 막히지 않도록 영역별로 격리한다.
4342                    if (result.advanced_trace_data != null) {
4343                        var appendAdvancedTraceCode = function (selector, code) {
4344                            if (code == '') return;
4345                            try {
4346                                $(selector).append(code);
4347                            } catch (e) {
4348                                if (window.console && window.console.error) {
4349                                    window.console.error('[ADVANCED_TRACE] add_cart 추적 코드 실행 오류 (' + selector + ')', e);
4350                                }
4351                            }
4352                        };
4353                        appendAdvancedTraceCode('head', result.advanced_trace_data.header);
4354                        appendAdvancedTraceCode('body', result.advanced_trace_data.body);
4355                        appendAdvancedTraceCode('footer', result.advanced_trace_data.footer);
4356                    }
4357
4358                    // 같은 상품이 장바구니에 존재하지 않을 경우 장바구니 카운트 증가
4359                    if (!result.prod_found) {
4360                        var $shop_cart_badge_cnt_wrap = $('._shop_cart_badge_cnt_wrap');
4361                        var org_cnt = 0;
4362                        if ($shop_cart_badge_cnt_wrap.eq(0).text() != '') org_cnt = parseInt($shop_cart_badge_cnt_wrap.eq(0).text());
4363                        $shop_cart_badge_cnt_wrap.text(org_cnt + 1);
4364
4365                        // 카운트가 증가할때 기존에 카운트가 0이어서 ui가 안보이던 것을 다시 보이게 활성화
4366                        $shop_cart_badge_cnt_wrap.removeAttr('disabled');
4367                    }
4368
4369                    // 무료배송 안내 모달의 게이지 누적 베이스라인 - 메인 상품 담기 시점의 가격으로 시작.
4370                    // 이후 SITE_SHOP_CART.addCart로 추가상품을 담을 때마다 이 누적값에 더해 imweb:freeShipNotify:cartTotal:update 이벤트를 dispatch한다.
4371                    // 메인 상품 담기 직후 CART_POPUP 모달이 뜨는 경로에서 dispatch 가 누락되면
4372                    // SDK 캐시/mfe store 가 갱신되지 않아 게이지가 0% 로 보이므로 베이스라인 설정과 함께 dispatch.
4373                    var freeShipNotifyCartPrice = (typeof result.free_shipping_price === 'number' ? result.free_shipping_price : (typeof result.total_price === 'number' ? result.total_price : 0));
4374                    var freeShipNotifySdk = window.IMWEB_SHOP_FREE_SHIP_NOTIFY_SDK;
4375                    if (freeShipNotifySdk && typeof freeShipNotifySdk.getCachedSelectedOptionsTotal === 'function') {
4376                        var selectedOptionsTotal = freeShipNotifySdk.getCachedSelectedOptionsTotal();
4377                        if (typeof selectedOptionsTotal === 'number' && selectedOptionsTotal >= 0) {
4378                            freeShipNotifyCartPrice = selectedOptionsTotal;
4379                        }
4380                    }
4381                    window.__imwebFreeShipCartCumulative = freeShipNotifyCartPrice;
4382                    window.__imwebFreeShipCartAdditionalsSum = 0;
4383                    window.dispatchEvent(new CustomEvent('imweb:freeShipNotify:cartTotal:update', {
4384                        detail: { totalPrice: window.__imwebFreeShipCartCumulative }
4385                    }));
4386
4387                    if (typeof window.IMWEB_SHOP_FREE_SHIP_NOTIFY_SDK !== 'undefined'
4388                        && typeof window.IMWEB_SHOP_FREE_SHIP_NOTIFY_SDK.resetInlineMagnetItemCounts === 'function') {
4389                        window.IMWEB_SHOP_FREE_SHIP_NOTIFY_SDK.resetInlineMagnetItemCounts();
4390                    }
4391
4392                    window.__imwebAddToCartProdCodeList = [];
4393                    window.__imwebAddToCartHasFreeShipUpsell = false;
4394                    if (typeof result.prod_code === 'string' && result.prod_code !== '') {
4395                        window.__imwebAddToCartProdCodeList.push(result.prod_code);
4396                    }
4397                    if (typeof window.IMWEB_SHOP_FREE_SHIP_NOTIFY_SDK !== 'undefined'
4398                        && typeof window.IMWEB_SHOP_FREE_SHIP_NOTIFY_SDK.getInlineMagnetCodeByIdx === 'function'
4399                        && Array.isArray(selected_prod_additionals)) {
4400                        var codeByIdx = window.IMWEB_SHOP_FREE_SHIP_NOTIFY_SDK.getInlineMagnetCodeByIdx();
4401                        selected_prod_additionals.forEach(function (additional) {
4402                            var code = codeByIdx[additional.idx];
4403                            if (typeof code === 'string' && code !== '') {
4404                                window.__imwebAddToCartProdCodeList.push(code);
4405                                window.__imwebAddToCartHasFreeShipUpsell = true;
4406                            }
4407                        });
4408                    }
4409
4410                    callback();
4411                } else {
4412                    // 코드 7: 비회원 이용권 구매 시 로그인 페이지로
4413                    if (result.code === 7) {
4414                        redirectToLoginPage();
4415                    } else {
4416                        alert(result.msg);
4417                        if (result.code === 3) {
4418                            location.reload();
4419                        }
4420                    }
4421                }
4422            }
4423        });
4424    };
4425
4426    /**
4427     * 바로 주문하기  추가, C3 분기를 위해 is_c3 파라미터 추가
4428     */
4429    var _addOrder = function (type, backurl, params) {
4430			let is_oms = params && params.is_oms ? params.is_oms : false;
4431			let callback = params && params.callback ? params.callback : function () {};
4432			let is_gift_buy = params && params.is_gift_buy ? params.is_gift_buy : false;
4433			let selected_freebies = params && params.selected_freebies ? params.selected_freebies : [];
4434
4435
4436			add_order_progress_check = true;
4437			cart_type = $('input[name=add_cart_type]:checked').val();
4438
4439			var _data = {
4440				'backurl': backurl,
4441				'prodIdx': current_prod_idx,
4442				'optDataList': selected_options,
4443				'orderCount': order_count,
4444				'type': type,
4445				'deliv_type': $deliv_type,
4446				'deliv_pay_type': $deliv_pay_type,
4447				'deliv_country': $deliv_country,
4448				'order_type': cart_type,
4449				'is_gift_buy': is_gift_buy,
4450				'prod_additional_list': selected_prod_additionals,
4451				'prod_additional_option_list': selected_prod_additional_options,
4452				'freebie_list': cart_type !== 'regularly' ? selected_freebies : undefined,
4453				'shipping_template_code': selected_shipping_template_code
4454			};
4455
4456			if(is_oms){
4457				if (localStorage.getItem("selectedDeliveryCountry") === "undefined") {
4458					localStorage.removeItem("selectedDeliveryCountry");
4459				}
4460				if (typeof $deliv_country !== 'undefined') {
4461					localStorage.setItem("selectedDeliveryCountry", $deliv_country);
4462				}
4463				_data.infoUrl = location.origin;
4464			}
4465
4466			_data.ace_pid = window._AcePID || window._AceMID;
4467
4468			const [buyBtn, buyBtnMobile, buyBtnDialog, buyBtnMobileDialog, buyBtnRegularly, buyBtnRegularlyMobile] = [$('._btn_buy'), $('._btn_mobile_buy'), $('._btn_dialog_buy'), $('._btn_mobile_dialog_buy'), $('._btn_regularly'), $('._buy_regularly')];
4469			const [buyBtnText, buyBtnMobileText, buyBtnDialogText, buyBtnMobileDialogText, buyBtnRegularlyText, buyBtnRegularlyMobileText] = [buyBtn.first().text(), buyBtnMobile.first().text(), buyBtnDialog.first().text(), buyBtnMobileDialog.first().text(), buyBtnRegularly.first().text(), buyBtnRegularlyMobile.first().text()];
4470
4471			const setSpinner = function ($buttons) {
4472				const spinner = '<div class="loading-spinner-container"><div class="loading-spinner"></div></div>';
4473
4474				if (Array.isArray($buttons)) {
4475					for (const $button of $buttons) {
4476						if ($button.length > 0) {
4477							$button.html(spinner);
4478						}
4479					}
4480				} else {
4481					if ($buttons.length > 0) {
4482						$buttons.html(spinner);
4483					}
4484				}
4485			}
4486
4487			const unsetSpinner = function ($btn, text) {
4488				if ($btn.length > 0) {
4489					$btn.html(text);
4490				}
4491			};
4492
4493			const landingUrlHeaders = {};
4494			const _landingUrlValue = getBsLandingUrlHeaderValue();
4495			if (_landingUrlValue) {
4496				landingUrlHeaders['imweb-landing-url'] = _landingUrlValue;
4497			}
4498
4499			$.ajax({
4500				type: 'POST',
4501				headers: landingUrlHeaders,
4502				data: _data,
4503				// OMS path 분기
4504				url: is_oms ? '/shop/oms/OMS_add_order.cm' : '/shop/add_order.cm',
4505				dataType: 'json',
4506				cache: false,
4507				beforeSend: function () {
4508					// TODO 선물하기 버튼에 스피너 적용
4509					// 간편결제 버튼은 PG사 정책 우려로 인해 스피너 적용하지 않음
4510					if (is_gift_buy || type === 'npay' || type === 'talkpay') {
4511						add_order_progress_check = false;
4512
4513						return;
4514					}
4515
4516					// 배송 국가 선택 유효성 검사
4517					if ($('._deliv_country_selector').length > 0 && (!$deliv_country || $deliv_country === 'none')) {
4518						$('._deliv_country_selector').addClass('warning_select');
4519						alert(typeof LOCALIZE.설명_배송받을국가를선택해주세요 === 'function' ? LOCALIZE.설명_배송받을국가를선택해주세요() : '배송받을 국가를 선택해주세요.');
4520
4521						add_order_progress_check = false;
4522
4523						return false;
4524					}
4525
4526					// 필수옵션 선택 유효성 검사 실행
4527					if (!checkRequireOption()) {
4528						alert(LOCALIZE.설명_필수옵션이모두선택되어있지않습니다());
4529
4530						add_order_progress_check = false;
4531
4532						return false;
4533					}
4534
4535					// 구매하기, 모바일 구매하기, 다이얼로그 구매하기, 모바일 다이얼로그 구매하기, 정기구독 신청, 모바일 정기구독 신청 버튼에 로딩 스피너 추가
4536					setSpinner([buyBtn, buyBtnMobile, buyBtnDialog, buyBtnMobileDialog, buyBtnRegularly, buyBtnRegularlyMobile]);
4537				},
4538				error: function (result) {
4539					// 구매하기 PC 버튼 스피너 초기화
4540					unsetSpinner(buyBtn, buyBtnText);
4541					// 구매하기 모바일 버튼 스피너 초기화
4542					unsetSpinner(buyBtnMobile, buyBtnMobileText);
4543					// 구매하기 다이얼로그 버튼 스피너 초기화
4544					unsetSpinner(buyBtnDialog, buyBtnDialogText);
4545					// 구매하기 모바일 다이얼로그 버튼 스피너 초기화
4546					unsetSpinner(buyBtnMobileDialog, buyBtnMobileDialogText);
4547					// 정기구독 신청 버튼 스피너 초기화
4548					unsetSpinner(buyBtnRegularly, buyBtnRegularlyText);
4549					// 정기구독 신청 모바일 버튼 스피너 초기화
4550					unsetSpinner(buyBtnRegularlyMobile, buyBtnRegularlyMobileText);
4551
4552					add_order_progress_check = false;
4553				},
4554				success: function (result) {
4555					if (result.msg == 'SUCCESS') {
4556								// 페이지가 이동하는 동작의 시각적 연결성을 위해 성공 시에는 스피너를 초기화 하지 않는다.
4557
4558								if (typeof AW_PRODUCT != 'undefined' && typeof AW_F_D != 'undefined') {
4559										AW_F_D(current_prod_idx, 'i', order_count);
4560										AW_PRODUCT(order_count);
4561								}
4562
4563								if (result.advanced_kakao_trace_data != null) {
4564										$('body').append(result.advanced_kakao_trace_data);
4565								}
4566								fetch('/ajax/oms/OMS_auth.cm')
4567									.then((res) => res.json())
4568									.then((res) => {
4569										if (res.msg === 'SUCCESS') {
4570											window.sessionStorage.setItem('oms_token', res.token);
4571										}
4572									})
4573									.finally(() => callback(result))
4574						} else {
4575								add_order_progress_check = false;
4576
4577								// 구매하기 PC 버튼 스피너 초기화
4578								unsetSpinner(buyBtn, buyBtnText);
4579								// 구매하기 모바일 버튼 스피너 초기화
4580								unsetSpinner(buyBtnMobile, buyBtnMobileText);
4581								// 구매하기 다이얼로그 버튼 스피너 초기화
4582								unsetSpinner(buyBtnDialog, buyBtnDialogText);
4583								// 구매하기 모바일 다이얼로그 버튼 스피너 초기화
4584								unsetSpinner(buyBtnMobileDialog, buyBtnMobileDialogText);
4585								// 정기구독 신청 버튼 스피너 초기화
4586								unsetSpinner(buyBtnRegularly, buyBtnRegularlyText);
4587								// 정기구독 신청 모바일 버튼 스피너 초기화
4588								unsetSpinner(buyBtnRegularlyMobile, buyBtnRegularlyMobileText);
4589
4590								if (type == 'talkpay') {
4591										callback(result);
4592								} else {
4593										// 코드 7: 비회원 이용권 구매 시 로그인 페이지로
4594										if (result.code === 7) {
4595												redirectToLoginPage();
4596										} else {
4597												alert(result.msg.replace(/\\n/g, '\n'));
4598												if (result.code === 11) {
4599														callback(result);
4600												}
4601												if (result.code === 6) {
4602														location.reload();
4603												}
4604										}
4605								}
4606
4607								if(result.code === 2){
4608									$('._deliv_country_selector').toggleClass('warning_select');
4609								}
4610						}
4611
4612						add_order_progress_check = false;
4613				}
4614			});
4615    };
4616
4617    var addOrder = function (type, backurl, params) {
4618        if (add_order_progress_check) return false;
4619        if (!ensureAddOrderPermission()) return false;
4620        if (typeof AM_PRODUCT != 'undefined') AM_PRODUCT(order_count);
4621
4622            //type 이 guest_login 으로 넘어오는경우 비회원주문+로그인페이지로이동후주문 방식임 type 을 normal 로처리해야 다른 로직에 영향이 없어 이렇게 처리함
4623            // NOTE: 현재 addOrder 함수를 guest_login 값을 type인자로 호출하는 부분을 php코드베이스 내에서 찾을 수 없음.
4624            var is_guest_login = false;
4625            if (type == 'guest_login') {
4626                is_guest_login = true;
4627                type = 'normal';
4628            }
4629
4630            _addOrder(type, backurl, {
4631							...params,
4632							is_oms: false,
4633							callback: function(result){
4634                switch (type) {
4635                    case 'npay':
4636                        if (result.npay_url == '') {
4637                            if (result.errmsg) {
4638                                alert(escape_javascript(result.errmsg));
4639                            } else {
4640                                alert(LOCALIZE.설명_네이버페이상품구매실패(escape_javascript(result.errmsg)));
4641                            }
4642                            add_order_progress_check = false;
4643                        } else {
4644                            if (!!result.shopping_additional_price_msg) {
4645                                if (!confirm(result.shopping_additional_price_msg + '\n\n' + LOCALIZE.설명_네이버페이를계속해서진행하시겠습니까())) {
4646                                    add_order_progress_check = false;
4647                                    return false;
4648                                }
4649                            }
4650
4651                            if (result.auth_type == 2) {
4652                                // 성인 인증이 필요할 경우
4653                                window.location.href = '/?mode=adult_auth_npay&data=' + result.data;
4654                            } else {
4655                                // 성인 인증이 필요하지 않는 경우
4656                                var npay_order_info = result['npay_order_info'];
4657
4658                                if (typeof CHANNEL_PLUGIN != 'undefined') CHANNEL_PLUGIN.AddOrder();
4659                                if (typeof FB_PIXEL != 'undefined') {
4660                                    FB_PIXEL.InitiateCheckout(result.ic_event_id, result.total_price, result.currency, result.fb_external_id);
4661                                    if (result.fb_npay_switch === 'Y') FB_PIXEL.addNpayOrder(npay_order_info);
4662                                }
4663
4664                                if (typeof TIKTOK_PIXEL != 'undefined') {
4665                                    TIKTOK_PIXEL.track('InitiateCheckout', {
4666                                        event_id: result.order_code,
4667                                        contents: result.prod_list.map(prod => ({
4668                                            content_id: prod.id,
4669                                            content_name: prod.name,
4670                                            price: prod.price,
4671                                        })),
4672                                        content_type: 'product',
4673                                        value: result.total_price,
4674                                        currency: result.currency,
4675                                    });
4676                                }
4677
4678                                if (result.google_analytics_type == 'G' && result.is_ga_api_secret === false) {
4679                                    if (typeof GOOGLE_ANAUYTICS != 'undefined') GOOGLE_ANAUYTICS.addNpayOrder(npay_order_info);
4680                                }
4681                                if (typeof GOOGLE_ADWORDS_TRACE != 'undefined' && result.google_ads_include_npay === 'Y') GOOGLE_ADWORDS_TRACE.addNpayOrder(npay_order_info);
4682                                if (typeof CRITEO != 'undefined') CRITEO.npayTrackTransaction(npay_order_info);
4683	                            if (typeof NP_NDA != 'undefined') NP_NDA.BeginCheckout(result.prod_code_list);
4684															window.location.href = result.npay_url;
4685                            }
4686                        }
4687                        break;
4688                    case 'talkpay':
4689                        switch (params.type) {
4690                            case 'onOrder':
4691                                if (result.msg == 'SUCCESS') {
4692                                    if (result.order_sheet_id) {
4693                                        params.onSuccess(result.order_sheet_id);
4694                                    } else {
4695                                        params.onFailure({ message: result.msg });
4696                                    }
4697                                } else {
4698                                    params.onFailure({ message: result.msg });
4699                                }
4700                                break;
4701                            case 'onPayOrder':
4702                                if (result.msg == 'SUCCESS') {
4703                                    if (typeof FB_PIXEL != 'undefined') FB_PIXEL.InitiateCheckout(result.ic_event_id, result.total_price, result.currency, result.fb_external_id);
4704                                    if (typeof CHANNEL_PLUGIN != 'undefined') CHANNEL_PLUGIN.AddOrder(result.order_code);
4705	                                if (typeof NP_NDA != 'undefined') NP_NDA.BeginCheckout(result.prod_code_list);
4706
4707                                    if (typeof TIKTOK_PIXEL != 'undefined') {
4708                                        TIKTOK_PIXEL.track('InitiateCheckout', {
4709                                            event_id: result.order_code,
4710                                            contents: result.prod_list.map(prod => ({
4711                                                content_id: prod.id,
4712                                                content_name: prod.name,
4713                                                price: prod.price,
4714                                            })),
4715                                            content_type: 'product',
4716                                            value: result.total_price,
4717                                            currency: result.currency,
4718                                        });
4719                                    }
4720
4721                                    window.location.href = "/shop_payment/?order_code=" + encodeURIComponent(result.order_code);
4722                                }
4723                                break;
4724                        };
4725
4726                        add_order_progress_check = false;
4727                        break;
4728                    default:
4729                        if (typeof FB_PIXEL != 'undefined') FB_PIXEL.InitiateCheckout(result.ic_event_id, result.total_price, result.currency, result.fb_external_id);
4730                        if (typeof CHANNEL_PLUGIN != 'undefined') CHANNEL_PLUGIN.AddOrder(result.order_code);
4731	                    if (typeof NP_NDA != 'undefined') NP_NDA.BeginCheckout(result.prod_code_list);
4732
4733                        if (typeof TIKTOK_PIXEL != 'undefined') {
4734                            TIKTOK_PIXEL.track('InitiateCheckout', {
4735                                event_id: result.order_code,
4736                                contents: result.prod_list.map(prod => ({
4737                                    content_id: prod.id,
4738                                    content_name: prod.name,
4739                                    price: prod.price,
4740                                })),
4741                                content_type: 'product',
4742                                value: result.total_price,
4743                                currency: result.currency,
4744                            });
4745                        }
4746
4747                        if (is_guest_login && cm_data.use_login_popup === 'N' && !options.is_using_nomember_order_login_after) {	//비회원주문시 로그인 페이지로 이동처리
4748                            //비회원 구매 불가 조건의 경우 로그인 페이지로 보냄. (주문 생성 x)
4749                            if (result.code === 11) {
4750                                var back_url_base64 = window.location.pathname;
4751                                var url_param = document.location.href.split("?");
4752                                if (url_param.length > 1) {
4753                                    back_url_base64 += "?" + url_param[1];
4754                                }
4755                                window.location.href = '/login?back_url=' + encodeURIComponent(btoa(back_url_base64));
4756                            } else {
4757                                window.location.href = '/login?shopping_order_code=' + result.order_code + '&back_url=' + encodeURIComponent(result.back_url_base64);
4758                            }
4759                        } else if (result.code === 7) {
4760                            // 코드 7: 비회원 이용권 구매 시 로그인 페이지로
4761                            redirectToLoginPage();
4762                        } else if(result.msg === 'SUCCESS'){
4763                            //일반주문이며 주문서 생성에 성공 한 경우 주문서로 이동
4764                            window.location.href = "/shop_payment/?order_code=" + encodeURIComponent(result.order_code);
4765                        }
4766                        break;
4767                }
4768            }
4769					});
4770    }
4771
4772    var OMS_addOrder = async function (type, backurl, params) {
4773			if(add_order_progress_check) return false;
4774			if (!ensureAddOrderPermission()) return false;
4775			if ( typeof AM_PRODUCT != 'undefined' ) AM_PRODUCT(order_count);
4776
4777            if (params.shippingVerification) {
4778                const verified = await SHIPPING_SERVICE.verify({ type });
4779                if (!verified) {
4780                    return false;
4781                }
4782            }
4783
4784			//type 이 guest_login 으로 넘어오는경우 비회원주문+로그인페이지로이동후주문 방식임 type 을 normal 로처리해야 다른 로직에 영향이 없어 이렇게 처리함
4785			var is_guest_login=false;
4786			if (type=='guest_login'){
4787				is_guest_login=true;
4788				type='normal';
4789			}
4790
4791			_addOrder(type, backurl, {
4792				...params,
4793				is_oms: true,
4794				callback: function(result){
4795					switch( type ) {
4796						case 'npay':
4797							if(result.npay_url == '') {
4798								if ( result.errmsg ) {
4799									alert(escape_javascript(result.errmsg));
4800								} else {
4801									alert(LOCALIZE.설명_네이버페이상품구매실패(escape_javascript(result.errmsg)));
4802								}
4803								add_order_progress_check = false;
4804							} else {
4805								if ( !! result.shopping_additional_price_msg ) {
4806									if ( ! confirm(result.shopping_additional_price_msg + '\n\n' + LOCALIZE.설명_네이버페이를계속해서진행하시겠습니까()) ) {
4807										add_order_progress_check = false;
4808										return false;
4809									}
4810								}
4811
4812								if ( result.auth_type == 2 ) {
4813									// 성인 인증이 필요할 경우
4814									window.location.href = '/?mode=adult_auth_npay&data=' + result.data;
4815								} else {
4816									// 성인 인증이 필요하지 않는 경우
4817									var npay_order_info = result['npay_order_info'];
4818
4819									if ( typeof CHANNEL_PLUGIN != 'undefined' ) CHANNEL_PLUGIN.AddOrder();
4820									if ( typeof FB_PIXEL != 'undefined'){
4821										FB_PIXEL.InitiateCheckout(result.ic_event_id,result.total_price,result.currency,result.fb_external_id);
4822										if(result.fb_npay_switch === 'Y') FB_PIXEL.addNpayOrder(npay_order_info);
4823									}
4824
4825									if ( result.google_analytics_type == 'G' && result.is_ga_api_secret === false ) {
4826										if ( typeof GOOGLE_ANAUYTICS != 'undefined') GOOGLE_ANAUYTICS.addNpayOrder(npay_order_info);
4827									}
4828									if ( typeof GOOGLE_ADWORDS_TRACE != 'undefined' && result.google_ads_include_npay === 'Y') GOOGLE_ADWORDS_TRACE.addNpayOrder(npay_order_info);
4829									if ( typeof CRITEO != 'undefined') CRITEO.npayTrackTransaction(npay_order_info);
4830									if (typeof NP_NDA != 'undefined') NP_NDA.BeginCheckout(result.prod_code_list);
4831
4832                                    if (typeof TIKTOK_PIXEL != 'undefined') {
4833                                        TIKTOK_PIXEL.track('InitiateCheckout', {
4834                                            event_id: result.order_code,
4835                                            contents: result.prod_list.map(prod => ({
4836                                                content_id: prod.id,
4837                                                content_name: prod.name,
4838                                                price: prod.price,
4839                                            })),
4840                                            content_type: 'product',
4841                                            value: result.total_price,
4842                                            currency: result.currency,
4843                                        });
4844                                    }
4845
4846									if (typeof result.npay_url !== 'string' || result.npay_url.indexOf('pay.naver.com') < 0) return false;
4847									window.location.href = result.npay_url;
4848								}
4849							}
4850							break;
4851						case 'talkpay':
4852							switch ( params.type ) {
4853								case 'onOrder':
4854									if ( result.msg == 'SUCCESS' ) {
4855										if ( result.order_sheet_id ) {
4856											params.onSuccess(result.order_sheet_id);
4857										} else {
4858											params.onFailure({message: result.msg});
4859										}
4860									} else {
4861										params.onFailure({message: result.msg});
4862									}
4863									break;
4864								case 'onPayOrder':
4865									if ( result.msg == 'SUCCESS' ) {
4866										if ( typeof FB_PIXEL != 'undefined' ) FB_PIXEL.InitiateCheckout(result.ic_event_id,result.total_price,result.currency,result.fb_external_id);
4867										if ( typeof CHANNEL_PLUGIN != 'undefined' ) CHANNEL_PLUGIN.AddOrder(result.order_code);
4868										if (typeof NP_NDA != 'undefined') NP_NDA.BeginCheckout(result.prod_code_list);
4869
4870                                        if (typeof TIKTOK_PIXEL != 'undefined') {
4871                                            TIKTOK_PIXEL.track('InitiateCheckout', {
4872                                                event_id: result.order_code,
4873                                                contents: result.prod_list.map(prod => ({
4874                                                    content_id: prod.id,
4875                                                    content_name: prod.name,
4876                                                    price: prod.price,
4877                                                })),
4878                                                content_type: 'product',
4879                                                value: result.total_price,
4880                                                currency: result.currency,
4881                                            });
4882                                        }
4883
4884										var shop_payment_url = "/shop_payment/?order_code=" + encodeURIComponent(result.order_code) + '&order_no=' + encodeURIComponent(result.order_no);
4885										if (result.member_code) {
4886											shop_payment_url += "&order_member=" + encodeURIComponent(result.member_code);
4887										}
4888										if ( result.kakaopay_only === 'Y' ) {
4889											shop_payment_url += "&kakaopay_only=Y";
4890										}
4891										window.location.href = shop_payment_url;
4892									}
4893									break;
4894							};
4895
4896							add_order_progress_check = false;
4897							break;
4898						default:
4899							if ( typeof FB_PIXEL != 'undefined' ) FB_PIXEL.InitiateCheckout(result.ic_event_id,result.total_price,result.currency,result.fb_external_id);
4900							if ( typeof CHANNEL_PLUGIN != 'undefined' ) CHANNEL_PLUGIN.AddOrder(result.order_code);
4901							if (typeof NP_NDA != 'undefined') NP_NDA.BeginCheckout(result.prod_code_list);
4902
4903                            if (typeof TIKTOK_PIXEL != 'undefined') {
4904                                TIKTOK_PIXEL.track('InitiateCheckout', {
4905                                    event_id: result.order_code,
4906                                    contents: result.prod_list.map(prod => ({
4907                                        content_id: prod.id,
4908                                        content_name: prod.name,
4909                                        price: prod.price,
4910                                    })),
4911                                    content_type: 'product',
4912                                    value: result.total_price,
4913                                    currency: result.currency,
4914                                });
4915                            }
4916
4917							if (is_guest_login && cm_data.use_login_popup === 'N' && !options.is_using_nomember_order_login_after){	//비회원주문시 로그인 페이지로 이동처리
4918															//비회원 구매 불가 조건의 경우 로그인 페이지로 보냄. (주문 생성 x)
4919															if(result.code === 11){
4920																	var back_url_base64 = window.location.pathname;
4921																	var url_param = document.location.href.split("?");
4922																	if(url_param.length > 1){
4923																			back_url_base64 += "?"+url_param[1];
4924																	}
4925																	window.location.href = '/login?back_url='+encodeURIComponent(btoa(back_url_base64));
4926															}else{
4927																	window.location.href = '/login?shopping_order_code=' + result.order_code + '&back_url=' + encodeURIComponent(result.back_url_base64);
4928															}
4929							} else if (result.code === 7) {
4930															// 코드 7: 비회원 이용권 구매 시 로그인 페이지로
4931															redirectToLoginPage();
4932							}else{  //일반주문인경우 결제화면으로 이동
4933								if (typeof result.c3_result === "undefined" || typeof result.c3_result.orderCode === "undefined" || typeof result.c3_result.orderNo === "undefined") return
4934								window.location.href
4935									= "/shop_payment/?order_code="
4936									+ encodeURIComponent(result.c3_result.orderCode)
4937									+ "&order_no="
4938									+  encodeURIComponent(result.c3_result.orderNo)
4939									+ (result.c3_result.memberCode ? `&order_member=${encodeURIComponent(result.c3_result.memberCode)}` : '');
4940							}
4941							break;
4942					}
4943				}
4944			});
4945    }
4946
4947    /**
4948     * 네이버페이지 찜하기  등록
4949     */
4950    var addNPayWish = function () {
4951        $.ajax({
4952            type: 'POST',
4953            data: { 'prod_idx_list': [current_prod_idx] },
4954            url: ('/shop/add_npay_wish.cm'),
4955            dataType: 'json',
4956            cache: false,
4957            success: function (result) {
4958                if (result.msg == 'SUCCESS') {
4959                    if (result.mobile == 'Y')
4960                        window.location.href = result.npay_url;
4961                    else
4962                        window.open(result.npay_url, "", "scrollbars=yes,width=400,height=267");
4963                } else {
4964                    alert(LOCALIZE.설명_네이버페이찜등록실패(escape_javascript(result.msg)));
4965                }
4966            }
4967        });
4968    };
4969
4970
4971    var digitalFileDownload = function (order_no) {
4972        if (prod_type !== SHOP_CONST.PROD_TYPE.DIGITAL) return false;
4973        $.ajax({
4974            "type": "POST",
4975            "data": { "prod_no": current_prod_idx, "order_no": order_no, "mode": "detail" },
4976            "url": "/ajax/shop_digital_prod_download.cm",
4977            "dataType": "JSON",
4978            "success": function (res) {
4979                if (res['msg'] == 'SUCCESS') {
4980                    if (res['download_info_msg'].trim() == '' || confirm(res['download_info_msg'])) {
4981                        location.href = res['file_url'];
4982                    }
4983                } else {
4984                    alert(res['msg']);
4985                    if (typeof res['reload'] != 'undefined') {
4986                        location.reload();
4987                    }
4988                }
4989            }
4990        });
4991    };
4992
4993    /**
4994     * OMS 디지털 상품 다운로드 (쓰로틀링 적용)
4995     */
4996    var lastOmsDownloadClick = 0;
4997    var handleOmsDigitalDownload = function (e, href) {
4998        if (e) e.preventDefault();
4999
5000        var now = new Date().getTime();
5001        if (now - lastOmsDownloadClick < 1000) {
5002            return;
5003        }
5004
5005        lastOmsDownloadClick = now;
5006        window.location.href = href;
5007    };
5008    /**
5009     * 모바일에서 구매하기 클릭시 옵션 표시 처리
5010     * @param type	buy 구매하기, regularly 정기구독, gift 선물하기
5011     */
5012    var showMobileOptions = function (type) {
5013        if (!options.is_price_view_permission && window.IS_GUEST) {
5014            const backUrl = encodeURIComponent(base64Encode(window.location.href));
5015            if (cm_data.use_login_popup === 'Y') {
5016                window.SITE_MEMBER.openLogin(
5017                    backUrl,
5018                    'payment',
5019                    function () {
5020											if (window.USE_OMS === true) {
5021												OMS_addOrder(
5022													'normal',
5023													encodeURIComponent(base64Encode(window.location.href))
5024												)
5025											} else {
5026												addOrder(
5027													'normal',
5028													encodeURIComponent(base64Encode(window.location.href))
5029												);
5030											}
5031										},
5032                    'N',
5033                    'payment',
5034                );
5035            } else {
5036                window.location.href = '/login?back_url=' + backUrl;
5037            }
5038            return;
5039        }
5040        if (type === 'regularly') {
5041            $('input[name=add_cart_type]:checked').val('regularly');
5042            // 모바일 구매/옵션 바텀시트 여는 코드
5043            $prod_detail.addClass('open detail-regularly');
5044        } else {
5045            $('input[name=add_cart_type]:checked').val('normal');
5046            // 모바일 구매/옵션 바텀시트 여는 코드
5047            $prod_detail.addClass('open');
5048			$prod_detail.find('._btn_restock').css('display', 'flex');
5049        }
5050        showMobileFirstSelect();
5051        var full_input = false;
5052        var $form = $options.find('._form_parent');
5053        // 첫번째 선택 후 창을 닫았을때
5054        $form.each(function (index) {
5055
5056            var is_input = $(this).children().hasClass('option_box_wrap');
5057
5058            if (is_input) {
5059                var input_items = $(this).find('._requireInputOption');
5060                input_items.each(function (index) {
5061                    if ($(this).val() != '') {
5062                        if ((index + 1) == input_items.length) {
5063                            full_input = true;
5064                        }
5065                    } else {
5066                        return false;
5067                    }
5068                });
5069
5070            } else {
5071                var prev_is_selected = $(this).prev().find('.dropdown-item').hasClass('selected');
5072                var is_selected = $(this).find('.dropdown-item').hasClass('selected');
5073                // 이전 필수 드롭다운이 선택 되어 있을 경위
5074                if (prev_is_selected || full_input) {
5075                    $(this).toggleClass('disabled', false);
5076
5077                    full_input = false;
5078
5079                    if (!is_selected) {
5080                        var current = 0;
5081                        var $current_form_select = $(this).eq(current);
5082                        var $current_form_dropdown_toggle = $current_form_select.find('.dropdown-toggle');
5083
5084                        $current_form_dropdown_toggle.attr('aria-expanded', true);
5085                        $current_form_dropdown_toggle.toggleClass('active', true);
5086                        $current_form_select.attr('data-val', true);
5087
5088                        setTimeout(function () {
5089                            $current_form_dropdown_toggle.dropdown('toggle');
5090                        }, 10);
5091
5092                        $current_form_dropdown_toggle.off('click').on('click', function () {
5093                            $current_form_dropdown_toggle.attr('aria-expanded', false);
5094                            $current_form_dropdown_toggle.toggleClass('active', false);
5095                            $current_form_select.attr('data-val', false);
5096                        });
5097                    }
5098                } else if (index === 0) {
5099                    // 첫 드롭다운은 일단 true로 표시
5100                    $(this).toggleClass('disabled', false);
5101                } else {
5102                    $(this).toggleClass('disabled', true);
5103                }
5104            }
5105        });
5106        // 모달 또는 바텀시트 표시될 때 전체스크롤 등의 동작 막는 코드
5107        $('html').addClass('mobile-shop-open');
5108        $('body').addClass('mobile-nav-on modal-open');
5109        $('header#doz_header_wrap').addClass('bg-back');
5110        $('.categorize-mobile .site_prod_nav_wrap').addClass('bg-back');
5111
5112        // 실시간 상담 부분
5113        $('#ch-plugin, #kakao-talk-channel-chat-button, .talk_banner_wrap, #fb-root').hide();
5114
5115	    if (options.is_possible_use_gift) {
5116		    handleMobileGiftFooterUI(true, type);
5117	    }
5118    };
5119    const changeValueChecked = function () {
5120        $("input:radio[name='add_cart_type']:radio[value='normal']").prop('checked', false);
5121        $("input:radio[name='add_cart_type']:radio[value='regularly']").prop('checked', true);
5122    };
5123    /**
5124     * 조합형 + 필수 옵션일 때 첫번째 셀렉트 박스는 show 처리
5125     */
5126    var showMobileFirstSelect = function () {
5127        var $form_select = $options.find('._first_form_select');
5128        if ($form_select.length) {
5129            var current = 0;
5130            var $current_form_select = $form_select.eq(current);
5131            var $current_form_dropdown_toggle = $current_form_select.find('.dropdown-toggle');
5132
5133            $current_form_dropdown_toggle.attr('aria-expanded', true);
5134            $current_form_dropdown_toggle.toggleClass('active', true);
5135            $current_form_select.attr('data-val', true);
5136
5137            setTimeout(function () {
5138                $current_form_dropdown_toggle.dropdown('toggle');
5139            }, 50);
5140
5141            $current_form_dropdown_toggle.off('click').on('click', function () {
5142                $current_form_dropdown_toggle.attr('aria-expanded', false);
5143                $current_form_dropdown_toggle.toggleClass('active', false);
5144                $current_form_select.attr('data-val', false);
5145            });
5146            $form_select.parent('._form_parent').nextAll().toggleClass('disabled', true);
5147        }
5148    };
5149
5150    /**
5151     * 모바일에서 구매하기 클릭시 옵션 숨기기 처리
5152     */
5153    var hideMobileOptions = function () {
5154        $prod_detail.removeClass('open detail-regularly');
5155        $('html').removeClass('mobile-shop-open');
5156        $('body').removeClass('mobile-nav-on modal-open');
5157        $('header#doz_header_wrap').removeClass('bg-back');
5158        $('.categorize-mobile .site_prod_nav_wrap').removeClass('bg-back');
5159
5160        // 실시간 상담 부분
5161        $('#ch-plugin, #kakao-talk-channel-chat-button, .talk_banner_wrap, #fb-root').show();
5162				$prod_detail.find('._btn_restock').hide();
5163
5164		if (options.is_possible_use_gift) {
5165			handleMobileGiftFooterUI(false);
5166		}
5167    };
5168
5169    /**
5170     * PC B타입에서 구매하기/장바구니 클릭 시 옵션 표시 처리, 옵션 표시되어있으면 구매하기/장바구니
5171     * @param type order: 구매하기, cart: 장바구니, gift: 선물하기
5172     * @param backurl 비회원 구매 시 로그인 등에서 사용될 back url
5173     */
5174    var showPCOptions = function (type, backurl = location.href, is_c3 = false){
5175        if (backurl == undefined) backurl = location.href;
5176        if ($prod_detail.hasClass('open')) {
5177					// 바텀시트가 열려 있으면
5178            if(type == 'cart'){
5179							if (typeof BrandScope !== "undefined") {
5180								try {
5181									BrandScope.track('click_add_to_cart_shop_view', {
5182										'action': 'click',
5183										'content': 'add_to_cart',
5184										'where': 'shop_view',
5185										'prod_code': current_prod_code,
5186										'is_regularly_prod': $('input[name=add_cart_type]:checked').val() === 'regularly' ? true : false,
5187										'prod_type': prod_type
5188									});
5189								} catch {
5190									// ignore
5191								}
5192							}
5193                SITE_SHOP_DETAIL.addCart();
5194            } else if (type == 'gift') {
5195							  if (is_c3) {
5196									SITE_SHOP_DETAIL.OMS_addGiftOrder('normal', backurl);
5197								} else {
5198									SITE_SHOP_DETAIL.addGiftOrder('normal', backurl);
5199								}
5200            } else {
5201                if (is_c3) {
5202                    SITE_SHOP_DETAIL.OMS_addOrder('normal', backurl);
5203                }else{
5204                    SITE_SHOP_DETAIL.addOrder('normal', backurl);
5205                }
5206            }
5207        } else {
5208					// 바텀시트가 안열려 있으면
5209            $prod_detail.find('._btn_npay').hide();
5210            $prod_detail.find('._btn_kakaopay').hide();
5211			$prod_detail.find('._btn_restock').show();
5212            $prod_detail.addClass('open');
5213            var $add_cart_type = $prod_detail.find('input[name=add_cart_type]:checked');
5214            if ($add_cart_type.length > 0) {
5215                if ($add_cart_type.val() === 'regularly') {
5216                    $prod_detail.addClass('detail-regularly');
5217                }
5218            }
5219
5220			if (options.is_possible_use_gift) {
5221				handlePCGiftFooterUI(true, type);
5222			}
5223        }
5224    };
5225
5226    var hidePCOptions = function () {
5227        $prod_detail.removeClass('open detail-regularly');
5228        $prod_detail.find('._btn_npay').show();
5229        $prod_detail.find('._btn_kakaopay').show();
5230		$prod_detail.find('._btn_restock').hide();
5231
5232	    if (options.is_possible_use_gift) {
5233		    handlePCGiftFooterUI(false);
5234	    }
5235    };
5236
5237	var handlePCGiftFooterUI = function (isOpen, type) {
5238		const $root = $('._buy_footer_fixed');
5239
5240		const $footerPriceWrap = $root.find('._footer_price_wrap');
5241		const $btnCart = $root.find('._btn_cart');
5242		const $btnGift = $root.find('._btn_gift');
5243		const $btnBuy = $root.find('._btn_buy');
5244		const $btnDialogSoldout = $root.find('._btn_dialog_soldout');
5245		const $btnDialogBuy = $root.find('._btn_dialog_buy');
5246		const $btnDialogGift = $root.find('._btn_dialog_gift');
5247
5248		const hideAll = () => {
5249			$footerPriceWrap.hide();
5250			$btnCart.hide();
5251			$btnGift.hide();
5252			$btnBuy.hide();
5253			$btnDialogSoldout.hide();
5254			$btnDialogBuy.hide();
5255			$btnDialogGift.hide();
5256		}
5257
5258		const isSoldout = options.is_soldout;
5259
5260		hideAll();
5261
5262		if (isOpen) {
5263			if (isSoldout) {
5264				$btnDialogSoldout.show();
5265			} else {
5266				$footerPriceWrap.show();
5267				$btnCart.show();
5268
5269				switch(type) {
5270					case 'order':
5271						$btnDialogBuy.show();
5272						break;
5273					case 'gift':
5274						$btnDialogGift.show();
5275						break;
5276					case 'cart':
5277						$btnGift.show();
5278						$btnBuy.show();
5279						break;
5280				}
5281			}
5282		} else {
5283			$footerPriceWrap.show();
5284			$btnCart.show();
5285			$btnGift.show();
5286			$btnBuy.show();
5287		}
5288	}
5289
5290	const handleMobileGiftFooterUI = (isOpen, type) => {
5291		const $root = $('._mobile-gift-footer');
5292		const $socialBtnRoot = $('._social_m_position');
5293
5294		const $btnGift = $root.find('._btn_mobile_gift');
5295		const $btnBuy = $root.find('._btn_mobile_buy');
5296		const $btnNPay = $root.find('._btn_mobile_npay');
5297		const $btnKakaoPay = $root.find('._btn_mobile_kakaopay');
5298		const $btnDialogSoldout = $root.find('._btn_mobile_dialog_soldout');
5299		const $btnDialogCart = $root.find('._btn_mobile_dialog_cart');
5300		const $btnDialogBuy = $root.find('._btn_mobile_dialog_buy');
5301		const $btnDialogGift = $root.find('._btn_mobile_dialog_gift');
5302
5303		const hideAll = () => {
5304			$btnGift.hide();
5305			$btnBuy.hide();
5306			$btnNPay.hide();
5307			$btnKakaoPay.hide();
5308			$btnDialogSoldout.hide();
5309			$btnDialogCart.hide();
5310			$btnDialogBuy.hide();
5311			$btnDialogGift.hide();
5312			$socialBtnRoot.hide();
5313		}
5314
5315		hideAll();
5316
5317		const isSoldout = options.is_soldout;
5318
5319        if (isOpen) {
5320            if (isSoldout) {
5321                $btnDialogSoldout.show();
5322            } else {
5323                switch(type) {
5324                    case 'buy':
5325                        $btnDialogCart.show();
5326                        $btnDialogBuy.show();
5327                        if ($socialBtnRoot.find('._social_pay').children().length > 0) $socialBtnRoot.show();
5328                        break;
5329                    case 'gift':
5330                        $btnDialogGift.show();
5331                        break;
5332                }
5333            }
5334        } else {
5335            $btnGift.show();
5336            $btnBuy.show();
5337            $btnNPay.show();
5338            $btnKakaoPay.show();
5339        }
5340    }
5341
5342    var socialBtnResize = function () {
5343        var $social_box = $('._social_pay');
5344        var $pc_position = $('._social_pc_position');
5345        var $m_position = $('._social_m_position');
5346        var w_width = $(window).width();
5347
5348        if (w_width > 787) {
5349            $pc_position.append($social_box);
5350        } else {
5351            $m_position.append($social_box);
5352        }
5353
5354        $(window).resize(function () {
5355            w_width = $(window).width();
5356            if (w_width > 787) {
5357                $pc_position.append($social_box);
5358                if ($('html').hasClass('mobile-shop-open')) {
5359                    hideMobileOptions();
5360                }
5361                if ($('._style_b_clse').length) {
5362                    $('._style_b_clse').removeAttr('onclick');
5363                    $('._style_b_clse').attr('onclick', 'SITE_SHOP_DETAIL.hidePCOptions()');
5364                }
5365            } else {
5366                $m_position.append($social_box);
5367                if ($('._style_b_clse').length) {
5368                    $('._style_b_clse').removeAttr('onclick');
5369                    $('._style_b_clse').attr('onclick', 'SITE_SHOP_DETAIL.hideMobileOptions()');
5370                }
5371            }
5372        });
5373    };
5374
5375	const externalwidgetWrapResize = function(move_target, pc_position, mo_position){
5376		const $target = $(`.${move_target}`);
5377		const $pc_position = $(`.${pc_position}`);
5378		const $m_position = $(`.${mo_position}`);
5379		let w_width = $(window).width();
5380
5381        if($('body').hasClass('device_type_m')) {
5382            w_width = 768;
5383        }
5384
5385		if(w_width > 768){
5386			$pc_position.append($target);
5387		}else{
5388			$m_position.append($target);
5389		}
5390
5391		$(window).resize(function(){
5392			w_width = $(window).width();
5393			if(w_width > 768){
5394                if(!($pc_position.find($target).length)) {
5395                    $pc_position.append($target);
5396                    if($('html').hasClass('mobile-shop-open')){
5397                        hideMobileOptions();
5398                    }
5399                    if($('._style_b_clse').length){
5400                        $('._style_b_clse').removeAttr('onclick');
5401                        $('._style_b_clse').attr('onclick', 'SITE_SHOP_DETAIL.hidePCOptions()');
5402                    }
5403                }
5404			}else{
5405                if(!($m_position.find($target).length)){
5406                    $m_position.append($target);
5407                    if($('._style_b_clse').length){
5408                        $('._style_b_clse').removeAttr('onclick');
5409                        $('._style_b_clse').attr('onclick', 'SITE_SHOP_DETAIL.hideMobileOptions()');
5410                    }
5411                }
5412			}
5413		});
5414	};
5415
5416	/**
5417	 * qna 상세페이지 모달
5418	 * @param idx
5419	 * @param qna_page
5420	 * @param is_hash		주소창에 직접 입력 또는 최신글 위젯 등 상세페이지 외부에서 바로 넘어왔는지 여부, Y/N
5421	 */
5422	var viewQnaDetail = function(idx,qna_page,is_hash){
5423		$(function(){
5424			$.ajax({
5425				type : 'POST',
5426				data : {idx : idx, qna_page : qna_page},
5427				url : ('/ajax/qna_detail_view.cm'),
5428				dataType : 'json',
5429				async : false,
5430				cache : false,
5431				success : function(res){
5432					if(res.msg === 'SUCCESS'){
5433						$.cocoaDialog.open({
5434							type : 'prod_detail', custom_popup : res.html, width : 800});
5435						if(checkUseHistory()){
5436							// 모달 히스토리 커스텀(IE 10 이상)
5437							var current_url = location.href.indexOf('#') === -1 ? location.href : location.href.substr(0, location.href.indexOf('#'));
5438							var back_url = document.referrer.indexOf('#') === -1 ? document.referrer : document.referrer.substr(0, document.referrer.indexOf('#'));
5439							if(current_url !== back_url && is_hash !== 'Y'){
5440								history.pushState(null, null, current_url);
5441							}
5442							history.replaceState(null, null, current_url + "#prod_detail_qna!/" + res.idx);
5443						}else{
5444							location.hash = "prod_detail_qna!/" + res.idx;
5445						}
5446						$(window).off('hashchange').on('hashchange',function(){
5447							var hash_qna_spilt = location.hash.split('!/')[1];
5448							if(!hash_qna_spilt){
5449								$.cocoaDialog.close();
5450							}else{
5451								if(checkUseHistory()){
5452									var hash_spilt_tab = location.hash.split('!/')[0];
5453									if(hash_spilt_tab === '#prod_detail_review'){
5454										viewReviewDetail(hash_qna_spilt);
5455									}else if(hash_spilt_tab === '#prod_detail_qna'){
5456										viewQnaDetail(hash_qna_spilt);
5457									}
5458								}
5459							}
5460						});
5461						$('.modal_prod_detail').off('hidden.bs.modal').on('hidden.bs.modal', function (e) {
5462              $('html').toggleClass('modal-scroll-control', false);
5463							if(is_hash !== 'Y'){
5464								removeQnawHash();
5465							}
5466						});
5467					}else{
5468						alert(res.msg);
5469					}
5470				}
5471			});
5472		});
5473	};
5474    /**
5475     * qna 상세페이지 모달
5476     * @param idx
5477     * @param qna_page
5478     * @param is_hash		주소창에 직접 입력 또는 최신글 위젯 등 상세페이지 외부에서 바로 넘어왔는지 여부, Y/N
5479     */
5480    var viewQnaDetail = function (idx, qna_page, is_hash) {
5481        $(function () {
5482            $.ajax({
5483                type: 'POST',
5484                data: { idx: idx, qna_page: qna_page },
5485                url: ('/ajax/qna_detail_view.cm'),
5486                dataType: 'json',
5487                async: false,
5488                cache: false,
5489                success: function (res) {
5490                    if (res.msg === 'SUCCESS') {
5491                        $.cocoaDialog.open({
5492                            type: 'prod_detail', custom_popup: res.html, width: 800
5493                        });
5494                        if (checkUseHistory()) {
5495                            // 모달 히스토리 커스텀(IE 10 이상)
5496                            var current_url = location.href.indexOf('#') === -1 ? location.href : location.href.substr(0, location.href.indexOf('#'));
5497                            var back_url = document.referrer.indexOf('#') === -1 ? document.referrer : document.referrer.substr(0, document.referrer.indexOf('#'));
5498                            if (current_url !== back_url && is_hash !== 'Y') {
5499                                history.pushState(null, null, current_url);
5500                            }
5501                            history.replaceState(null, null, current_url + "#prod_detail_qna!/" + res.idx);
5502                        } else {
5503                            location.hash = "prod_detail_qna!/" + res.idx;
5504                        }
5505                        $(window).off('hashchange').on('hashchange', function () {
5506                            var hash_qna_spilt = location.hash.split('!/')[1];
5507                            if (!hash_qna_spilt) {
5508                                $.cocoaDialog.close();
5509                            } else {
5510                                if (checkUseHistory()) {
5511                                    var hash_spilt_tab = location.hash.split('!/')[0];
5512                                    if (hash_spilt_tab === '#prod_detail_review') {
5513                                        viewReviewDetail(hash_qna_spilt);
5514                                    } else if (hash_spilt_tab === '#prod_detail_qna') {
5515                                        viewQnaDetail(hash_qna_spilt);
5516                                    }
5517                                }
5518                            }
5519                        });
5520                        $('.modal_prod_detail').off('hidden.bs.modal').on('hidden.bs.modal', function (e) {
5521                            $('html').toggleClass('modal-scroll-control', false);
5522                            if (is_hash !== 'Y') {
5523                                removeQnawHash();
5524                            }
5525                        });
5526                    } else {
5527                        alert(res.msg);
5528                    }
5529                }
5530            });
5531        });
5532    };
5533
5534    var removeQnawHash = function () {
5535        $(window).off('hashchange');
5536        var hash_qna_spilt = location.hash.split('!/')[1];
5537        if (hash_qna_spilt) {
5538            if (checkUseHistory()) {
5539                history.back();
5540            } else {
5541                location.href = '#prod_detail_qna';
5542            }
5543        }
5544    };
5545
5546    /**
5547     *  리뷰 모달 html 변경 (앞 뒤 버튼으로 호출)
5548     * @param idx
5549     */
5550    var changeReviewDetail = function (idx) {
5551        $.ajax({
5552            type: 'POST',
5553            data: { idx: idx, review_page: 1, only_photo: "Y" },
5554            url: ('/ajax/review_detail_change.cm'),
5555            dataType: 'json',
5556            async: false,
5557            cache: false,
5558            success: function (res) {
5559                if (res.msg === 'SUCCESS') {
5560                    $('._review_modal_body').html(res.html);
5561                    if (IS_MOBILE) {
5562                        // setReviewSwipe(res.prev_idx, res.next_idx);
5563                        $body.find('.modal.review').after(res.btn_html);
5564                        $body.find('._btn_nav_wrap').off('click').on('click', function () {
5565                            $(this).remove();
5566                        });
5567                        $body.find('#review_detail_close').off('click').on('click', function () {
5568                            $('._btn_nav_wrap').remove();
5569                        });
5570                        setReviewBtnAnimation();
5571                        setReviewClass(idx);
5572                    }
5573                    setReviewCarousel();
5574                    $(window).resize(function () {
5575                        setReviewCarousel();
5576                    });
5577                    if (res.review_code != '') SHOP_REVIEW_COMMENT.getReviewCommentHtml(res.review_code, 'modal');
5578                } else {
5579                    alert(res.msg);
5580                }
5581            }
5582        });
5583    };
5584
5585    /**
5586     * 리뷰 상세페이지 모달
5587     * @param idx
5588     * @param review_page
5589     * @param only_photo
5590     * @param is_hash		주소창에 직접 입력 또는 최신글 위젯 등 상세페이지 외부에서 바로 넘어왔는지 여부, Y/N
5591     */
5592    var viewReviewDetail = function (idx, review_page, only_photo, is_hash) {
5593        $(function () {
5594            $.ajax({
5595                type: 'POST',
5596                data: { idx: idx, review_page: review_page, only_photo: only_photo },
5597                url: ('/ajax/review_detail_view.cm'),
5598                dataType: 'json',
5599                async: false,
5600                cache: false,
5601                success: function (res) {
5602                    if (res.msg === 'SUCCESS') {
5603                        $.cocoaDialog.open({ type: 'prod_detail review', custom_popup: res.html, width: 800 });
5604                        if (checkUseHistory()) {
5605                            // 모달 히스토리 커스텀(IE 10 이상)
5606                            var current_url = location.href.indexOf('#') === -1 ? location.href : location.href.substr(0, location.href.indexOf('#'));
5607                            var back_url = document.referrer.indexOf('#') === -1 ? document.referrer : document.referrer.substr(0, document.referrer.indexOf('#'));
5608                            if (current_url !== back_url && is_hash !== 'Y') {
5609                                history.pushState(null, null, current_url);
5610                            }
5611                            history.replaceState(null, null, current_url + "#prod_detail_review!/" + res.idx);
5612                        } else {
5613                            location.hash = "prod_detail_review!/" + res.idx;
5614                        }
5615                        $(window).off('hashchange').on('hashchange', function () {
5616                            var hash_review_spilt = location.hash.split('!/')[1];
5617                            if (!hash_review_spilt) {
5618                                $.cocoaDialog.close();
5619                            } else {
5620                                if (checkUseHistory()) {
5621                                    var hash_spilt_tab = location.hash.split('!/')[0];
5622                                    if (hash_spilt_tab === '#prod_detail_qna') {
5623                                        viewQnaDetail(hash_review_spilt);
5624                                    } else if (hash_spilt_tab === '#prod_detail_review') {
5625                                        viewReviewDetail(hash_review_spilt);
5626                                    }
5627                                }
5628                            }
5629                        });
5630                        $('.modal_prod_detail').off('hidden.bs.modal').on('hidden.bs.modal', function (e) {
5631                            $('html').toggleClass('modal-scroll-control', false);
5632                            if (is_hash !== 'Y') {
5633                                removeReviewHash();
5634                            }
5635                        });
5636                    } else {
5637                        alert(res.msg);
5638                    }
5639                }
5640            });
5641        });
5642    };
5643
5644    var removeReviewHash = function () {
5645        $(window).off('hashchange');
5646        var hash_review_spilt = location.hash.split('!/')[1];
5647        if (hash_review_spilt) {
5648            if (checkUseHistory()) {
5649                history.back();
5650            } else {
5651                location.href = '#prod_detail_review';
5652            }
5653        }
5654    };
5655
5656
5657
5658    /**
5659     *
5660     */
5661    var getOnlyPhotoReview = function (only_photo_switch, is_mobile, is_one_page, rating) {
5662		    let $target_html;
5663        const isMobile_width = (window.innerWidth < SHOP_CONST.WIDTH_MOBILE);
5664        let isMobile = is_mobile === 'Y';
5665        if (isMobile !== isMobile_width) {
5666            isMobile = !isMobile;
5667        }
5668        const device_name = isMobile ? 'mobile' : 'pc';
5669        const one_page_mode = is_one_page === 'Y';
5670        const ajax_target = `/shop/prod_review_${device_name}_html.cm`;
5671
5672        if (one_page_mode) $target_html = $(`._detail_review_wrap${device_name === 'pc'? '' : '_' + device_name} ._review_wrap`);
5673		    else $target_html = $(`.product_review${device_name === 'pc'? '' : '_' + device_name}`);
5674
5675        if ($target_html.length) {
5676            var $icon_picture = $('.icon-picture');
5677            $.ajax({
5678                type: 'POST',
5679                data: {
5680                  'prod_idx': current_prod_idx,
5681                  'only_photo': only_photo_switch,
5682                  'rating': rating,
5683                },
5684                url: (ajax_target),
5685                dataType: 'html',
5686                cache: false,
5687                async: false,
5688                success: function (result) {
5689                    $target_html.html(result);
5690                    if ($icon_picture.hasClass('active')) {
5691                        $icon_picture.removeClass('active');
5692                    } else {
5693                        $icon_picture.addClass('active');
5694                    }
5695                }
5696            });
5697        }
5698    };
5699
5700    const getStarRatingReview = function (only_photo_switch, is_mobile, is_one_page, rating) {
5701      let $target_html;
5702      const isMobile_width = (window.innerWidth < SHOP_CONST.WIDTH_MOBILE);
5703      let isMobile = is_mobile === 'Y';
5704      if (isMobile !== isMobile_width) {
5705        isMobile = !isMobile;
5706      }
5707      const device_name = isMobile ? 'mobile' : 'pc';
5708      const one_page_mode = is_one_page === 'Y';
5709      const ajax_target = `/shop/prod_review_${device_name}_html.cm`;
5710
5711      if (one_page_mode) $target_html = $(`._detail_review_wrap${device_name === 'pc'? '' : '_' + device_name} ._review_wrap`);
5712      else $target_html = $(`.product_review${device_name === 'pc'? '' : '_' + device_name}`);
5713
5714      if ($target_html.length) {
5715        var $icon_picture = $('.icon-picture');
5716        $.ajax({
5717          type: 'POST',
5718          data: {
5719            'prod_idx': current_prod_idx,
5720            'only_photo': only_photo_switch,
5721            'rating' : rating,
5722          },
5723          url: (ajax_target),
5724          dataType: 'html',
5725          cache: false,
5726          async: false,
5727          success: function (result) {
5728            $target_html.html(result);
5729            const $rating = $('.icon-picture');
5730          }
5731        });
5732      }
5733    }
5734
5735    var getFirstPhotoReview = function (page) {
5736        if ($first_photo_review_wrap.length) {
5737            var is_cache = false;
5738            var callback = function (result) {
5739                $first_photo_review_wrap.html(result);
5740                if (!is_cache) IMWEB_SESSIONSTORAGE.set("PROD_REVIEW_PC_PHOTO_" + current_prod_idx + "_" + page, result.replace(/\t+/g, '').trim(), 60);
5741            };
5742
5743            var html = IMWEB_SESSIONSTORAGE.get("PROD_REVIEW_PC_PHOTO_" + current_prod_idx + "_" + page);
5744            if (html) {
5745                is_cache = true;
5746                callback(html);
5747            } else {
5748                $.ajax({
5749                    type: 'POST',
5750                    data: { prod_idx: current_prod_idx, page: page },
5751                    url: '/shop/prod_photo_review_pc_html.cm',
5752                    dataType: 'html',
5753                    cache: false,
5754                    async: false,
5755                    success: callback
5756                });
5757            }
5758        }
5759    };
5760
5761    var viewPhotoReviewMore = function (is_mobile, is_one_page) {
5762        if (is_mobile !== 'Y') {
5763            // 모바일에서는 구매평 내부에서만 호출하므로 탭 전환 필요 없음
5764            changeContentPCTab(SHOP_CONST.TAB_TYPE.REVIEW, review_page, qna_page, false);
5765        }
5766        getOnlyPhotoReview('Y', is_mobile, is_one_page);
5767        if (is_mobile !== 'Y') {
5768            if (is_one_page === 'Y') {
5769                $('._detail_review_wrap')[0].scrollIntoView();
5770            } else {
5771                $('._prod_detail_detail_lazy_load')[0].scrollIntoView();
5772            }
5773        } else {
5774            if (is_one_page === 'Y') {
5775                $('._detail_review_wrap_mobile')[0].scrollIntoView();
5776            } else {
5777                $('._prod_detail_detail_lazy_load_mobile')[0].scrollIntoView();
5778            }
5779        }
5780    };
5781
5782    var getReviewSummary = function (target) {
5783        if ($review_summary_wrap_mobile.length) {
5784            $.ajax({
5785                type: 'POST',
5786                data: { 'prod_idx': current_prod_idx, 'type': 'mobile' },
5787                url: ('/shop/prod_review_summary_html.cm'),
5788                dataType: 'html',
5789	              cache: false,
5790                async: false,
5791                success: function (result) {
5792                    if(target) {
5793                        target.style.display = 'block';
5794                        target.innerHTML = result;
5795                    } else {
5796                        $review_summary_wrap_mobile.html(result);
5797                        $review_summary_wrap_mobile.show();
5798                    }
5799                    startReviewImageRolling('mobile');
5800                }
5801            });
5802        }
5803
5804        if (!IS_MOBILE) {
5805            if ($review_summary_wrap.length) {
5806                var pc_type = $prod_detail.width() > 991 ? 'pc' : 'tablet';
5807
5808                $.ajax({
5809                    type: 'POST',
5810                    data: { 'prod_idx': current_prod_idx, 'type': pc_type },
5811                    url: ('/shop/prod_review_summary_html.cm'),
5812                    dataType: 'html',
5813	                  cache: false,
5814                    async: false,
5815                    success: function (result) {
5816                        $review_summary_wrap.html(result);
5817                        if ($prod_detail.width() <= 991) {
5818                            $review_summary_wrap.removeClass('review_summary_wrap');
5819                            $review_summary_wrap.addClass('review_summary_wrap_tablet');
5820                        }
5821                        $review_summary_wrap.show();
5822                        startReviewImageRolling(pc_type);
5823                    }
5824                });
5825            }
5826        }
5827    };
5828
5829    var startReviewImageRolling = function (type) {
5830        if (type === 'mobile') {
5831            $review_image_list = $review_summary_wrap_mobile.find('.review_image_list');
5832        } else {
5833            $review_image_list = $review_summary_wrap.find('.review_image_list');
5834        }
5835        $review_image_list_rolling = $review_image_list.find('div.owl-carousel');
5836
5837        var navtext_left = $('<i class="btl bt-angle-left" style="position: absolute; font-size: 20px; left: -25px; top: 45px;"></i>');
5838        var navtext_right = $('<i class="btl bt-angle-right" style="position: absolute; font-size: 20px; right: -25px; top: 45px;"></i>');
5839
5840        var margin = type === 'mobile' ? 5 : 10;
5841        var items = 4;
5842
5843
5844        var img_width = Math.floor(($review_image_list.innerWidth() - (margin * (items - 1))) / items);
5845        if (type === 'tablet') {
5846            if (img_width > 150) {
5847                // img_width가 150보다 크면 개수 자체를 150에 맞춰서 재계산
5848                items = Math.ceil($review_image_list.innerWidth() / (150 + margin / 2));
5849                img_width = Math.floor(($review_image_list.innerWidth() - (margin * (items - 1))) / items);
5850            }
5851        }
5852        $('._review_carousel_image').each(function () {
5853            $(this).css('width', img_width);
5854            $(this).css('height', img_width);
5855        });
5856
5857        $review_image_list_rolling.owlCarousel({
5858            dots: false,
5859            nav: type === 'pc',
5860            navText: [navtext_left, navtext_right],
5861            slideSpeed: 300,
5862            paginationSpeed: 400,
5863            animateOut: 'fadeOut',
5864            autoWidth: true,
5865            items: items,
5866            margin: margin
5867        });
5868    };
5869
5870
5871    /**
5872     * 한페이지 전체 노출에서 리뷰 삭제 코드
5873     * @param prod_code
5874     * @param only_photo
5875     * @returns {boolean}
5876     */
5877
5878    const deleteReviewInTabDisplay = function (prod_code, only_photo) {
5879
5880        if (options.shop_view_tab_display !== "Y") return false;
5881
5882        $.ajax({
5883            type: 'POST',
5884            data: { 'prod_idx': current_prod_idx, 'review_page': review_page, 'qna_page': qna_page, 'only_photo': only_photo },
5885            url: ('/shop/prod_review_pc_html.cm'),
5886            dataType: 'html',
5887            cache: false,
5888            async: true,
5889            success: function (result) {
5890                $('.categorize ._review_wrap').html(result);
5891            }
5892        });
5893
5894        $.ajax({
5895            type: 'POST',
5896            data: { 'prod_idx': current_prod_idx, 'review_page': review_page, 'qna_page': qna_page, 'only_photo': only_photo },
5897            url: ('/shop/prod_review_mobile_html.cm'),
5898            dataType: 'html',
5899            cache: false,
5900            async: true,
5901            success: function (result) {
5902                $('.categorize-mobile ._review_wrap').html(result);
5903            }
5904        });
5905
5906    }
5907
5908    /**
5909     *  pc 상세페이지 탭 이벤트 처리
5910     * @param type 탭 type
5911     * @param r_p
5912     * @param q_p
5913     * @param paging_on 스크롤 조정 //deprecated
5914     * @param only_photo
5915     * @param shop_view_body_width
5916     * @param rating
5917     */
5918    var changeContentPCTab = function (type, r_p, q_p, paging_on, only_photo, rating = 0, shop_view_body_width) {
5919        var $site_prod_nav_wrap = $('.categorize .site_prod_nav_wrap');
5920
5921        $site_prod_nav_wrap.find(`a`).removeClass('active');
5922
5923        current_content_tab = type;
5924
5925        $site_prod_nav_wrap.find(`a._${type}`).addClass('active');
5926
5927		var review_paging = false;
5928		var qna_paging = false;
5929
5930		if(parseInt(r_p) > 0){
5931			if(review_page != parseInt(r_p) && type == SHOP_CONST.TAB_TYPE.REVIEW){
5932				review_paging = true;
5933			}
5934			review_page = parseInt(r_p);
5935		}else{
5936			r_p = review_page;
5937		}
5938		if(parseInt(q_p) > 0){
5939			if(qna_page != parseInt(q_p) && type == SHOP_CONST.TAB_TYPE.QNA){
5940				qna_paging = true;
5941			}
5942			qna_page = parseInt(q_p);
5943		}else{
5944			q_p = qna_page;
5945		}
5946
5947	    if(options.shop_view_tab_display !== "Y" || review_paging || qna_paging){
5948		    switch (true) {
5949			    case use_cdn_optimized && (type === SHOP_CONST.TAB_TYPE.DETAIL): {
5950				    if (options.shop_view_tab_display !== "Y"){
5951					    if($review_summary_wrap.html()){
5952						    $review_summary_wrap.empty();
5953						    $review_summary_wrap.hide();
5954					    }
5955				    }
5956					$prod_detail_content_pc.empty();
5957					Promise.resolve(loadTemplate('prodDetailPC', $prod_detail_content_pc, async(node) => {
5958						if(use_lazy_load){
5959							await setImageWidthHeightBeforeLoad(node);
5960						}
5961						return node;
5962					})).then(() => {
5963						// 크리마 리뷰 사용 중일 시 위젯 호출
5964						if(document.querySelector(".crema-product-reviews")){
5965							if(typeof crema !== 'undefined' && typeof crema.run === 'function'){
5966								crema.run();
5967							}
5968						}
5969            setTimeout(()=>{
5970              document.querySelectorAll('#prod_detail table._table_responsive').forEach((table)=>{
5971                if (table.classList.contains('table')) return;
5972
5973                table.classList.add('table');
5974                table.outerHTML = `<div class="table-responsive">${table.outerHTML}</div>`;
5975              });
5976            }, 0);
5977					safeScheduleProdDetailBodyExpandedHeightUpdate();
5978					});
5979				    if (typeof shop_view_body_width != 'undefined') {
5980					    $prod_detail_content_pc.css({ 'width': shop_view_body_width + '%' });
5981				    }
5982
5983				    $prod_detail_content_pc.toggleClass('product_detail', true);
5984				    $prod_detail_content_pc.toggleClass('product_review', false);
5985				    $prod_detail_content_pc.toggleClass('product_qna', false);
5986				    $prod_detail_content_pc.toggleClass('product_return', false);
5987				    break;
5988				}
5989				case type === SHOP_CONST.TAB_TYPE.DETAIL: {
5990					if (options.shop_view_tab_display !== "Y"){
5991					    if($review_summary_wrap.html()){
5992						    $review_summary_wrap.empty();
5993						    $review_summary_wrap.hide();
5994					    }
5995				    }
5996				    var pc_detail_html = IMWEB_TEMPLATE.loadSimple("prodDetailPC");
5997				    $prod_detail_content_pc.html(pc_detail_html);
5998				    if (typeof shop_view_body_width != 'undefined') {
5999					    $prod_detail_content_pc.css({ 'width': shop_view_body_width + '%' });
6000				    }
6001				    if (use_lazy_load) {
6002					    runLazyload();
6003				    }
6004					// 크리마 리뷰 사용 중일 시 위젯 호출
6005					if(document.querySelector(".crema-product-reviews")){
6006						if(typeof crema !== 'undefined' && typeof crema.run === 'function'){
6007							crema.run();
6008						}
6009					}
6010					safeScheduleProdDetailBodyExpandedHeightUpdate();
6011
6012				    $prod_detail_content_pc.toggleClass('product_detail', true);
6013				    $prod_detail_content_pc.toggleClass('product_review', false);
6014				    $prod_detail_content_pc.toggleClass('product_qna', false);
6015				    $prod_detail_content_pc.toggleClass('product_return', false);
6016				    break;
6017				}
6018			    case type === SHOP_CONST.TAB_TYPE.REVIEW: {
6019				    if (!$review_summary_wrap.html()) {
6020					    getReviewSummary();
6021				    }
6022				    $.ajax({
6023					    type: 'POST',
6024					    data: { 'prod_idx': current_prod_idx, 'review_page': review_page, 'qna_page': qna_page, 'only_photo': only_photo, 'is_mobile' : SHOP_CONST.IS_MOBILE, 'rating' : rating },
6025					    url: ('/shop/prod_review_pc_html.cm'),
6026					    dataType: 'html',
6027					    cache: false,
6028					    async: false,
6029					    success: function (result) {
6030
6031							if ($('._external_widget_review_mo_position')) {
6032								$('._external_widget_review_mo_position').remove();
6033							}
6034
6035							if (tab_type === 'Y') {
6036							    $('.categorize ._review_wrap').html(result);
6037						    } else {
6038                                // 구조 변경 이후 해당 부분 아닌데, 이부분에 코드 추가됨
6039							    $prod_detail_content_pc.html(result).css({ 'width': '100%' });
6040						    }
6041					    }
6042				    });
6043
6044				    $prod_detail_content_pc.toggleClass('product_detail', false);
6045				    $prod_detail_content_pc.toggleClass('product_review', true);
6046				    $prod_detail_content_pc.toggleClass('product_qna', false);
6047				    $prod_detail_content_pc.toggleClass('product_return', false);
6048				    break;
6049				}
6050			    case type === SHOP_CONST.TAB_TYPE.QNA: {
6051				    if (options.shop_view_tab_display !== "Y"){
6052					    if($review_summary_wrap.html()){
6053						    $review_summary_wrap.empty();
6054						    $review_summary_wrap.hide();
6055					    }
6056				    }
6057				    $.ajax({
6058					    type: 'POST',
6059					    data: { 'prod_idx': current_prod_idx, 'review_page': review_page, 'qna_page': qna_page },
6060					    url: ('/shop/prod_qna_pc_html.cm'),
6061					    dataType: 'html',
6062					    cache: false,
6063					    async: false,
6064					    success: function (result) {
6065						    if (tab_type === 'Y') {
6066							    $('.categorize ._qna_wrap').html(result);
6067						    } else {
6068							    $prod_detail_content_pc.html(result).css({ 'width': '100%' });
6069						    }
6070					    }
6071				    });
6072
6073				    $prod_detail_content_pc.toggleClass('product_detail', false);
6074				    $prod_detail_content_pc.toggleClass('product_review', false);
6075				    $prod_detail_content_pc.toggleClass('product_qna', true);
6076				    $prod_detail_content_pc.toggleClass('product_return', false);
6077				    break;
6078				}
6079			    case type === SHOP_CONST.TAB_TYPE.RETURN: {
6080				    if (options.shop_view_tab_display !== "Y"){
6081					    if($review_summary_wrap.html()){
6082						    $review_summary_wrap.empty();
6083						    $review_summary_wrap.hide();
6084					    }
6085				    }
6086				    if (last_refund_data) {
6087					    const prod_detail_return_html = last_refund_data.use_shop_return ? IMWEB_TEMPLATE.loadSimple("prodReturnPc", last_refund_data) : IMWEB_TEMPLATE.loadSimple("prodReturnDisable", null);
6088					    if (tab_type === 'Y') {
6089						    var $_target = $('.categorize ._return_wrap');
6090					    }else{
6091						    var $_target = $prod_detail_content_pc;
6092					    }
6093					    $_target.empty();
6094					    $_target.html(prod_detail_return_html).css({ 'width': '100%' });
6095				    }
6096
6097				    $prod_detail_content_pc.toggleClass('product_detail', false);
6098				    $prod_detail_content_pc.toggleClass('product_review', false);
6099				    $prod_detail_content_pc.toggleClass('product_qna', false);
6100				    $prod_detail_content_pc.toggleClass('product_return', true);
6101				    break;
6102				}
6103		    }
6104	    }
6105
6106	    let margin_top = 0;
6107	    let prod_tab_target;
6108	    if(options.shop_view_tab_display !== "Y"){
6109		    prod_tab_target = document.querySelector('#prod_tab_target');
6110		    if(options.shop_tab_fixed !== "Y"){
6111			    margin_top = -1 * getFixedHeaderHeight() + 'px';
6112		    }else if(review_paging){
6113			    margin_top = (-1 * document.querySelector('#fixed_tab ._prod_detail_tab_fixed').getBoundingClientRect().height) + 'px';
6114		    }
6115	    }else{
6116		    prod_tab_target = document.querySelector(`#prod_detail_${type}_target`);
6117		    margin_top = (-1 * document.querySelector('#fixed_tab ._prod_detail_tab_fixed').getBoundingClientRect().height) + 'px';
6118	    }
6119	    if(review_paging){
6120		    prod_tab_target = document.querySelector('.categorize ._prod_detail_review_board_target');
6121	    }
6122
6123	    if(review_paging || qna_paging || is_init_detail){
6124			if(prod_tab_target){
6125				prod_tab_target.style.top = margin_top;
6126				prod_tab_target.scrollIntoView();
6127			}
6128		    if(is_init_detail){
6129			    setTabHistory(type);
6130			    setTimeout(() => {
6131				    checkSeemore(type);
6132			    }, 200);
6133		    }
6134	    }
6135
6136    };
6137
6138    /**
6139     * 상세정보 탭 변경 처리
6140     * @param type
6141     * @param r_p
6142     * @param q_p
6143     * @param paging_on 스크롤 조정 //deprecated
6144     * @param only_photo
6145     * @param rating
6146     */
6147    var changeContentTab = function (type, r_p, q_p, paging_on, only_photo, rating = 0) {
6148        if (current_content_tab != '') {
6149            $('.active').parent('li').removeClass('activeborder');
6150            $prod_detail_content_tab_mobile.find('a').removeClass('active');
6151        }
6152        current_content_tab = type;
6153        $prod_detail_content_tab_mobile.find('a._' + type).addClass('active');
6154        $('.table-cell > .active').parent('li').addClass('activeborder');
6155
6156        var $seemore_wrap = $body.find('._seemore_wrap');
6157
6158	    var review_paging = false;
6159	    var qna_paging = false;
6160
6161	    if(parseInt(r_p) > 0){
6162		    if(review_page != parseInt(r_p) && type == SHOP_CONST.TAB_TYPE.REVIEW){
6163			    review_paging = true;
6164		    }
6165		    review_page = parseInt(r_p);
6166	    }else{
6167		    r_p = review_page;
6168	    }
6169	    if(parseInt(q_p) > 0){
6170		    if(qna_page != parseInt(q_p) && type == SHOP_CONST.TAB_TYPE.QNA){
6171			    qna_paging = true;
6172		    }
6173		    qna_page = parseInt(q_p);
6174	    }else{
6175		    q_p = qna_page;
6176	    }
6177
6178	    if(options.shop_view_tab_display !== "Y" || review_paging || qna_paging){
6179		    switch (true) {
6180			    case use_cdn_optimized && (type === SHOP_CONST.TAB_TYPE.DETAIL): {
6181				    if (options.shop_view_tab_display !== "Y"){
6182					    if($review_summary_wrap_mobile.html()){
6183						    $review_summary_wrap_mobile.empty();
6184						    $review_summary_wrap_mobile.hide();
6185					    }
6186				    }
6187
6188					$prod_detail_content_mobile.empty();
6189					Promise.resolve(loadTemplate('prodDetailMobile', $prod_detail_content_mobile, async (node) => {
6190						if (use_lazy_load) {
6191							await setImageWidthHeightBeforeLoad(node);
6192						}
6193						if (node.children.length === 0) {
6194							$prod_detail_content_mobile.html('<div style="text-align: center; padding: 50px 0;"><div class="body_font_color_40" style="font-size: 18px; margin:30px"><div> </div>');
6195						}
6196						return node;
6197					})).then(() => {
6198						setMobileVideoRatio();
6199						// 크리마 리뷰 사용 중일 시 위젯 호출
6200						if(document.querySelector(".crema-product-reviews")){
6201							if(typeof crema !== 'undefined' && typeof crema.run === 'function'){
6202								crema.run();
6203							}
6204						}
6205            setTimeout(()=>{
6206              document.querySelectorAll('#prod_detail table._table_responsive').forEach((table)=>{
6207                if (table.classList.contains('table')) return;
6208
6209                table.classList.add('table');
6210                table.outerHTML = `<div class="table-responsive">${table.outerHTML}</div>`;
6211              });
6212            }, 0);
6213					safeScheduleProdDetailBodyExpandedHeightUpdate();
6214					});
6215
6216				    $prod_detail_content_mobile.toggleClass('product_detail_mobile', true);
6217				    $prod_detail_content_mobile.toggleClass('product_review_mobile', false);
6218				    $prod_detail_content_mobile.toggleClass('product_qna_mobile', false);
6219				    $prod_detail_content_mobile.toggleClass('product_return_mobile', false);
6220				    break;
6221				}
6222			    case type === SHOP_CONST.TAB_TYPE.DETAIL: {
6223				    if (options.shop_view_tab_display !== "Y"){
6224					    if($review_summary_wrap_mobile.html()){
6225						    $review_summary_wrap_mobile.empty();
6226						    $review_summary_wrap_mobile.hide();
6227					    }
6228				    }
6229
6230				    var mobile_detail_html = IMWEB_TEMPLATE.loadSimple('prodDetailMobile');
6231				    if (mobile_detail_html) {
6232					    if (use_lazy_load) {
6233                            $prod_detail_content_mobile.html(mobile_detail_html);
6234                            runLazyload();
6235					    } else {
6236						    $prod_detail_content_mobile.html(mobile_detail_html);
6237					    }
6238				    } else {
6239					    $prod_detail_content_mobile.html('<div style="text-align: center; padding: 50px 0;"><div class="body_font_color_40" style="font-size: 18px; margin:30px"><div> </div>');
6240				    }
6241				    setMobileVideoRatio();
6242
6243					// 크리마 리뷰 사용 중일 시 위젯 호출
6244				    if(document.querySelector(".crema-product-reviews")){
6245					    if(typeof crema !== 'undefined' && typeof crema.run === 'function'){
6246						    crema.run();
6247					    }
6248				    }
6249					safeScheduleProdDetailBodyExpandedHeightUpdate();
6250
6251				    $prod_detail_content_mobile.toggleClass('product_detail_mobile', true);
6252				    $prod_detail_content_mobile.toggleClass('product_review_mobile', false);
6253				    $prod_detail_content_mobile.toggleClass('product_qna_mobile', false);
6254				    $prod_detail_content_mobile.toggleClass('product_return_mobile', false);
6255				    break;
6256				}
6257			    case type === SHOP_CONST.TAB_TYPE.REVIEW: {
6258				    $.ajax({
6259					    type: review_paging ? 'POST' : 'GET',
6260					    data: { 'prod_idx': current_prod_idx, 'review_page': review_page, 'qna_page': qna_page, 'only_photo': only_photo, 'member_hash': MEMBER_HASH, 'is_mobile' : SHOP_CONST.IS_MOBILE, 'rating' : rating },
6261					    url: ('/shop/prod_review_mobile_html.cm'),
6262					    dataType: 'html',
6263					    async: false,
6264					    success: function (result) {
6265						    if ($('._external_widget_review_pc_position')) {
6266							    $('._external_widget_review_pc_position').remove();
6267						    }
6268						    if (!$review_summary_wrap_mobile.html()) {
6269							    getReviewSummary();
6270						    }
6271						    if (tab_type === 'Y') {
6272							    $('.categorize-mobile ._review_wrap').html(result);
6273						    }else{
6274							    $prod_detail_content_mobile.html(result);
6275						    }
6276
6277						    $prod_detail_content_mobile.toggleClass('product_detail_mobile', false);
6278						    $prod_detail_content_mobile.toggleClass('product_review_mobile', true);
6279						    $prod_detail_content_mobile.toggleClass('product_qna_mobile', false);
6280						    $prod_detail_content_mobile.toggleClass('product_return_mobile', false);
6281					    }
6282				    });
6283				    break;
6284				}
6285			    case type === SHOP_CONST.TAB_TYPE.QNA: {
6286				    $.ajax({
6287					    type: 'POST',
6288					    data: { 'prod_idx': current_prod_idx, 'review_page': review_page, 'qna_page': qna_page },
6289					    url: ('/shop/prod_qna_mobile_html.cm'),
6290					    dataType: 'html',
6291					    cache: false,
6292					    async: false,
6293					    success: function (result) {
6294						    if (options.shop_view_tab_display !== "Y"){
6295							    if($review_summary_wrap_mobile.html()){
6296								    $review_summary_wrap_mobile.empty();
6297								    $review_summary_wrap_mobile.hide();
6298							    }
6299						    }
6300						    if (tab_type === 'Y') {
6301							    $('.categorize-mobile ._qna_wrap').html(result);
6302						    } else {
6303							    $prod_detail_content_mobile.html(result).css({ 'width': '100%' });
6304						    }
6305						    $prod_detail_content_mobile.toggleClass('product_detail_mobile', false);
6306						    $prod_detail_content_mobile.toggleClass('product_review_mobile', false);
6307						    $prod_detail_content_mobile.toggleClass('product_qna_mobile', true);
6308						    $prod_detail_content_mobile.toggleClass('product_return_mobile', false);
6309					    }
6310				    });
6311				    break;
6312				}
6313			    case type === SHOP_CONST.TAB_TYPE.RETURN: {
6314				    if (options.shop_view_tab_display !== "Y"){
6315					    if($review_summary_wrap_mobile.html()){
6316						    $review_summary_wrap_mobile.empty();
6317						    $review_summary_wrap_mobile.hide();
6318					    }
6319				    }
6320				    if (last_refund_data) {
6321					    const prod_detail_return_html = last_refund_data.use_shop_return ? IMWEB_TEMPLATE.loadSimple("prodReturnMobile", last_refund_data) : IMWEB_TEMPLATE.loadSimple("prodReturnDisable", null);
6322					    if (tab_type === 'Y') {
6323						    var $_target = $('.categorize-mobile ._return_wrap');
6324					    }else{
6325						    var $_target = $prod_detail_content_mobile;
6326					    }
6327					    $_target.empty();
6328					    $_target.html(prod_detail_return_html).css({ 'width': '100%' });
6329				    }
6330				    $prod_detail_content_mobile.toggleClass('product_detail_mobile', false);
6331				    $prod_detail_content_mobile.toggleClass('product_review_mobile', false);
6332				    $prod_detail_content_mobile.toggleClass('product_qna_mobile', false);
6333				    $prod_detail_content_mobile.toggleClass('product_return_mobile', true);
6334				    break;
6335				}
6336		    }
6337	    }
6338
6339		let margin_top = 0;
6340		let prod_tab_target;
6341		if(options.shop_view_tab_display !== "Y"){
6342			prod_tab_target = document.querySelector('#prod_tab_target_mobile');
6343			if(options.shop_tab_fixed !== "Y"){
6344				margin_top = -1 * getFixedHeaderHeight() + 'px';
6345			}else if(review_paging){
6346				margin_top = (-1 * document.querySelector('#fixed_tab_mobile ._prod_detail_tab_fixed').getBoundingClientRect().height) + 'px';
6347			}
6348		}else{
6349			prod_tab_target = document.querySelector(`#prod_detail_${type}_target_mobile`);
6350			margin_top = (-1 * document.querySelector('#fixed_tab_mobile ._prod_detail_tab_fixed').getBoundingClientRect().height) + 'px';
6351		}
6352	    if(review_paging){
6353		    prod_tab_target = document.querySelector('.categorize-mobile ._prod_detail_review_board_target');
6354	    }
6355
6356		if(review_paging || qna_paging || is_init_detail){
6357			if(prod_tab_target){
6358				prod_tab_target.style.top = margin_top;
6359				prod_tab_target.scrollIntoView();
6360			}
6361			if(is_init_detail){
6362				setTabHistory(type);
6363				setTimeout(() => {
6364					checkSeemore(type);
6365				}, 200);
6366			}
6367		}
6368    };
6369
6370	var getFixedHeaderHeight = function(){
6371		let margin_top = 0;
6372		let $fixed_header_disable;
6373		if(document.querySelector('#inline_header_fixed')){
6374			if (!IS_MOBILE) {
6375				$fixed_header_disable = $('#inline_header_fixed');
6376			} else {
6377				$fixed_header_disable = $('#inline_header_mobile').find('._fixed_header_section');
6378			}
6379		}else{
6380			if (!IS_MOBILE) {
6381				$fixed_header_disable = $('#inline_header_normal').find('._fixed_header_section');
6382			} else {
6383				$fixed_header_disable = $('#inline_header_mobile').find('._fixed_header_section');
6384			}
6385		}
6386		for ( var i = 0; i < $fixed_header_disable.length; i++ ) {
6387			var target = $fixed_header_disable[i].getBoundingClientRect();
6388			margin_top += target.height;
6389		}
6390		return margin_top;
6391	};
6392
6393    /**
6394     * 상세정보 펼쳐보기 버튼 노출 처리
6395     */
6396    let checkSeemore = function (type) {
6397        const $seemore_wrap = $body.find('._seemore_wrap');
6398        if($seemore_wrap.length === 0) return;
6399
6400        // 탭 노출 방식이 한 페이지 노출 방식이거나 탭 노출 방식이 아니면서 상세정보 탭일 때만 펼쳐보기 노출
6401        if(options.shop_view_tab_display == "Y" || (options.shop_view_tab_display == "N" && type == SHOP_CONST.TAB_TYPE.DETAIL)) {
6402			// template 이 변환되는 시점이 비동기가 되어 당장 사이즈를 알 수가 없다.
6403            // if((!is_mobile_width && $prod_detail_content_pc.outerHeight() > SHOP_CONST.SEEMORE_HEIGHT.PC) || (is_mobile_width && $prod_detail_content_mobile.outerHeight() > SHOP_CONST.SEEMORE_HEIGHT.MOBILE)) {
6404                $prod_detail_content_pc.toggleClass('hide_seemore', false);
6405                $prod_detail_content_mobile.toggleClass('hide_seemore', false);
6406				$seemore_wrap.show();
6407				safeScheduleProdDetailBodyExpandedHeightUpdate();
6408				return;
6409            // }
6410        }
6411		$prod_detail_content_pc.toggleClass('hide_seemore', true);
6412		$prod_detail_content_mobile.toggleClass('hide_seemore', true);
6413		$seemore_wrap.hide();
6414		safeScheduleProdDetailBodyExpandedHeightUpdate();
6415	};
6416
6417    var setTabHistory = function (type) {
6418        if (!$('body').hasClass('admin')) {
6419            if (checkUseHistory()) {
6420                var current_url = location.href.indexOf('#') === -1 ? location.href : location.href.substr(0, location.href.indexOf('#'));
6421                switch (type) {
6422                    case SHOP_CONST.TAB_TYPE.DETAIL:
6423                        history.replaceState(null, null, current_url + "#prod_detail_detail");
6424                        break;
6425                    case SHOP_CONST.TAB_TYPE.REVIEW:
6426                        history.replaceState(null, null, current_url + "#prod_detail_review");
6427                        break;
6428                    case SHOP_CONST.TAB_TYPE.QNA:
6429                        history.replaceState(null, null, current_url + "#prod_detail_qna");
6430                        break;
6431                    case SHOP_CONST.TAB_TYPE.RETURN:
6432                        history.replaceState(null, null, current_url + "#prod_detail_return");
6433                        break;
6434                    default:
6435                        let target_tab = options.first_tab === "prod_detail" ? SHOP_CONST.TAB_TYPE.DETAIL : options.first_tab === "prod_review" ? SHOP_CONST.TAB_TYPE.REVIEW : options.first_tab === "prod_qna" ? SHOP_CONST.TAB_TYPE.QNA : options.first_tab === "prod_return" ? SHOP_CONST.TAB_TYPE.RETURN : "";
6436
6437                        history.replaceState(null, null, current_url + `#prod_detail_${target_tab}`);
6438                        break;
6439                }
6440            } else {
6441                switch (type) {
6442                    case SHOP_CONST.TAB_TYPE.DETAIL:
6443                        location.hash = "prod_detail_detail";
6444                        break;
6445                    case SHOP_CONST.TAB_TYPE.REVIEW:
6446                        location.hash = "prod_detail_review";
6447                        break;
6448                    case SHOP_CONST.TAB_TYPE.QNA:
6449                        location.hash = "prod_detail_qna";
6450                        break;
6451                    case SHOP_CONST.TAB_TYPE.RETURN:
6452                        location.hash = "prod_detail_return";
6453                        break;
6454                    default:
6455                        let target_tab = options.first_tab === "prod_detail" ? SHOP_CONST.TAB_TYPE.DETAIL : options.first_tab === "prod_review" ? SHOP_CONST.TAB_TYPE.REVIEW : options.first_tab === "prod_qna" ? SHOP_CONST.TAB_TYPE.QNA : options.first_tab === "prod_return" ? SHOP_CONST.TAB_TYPE.RETURN : "";
6456
6457                        location.hash = `prod_detail_${target_tab}`;
6458                        break;
6459                }
6460            }
6461        }
6462    };
6463
6464    var setMobileVideoRatio = function () {
6465        // 동영상 크기 값이 입력되어 있을 시 모바일에서도 입력된 크기 기준으로 비율 계산해서 반영
6466        var $fr_video = $prod_detail_content_mobile.find('.fr-video.fr-dvb');
6467		if ($fr_video.length > 0) {
6468            $fr_video.each(function (k, v) {
6469                var $fr_video_iframe = $(v).find('iframe');
6470                if ($fr_video_iframe.length > 0) {
6471					var origin_width = $fr_video_iframe[0].style.width;
6472                    var origin_height = $fr_video_iframe[0].style.height;
6473					if (origin_width != '' && origin_height != '') {
6474                        // px 단위일 때만 비율 보정
6475                        var origin_width_split = origin_width.split('px');
6476                        var origin_height_split = origin_height.split('px');
6477						if (origin_width_split.length === 2 && origin_height_split.length === 2) {
6478                            var video_ratio = (origin_height_split[0] / origin_width_split[0]) * 100;
6479                            $(v).css('padding-bottom', video_ratio + '%');
6480                        }
6481                    }
6482                }
6483            });
6484        }
6485    };
6486
6487    var prepareAddCartAlarmModal = function () {
6488        if (typeof $.cocoaDialog === 'object' && typeof $.cocoaDialog.close === 'function') {
6489            $.cocoaDialog.close();
6490        }
6491        if (typeof imSheet !== 'undefined' && typeof imSheet.close === 'function') {
6492            imSheet.close('prod_additional_sheet');
6493        }
6494    };
6495
6496	    var openAddCartAlarmHtml = function (html) {
6497	        if (typeof html !== 'string' || html.trim() === '') {
6498	            $add_cart_alarm.show();
6499	            return;
6500	        }
6501
6502        if (is_mobile_width) {
6503            imSheet.close('shop_detail_add_cart_alarm_sheet', function () {
6504                imSheet.open({
6505                    id: 'shop_detail_add_cart_alarm_sheet',
6506                    html: html,
6507                    backdrop: 'rgba(0, 0, 0, 0.15)',
6508                    zIndex: 17001
6509                });
6510            });
6511        } else {
6512            if (typeof $.cocoaDialog === 'object' && typeof $.cocoaDialog.close === 'function') {
6513                $.cocoaDialog.close();
6514            }
6515            $.cocoaDialog.open({
6516                type: 'site_shop_free_ship_notify',
6517                custom_popup: html,
6518                pc_width: 440
6519            });
6520	        }
6521	    };
6522
6523	    var PRODUCT_RECOMMENDATION_ADD_CART_ALARM_SHOWN_SESSION_KEY_PREFIX = 'SHOP_PRODUCT_RECOMMEND:SHOWN:PROD:';
6524
6525	    var getProductRecommendationAddCartAlarmShownSessionKey = function (prodIdx) {
6526	        return PRODUCT_RECOMMENDATION_ADD_CART_ALARM_SHOWN_SESSION_KEY_PREFIX + prodIdx;
6527	    };
6528
6529	    var hasProductRecommendationAddCartAlarmShown = function (prodIdx) {
6530	        if (_isFreeShipNotifySalesToolPreview) {
6531	            return true;
6532	        }
6533	        if (!prodIdx) {
6534	            return false;
6535	        }
6536
6537	        try {
6538	            if (typeof window.sessionStorage === 'undefined') {
6539	                return false;
6540	            }
6541	            return window.sessionStorage.getItem(getProductRecommendationAddCartAlarmShownSessionKey(prodIdx)) === 'Y';
6542	        } catch (e) {
6543	            return false;
6544	        }
6545	    };
6546
6547	    var markProductRecommendationAddCartAlarmShown = function (prodIdx) {
6548	        if (_isFreeShipNotifySalesToolPreview) {
6549	            return;
6550	        }
6551	        if (!prodIdx) {
6552	            return;
6553	        }
6554
6555	        try {
6556	            if (typeof window.sessionStorage === 'undefined') {
6557	                return;
6558	            }
6559	            window.sessionStorage.setItem(getProductRecommendationAddCartAlarmShownSessionKey(prodIdx), 'Y');
6560	        } catch (e) {}
6561	    };
6562
6563    function getFreeShipNotifyPreviewMarker() {
6564        var markerEl = document.getElementById('shop_detail_add_cart_alarm_with_free_ship_notify');
6565        if (markerEl && document.documentElement.contains(markerEl)) {
6566            return markerEl;
6567        }
6568
6569        if (!_isFreeShipNotifySalesToolPreview) {
6570            return null;
6571        }
6572
6573        var inlineMarkerEl = document.querySelector('#foFreeShipNotifyProdDetailMagnet.foFreeShipNotifyMagnet, .foFreeShipNotifyMagnet[data-type="prod"]');
6574        var inlineThreshold = inlineMarkerEl
6575            ? parseFloat(inlineMarkerEl.getAttribute('data-init-deliv-price-flexable-key') || '0')
6576            : 0;
6577        return inlineMarkerEl && !isNaN(inlineThreshold) && inlineThreshold > 0 ? inlineMarkerEl : null;
6578    }
6579
6580    function isFreeShipNotifyPreviewReady() {
6581        return !!(
6582            window.IMWEB_SHOP_FREE_SHIP_NOTIFY_SDK
6583            && getFreeShipNotifyPreviewMarker()
6584        );
6585    }
6586
6587    function flushFreeShipNotifyPreviewReadyCallbacks() {
6588        if (!isFreeShipNotifyPreviewReady()) {
6589            return;
6590        }
6591
6592        window.removeEventListener('imweb:freeShipNotify:sdk-ready', flushFreeShipNotifyPreviewReadyCallbacks);
6593        document.removeEventListener('DOMContentLoaded', flushFreeShipNotifyPreviewReadyCallbacks);
6594        window.removeEventListener('load', flushFreeShipNotifyPreviewReadyCallbacks);
6595        if (_freeShipNotifyPreviewReadyObserver) {
6596            _freeShipNotifyPreviewReadyObserver.disconnect();
6597            _freeShipNotifyPreviewReadyObserver = null;
6598        }
6599
6600        var callbacks = _freeShipNotifyPreviewReadyCallbacks.splice(0);
6601        callbacks.forEach(function (callback) {
6602            callback();
6603        });
6604    }
6605
6606    function waitForFreeShipNotifyPreviewReady(callback) {
6607        if (!_isFreeShipNotifySalesToolPreview) {
6608            callback();
6609            return;
6610        }
6611        if (isFreeShipNotifyPreviewReady()) {
6612            callback();
6613            return;
6614        }
6615
6616        _freeShipNotifyPreviewReadyCallbacks.push(callback);
6617
6618        window.addEventListener('imweb:freeShipNotify:sdk-ready', flushFreeShipNotifyPreviewReadyCallbacks);
6619        document.addEventListener('DOMContentLoaded', flushFreeShipNotifyPreviewReadyCallbacks);
6620        window.addEventListener('load', flushFreeShipNotifyPreviewReadyCallbacks);
6621
6622        if (!_freeShipNotifyPreviewReadyObserver && typeof MutationObserver !== 'undefined') {
6623            _freeShipNotifyPreviewReadyObserver = new MutationObserver(flushFreeShipNotifyPreviewReadyCallbacks);
6624            _freeShipNotifyPreviewReadyObserver.observe(document.documentElement, {
6625                attributes: true,
6626                childList: true,
6627                subtree: true
6628            });
6629        }
6630    }
6631
6632	    /**
6633	     * 장바구니 담기 완료 모달 표시.
6634	     * 무료 배송 안내 대상 상품이면 무료 배송 안내를 우선 노출하고, 이미 노출된 경우 기본 모달로 fallback 한다.
6635	     * 무료 배송 안내 대상 상품이 아닐 때만 구매 상품 추천 모달을 노출한다.
6636	     */
6637    var showAddCartAlarm = function () {
6638        var freeShipNotifySdk = window.IMWEB_SHOP_FREE_SHIP_NOTIFY_SDK;
6639        var freeShipMarkerEl = $add_cart_alarm_with_free_ship_notify && $add_cart_alarm_with_free_ship_notify.length > 0
6640            ? $add_cart_alarm_with_free_ship_notify.get(0)
6641            : null;
6642        if (!freeShipMarkerEl || !document.documentElement.contains(freeShipMarkerEl)) {
6643            freeShipMarkerEl = getFreeShipNotifyPreviewMarker();
6644        }
6645        if (freeShipMarkerEl) {
6646            $add_cart_alarm_with_free_ship_notify = $(freeShipMarkerEl);
6647        }
6648        var initDelivPriceFlexableKey = freeShipMarkerEl
6649            ? parseFloat(freeShipMarkerEl.getAttribute('data-init-deliv-price-flexable-key') || '0')
6650            : 0;
6651        var productRecommendationMarkerEl = $add_cart_alarm_with_product_recommendation && $add_cart_alarm_with_product_recommendation.length > 0
6652            ? $add_cart_alarm_with_product_recommendation.get(0)
6653            : null;
6654        if (!productRecommendationMarkerEl || !document.documentElement.contains(productRecommendationMarkerEl)) {
6655            productRecommendationMarkerEl = document.getElementById('shop_detail_add_cart_alarm_with_product_recommendation');
6656        }
6657        if (productRecommendationMarkerEl) {
6658            $add_cart_alarm_with_product_recommendation = $(productRecommendationMarkerEl);
6659        }
6660        var productRecommendationProdIdx = productRecommendationMarkerEl
6661            ? parseInt(productRecommendationMarkerEl.getAttribute('data-prod-idx') || '0', 10)
6662            : 0;
6663        var isFreeShipNotifyTargetProduct = !!freeShipMarkerEl;
6664        if (_isFreeShipNotifySalesToolPreview && (!isFreeShipNotifyTargetProduct || !freeShipNotifySdk)) {
6665            if (!_isWaitingFreeShipNotifySdkForAddCartAlarm) {
6666                _isWaitingFreeShipNotifySdkForAddCartAlarm = true;
6667                waitForFreeShipNotifyPreviewReady(function () {
6668                    _isWaitingFreeShipNotifySdkForAddCartAlarm = false;
6669                    showAddCartAlarm();
6670                });
6671            }
6672            return;
6673        }
6674        var hasFreeShipNotifyAlarmShown = !!(
6675            freeShipNotifySdk
6676            && freeShipNotifySdk.hasAddCartAlarmShown()
6677        );
6678
6679        var canShowFreeShipNotify = !!(
6680            isFreeShipNotifyTargetProduct
6681            && freeShipNotifySdk
6682            && !hasFreeShipNotifyAlarmShown
6683            && !($('._deliv_country_selector').length > 0 && $deliv_country !== 'KR')
6684            && !isNaN(initDelivPriceFlexableKey)
6685            && initDelivPriceFlexableKey > 0
6686        );
6687	        var canShowProductRecommendation = !!(
6688	            !isFreeShipNotifyTargetProduct
6689	            && productRecommendationMarkerEl
6690	            && !isNaN(productRecommendationProdIdx)
6691	            && productRecommendationProdIdx > 0
6692	            && !hasProductRecommendationAddCartAlarmShown(productRecommendationProdIdx)
6693	        );
6694
6695        if (isFreeShipNotifyTargetProduct && !canShowFreeShipNotify) {
6696            $add_cart_alarm.show();
6697            return;
6698        }
6699
6700        if (!canShowFreeShipNotify && !canShowProductRecommendation) {
6701            $add_cart_alarm.show();
6702            return;
6703        }
6704
6705        if (canShowFreeShipNotify) {
6706            window.__imwebFreeShipCartItemCounts = {};
6707            window.dispatchEvent(new CustomEvent('imweb:freeShipNotify:cartItems:update', {
6708                detail: { counts: {} }
6709            }));
6710
6711            var prodImage = freeShipMarkerEl.getAttribute('data-prod-image') || '';
6712            var prodName = freeShipMarkerEl.getAttribute('data-prod-name') || '';
6713
6714            freeShipNotifySdk.markAddCartAlarmShown();
6715            prepareAddCartAlarmModal();
6716
6717            $.ajax({
6718                type: 'POST',
6719                url: '/shop/free_ship_notify/get_add_cart_alarm_modal.cm',
6720                data: {
6721                    prod_image: prodImage,
6722                    prod_name: prodName,
6723                    init_deliv_price_flexable_key: initDelivPriceFlexableKey
6724                },
6725                dataType: 'html',
6726                cache: false,
6727                success: openAddCartAlarmHtml,
6728                error: function () {
6729                    $add_cart_alarm.show();
6730                }
6731            });
6732            return;
6733        }
6734
6735        prepareAddCartAlarmModal();
6736
6737        $.ajax({
6738            type: 'POST',
6739            url: '/shop/product_recommendation/get_add_cart_alarm_modal.cm',
6740            data: {
6741                prod_idx: productRecommendationProdIdx,
6742                prod_code: productRecommendationMarkerEl.getAttribute('data-prod-code') || '',
6743                prod_image: productRecommendationMarkerEl.getAttribute('data-prod-image') || '',
6744                prod_name: productRecommendationMarkerEl.getAttribute('data-prod-name') || ''
6745            },
6746            dataType: 'html',
6747            cache: false,
6748            success: function (html) {
6749	                if (typeof html !== 'string' || html.trim() === '') {
6750	                    $add_cart_alarm.show();
6751	                    return;
6752	                }
6753	                markProductRecommendationAddCartAlarmShown(productRecommendationProdIdx);
6754	                openAddCartAlarmHtml(html);
6755	            },
6756            error: function () {
6757                $add_cart_alarm.show();
6758            }
6759        });
6760    };
6761
6762    var hideAddCartAlarm = function () {
6763        trackAddToCartPopupAction('click_continue_shopping_add_to_cart_popup', 'continue_shopping');
6764        $add_cart_alarm.hide();
6765        if (($add_cart_alarm_with_free_ship_notify && $add_cart_alarm_with_free_ship_notify.length > 0)
6766	        || ($add_cart_alarm_with_product_recommendation && $add_cart_alarm_with_product_recommendation.length > 0)) {
6767            // 매출 상승 도구 장바구니 알림 사용 사이트 - cocoaDialog/imSheet 양쪽 모두 닫기
6768            if (typeof $.cocoaDialog === 'object' && typeof $.cocoaDialog.close === 'function') {
6769                $.cocoaDialog.close();
6770            }
6771            if (typeof imSheet !== 'undefined' && typeof imSheet.close === 'function') {
6772                imSheet.close('shop_detail_add_cart_alarm_sheet');
6773            }
6774            // imSheet.close 의 비동기 cleanup 이 다른 시트 진입과 race 가 되면 orphan sheet element 가 남아
6775            // 옵션 시트의 dim 위에 중첩되는 현상을 방어. 같은 id 의 element 를 즉시 DOM 에서 제거.
6776            document.querySelectorAll('#shop_detail_add_cart_alarm_sheet').forEach(function (el) {
6777                el.remove();
6778            });
6779
6780            if (typeof window.IMWEB_SHOP_FREE_SHIP_NOTIFY_SDK !== 'undefined'
6781                && typeof window.IMWEB_SHOP_FREE_SHIP_NOTIFY_SDK.dispatchInlineMagnetItemCounts === 'function') {
6782                window.IMWEB_SHOP_FREE_SHIP_NOTIFY_SDK.dispatchInlineMagnetItemCounts();
6783            }
6784        }
6785    };
6786
6787    var moveCart = function () {
6788        trackAddToCartPopupAction('click_go_cart_add_to_cart_popup', 'go_cart');
6789        cart_type = $('input[name=add_cart_type]:checked').val();
6790        if (cart_type == 'regularly') {
6791            window.location.href = '/shop_cart?type=regularly';
6792        } else {
6793            window.location.href = '/shop_cart';
6794        }
6795    };
6796
6797    var trackAddToCartPopupAction = function (eventName, content, options) {
6798        if (typeof window.BrandScope === 'undefined' || typeof window.BrandScope.track !== 'function') return;
6799        var isFreeShippingUpsell = (options && typeof options.isFreeShippingUpsell === 'boolean')
6800            ? options.isFreeShippingUpsell
6801            : !!window.__imwebAddToCartHasFreeShipUpsell;
6802        window.BrandScope.track(eventName, {
6803            action: 'click',
6804            content: content,
6805            where: 'add_to_cart_popup',
6806            prod_code_list: Array.isArray(window.__imwebAddToCartProdCodeList)
6807                ? window.__imwebAddToCartProdCodeList.slice()
6808                : [],
6809            is_free_shipping_upsell: isFreeShippingUpsell
6810        });
6811    };
6812
6813    var changeInput = function () {
6814        $(document).on('keypress', 'input._requireInputOption', function (e) {
6815            if (e.which == 13) {
6816                e.preventDefault();
6817                var $next = $('[tabIndex=' + (+this.tabIndex + 1) + ']');
6818                if ($next.length != 0) {
6819                    $next.focus().click();
6820                } else {
6821                    e.target.blur();
6822                }
6823            }
6824        });
6825    };
6826
6827    var changeTab = function (type) {
6828        $('._detail_detail_wrap').hide();
6829        $('._detail_review_wrap').hide();
6830        $('._detail_qna_wrap').hide();
6831        $('._detail_' + type + '_wrap').show();
6832    };
6833
6834    var countryCodeChange = function (country) {
6835        $.ajax({
6836            type: 'POST',
6837            data: { 'country': country },
6838            url: ('/shop/country_code_change.cm'),
6839            dataType: 'json',
6840            cache: false,
6841            success: function (result) {
6842                if (result.msg != "SUCCESS") {
6843                    alert(result.msg);
6844                }
6845            }
6846        });
6847    };
6848
6849    var DetailItemMake = function (idx, change_country, deliv_type, deliv_pay_type) {
6850        var is_design_mode = (location.pathname.indexOf('/admin/design') != -1);
6851        $deliv_country = change_country;
6852        $deliv_pay_type = deliv_pay_type;
6853        $.ajax({
6854            type: 'GET',
6855            data: {
6856                'idx': idx,
6857                'change_country': change_country,
6858                'deliv_type': deliv_type,
6859                'deliv_pay_type': deliv_pay_type,
6860                '__': MEMBER_HASH
6861            },
6862            url: ('/shop/prod_detail_make.cm'),
6863            dataType: 'json',
6864            cache: !is_design_mode,
6865            success: function (result) {
6866                if (result.msg == 'SUCCESS') {
6867                    $prod_detail.find('._item_detail_wrap').html(result.html);
6868
6869                    /* 환불 정보 입력 */
6870                    last_refund_data = result.refund;
6871                    setDetailReturnHtml();
6872
6873                    if (window.__npayButtonV2 && typeof makeNaverPayBtn !== 'undefined') {
6874												try{
6875													makeNaverPayBtn('naverPayWrap', change_country == 'KR' || change_country == 'none' || change_country == '');
6876												}catch(e) {
6877
6878												}
6879                    }
6880
6881                    if (typeof kakaoCheckout != 'undefined' && typeof makeTalkPayButton != 'undefined') {
6882												try{
6883                          makeTalkPayButton('create-kakao-checkout-button', (change_country == 'KR' || change_country == 'none' || change_country == ''));
6884												}catch(e) {
6885
6886												}
6887                    }
6888                }
6889            }
6890        });
6891    };
6892    function setDetailReturnHtml() {
6893        if (!last_refund_data) return;
6894        if (options.shop_view_tab_display == 'Y') {
6895            let prod_detail_return_html = null;
6896            prod_detail_return_html = last_refund_data.use_shop_return ? IMWEB_TEMPLATE.loadSimple("prodReturnPc", last_refund_data) : IMWEB_TEMPLATE.loadSimple("prodReturnDisable", null);
6897            $prod_detail_return.html(prod_detail_return_html);
6898            prod_detail_return_html = last_refund_data.use_shop_return ? IMWEB_TEMPLATE.loadSimple("prodReturnMobile", last_refund_data) : IMWEB_TEMPLATE.loadSimple("prodReturnDisable", null);
6899            $prod_detail_return_mobile.html(prod_detail_return_html);
6900
6901            // 반품/교환 주소지 동적 업데이트 (템플릿에서 PHP 변수로 초기 렌더링된 부분)
6902            if (last_refund_data.return_address) {
6903                $prod_detail_return.find('._return_address_text').html(last_refund_data.return_address);
6904                $prod_detail_return_mobile.find('._return_address_text').html(last_refund_data.return_address);
6905            }
6906        } else {
6907            if (current_content_tab !== SHOP_CONST.TAB_TYPE.RETURN) return;
6908            SITE_SHOP_DETAIL.changeContentPCTab(SHOP_CONST.TAB_TYPE.RETURN);
6909            SITE_SHOP_DETAIL.changeContentTab(SHOP_CONST.TAB_TYPE.RETURN);
6910        }
6911    }
6912
6913    /**
6914     * 환불 정보 데이터 업데이트 및 템플릿 렌더링
6915     * @param {Object} refundData - 환불 정보 데이터
6916     */
6917    const updateRefundData = (refundData) => {
6918        if (!refundData) {
6919            return;
6920        }
6921        last_refund_data = refundData;
6922        setDetailReturnHtml();
6923    };
6924
6925    /**
6926     * 모바일 옵션 input클릭시 입력키보드 때문에 css예외처리가 필요하여 클래스 추가.
6927     */
6928    var addEventMobileOptionInput = function () {
6929        $options.find('input.form-control').focus(function () {
6930            if (!$('body').hasClass('mobile_focus_on')) {
6931                $('body').addClass('mobile_focus_on');
6932            }
6933        });
6934        $options.find('input').blur(function () {
6935            if ($('body').hasClass('mobile_focus_on')) {
6936                $('body').removeClass('mobile_focus_on');
6937            }
6938        });
6939    };
6940
6941    /**
6942     * 쇼핑 카테고리 목록에서 상품 상세페이지 레이어팝업 띄우는 함수
6943	 * @param idx
6944	 * @param back_url
6945	 * @param is_prod_detail_page: 상품상세페이지에서 레이어팝업 - 상품 상세 Dialog - 을 띄울 경우 'Y'
6946	 * @param is_mobile
6947	 * @param prod_idx_org
6948     */
6949    var openProdDetailFromShoppingList = function (idx, back_url, is_prod_detail_page, is_mobile, prod_idx_org) {
6950        $.ajax({
6951            type: 'GET',
6952            data: { 'idx': idx, 'is_prod_detail_page': is_prod_detail_page },
6953            url: ('/shop/prod_detail_from_shopping_list.cm'),
6954            dataType: 'html',
6955            cache: true,
6956            async: false,
6957            success: function (result) {
6958                $.cocoaDialog.open({ type: 'prod_detail_from_shopping_list', custom_popup: result, 'close_block': false });
6959                const currentUrl = location.href;
6960                history.replaceState(null, null, currentUrl + '?idx=' + idx);
6961                $('.modal_prod_detail_from_shopping_list').addClass('custom_tooltip');
6962
6963                if (is_prod_detail_page == 'Y' && is_mobile) {
6964                    var $mobile_action_btn_wrap = $('._mobile_action_btn_wrap');
6965                    $mobile_action_btn_wrap.hide();
6966                }
6967
6968				// 모달이 닫힐 때 실행되는 콜백 붙이는 부분
6969                $('.modal_prod_detail_from_shopping_list').on('hidden.bs.modal', function (e) {
6970                    $('.modal_prod_detail_from_shopping_list').find('.modal-content').html(''); // close 해도 html 이 남아있어서 스크립트가 재실행이 안 되므로 html 을 삭제시킴
6971                    $('html').removeClass('mobile-shop-open');
6972                    history.replaceState(null, null, currentUrl);
6973
6974                    if (is_prod_detail_page == 'Y') {
6975                        initDetail({
6976                            prod_idx: prod_idx_org,
6977														prod_code: prod_code_org,
6978                            prod_price: prod_price_org,
6979                            require_option_count: require_option_count_org,
6980							use_image_optimizer_on_product_detail_markup: use_lazy_load_org,
6981                            shop_use_full_load: use_lazy_load_org,
6982                            shop_view_tab_display: tab_type_org,
6983                            is_site_page: is_site_page_org,
6984                            prod_type: prod_type_org,
6985                            is_prod_detail_page: false,
6986                            is_price_view_permission: is_view_price,
6987                            cm_style: options.cm_style,
6988                            section_code: section_code
6989                        });
6990
6991                        if (is_mobile) $mobile_action_btn_wrap.show();
6992                    }
6993                });
6994
6995                if (checkUseHistory()) { // 뒤로가기 누를 경우 모달만 꺼지고 현재 페이지에 남아있도록
6996                    history.pushState(null, null, location.href);
6997                    window.onpopstate = function () {
6998                        if ($('.modal_prod_detail_from_shopping_list').hasClass('in')) {
6999                            $.cocoaDialog.close();
7000                        }
7001                    };
7002                }
7003
7004				// 최근 본 상품, refresh
7005				if (window.RECENT_PRODUCT && typeof window.RECENT_PRODUCT.refresh === 'function') {
7006					window.RECENT_PRODUCT.refresh();
7007				}
7008            }
7009        });
7010    };
7011    var getReviewCountFromShoppingList = function (prod_code_list) {
7012        $.ajax({
7013            type: 'POST',
7014            data: { 'prod_code_list': prod_code_list },
7015            url: ('/shop/get_review_count_from_shopping_list.cm'),
7016            dataType: 'json',
7017            cache: false,
7018            async: true,
7019            success: function (res) {
7020                // $('"#' +  + '"').find("._review_count_text").text(res.review_count);
7021                // $(prod_code).find("._wish_count_text").text(res.wish_count);
7022            }
7023        });
7024    };
7025
7026    /**
7027     * review, qna 권한이 buyer일때 체크 모달
7028     * @param prod_code
7029     * @param type
7030     */
7031    var openBuyerReview = function (prod_code) {
7032        $.ajax({
7033            type: 'POST',
7034            data: { 'prod_code': prod_code },
7035            url: ('/shop/open_buyer_review.cm'),
7036            dataType: 'json',
7037            cache: false,
7038            async: false,
7039            success: function (result) {
7040                if (result.msg === 'SUCCESS') {
7041                    $.cocoaDialog.open({
7042                        type: 'eduModal', custom_popup: result.html, width: 800, hide_event: function () {
7043                        }
7044                    });
7045                } else {
7046                    alert(result.msg);
7047                }
7048            }
7049        });
7050    };
7051
7052    var deleteReviewImage = function (obj) {
7053        var box_obj = obj.parent().parent();
7054        obj.parent().remove();
7055        if (box_obj.find('.file-add').length == 0) box_obj.hide();
7056    };
7057
7058    /**
7059     * 상품상세 페이지에 뿌려지는 review, qna 의 카운트 가져ì˜
7059¤ê¸°.
7060     * @param prod_code
7061     */
7062    var getReviewQnaCount = function (prod_code) {
7063        $.ajax({
7064            type: 'POST',
7065            data: { 'prod_code': prod_code },
7066            url: ('/shop/get_review_qna_count.cm'),
7067            dataType: 'json',
7068            cache: false,
7069            async: true,
7070            success: function (res) {
7071                setReviewQnaCountText($("._review_count_text"), res.review_count);
7072                setReviewQnaCountText($("._qna_count_text"), res.qna_count);
7073            }
7074        });
7075    };
7076
7077    var setReviewQnaCountText = function ($target, count) {
7078        $target.text(count);
7079    };
7080
7081    var getCurrentProdNo = function () {
7082        return current_prod_idx;
7083    };
7084
7085    // current_prod_code 는 initDetail 이 마지막으로 초기화한 값이라 페이지를 떠나도 남는다.
7086    // 리스트 페이지에서 퀵뷰를 열면 모달의 initDetail 이 값을 덮어쓰는데, 모달을 닫을 때의 복원은
7087    // 상품 상세 위에서 띄운 경우에만 동작하므로 리스트 페이지에는 잔류값이 그대로 남는다.
7088    // 모달이 닫힐 때 모달 본문 마크업이 지워지는 것을 이용해, 상품 상세 마크업이 지금 문서에
7089    // 붙어 있을 때만 "고객이 지금 보고 있는 상품"으로 인정한다.
7090    var isProdDetailMounted = function () {
7091        return $('#prod_goods_form').length > 0;
7092    };
7093
7094    // 이름과 달리 getCurrentProdNo 가 돌려주는 것은 idx 다. 상품 코드가 필요한 쪽은 이것을 쓴다.
7095    var getCurrentProdCode = function () {
7096        if (!isProdDetailMounted()) return '';
7097        return current_prod_code;
7098    };
7099
7100    var saveSelectedProd = function () {
7101        if (typeof window.sessionStorage === "undefined") {
7102            return false;
7103        }
7104
7105        // 초기화
7106        var session_storage_keys = Object.keys(sessionStorage);
7107        session_storage_keys.forEach(function (_key) {
7108            if (_key.indexOf('PROD_SELECTED_OPTION_') !== -1) {
7109                sessionStorage.removeItem(_key);
7110            }
7111        });
7112
7113        // 마지막으로 본 것만 남도록 추가
7114        sessionStorage.setItem("LAST_SELECTED_PROD", current_prod_idx);
7115        sessionStorage.setItem(("PROD_SELECTED_OPTION_" + current_prod_idx), JSON.stringify({
7116            'prodIdx': current_prod_idx,
7117            'optDataList': selected_options,
7118            'orderCount': order_count
7119        }));
7120    };
7121
7122    /**
7123     * 로그인 이전에 선택한 옵션이 세션에 저장되어 있을 경우 로그인 후 해당 옵션을 다시 설정해줌
7124     * @param is_set
7125     * @returns {boolean}
7126     */
7127    var setSelectedProd = function (is_set) {
7128        if (typeof window.sessionStorage === "undefined") {
7129            return false;
7130        }
7131
7132        var selected_data = JSON.parse(sessionStorage.getItem("PROD_SELECTED_OPTION_" + current_prod_idx));
7133        var last_prod_idx = sessionStorage.getItem("LAST_SELECTED_PROD");
7134
7135        // 초기화
7136        var session_storage_keys = Object.keys(sessionStorage);
7137        session_storage_keys.forEach(function (_key) {
7138            if (_key.indexOf('PROD_SELECTED_OPTION_') !== -1) {
7139                sessionStorage.removeItem(_key);
7140            }
7141        });
7142
7143        // 마지막으로 본 상품이랑 일치하지 않는경우 리셋하고 끝
7144        if (last_prod_idx != current_prod_idx) return false;
7145        if (typeof selected_data == "undefined") return false;
7146        if (selected_data == null) return false;
7147        if (Object.keys(selected_data).length <= 0) return false;
7148        if (!is_set) return false;
7149
7150        for (var _key in selected_data) {
7151            switch (_key) {
7152                case "optDataList":
7153                    selected_options = selected_data[_key];
7154                    break;
7155                case "orderCount":
7156                    order_count = selected_data[_key];
7157                    break;
7158            }
7159        }
7160        updateSelectedOptions('prod');
7161        if (IS_MOBILE) {
7162            showMobileOptions(options.only_regularly ? 'regularly' : 'buy');
7163        } else {
7164            showPCOptions('order');
7165        }
7166    };
7167
7168    var openCouponDownload = function () {
7169        $.ajax({
7170            type: 'POST',
7171            data: {},
7172            url: ('/shop/open_coupon_download.cm'),
7173            dataType: 'json',
7174            cache: false,
7175            async: false,
7176            success: function (result) {
7177                if (result.msg === 'SUCCESS') {
7178                    $.cocoaDialog.open({ type: 'site_alert', custom_popup: result.html, width: 800 });
7179                } else {
7180                    alert(result.msg);
7181                }
7182            }
7183        });
7184    };
7185
7186    var setReviewCarousel = function () {
7187        var checkWidth = $(window).width();
7188        var owl = $('._review_modal_body .owl-carousel');
7189        if (checkWidth > 768) {
7190            owl.owlCarousel({
7191                'loop': owl.children().length > 1,
7192                'margin': 0,
7193                'nav': false,
7194                'items': 1
7195            });
7196        }
7197    };
7198
7199    var setReviewClass = function (idx) {
7200        if (idx <= 0) {
7201            $body.toggleClass('no-images', true);
7202            $body.find('.modal_prod_detail.review .modal-content').toggleClass('clearfix', true);
7203        } else {
7204            $body.toggleClass('no-images', false);
7205            $body.find('.modal_prod_detail.review .modal-content').toggleClass('clearfix', false);
7206        }
7207    };
7208
7209    var setReviewBtnAnimation = function () {
7210        var $btn_nav = $body.find('._btn_nav');
7211        setTimeout(function () {
7212            $btn_nav.attr('aria-hidden', true);
7213        }, 3000);
7214        $body.find('.modal-left').off('touchstart').on('touchstart', function () {
7215            $btn_nav.attr('aria-hidden', false);
7216            setTimeout(function () {
7217                $btn_nav.attr('aria-hidden', true);
7218            }, 3000);
7219        });
7220    };
7221
7222    var setReviewSwipe = function (prev_idx, next_idx) {
7223        $body.find('._review_modal_body').swipe({
7224            swipe: function (event, direction, distance, duration, fingerCount, fingerData) {
7225                if (direction == 'left') {
7226                    if (next_idx > 0) {
7227                        $('body').find('._btn_nav_wrap').remove();
7228                        changeReviewDetail(next_idx);
7229                    }
7230                } else if (direction == 'right') {
7231                    if (prev_idx > 0) {
7232                        $('body').find('._btn_nav_wrap').remove();
7233                        changeReviewDetail(prev_idx);
7234                    }
7235                }
7236            },
7237            threshold: 0,
7238            allowPageScroll: 'vertical',
7239            excludedElements: 'button, a, textarea, input'
7240        });
7241    };
7242
7243    var dispatchSeeMoreEvent = function (expanded) {
7244        try {
7245            document.dispatchEvent(new CustomEvent('imweb:prod_detail:seemore', {
7246                detail: {
7247                    expanded: expanded
7248                }
7249            }));
7250        } catch (e) {
7251            // CustomEvent 미지원 환경 대비
7252        }
7253    };
7254
7255    var showSeeMoreButton = function () {
7256        $prod_detail_content_pc.toggleClass('active', true);
7257        $prod_detail_content_mobile.toggleClass('active', true);
7258        dispatchSeeMoreEvent(true);
7259    };
7260    var hideSeeMoreButton = function () {
7261        $prod_detail_content_pc.toggleClass('active', false);
7262        $prod_detail_content_mobile.toggleClass('active', false);
7263        dispatchSeeMoreEvent(false);
7264    };
7265
7266    var openRequireSelect = function (option_code) {
7267        var $form_select = $('._form_select_wrap_' + option_code);
7268        var $goods_wrap = $body.find('#goods_wrap');
7269        $form_select.addClass('open');
7270        if ($('body').hasClass('mobile_focus_on')) {
7271            $('body').removeClass('mobile_focus_on');
7272        }
7273        var top = Math.floor($form_select.offset().top); // 셀렉트 박스의 위치
7274        var scroll_top = Math.floor($goods_wrap.scrollTop()); // 컨테이너의 스크롤 top 위치
7275        var position_top = $('#option_' + option_code).position().top;
7276        var height = Math.floor($('.opt-group').height()); // 스크롤 영역의 높이
7277        var default_value = 180;
7278
7279        $form_select.parent('._form_parent').nextAll().toggleClass('disabled', true);
7280        if (top - height >= default_value) {
7281            $goods_wrap.animate({ scrollTop: position_top - 50 }, 0);
7282            if (scroll_top >= default_value) {
7283                $goods_wrap.animate({ scrollTop: position_top + $('.option_box_wrap').outerHeight() }, 0);
7284            }
7285        }
7286    };
7287
7288    var addRegularlyCart = function () {
7289        addCart(function () {
7290            showAddCartAlarm();
7291        });
7292    };
7293
7294	var toggleWishButtons = function($wishButtons, wishCount, showWishCount, isActive) {
7295		const handleRoot = ($root) => {
7296			if (isActive) {
7297				$root.addClass('active');
7298			} else {
7299				$root.removeClass('active');
7300			}
7301		}
7302
7303		const handleIcon = ($root) => {
7304			const $icon = $($root).find('._wish_button_icon');
7305
7306			if (isActive) {
7307				$icon.removeClass('im-ico-like');
7308				$root.addClass('active');
7309				$icon.addClass('im-ico-liked');
7310			} else {
7311				$icon.removeClass('im-ico-liked');
7312				$root.removeClass('active');
7313				$icon.addClass('im-ico-like');
7314			}
7315		}
7316
7317		const handleText = ($root) => {
7318			const $label = $($root).find('._wish_button_count_label');
7319
7320			if (showWishCount) {
7321				$label.text(wishCount);
7322			} else {
7323				$label.text('');
7324			}
7325		}
7326
7327		$wishButtons.each(($idx, root) => {
7328			const $root = $(root);
7329
7330			handleRoot($root);
7331			handleIcon($root);
7332			handleText($root);
7333		});
7334	}
7335
7336
7337	/**
7338	 *
7339	 * @returns {Promise}
7340	 * 사은품 선택 다이얼로그를 띄우고, 사은품 선택이 완료되면 resolve 를 호출한다. 이때 유저가 선택한 사은품을 획득한다.
7341	 */
7342	async function selectFreebieAsync () {
7343		cart_type = document.querySelector('#prod_period input[name=add_cart_type]:checked')?.value || 'normal';
7344		if (!isFreebieMfeMounted() || cart_type === 'regularly' || !checkRequireOption()) {
7345			// 사은품 미사용 사이트이거나, 정기결제 상품인 경우 사은품 선택을 하지 않는다.
7346			return Promise.resolve([]);
7347		}
7348
7349		// selected_options
7350		const items = [
7351			...(selected_options.filter(optionSelected => optionSelected.require).length === 0
7352			// 옵션이 없거나, 필수 옵션이 없으면 본품을 추가한다.
7353				? [{
7354						prodIdx: current_prod_idx,
7355						// prod code는 브라우저에서 채워넣는다.
7356						prodCode: null,
7357						optionDetailCode: "",
7358						prodOptions: [],
7359						qty: order_count,
7360					}]
7361				: []),
7362			...(selected_options.map((option) => {
7363				return {
7364					prodIdx: current_prod_idx,
7365					prodCode: null,
7366					qty: option.count,
7367					optionDetailCode: option.option_mix_type === "SINGLE" ? undefined : option.option_detail_code,
7368					prodOptions: option.options.filter(option => option.value_type === 'SELECT').map((option) => {
7369						return {
7370							optionCode: option.option_code,
7371							valueCode: option.value_code,
7372						}
7373					}),
7374				}
7375			}))
7376		];
7377
7378		// selected_prod_additionals
7379		// selected_prod_additional_options
7380		const item_additional = selected_prod_additionals.flatMap((additional) => {
7381			const additionalOptions = selected_prod_additional_options
7382				.filter(option => option.prod_idx === additional.idx);
7383			const requiresOption = prod_additional_require_option_count[additional.idx] > 0;
7384			const hasRequiredOption = additionalOptions.some(option => option.require);
7385
7386			// 주문에서 제외되는 추가상품은 사은품 조건 금액과 수량에도 포함하지 않는다.
7387			if (requiresOption && !hasRequiredOption) {
7388				return [];
7389			}
7390
7391			if (additionalOptions.length === 0) {
7392				return [{
7393					prodIdx: additional.idx,
7394					prodCode: null,
7395					optionDetailCode: '',
7396					prodOptions: [],
7397					qty: additional.count,
7398				}]
7399			}
7400			return additionalOptions.map((option) => {
7401				return {
7402					prodIdx: additional.idx,
7403					prodCode: null,
7404					optionDetailCode: option.option_detail_code,
7405					prodOptions: option.options.filter(option => option.value_type === 'SELECT').map((option) => {
7406						return {
7407							optionCode: option.option_code,
7408							valueCode: option.value_code,
7409						}
7410					}),
7411					qty: option.count,
7412				}
7413			});
7414		})
7415
7416		return new Promise((resolve) => {
7417			// TODO: 사려고 하는 상품의 정보를 전달해줘야 함.
7418			document.dispatchEvent(new CustomEvent('@im/fo-prod-detail-freebie:openDialogSelectFreebie', { detail : {
7419				callback: resolve,
7420				orderItems: [...items, ...item_additional],
7421			} } ))
7422		})
7423	}
7424	/**
7425	 * 장바구니에 상품이 있는지 체크해서 있으면 장바구니 상품 구매유도 알럿을 띄우고 아니면 바로 주문으로 이동하는 메소드입니다.
7426	 * type: "guest_login" | "npay" | "talkpay" | "normal"
7427	 */
7428	async function confirmOrderWithCartItems(type, backurl, params) {
7429		if (!ensureAddOrderPermission()) {
7430			return;
7431		}
7432
7433        const verified = await SHIPPING_SERVICE.verify({ type });
7434        if (!verified) {
7435            return;
7436        }
7437
7438		if (!options.is_price_view_permission &&
7439			window.IS_GUEST &&
7440			cm_data.use_login_popup === 'Y' &&
7441			options.is_using_nomember_order_login_after
7442		) {
7443			// 구매 권한이 전체가 아닌 상품을 비회원이 구매 하려는 경우 사이트 공통 디자인이 로그인 모달을 사용중이라면 비회원 바로구매 기능이 켜져있는 경우 로그인 모달을 띄워준다.
7444			// 비회원 바로 구매기능이 켜져있지 않다면, site_ui.cls 의 getAnchor 함수를 통해 로그인이 모달이 필요한 상황에 로그인 모달을 표시할 수 있지만,
7445			// 비회원 바로 구매기능이 켜져있는 경우에는 로그인 모달을 보여주는 처리를 해주고 있지 않기 때문.
7446			// site_ui.cls 의 getAnchor 함수에서 해당 로직을 처리하지 않는 이유는 getAnchor 함수는 $params 인자로 받는 도착지 url의 상품 또는 상품 카테고리에 대한 정보를 얻을 수 없기 때문
7447			window.SITE_MEMBER.openLogin(
7448				encodeURIComponent(base64Encode(window.location.href)),
7449				'payment',
7450				_confirmOrderWithCartItems,
7451				'N',
7452				'payment',
7453			);
7454		} else {
7455			_confirmOrderWithCartItems()
7456		}
7457
7458
7459		function proceedToOrder () {
7460			if(typeof params.onMoveOrder === 'function') {
7461				// params.onMoveOrder 는 talkpay 주문시 전달되는 콜백 함수
7462				params.onMoveOrder()
7463			} else if (window.USE_OMS === true) {
7464				if (type === 'guest_login' || type === 'npay' || type === 'talkpay') {
7465					OMS_addOrder(type, backurl, params);
7466				} else {
7467					// TODO: 사은품 가능 여부 체크 후 선택 UI등 표시
7468					selectFreebieAsync()
7469						.then((selected_freebies) => {
7470							params.selected_freebies = selected_freebies || [];
7471							OMS_addOrder(type, backurl, params)
7472						});
7473				}
7474			} else {
7475				addOrder(type, backurl, params);
7476			}
7477		}
7478
7479		async function _confirmOrderWithCartItems () {
7480			// "regularly" | "normal"
7481			cart_type = document.querySelector('#prod_period input[name=add_cart_type]:checked')?.value || 'normal';
7482			params = params || {};
7482 // 기본값 설정
7483
7484			if (!ensureAddOrderPermission()) {
7485				return;
7486			}
7487
7488			// 장바구니가 비어있는지 체크 api 호출
7489			const res = await fetch('/shop/check_cart_empty.cm', {
7490				method: 'POST',
7491				headers: {
7492					'Content-Type': 'application/x-www-form-urlencoded',
7493				},
7494				body: serializeToUrlencoded({
7495					'prodIdx': current_prod_idx,
7496					'options': selected_options,
7497					'orderCount': order_count,
7498					'deliv_type': $deliv_type,
7499					'deliv_pay_type': $deliv_pay_type,
7500					'cart_type': cart_type
7501				})
7502			});
7503			const result = await res.json();
7504
7505			// 통신이 실패했거나, 장바구니가 비어있거나, 정기결제 상품이거나, 게스트일 경우 바로 주문으로 이동
7506			if (!res.ok || result.isGuest || result.msg !== 'SUCCESS' || result.cart_count === 0 || cart_type === 'regularly' ) {
7507				proceedToOrder();
7508				return;
7509			}
7510			// 카트에 상품이 있으며 정기결제 상품이 아닐 경우 장바구니 추가 구매유도 알럿을 띄움
7511			// 장바구니 추가 구매유도 다이얼로그 띄우기
7512			$confirm_order_with_cart_alarm.show();
7513			$confirm_order_with_cart_alarm.off('click').click(function (e) {
7514				if(this === e.target) {
7515					$(this).hide()
7516				}
7517			})
7518			$confirm_order_with_cart_alarm.find('#move_to_cart').off('click').click(function () {
7519				if (typeof params.onMoveCart === 'function') {
7520					params.onMoveCart();
7521				} else {
7522					// confirmOrderWithCartItems에서 이미 shippingService 검증을 완료했으므로 스킵
7523					addCart(function () {
7524						moveCart();
7525					}, { skipShippingValidation: true });
7526				}
7527				$confirm_order_with_cart_alarm.hide();
7528			});
7529			$confirm_order_with_cart_alarm.find('#buy_now').off('click').click(function () {
7530				proceedToOrder();
7531				$confirm_order_with_cart_alarm.hide()
7532			});
7533		}
7534	}
7535
7536	const trackClickPurchaseShopView = function (paymentButtonType) {
7537		if (typeof BrandScope !== "undefined") {
7538			try {
7539				BrandScope.track('click_purchase_shop_view', {
7540					'action': 'click',
7541					'content': 'purchase',
7542					'where': 'shop_view',
7543					'payment_button_type': paymentButtonType,
7544					'prod_code': current_prod_code,
7545					'prod_type': prod_type,
7546					'is_regularly_prod': false
7547				});
7548			} catch {
7549				// ignore
7550			}
7551		}
7552	};
7553
7554	function getThumbnailBadgeStyleByPosition({ badgeColor, badgePosition, imageWidth = ''}) {
7555		const BADGE_POSITION_LEFT_TOP = 'left_top';
7556		const BADGE_POSITION_BOTTOM = 'bottom';
7557
7558		const containerWidth = imageWidth;
7559		if (!containerWidth) return '';
7560
7561		// 최소/최대 프레임 너비 설정
7562		const MIN_FRAME_WIDTH = 120;
7563		const MAX_FRAME_WIDTH = 800;
7564
7565		// 프레임 너비 기준
7566		const frameWidth = Math.max(MIN_FRAME_WIDTH, Math.min(MAX_FRAME_WIDTH, containerWidth));
7567
7568		// 프레임 너비에 따른 비율 계산
7569		// 기준 frameWidth: 308px
7570		const leftTopFontRatio = 0.035;   // frameWidth × 0.035 (308px일 때 약 10.78px)
7571		const leftTopPaddingRatio = 0.015; // frameWidth × 0.015 (308px일 때 약 4.62px)
7572		const bottomFontRatio = 0.04;     // frameWidth × 0.04 (308px일 때 약 12.32px)
7573		const bottomPaddingRatio = 0.02;  // frameWidth × 0.02 (308px일 때 약 6.16px)
7574
7575		// 실제 크기 계산
7576		let leftTopFontSize = Math.round(frameWidth * leftTopFontRatio * 1000) / 1000;
7577		let leftTopPadding = Math.round(frameWidth * leftTopPaddingRatio * 1000) / 1000;
7578		let bottomFontSize = Math.round(frameWidth * bottomFontRatio * 1000) / 1000;
7579		let bottomPadding = Math.round(frameWidth * bottomPaddingRatio * 1000) / 1000;
7580
7581		// 최소/최대 값 제한
7582		leftTopFontSize = Math.max(10, Math.min(13, leftTopFontSize));   // min: 9px / max: 13px
7583		leftTopPadding = Math.max(3, Math.min(6, leftTopPadding));     // min: 3px / max: 6px
7584		bottomFontSize = Math.max(12, Math.min(14, bottomFontSize));   // min: 12px / max: 14px
7585		bottomPadding = Math.max(4, Math.min(8, bottomPadding));       // min: 4px / max: 8px
7586
7587		// badgeLeftTopGapSize 추가 (기존 로직 보존)
7588		const badgeLeftTopGapSize = roundTo3(leftTopFontSize * (8 / 28));
7589
7590		if (badgePosition === BADGE_POSITION_LEFT_TOP) {
7591			const style = [
7592				'position: absolute',
7593				'border-radius: 3px',
7594				`top: ${badgeLeftTopGapSize}px`,
7595				`left: ${badgeLeftTopGapSize}px`,
7596				`padding: ${leftTopPadding}px`,
7597				'color: #FFF',
7598				'vertical-align: middle',
7599				'display: inline-flex',
7600				'align-items: center',
7601				'justify-content: center',
7602				`background-color: ${badgeColor}`,
7603				'z-index: 10',
7604				`font-size: ${leftTopFontSize}px;`
7605			].join('; ');
7606			return style;
7607		}
7608
7609		if (badgePosition === BADGE_POSITION_BOTTOM) {
7610			const style = [
7611				'position: absolute',
7612				'left: 0',
7613				'bottom: 0',
7614				'width: 100%',
7615				`padding: ${bottomPadding}px`,
7616				'color: #FFF',
7617				'vertical-align: middle',
7618				'display: inline-flex',
7619				'align-items: center',
7620				'justify-content: center',
7621				`background-color: ${badgeColor}`,
7622				'z-index: 10',
7623				`font-size: ${bottomFontSize}px;`
7624			].join('; ');
7625			return style;
7626		}
7627
7628		return '';
7629	}
7630
7631	function roundTo3(val) {
7632		return Math.round(val * 1000) / 1000;
7633	}
7634
7635    // BO sales tool 미리보기 트리거 수신: URL에 sales_tool_preview 파라미터가 있을 때(=미리보기 모드)에만
7636    // 부모 BO에서 보낸 메시지에 따라 알림 모달이나 옵션 시트를 즉시 띄운다.
7637    var salesToolPreviewParams = (function () {
7638        try {
7639            return new URLSearchParams(window.location.search);
7640        } catch (e) {
7641            return null;
7642        }
7643    })();
7644    var isInSalesToolPreview = salesToolPreviewParams ? salesToolPreviewParams.has('sales_tool_preview') : false;
7645    var salesToolPreviewDevice = salesToolPreviewParams ? salesToolPreviewParams.get('device') : null;
7646    _isFreeShipNotifySalesToolPreview = isInSalesToolPreview
7647        && salesToolPreviewParams.get('sales_tool_preview') === 'freeShipNotify';
7648    var _isFreeShipNotifyTemplatePreview = _isFreeShipNotifySalesToolPreview
7649        && (parseInt(salesToolPreviewParams.get('idx') || '0', 10) || 0) <= 0;
7650
7651    function isAllowedSalesToolPreviewOrigin(origin) {
7652        if (origin === window.location.origin) return true;
7653        if (/(^|\.)imtest\.me$/.test(window.location.hostname) && origin === 'https://localhost:5173') return true;
7654        return /^https:\/\/[a-z0-9-]+\.crm\.(imweb|imtest)\.me$/.test(origin);
7655    }
7656
7657    function getSalesToolPreviewParentOrigin() {
7658        try {
7659            if (document.referrer) {
7660                return new URL(document.referrer).origin;
7661            }
7662        } catch (e) {}
7663        return '*';
7664    }
7665
7666    function notifySalesToolPreviewReady() {
7667        if (!isInSalesToolPreview || !window.parent || window.parent === window) return;
7668        if (!_initDetailDone) return;
7669        if (_isFreeShipNotifySalesToolPreview && !isFreeShipNotifyPreviewReady()) {
7670            if (!_isWaitingFreeShipNotifyPreviewReadyToNotify) {
7671                _isWaitingFreeShipNotifyPreviewReadyToNotify = true;
7672                waitForFreeShipNotifyPreviewReady(function () {
7673                    _isWaitingFreeShipNotifyPreviewReadyToNotify = false;
7674                    notifySalesToolPreviewReady();
7675                });
7676            }
7677            return;
7678        }
7679        window.parent.postMessage({ type: 'sales-tool-preview:ready' }, getSalesToolPreviewParentOrigin());
7680    }
7681
7682    if (isInSalesToolPreview) {
7683        // body 마커 클래스 — BO sales tool 미리보기 컨텍스트 식별 (CSS 분기/디버깅 용도)
7684        // 실제 스크롤바 숨김 등 스타일은 site2.css 의 body.sales-tool-preview 룰이 담당.
7685        var addSalesToolPreviewBodyClass = function () {
7686            if (document.body) {
7687                document.body.classList.add('sales-tool-preview');
7688            }
7689        };
7690        if (document.body) {
7691            addSalesToolPreviewBodyClass();
7692        } else {
7693            document.addEventListener('DOMContentLoaded', addSalesToolPreviewBodyClass);
7694        }
7695        window.addEventListener('imweb:freeShipNotify:sdk-ready', notifySalesToolPreviewReady);
7696
7697        _handleSalesToolPreviewMessage = function (data) {
7698            if (data.type === 'sales-tool-preview:request-ready') {
7699                notifySalesToolPreviewReady();
7700                return;
7701            }
7702
7703            if (data.type === 'sales-tool-preview:show-add-cart-alarm') {
7704                showAddCartAlarm();
7705                return;
7706            }
7707
7708            // BO 미리보기 — 마스터 토글 off 나 스타일 전환 시 명시적으로 알림 모달을 닫는다.
7709            if (data.type === 'sales-tool-preview:hide-add-cart-alarm') {
7710                hideAddCartAlarm();
7711                return;
7712            }
7713
7714            if (data.type === 'sales-tool-preview:show-options') {
7715                // 기존 알림 모달이 떠 있다면 닫기 (cocoaDialog/imSheet 양쪽 idempotent 처리)
7716                hideAddCartAlarm();
7717
7718                if (salesToolPreviewDevice === 'mobile') {
7719                    showMobileOptions('buy');
7720                } else if (salesToolPreviewDevice === 'desktop' && shop_view_style === 'b') {
7721                    showPCOptions('buy', undefined, true);
7722                }
7723                return;
7724            }
7725
7726            // BO 미리보기 — 마스터 토글 off 나 스타일 전환 시 옵션 패널을 닫는다 (mobile/PC 양쪽 idempotent).
7727            if (data.type === 'sales-tool-preview:hide-options') {
7728                hideMobileOptions();
7729                hidePCOptions();
7730                return;
7731            }
7732
7733            // mfe magnet에 직접 전달되는 동적 설정 — 페이로드를 그대로 CustomEvent로 re-dispatch.
7734            // mfe(fo-shopping/free-ship-notify)의 freeShipNotifySettings 스토어가 이 이벤트를 구독해 즉시 반영한다.
7735            // 파일 상단의 imweb:freeShipNotify:settings:update 리스너가 --free-ship-notify-accent도 함께 따라간다.
7736            if (data.type === 'sales-tool-preview:free-ship-notify-settings'
7737                || data.type === 'sales-tool-preview:settings') {
7738                var settingsPayload = data.payload || {};
7739                window.dispatchEvent(new CustomEvent('imweb:freeShipNotify:settings:update', {
7740                    detail: settingsPayload
7741                }));
7742
7743                // shopDetailStyle이 CART_POPUP이 아닌 값으로 전환되면 떠있는 장바구니 담기 알림 시트/모달을 닫는다.
7744                // (장바구니 담기 알림은 CART_POPUP 모드 전용이므로 다른 모드 전환 시 잔존하면 backdrop이 남는 증상이 생긴다.)
7745                // BO가 명시적 hide-add-cart-alarm을 보내지 않더라도 settings 전이만으로 안전하게 정리되도록 방어한다.
7746                if (typeof settingsPayload.shopDetailStyle === 'string'
7747                    && settingsPayload.shopDetailStyle !== 'cart_popup') {
7748                    hideAddCartAlarm();
7749                }
7750            }
7751        };
7752
7753        window.addEventListener('message', function (event) {
7754            if (!isAllowedSalesToolPreviewOrigin(event.origin)) return;
7755            var data = event.data;
7756            if (!data || typeof data !== 'object') return;
7757
7758            if (!_initDetailDone) {
7759                _pendingSalesToolPreviewMessages.push(data);
7760                return;
7761            }
7762            _handleSalesToolPreviewMessage(data);
7763        });
7764    }
7765
7766    return {
7767        addProdWish: function (prod_code, back_url) {
7768            addProdWish(prod_code, back_url);
7769        },
7770        /** type (prod/cart) */
7771        increaseOptionCount: function (optNo, type) {
7772            increaseOptionCount(optNo, function () {
7773                updateSelectedOptions(type);
7774            });
7775        },
7776        /** type (prod/cart) */
7777        decreaseOptionCount: function (optNo, type) {
7778            decreaseOptionCount(optNo, function () {
7779                updateSelectedOptions(type);
7780            });
7781        },
7782	      /** type (prod/cart) */
7783        increaseProdAdditionalOptionCount: function (optNo) {
7784            increaseProdAdditionalOptionCount(optNo, function () {
7785                updateSelectedOptions('prod');
7786            });
7787        },
7788        /** type (prod/cart) */
7789        decreaseProdAdditionalOptionCount: function (optNo) {
7790            decreaseProdAdditionalOptionCount(optNo, function () {
7791                updateSelectedOptions('prod');
7792            });
7793        },
7794        changeProdAdditionalOptionCount: function (optNo, toBeCount) {
7795            changeProdAdditionalOptionCount(optNo, toBeCount, function () {
7796                updateSelectedOptions('prod');
7797            });
7798        },
7799        // initDetail : function(prodIdx, price, requireOptionCnt, use_np_mobile, use_lazyload, tab_type,is_site_page, prod_type, is_prod_detail_page, view_price, cm_data, section_code){
7800        // 	initDetail(prodIdx, price, requireOptionCnt, use_np_mobile, use_lazyload, tab_type,is_site_page, prod_type, is_prod_detail_page, view_price, cm_data, section_code);
7801        // },
7802        initDetail: function (option) {
7803            initDetail(option);
7804            this.setShippingTemplateCode(options.shipping_template_code || '');
7805        },
7806        initProdStock: function (stock_use, stock, stock_un_limit) {
7807            initProdStock(stock_use, stock, stock_un_limit);
7808        },
7809        initLocalize: function (code) {
7810            initLocalize(code);
7811        },
7812        selectOption: function (prod_idx, {options, require, count, idx = null, skip_quantity_validation = false}, success, failed) {
7813            selectOption(
7814                prod_idx,
7815                {
7816                    options: options,
7817                    require : require,
7818                    count : count,
7819                    idx : idx,
7820                    skip_quantity_validation : skip_quantity_validation
7821                },
7822                function () {
7823                    success();
7824                },
7825                function (msg) {
7826                    failed(msg);
7827                }
7828            );
7829        },
7830        removeSelectedOption: function (optNo, type) {
7831            removeSelectedOption(optNo, function () {
7832                selected_require_options = [];		// 선택된 필수 옵션 초기화
7833                loadOption(type, current_prod_idx);		// 옵션을 다시 로드함
7834                updateSelectedOptions(type);
7835            });
7836        },
7837        resetSelectedRequireOptions: function () {
7838            selected_require_options = [];
7839        },
7840	      removeSelectedProdAdditional: function (prodIdx, type) {
7841		      removeSelectedProdAdditional(prodIdx, function () {
7842                loadProdAdditional(current_prod_idx);		// 옵션을 다시 로드함
7843	              updateSelectedOptions(type);
7844            });
7845        },
7846	      removeSelectedProdAdditionalOption: function (optNo, type) {
7847		      removeSelectedProdAdditionalOption(optNo, function () {
7848                loadProdAdditional(current_prod_idx);		// 옵션을 다시 로드함
7849	              updateSelectedOptions(type);
7850            });
7851        },
7852        selectRequireOption: function (type, prod_idx, option_code, value_code, value_name, success) {
7853            selectRequireOption(type, prod_idx, option_code, value_code, value_name, function () {
7854                success();
7855            });
7856        },
7857	      selectProdAdditionalRequireOption: (prod_idx, option_code, value_code, value_name, success, is_shop_free_ship_notify = false) => {
7858			      var data = { 'value_type': 'SELECT', 'option_code': option_code, 'value_code': value_code, 'value_name': value_name };
7859			      var no = findSelectedProdAdditionalRequireOption(option_code);
7860						/* @TODO selected_require_options 세분화해야 함(아니면 option_code만으로 구분 가능하게 처리하거나 -> length 비교하므로 분리되어야 하는 게 맞음 */
7861			      if (no == -1) {
7862				      if (value_code == '') return;
7863				      /** 처음 선택된 옵션인 경우 새로 추가*/
7864				      selected_prod_additional_require_options.push(data);
7865			      } else {
7866				      if (value_code == '') {
7867					      /** 옵션 삭제 */
7868					      selected_prod_additional_require_options.splice(no, (selected_prod_additional_require_options.length - no));
7869				      } else {
7870					      selected_prod_additional_require_options[no] = data;
7871					      /** 이미 선택된 옵션인 경우 기존 값 교체 */
7872					      if (no < selected_prod_additional_require_options.length - 1) {
7873						      selected_prod_additional_require_options.splice(no + 1, (selected_prod_additional_require_options.length - (no + 1)));
7874					      }
7875				      }
7876			      }
7877			      if (selected_prod_additional_require_options.length == prod_additional_require_option_count[prod_idx]) {
7878							if(isFreeShipNotifyAdditionalActive()){
7879								// 필수 옵션이 모두 선택되었다고 하더라도 무료 배송 안내 유도 상품 모달에서는 추가 버튼을 눌러야 반영
7880								// 추가하기 버튼 누를 때까지 저장처리하지 않고 선택 처리만 한 상태로 대기
7881								loadProdAdditionalOptions(prod_idx);
7882							}else{
7883								/** 필수 옵션이 모두 선택되었을 경우*/
7884								selectProdAdditionalOption(prod_idx, selected_prod_additional_require_options, true, 1, function () {
7885									if(is_mobile_width){
7886										imSheet.close('prod_additional_sheet');
7887									}
7888									current_select_additional_prod_idx = 0;
7889									selected_prod_additional_require_options = [];
7890									$prod_additional_options.html('');
7891									$('#prod_additional_drop_menu_content').text(getLocalizeString('타이틀_추가상품', '', '추가 상품'));  // PC
7892									$('#btnProdAdditionalSelect').text(getLocalizeString('타이틀_추가상품', '', '추가 상품'));  // 모바일
7893									success();
7894								}, function (msg) {
7895									alert(msg);
7896								});
7897							}
7898			      } else {
7899				      /** 필수옵션 선택이 아직 끝나지 않았을 경우 옵션 재로드 */
7900				      loadProdAdditionalOptions(prod_idx);
7901			      }
7902	      },
7903	      completeAdditionalProductSelection: () => {
7904		      try {
7905			      // 선택된 추가 상품이 있는 경우 selectProdAdditionalOption 호출
7906			      if (current_select_additional_prod_idx && current_select_additional_prod_idx > 0) {
7907				      var requireCount = prod_additional_require_option_count[current_select_additional_prod_idx] || 0;
7908				      if (selected_prod_additional_require_options.length < requireCount) {
7909					      alert(LOCALIZE.설명_필수옵션이모두선택되어있ì
7909§€ì•ŠìŠµë‹ˆë‹¤());
7910					      return;
7911				      }
7912				      selectProdAdditionalOption(current_select_additional_prod_idx, selected_prod_additional_require_options, true, 1, function () {
7913					      if(is_mobile_width){
7914						      imSheet.close('prod_additional_sheet');
7915					      }
7916					      current_select_additional_prod_idx = 0;
7917					      selected_prod_additional_require_options = [];
7918					      $prod_additional_options.html('');
7919					      $('#prod_additional_drop_menu_content').text(getLocalizeString('타이틀_추가상품', '', '추가 상품'));  // PC
7920					      $('#btnProdAdditionalSelect').text(getLocalizeString('타이틀_추가상품', '', '추가 상품'));  // 모바일
7921
7922					      var $optionModal = $('.modal_site_shop_free_ship_notify_option.in');
7923					      if ($optionModal.length > 0) {
7924						      $optionModal.modal('hide');
7925					      } else if (typeof $ !== 'undefined' && typeof $.cocoaDialog === 'object' && typeof $.cocoaDialog.close === 'function') {
7926						      $.cocoaDialog.close();
7927					      }
7928
7929					      updateSelectedOptions('prod');
7930				      }, function (msg) {
7931					      alert(msg);
7932				      });
7933			      } else {
7934				      var $optionModal = $('.modal_site_shop_free_ship_notify_option.in');
7935				      if ($optionModal.length > 0) {
7936					      $optionModal.modal('hide');
7937				      } else if (typeof $ !== 'undefined' && typeof $.cocoaDialog === 'object' && typeof $.cocoaDialog.close === 'function') {
7938					      $.cocoaDialog.close();
7939				      }
7940
7941				      updateSelectedOptions('prod');
7942			      }
7943		      } catch (error) {
7944			      console.error('[SITE_SHOP_DETAIL] 추가 상품 선택 완료 처리 중 오류:', error);
7945		      }
7946	      },
7947	      // 추가 상품 옵션 모달 진입 시 호출. 해당 prod_idx의 현재 selected_prod_additionals 항목과
7948	      // selected_prod_additional_options를 snapshot으로 반환한다. cancel/backdrop close 시
7949	      // restoreProdAdditionalForModal에 전달하면 진입 직전 상태로 복원되어, 같은 상품을 재추가하다가
7950	      // 취소해도 기존에 담았던 옵션이 잘못 사라지지 않는다.
7951	      snapshotProdAdditionalForModal: function (idx) {
7952		      var additional = selected_prod_additionals.find(function (v) { return v.idx === idx; });
7953		      var optionsForIdx = selected_prod_additional_options.filter(function (o) { return o.prod_idx === idx; });
7954		      return {
7955			      exists: !!additional,
7956			      count: additional ? additional.count : 0,
7957			      options: optionsForIdx.map(function (o) { return Object.assign({}, o); })
7958		      };
7959	      },
7960	      // snapshot으로 selected_prod_additionals/options를 복원한다. 진입 직전 항목이 없었으면 제거,
7961	      // 있었으면 count를 snapshot 값으로 되돌리고 options 배열도 snapshot 항목들로 교체.
7962	      restoreProdAdditionalForModal: function (idx, snapshot) {
7963		      if (!snapshot) return;
7964		      if (snapshot.exists) {
7965			      var found = selected_prod_additionals.find(function (v) { return v.idx === idx; });
7966			      if (found) {
7967				      found.count = snapshot.count;
7968			      }
7969		      } else {
7970			      selected_prod_additionals = selected_prod_additionals.filter(function (v) { return v.idx !== idx; });
7971			      delete prod_additional_require_option_count[idx];
7972		      }
7973		      selected_prod_additional_options = selected_prod_additional_options.filter(function (o) { return o.prod_idx !== idx; });
7974		      if (snapshot.options && snapshot.options.length > 0) {
7975			      selected_prod_additional_options = selected_prod_additional_options.concat(snapshot.options);
7976		      }
7977	      },
7978	      selectAdditionalProductForModal: (product_idx, price, name, maximum_purchase_quantity, member_maximum_purchase_quantity, maximum_purchase_quantity_type, optional_limit, optional_limit_type, stock_use, stock, stock_unlimit, option_mix_type, prod_type, period_discount_flag, period_discount_data, period_discout_group_list, membership_discount, additional_creator_discount_data) => {
7979		      try {
7980			      let unselected = [];
7981			      Object.keys(prod_additional_require_option_count).map((key) => {
7982				      if (prod_additional_require_option_count[key] > 0) {
7983					      if(selected_prod_additional_options.filter((option) => option.prod_idx == key)[0]?.options?.length < prod_additional_require_option_count[key]){
7984						      unselected.push(key);
7985					      }
7986				      }
7987			      });
7988
7989			      unselected.forEach((idx) => {
7990				      selected_prod_additionals = selected_prod_additionals.filter((prod_additional) =>
7990 prod_additional.idx != idx);
7991				      selected_prod_additional_options = selected_prod_additional_options.filter((option) => option.prod_idx != idx);
7992				      delete prod_additional_require_option_count[idx];
7993			      });
7994
7995			      // @TODO 기존 추가 상품 옵션 초기화, 추가 상품 완료가 안되었다면 selected_prod_additionals에서도 빠져야 함
7996			      selected_prod_additional_require_options = [];
7997
7998			      // 6837-6842라인: 상품 정보를 selected_prod_additionals에 추가
7999			      const prod_additional_found = selected_prod_additionals.find(v=>v.idx===product_idx);
8000			      if(prod_additional_found) {
8001				      prod_additional_found.count++;
8002			      }else{
8003				      selected_prod_additionals.push({
8004					      idx: product_idx,
8005					      price,
8006					      name,
8007					      maximum_purchase_quantity,
8008					      member_maximum_purchase_quantity,
8009					      maximum_purchase_quantity_type,
8010					      optional_limit,
8011					      optional_limit_type,
8012					      stock_use,
8013					      stock,
8014					      stock_unlimit,
8015					      option_mix_type,
8016					      prod_type,
8017					      membership_discount,
8018					      creator_discount_data: additional_creator_discount_data || null,
8019					      count: 1
8020				      });
8021				      setProdAdditionalPeriodDiscountData(product_idx, period_discount_flag, period_discount_data, period_discout_group_list);
8022			      }
8023
8024			      // current_select_additional_prod_idx 설정
8025			      current_select_additional_prod_idx = product_idx;
8026
8027			      if (typeof TEST_SERVER !== 'undefined' && TEST_SERVER) {
8028				      console.log('[SITE_SHOP_DETAIL] selectAdditionalProductForModal 완료:', current_select_additional_prod_idx);
8029			      }
8030		      } catch (error) {
8031			      console.error('[SITE_SHOP_DETAIL] selectAdditionalProductForModal 실행 중 오류:', error);
8032		      }
8033	      },
8034			    selectAdditionalProduct: (product_idx, price, name, maximum_purchase_quantity, member_maximum_purchase_quantity, maximum_purchase_quantity_type, optional_limit, optional_limit_type, stock_use, stock, stock_unlimit, option_mix_type, prod_type, period_discount_flag, period_discount_data, period_discout_group_list, membership_discount, additional_creator_discount_data, is_shop_free_ship_notify = false) => {
8035					// 수량을 조절할 수 없는 상품(디지털/이용권 상품인데 옵션이 없는 경우)이 이미 추가된 상태에서 다시 선택할 시 얼럿 노출
8036			    const prod_additional_found = selected_prod_additionals.find(v=>v.idx===product_idx);
8037			    if(prod_type !== SHOP_CONST.PROD_TYPE.NORMAL && prod_additional_found){
8038						if(selected_prod_additional_options.filter((option) => option.prod_idx == product_idx).length === 0){
8039							// 옵션이 있다면 기존에 추가 시에 옵션도 함께 등록되게 됨
8040							alert(getLocalizeString("설명_이미선택된상품입니다", "", "이미 선택된 상품입니다."));
8041							return false;
8042						}
8043			    }
8044
8045                let unselected = [];
8046                Object.keys(prod_additional_require_option_count).map((key) => {
8047                        if (prod_additional_require_option_count[key] > 0) {
8048                            if(selected_prod_additional_options.filter((option) => option.prod_idx == key)[0]?.options?.length < prod_additional_require_option_count[key]){
8049                                unselected.push(key);
8050                            }
8051                        }
8052                    }
8053                );
8054
8055                if(unselected.length > 0){
8056                    Object.keys(unselected).map((key) => {
8057                        if (prod_additional_require_option_count[key] > 0) {
8058                            selected_prod_additionals = selected_prod_additionals.filter((prod_additional) => prod_additional.idx != key);
8059                            selected_prod_additional_options = selected_prod_additional_options.filter((option) => option.prod_idx != key);
8060                            delete prod_additional_require_option_count[key];
8061                        }
8062                    });
8063                }
8064
8065					// @TODO 기존 추가 상품 옵션 초기화, 추가 상품 완료가 안되었다면 selected_prod_additionals에서도 빠져야 함
8066			    selected_prod_additional_require_options = [];
8067			    if(prod_additional_found) {
8068				    prod_additional_found.count++;
8069			    }else{
8070					    selected_prod_additionals.push({idx: product_idx, price, name, maximum_purchase_quantity, member_maximum_purchase_quantity, maximum_purchase_quantity_type, optional_limit, optional_limit_type, stock_use, stock, stock_unlimit, option_mix_type, prod_type, membership_discount, creator_discount_data: additional_creator_discount_data || null, count: 1});
8071						setProdAdditionalPeriodDiscountData(product_idx, period_discount_flag, period_discount_data, period_discout_group_list);
8072			    }
8073			    current_select_additional_prod_idx = product_idx;
8074			    if(is_mobile_width){
8075						// 상품을 선택했을 때 1depth에는 옵션 목록을 띄
8075우고 2depth에는 다음 옵션 시트를 띄움
8076						// @TODO 1depth 모든 옵션 추가(선택 옵션 포함) > 열리지는 않게 처리하면 될 듯
8077				    loadOption('prod', current_prod_idx);		// 옵션을 다시 로드함
8078				    loadProdAdditionalOptions(product_idx);
8079			    }else{
8080						if(is_shop_free_ship_notify && isFreeShipNotifyAdditionalActive()){
8081							$prod_additional_options = $('#prod_free_ship_notify_additional_options');
8082						}else {
8083							// PC: 메인 옵션은 리로드하지 않음 (리로드시 추가상품 영역이 다시 그려져 인라인 옵션이 사라짐)
8084							// PC: 클릭한 추가상품 바로 아래에 옵션을 그리기 위해 임시 컨테이너를 삽입
8085							try {
8086								var $clicked = $(
8087									event && event.currentTarget ? event.currentTarget : null);
8088								var $listItem = ($clicked && $clicked.length)
8089									? $clicked.closest('.list-item')
8090									: $();
8091								if ($listItem.length) {
8092									var $inline = $(
8093										'<div class="_prod_additional_inline_options"></div>');
8094									$listItem.after($inline);
8095									$prod_additional_options = $inline;
8096								} else {
8097									$prod_additional_options = $prod_additional_options_default;
8098								}
8099							} catch (e) {
8100								$prod_additional_options = $prod_additional_options_default;
8101							}
8102						}
8103
8104				    loadProdAdditionalOptions(product_idx);
8105			    }
8106		    },
8107        selectOptionalOption: function (prod_idx, option_code, value_code, value_name, success) {
8108            selectOption(
8109                prod_idx,
8110                {
8111                    options:[
8112                        {
8113                            'value_type': 'SELECT',
8114                            'option_code': option_code,
8115                            'value_code': value_code,
8116                            'value_name': value_name
8117                        }
8118                    ],
8119                    require: false,
8120                    count: 1
8121                },
8122                function () {
8123                    success();
8124                },
8125                function (msg) {
8126                    alert(msg);
8127                }
8128            );
8129        },
8130	      selectProdAdditionalOptionalOption: function (prod_idx, option_code, value_code, value_name, success) {
8131            selectProdAdditionalOption(prod_idx, [{
8132                'value_type': 'SELECT',
8133                'option_code': option_code,
8134                'value_code': value_code,
8135                'value_name': value_name
8136            }], false, 1, function () {
8137                success();
8138		            if(is_mobile_width){
8139			            imSheet.close('prod_additional_sheet');
8140		            }
8141            }, function (msg) {
8142                alert(msg);
8143            });
8144        },
8145        /** type (prod/cart) */
8146        changeOptionCount: function (optNo, optCount, type) {
8147            changeOptionCount(optNo, optCount, function () {
8148                updateSelectedOptions(type);
8149            });
8150        },
8151        "getCurrentProdNo": function () {
8152            return getCurrentProdNo();
8153        },
8154        "getCurrentProdCode": function () {
8155            return getCurrentProdCode();
8156        },
8157        "saveSelectedProd": function () {
8158            saveSelectedProd();
8159        },
8160        "setSelectedProd": function (is_set) {
8161            setSelectedProd(is_set);
8162        },
8163        addOrder: function (type, backurl, params) {
8164            addOrder(type, backurl, params);
8165        },
8166        addGiftOrder: function (type, backurl, params = {}) {
8167            // 선물하기 구매 플래그 활성화
8168			params.is_gift_buy = true;
8169            this.addOrder(type, backurl, params);
8170        },
8171        addCart: function () {
8172            addCart(function () {
8173                showAddCartAlarm();
8174            });
8175        },
8176        setShippingTemplateCode: function (code) {
8177            selected_shipping_template_code = code || '';
8178        },
8179        getShippingTemplateCode: function () {
8180            return selected_shipping_template_code;
8181        },
8182        "digitalFileDownload": function (order_no) {
8183            digitalFileDownload(order_no);
8184        },
8185        "handleOmsDigitalDownload": function (e, href) {
8186            handleOmsDigitalDownload(e, href);
8187        },
8188        updateSelectedOptions: function (type) {
8189            updateSelectedOptions(type);
8190        },
8191        checkOptionLength: function (target, limit) {
8192            checkOptionLength(target, limit);
8193        },
8194        getSelectedOption: function () {
8195            return selected_options;
8196        },
8197        getRequireOptionCount: function () {
8198            return require_option_count;
8199        },
8200        getSelectedRequireOptions: function () {
8201            return selected_require_options;
8202        },
8203        changeOrderCount: function (type, count, success, is_alert) {
8204            return changeOrderCount(type, count, success, is_alert);
8205        },
8206		    changeProdAdditionalOrderCount: function (no, type, count, success, is_alert) {
8207			    return changeProdAdditionalOrderCount(no, type, count, success, is_alert);
8208		    },
8209        selectCartType: function (type) {
8210            return selectCartType(type);
8211        },
8212        checkProdStock: function (cnt) {
8213            return checkProdStock(cnt);
8214        },
8215        increaseOrderCount: function (type, success) {
8216            return increaseOrderCount(type, success);
8217        },
8218        decreaseOrderCount: function (type, success) {
8219            return decreaseOrderCount(type, success);
8220        },
8221		    increaseProdAdditionalOrderCount: function (no, type, success) {
8222			    return increaseProdAdditionalOrderCount(no, type, success);
8223		    },
8224		    decreaseProdAdditionalOrderCount: function (no, type, success) {
8225			    return decreaseProdAdditionalOrderCount(no, type, success);
8226		    },
8227        showMobileOptions: function (type) {
8228            return showMobileOptions(type);
8229        },
8230        changeValueChecked: function (type) {
8231            return changeValueChecked(type);
8232        },
8233        showMobileFirstSelect: function () {
8234            return showMobileFirstSelect();
8235        },
8236        hideMobileOptions: function () {
8237            return hideMobileOptions();
8238        },
8239        showPCOptions: function (type, backurl) {
8240            return showPCOptions(type, backurl, false);
8241        },
8242        hidePCOptions: function () {
8243            return hidePCOptions();
8244        },
8245        socialBtnResize: function () {
8246            return socialBtnResize();
8247        },
8248		externalwidgetWrapResize : function(target, pc_position, mo_position) {
8249			return externalwidgetWrapResize(target, pc_position, mo_position);
8250		},
8251        changeContentTab: function (type, r_p, q_p, paging_on, only_photo, rating = 0) {
8252            return changeContentTab(type, r_p, q_p, paging_on, only_photo, rating);
8253        },
8254        deleteReviewInTabDisplay: function (prod_code, only_photo) {
8255            deleteReviewInTabDisplay(prod_code, only_photo);
8256        },
8257        changeContentPCTab: function (type, r_p, q_p, paging_on, only_photo,  rating = 0 , shop_view_body_width) {
8258            return changeContentPCTab(type, r_p, q_p, paging_on, only_photo, rating, shop_view_body_width);
8259        },
8260        removeReviewHash: function () {
8261            return removeReviewHash();
8262        },
8263        removeQnawHash: function () {
8264            return removeQnawHash();
8265        },
8266        getOnlyPhotoReview: function (only_photo_switch, is_mobile, is_one_page, rating) {
8267            return getOnlyPhotoReview(only_photo_switch, is_mobile, is_one_page, rating);
8268        },
8269        getStarRatingReview: function (only_photo_switch, is_mobile, is_one_page, rating) {
8270            return getStarRatingReview(only_photo_switch, is_mobile, is_one_page, rating);
8271        },
8272        getFirstPhotoReview: function (page) {
8273            return getFirstPhotoReview(page);
8274        },
8275        viewPhotoReviewMore: function (is_mobile, is_one_page) {
8276            return viewPhotoReviewMore(is_mobile, is_one_page);
8277        },
8278        getReviewSummary: function (target) {
8279            return getReviewSummary(target);
8280        },
8281        changeReviewDetail: function (idx) {
8282            return changeReviewDetail(idx);
8283        },
8284        viewReviewDetail: function (idx, r_p, only_photo, is_hash) {
8285            return viewReviewDetail(idx, r_p, only_photo, is_hash);
8286        },
8287        "setMembershipSaleData": function (data, is_main_product_membership_discount) {
8288            setMembershipSaleData(data, is_main_product_membership_discount);
8289        },
8290        "setPeriodDiscountData": function (flag, $data2, group_list) {
8291            setPeriodDiscountData(flag, $data2, group_list);
8292        },
8293        "setCreatorDiscountData": function (data) {
8294            // 크리에이터 전용 혜택가 데이터 주입 (PHP detail.sub 에서 호출).
8295            // 배너 DOM 은 PHP 측 detail/creator-benefit-banner.sub 가 출력한다 — JS 측에서 다시 그리지 않는다 (DOM 중복 회피).
8296            creator_discount_data = data || null;
8297            creator_discount_max_reached = false;
8298            hideCreatorDiscountMaxToast();
8299            refreshFreeShipNotifyInitialTotal();
8300        },
8301        "setFreeShipNotifyProdAdditionalRegularPrice": function (prod_idx, regular_price) {
8302            setFreeShipNotifyProdAdditionalRegularPrice(prod_idx, regular_price);
8303        },
8304		    "setProdAdditionalPeriodDiscountData": function (no, flag, $data2, group_list) {
8305			    setProdAdditionalPeriodDiscountData(no, flag, $data2, group_list);
8306		    },
8307		    "setProdAdditionalRequireOptionCount": function (require_option_count, require_input_option_count) {
8308			    prod_additional_require_option_count = {
8309				    ...prod_additional_require_option_count,
8310				    ...(require_option_count || {})
8311			    };
8312			    prod_additional_require_input_option_count = {
8313				    ...prod_additional_require_input_option_count,
8314				    ...(require_input_option_count || {})
8315			    };
8316		    },
8317        viewQnaDetail: function (idx, q_p, is_hash) {
8318            return viewQnaDetail(idx, q_p, is_hash);
8319        },
8320        changeProdImageRolling: function (no) {
8321            return changeProdImageRolling(no);
8322        },
8323        startProdImageRolling: function (autoWidth) {
8324            return startProdImageRolling(autoWidth);
8325        },
8326        setOrderCount: function (count) {
8327            order_count = count;
8328        },
8329        getOrderCount: function () {
8330            return order_count;
8331        },
8332        addNPayWish: function () {
8333            return addNPayWish();
8334        },
8335        hideAddCartAlarm: function () {
8336            hideAddCartAlarm();
8337        },
8338        moveCart: function () {
8339            moveCart();
8340        },
8341        trackAddToCartPopupAction: function (eventName, content, options) {
8342            trackAddToCartPopupAction(eventName, content, options);
8343        },
8344        loadProductDetail: function (code, prod_idx, menu_code, current_url) {
8345            loadProductDetail(code, prod_idx, menu_code, current_url);
8346        },
8347		    loadBookingDetail: function (code, idx, day, endDay, current_url) {
8348			    loadBookingDetail(code, idx, day, endDay, current_url);
8349		    },
8350		    loadCode: function (code) {
8351			    loadCode(code);
8352		    },
8353        loadOption: function (type, prod_idx) {
8354            return loadOption(type, prod_idx);
8355        },
8356        loadDelivSetting: function (prod_idx, change_country, deliv_type, deliv_pay_type) {
8357            return loadDelivSetting(prod_idx, change_country, deliv_type, deliv_pay_type);
8358        },
8359        updateRefundData: (refundData) => {
8360            return updateRefundData(refundData);
8361        },
8362        changeRequireInputOption: function (type, prod_idx, option_code, msg, success) {
8363            changeRequireInputOption(type, prod_idx, option_code, msg, function () {
8364                success();
8365            });
8366        },
8367	      changeProdAdditionalRequireInputOption: function (type, prod_idx, option_code, msg, success) {
8368		        changeProdAdditionalRequireInputOption(type, prod_idx, option_code, msg, function () {
8369                success();
8370            });
8371        },
8372        changeInput: function () {
8373            changeInput();
8374        },
8375        changeTab: function (type) {
8376            changeTab(type);
8377        },
8378        countryCodeChange: function (country) {
8379            countryCodeChange(country);
8380        },
8381        DetailItemMake: function (idx, change_country, deliv_type, deliv_pay_type) {
8382            return DetailItemMake(idx, change_country, deliv_type, deliv_pay_type);
8383        },
8384        loadItemInfo(params) {
8385            return loadItemInfo(params);
8386        },
8387        addDelivType: function (type) {
8388	        $deliv_type = type;
8389        },
8390        addDelivPayType: function (type) {
8391            $deliv_pay_type = type;
8392        },
8393        addDelivCountry: function (country) {
8394            $deliv_country = country;
8395        },
8396        getDelivType: function () {
8397            return $deliv_type || '';
8398        },
8399        getDelivPayType: function () {
8400            return $deliv_pay_type || '';
8401        },
8402        getDelivCountry: function () {
8403            return $deliv_country || '';
8404        },
8405        visitFormMake: function () {
8406            $deliv_visit_wrap.show();
8407        },
8408        openProdDetailFromShoppingList: function (idx, back_url, is_prod_detail_page, is_mobile, prod_idx_org) {
8409            return openProdDetailFromShoppingList(idx, back_url, is_prod_detail_page, is_mobile, prod_idx_org);
8410        },
8411        'getReviewQnaCount': function (prod_code) {
8412            return getReviewQnaCount(prod_code);
8413        },
8414        'setReviewQnaCountText': function ($target, count) {
8415            return setReviewQnaCountText($target, count);
8416        },
8417        getReviewCountFromShoppingList: function (prod_code_list) {
8418            return getReviewCountFromShoppingList(prod_code_list);
8419        },
8420        openBuyerReview: function (prod_code) {
8421            return openBuyerReview(prod_code);
8422        },
8423        deleteReviewImage: function (obj) {
8424            return deleteReviewImage(obj);
8425        },
8426        openCouponDownload: function () {
8427            return openCouponDownload();
8428        },
8429        setReviewCarousel: function () {
8430            return setReviewCarousel();
8431        },
8432        setReviewClass: function (idx) {
8433            return setReviewClass(idx);
8434        },
8435        setReviewBtnAnimation: function () {
8436            return setReviewBtnAnimation();
8437        },
8438        setReviewSwipe: function (prev_idx, next_idx) {
8439            return setReviewSwipe(prev_idx, next_idx);
8440        },
8441        showSeeMoreButton: function () {
8442            return showSeeMoreButton();
8443        },
8444        hideSeeMoreButton: function () {
8445            return hideSeeMoreButton();
8446        },
8447        openRequireSelect: function (option_code) {
8448            return openRequireSelect(option_code);
8449        },
8450        addRegularlyCart: function () {
8451            return addRegularlyCart();
8452        },
8453	    toggleWishButtons: function($wishButtons, wishCount, isActive) {
8454			return toggleWishButtons($wishButtons, wishCount, isActive);
8455	    },
8456        // C3 메서드 추가
8457		OMS_addOrder : function(type, backurl, params){
8458			// 외부채널은 사은품 미지원
8459			if (type === 'npay' || type === 'talkpay') {
8460				OMS_addOrder(type, backurl, params);
8461				return;
8462			}
8463			selectFreebieAsync()
8464				.then((selected_freebies) => {
8465					params.selected_freebies = selected_freebies || [];
8466					OMS_addOrder(type, backurl, params);
8467				});
8468		},
8469		OMS_showPCOptions : function(type, backurl){
8470			return showPCOptions(type, backurl, true);
8471		},
8472		OMS_addGiftOrder: async function (type, backurl, params = {}) {
8473			params.is_gift_buy = true; // 선물하기 구매 플래그 활성화
8474
8475			// 빠른배송이 활성화된 경우 선물하기 불가 안내 모달
8476			const isShippingServiceActive = typeof SHIPPING_SERVICE !== 'undefined' && SHIPPING_SERVICE.isActive();
8477			if (isShippingServiceActive) {
8478				const confirmedGift = await SHIPPING_SERVICE.confirmGift();
8479				if (!confirmedGift) {
8480					return;
8481				}
8482				// 일반배송으로 구매 선택 시 일반배송 템플릿 코드 설정
8483				SITE_SHOP_DETAIL.setShippingTemplateCode('');
8484			}
8485
8486			if (type === 'guest_login') {
8487				OMS_addOrder(type, backurl, params)
8488				return;
8489			}
8490
8491			// 외부채널은 사은품 미지원
8492			if (type === 'npay' || type === 'talkpay') {
8493				OMS_addOrder(type, backurl, params)
8494				return;
8495			}
8496
8497			selectFreebieAsync()
8498				.then((selected_freebies) => {
8499					params.selected_freebies = selected_freebies || [];
8500					OMS_addOrder(type, backurl, params)
8501				});
8502		},
8503		confirmOrderWithCartItems: function (type, backurl, params) {
8504			// type: "guest_login" | "npay" | "talkpay" | "normal"
8505				confirmOrderWithCartItems(type, backurl, params);
8506		},
8507		trackClickPurchaseShopView: function (paymentButtonType) {
8508			trackClickPurchaseShopView(paymentButtonType);
8509		},
8510		hideConfirmOrderWithCartItems: function () {
8511			if($confirm_order_with_cart_alarm) $confirm_order_with_cart_alarm.hide();
8512		},
8513		getThumbnailBadgeStyleByPosition: function ({ badgeColor, badgePosition, imageWidth = ''}){
8514			return getThumbnailBadgeStyleByPosition({ badgeColor, badgePosition, imageWidth});
8515		},
8516		selectFreebieAsync: selectFreebieAsync.bind(this),
8517		// 장바구니 수량 제한 관련 함수 추가
8518		setMaxPurchaseQuantity: function(_max_prod, _max_member, _type, _opt_limit, _opt_limit_type, _prod_name) {
8519			max_prod_quantity = _max_prod || 0;
8520			max_member_quantity = _max_member || 0;
8521			maximum_purchase_quantity_type = _type || 'order';
8522			optional_limit = _opt_limit || 0;
8523			optional_limit_type = _opt_limit_type || 'relative';
8524			if (_prod_name) prod_name = _prod_name;
8525		},
8526		validateMaxPurchaseQuantity: function() {
8527			if (max_prod_quantity <= 0) return true;
8528
8529			var is_valid = true;
8530			var error_msg = '';
8531
8532			$.each(selected_options, function(idx, opt) {
8533				if (!is_valid) return false;
8534
8535				var count = opt.count;
8536				var is_require_option = opt.require;  // DOM 대신 opt.require 사용
8537				var should_check = false;
8538
8539				switch (maximum_purchase_quantity_type) {
8540					case 'prod':
8541						// 옵션별 수량: 본품, 필수옵션, 선택옵션 모두 각각 검증
8542						should_check = true;
8543						break;
8544					case 'required_option':
8545						// 옵션별 수량(필수옵션): 본품, 필수옵션만 검증 (선택옵션 제외)
8546						should_check = is_require_option;
8547						break;
8548					case 'optional_option':
8549						// 옵션별 수량(선택옵션): 선택옵션만 검증 (본품, 필수옵션 제외)
8550						should_check = !is_require_option;
8551						break;
8552					case 'order':
8553					default:
8554						// order 타입은 아래에서 전체 수량으로 체크
8555						break;
8556				}
8557
8558				if (should_check && count > max_prod_quantity) {
8559					is_valid = false;
8560					error_msg = LOCALIZE.설명_최대구매수량(max_prod_quantity);
8561				}
8562			});
8563
8564			// order 타입인 경우 전체 수량 합계 체크
8565			if (is_valid && maximum_purchase_quantity_type === 'order') {
8566				var total_count = 0;
8567				$.each(selected_options, function(idx, opt) {
8568					total_count += opt.count;
8569				});
8570				if (total_count > max_prod_quantity) {
8571					is_valid = false;
8572					error_msg = LOCALIZE.설명_최대구매수량(max_prod_quantity);
8573				}
8574
8575				// 회원별 최대 구매수량 검증 추가
8576				if (is_valid && max_member_quantity > 0) {
8577					if (total_count > max_member_quantity) {
8578						is_valid = false;
8579						error_msg = LOCALIZE.설명_회원당최대구매수량상품명포함(prod_name, max_member_quantity);
8580					}
8581				}
8582			}
8583
8584			return is_valid ? true : error_msg;
8585		},
8586		// 본품 수량 검증 함수 (optional_option 타입 제외)
8587		checkMaxPurchaseQuantityForOrderCount: function(count) {
8588			if (max_prod_quantity > 0 && maximum_purchase_quantity_type !== 'optional_option') {
8589				var _total_count = count; // 본품 수량
8590
8591				// order 타입이면 본상품의 필수/선택 옵션도 합산
8592				if (maximum_purchase_quantity_type === 'order') {
8593					// 필수/선택 옵션 수량 포함
8594					$.each(selected_options, function (idx, obj) {
8595						_total_count += obj.count;
8596					});
8597				}
8598
8599				if (_total_count > max_prod_quantity) {
8600					return LOCALIZE.설명_최대구매수량(max_prod_quantity);
8601				}
8602			}
8603
8604			// 회원당 최대 구매수량 검증 (1회 구매 제한이 옵션별 타입이면 1인 제한 미적용 - 스펙)
8605			if (max_member_quantity > 0 && maximum_purchase_quantity_type === 'order') {
8606				var _member_total = count;
8607				// 필수/선택 옵션 수량 포함
8608				$.each(selected_options, function (idx, obj) {
8609					_member_total += obj.count;
8610				});
8611				if (_member_total > max_member_quantity) {
8612					return LOCALIZE.설명_회원당최대구매수량상품명포함(prod_name, max_member_quantity);
8613				}
8614			}
8615
8616			return true;
8617		},
8618    };
8619}();
8620
8621window.addEventListener('imweb:productRecommendationModal:requestClose', function () {
8622    if (window.SITE_SHOP_DETAIL && typeof window.SITE_SHOP_DETAIL.hideAddCartAlarm === 'function') {
8623        window.SITE_SHOP_DETAIL.hideAddCartAlarm();
8624    }
8625});
8626
8627var SITE_SHOP_CART = function () {
8628    var selectedCartItem = [];
8629    var $cartItemCheckboxList;
8630    var $cartAllCheckBox;
8631    var $shop_cart_list;
8632    var $shop_cart_wish_list;
8633    var $shop_cart_wish_list_empty;
8634    var $changeCartItemLayer;
8635    var $total_price;
8636    /** 크리에이터 전용 혜택가 데이터 (cart 컨텍스트) — PHP 에서 setCreatorDiscountData 로 주입. null 이면 비활성 */
8637    var creator_cart_data = null;
8638
8639    var $cart_list_wrap;
8640    var $global_select;
8641    var $cart_result_container;
8642
8643    var get_selected_price_request_seq = 0;
8644    var is_cart_changed = false;
8645    var currentCartCode = '';
8646    var current_backurl = '';
8647    var currentChangeCartItemCode = '';
8648
8649    var currentCartItemMaxQuantity = 0;
8650    var currentCartItemMaxQuantityType = 'order';
8651		var $deliv_country = '';
8652    var add_order_progress_check = false;
8653    var cart_type = '';
8654    // 무료배송 안내 모달이 떠있는 동안 원래 addOrderWithCart 호출 인자를 보류해둔다.
8655    // 사용자가 모달의 "주문하기" 버튼을 누르면 proceedOrderAfterFreeShipAlarm에서 이 인자로 함수를 재호출.
8656    var _pendingOrderWithCart = null;
8657
8658    // 수량별 할인 계산 헬퍼 (장바구니)
8659    // data-qd-exclude="Y" 카운터(선택옵션 등)는 조건 수량/할인 base 모두에서 제외 (IMIO-7150)
8660    // 품목 금액 초과 방지 클램핑 적용 (결제페이지/OMS 동일, IMIO-7184)
8661    const _calcCartQuantityDiscount = ($row, priceRaw) => {
8662        if (!$row || !$row.length) return 0;
8663        const tiersRaw = $row.attr('data-qd-tiers');
8664        if (!tiersRaw) return 0;
8665        try {
8666            const tiers = JSON.parse(tiersRaw);
8667            if (!Array.isArray(tiers) || tiers.length === 0) return 0;
8668
8669            // 카운터 순회: 제외 대상이면 선택옵션 금액 누적, 아니면 조건 수량 누적
8670            let conditionQty = 0;
8671            let excludedOptionPrice = 0;
8672            $row.find('._counter_value').each(function () {
8673                const $counter = $(this).closest('._cart_counter');
8674                const qty = parseInt(this.tagName === 'INPUT' ? this.value : this.textContent, 10) || 0;
8675                if ($counter.attr('data-qd-exclude') === 'Y') {
8676                    const unitPrice = parseFloat(
8677                        $counter.closest('._cart_option_card').find('._cart_option_price').attr('data-unit-price')
8678                    ) || 0;
8679                    excludedOptionPrice += unitPrice * qty;
8680                    return;
8681                }
8682                conditionQty += qty;
8683            });
8684            if (conditionQty <= 0) return 0;
8685
8686            // 충족된 tier 중 min_quantity 최댓값 선택
8687            const matchedTier = tiers
8688                .filter(t => conditionQty >= t.min_quantity)
8689                .sort((a, b) => b.min_quantity - a.min_quantity)[0];
8690            if (!matchedTier) return 0;
8691
8692            // base = 품목 금액 − 선택옵션 금액 (0 이상으로 클램핑)
8693            const base = Math.max((parseInt(priceRaw, 10) || 0) - excludedOptionPrice, 0);
8694            if (matchedTier.method === 'percentage') {
8695                return Math.min(Math.ceil(base * matchedTier.value / 100), base);
8696            }
8697            return Math.min(parseInt(matchedTier.value, 10) || 0, base);
8698        } catch { return 0; }
8699    };
8700
8701    /**
8702     * 카트 row 의 가격 표시를 일관되게 갱신.
8703     * basePrice는 period 차감/QD 미반영 상태(= data-discounted-price와 동일).
8704     * QD는 data-qd-tiers 기준으로 계산한다.
8705     * PC/mobile 양쪽의 가격 텍스트와 data 속성, origin 표시까지 한 번에 처리.
8706     *
8707     * @param {jQuery} $row - 카트 tr.content 요소
8708     * @param {Object} opts
8709     * @param {number} opts.basePrice - period 차감 후 row 단가 (QD 미반영)
8710     * @param {number} [opts.originPrice] - 원가 (strikethrough). 미지정 시 origin 갱신 안 함
8711     * @param {number} [opts.fpDiscount=0] - 첫 구매 혜택 차감액
8712     */
8713    const renderCartRowPrice = ($row, opts) => {
8714        if (!$row || !$row.length) return;
8715        const basePrice = parseInt(opts.basePrice, 10) || 0;
8716        const fpDiscount = parseInt(opts.fpDiscount, 10) || 0;
8717        const qd = _calcCartQuantityDiscount($row, basePrice);
8718        const finalPrice = Math.max(basePrice - qd - fpDiscount, 0);
8719        const finalText = LOCALIZE.getCurrencyFormat(finalPrice);
8720
8721        // PC 가격
8722        const $pcDefault = $row.find('._cart_price_default');
8723        if ($pcDefault.length) {
8724            $pcDefault.data('discounted-price', basePrice);
8725            $pcDefault.find('._cart_price_default_value').text(finalText);
8726        }
8727
8728        // Mobile 가격
8729        const $mobilePrice = $row.find('._cart_mobile_order_price');
8730        if ($mobilePrice.length) {
8731            $mobilePrice.data('discounted-price', basePrice);
8732            $mobilePrice.text(finalText);
8733        }
8734
8735        // Origin 표시 (선택)
8736        if (opts.originPrice !== undefined) {
8737            const originPrice = parseInt(opts.originPrice, 10) || 0;
8738            const originText = LOCALIZE.getCurrencyFormat(originPrice);
8739            const hasDiscount = originPrice !== finalPrice;
8740
8741            const $pcOrigin = $row.find('._cart_price_origin');
8742            if ($pcOrigin.length) {
8743                $pcOrigin.data('origin-price', originPrice).text(originText);
8744                $pcOrigin.data('default-visible', hasDiscount ? 'Y' : 'N').toggle(hasDiscount);
8745            }
8746
8747            const $mobileOrigin = $row.find('._cart_mobile_order_price_origin');
8748            if ($mobileOrigin.length) {
8749                $mobileOrigin.data('origin-price', originPrice).text(originText);
8750                $mobileOrigin.data('default-visible', hasDiscount ? 'Y' : 'N').toggle(hasDiscount);
8751            }
8752        }
8753    };
8754
8755    var initCart = function (cart_code, backurl, is_regularly, is_c3 = false){
8756        currentCartCode = cart_code;
8757        current_backurl = backurl;
8758        $shop_cart_list = $('#shop_cart_list');
8759        $changeCartItemLayer = $('#shop_cart_change_layer');
8760        $shop_cart_wish_list = $('#shop_cart_wish_list');
8761        $shop_cart_wish_list_empty = $('#shop_cart_wish_list_empty');
8762
8763        $cart_list_wrap = $shop_cart_list.find('._cart_list_wrap');
8764        $global_select = $shop_cart_list.find('._global_select');
8765        // 모듈 스코프 cart_type 에 일반/정기 구분값을 반영 (외부 함수에서 재사용)
8766        cart_type = SHOP_CONST.CART_TYPE_NORMAL;
8767        if (is_regularly == 'Y') cart_type = SHOP_CONST.CART_TYPE_REGULARLY;
8768        cartListMake(backurl, cart_type, is_c3);
8769    };
8770
8771    var findCartItemDelivTd = function ($row, itemCode) {
8772        var $delivTd = $cart_list_wrap.find('._cart_item_deliv_td[data-item-code="' + itemCode + '"]');
8773        if ($delivTd.length) return $delivTd;
8774
8775        $delivTd = $row.find('._cart_item_deliv_td');
8776        if ($delivTd.length) return $delivTd;
8777
8778        // PC 테이블은 묶음배송 그룹의 첫 행에만 배송비 셀이 있고 rowspan으로 묶인다.
8779        return $row.prevAll('tr.content').filter(function () {
8780            return $(this).find('._cart_item_deliv_td').length > 0;
8781        }).first().find('._cart_item_deliv_td');
8782    };
8783
8784    var findCartBundleShippingRow = function ($row, itemCode) {
8785        var $shippingRow = $cart_list_wrap.find('tr.im-tr-shipping[data-item-code="' + itemCode + '"]');
8786        if ($shippingRow.length) return $shippingRow;
8787
8788        return $row.nextAll('tr.im-tr-shipping').first();
8789    };
8790
8791    var updateCartItemDelivText = function ($delivTd, delivPriceText) {
8792        if (!$delivTd.length || delivPriceText === undefined) return;
8793
8794        var $delivPrice = $delivTd.find('._cart_item_deliv_price');
8795        if (!$delivPrice.length) return;
8796
8797        var $textWrap = $delivPrice.find('span.text-bold').first();
8798        if ($textWrap.length) {
8799            $textWrap.html(delivPriceText);
8800            return;
8801        }
8802
8803        if (delivPriceText !== '') {
8804            $delivPrice.prepend('<span class="text-bold">' + delivPriceText + '</span>');
8805        }
8806    };
8807
8808    var updateCartDeliveryData = function (itemDeliveryData) {
8809        if (!itemDeliveryData) return;
8810
8811        $.each(itemDeliveryData, function (itemCode, info) {
8812            const $itemRow = $cart_list_wrap.find(`tr[data-item-code="${itemCode}"]`);
8813            if (!$itemRow.length) return;
8814
8815            if (info.deliv_price_text !== undefined && info.deliv_price_text !== '') {
8816                updateCartItemDelivText(findCartItemDelivTd($itemRow, itemCode), info.deliv_price_text);
8817
8818                const $mobileDelivText = $cart_list_wrap.find(`._cart_mobile_deliv_price[data-item-code="${itemCode}"] ._cart_mobile_deliv_price_text`);
8819                if ($mobileDelivText.length) {
8820                    $mobileDelivText.html(info.deliv_price_text);
8821                }
8822            }
8823
8824            if (info.bundle_deliv_text !== undefined && info.bundle_deliv_text !== '') {
8825                const $shippingRow = findCartBundleShippingRow($itemRow, itemCode);
8826                if ($shippingRow.length) {
8827                    $shippingRow.find('._cart_bundle_deliv_text').html(info.bundle_deliv_text);
8828                }
8829            }
8830        });
8831    };
8832
8833    var getEmptySelectionDelivPriceText = function ($target) {
8834        var delivPriceText = $target.attr('data-free-deliv-price-text');
8835        if (delivPriceText !== undefined && delivPriceText !== '') return delivPriceText;
8836        return '<span>' + getLocalizeString('설명_무료', '', '무료') + '</span>';
8837    };
8838
8839    var updateCartDeliveryForEmptySelection = function () {
8840        $cart_list_wrap.find('._cart_item_deliv_td').each(function () {
8841            var $delivTd = $(this);
8842            updateCartItemDelivText($delivTd, getEmptySelectionDelivPriceText($delivTd));
8843        });
8844
8845        $cart_list_wrap.find('._cart_mobile_deliv_price').each(function () {
8846            var $mobileDelivPrice = $(this);
8847            var $mobileDelivText = $mobileDelivPrice.find('._cart_mobile_deliv_price_text');
8848            if ($mobileDelivText.length) {
8849                $mobileDelivText.html(getEmptySelectionDelivPriceText($mobileDelivPrice));
8850            }
8851        });
8852
8853        $cart_list_wrap.find('tr.im-tr-shipping').each(function () {
8854            var $shippingRow = $(this);
8855            var bundleDelivText = $shippingRow.attr('data-free-bundle-deliv-text');
8856            if (bundleDelivText !== undefined && bundleDelivText !== '') {
8857                $shippingRow.find('._cart_bundle_deliv_text').html(bundleDelivText);
8858            }
8859        });
8860    };
8861
8862    /**
8863     * 선택한 항목의 가격 계산
8864     */
8865    var getSelectedPrice = function (is_regularly) {
8866        var requestSeq = ++get_selected_price_request_seq;
8867        $.ajax({
8868            type: 'POST',
8869            data: {
8870                'item_list': selectedCartItem,
8871                'is_regularly': is_regularly,
8872                // 매출 상승 도구 미리보기 모드 감지용 - 서버에서 current_full_url의 GET 파라미터를 파싱
8873                'current_full_url': current_backurl || ''
8874            },
8875            url: ('/shop/get_cart_price.cm'),
8876            dataType: 'json',
8877            success: function (res) {
8878                if (requestSeq !== get_selected_price_request_seq) return;
8879                if (res.msg == 'SUCCESS') {
8880                    // CB-3865: setCartPriceHtml 보다 먼저 사은품 dispatch — UI 가격 갱신과 독립.
8881                    // setCartPriceHtml 내부의 jQuery 체인이 swallow 되면 dispatch 누락 방지.
8882                    var selectedSet = new Set(selectedCartItem);
8883                    _cartRawItems.forEach(function (item) {
8884                        item.selected = selectedSet.has(item.item_code);
8885                    });
8886                    dispatchFreebieCartChange();
8887                    setCartPriceHtml(res['price_data']);
8888                } else {
8889                    if (res.res_code != 1) {
8890                        alert(res.msg);
8891                    }
8892                    is_cart_changed = false;
8893                }
8894            }
8895        });
8896    };
8897
8898    var setCartPriceHtml = function (data) {
8899        var freeShipProgressPrice = data ? parseFloat(data['free_shipping_progress_price']) : NaN;
8900        var adjustedFreeShipProgressPrice = freeShipProgressPrice;
8901
8902        $total_price = $cart_list_wrap.find('._cart_main_total_price');
8903        $cart_result_container = $cart_list_wrap.find('._cart_result_container');
8904        var $mobile_cart_result_container = $cart_list_wrap.find('tr._cart_result_container');
8905        var $pc_cart_result_container = $cart_list_wrap.find('._cart_result_table').find('._cart_result_container');
8906
8907        $cart_list_wrap.find('._cart_count').text(data['count']);
8908        $cart_result_container.find('._cart_price').html(data['price']);
8909        $cart_result_container.find('._cart_deliv_price').html(data['deliv_price']);
8910        $cart_result_container.find('._cart_add_point').html(data['add_point']);
8911        if ($cart_list_wrap.find('._cartItemCheckbox:checked').length === 0) {
8912            updateCartDeliveryForEmptySelection();
8913        } else {
8914            updateCartDeliveryData(data['item_delivery_data']);
8915        }
8916        // 수량별 할인 합계 계산
8917        let _cartQuantityDiscountTotal = 0;
8918        $cart_list_wrap.find('tr.content[data-qd-tiers]').each(function () {
8919            const $row = $(this);
8920            const itemCode = String($row.data('item-code'));
8921            if ($.inArray(itemCode, selectedCartItem) === -1) return;
8922            // 크리에이터 매칭 item 은 수량별 할인 대상에서 제외 (회원할인과 일관)
8923            if (creator_cart_data && creator_cart_data.matched_items && creator_cart_data.matched_items[itemCode]) return;
8924            const priceRaw = parseInt($row.find('._cart_price_default').data('discounted-price'), 10) || 0;
8925            _cartQuantityDiscountTotal += _calcCartQuantityDiscount($row, priceRaw);
8926        });
8927
8928        // 회원 할인금액: 등급할인 base에서 수량별 할인 차감 후 재계산
8929        const serverMembershipRaw = parseInt(data['membership_discount_raw'], 10) || 0;
8930        const membershipTargetRaw = parseInt(data['membership_discount_target_raw'], 10) || 0;
8931        const membershipConfig = data['membership_discount_config'] || null;
8932        let membershipDiscountRaw = serverMembershipRaw;
8933        if (_cartQuantityDiscountTotal > 0 && membershipConfig && membershipTargetRaw > 0) {
8934            const adjustedTarget = Math.max(membershipTargetRaw - _cartQuantityDiscountTotal, 0);
8935            // 등급할인 재계산 (checkMembershipBenefit 서버 로직과 동일)
8936            let _mcDiscount = 0;
8937            if (membershipConfig['amount'] && (membershipConfig['minimum'] <= adjustedTarget)) {
8938                if (membershipConfig['type'] !== 'price') {
8939                    const _mcAmount = Math.min(membershipConfig['amount'], 100);
8940                    _mcDiscount = (adjustedTarget / 100) * _mcAmount;
8941                    if (_mcDiscount > membershipConfig['maximum']) {
8942                        _mcDiscount = membershipConfig['maximum'];
8943                    }
8944                } else {
8945                    _mcDiscount = membershipConfig['amount'];
8946                }
8947            }
8948            if (_mcDiscount > adjustedTarget) _mcDiscount = adjustedTarget;
8949            const decimalCount = LOCALIZE.getCurrencyDecimalCount();
8950            membershipDiscountRaw = decimalCount === 0
8951                ? Math.ceil(_mcDiscount)
8952                : Math.ceil(_mcDiscount * Math.pow(10, decimalCount)) / Math.pow(10, decimalCount);
8953        }
8954        const membershipDiscountText = membershipDiscountRaw > 0 ? LOCALIZE.getCurrencyFormat(membershipDiscountRaw) : '-';
8955        const membershipPriceExist = membershipDiscountRaw > 0;
8956        $cart_result_container.find('._cart_membership_discount').html(membershipDiscountText);
8957        $cart_list_wrap.find('._cart_membership_field').toggle(membershipPriceExist);
8958
8959        var isFreeShipNotifyAfterSale = data && data['free_shipping_deliv_price_cost_type'] === 'after_sale';
8960        if (isFreeShipNotifyAfterSale && !isNaN(adjustedFreeShipProgressPrice)) {
8961            var freeShipDiscountAdjust = _cartQuantityDiscountTotal;
8962            if (membershipDiscountRaw > 0) {
8963                var serverMembershipAppliedToFreeShip = false;
8964                if (serverMembershipRaw > 0 && membershipTargetRaw > 0) {
8965                    serverMembershipAppliedToFreeShip = adjustedFreeShipProgressPrice <= Math.max(membershipTargetRaw - serverMembershipRaw, 0) + 0.0001;
8966                }
8967                freeShipDiscountAdjust += membershipDiscountRaw;
8968                if (serverMembershipAppliedToFreeShip) {
8969                    freeShipDiscountAdjust -= serverMembershipRaw;
8970                }
8971            }
8972            adjustedFreeShipProgressPrice = Math.max(adjustedFreeShipProgressPrice - freeShipDiscountAdjust, 0);
8973        }
8974
8975        // 무료배송 안내 마그넷 — 실제 배송비 계산값은 건드리지 않고 안내용 진행/잔여 금액만 화면 할인값과 맞춘다.
8976        if (typeof window.IMWEB_SHOP_FREE_SHIP_NOTIFY_SDK !== 'undefined'
8977            && typeof window.IMWEB_SHOP_FREE_SHIP_NOTIFY_SDK.setCartFreeShippingRemainingPrice === 'function'
8978            && data && 'free_shipping_remaining_price' in data) {
8979            var freeShipRemaining = ($('#shop_cart_list ._global_select').length > 0 && $deliv_country !== 'KR')
8980                ? null
8981                : data.free_shipping_remaining_price;
8982            var freeShipRemainingPrice = parseFloat(freeShipRemaining);
8983            if (isFreeShipNotifyAfterSale && !isNaN(freeShipRemainingPrice) && !isNaN(freeShipProgressPrice) && !isNaN(adjustedFreeShipProgressPrice)) {
8984                freeShipRemaining = Math.max((freeShipRemainingPrice + Math.max(freeShipProgressPrice, 0)) - adjustedFreeShipProgressPrice, 0);
8985                freeShipRemainingPrice = parseFloat(freeShipRemaining);
8986            }
8987            if (!isNaN(adjustedFreeShipProgressPrice) && adjustedFreeShipProgressPrice <= 0
8988                && !isNaN(freeShipRemainingPrice) && freeShipRemainingPrice <= 0) {
8989                freeShipRemaining = null;
8990            }
8991            window.IMWEB_SHOP_FREE_SHIP_NOTIFY_SDK.setCartFreeShippingRemainingPrice(freeShipRemaining);
8992        }
8993
8994        // 크리에이터 전용 혜택가 — PC 합계는 별도 row, 모바일 합계는 상품할인금액 하위 row 로 노출
8995        var creatorDiscountRaw = parseInt(data['creator_discount_raw'], 10) || 0;
8996        var creatorDiscountExist = creatorDiscountRaw > 0;
8997        var creatorDiscountText = data['creator_discount_text'] || '-';
8998
8999        // 상품할인금액 = 서버 period_price + 첫구매혜택 + 수량별 할인
9000        const periodPriceRaw = parseInt(data['period_price_raw'], 10) || 0;
9001        const benefitDiscountRaw = parseInt(data['benefit_discount_raw'], 10) || 0;
9002        const pcDiscountPrice = periodPriceRaw + benefitDiscountRaw + _cartQuantityDiscountTotal;
9003        const mobileDiscountPrice = pcDiscountPrice + creatorDiscountRaw;
9004        const pcDiscountPriceExist = pcDiscountPrice > 0;
9005        const mobileDiscountPriceExist = mobileDiscountPrice > 0;
9006
9007        $mobile_cart_result_container.find('._cart_period_field').toggle(mobileDiscountPriceExist);
9008        $mobile_cart_result_container.find('._cart_period_discount').text(mobileDiscountPriceExist ? LOCALIZE.getCurrencyFormat(mobileDiscountPrice) : '-');
9009        $mobile_cart_result_container.find('._cart_mobile_creator_discount_total_field').toggle(creatorDiscountExist);
9010        $mobile_cart_result_container.find('._cart_mobile_creator_discount_total').text(creatorDiscountExist ? '- ' + creatorDiscountText : '');
9011        $mobile_cart_result_container.find('._cart_mobile_discount_detail_wrap').toggle(creatorDiscountExist);
9012        $pc_cart_result_container.find('._cart_period_field').toggle(pcDiscountPriceExist);
9013        $pc_cart_result_container.find('._cart_period_discount').text(pcDiscountPriceExist ? LOCALIZE.getCurrencyFormat(pcDiscountPrice) : '-');
9014
9015        $mobile_cart_result_container.find('._cart_creator_field').hide();
9016        $pc_cart_result_container.find('._cart_creator_field').toggle(creatorDiscountExist);
9017        $pc_cart_result_container.find('._cart_creator_discount').text(creatorDiscountExist ? creatorDiscountText : '-');
9018        const cartSelectedCount = parseInt(data['count'], 10) || 0;
9019        const cartNoDeliveryCount = parseInt(data['no_deliv_count'], 10) || 0;
9020        if (cartSelectedCount === 0) {
9021            // 선택된 상품이 없을 때는 0원 합계의 기본 항목을 모두 노출한다.
9022            $cart_list_wrap.find('._cart_price_field').show();
9023            $cart_list_wrap.find('._cart_deliv_price_field').show();
9024            $cart_list_wrap.find('.im-ico-equals-plain').show();
9025            $mobile_cart_result_container.find('._plus_deliv_price').show();
9026        } else {
9027            $cart_list_wrap.find('._cart_deliv_price_field').toggle(cartSelectedCount !== cartNoDeliveryCount);
9028            if (data['membership_price_exist'] === false && mobileDiscountPriceExist === false && (cartSelectedCount === cartNoDeliveryCount)) {
9029                $cart_list_wrap.find('.im-ico-equals-plain').hide();
9030                $pc_cart_result_container.find('._cart_price_field').hide();
9031            } else {
9032                if (data['only_deliv_price_after']) {
9033                    $cart_list_wrap.find('._cart_deliv_price_field').show();
9034                    $cart_list_wrap.find('.im-ico-equals-plain').show();
9035                    $pc_cart_result_container.find('._cart_price_field').show();
9036                    $mobile_cart_result_container.find('._plus_deliv_price').hide();
9037                } else {
9038                    $cart_list_wrap.find('.im-ico-equals-plain').show();
9039                    $pc_cart_result_container.find('._cart_price_field').show();
9040                    $mobile_cart_result_container.find('._plus_deliv_price').show();
9041                }
9042            }
9043        }
9044
9045        // 총 주문금액 (수량별 할인 + 등급할인 보정 반영)
9046        const membershipAdjust = serverMembershipRaw - membershipDiscountRaw;
9047        const totalPriceRaw = Math.max(parseInt(data['total_price_raw'], 10) - _cartQuantityDiscountTotal + membershipAdjust, 0);
9048        $total_price.text(LOCALIZE.getCurrencyFormat(totalPriceRaw));
9049        $cart_list_wrap.find('._cart_result_table').show();
9050        setCartBenefitInfo(data);
9051
9052        // MFE 무료배송 안내 위젯(fo-shopping/free-ship-notify)의 게이지/임계값 계산용으로 숫자 총액을 브로드캐스트.
9053        // 배송 그룹이 분리된 경우에는 서버가 내려준 무료배송 안내 대상 그룹의 진행 금액만 사용한다.
9054        var totalPriceValue = !isNaN(adjustedFreeShipProgressPrice)
9055            ? adjustedFreeShipProgressPrice
9056            : (typeof data['total_price_raw'] === 'number' ? data['total_price_raw'] : 0);
9057        window.dispatchEvent(new CustomEvent('imweb:freeShipNotify:cartTotal:update', {
9058            detail: { totalPrice: totalPriceValue }
9059        }));
9060
9061        // 같은 시점에 prod_code 단위 카트 수량 스냅샷도 dispatch.
9062        // 추천 상품 카드(fo-shopping/free-ship-notify ProductCard)가 "+ 아이콘 ↔ 담긴 수량 숫자" 전환에 사용.
9063        // 같은 prod_code 의 옵션 라인이 여러 row 로 나뉠 수 있어 합산.
9064        var itemCounts = {};
9065        $cart_list_wrap.find('tr.content[data-prod-code]').each(function () {
9066            var prodCode = this.getAttribute('data-prod-code');
9067            if (!prodCode) return;
9068            var rowCount = 0;
9069            this.querySelectorAll('._counter_value').forEach(function (input) {
9070                var qty = parseInt(input.value, 10);
9071                if (Number.isFinite(qty)) rowCount += qty;
9072            });
9073            itemCounts[prodCode] = (itemCounts[prodCode] || 0) + rowCount;
9074        });
9075        window.dispatchEvent(new CustomEvent('imweb:freeShipNotify:cartItems:update', {
9076            detail: { counts: itemCounts }
9077        }));
9078    };
9079
9080    var setCartBenefitInfo = function (data) {
9081        var benefitInfo = data ? data['benefit_info'] : null;
9082        var $benefitItems = $cart_list_wrap.find('._cart_benefit_info');
9083        var $mobileCartResultContainer = $cart_list_wrap.find('tr._cart_result_container');
9084        var $pcCartResultContainer = $cart_list_wrap.find('._cart_result_table').find('._cart_result_container');
9085        var $mobileBenefitField = $mobileCartResultContainer.find('._cart_benefit_field');
9086        var $pcBenefitField = $pcCartResultContainer.find('._cart_benefit_field');
9087        var $benefitName = $cart_list_wrap.find('._cart_benefit_name');
9088        var $benefitPrice = $cart_list_wrap.find('._cart_benefit_price');
9089        var $pcBenefitLabel = $cart_list_wrap.find('._cart_benefit_pc_label');
9090        var $pcBenefitPrice = $cart_list_wrap.find('._cart_benefit_pc_price');
9091        var $pcBenefitSub = $cart_list_wrap.find('._cart_benefit_pc_sub');
9092        var $pcPriceDefault = $cart_list_wrap.find('._cart_price_default');
9093        var $mobileBenefitDiscountField = $cart_list_wrap.find('._cart_mobile_benefit_discount_field');
9094        var $mobileBenefitDiscountPrice = $cart_list_wrap.find('._cart_mobile_benefit_discount_price');
9095        var $mobileBenefitDetailField = $cart_list_wrap.find('._cart_mobile_benefit_detail_field');
9096        var $mobileBenefitDetailName = $cart_list_wrap.find('._cart_mobile_benefit_name');
9097        var $mobileBenefitDetailPrice = $cart_list_wrap.find('._cart_mobile_benefit_detail_price');
9098        var $mobileOrderHintField = $cart_list_wrap.find('._cart_mobile_order_hint_field');
9099        var $mobileOrderHint = $cart_list_wrap.find('._cart_mobile_order_hint');
9100        var $mobileOrderPriceOrigin = $cart_list_wrap.find('._cart_mobile_order_price_origin');
9101        var $pcPriceOrigin = $cart_list_wrap.find('._cart_price_origin');
9102
9103        $benefitItems.hide().text('');
9104        $mobileBenefitField.hide();
9105        $pcBenefitField.hide();
9106        $pcBenefitLabel.hide().text('');
9107        $pcBenefitPrice.hide().text('');
9108        $pcBenefitSub.hide().text('');
9109        $pcPriceDefault.show();
9110        $mobileBenefitDiscountField.hide();
9111        $mobileBenefitDiscountPrice.text('');
9112        $mobileBenefitDetailField.hide();
9113        $mobileBenefitDetailName.text('');
9114        $mobileBenefitDetailPrice.text('');
9115        $mobileOrderHintField.hide();
9116        $mobileOrderHint.text('');
9117        if ($mobileOrderPriceOrigin.length > 0) {
9118            $mobileOrderPriceOrigin.each(function () {
9119                var $origin = $(this);
9120                $origin.toggle($origin.data('default-visible') === 'Y');
9121            });
9122        }
9123        if ($pcPriceOrigin.length > 0) {
9124            $pcPriceOrigin.each(function () {
9125                var $origin = $(this);
9126                $origin.toggle($origin.data('default-visible') === 'Y');
9127            });
9128        }
9129
9130        // 첫구매 혜택 모달 description 서버 응답 기준으로 갱신 (모달 열릴 때 shadow DOM 재생성으로 반영)
9131        var $promotionModalShell = $('#_cart_promotion_modal_shell');
9132        if ($promotionModalShell.length > 0) {
9133            var modalDescription;
9134            if (!benefitInfo || benefitInfo['promotion_exists'] !== true) {
9135                modalDescription = '조건 충족 시 아래 상품 중 1개를<br/>첫 구매 혜택가에 구매할 수 있어요!';
9136            } else {
9137                var shortfallAmount = parseInt(benefitInfo['benefit_shortfall_amount'] || 0, 10) || 0;
9138                var shortfallQuantity = parseInt(benefitInfo['benefit_shortfall_quantity'] || 0, 10) || 0;
9139                if (shortfallAmount > 0) {
9140                    modalDescription = '<strong>' + LOCALIZE.getCurrencyFormat(shortfallAmount) + '</strong> 더 담으면 아래 상품 중 1개를<br/>첫 구매 혜택가에 구매할 수 있어요!';
9141                } else if (shortfallQuantity > 0) {
9142                    modalDescription = '<strong>' + shortfallQuantity.toLocaleString('ko-KR') + '개</strong> 더 담으면 아래 상품 중 1개를<br/>첫 구매 혜택가에 구매할 수 있어요!';
9143                } else {
9144                    modalDescription = '조건 충족 시 아래 상품 중 1개를<br/>첫 구매 혜택가에 구매할 수 있어요!';
9145                }
9146            }
9147            $promotionModalShell.attr('data-description', modalDescription);
9148        }
9149
9150        if (!benefitInfo || benefitInfo['promotion_exists'] !== true) {
9151            return;
9152        }
9153
9154        var benefitProducts = benefitInfo['benefit_products'] || [];
9155        var promotionName = benefitInfo['promotion_name'] || '';
9156        var isQualified = benefitInfo['is_qualified'] === true;
9157        var appliedItemCode = benefitInfo['applied_item_code'] || '';
9158        var benefitDiscountPrice = parseInt(benefitInfo['benefit_discount_price'] || 0, 10) || 0;
9159        var benefitDiscountText = benefitInfo['benefit_discount_price_text'] || '';
9160        if (benefitProducts.length === 0 || promotionName === '') {
9161            return;
9162        }
9163
9164        var benefitMap = {};
9165        $.each(benefitProducts, function (idx, benefitProduct) {
9166            if (!benefitProduct || !benefitProduct['prod_code']) {
9167                return;
9168            }
9169            benefitMap[benefitProduct['prod_code']] = benefitProduct;
9170        });
9171
9172        // 모든 row 가격을 base 기준으로 reset (FP 합성 분기 진입 전 초기화). QD는 헬퍼가 자동 차감.
9173        $cart_list_wrap.find('tr.content').each(function () {
9174            var $row = $(this);
9175            var $base = $row.find('._cart_price_default');
9176            if (!$base.length) $base = $row.find('._cart_mobile_order_price');
9177            var basePrice = parseInt($base.data('discounted-price'), 10);
9178            if (!isNaN(basePrice) && basePrice >= 0) {
9179                renderCartRowPrice($row, { basePrice: basePrice });
9180            }
9181        });
9182
9183        $benefitItems.each(function () {
9184            var $el = $(this);
9185            var itemCode = String($el.data('item-code'));
9186            var prodCode = $el.data('prod-code');
9187
9188            if ($.inArray(itemCode, selectedCartItem) === -1) {
9189                return;
9190            }
9191
9192            if (!benefitMap[prodCode]) {
9193                return;
9194            }
9195
9196            // 크리에이터 매칭 item 은 첫구매 BOGO row sub 영역 비노출 (회원할인과 일관)
9197            if (creator_cart_data && creator_cart_data.matched_items && creator_cart_data.matched_items[itemCode]) {
9198                return;
9199            }
9200
9201            var priceText = benefitMap[prodCode]['benefit_price_text'] || '';
9202            var $row = $cart_list_wrap.find('tr[data-item-code="' + itemCode + '"]');
9203            if ($row.length > 0) {
9204                if (isQualified && benefitMap[prodCode]['is_applied'] === true) {
9205                    if (appliedItemCode && itemCode !== appliedItemCode) {
9206                        return;
9207                    }
9208                    $row.find('._cart_benefit_pc_label').text('첫 구매 혜택').show();
9209                    var $pcOrderPrice = $row.find('._cart_price_default');
9210                    var basePriceForFp = parseInt($pcOrderPrice.data('discounted-price'), 10);
9211                    if (isNaN(basePriceForFp) || basePriceForFp < 0) {
9212                        basePriceForFp = parseInt($row.find('._cart_mobile_order_price').data('discounted-price'), 10);
9213                    }
9214                    var hasFpPrice = false;
9215                    if (!isNaN(basePriceForFp) && benefitDiscountPrice > 0) {
9216                        // 가격 표시는 헬퍼가 PC/mobile 동시 처리 (QD + FP 합성)
9217                        renderCartRowPrice($row, { basePrice: basePriceForFp, fpDiscount: benefitDiscountPrice });
9218                        hasFpPrice = true;
9219                    } else if (priceText) {
9220                        // 서버가 직접 표시 텍스트를 내려주는 fallback (할인 금액 없이 텍스트만)
9221                        $pcOrderPrice.find('._cart_price_default_value').text(priceText);
9222                        hasFpPrice = true;
9223                    }
9224
9225                    // PC 메타 (라벨/보조 가격/sub 영역)
9226                    $row.find('._cart_benefit_pc_price').hide().text('');
9227                    $row.find('._cart_price_default').show();
9228                    $row.find('._cart_benefit_pc_sub').hide().text('');
9229                    if (hasFpPrice) {
9230                        $row.find('._cart_price_origin').show();
9231                    }
9232
9233                    // Mobile 메타 (할인 합계, 혜택 명칭, 필드 토글) — 가격 자체는 위 헬퍼에서 처리됨
9234                    if (benefitDiscountPrice > 0 && benefitDiscountText) {
9235                        var periodDiscountPrice = 0;
9236                        var $mobilePeriodDiscountPrice = $row.find('._cart_mobile_period_discount_price');
9237                        if ($mobilePeriodDiscountPrice.length > 0) {
9238                            periodDiscountPrice = parseInt($mobilePeriodDiscountPrice.data('period-discount-price'), 10) || 0;
9239                            $row.find('._cart_mobile_period_discount_field').hide();
9240                        }
9241                        var totalDiscountPrice = periodDiscountPrice + benefitDiscountPrice;
9242                        $row.find('._cart_mobile_benefit_discount_price').text('- ' + LOCALIZE.getCurrencyFormat(totalDiscountPrice));
9243                        $row.find('._cart_mobile_benefit_detail_price').text('- ' + benefitDiscountText);
9244                        $row.find('._cart_mobile_benefit_name').text(promotionName);
9245                        $row.find('._cart_mobile_benefit_discount_field').show();
9246                        $row.find('._cart_mobile_benefit_detail_field').show();
9247                        $row.find('._cart_mobile_order_price_origin').show();
9248                    }
9249                } else if (!isQualified) {
9250                    if (priceText) {
9251                        var benefitLinkStyle = 'color:#0090D4;font-size:12px;font-weight:600;line-height:16px;display:inline-flex;align-items:center;';
9252                        var benefitInfoIcon = '<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" style="vertical-align:middle;margin-left:2px;"><g clip-path="url(#clip0_1166_73396)"><path d="M7.9987 14.6668C11.6806 14.6668 14.6654 11.6821 14.6654 8.00016C14.6654 4.31826 11.6806 1.3335 7.9987 1.3335C4.3168 1.3335 1.33203 4.31826 1.33203 8.00016C1.33203 11.6821 4.3168 14.6668 7.9987 14.6668Z" stroke="#717680" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/><path d="M8 11.3335H8.00667" stroke="#717680" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/><path d="M6.19922 6.12738C6.34708 5.70704 6.63894 5.3526 7.02309 5.12683C7.40724 4.90106 7.8589 4.81853 8.29807 4.89386C8.73724 4.96919 9.13558 5.19752 9.42254 5.5384C9.70949 5.87928 9.86655 6.31072 9.86588 6.75631C9.86588 8.01417 7.97909 8.6431 7.97909 8.6431V9.33337" stroke="#717680" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/></g><defs><clipPath id="clip0_1166_73396"><rect width="16" height="16" fill="white"/></clipPath></defs></svg>';
9253                        // view_component_promotion_cart (PTY02) 트래킹용 도메인 데이터
9254                        var cartCode = (typeof currentCartCode === 'string' && currentCartCode) ? currentCartCode : '';
9255                        var fpPromotionCode = $row.attr('data-fp-promotion-code') || '';
9256                        var prodType = $row.attr('data-prod-type') || 'normal';
9257                        var trackPromotionAttr = '';
9258                        if (fpPromotionCode) {
9259                            var trackDomain = {
9260                                prod_code: prodCode,
9261                                prod_type: prodType,
9262                                promotion_code: fpPromotionCode,
9263                                promotion_type: 'first_purchase',
9264                                promotion_product_role: 'target',
9265                                cart_code: cartCode,
9266                                cart_item_code: itemCode,
9267                            };
9268                            // attribute escape: 큰따옴표 attribute 안에 JSON 의 " 가 그대로 들어가면 attribute 가 깨지므로 & " 만 escape
9269                            var trackJson = JSON.stringify(trackDomain).replace(/&/g, '&amp;').replace(/"/g, '&quot;');
9270                            trackPromotionAttr = ' data-bs-promotion="' + trackJson + '"';
9271                        }
9272                        var pcBenefitLinkHtml = '<a href="#" onclick="SITE_SHOP_CART.openPromotionModal(); return false;" class="_cart_promotion_modal_link" style="' + benefitLinkStyle + '"' + trackPromotionAttr + '>첫 구매 혜택가 ' + priceText + benefitInfoIcon + '</a>';
9273                        var mobileBenefitLinkHtml = '<a href="#" onclick="SITE_SHOP_CART.openPromotionModal(); return false;" class="_cart_promotion_modal_link" style="' + benefitLinkStyle + '"' + trackPromotionAttr + '>첫 구매 혜택가 ' + priceText + benefitInfoIcon + '</a>';
9274                        $row.find('._cart_benefit_pc_sub').html(pcBenefitLinkHtml).show();
9275                        $row.find('._cart_mobile_order_hint').html(mobileBenefitLinkHtml);
9276                        $row.find('._cart_mobile_order_hint_field').show();
9277                    }
9278                }
9279            }
9280        });
9281
9282        var benefitDiscountText = benefitInfo['benefit_discount_price_text'] || '';
9283        if (isQualified && benefitDiscountText) {
9284            $benefitName.text(promotionName);
9285            $benefitPrice.text('- ' + benefitDiscountText);
9286            $mobileBenefitField.show();
9287        }
9288
9289    };
9290
9291    var openPromotionModal = function () {
9292        // shadow DOM은 최초 렌더 시 data-description을 1회만 읽으므로, promotion-modal 을 제거-재추가해 shadow DOM 을 재생성한다
9293        var $promotionModalShell = $('#_cart_promotion_modal_shell');
9294        if ($promotionModalShell.length > 0) {
9295            var shell = $promotionModalShell.get(0);
9296            if (shell) {
9297                var modalElement = shell.querySelector('promotion-modal');
9298                if (modalElement && modalElement.parentNode) {
9299                    modalElement.parentNode.removeChild(modalElement);
9300                    shell.appendChild(modalElement);
9301                }
9302            }
9303        }
9304        history.replaceState(null, '', '#promotion-modal');
9305        window.dispatchEvent(new HashChangeEvent('hashchange'));
9306    };
9307
9308    /**
9309     * 상품 위시리스트 추가 처리
9310     * @param cart_item_list
9311     */
9312    var addProdWish = function (cart_item_list, is_regularly) {
9313        $.ajax({
9314            type: 'POST',
9315            data: { 'type': 'add', 'cart_item_list': cart_item_list, 'is_regularly': is_regularly },
9316            url: ('/shop/add_prod_wish_cart.cm'),
9317            dataType: 'json',
9318            success: function (res) {
9319                if (res.msg == 'SUCCESS') {
9320                    for (var i = 0; i < cart_item_list.length; i++) {
9321                        if (typeof FB_PIXEL != 'undefined') FB_PIXEL.AddToWishlist();
9322                        $shop_cart_list.find("._wish_status_" + cart_item_list[i]).addClass('check');
9323                    }
9324                    loadProdWishList();
9325                } else
9326                    alert(res.msg);
9327            }
9328        });
9329    };
9330
9331    /**
9332     * 위시리스트 가져오기
9333     * @param prod_code
9334     */
9335    var loadProdWishList = function () {
9336			var is_design_mode = (location.pathname.indexOf('/admin/design') != -1);
9337        $.ajax({
9338            type: 'POST',
9339            data: { 'type': 'cart', 'is_design_mode': is_design_mode ? 'Y' : 'N' },
9340            url: ('/shop/get_prod_wish_list.cm'),
9341            dataType: 'json',
9342            success: function (res) {
9343                if (res.msg == 'SUCCESS') {
9344                    if (res.count > 0) {
9345                        $shop_cart_wish_list_empty.hide();
9346                        $shop_cart_wish_list.show().html(res.html);
9347                        setTimesale();
9348                    } else {
9349                        $shop_cart_wish_list_empty.show();
9350                        $shop_cart_wish_list.hide();
9351                    }
9352		                // 크리마 리뷰 사용 중일 시 위젯 호출
9353		                if(document.querySelector(".crema-product-reviews-score")){
9354			                if(typeof crema !== 'undefined' && typeof crema.run === 'function'){
9355				                crema.run();
9356			                }
9357		                }
9358                } else
9359                    alert(res.msg);
9360            }
9361        });
9362    };
9363
9364    /**
9365     * 위시리스트 제거
9366     * @param cart_item_list
9367     */
9368    var deleteProdWish = function (cart_item_list, is_regularly) {
9369        $.ajax({
9370            type: 'POST',
9371            data: { 'type': 'delete', 'cart_item_list': cart_item_list, 'is_regularly': is_regularly },
9372            url: ('/shop/add_prod_wish_cart.cm'),
9373            dataType: 'json',
9374            success: function (res) {
9375                if (res.msg == 'SUCCESS') {
9376                    for (var i = 0; i < cart_item_list.length; i++) {
9377                        $shop_cart_list.find("._wish_status_" + cart_item_list[i]).removeClass('check');
9378                    }
9379                    loadProdWishList();
9380                } else
9381                    alert(res.msg);
9382            }
9383        });
9384    };
9385
9386    /**
9387     * 위시리스트 제거 (상품코드사용
9388     * @param prod_code
9389     */
9390    var deleteProdWishByProdCode = function (prod_code) {
9391        $.ajax({
9392            type: 'POST',
9393            data: { 'type': 'delete_prodcode', 'prod_code': prod_code },
9394            url: ('/shop/add_prod_wish_cart.cm'),
9395            dataType: 'json',
9396            success: function (res) {
9397                if (res.msg == 'SUCCESS') {
9398                    if ($('#wish-item-' + prod_code).length > 0) {
9399                        $('#wish-item-' + prod_code).remove();
9400                    } else {
9401                        window.location.reload();
9402                    }
9403                } else
9404                    alert(res.msg);
9405            }
9406        });
9407    };
9408
9409    /**
9410     * 묶음배송 그룹에서 상품 행 삭제 전 rowspan/모바일 배송정보 행 보정
9411     */
9412    const _adjustBundleOnRemove = ($row) => {
9413        const $rowspanTd = $row.find('td[rowspan]');
9414        let $bundleFirstRow = null;
9415
9416        if ($rowspanTd.length) {
9417            const rowspan = parseInt($rowspanTd.attr('rowspan'), 10);
9418            if (rowspan > 1) {
9419                // 첫 번째 행 삭제: 배송비 td를 다음 행으로 이동
9420                const $next = $row.nextAll('tr.content').first();
9421                if ($next.length) {
9422                    $rowspanTd.attr('rowspan', rowspan - 1);
9423                    $next.append($rowspanTd);
9424                    $bundleFirstRow = $next;
9425                }
9426            }
9427        } else {
9428            // 첫 번째가 아닌 행 삭제: 그룹 첫 행의 rowspan 감소
9429            $row.prevAll('tr.content').each(function () {
9430                const $td = $(this).find('td[rowspan]');
9431                if ($td.length) {
9432                    const rs = parseInt($td.attr('rowspan'), 10);
9433                    if (rs > 1) $td.attr('rowspan', rs - 1);
9434                    $bundleFirstRow = $(this);
9435                    return false;
9436                }
9437            });
9438        }
9439
9440        // 모바일 묶음배송 정보 행: 삭제 후 남은 상품이 1개 이하면 숨김
9441        if ($bundleFirstRow && $bundleFirstRow.length) {
9442            const newRowspan = parseInt($bundleFirstRow.find('td[rowspan]').attr('rowspan'), 10) || 1;
9443            if (newRowspan <= 1) {
9444                let $cur = $bundleFirstRow;
9445                while ($cur.length) {
9446                    $cur = $cur.next('tr');
9447                    if ($cur.hasClass('im-tr-shipping')) { $cur.addClass('hide'); break; }
9448                    if (!$cur.hasClass('content')) break;
9449                }
9450            }
9451        }
9452    };
9453
9454    /**
9455     * click_remove_item_cart 트래킹 (서버 응답 200 + cart 화면 반영 직후)
9456     * @param {('option'|'product')} unit
9457     * @param {{prodCode, prodType, quantityBefore}} ctx
9458     * @param {string|null} cartItemCode option 삭제 시 채움. product 삭제 시 null
9459     * @param {string|null} optionInfo option 삭제 시 채움. product 삭제 시 null
9460     */
9461    const _trackRemoveItemCart = (unit, ctx, cartItemCode, optionInfo) => {
9462        if (typeof window.BrandScope === 'undefined' || !ctx) return;
9463        const cartCode = (typeof currentCartCode === 'string' && currentCartCode) ? currentCartCode : '';
9464        window.BrandScope.track('click_remove_item_cart', {
9465            action:          'click',
9466            content:         'remove_item',
9467            where:           'cart',
9468            prod_code:       ctx.prodCode || '',
9469            prod_type:       ctx.prodType || 'normal',
9470            cart_code:       cartCode,
9471            cart_item_code:  cartItemCode,
9472            option_info:     optionInfo,
9473            quantity_unit:   unit,
9474            quantity_before: ctx.quantityBefore,
9475            quantity_after:  0,
9476        });
9477    };
9478
9479    /**
9480     * 특정 카트 아이템 삭제
9481     */
9482    const removeCartItem = (item_codeList, is_regularly) => {
9483        // 트래킹 데이터 사전 캡처 (success 시 row 제거되므로 prod_code/prod_type/quantity_before 가 사라짐)
9484        const trackingContexts = item_codeList.map((code) => {
9485            const $row = $cart_list_wrap.find(`tr[data-item-code="${code}"]`);
9486            if (!$row.length) return null;
9487            // 상품 단위 총 수량 = 행 내 모든 _counter_value 합산 (옵션 라인 + product_counter)
9488            const totalCount = $row.find('._counter_value').toArray()
9489                .reduce((sum, el) => sum + (parseInt(el.value, 10) || 0), 0);
9490            return {
9491                prodCode:       $row.attr('data-prod-code') || '',
9492                prodType:       $row.attr('data-prod-type') || 'normal',
9493                quantityBefore: totalCount,
9494            };
9495        });
9496
9497        $.ajax({
9498            type: 'POST',
9499            data: { item_codeList, is_regularly },
9500            url: '/shop/remove_cart_item.cm',
9501            dataType: 'json',
9502            cache: false,
9503            success: ({ msg, delete_cart_info }) => {
9504                if (msg !== 'SUCCESS') return alert(msg);
9505
9506                item_codeList.forEach((item_code) => {
9507                    const info = delete_cart_info[item_code];
9508                    if (typeof AW_DEL !== 'undefined') AW_DEL(info['prod_no'], info['count']);
9509                    if (typeof AM_DEL !== 'undefined') AM_DEL(info['prod_no'], info['count']);
9510                });
9511                if (typeof CHANNEL_PLUGIN !== 'undefined') CHANNEL_PLUGIN.updateChannelProfileAttr('cart');
9512
9513                // 페이지 리로드 대신 DOM 요소 제거
9514                const $rows = item_codeList
9515                    .map((code) => $cart_list_wrap.find(`tr[data-item-code="${code}"]`))
9516                    .filter(($el) => $el.length);
9517
9518                if (!$rows.length) return window.location.reload();
9519
9520                $rows.forEach(($row) => {
9521                    _adjustBundleOnRemove($row);
9522                    $row.remove();
9523                });
9524
9525                // click_remove_item_cart 트래킹 (휴지통: product 단위 삭제. cart_item_code/option_info 는 null)
9526                trackingContexts.forEach((ctx) => _trackRemoveItemCart('product', ctx, null, null));
9527
9528                // 장바구니가 비었으면 리로드하여 빈 상태 UI 표시
9529                if (!$cart_list_wrap.find('._cartItemCheckbox').length) return window.location.reload();
9530
9531                CartItemChanged(is_regularly === 'Y' ? 'Y' : 'N');
9532            }
9533        });
9534    };
9535
9536    /**
9537     * 단일 상품을 카트에 담기.
9538     * 무료 배송 안내 SDK 등 카트 페이지 컨텍스트에서 사용한다.
9539     *
9540     * 오버로드:
9541     *   addCart(prodIdx, callback)                                            // 옵션 없음 + 수량 1
9542     *   addCart(prodIdx, options, orderCount, callback, requestOptions)       // 옵션/수량/요청 옵션 지정
9543     *
9544     * @param {number} prodIdx 추가할 상품 idx
9545     * @param {Array|function} optionsOrCallback 옵션 배열 또는 콜백 (오버로드 분기)
9546     * @param {number} [orderCount] 주문 수량 (기본 1)
9547     * @param {function(boolean,string,object=):void} [callback] 완료 콜백 (성공 여부, 메시지, add_cart.cm 응답)
9548     * @param {object} [requestOptions] add_cart.cm 추가 요청 옵션
9549     */
9550    const addCart = (prodIdx, optionsOrCallback, orderCount, callback, requestOptions) => {
9551        // 오버로드 분기: 두 번째 인자가 function이면 (prodIdx, callback) 호출로 간주
9552        var options = [];
9553        var count = 1;
9554        var cb = null;
9555        if (typeof optionsOrCallback === 'function') {
9556            cb = optionsOrCallback;
9557        } else {
9558            options = Array.isArray(optionsOrCallback) ? optionsOrCallback : [];
9559            count = (typeof orderCount === 'number' && orderCount > 0) ? orderCount : 1;
9560            cb = (typeof callback === 'function') ? callback : null;
9561        }
9562        var cartRequestOptions = (requestOptions && typeof requestOptions === 'object') ? requestOptions : {};
9563        var shippingTemplateCode = typeof cartRequestOptions.shipping_template_code === 'string'
9564            ? cartRequestOptions.shipping_template_code
9565            : '';
9566        var requestCartType = typeof cartRequestOptions.cart_type === 'string'
9567            ? cartRequestOptions.cart_type
9568            : '';
9569        if (requestCartType !== SHOP_CONST.CART_TYPE_NORMAL && requestCartType !== SHOP_CONST.CART_TYPE_REGULARLY) {
9570            requestCartType = '';
9571        }
9572        var effectiveCartType = requestCartType || SHOP_CONST.CART_TYPE_NORMAL;
9573
9574        $.ajax({
9575            type: 'POST',
9576            url: '/shop/add_cart.cm',
9577            dataType: 'json',
9578            data: {
9579                prodIdx: prodIdx,
9580                options: options,
9581                orderCount: count,
9582                deliv_type: '',
9583                deliv_pay_type: '',
9584                cart_type: effectiveCartType,
9585                prod_additional_list: [],
9586                prod_additional_option_list: [],
9587                shipping_template_code: shippingTemplateCode
9588            },
9589            success: (result) => {
9590                if (result.msg !== 'SUCCESS') {
9591                    if (cb) cb(false, result.msg);
9592                    return;
9593                }
9594
9595                // 전환추적 (SITE_SHOP_DETAIL.addCart 와 동일 배터리)
9596                if (typeof NP_LOG !== 'undefined') NP_LOG.AddToCart();
9597                if (typeof NP_NDA !== 'undefined') NP_NDA.AddToCart(result.prod_code);
9598                if (typeof CRITEO !== 'undefined') CRITEO.AddToCart(result.prod_id, result.total_price);
9599                if (Object.prototype.hasOwnProperty.call(result, 'meta_compatibility')) {
9600                    // 구 provider에는 송신을 위임하지 않는다. 대상 응답이 잘못되어도 미송신을 유지한다.
9601                    try {
9602                        if (typeof FB_PIXEL !== 'undefined' && FB_PIXEL && typeof FB_PIXEL.AddToCartDryRun === 'function') {
9603                            FB_PIXEL.AddToCartDryRun(result.prod_id, result.prod_name, result.cart_price, result.currency, result.prod_count, result.total_price, result.fb_event_id, result.fb_external_id, result.meta_compatibility);
9604                        } else if (typeof console !== 'undefined' && typeof console.info === 'function') {
9605                            console.info('[FS-3585][META_EVENT_DRY_RUN]', {feature_key: 'FS-3585', mode: 'dry_run', provider: 'meta', event_name: 'AddToCart', disposition: 'provider_unsupported'});
9606                        }
9607                    } catch (e) { /* 관측 오류와 무관하게 후속 UI 및 다른 광고 채널을 계속 실행한다. */ }
9608                } else {
9609                    if (typeof FB_PIXEL !== 'undefined') FB_PIXEL.AddToCart(result.prod_id, result.prod_name, result.cart_price, result.currency, result.prod_count, result.total_price, result.fb_event_id, result.fb_external_id);
9610                }
9611                if (typeof ACE_COUNTER !== 'undefined') ACE_COUNTER.AddToCart(result.prod_id, result.prod_name, result.prod_count);
9612                if (typeof CHANNEL_PLUGIN !== 'undefined') CHANNEL_PLUGIN.AddToCart(result.prod_id, result.prod_count, result.total_price, result.currency);
9613                if (typeof ACE_COUNTER_PARTNER !== 'undefined') ACE_COUNTER_PARTNER.AddToCart(result.prod_id, result.prod_count, result.check_quantity);
9614                if (typeof AW_PRODUCT !== 'undefined') AW_PRODUCT(result.prod_count);
9615                if (typeof AM_PRODUCT !== 'undefined') AM_PRODUCT(result.prod_count);
9616                if (typeof TIKTOK_PIXEL !== 'undefined') {
9617                    TIKTOK_PIXEL.track('AddToCart', {
9618                        contents: [{
9619                            content_id: result.prod_id,
9620                            content_name: result.prod_name,
9621                            quantity: result.prod_count,
9622                            price: result.cart_price
9623                        }],
9624                        event_id: result.tiktok_event_id,
9625                        content_type: 'product',
9626                        value: result.total_price,
9627                        currency: result.currency
9628                    });
9629                }
9630                if (typeof CRM_ONSITE !== 'undefined') {
9631                    CRM_ONSITE.track({ name: 'ADD_TO_CART' });
9632                }
9633
9634                // 카트 리스트 부분 갱신 (전체 페이지 reload 대신 카트 영역만 재렌더링).
9635                // 단, 카트 페이지가 아닌 컨텍스트(상세 페이지의 장바구니 담기 완료 모달 내 매그넷 등)에서
9636                // SITE_SHOP_CART.addCart가 호출될 수 있는데, 이 경우 카트 DOM($cart_list_wrap)이 존재하지 않으므로 스킵한다.
9637                // initCart가 호출된 카트 페이지에서만 $cart_list_wrap이 셋업되어 있다.
9638                if (typeof $cart_list_wrap !== 'undefined' && $cart_list_wrap && $cart_list_wrap.length > 0) {
9639                    // addCart 흐름은 항상 C3/OMS 컨텍스트이므로 is_c3=true 로 고정
9640                    cartListMake(current_backurl, effectiveCartType, true);
9641                } else {
9642                    // 상세 페이지 컨텍스트: 카트 DOM이 없으므로 cartListMake 대신
9643                    // 무료배송 안내 모달용 누적 총액을 갱신하여 MFE 위젯에 imweb:freeShipNotify:cartTotal:update 이벤트 dispatch.
9644                    // 베이스라인은 SITE_SHOP_DETAIL.addCart 성공 시 메인 상품 가격으로 세팅된 window.__imwebFreeShipCartCumulative.
9645                    if (typeof window.__imwebFreeShipCartCumulative !== 'number') {
9646                        window.__imwebFreeShipCartCumulative = 0;
9647                    }
9648                    if (typeof window.__imwebFreeShipCartAdditionalsSum !== 'number') {
9649                        window.__imwebFreeShipCartAdditionalsSum = 0;
9650                    }
9651                    if (typeof window.__imwebFreeShipCartItemCounts !== 'object' || window.__imwebFreeShipCartItemCounts === null) {
9652                        window.__imwebFreeShipCartItemCounts = {};
9653                    }
9654
9655                    var freeShipNotifyCartPrice = (typeof result.free_shipping_price === 'number' ? result.free_shipping_price : (typeof result.total_price === 'number' ? result.total_price : 0));
9656                    window.__imwebFreeShipCartCumulative += freeShipNotifyCartPrice;
9657                    window.__imwebFreeShipCartAdditionalsSum += freeShipNotifyCartPrice;
9658
9659                    if (typeof result.prod_code === 'string' && result.prod_code !== '') {
9660                        window.__imwebFreeShipCartItemCounts[result.prod_code] =
9661                            (window.__imwebFreeShipCartItemCounts[result.prod_code] || 0) + 1;
9662
9663                        if (!Array.isArray(window.__imwebAddToCartProdCodeList)) {
9664                            window.__imwebAddToCartProdCodeList = [];
9665                        }
9666                        window.__imwebAddToCartProdCodeList.push(result.prod_code);
9667                        window.__imwebAddToCartHasFreeShipUpsell = true;
9668                    }
9669
9670                    window.dispatchEvent(new CustomEvent('imweb:freeShipNotify:cartTotal:update', {
9671                        detail: { totalPrice: window.__imwebFreeShipCartCumulative }
9672                    }));
9673
9674                    var markerEl = document.getElementById('shop_detail_add_cart_alarm_with_free_ship_notify');
9675                    if (markerEl
9676                        && typeof window.IMWEB_SHOP_FREE_SHIP_NOTIFY_SDK !== 'undefined'
9677                        && typeof window.IMWEB_SHOP_FREE_SHIP_NOTIFY_SDK.setCartFreeShippingRemainingPrice === 'function') {
9678                        var initRemaining = parseFloat(markerEl.getAttribute('data-init-deliv-price-flexable-key') || '0');
9679                        var newRemaining = Math.max(0, initRemaining - window.__imwebFreeShipCartCumulative);
9680                        window.IMWEB_SHOP_FREE_SHIP_NOTIFY_SDK.setCartFreeShippingRemainingPrice(newRemaining);
9681                    }
9682
9683                    window.dispatchEvent(new CustomEvent('imweb:freeShipNotify:cartItems:update', {
9684                        detail: { counts: Object.assign({}, window.__imwebFreeShipCartItemCounts) }
9685                    }));
9686                }
9687
9688                if (cb) cb(true, 'SUCCESS', result);
9689            },
9690            error: () => {
9691                if (cb) cb(false, '상품 담기 중 오류가 발생했습니다.');
9692            }
9693        });
9694    };
9695
9696    /**
9697     *  품절된 상품만 삭제
9698     * @param item_codeList
9699     * @param is_regularly
9700     */
9701    const removeSelectedCartSoldOutItem = (item_codeList, is_regularly) => {
9702        $.ajax({
9703            type: 'POST',
9704            data: { item_codeList, is_regularly },
9705            url: '/shop/remove_cart_soldout_item.cm',
9706            dataType: 'json',
9707            cache: false,
9708            success: ({ msg, delete_cart_info }) => {
9709                if (msg !== 'SUCCESS') return alert(msg);
9710
9711                item_codeList.forEach((item_code) => {
9712                    const info = delete_cart_info[item_code];
9713                    if (info) {
9714                        if (typeof AW_DEL !== 'undefined') AW_DEL(info['prod_no'], info['count']);
9715                        if (typeof AM_DEL !== 'undefined') AM_DEL(info['prod_no'], info['count']);
9716                    }
9717                });
9718                if (typeof CHANNEL_PLUGIN !== 'undefined') CHANNEL_PLUGIN.updateChannelProfileAttr('cart');
9719
9720                // 페이지 리로드 대신 DOM 요소 제거
9721                const deletedCodes = Object.keys(delete_cart_info || {});
9722                const $rows = deletedCodes
9723                    .map((code) => $cart_list_wrap.find(`tr[data-item-code="${code}"]`))
9724                    .filter(($el) => $el.length);
9725
9726                if (!$rows.length) return window.location.reload();
9727
9728                $rows.forEach(($row) => {
9729                    _adjustBundleOnRemove($row);
9730                    $row.remove();
9731                });
9732
9733                // 장바구니가 비었으면 리로드하여 빈 상태 UI 표시
9734                if (!$cart_list_wrap.find('._cartItemCheckbox').length) return window.location.reload();
9735
9736                CartItemChanged(is_regularly === 'Y' ? 'Y' : 'N');
9737            }
9738        });
9739    };
9740
9741    /**
9742     * 특정 카트 아이템 옵션 삭제
9743     */
9744    const removeCartItemOption = (item_code, optionNo, is_regularly) => {
9745        // 트래킹 데이터 사전 캡처 (success 시 $card 제거되므로)
9746        const $cardBefore = $cart_list_wrap.find(`._cart_option_card[data-item-code="${item_code}"][data-option-no="${optionNo}"]`);
9747        const $rowBefore = $cardBefore.closest(`tr[data-item-code="${item_code}"]`);
9748        const trackingCtx = $cardBefore.length ? {
9749            prodCode:       $rowBefore.attr('data-prod-code') || '',
9750            prodType:       $rowBefore.attr('data-prod-type') || 'normal',
9751            quantityBefore: parseInt($cardBefore.find('._counter_value').val(), 10) || 0,
9752        } : null;
9753        let trackingOptionInfo = null;
9754        if ($cardBefore.length) {
9755            const parts = [];
9756            $cardBefore.find('.opt-name').each(function () {
9757                const text = $(this).parent().text().replace(/\s+/g, ' ').trim();
9758                if (text) parts.push(text);
9759            });
9760            if (parts.length) trackingOptionInfo = parts.join(' / ');
9761        }
9762
9763        $.ajax({
9764            type: 'POST',
9765            data: { item_code, optionNo, is_regularly },
9766            url: '/shop/remove_cart_item_option.cm',
9767            dataType: 'json',
9768            cache: false,
9769            success: ({ msg }) => {
9770                if (msg !== 'SUCCESS') return alert(msg);
9771
9772                const $card = $cart_list_wrap.find(`._cart_option_card[data-item-code="${item_code}"][data-option-no="${optionNo}"]`);
9773                if (!$card.length) return window.location.reload();
9774
9775                const $row = $card.closest(`tr[data-item-code="${item_code}"]`);
9776
9777                // 삭제되는 옵션의 가격을 행 가격에서 차감
9778                const optCount = parseInt($card.find('._counter_value').val(), 10) || 0;
9779                const $optPrice = $card.find('._cart_option_price');
9780                const $optOrigin = $card.find('._cart_option_origin_price');
9781                const discountedUnit = parseInt($optPrice.data('unit-price'), 10) || 0;
9782                const originUnit = $optOrigin.length ? (parseInt($optOrigin.data('unit-price'), 10) || 0) : discountedUnit;
9783                const discountedDelta = discountedUnit * optCount;
9784                const originDelta = originUnit * optCount;
9785
9786                // 가격 갱신 (PC/mobile 헬퍼 통합)
9787                const $pcDefault = $row.find('._cart_price_default');
9788                const $pcOrigin = $row.find('._cart_price_origin');
9789                const newDiscounted = (parseInt($pcDefault.data('discounted-price'), 10) || 0) - discountedDelta;
9790                const newOrigin = (parseInt($pcOrigin.data('origin-price'), 10) || 0) - originDelta;
9791                renderCartRowPrice($row, { basePrice: newDiscounted, originPrice: newOrigin });
9792
9793                // 타이틀 행 아이템 가격(strikethrough 영역) 별도 갱신
9794                $row.find('._cart_item_price').text(LOCALIZE.getCurrencyFormat(newOrigin));
9795
9796                $card.remove();
9797                // 옵션 삭제 후 남은 카드의 option-no를 재할당 (서버 인덱스와 동기화)
9798                $row.find('._cart_option_card').each(function (i) {
9799                    $(this).attr('data-option-no', i);
9800                    $(this).find('._cart_counter').data('optionNo', i).attr('data-option-no', i);
9801                });
9802                // 옵션 카드가 없어도 본품 카운터가 있으면 행 유지
9803                const hasProductCounter = $row.find('._cart_counter[data-mode="product_count"]').length > 0;
9804                if (!$row.find('._cart_option_card').length && !hasProductCounter) $row.remove();
9805                CartItemChanged(is_regularly);
9806                // click_remove_item_cart 트래킹 (옵션 X 버튼: option 단위 삭제)
9807                _trackRemoveItemCart('option', trackingCtx, item_code, trackingOptionInfo);
9808            }
9809        });
9810    };
9811
9812    /**
9813     * 체크박스 전체 선택 제어
9814     * @param bool 
9814b
9815     */
9816    var toggleAllCheckCartItem = function (b, is_regularly) {
9817        $cart_list_wrap.find('._cartItemCheckbox').each(function () {
9818            $(this).prop('checked', (b === true));
9819        });
9820        CartItemChanged(is_regularly);
9821    };
9822
9823    /**
9824     * 체크박스 선택 제어
9825     */
9826    var toggleCheckCartItem = function (is_regularly) {
9827        var $cartItemCheckbox = $cart_list_wrap.find('._cartItemCheckbox');
9828        var $cartItemCheckboxSeleted = $cart_list_wrap.find('._cartItemCheckbox:checked');
9829        $cart_list_wrap.find('._all_check').prop('checked', ($cartItemCheckbox.length == $cartItemCheckboxSeleted.length));
9830        CartItemChanged(is_regularly);
9831    };
9832
9833    /**
9834     * 체크박스 변경시 재계산
9835     */
9836    var CartItemChanged = function (is_regularly) {
9837        // 초기화
9838        selectedCartItem = [];
9839        $cart_list_wrap.find('._cartItemCheckbox:checked').each(function () {
9840            selectedCartItem.push($(this).val());
9841        });
9842        getSelectedPrice(is_regularly);
9843    };
9844
9845    /**
9846     * 전체 선택인지 아닌지 체크
9847     */
9848    var toggleAllSelectCk = function () {
9849        var $cartItemSelectCount = 0;
9850        $cartItemCheckboxList = $shop_cart_list.find("input._cartItemCheckbox");
9851        $cartAllCheckBox = $shop_cart_list.find("input._all_check");
9852        $cartItemCheckboxList.each(function () {
9853            if ($(this).is(":checked") === true) {
9854                $cartItemSelectCount++;
9855            }
9856        });
9857        if ($cartItemCheckboxList.length == $cartItemSelectCount) {
9858            $cartAllCheckBox.prop('checked', true);
9859        } else {
9860            $cartAllCheckBox.prop('checked', false);
9861        }
9862    };
9863
9864    /**
9865     * 카트 전체 주문하기, C3 분기 추가
9866     * @param direct true/false (선택유무와 상관없이 바로 주문)
9867		 * TODO: 실행시 유저가 받을 사은품을 전달받아 페이로드에 담도록 한다.
9868     */
9869    /**
9870     * CB-3985 — 주문 생성 응답으로 머천트 전환/마케팅 픽셀 발화 (단일 소스).
9871     *
9872     * addOrderWithCart 콜백(npay / talkpay onPayOrder / default)의 픽셀 블록을 함수로 추출.
9873     * 레거시 장바구니 주문(바로구매 포함)과 MFE 장바구니(SITE_SHOP_CART.OMS_fireOrderTracking 노출)가
9874     * 동일 코드를 호출해 멱등성을 보장한다 — 픽셀 로직을 MFE 가 재구현하지 않는다.
9875     *
9876     * - 각 SDK 는 머천트 설정에 따라 주입되므로 typeof 가드 필수 (미설정 머천트는 자동 skip).
9877     * - npay 성인인증(auth_type==2)은 레거시와 동일하게 픽셀 미발화.
9878     * - npay 추가배송비 안내 / adult_auth redirect / advanced_kakao_trace 등 부수 흐름은 호출자 책임(범위 밖).
9879     */
9880    var fireOrderTracking = function (type, result) {
9881        // CB-3985 — 픽셀 SDK(머천트 설치)에서 예외가 나도 주문 redirect 등 후속 로직을 끊지 않도록 차단.
9882        try {
9883        if (type === 'npay') {
9884            if (result.auth_type == 2) return;
9885            var npay_order_info = result['npay_order_info'];
9886            if (typeof FB_PIXEL != 'undefined') {
9887                FB_PIXEL.InitiateCheckout(result.ic_event_id, result.total_price, result.currency, result.fb_external_id);
9888                if (result.fb_npay_switch === 'Y') FB_PIXEL.addNpayOrder(npay_order_info);
9889            }
9890            if (typeof CHANNEL_PLUGIN != 'undefined') CHANNEL_PLUGIN.AddOrder();
9891            if (result.google_analytics_type == 'G' && result.is_ga_api_secret === false) {
9892                if (typeof GOOGLE_ANAUYTICS != 'undefined') GOOGLE_ANAUYTICS.addNpayOrder(npay_order_info);
9893            }
9894            if (typeof GOOGLE_ADWORDS_TRACE != 'undefined' && result.google_ads_include_npay === 'Y') GOOGLE_ADWORDS_TRACE.addNpayOrder(npay_order_info);
9895            if (typeof CRITEO != 'undefined') CRITEO.npayTrackTransaction(npay_order_info);
9896            if (typeof NP_NDA != 'undefined') NP_NDA.BeginCheckout(result.prod_code_list);
9897            if (typeof TIKTOK_PIXEL != 'undefined') {
9898                TIKTOK_PIXEL.track('InitiateCheckout', {
9899                    event_id: result.order_code,
9900                    contents: result.prod_list.map(prod => ({
9901                        content_id: prod.id,
9902                        content_name: prod.name,
9903                        price: prod.price,
9904                    })),
9905                    content_type: 'product',
9906                    value: result.total_price,
9907                    currency: result.currency,
9908                });
9909            }
9910        } else {
9911            // normal / regularly / talkpay onPayOrder — 일반 주문 픽셀.
9912            if (typeof FB_PIXEL != 'undefined') FB_PIXEL.InitiateCheckout(result.ic_event_id, result.total_price, result.currency, result.fb_external_id);
9913            if (typeof CHANNEL_PLUGIN != 'undefined') CHANNEL_PLUGIN.AddOrder();
9914            if (typeof NP_NDA != 'undefined') NP_NDA.BeginCheckout(result.prod_code_list);
9915            if (typeof TIKTOK_PIXEL != 'undefined') {
9916                TIKTOK_PIXEL.track('InitiateCheckout', {
9917                    event_id: result.order_code,
9918                    contents: result.prod_list.map(prod => ({
9919                        content_id: prod.id,
9920                        content_name: prod.name,
9921                        price: prod.price,
9922                    })),
9923                    content_type: 'product',
9924                    value: result.total_price,
9925                    currency: result.currency,
9926                });
9927            }
9928        }
9929        } catch (e) {
9930            // 픽셀 발화 실패는 무시 (주문 흐름 보호).
9931        }
9932    };
9933
9934    var addOrderWithCart = async function (type, item_code, backurl, direct, is_oms, params, is_gift_buy, skipFreeShipNotifyAlarm){
9935			if (is_cart_changed) return false;
9936			if (add_order_progress_check) return false;
9937			if (currentCartCode == '') {
9938				alert(LOCALIZE.설명_장바구니가비어있습니다());
9939				return false;
9940			}
9941
9942			//type 이 guest_login 으로 넘어오는경우 비회원주문+로그인페이지로이동후주문 방식임 type 을 normal 로처리해야 다른 로직에 영향이 없어 이렇게 처리함
9943			var is_guest_login = false;
9944			if (type == 'guest_login') {
9945				is_guest_login = true;
9946				type = 'normal';
9947			}
9948
9949			var item_code_list = [];
9950			if (item_code == '')
9951				item_code_list = selectedCartItem;
9952			else
9953				item_code_list = [item_code];
9954
9955			if (item_code_list.length <= 0) {
9956				alert(LOCALIZE.설명_주문하실상품을선택해주세요());
9957				return false;
9958			}
9959
9960			// 배송 국가 선택 유효성 검사
9961			if ($global_select && $global_select.children().length > 0 && (!$deliv_country || $deliv_country === 'none')) {
9962				$('._deliv_country_selector').addClass('warning_select');
9963				alert(typeof LOCALIZE.설명_배송받을국가를선택해주세요 === 'function' ? LOCALIZE.설명_배송받을국가를선택해주세요() : '배송받을 국가를 선택해주세요.');
9964				return false;
9965			}
9966
9967			// 무료배송 안내 모달 - 주문 진행 전 사용자에게 노출 (마커가 DOM에 있을 때만)
9968			// 페이지뷰 1회만 노출하기 위해 SDK의 가드를 사용한다. 다른 페이지를 다녀오면 SDK 클로저가 리셋되어 다시 노출됨.
9969			var freeShipNotifySdk = window.IMWEB_SHOP_FREE_SHIP_NOTIFY_SDK;
9970			if (!skipFreeShipNotifyAlarm && freeShipNotifySdk && !freeShipNotifySdk.hasOrderAlarmShown()
9971				&& ($('#shop_cart_list ._global_select').length === 0 || $deliv_country === 'KR')) {
9972				var $orderAlarmMarker = $('#shop_cart_order_alarm_with_free_ship_notify');
9973				if ($orderAlarmMarker.length > 0) {
9974					var initDelivPriceFlexableKey = parseFloat($orderAlarmMarker.attr('data-init-deliv-price-flexable-key') || '0');
9975					if (initDelivPriceFlexableKey > 0) {
9976						freeShipNotifySdk.markOrderAlarmShown();
9977						_pendingOrderWithCart = {
9978							type: type,
9979							item_code: item_code,
9980							backurl: backurl,
9981							direct: direct,
9982							is_oms: is_oms,
9983							params: params,
9984							is_gift_buy: is_gift_buy
9985						};
9986						$.ajax({
9987							type: 'POST',
9988							url: '/shop/free_ship_notify/get_order_alarm_modal.cm',
9989							data: { init_deliv_price_flexable_key: initDelivPriceFlexableKey },
9990							dataType: 'html',
9991							cache: false,
9992							success: function (html) {
9993								// SITE_SHOP_DETAIL.is_mobile_width와 동일 기준 (window.innerWidth < 768)
9994								var is_mobile_width = (window.innerWidth < 768);
9995								if (is_mobile_width) {
9996									imSheet.close('shop_cart_order_alarm_sheet', function () {
9997										imSheet.open({
9998											id: 'shop_cart_order_alarm_sheet',
9999											html: html,
10000											backdrop: 'rgba(0, 0, 0, 0.15)',
10001											zIndex: 17001
10002										});
10003									});
10004								} else {
10005									if (typeof $.cocoaDialog === 'object' && typeof $.cocoaDialog.close === 'function') {
10006										$.cocoaDialog.close();
10007									}
10008									$.cocoaDialog.open({
10009										type: 'site_shop_free_ship_notify',
10010										custom_popup: html,
10011										pc_width: 440
10012									});
10013								}
10014							},
10015							error: function () {
10016								// fetch 실패 시 보류 인자 정리하고 모달 없이 주문 진행
10017								_pendingOrderWithCart = null;
10018								addOrderWithCart(type, item_code, backurl, direct, is_oms, params, is_gift_buy, true);
10019							}
10020						});
10021						return false;
10022					}
10023				}
10024			}
10025
10026			if (is_oms) {
10027				const $cartContainer = $shop_cart_list || $('#shop_cart_list');
10028				const $cartTable = $cartContainer.find('._shop_table').first();
10029				const hasDefaultShippingAddress = $cartTable.attr('data-has-default-shipping-address') === 'Y';
10030				let hasFastShipping = false;
10031				for (let i = 0; i < item_code_list.length; i++) {
10032					const code = item_code_list[i];
10033					const $checkbox = $cartContainer.find(`input._cartItemCheckbox[value="${code}"]`).first();
10034					if ($checkbox.length === 0) continue;
10035					if ($checkbox.attr('data-fast-shipping') === 'Y') {
10036						hasFastShipping = true;
10037						break;
10038					}
10039				}
10040				if (hasFastShipping && !hasDefaultShippingAddress) {
10041					alert('기본 배송지가 등록되지 않아 일반배송으로 자동 변경돼요.');
10042				}
10043				if (hasFastShipping && (is_gift_buy || type === 'npay' || type === 'talkpay')) {
10044					if (is_gift_buy) {
10045						alert('일반배송으로 선물하기가 진행돼요');
10046					} else {
10047						alert('선택한 배송은 톡체크아웃·네이버페이와 같은 간편구매에서는 이용할 수 없어요. 구매는 일반배송으로 자동 변경돼요.');
10048					}
10049				}
10050			}
10051
10052			add_order_progress_check = true;
10053
10054			if(is_oms){
10055				if (localStorage.getItem("selectedDeliveryCountry") === "undefined") {
10056					localStorage.removeItem("selectedDeliveryCountry");
10057				}
10058				if (typeof $deliv_country !== 'undefined') {
10059					localStorage.setItem("selectedDeliveryCountry", $deliv_country);
10060				}
10061			}
10062
10063			const [orderBtn, orderRegularlyBtn, orderGiftBtn] = [$('._btn_order'), $('._btn_order_regularly'), $('._btn_order_gift')];
10064			const [orderBtnText, orderRegularlyBtnText, orderGiftBtnText] = [orderBtn.first().text(), orderRegularlyBtn.first().text(), orderGiftBtn.first().text()];
10065
10066			const setSpinner = function ($buttons) {
10067				const spinner = '<div class="loading-spinner-container"><div class="loading-spinner"></div></div>';
10068
10069				if (Array.isArray($buttons)) {
10070					for (const $button of $buttons) {
10071						if ($button.length > 0) {
10072							$button.html(spinner);
10073						}
10074					}
10075				} else {
10076					if ($buttons.length > 0) {
10077						$buttons.html(spinner);
10078					}
10079				}
10080			}
10081
10082			const unsetSpinner = function ($btn, text) {
10083				if ($btn.length > 0) {
10084					$btn.text(text);
10085				}
10086			}
10087
10088			// 간편결제 버튼은 PG사 정책 우려로 인해 스피너 적용하지 않음
10089			if (type !== 'npay' && type !== 'talkpay') {
10090				// 주문하기, 정기구독 버튼에 로딩 스피너 추가
10091				setSpinner([orderBtn, orderRegularlyBtn, orderGiftBtn]);
10092			}
10093
10094			const { freebiesUserSelected, selectedAllFreebiesRequired } = await new Promise((resolve) => {
10095				// MFE 장바구니가 사은품 선택값을 직접 전달한 경우(모바일 바로구매 — 포함형 cart MFE라
10096				// 이벤트 pull 응답자 없음). 이벤트 발송/타임아웃/게이팅 없이 전달분을 그대로 사용한다.
10097				// MFE 측에서 이미 선택형 사은품 충족을 검증하므로 selectedAllFreebiesRequired=true.
10098				if (params && Array.isArray(params.freebie_list)) {
10099					resolve({ freebiesUserSelected: params.freebie_list, selectedAllFreebiesRequired: true });
10100					return;
10101				}
10102				// OMS 주문이 아니거나 정기구독인 경우 사은품 선택 없이 바로 진행
10103				if (!is_oms || type === 'regularly') {
10104					resolve({ freebiesUserSelected: [], selectedAllFreebiesRequired: true });
10105					return;
10106				}
10107
10108				if (!isFreebieMfeMounted()) {
10109					// 사은품 미사용 사이트는 MFE 미적재 → 사은품 선택 없이 바로 진행
10110					resolve({ freebiesUserSelected: [], selectedAllFreebiesRequired: true });
10111					return;
10112				}
10113
10114				// 모바일 장바구니 MFE 는 사은품 선택 UI(freebiesUserSelectedOnCartMobile 이벤트)를
10115				// 사용하지 않는다. 응답자가 없어 아래 dispatch 가 10초 타임아웃까지 대기 → 모바일 바로구매/
10116				// 주문 지연의 원인. cart MFE 활성(magnet-shell[data-mfe="fo-shopping/cart"], cartListMake
10117				// 와 동일 식별) + 모바일이면 사은품 선택 없이 바로 진행한다.
10118				// (카트 셸이 없는 화면에서는 기존 사은품 선택 흐름 그대로 유지)
10119				if (window.IS_MOBILE && $('#shop_cart_list magnet-shell[data-mfe="fo-shopping/cart"]').length > 0) {
10120					resolve({ freebiesUserSelected: [], selectedAllFreebiesRequired: true });
10121					return;
10122				}
10123
10124				// 네이버페이, 톡체크아웃인 경우 안내 후 사은품 없이 진행
10125				if (type === 'npay' || type === 'talkpay') {
10126					resolve({ freebiesUserSelected: [], selectedAllFreebiesRequired: true });
10127					return;
10128				}
10129
10130				// 일반 OMS 주문인 경우 사은품 선택 이벤트 발송
10131				let settled = false;
10132				const timeoutId = setTimeout(() => {
10133					if (settled) return;
10134					settled = true;
10135					// 10000ms 내 MFE 응답 없으면 사은품 없이 주문 진행 (사은품 미사용 사이트 포함)
10136					resolve({ freebiesUserSelected: [], selectedAllFreebiesRequired: true });
10137				}, 10000);
10138
10139				window.document.dispatchEvent(new CustomEvent('@im/fo-prod-detail-freebie:freebiesUserSelectedOnCart' + (window.IS_MOBILE ? 'Mobile' : ''), {
10140					detail: {
10141						// (args: { freebiesUserSelected: Freebie[]; selectedAllFreebiesRequired: boolean }) => void
10142						resolveCallback: (args) => {
10143							if (settled) return;
10144							settled = true;
10145							clearTimeout(timeoutId);
10146
10147							if (args.isReady === false) {
10148								alert('사은품 정보를 불러오는 중입니다. 잠시 후 다시 시도해 주세요.');
10149								unsetSpinner(orderBtn, orderBtnText);
10150								unsetSpinner(orderRegularlyBtn, orderRegularlyBtnText);
10151								unsetSpinner(orderGiftBtn, orderGiftBtnText);
10152								add_order_progress_check = false;
10153								return;
10154							}
10155
10156							resolve(args);
10157						}
10158					}
10159				}))
10160			})
10161
10162			if (!selectedAllFreebiesRequired) {
10163				// 일반 주문에서만 사은품 관련 처리 - 정기구독에서의 사은품은 스팩아웃
10164				unsetSpinner(orderBtn, orderBtnText);
10165				alert('아직 사은품을 선택하지 않았습니다. 사은품을 선택해주세요.');
10166				add_order_progress_check = false;
10167				return false;
10168			}
10169
10170			let shippingTemplateCodeMap = {};
10171			if (is_oms) {
10172				const $cartContainer = $shop_cart_list || $('#shop_cart_list');
10173				const templateMap = {};
10174				for (let i = 0; i < item_code_list.length; i++) {
10175					const code = item_code_list[i];
10176					const $checkbox = $cartContainer.find(`input._cartItemCheckbox[value="${code}"]`).first();
10177					if ($checkbox.length === 0) continue;
10178					const templateCode = $checkbox.attr('data-shipping-template-code');
10179					if (templateCode) {
10180						templateMap[code] = templateCode;
10181					}
10182				}
10183				if (Object.keys(templateMap).length > 0) {
10184					shippingTemplateCodeMap = templateMap;
10185				}
10186			}
10187
10188			const url = is_oms ? '/shop/oms/OMS_add_order_cart.cm' : "/shop/add_order_cart.cm";
10189			const headers = new Headers();
10190			headers.append('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
10191			const _landingUrlValue = getBsLandingUrlHeaderValue();
10192			if (_landingUrlValue) {
10193				headers.append('imweb-landing-url', _landingUrlValue);
10194			}
10195			const requestPayload = {
10196				'type': type,
10197				'cart_code': currentCartCode,
10198				'item_code_list': item_code_list,
10199				'backurl': backurl,
10200				'direct': (direct ? 'Y' : 'N'),
10201				'is_gift_buy': is_gift_buy,
10202				'deliv_country': $deliv_country || '',
10203				freebie_list: (type !== 'npay' && type !== 'talkpay') ? freebiesUserSelected : undefined,
10204				ace_pid: is_oms ?  (window._AcePID || window._AceMID) : undefined,
10205				'infoUrl': is_oms ? location.origin : undefined,
10206			};
10207			if (is_oms && Object.keys(shippingTemplateCodeMap).length > 0) {
10208				requestPayload.shipping_template_code = JSON.stringify(shippingTemplateCodeMap);
10209			}
10210			const body = serializeToUrlencoded(requestPayload);
10211
10212			try {
10213				const response = await fetch(url, { method: 'POST', headers, body });
10214				if (!response.ok) {
10215					throw new Error(`HTTP error! status: ${response.status}`);
10216				}
10217				const result = await response.json();
10218				add_order_progress_check = false;
10219
10220				if (result.msg == 'SUCCESS') {
10221					switch (type) {
10222						case 'npay': {
10223							if (result.npay_url == '') {
10224								if (result.errmsg) {
10225									alert(escape_javascript(result.errmsg));
10226								} else {
10227									alert(LOCALIZE.설명_네이버페이상품구매실패(escape_javascript(result.errmsg)));
10228								}
10229							} else {
10230								if (!!result.shopping_additional_price_msg) {
10231									alert(result.shopping_additional_price_msg);
10232								}
10233								if (result.advanced_kakao_trace_data != null) {
10234									$('body').append(result.advanced_kakao_trace_data);
10235								}
10236								if (result.auth_type == 2) {
10237										// 성인 인증이 필요할 경우
10238										window.location.href = '/?mode=adult_auth_npay&data=' + result.data;
10239								} else {
10240									// 성인 인증이 필요하지 않는 경우
10241									// 네이버 페이 구매시 전환추적 추가
10242									// CB-3985 — 머천트 PG/마케팅 픽셀 발화 (fireOrderTracking 단일 소스). 네이버페이.
10243									fireOrderTracking('npay', result);
10244									window.location.href = result.npay_url;
10245								}
10246							}
10247							break;
10248						}
10249						case 'talkpay': {
10250							switch (params.type) {
10251								case 'onOrder':
10252									if (result.msg == 'SUCCESS') {
10253										if (result.order_sheet_id) {
10254											params.onSuccess(result.order_sheet_id);
10255										} else {
10256											params.onFailure({ message: result.msg });
10257										}
10258									} else {
10259										params.onFailure({ message: result.msg });
10260									}
10261									break;
10262								case 'onPayOrder':
10263									if (result.msg == 'SUCCESS') {
10264											// CB-3985 — 머천트 PG/마케팅 픽셀 발화 (fireOrderTracking 단일 소스). 톡체크아웃 onPayOrder.
10265											fireOrderTracking('talkpay', result);
10266										var shop_payment_url = "/shop_payment/?order_code=" + encodeURIComponent(result.order_code) + '&order_no=' + encodeURIComponent(result.order_no);
10267										if (result.member_code) {
10268											shop_payment_url += "&order_member=" + encodeURIComponent(result.member_code);
10269										}
10270										if ( result.kakaopay_only === 'Y' ) {
10271											shop_payment_url += "&kakaopay_only=Y";
10272										}
10273										window.location.href = shop_payment_url;
10274									}
10275									break;
10276							};
10277							break;
10278						}
10279						default: {
10280							// CB-3985 — 머천트 PG/마케팅 픽셀 발화 (fireOrderTracking 단일 소스). 일반/정기 주문.
10281							fireOrderTracking(type, result);
10282							if (is_guest_login) {	//비회원주문시 로그인 페이지로 이동처리
10283								window.location.href = '/login?shopping_order_code=' + result.order_code + '&back_url=' + encodeURIComponent(result.back_url_base64);
10284							}
10285							else {	//일반주문인경우 결제화면으로 이동
10286								if (is_oms) {
10287									if (typeof result.c3_result === "undefined" || typeof result.c3_result.orderCode === "undefined" || typeof result.c3_result.orderNo === "undefined") return
10288									var shop_payment_url = "/shop_payment/?order_code=" + encodeURIComponent(result.c3_result.orderCode) + "&order_no=" + encodeURIComponent(result.c3_result.orderNo);
10289
10290									if (result.c3_result.memberCode) {
10291										shop_payment_url += "&order_member=" + encodeURIComponent(result.c3_result.memberCode);
10292									}
10293									window.location.href = shop_payment_url;
10294								} else {
10295									window.location.href = "/shop_payment/?order_code=" + encodeURIComponent(result.order_code);
10296								}
10297							}
10298						}
10299					}
10300				} else {
10301					// 주문하기 버튼에 로딩 스피너 제거
10302					unsetSpinner(orderBtn, orderBtnText);
10303					// 정기구독 버튼에 로딩 스피너 제거
10304					unsetSpinner(orderRegularlyBtn, orderRegularlyBtnText);
10305					// 선물하기 버튼에 로딩 스피너 제거
10306					unsetSpinner(orderGiftBtn, orderGiftBtnText);
10307
10308					if (type == 'talkpay') {
10309						switch (params.type) {
10310							case 'onOrder': {
10311								params.onFailure({ message: result.msg });
10312								break;
10313							}
10314							case 'onPayOrder': {
10315								alert(result.msg);
10316								break;
10317							}
10318						}
10319					} else {
10320						if(result.code === 2){
10321							$('._deliv_country_selector').toggleClass('warning_select');
10322						}
10323						// 코드 7: 비회원 이용권 구매 시 로그인 페이지로
10324						if (result.code === 7) {
10325							redirectToLoginPage();
10326						} else {
10327							alert(result.msg.replace(/\\n/g, '\n'));
10328							// 코드 11: 기존 로직 유지
10329							if (is_guest_login && result.code === 11) {
10330								var back_url_base64 = window.location.pathname;
10331								var url_param = document.location.href.split("?");
10332								if (url_param.length > 1) {
10333									back_url_base64 += "?" + url_param[1];
10334								}
10335								window.location.href = '/login?back_url=' + encodeURIComponent(btoa(back_url_base64));
10336							}
10337						}
10338					}
10339				}
10340
10341			} catch (e) {
10342				add_order_progress_check = false;
10343				// 주문하기 버튼에 로딩 스피너 제거
10344				unsetSpinner(orderBtn, orderBtnText);
10345				// 정기구독 버튼에 로딩 스피너 제거
10346				unsetSpinner(orderRegularlyBtn, orderRegularlyBtnText);
10347				// 선물하기 버튼에 로딩 스피너 제거
10348				unsetSpinner(orderGiftBtn, orderGiftBtnText);
10349			}
10350    };
10351
10352    var addOrderRegularly = function (backurl, params, is_c3 = false){
10353        addOrderWithCart('regularly', '', backurl, false, is_c3, params);
10354    };
10355
10356		const trackClickPurchaseCart = function (paymentButtonType) {
10357			if (typeof BrandScope !== "undefined") {
10358				try {
10359					BrandScope.track('click_purchase_cart', {
10360						'action': 'click',
10361						'content': 'purchase',
10362						'where': 'cart',
10363						'is_single_purchase': false,
10364						'payment_button_type': paymentButtonType,
10365						'is_regularly_prod': false
10366					});
10367				} catch {
10368					// ignore
10369				}
10370			}
10371		}
10372
10373    /**
10374     * 카트 전체 네이버찜등록
10375     */
10376    var addNPayWishWithCart = function (item_code) {
10377        if (currentCartCode == '') {
10378            alert(LOCALIZE.설명_장바구니가비어있습니다());
10379            return false;
10380        }
10381        $.ajax({
10382            type: 'POST',
10383            data: { 'cart_code': currentCartCode, 'item_code': item_code },
10384            url: ('/shop/add_npay_wish_cart.cm'),
10385            dataType: 'json',
10386            cache: false,
10387            success: function (result) {
10388                if (result.msg == 'SUCCESS') {
10389                    if (result.npay_url == '')
10390                        alert(LOCALIZE.설명_네이버페이찜등록실패(escape_javascript(result.errmsg)));
10391                    else {
10392                        if (result.mobile == 'Y')
10393                            window.location.href = result.npay_url;
10394                        else
10395                            window.open(result.npay_url);
10396                    }
10397                } else {
10398                    alert(result.msg);
10399                }
10400            }
10401        });
10402    };
10403    /**
10404     * click_change_options_cart 트래킹 (모달 진입 버튼 클릭 즉시)
10405     */
10406    const _trackChangeOptionsCart = (item_code) => {
10407        if (typeof window.BrandScope === 'undefined') return;
10408        const $row = $cart_list_wrap.find(`tr.content[data-item-code="${item_code}"]`);
10409        if (!$row.length) return;
10410        const cartCode = (typeof currentCartCode === 'string' && currentCartCode) ? currentCartCode : '';
10411        window.BrandScope.track('click_change_options_cart', {
10412            action:         'click',
10413            content:        'change_options',
10414            where:          'cart',
10415            prod_code:      $row.attr('data-prod-code') || '',
10416            prod_type:      $row.attr('data-prod-type') || 'normal',
10417            cart_code:      cartCode,
10418            cart_item_code: item_code,
10419            has_options:    $row.find('._cart_option_card').length > 0,
10420        });
10421    };
10422
10423    /**
10424     * click_complete_change_options 트래킹 (서버 응답 200 + cart 재렌더링 직후)
10425     * cartListMake 가 비동기라 발사 시점엔 cart row 가 변경 전 옵션 카드 보존 →
10426     * dt.options(변경 후) 와 row(변경 전) 직접 비교로 added/removed/qty_changed 산출
10427     * 옵션 라인 키 = 라인 내 value_name 들 sorted-join (DOM·API 응답 양쪽 동일 형식)
10428     */
10429    const _trackCompleteChangeOptions = (dt) => {
10430        if (typeof window.BrandScope === 'undefined') return;
10431        const $row = $cart_list_wrap.find(`tr.content[data-item-code="${dt.item_code}"]`);
10432        if (!$row.length) return;
10433
10434        const oldOptions = $row.find('._cart_option_card').toArray().map(card => ({
10435            key:   $(card).find('.opt-value').toArray().map(el => el.textContent.trim()).sort().join('|'),
10436            count: parseInt($(card).find('._counter_value').val(), 10) || 0,
10437        }));
10438        const newOptions = (dt.options || []).map(sel => ({
10439            key:   (sel.options || []).map(o => o.value_name).sort().join('|'),
10440            count: parseInt(sel.count, 10) || 0,
10441        }));
10442
10443        const oldByKey = new Map(oldOptions.map(o => [o.key, o.count]));
10444        const newByKey = new Map(newOptions.map(o => [o.key, o.count]));
10445        let added = 0, removed = 0, qtyChanged = 0;
10446        new Set([...oldByKey.keys(), ...newByKey.keys()]).forEac
10446h(k => {
10447            if (!oldByKey.has(k)) added++;
10448            else if (!newByKey.has(k)) removed++;
10449            else if (oldByKey.get(k) !== newByKey.get(k)) qtyChanged++;
10450        });
10451
10452        const sum = (arr) => arr.reduce((s, o) => s + o.count, 0);
10453        const oldQty = oldOptions.length ? sum(oldOptions)
10454            : parseInt($row.find('._cart_counter[data-mode="product_count"] ._counter_value').val(), 10) || 0;
10455        const newQty = newOptions.length ? sum(newOptions)
10456            : parseInt(dt.order_count, 10) || 0;
10457
10458        const cartCode = (typeof currentCartCode === 'string' && currentCartCode) ? currentCartCode : '';
10459        window.BrandScope.track('click_complete_change_options', {
10460            action:                        'click',
10461            content:                       'complete',
10462            where:                         'change_options',
10463            prod_code:                     $row.attr('data-prod-code') || '',
10464            prod_type:                     $row.attr('data-prod-type') || 'normal',
10465            cart_code:                     cartCode,
10466            cart_item_code:                dt.item_code,
10467            option_added_count:            added,
10468            option_removed_count:          removed,
10469            option_quantity_changed_count: qtyChanged,
10470            quantity_unit:                 'product',
10471            quantity_before:               oldQty,
10472            quantity_after:                newQty,
10473        });
10474    };
10475
10476    /**
10477     * 장바구니 옵션변경 화면 표시
10478     */
10479    var showChangeCartItem = function (item_code, is_regularly) {
10480        _trackChangeOptionsCart(item_code);
10481        $.ajax({
10482            type: 'POST',
10483            data: { 'item_code': item_code, 'is_regularly': is_regularly },
10484            url: ('/shop/change_cart_item_request.cm'),
10485            dataType: 'json',
10486            cache: false,
10487            success: function (result) {
10488                if (result.msg == 'SUCCESS') {
10489                    $changeCartItemLayer.find('div.container-fluid').html(result.html);
10490                    $changeCartItemLayer.show();
10491
10492                    SITE_SHOP_DETAIL.initDetail({
10493                        prod_idx: result.prodIdx,
10494                        prod_price: result.price,
10495                        require_option_count: result.require_option_count,
10496						use_image_optimizer_on_product_detail_markup: false,
10497                        shop_use_full_load: false,
10498                    });
10499
10500                    SITE_SHOP_DETAIL.initProdStock(result.prod_stock.stock_use, result.prod_stock.stock_no_option, result.prod_stock.stock_unlimit);
10501
10502                    // 수량 제한 설정 - AJAX 성공 직후 바로 호출 (옵션 복원 전)
10503                    if (result.max_prod_quantity !== undefined) {
10504                        SITE_SHOP_DETAIL.setMaxPurchaseQuantity(
10505                            result.max_prod_quantity,
10506                            result.max_member_quantity,
10507                            result.maximum_purchase_quantity_type,
10508                            result.optional_limit,
10509                            result.optional_limit_type,
10510                            result.prod_name
10511                        );
10512                    }
10513
10514                    SITE_SHOP_CART.changeCartLoadOption(result.prodIdx);
10515                    var selected_option_count = result.selected_option_list.length;
10516                    var cnt = 0;
10517                    currentChangeCartItemCode = item_code;
10518                    currentCartItemMaxQuantity = result.maximum_purchase_quantity || 0;
10519                    currentCartItemMaxQuantityType = result.maximum_purchase_quantity_type || 'order';
10520                    $.each(result.selected_option_list, function (no, data) {
10521                        SITE_SHOP_DETAIL.selectOption(
10522                            result.prodIdx,
10523                            {
10524                                options: data.options,
10525                                require: data.require,
10526                                count: data.count,
10527                                idx: data.idx,
10528                                skip_quantity_validation: true
10529                            },
10530                            function () {
10531                                cnt++;
10532                                if (cnt >= selected_option_count){
10533                                    SITE_SHOP_DETAIL.updateSelectedOptions('cart');
10534                                }
10535                            },
10536                            function () {
10537                                cnt++;
10538                                if (cnt >= selected_option_count) {
10539                                    SITE_SHOP_DETAIL.updateSelectedOptions('cart');
10540                                }
10541                            }
10542                        );
10543                    });
10544                    if (result.require_option_count == 0) {
10545                        SITE_SHOP_CART.changeCartOrderCount("pc", result.count, false);
10546                        SITE_SHOP_CART.changeCartOrderCount("mobile", result.count, false);
10547                    }
10548                } else {
10549                    alert(result.msg);
10550                }
10551            }
10552        });
10553    };
10554    var hideChangeCartItem = function () {
10555        $changeCartItemLayer.find('div.container-fluid').empty();
10556        $changeCartItemLayer.hide();
10557        currentChangeCartItemCode = '';
10558    };
10559    /**
10560     * 장바구니 옵션 변경 완료 처리
10561     */
10562    var changeCartItemComplete = function (current_ul, is_c3 = false) {
10563        var is_regularly = 'N';
10564        if (cart_type == SHOP_CONST.CART_TYPE_REGULARLY) {
10565            is_regularly = 'Y';
10566        }
10567        if (currentChangeCartItemCode != '') {
10568            // 프론트엔드 수량 검증 (옵션별 최대 구매수량)
10569            var validation_result = SITE_SHOP_DETAIL.validateMaxPurchaseQuantity();
10570            if (validation_result !== true) {
10571                alert(validation_result);
10572                return;
10573            }
10574            changeCartItemData({
10575                "mode": "detail",
10576                "item_code": currentChangeCartItemCode,
10577                "options": SITE_SHOP_DETAIL.getSelectedOption(),
10578                "order_count": SITE_SHOP_DETAIL.getOrderCount(),
10579                "is_regularly": is_regularly,
10580                "current_ul": current_ul
10581            }, is_c3);
10582        }
10583    };
10584
10585    var changeCartItemDelivType = function (item_code, deliv_type, is_regularly, is_c3 = false) {
10586        changeCartItemData({
10587            "mode": "delivery",
10588            "item_code": item_code,
10589            "deliv_type": deliv_type,
10590            "is_regularly": is_regularly
10591        }, is_c3);
10592    };
10593
10594    var changeCartItemDelivPayType = function (item_code, deliv_pay_type, is_regularly, is_c3 = false) {
10595        changeCartItemData({
10596            "mode": "delivery",
10597            "item_code": item_code,
10598            "deliv_pay_type": deliv_pay_type,
10599            "is_regularly": is_regularly
10600        }, is_c3);
10601    };
10602
10603    var changeCartItemData = function (dt, is_c3 = false) {
10604        if (typeof dt['item_code'] == 'undefined') return false;
10605        $.ajax({
10606            type: 'POST',
10607            data: dt,
10608			url : is_c3 ? '/shop/oms/OMS_change_cart_item.cm' : '/shop/change_cart_item.cm',
10609            dataType: 'json',
10610            cache: false,
10611            success: function (result) {
10612                if (result.msg == 'SUCCESS') {
10613                    if (typeof AW_INOUT != 'undefined') {
10614                        AW_INOUT(result.prod_no, result.after_count);
10615                    }
10616                    if (typeof AM_INOUT !== 'undefined') {
10617                        AM_INOUT(result.prod_no, result.after_count);
10618                    }
10619                    if (typeof CHANNEL_PLUGIN != "undefined") CHANNEL_PLUGIN.updateChannelProfileAttr('cart');
10620                    var cart_type = SHOP_CONST.CART_TYPE_NORMAL;
10621                    if (dt['is_regularly'] == 'Y') {
10622                        cart_type = SHOP_CONST.CART_TYPE_REGULARLY;
10623                    }
10624                    $changeCartItemLayer.hide();
10625                    cartListMake(dt['current_url'], cart_type, is_c3);
10626                    if (dt['mode'] === 'detail') _trackCompleteChangeOptions(dt);
10627                } else {
10628                    alert(result.msg);
10629                }
10630            }
10631        });
10632    };
10633
10634    var countryCodeChange = function (country) {
10635			$deliv_country = country;
10636        $.ajax({
10637            type: 'POST',
10638            data: { 'country': country },
10639            url: ('/shop/country_code_change.cm'),
10640            dataType: 'json',
10641            cache: false,
10642            success: function (result) {
10643                if (result.msg != "SUCCESS") {
10644                    alert(result.msg);
10645                } else {
10646                    window.location.reload();
10647                }
10648            }
10649        });
10650    };
10651
10652    // ───── CB-3865: 사은품 MFE 에 cartChange payload(cart raw) push ───────────────
10653    // freebie MFE 가 자체 OMS endpoint 호출 없이 사은품 결정하도록 cart raw 를 push.
10654    // OMS_get_cart_list_make 응답의 cart 를 closure 캐시 → mutation 시 count 부분 patch.
10655    // raw → orderItems 변환은 freebie 측 책임 (single source of truth — apply-promotion 입력).
10656    // BE 변경 0. dedupe: 동일 raw 면 dispatch skip (React Query 의 새 queryKey 회피).
10657    var _cartRawItems = [];
10658    var _lastDispatchedCartSig = '';
10659
10660    var dispatchFreebieCartChange = function () {
10661        var sig = JSON.stringify(_cartRawItems);
10662        if (sig === _lastDispatchedCartSig) return;
10663        _lastDispatchedCartSig = sig;
10664        window.document.dispatchEvent(new CustomEvent(
10665            '@im/fo-prod-detail-freebie:cartChange',
10666            { detail: { cart: _cartRawItems } }
10667        ));
10668    };
10669
10670    /**
10671     * 장바구니 목록 생성 -  C3 바로구매 분기 추가
10672     * @param current_full_url
10673     * @param type		normal 일반구매, regularly 정기구독
10674     */
10675    var cartListMake = function (current_full_url, type, is_c3 = false) {
10676        selectedCartItem = [];
10677        // MFE 모드인 경우 PHP AJAX 렌더를 스킵 (MFE 셸이 자체적으로 데이터 페치)
10678        // cart 전용 magnet-shell([data-mfe="fo-shopping/cart"]) 직속 자식 존재 여부로 식별 — 같은 페이지의 사은품/프로모션 shell 과 구분
10679        if ($cart_list_wrap && $cart_list_wrap.length && $cart_list_wrap.children('magnet-shell[data-mfe="fo-shopping/cart"]').length) {
10680            cart_type = type;
10681            is_cart_changed = false;
10682            // CB-3816: 호스트 측 cart 변경 (옵션 다이얼로그 확정 등) 후 MFE 에 재조회 요청.
10683            // MFE 가 자체 mutation 으로 트리거한 변경은 이 경로를 타지 않으므로 중복 발행 없음.
10684            // detail.cart_type 으로 변경된 탭을 명시 — MFE 의 현재 활성 탭과 다를 수 있어
10685            // (탭 전환 race) 정확한 query key 를 invalidate 하기 위해 발행자가 알려야 함.
10686            window.dispatchEvent(new CustomEvent('imweb:cart:invalidated', { detail: { cart_type: type } }));
10687            return;
10688        }
10689        $.ajax({
10690            type: 'POST',
10691            data: { 'current_full_url': current_full_url, 'type': type },
10692			url : is_c3 ? '/shop/oms/OMS_get_cart_list_make.cm' : '/shop/get_cart_list_make.cm',
10693            dataType: 'json',
10694            success: function (res) {
10695                if (res.msg == 'SUCCESS') {
10696                    // CB-3865: 페이지 로드 / 항목 추가·삭제·옵션변경 후 cart raw 캐시 갱신.
10697                    // 이후 getSelectedPrice → dispatchFreebieCartChange 로 사은품 MFE push.
10698                    _cartRawItems = res.cart || [];
10699                    $cart_list_wrap.html('');
10700										const range = document.createRange();
10701										// <script type="module"> 태그가 .html() 으로 삽입되면 모듈이 로드되지 않으므로, createContextualFragment를 사용하여 HTML을 삽입
10702                    const table_fragment = range.createContextualFragment(res.table_html);
10703                    $cart_list_wrap[0].appendChild(table_fragment);
10704                    if (res.global_select_html !== '') {
10705                        $global_select.html(res.global_select_html);
10706												$deliv_country = $global_select.find('[selected]').val();
10707                    } else {
10708                        $global_select.remove();
10709                    }
10710                    if (res.cart_item_count > 0) {
10711                        $('.pay-box').show();
10712                        $shop_cart_list.removeClass('cart_empty_wrap');
10713                        fixedShopTableHeader();
10714                        resizeShopTableHeader();
10715                    } else {
10716                        $('.pay-box').hide();
10717                        $shop_cart_list.addClass('cart_empty_wrap');
10718                    }
10719                    cart_type = type;
10720                    if (type === SHOP_CONST.CART_TYPE_REGULARLY) {
10721                        currentCartCode = res.cart_code;
10722                        $shop_cart_list.find('._btn_order').hide();
10723                        $shop_cart_list.find('._btn_order_gift').hide();
10724                        $shop_cart_list.find('._btn_order_regularly').css('display', 'flex');
10725                        $shop_cart_list.find('._social_pay').hide();
10726                        $('#cart_normal_tab').removeClass('active');
10727                        $('#cart_regularly_tab').addClass('active');
10728                    } else {
10729                        currentCartCode = res.cart_code;
10730                        $shop_cart_list.find('._btn_order_regularly').hide();
10731                        $shop_cart_list.find('._btn_order').css('display', 'flex');
10732                        $shop_cart_list.find('._btn_order_gift').css('display', 'flex');
10733                        $shop_cart_list.find('._social_pay').show();
10734                        $('#cart_regularly_tab').removeClass('active');
10735                        $('#cart_normal_tab').addClass('active');
10736                    }
10737                    // 미리보기 모드든 일반 모드든 동일하게 getSelectedPrice 호출 (
10737서버에서 current_full_url로 미리보기 분기).
10738                    getSelectedPrice(type === SHOP_CONST.CART_TYPE_REGULARLY);
10739
10740                    // 매출 상승 도구 미리보기 모드 - 주문/체크박스/수량/삭제 등 후속 동작 차단 + 안내 배너
10741                    if (res.is_preview_mode) {
10742                        applyCartPreviewLock();
10743                    }
10744                } else
10745                    alert(res.msg);
10746
10747                is_cart_changed = false;
10748            },
10749            error: function () {
10750                alert(getLocalizeString('설명_잠시후다시시도해주세요', '', '잠시 후 다시 시도해주세요.'));
10751            }
10752        });
10753    };
10754
10755    /**
10756     * 매출 상승 도구 미리보기 모드 - 카트의 모든 인터랙션(주문/체크/수량/삭제)을 잠그고 안내 배너 표시.
10757     * cartListMake가 응답에 is_preview_mode=true를 받았을 때 호출된다.
10758     * 주문/결제 버튼은 시각적으로는 일반 상태 그대로 노출하고 클릭 동작만 차단한다.
10759     * totals/할인/배송비 등은 서버 측 get_cart_price.cm이 동일한 미리보기 분기로 정상 응답하므로 별도 주입 불필요.
10760     */
10761    var applyCartPreviewLock = function () {
10762        if (!$cart_list_wrap || $cart_list_wrap.length === 0) return;
10763
10764        // 카트 영역 인터랙션 차단
10765        $cart_list_wrap.find('._cartItemCheckbox, ._all_check').prop('disabled', true);
10766        $cart_list_wrap.find('._cart_counter button, ._cart_counter input').prop('disabled', true);
10767        $cart_list_wrap.css('pointer-events', 'none');
10768
10769        // 결제/주문 버튼: 노출은 그대로 두고 클릭 동작만 차단 (capture 단계에서 가로채 inline onclick까지 무력화)
10770        $shop_cart_list.find('._btn_order, ._btn_order_regularly, ._btn_order_gift, ._social_pay').each(function () {
10771            var el = this;
10772            el.removeEventListener('click', _previewLockClickHandler, true);
10773            el.addEventListener('click', _previewLockClickHandler, true);
10774        });
10775    };
10776
10777    var _previewLockClickHandler = function (e) {
10778        e.preventDefault();
10779        e.stopImmediatePropagation();
10780        return false;
10781    };
10782
10783    var fixedShopTableHeader = function () {
10784        var window_width = window.innerWidth;
10785        if (window_width < 768) {
10786            var section_fixed_height = getMobileSectionFixedHeight();
10787            var $cart_list_thead = $cart_list_wrap.find('._shop_table thead');
10788            $cart_list_thead.css('top', section_fixed_height + 'px');
10789        }
10790    };
10791
10792    var getMobileSectionFixedHeight = function () {
10793        /* 기본 top을 0px로 설정 시 1px 가량의 여백이 생겨 뒤가 비쳐보이는 경우가 있어 -1px로 설정 */
10794        var section_fixed_height = -1;
10795        var $fixed_header_disable = $('#inline_header_mobile').find('._fixed_header_section');
10796        for (var i = 0; i < $fixed_header_disable.length; i++) {
10797            var target = $fixed_header_disable[i].getBoundingClientRect();
10798            section_fixed_height += target.height;
10799        }
10800        var prev_fixed_section = $shop_cart_list.parents('div[doz_type="section"]').prevAll('._fixed_section[doz_mobile_hide="N"]')[0];
10801        if (typeof prev_fixed_section !== 'undefined') {
10802            section_fixed_height += prev_fixed_section.getBoundingClientRect().height;
10803        }
10804        return section_fixed_height;
10805    };
10806
10807    var resizeShopTableHeader = function () {
10808        var is_resizing = false;
10809        $(window).resize(function () {
10810            if (!is_resizing) {
10811                is_resizing = true;
10812                setTimeout(function () {
10813                    fixedShopTableHeader();
10814                    is_resizing = false;
10815                }, 100);
10816            }
10817        });
10818    };
10819
10820    var setTimesale = function () {
10821        var $doz_timesale_wrap = $shop_cart_wish_list.find('._doz_timesale_wrap');
10822        if ($doz_timesale_wrap.length > 0) {
10823            $doz_timesale_wrap.each(function () {
10824                var $that = $(this);
10825                var start_time = ($that.find('._doz_timesale').attr('data-start-time') * 1000);
10826                var $doz_timesale = $that.find('._doz_timesale');
10827                var timesale_interval = setInterval(function () {
10828                    var remain_ms = ($doz_timesale.attr('data-end-time') * 1000) - start_time;
10829                    if (remain_ms > 0) {
10830                        var remain_d = Math.floor(remain_ms / 86400000);
10831                        var remain_h = Math.floor((remain_ms % 86400000) / 3600000);
10832                        var remain_m = Math.floor((remain_ms % 3600000) / 60000);
10833                        var remain_s = Math.floor((remain_ms % 60000) / 1000);
10834
10835                        var remain_hh = remain_h < 10 ? '0' + remain_h : '' + remain_h;
10836                        var remain_mm = remain_m < 10 ? '0' + remain_m : '' + remain_m;
10837                        var remain_ss = remain_s < 10 ? '0' + remain_s : '' + remain_s;
10838                        if (remain_d >= 1) {
10839                            $doz_timesale.text(getLocalizeString('설명_종료까지n1일n2시n3분n4초남음', [remain_d, remain_hh, remain_mm, remain_ss], '종료까지 %1일 %2:%3:%4'));
10840                        } else {
10841                            $doz_timesale.text(getLocalizeString('설명_종류까지n1시n2분n3초남음', [remain_hh, remain_mm, remain_ss], '종료까지 %1:%2:%3 남음'));
10842                        }
10843                        start_time = start_time + 1000;
10844                    } else {
10845                        /* 타임세일 종료 */
10846                        clearInterval(timesale_interval);
10847                        $that.remove();
10848                    }
10849                }, 1000);
10850            });
10851        }
10852    };
10853
10854    // BO sales tool 미리보기 트리거 수신: URL에 sales_tool_preview 파라미터가 있을 때만 동작
10855    var salesToolPreviewParams = (function () {
10856        try {
10857            return new URLSearchParams(window.location.search);
10858        } catch (e) {
10859            return null;
10860        }
10861    })();
10862    var isInSalesToolPreview = salesToolPreviewParams ? salesToolPreviewParams.has('sales_tool_preview') : false;
10863
10864    function isAllowedSalesToolPreviewOrigin(origin) {
10865        if (origin === window.location.origin) return true;
10866        if (/(^|\.)imtest\.me$/.test(window.location.hostname) && origin === 'https://localhost:5173') return true;
10867        return /^https:\/\/[a-z0-9-]+\.crm\.(imweb|imtest)\.me$/.test(origin);
10868    }
10869
10870    /**
10871     * 미리보기 전용: 주문 알림 모달(get_order_alarm_modal.cm)을 유효성 검사 없이 즉시 표시.
10872     * 운영 흐름의 addOrderWithCart는 다수 검증을 거친 뒤에야 모달을 띄우는데, 미리보기에서는 검증을 모두 건너뛰고 모달만 노출한다.
10873     */
10874    var showOrderAlarmModalForPreview = function () {
10875        var $orderAlarmMarker = $('#shop_cart_order_alarm_with_free_ship_notify');
10876        if ($orderAlarmMarker.length === 0) return;
10877        var initDelivPriceFlexableKey = parseFloat($orderAlarmMarker.attr('data-init-deliv-price-flexable-key') || '0');
10878        if (initDelivPriceFlexableKey <= 0) return;
10879
10880        $.ajax({
10881            type: 'POST',
10882            url: '/shop/free_ship_notify/get_order_alarm_modal.cm',
10883            data: { init_deliv_price_flexable_key: initDelivPriceFlexableKey },
10884            dataType: 'html',
10885            cache: false,
10886            success: function (html) {
10887                var is_mobile_width = (window.innerWidth < 768);
10888                if (is_mobile_width) {
10889                    imSheet.close('shop_cart_order_alarm_sheet', function () {
10890                        imSheet.open({
10891                            id: 'shop_cart_order_alarm_sheet',
10892                            html: html,
10893                            backdrop: 'rgba(0, 0, 0, 0.15)',
10894                            zIndex: 17001
10895                        });
10896                    });
10897                } else {
10898                    if (typeof $.cocoaDialog === 'object' && typeof $.cocoaDialog.close === 'function') {
10899                        $.cocoaDialog.close();
10900                    }
10901                    $.cocoaDialog.open({
10902                        type: 'site_shop_free_ship_notify',
10903                        custom_popup: html,
10904                        pc_width: 440
10905                    });
10906                }
10907            }
10908        });
10909    };
10910
10911    /**
10912     * 미리보기 전용: 표시 중인 주문 알림 모달을 닫는다 (cocoaDialog/imSheet 양쪽 idempotent).
10913     */
10914    var hideOrderAlarmModalForPreview = function () {
10915        if (typeof $.cocoaDialog === 'object' && typeof $.cocoaDialog.close === 'function') {
10916            $.cocoaDialog.close();
10917        }
10918        if (typeof imSheet !== 'undefined' && typeof imSheet.close === 'function') {
10919            imSheet.close('shop_cart_order_alarm_sheet');
10920        }
10921    };
10922
10923    /**
10924     * 미리보기 전용: 장바구니 페이지의 magnet-shell(cart.sub의 #foFreeShipNotifyCartPageMagnet) 표시 토글.
10925     * 구매하기 버튼 클릭 후 팝업 스타일에서는 in-page magnet이 중복으로 보이지 않도록 감추고,
10926     * 페이지 최상단 스타일에선 다시 보이도록 한다. wrapper div 단위로 padding까지 함께 사라지도록 부모를 토글.
10927     */
10928    var setCartMagnetVisibilityForPreview = function (visible) {
10929        var $magnet = $('#foFreeShipNotifyCartPageMagnet');
10930        if ($magnet.length === 0) return;
10931        var $wrapper = $magnet.parent();
10932        if (visible) {
10933            $wrapper.show();
10934        } else {
10935            $wrapper.hide();
10936        }
10937    };
10938
10939    if (isInSalesToolPreview) {
10940        window.addEventListener('message', function (event) {
10941            if (!isAllowedSalesToolPreviewOrigin(event.origin)) return;
10942            var data = event.data;
10943            if (!data || typeof data !== 'object') return;
10944
10945            if (data.type === 'sales-tool-preview:show-order-alarm') {
10946                // 팝업 스타일이므로 in-page magnet은 감추고 모달만 노출
10947                setCartMagnetVisibilityForPreview(false);
10948                showOrderAlarmModalForPreview();
10949                return;
10950            }
10951            if (data.type === 'sales-tool-preview:hide-order-alarm') {
10952                // 페이지 최상단 스타일로 복귀 — magnet 다시 보이도록
10953                hideOrderAlarmModalForPreview();
10954                setCartMagnetVisibilityForPreview(true);
10955                return;
10956            }
10957            if (data.type === 'sales-tool-preview:free-ship-notify-settings'
10958                || data.type === 'sales-tool-preview:settings') {
10959                var settingsPayload = data.payload || {};
10960                window.dispatchEvent(new CustomEvent('imweb:freeShipNotify:settings:update', {
10961                    detail: settingsPayload
10962                }));
10963            }
10964        });
10965    }
10966
10967    // '장바구니에 담았습니다' 확인 모달을 document.body 에 1회 생성해 노출/재사용
10968    // (정적 마크업은 상세 템플릿 전용이라 다른 페이지엔 없음). 계속쇼핑=닫기 / 장바구니가기=/shop_cart.
10969    var showAddCartAnywhereAlarm = function () {
10970        var MODAL_ID = 'shop_cart_add_to_cart_anywhere_alarm';
10971
10972        var existing = document.getElementById(MODAL_ID);
10973        if (existing) {
10974            existing.style.display = 'block';
10975            return;
10976        }
10977
10978        // DOM API 로 구성 (textContent 만 사용, XSS 무관). 스타일은 전역 custom.sub 의 modal_site_alert 재사용.
10979        var el = function (tag, className) {
10980            var node = document.createElement(tag);
10981            if (className) node.className = className;
10982            return node;
10983        };
10984
10985        var message = el('p', 'text-center');
10986        message.style.marginBottom = '0';
10987        message.textContent = getLocalizeString('설명_선택하신상품을장바구니에담았습니다', '', '선택하신 상품을 장바구니에 담았습니다.');
10988        var container = el('div', 'container-fluid');
10989        container.appendChild(message);
10990
10991        var continueBtn = el('a', 'btn');
10992        continueBtn.setAttribute('href', 'javascript:');
10993        continueBtn.textContent = getLocalizeString('버튼_계속쇼핑', '', '계속 쇼핑');
10994        continueBtn.addEventListener('click', function () {
10995            try {
10996                SITE_SHOP_DETAIL.trackAddToCartPopupAction('click_continue_shopping_add_to_cart_popup', 'continue_shopping', { isFreeShippingUpsell: false });
10997            } catch (e) {}
10998            var m = document.getElementById(MODAL_ID);
10999            if (m) m.style.display = 'none';
11000        });
11001
11002        var goCartBtn = el('a', 'btn right');
11003        goCartBtn.setAttribute('href', 'javascript:');
11004        goCartBtn.textContent = getLocalizeString('버튼_장바구니가기', '', '장바구니 가기');
11005        goCartBtn.addEventListener('click', function () {
11006            try {
11007                SITE_SHOP_DETAIL.trackAddToCartPopupAction('click_go_cart_add_to_cart_popup', 'go_cart', { isFreeShippingUpsell: false });
11008            } catch (e) {}
11009            window.location.href = '/shop_cart';
11010        });
11011
11012        var buttons = el('div', 'btn-group-justified');
11013        buttons.appendChild(continueBtn);
11014        buttons.appendChild(goCartBtn);
11015
11016        var layer = el('div', 'layer_pop');
11017        layer.appendChild(container);
11018        layer.appendChild(buttons);
11019        var content = el('div', 'modal-content');
11020        content.appendChild(layer);
11021        var dialog = el('div', 'modal-dialog');
11022        dialog.appendChild(content);
11023
11024        var modal = el('div', 'modal in modal_site_alert');
11025        modal.id = MODAL_ID;
11026        modal.setAttribute('tabindex', '-1');
11027        modal.setAttribute('role', 'dialog');
11028        modal.appendChild(dialog);
11029
11030        document.body.appendChild(modal);
11031        modal.style.display = 'block';
11032    };
11033
11034    // 외부 위젯(이프두 SUGGEST 등)이 SITE_SHOP_CART.addCartAnywhere(idx) 로 호출하는 공개 진입점.
11035    // 코드베이스 내 호출부가 없어도 외부에서 사용하므로, 미사용으로 오인해 제거하지 말 것.
11036    var addCartAnywhere = function (idx) {
11037        var pid = parseInt(idx, 10);
11038        if (!pid || pid <= 0) return;
11039        if (typeof window.IMWEB_SHOP_FREE_SHIP_NOTIFY_SDK === 'undefined'
11040            || typeof window.IMWEB_SHOP_FREE_SHIP_NOTIFY_SDK.selectAdditionalProductByIdx !== 'function') {
11041            return;
11042        }
11043
11044        // 담기 전 상품 존재 확인 → 없으면 안내 후 중단 (SDK 는 무수정)
11045        $.ajax({
11046            type: 'POST',
11047            url: '/shop/free_ship_notify/get_additional_product_info.cm',
11048            data: { 'product_idx': pid },
11049            dataType: 'json',
11050            cache: false,
11051            timeout: 10000,
11052            success: function (result) {
11053                if (!result || result.msg !== 'SUCCESS' || !result.product_info) {
11054                    alert(getLocalizeString('설명_상품이존재하지않습니다', '', '존재하지 않는 상품입니다.'));
11055                    return;
11056                }
11057
11058                // 확인 모달은 콜백이 아닌 담기 완료 이벤트 기준 + idx 일치 스코프
11059                // (옵션 상품은 콜백이 '옵션 모달 표시' 시점에 오고, 이벤트는 타 담기 흐름에서도 발화하므로).
11060                var ADDED_EVENT = 'imweb:freeShipNotify:additionalProductAdded';
11061                var cleanup;
11062                var onAdded = function (e) {
11063                    var addedIdx = (e && e.detail) ? parseInt(e.detail.prodIdx, 10) : 0;
11064                    if (addedIdx !== pid) return;
11065                    cleanup();
11066                    showAddCartAnywhereAlarm();
11067                };
11068                var timer = setTimeout(function () { cleanup(); }, 5 * 60 * 1000);
11069                cleanup = function () {
11070                    window.removeEventListener(ADDED_EVENT, onAdded);
11071                    clearTimeout(timer);
11072                };
11073                window.addEventListener(ADDED_EVENT, onAdded);
11074
11075                try {
11076                    window.IMWEB_SHOP_FREE_SHIP_NOTIFY_SDK.selectAdditionalProductByIdx(pid, 'cart', function (success, message) {
11077                        if (!success) {
11078                            cleanup();
11079                            if (message) alert(message);
11080                        }
11081                    });
11082                } catch (e) {
11083                    cleanup();
11084                }
11085            },
11086            error: function () {
11087                alert(getLocalizeString('설명_오류가발생했습니다', '', '오류가 발생했습니다.'));
11088            }
11089        });
11090    };
11091
11092    return {
11093        initCart: function (cart_code, backurl, is_regularly) {
11094            initCart(cart_code, backurl, is_regularly);
11095        },
11096        loadProdWishList: function () {
11097            loadProdWishList();
11098        },
11099        deleteProdWish: function (cart_item, is_regularly) {
11100            deleteProdWish([cart_item], is_regularly);
11101        },
11102        deleteProdWishByProdCode: function (prod_code) {
11103            deleteProdWishByProdCode(prod_code);
11104        },
11105        addProdWish: function (cart_item, is_regularly) {
11106            addProdWish([cart_item], is_regularly);
11107        },
11108        addProdWishMulti: function (is_regularly) {
11109            if (selectedCartItem.length == 0) {
11110                alert(LOCALIZE.설명_선택한항목이없습니다());
11111                return false;
11112            }
11113            addProdWish(selectedCartItem, is_regularly);
11114        },
11115        removeCartItemOption: function (item_code, optionNo, is_regularly) {
11116            removeCartItemOption(item_code, optionNo, is_regularly);
11117        },
11118        removeCartItem: function (item_code, is_regularly) {
11119            removeCartItem([item_code], is_regularly);
11120        },
11121        addCart: function (prodIdx, optionsOrCallback, orderCount, callback, requestOptions) {
11122            addCart(prodIdx, optionsOrCallback, orderCount, callback, requestOptions);
11123        },
11124        addCartAnywhere: function (idx) {
11125            addCartAnywhere(idx);
11126        },
11127        removeSelectedCartItem: function (is_regularly) {
11128            if (selectedCartItem.length == 0) {
11129                alert(LOCALIZE.설명_선택한상품이없습니다());
11130                return false;
11131            }
11132            removeCartItem(selectedCartItem, is_regularly);
11133        },
11134        removeSelectedCartSoldOutItem: function (is_regularly) {
11135            if (selectedCartItem.length == 0) {
11136                alert(LOCALIZE.설명_선택한상품이없습니다());
11137                return false;
11138            }
11139            removeSelectedCartSoldOutItem(selectedCartItem, is_regularly);
11140        },
11141        "toggleCheckCartItem": function (is_regularly) {
11142            toggleCheckCartItem(is_regularly);
11143        },
11144        "toggleAllCheckCartItem": function (b, is_regularly) {
11145            toggleAllCheckCartItem(b, is_regularly);
11146        },
11147        addOrderWithCart: function (type, item_code, backurl, params) {
11148            addOrderWithCart(type, item_code, backurl, false, false, params);
11149        },
11150        addOrderWithCartDirect: function (type, item_code, backurl) {
11151            addOrderWithCart(type, item_code, backurl, true, false);
11152        },
11153        /**
11154         * 무료배송 안내 모달의 "주문하기" 버튼에서 호출.
11155         * 보류된 addOrderWithCart 인자를 사용해 skipFreeShipNotifyAlarm=true로 재호출하여 원래 주문 흐름 진행.
11156         * 모달이 떠있는 동안 매그넷에서 추가상품을 담아 카트가 갱신된 경우, cartListMake가 selectedCartItem을
11157         * 빈 배열로 리셋한 상태이므로 현재 체크된 카트 아이템을 재수집해야 신규 아이템도 주문에 포함된다.
11158         */
11159        proceedOrderAfterFreeShipAlarm: function () {
11160            if (!_pendingOrderWithCart) return;
11161            var a = _pendingOrderWithCart;
11162            _pendingOrderWithCart = null;
11163            // 모달/시트 양쪽 모두 닫기 (어느 쪽이 떴든 idempotent)
11164            if (typeof $.cocoaDialog === 'object' && typeof $.cocoaDialog.close === 'function') {
11165                $.cocoaDialog.close();
11166            }
11167            if (typeof imSheet !== 'undefined' && typeof imSheet.close === 'function') {
11168                imSheet.close('shop_cart_order_alarm_sheet');
11169            }
11170
11171            // 원래 item_code가 비어있을 때(전체 선택 주문)는 모달에서 추가된 아이템도 포함되도록 selectedCartItem 재구성.
11172            // 특정 item_code 직접 주문(addOrderWithCartDirect 등)은 원래 의도 보존을 위해 건드리지 않는다.
11173            if (!a.item_code && $cart_list_wrap && $cart_list_wrap.length > 0) {
11174                selectedCartItem = [];
11175                $cart_list_wrap.find('._cartItemCheckbox:checked').each(function () {
11176                    selectedCartItem.push($(this).val());
11177                });
11178            }
11179
11180            addOrderWithCart(a.type, a.item_code, a.backurl, a.direct, a.is_oms, a.params, a.is_gift_buy, true);
11181        },
11182        addOrderRegularly: function (backurl, params) {
11183            addOrderRegularly(backurl, params, false);
11184        },
11185        trackClickPurchaseCart: function (paymentButtonType) {
11186            trackClickPurchaseCart(paymentButtonType);
11187        },
11188        showChangeCartItem: function (item_code, is_regularly) {
11189            showChangeCartItem(item_code, is_regularly);
11190        },
11191        hideChangeCartItem: function () {
11192            hideChangeCartItem();
11193        },
11194        changeCartSelectRequireOption: function (prod_idx, option_code, value_code, value_name) {
11195            SITE_SHOP_DETAIL.selectRequireOption('cart', prod_idx, option_code, value_code, value_name, function () {
11196                SITE_SHOP_DETAIL.updateSelectedOptions('cart');
11197            });
11198        },
11199        changeCartChangeRequireInputOption: function (prod_idx, option_code, msg) {
11200            SITE_SHOP_DETAIL.changeRequireInputOption('cart', prod_idx, option_code, msg, function () {
11201                SITE_SHOP_DETAIL.updateSelectedOptions('cart');
11202            });
11203        },
11204        changeCartSelectOptionalOption: function (prod_idx, option_code, value_code, value_name) {
11205            SITE_SHOP_DETAIL.selectOptionalOption(prod_idx, option_code, value_code, value_name, function () {
11206                SITE_SHOP_DETAIL.updateSelectedOptions('cart');
11207            });
11208        },
11209        changeCartItemIncrease: function (optNo, reload) {
11210            SITE_SHOP_DETAIL.increaseOptionCount(optNo, 'cart');
11211        },
11212        changeCartItemDecrease: function (optNo) {
11213            SITE_SHOP_DETAIL.decreaseOptionCount(optNo, 'cart');
11214        },
11215        changeCartIncrease: function (type) {
11216            var o = $changeCartItemLayer.find('input._order_count_' + type);
11217            var curCount = o.val();
11218            if (isNaN(curCount))
11219                curCount = 1;
11220            else
11221                curCount = parseInt(curCount) + 1;
11222
11223            var res = SITE_SHOP_DETAIL.checkProdStock(curCount);
11224            if (res === true) {
11225                // 최대 구매수량 검증 추가 (optional_option 타입 제외)
11226                var max_quantity_check = SITE_SHOP_DETAIL.checkMaxPurchaseQuantityForOrderCount(curCount);
11227                if (max_quantity_check !== true) {
11228                    alert(max_quantity_check);
11229                    return;
11230                }
11231                o.val(curCount);
11232                SITE_SHOP_DETAIL.setOrderCount(curCount);
11233            } else {
11234                alert(res);
11235            }
11236
11237            SITE_SHOP_DETAIL.updateSelectedOptions('cart');
11238        },
11239        changeCartDecrease: function (type) {
11240            var o = $changeCartItemLayer.find('input._order_count_' + type);
11241            var curCount = o.val();
11242            if (isNaN(curCount))
11243                curCount = 1;
11244            else
11245                curCount = parseInt(curCount) - 1;
11246            if (curCount < 1) curCount = 1;
11247
11248            var res = SITE_SHOP_DETAIL.checkProdStock(curCount);
11249            if (res === true) {
11250                o.val(curCount);
11251                SITE_SHOP_DETAIL.setOrderCount(curCount);
11252            } else {
11253                alert(res);
11254            }
11255
11256            SITE_SHOP_DETAIL.updateSelectedOptions('cart');
11257        },
11258        changeCartOrderCount: function (type, count, is_alert) {
11259            if (is_alert == void 0) is_alert = true;
11260
11261            if (isNaN(count))
11262                count = 1;
11263            else
11264                count = parseInt(count);
11265            if (count < 1) count = 1;
11266
11267            var res = SITE_SHOP_DETAIL.checkProdStock(count);
11268            if (res === true) {
11269                if (is_alert) {
11270                    // 최대 구매수량 검증 추가 (optional_option 타입 제외)
11271                    var max_quantity_check = SITE_SHOP_DETAIL.checkMaxPurchaseQuantityForOrderCount(count);
11272                    if (max_quantity_check !== true) {
11273                        alert(max_quantity_check);
11274                        return;
11275                    }
11276                }
11277                $changeCartItemLayer.find("input._order_count_" + type).val(count);
11278                SITE_SHOP_DETAIL.setOrderCount(count);
11279            } else {
11280                if (is_alert) alert(res);
11281            }
11282
11283            SITE_SHOP_DETAIL.updateSelectedOptions('cart');
11284        },
11285        changeCartItemRemove: function (optNo) {
11286            SITE_SHOP_DETAIL.removeSelectedOption(optNo, 'cart');
11287        },
11288        changeCartItemCount: function (optNo, optCount) {
11289            SITE_SHOP_DETAIL.changeOptionCount(optNo, optCount, 'cart');
11290        },
11291        // 스피너 애니메이션 keyframes 삽입 (1회)
11292        _spinnerStyleInjected: false,
11293        _ensureSpinnerStyle() {
11294            if (this._spinnerStyleInjected) return;
11295            const style = document.createElement('style');
11296            style.textContent = '@keyframes cartBtnSpin{100%{transform:rotate(360deg)}}';
11297            document.head.appendChild(style);
11298            this._spinnerStyleInjected = true;
11299        },
11300        // 수량 변경 AJAX 진행 카운터 (0이 되어야 로딩 해제)
11301        _pendingCount: 0,
11302        _btnOriginalTexts: null,
11303        _setLoading() {
11304            this._pendingCount++;
11305            if (this._pendingCount > 1) return; // 이미 로딩 중
11306            this._ensureSpinnerStyle();
11307            const spinnerSvg = '<span class="_cart_btn_spinner" style="display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;animation:cartBtnSpin 1s linear infinite"><svg width="100%" height="100%" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M14.75 8C15.1642 8 15.5039 7.66338 15.4625 7.25123C15.3501 6.13087 14.9865 5.04679 14.3948 4.08126C13.6721 2.90192 12.6373 1.9454 11.4049 1.31745C10.1725 0.689506 8.79046 0.414598 7.41156 0.52312C6.03265 0.631642 4.71062 1.11937 3.59161 1.93237C2.4726 2.74538 1.60022 3.85199 1.0709 5.12988C0.541588 6.40776 0.375962 7.80712 0.592338 9.17326C0.808713 10.5394 1.39866 11.8191 2.29696 12.8709C3.03239 13.7319 3.95104 14.4128 4.98183 14.8659C5.36102 15.0326 5.78614 14.8136 5.91414 14.4196C6.04214 14.0257 5.82382 13.6068 5.44891 13.4307C4.67966 13.0693 3.99324 12.5473 3.43756 11.8967C2.71893 11.0553 2.24697 10.0315 2.07387 8.93861C1.90077 7.8457 2.03327 6.7262 2.45672 5.7039C2.88017 4.6816 3.57808 3.7963 4.47329 3.1459C5.36849 2.49549 6.42612 2.10531 7.52924 2.0185C8.63237 1.93168 9.73801 2.1516 10.7239 2.65396C11.7099 3.15632 12.5377 3.92153 13.1158 4.86501C13.5629 5.59453 13.8472 6.40866 13.9532 7.25194C14.0048 7.66292 14.3358 8 14.75 8Z" fill="currentColor"/></svg></span>';
11308            const $btns = $('.bottom-btn.btn-wrap').find('a.btn');
11309            this._btnOriginalTexts = [];
11310            $btns.each((i, el) => { this._btnOriginalTexts.push($(el).html()); });
11311            $btns.each(function () { $(this).html(spinnerSvg); }
11311).addClass('disabled').css('pointer-events', 'none');
11312            $('._social_pay').css({ 'pointer-events': 'none', 'opacity': '0.4' });
11313        },
11314        _clearLoading() {
11315            this._pendingCount--;
11316            if (this._pendingCount > 0) return; // 아직 진행 중인 요청 있음
11317            this._pendingCount = 0;
11318            const $btns = $('.bottom-btn.btn-wrap').find('a.btn');
11319            if (this._btnOriginalTexts) {
11320                $btns.each((i, el) => {
11321                    if (this._btnOriginalTexts[i]) $(el).html(this._btnOriginalTexts[i]);
11322                });
11323                this._btnOriginalTexts = null;
11324            }
11325            $btns.removeClass('disabled').css('pointer-events', '');
11326            $('._social_pay').css({ 'pointer-events': '', 'opacity': '' });
11327        },
11328        // 키 기반 debounce 유틸리티
11329        _debounceTimers: {},
11330        _debounce(key, fn, delay) {
11331            clearTimeout(this._debounceTimers[key]);
11332            this._debounceTimers[key] = setTimeout(() => {
11333                delete this._debounceTimers[key];
11334                fn();
11335            }, delay);
11336        },
11337        // debounce 전 원래 수량 저장 (복원용)
11338        _originalCounts: {},
11339        // 수량 cell UI 즉시 업데이트 (단일상품만 즉시 반영, 옵션 상품의 품목 수량은 서버 응답으로 갱신)
11340        _updateCartCountCell($row, mode, option_no, count) {
11341            if (mode === 'product_count') {
11342                $row.find('._cart_item_count').text(count);
11343                const $countText = $row.find('._cart_item_count_text');
11344                if ($countText.length) {
11345                    $countText.text($countText.text().replace(/\d+/, count));
11346                }
11347            }
11348            // 옵션/상품 가격 업데이트 (정가 + 할인가)
11349            const updatePriceEl = ($container, priceClass, originPriceClass) => {
11350                const $price = $container.find('.' + priceClass);
11351                const $origin = $container.find('.' + originPriceClass);
11352                if ($price.length) {
11353                    const unitPrice = parseInt($price.data('unit-price'), 10);
11354                    if (unitPrice) $price.text(LOCALIZE.getCurrencyFormat(unitPrice * count));
11355                }
11356                if ($origin.length) {
11357                    const unitOrigin = parseInt($origin.data('unit-price'), 10);
11358                    if (unitOrigin) $origin.text(LOCALIZE.getCurrencyFormat(unitOrigin * count));
11359                }
11360            };
11361            if (mode === 'option_count') {
11362                const $card = $row.find('._cart_option_card[data-option-no="' + option_no + '"]');
11363                updatePriceEl($card, '_cart_option_price', '_cart_option_origin_price');
11364            } else if (mode === 'product_count') {
11365                updatePriceEl($row, '_cart_product_price', '_cart_product_origin_price');
11366            }
11367        },
11368        // click_change_quantity_cart 트래킹 (서버 응답 200 + cart 화면 반영 직후 호출)
11369        _trackChangeQuantityCart($counter, $row, mode, before, after, method) {
11370            if (typeof window.BrandScope === 'undefined' || !method) return;
11371            const isOption = mode === 'option_count';
11372            let optionInfo = null;
11373            if (isOption) {
11374                // 옵션 라벨 + 값 wrapper 텍스트 추출 (예: "포장방법: 포장백 / 색상: 빨강")
11375                // .opt-name 의 parent span 이 라벨+값 한 옵션을 묶고, 같은 카드에 옵션 N개면 N개 parent span
11376                const $optCard = $counter.closest('._cart_option_card');
11377                if ($optCard.length) {
11378                    const parts = [];
11379                    $optCard.find('.opt-name').each(function () {
11380                        const text = $(this).parent().text().replace(/\s+/g, ' ').trim();
11381                        if (text) parts.push(text);
11382                    });
11383                    if (parts.length) optionInfo = parts.join(' / ');
11384                }
11385            }
11386            const cartCode = (typeof currentCartCode === 'string' && currentCartCode) ? currentCartCode : '';
11387            window.BrandScope.track('click_change_quantity_cart', {
11388                action:                 'click',
11389                content:                'change_quantity',
11390                where:                  'cart',
11391                prod_code:              $row.attr('data-prod-code') || '',
11392                prod_type:              $row.attr('data-prod-type') || 'normal',
11393                cart_code:              cartCode,
11394                cart_item_code:         $counter.data('itemCode') || '',
11395                option_info:            optionInfo,
11396                quantity_change_method: method,
11397                quantity_unit:          isOption ? 'option' : 'product',
11398                quantity_before:        before,
11399                quantity_after:         after,
11400            });
11401        },
11402        // 수량 변경 후 서버 동기화 (debounce + AJAX, +/- 와 직접입력 공통)
11403        // trackingMethod: 'button_increase' | 'button_decrease' | 'input'
11404        // (click_change_quantity_cart 트래킹 시 사용 — 응답 200 + 화면 반영 직후 발사)
11405        _syncCartCount($counter, $val, $row, mode, option_no, timerKey, trackingMethod) {
11406            const { itemCode: item_code, isRegularly: is_regularly, isC3: is_c3 } = $counter.data();
11407            const rollback = (count) => {
11408                $val.val(count);
11409                this._updateCartCountCell($row, mode, option_no, count);
11410            };
11411            this._debounce(timerKey, () => {
11412                const originalCount = this._originalCounts[timerKey];
11413                delete this._originalCounts[timerKey];
11414                // debounce 대기 중 옵션이 삭제된 경우 요청 취소
11415                if (!$.contains(document, $counter[0])) return;
11416                const finalCount = parseInt($val.val()) || 1;
11417
11418                // 선택 상품이 없으면 item_list가 직렬화되지 않으므로 빈 선택 목록도 명시한다.
11419                const postData = { mode, item_code, count: finalCount, is_regularly, item_list: selectedCartItem, item_list_sent: 'Y' };
11420                if (mode === 'option_count') postData.option_no = option_no;
11421
11422                this._setLoading();
11423
11424                $.ajax({
11425                    type: 'POST',
11426                    data: postData,
11427                    url: is_c3 ? '/shop/oms/OMS_change_cart_item.cm' : '/shop/change_cart_item.cm',
11428                    dataType: 'json',
11429                    cache: false,
11430                    success: (result) => {
11431                        if (result.msg === 'SUCCESS') {
11432                            if (typeof AW_INOUT !== 'undefined') AW_INOUT(result.prod_no, result.after_count);
11433                            if (typeof AM_INOUT !== 'undefined') AM_INOUT(result.prod_no, result.after_count);
11434                            if (typeof CHANNEL_PLUGIN !== 'undefined') CHANNEL_PLUGIN.updateChannelProfileAttr('cart');
11435                            if (result.item_prices) {
11436                                $.each(result.item_prices, (itemCode, info) => {
11437                                    const $itemRow = $cart_list_wrap.find(`tr[data-item-code="${itemCode}"]`);
11438                                    if (!$itemRow.length) return;
11439                                    // 서버에서 계산된 정확한 품목 수량 반영
11440                                    if (info.count !== undefined) {
11441                                        $itemRow.find('._cart_item_count').text(info.count);
11442                                        const $countText = $itemRow.find('._cart_item_count_text');
11443                                        if ($countText.length) {
11444                                            $countText.text($countText.text().replace(/\d+/, info.count));
11445                                        }
11446                                    }
11447                                    // 가격 갱신 (PC/mobile + origin 헬퍼 통합, QD는 헬퍼에서 차감)
11448                                    renderCartRowPrice($itemRow, { basePrice: info.price_raw, originPrice: info.origin_price_raw });
11449                                    // 타이틀 행 아이템 가격(strikethrough 영역) 별도 갱신
11450                                    $itemRow.find('._cart_item_price').text(info.origin_price);
11451                                    // 수량별 할인 (JS 계산)
11452                                    const _quantityDiscountItemPrice = _calcCartQuantityDiscount($itemRow, parseInt(info.price_raw, 10) || 0);
11453
11454                                    // 배송비 업데이트
11455                                    if (info.deliv_price_text !== undefined && info.deliv_price_text !== '') {
11456                                        updateCartItemDelivText(findCartItemDelivTd($itemRow, itemCode), info.deliv_price_text);
11457
11458                                        // 모바일 개별 배송비 업데이트
11459                                        const $mobileDelivText = $cart_list_wrap.find(`._cart_mobile_deliv_price[data-item-code="${itemCode}"] ._cart_mobile_deliv_price_text`);
11460                                        if ($mobileDelivText.length) {
11461                                            $mobileDelivText.html(info.deliv_price_text);
11462                                        }
11463                                    }
11464                                    // 모바일 묶음배송 배송비 업데이트
11465                                    if (info.bundle_deliv_text !== undefined && info.bundle_deliv_text !== '') {
11466                                        const $shippingRow = findCartBundleShippingRow($itemRow, itemCode);
11467                                        if ($shippingRow.length) {
11468                                            $shippingRow.find('._cart_bundle_deliv_text').html(info.bundle_deliv_text);
11469                                        }
11470                                    }
11471                                    // 모바일 아이템별 할인 영역 업데이트
11472                                    if (info.period_discount !== undefined) {
11473                                        const $discountWrap = $itemRow.find('._cart_mobile_discount_wrap');
11474                                        const $periodField = $itemRow.find('._cart_mobile_period_discount_field');
11475                                        const $periodPrice = $itemRow.find('._cart_mobile_period_discount_price');
11476
11477                                        // _quantityDiscountItemPrice는 위에서 이미 계산됨
11478                                        const _periodDiscount = info.peri
11478od_discount;
11479                                        const _creatorDiscountItemPrice = parseInt(info.creator_discount, 10) || 0;
11480                                        const _totalItemDiscount = _periodDiscount + _quantityDiscountItemPrice + _creatorDiscountItemPrice;
11481
11482                                        if (_totalItemDiscount > 0) {
11483                                            // 상품 할인 금액 (합계)
11484                                            $periodPrice.data('period-discount-price', _totalItemDiscount);
11485                                            $periodPrice.text('- ' + LOCALIZE.getCurrencyFormat(_totalItemDiscount));
11486                                            $periodField.show();
11487
11488                                            // 즉시/기간 할인 하위 항목
11489                                            const $periodSub = $itemRow.find('._cart_mobile_period_sub_field');
11490                                            if (_periodDiscount > 0 && $periodSub.length) {
11491                                                $periodSub.find('._cart_mobile_period_sub_price').text('- ' + LOCALIZE.getCurrencyFormat(_periodDiscount));
11492                                                $periodSub.show();
11493                                            } else if ($periodSub.length) {
11494                                                $periodSub.hide();
11495                                            }
11496
11497                                            // 수량별 할인 하위 항목
11498                                            const $quantityDiscountField = $itemRow.find('._cart_mobile_qd_discount_field');
11499                                            const $quantityDiscountPrice = $itemRow.find('._cart_mobile_quantity_discount_price');
11500                                            if (_quantityDiscountItemPrice > 0) {
11501                                                $quantityDiscountPrice.text('- ' + LOCALIZE.getCurrencyFormat(_quantityDiscountItemPrice));
11502                                                $quantityDiscountField.show();
11503                                            } else {
11504                                                $quantityDiscountField.hide();
11505                                            }
11506
11507                                            // 크리에이터 할인 하위 항목
11508                                            const $creatorDiscountField = $itemRow.find('._cart_mobile_creator_discount_field');
11509                                            const $creatorDiscountPrice = $itemRow.find('._cart_mobile_creator_discount_price');
11510                                            if (_creatorDiscountItemPrice > 0) {
11511                                                $creatorDiscountPrice
11512                                                    .data('creator-discount-price', _creatorDiscountItemPrice)
11513                                                    .text('- ' + LOCALIZE.getCurrencyFormat(_creatorDiscountItemPrice));
11514                                                $creatorDiscountField.show();
11515                                            } else {
11516                                                $creatorDiscountPrice.data('creator-discount-price', 0).text('');
11517                                                $creatorDiscountField.hide();
11518                                            }
11519
11520                                            $discountWrap.show();
11521                                        } else {
11522                                            $periodPrice.data('period-discount-price', 0);
11523                                            $periodPrice.text('');
11524                                            $periodField.hide();
11525                                            $itemRow.find('._cart_mobile_creator_discount_price').data('creator-discount-price', 0).text('');
11526                                            $itemRow.find('._cart_mobile_creator_discount_field').hide();
11527                                            if ($discountWrap.length) $discountWrap.hide();
11528                                        }
11529                                    }
11530                                });
11531                            }
11532                            if (result.totals) {
11533                                setCartPriceHtml(result.totals);
11534                            }
11535                            // 사은품 MFE에 실제 콘텐츠가 렌더링되어 있으면 장바구니 변경 이벤트 발송
11536                            const freebieEl = document.querySelector('fo-prod-detail-freebie-cart') || document.querySelector('fo-prod-detail-freebie-cart-mobile');
11537                            const freebieRoot = freebieEl && freebieEl.shadowRoot ? freebieEl.shadowRoot.querySelector('[data-root] > div') : null;
11538                            if (freebieRoot) {
11539                                // CB-3865: 수량 변경 후 closure 의 count 만 patch.
11540                                // option_no 는 DOM data-option-no (0-based array index). 본품은 -1.
11541                                // totalItemPrice 는 patch 안 함 — apply-promotion schema 상 optional,
11542                                // BE 가 prodCode/qty 로 자체 계산. tiered pricing 부정확 회피.
11543                                var _patchedItem = _cartRawItems.find(function (i) { return i.item_code === $counter.data('itemCode'); });
11544                                if (_patchedItem) {
11545                                    var _target = (option_no === -1)
11546                                        ? _patchedItem
11547                                        : ((_patchedItem.options && _patchedItem.options.list) || [])[option_no];
11548                                    if (_target) _target.count = finalCount;
11549                                }
11550                                dispatchFreebieCartChange();
11551                            }
11552                            // click_change_quantity_cart 트래킹 (서버 응답 200 + 화면 반영 직후)
11553                            this._trackChangeQuantityCart($counter, $row, mode, originalCount, finalCount, trackingMethod);
11554                        } else {
11555                            rollback(originalCount);
11556                            alert(result.msg);
11557                        }
11558                    },
11559                    error: () => rollback(originalCount),
11560                    complete: () => {
11561                        this._clearLoading();
11562                    }
11563                });
11564            }, 300);
11565        },
11566        // 장바구니 1품목당 최대 수량 (프론트 입력 한도
11566)
11567        _MAX_CART_ITEM_COUNT: 9999,
11568        // 장바구니 목록에서 수량 직접 변경 (옵션/단일상품 공통, +/- 버튼 핸들러)
11569        directChangeCount(el, delta) {
11570            const $counter = $(el).closest('._cart_counter');
11571            const $val = $counter.find('._counter_value');
11572            const $row = $counter.closest('tr');
11573            const currentCount = parseInt($val.val()) || 1;
11574            let newCount = currentCount + delta;
11575            if (newCount < 1) newCount = 1;
11576            if (newCount > this._MAX_CART_ITEM_COUNT) newCount = this._MAX_CART_ITEM_COUNT;
11577            if (newCount === currentCount) return;
11578
11579            // 수량 증가 시 품절/재고 검증 (클라이언트 즉각 피드백)
11580            // 최대구매수량 검증은 서버에서 일괄 처리 (모달과 동일한 검증 로직 적용)
11581            if (delta > 0 && !this._checkStockLimit($counter, newCount)) return;
11582
11583            const { mode, optionNo } = $counter.data();
11584            const option_no = (mode === 'option_count') ? optionNo : -1;
11585            const timerKey = `${$counter.data('itemCode')}_${option_no}`;
11586
11587            // 최초 클릭 시 원래 수량 보관
11588            if (!(timerKey in this._originalCounts)) {
11589                this._originalCounts[timerKey] = currentCount;
11590            }
11591
11592            // 즉시 UI 반영
11593            $val.val(newCount);
11594            this._updateCartCountCell($row, mode, option_no, newCount);
11595
11596            // 트래킹용 변경 방식 (click_change_quantity_cart)
11597            const trackingMethod = delta > 0 ? 'button_increase' : 'button_decrease';
11598            this._syncCartCount($counter, $val, $row, mode, option_no, timerKey, trackingMethod);
11599        },
11600        // 장바구니 목록에서 수량 직접 입력 (input onchange 핸들러)
11601        directInputCount(el) {
11602            const $counter = $(el).closest('._cart_counter');
11603            const $val = $counter.find('._counter_value');
11604            const $row = $counter.closest('tr');
11605
11606            // focus 시점에 저장된 직전 값 (없으면 input 의 현재값을 fallback)
11607            const previousCount = parseInt(el.dataset.prevValue, 10) || (parseInt($val.val(), 10) || 1);
11608
11609            // 입력값 정규화:
11610            //   1) Number() 로 우선 파싱 → finite 면 Math.floor 적용 (소수점 절삭, 음수 보존)
11611            //   2) finite 가 아니면 (NaN/Infinity) 숫자 외 문자 제거 후 parseInt 폴백
11612            //   3) 최종값을 [1, _MAX_CART_ITEM_COUNT] 범위로 클램프 후 input 에 반영
11613            // 예: "1.5" → 1, "-5" → 1, "abc1234" → 1234, "12,000" → 9999, 빈문자열 → 1
11614            const rawInput = String($val.val());
11615            const parsed = Number(rawInput);
11616            let newCount;
11617            if (Number.isFinite(parsed)) {
11618                newCount = Math.floor(parsed);
11619            } else {
11620                const stripped = rawInput.replace(/[^0-9]/g, '');
11621                newCount = parseInt(stripped, 10);
11622            }
11623            if (isNaN(newCount) || newCount < 1) newCount = 1;
11624            if (newCount > this._MAX_CART_ITEM_COUNT) newCount = this._MAX_CART_ITEM_COUNT;
11625            $val.val(newCount);
11626
11627            if (newCount === previousCount) return;
11628
11629            // 수량 증가 시 품절/재고 검증
11630            if (newCount > previousCount && !this._checkStockLimit($counter, newCount)) {
11631                $val.val(previousCount);
11632                return;
11633            }
11634
11635            const { mode, optionNo } = $counter.data();
11636            const option_no = (mode === 'option_count') ? optionNo : -1;
11637            const timerKey = `${$counter.data('itemCode')}_${option_no}`;
11638
11639            // 최초 변경 시 원래 수량 보관 (서버 응답 실패 시 복원용)
11640            if (!(timerKey in this._originalCounts)) {
11641                this._originalCounts[timerKey] = previousCount;
11642            }
11643
11644            // 즉시 가격 셀 갱신
11645            this._updateCartCountCell($row, mode, option_no, newCount);
11646
11647            this._syncCartCount($counter, $val, $row, mode, option_no, timerKey, 'input');
11648        },
11649        // 품절/재고 한도 검증 (true: 통과, false: 차단)
11650        _checkStockLimit($counter, newCount) {
11651            if ($counter.data('soldout') === 'Y') {
11652                alert(LOCALIZE.설명_현재재고부족으로N개이상구매할수없습니다(1));
11653                return false;
11654            }
11655            const maxStock = $counter.data('stock');
11656            if (maxStock !== undefined && newCount > parseInt(maxStock)) {
11657                alert(LOCALIZE.설명_현재재고부족으로N개이상구매할수없습니다(parseInt(maxStock) + 1));
11658                return false;
11659            }
11660            return true;
11661        },
11662        "changeCartItemDelivType": function (item_code, deliv_type, is_regularly) {
11663            changeCartItemDelivType(item_code, deliv_type, is_regularly);
11664        },
11665        "changeCartItemDelivPayType": function (item_code, deliv_pay_type, is_regularly) {
11666            changeCartItemDelivPayType(item_code, deliv_pay_type, is_regularly);
11667        },
11668        changeCartItemComplete: function (current_ul) {
11669            changeCartItemComplete(current_ul);
11670        },
11671        addNPayWishWithCart: function (item_code) {
11672            addNPayWishWithCart(item_code);
11673        },
11674        addSelectedCartItem: function (item_code) {
11675            selectedCartItem.push(item_code);
11676        },
11677        // MFE 주문 재시도 시 이전 선택을 누적하지 않고 현재 목록으로 교체한다.
11678        setSelectedCartItems: function (item_codes) {
11679            selectedCartItem = item_codes.slice();
11680        },
11681        changeCartLoadOption: function (prod_idx) {
11682            SITE_SHOP_DETAIL.loadOption('cart', prod_idx);
11683        },
11684        cartListMake: function (current_full_url, type) {
11685            cartListMake(current_full_url, type);
11686        },
11687        "setCreatorDiscountData": function (data) {
11688            // 크리에이터 전용 혜택가 데이터 주입 (PHP cart_main.sub 에서 호출).
11689            // BE 차감은 OMS 자동 처리이므로 여기선 표시용 데이터만 보관.
11690            creator_cart_data = data || null;
11691        },
11692        countryCodeChange: function (country) {
11693            countryCodeChange(country);
11694        },
11695        openPromotionModal: function () {
11696            openPromotionModal();
11697        },
11698        toggleAllSelectCk: function () {
11699            toggleAllSelectCk();
11700        },
11701        // C3 에 주문을 생성하는 endpoint를 바라보는 함수
11702		C3_initCart : function(cart_code, backurl, is_regularly){
11703			initCart(cart_code, backurl, is_regularly, true);
11704		},
11705		OMS_addOrderWithCart : function(type, item_code, backurl, params, is_gift_buy = false){
11706			addOrderWithCart(type, item_code, backurl, false, true, params, is_gift_buy);
11707		},
11708		OMS_addOrderWithCartDirect : function(type, item_code, backurl, cart_code, freebie_list){
11709			// IMIO-7501 — MFE 장바구니는 탭 전환 시 리로드가 없어 C3_initCart 가 재실행되지 않는다.
11710			// currentCartCode 가 첫 로드 type 에 고정돼 다른 탭 바로구매가 빈 cart 로 판정되므로,
11711			// MFE 가 넘긴 활성 탭 cart_code 로 호출 시점에 동기화한다.
11712			if (typeof cart_code === 'string' && cart_code !== '') {
11713				currentCartCode = cart_code;
11714			}
11715			// 모바일 MFE 장바구니는 사은품이 포함형(별도 freebie MFE 미적재)이라 host 의 사은품 pull
11716			// 이벤트에 응답할 주체가 없다. 바로구매는 MFE 가 선택한 사은품을 직접 인자로 넘기므로,
11717			// params.freebie_list 로 addOrderWithCart 에 전달해 주문서에 담는다 (레거시 바로구매 사은품 회귀 복구).
11718			addOrderWithCart(type, item_code, backurl, true, true, freebie_list ? { freebie_list: freebie_list } : undefined);
11719		},
11720		// CB-3985 — MFE 장바구니가 주문 응답으로 머천트 픽셀을 호스트에서 발화하기 위해 노출.
11721		OMS_fireOrderTracking : function (type, result) {
11722		    fireOrderTracking(type, result);
11723		},
11724		C3_cartListMake : function(current_full_url, type){
11725			cartListMake(current_full_url, type, true);
11726		},
11727		C3_changeCartItemComplete : function(current_ul){
11728			changeCartItemComplete(current_ul, true);
11729		},
11730		C3_changeCartItemDelivType: function(item_code, deliv_type,is_regularly){
11731			changeCartItemDelivType(item_code, deliv_type,is_regularly, true);
11732		},
11733		C3_changeCartItemDelivPayType: function(item_code, deliv_pay_type,is_regularly) {
11734			changeCartItemDelivPayType(item_code, deliv_pay_type,is_regularly, true);
11735		},
11736    };
11737}();
11738
11739var SITE_SHOP_REVIEW = function () {
11740    var $review_wrap;
11741    var $mobile_review_wrap;
11742    var $mobile_form;
11743    var $rating;
11744    var $star;
11745    var $m_rating;
11746    var $m_star;
11747    var $comment_body;
11748    var $comment_area;
11749    var review_body;
11750    var $review_container;
11751    var $review_form;
11752    var body_input;
11753    var placeholderText;
11754    var isIOS, isSafari, $fr_m_custom, $write_header, m_sticky_container_trigger_top, $toolbarContainer;
11755    var images = {};
11756    var add_review_wrap;
11757    var is_submit = false;
11758
11759		// 이미지 엑박 여부를 확인하는 함수
11760		const isImageBroken = (img) => {
11761			return img.complete && (img.naturalWidth === 0 || img.naturalHeight === 0);
11762		}
11763
11764		// 이미지를 다시 로드하는 함수
11765		// 업로드 직후 imgo 최적화본이 없으면 원본이 폴백으로 온다. jpg·png 는 그대로 표시되지만
11766		// heic·heif 는 브라우저가 디코드하지 못해 엑박이 된다. 변환이 끝나면 webp 로 바뀌므로 다시 받아 본다.
11767		// 재시도 간격(ms). 뒤로 갈수록 벌려 총 12초 안에 4번만 시도한다.
11768		const RELOAD_IMAGE_RETRY_DELAYS = [1000, 2000, 3000, 6000];
11769		const reloadImage = (img, src) => {
11770			// src 에 이미 ?w= 가 있으므로 "?t=" 를 이어 붙이면 w 값이 "300?t=..." 로 파싱되어
11771			// 폭이 NaN 이 되고 최소 프리셋(100px)이 내려온다. URL API 로 파라미터를 교체한다.
11772			const tried = Number(img.dataset.reloadRetry || 0);
11773			if (tried >= RELOAD_IMAGE_RETRY_DELAYS.length) {
11774				return;
11775			}
11776			img.dataset.reloadRetry = tried + 1;
11777			setTimeout(() => {
11778				const retryUrl = new URL(src);
11779				retryUrl.searchParams.set('t', Date.now()); // 캐시를 피하기 위해 쿼리 파라미터 추가
11780				img.src = retryUrl.toString();
11781			}, RELOAD_IMAGE_RETRY_DELAYS[tried]);
11782		}
11783
11784    var init = function (code) {
11785        $review_wrap = $('._review_wrap');
11786        $review_form = $('#review_form');
11787        $rating = $('#rating');
11788        $star = $('._star');
11789        $review_form.find('._btn_add_image').toggleClass('no-margin', true);
11790        review_body = $('#review_modal_body');
11791        $review_container = $('#review_container');
11792        body_input = $('#body_input');
11793        placeholderText = $('#placeholderText').val();
11794        $review_form.find('#review_image_upload_btn').fileupload({
11795            url: '/ajax/review_image_upload.cm',
11796            formData: { temp: 'Y', target: 'site_review', type: 'image' },
11797            dataType: 'json',
11798            singleFileUploads: true,
11799            limitMultiFileUploads: 1,
11800            start: function (e, data) {
11801            },
11802            progress: function (e, data) {
11803            },
11804            done: function (e, data) {
11805                $.each(data.result.files, function (e, tmp) {
11806                    if (tmp.error == null) {
11807                        $review_form.find("#review_image_box").show();
11808                        $review_form.find('._btn_add_image').toggleClass('no-margin', false);
11809												// 경로에 쿼리가 이미 있으면 '?w=300' 을 이어 붙일 때 w 가 파싱되지 않으므로 URL API 로 붙인다
11810												const _optimizedImageUrl = new URL(CDN_OPTIMIZED_URL + tmp.url);
11811												_optimizedImageUrl.searchParams.set('w', 300);
11812												const url = _optimizedImageUrl.toString();
11813                        images[tmp.code] = tmp.url;
11814                        const html = '<span class="file-add _img_' + tmp.code + '"><input type="hidden" name="img" value="' + tmp.name + '"><div class="img-thumb-wrap"><img id="review_' + tmp.url + '" src="' + url + '"></div><a class="del" href="javascript:;" onclick="SITE_SHOP_REVIEW.deleteReviewImage(\'' + tmp.code + '\')"><i class="btm bt-times vertical-middle" aria-hidden="true"></i></a></span>';
11815                        $review_form.find("#review_image_box ._btn_add_image").before(html);
11816
11817		                    const reviewImage = document.getElementById(`review_${tmp.url}`);
11818												reviewImage.style.display = 'none';
11819
11820		                    // 이미지 로드 완료 후 확인
11821		                    reviewImage.onload = reviewImage.onerror = function() {
11822			                    if (isImageBroken(reviewImage)) {
11823				                    reloadImage(reviewImage, reviewImage.src);
11824			                    } else {
11825				                    reviewImage.style.display = 'block';
11826			                    }
11827		                    };
11828
11829		                    // 만약 이미 이미지가 로드되었으면 즉시 확인
11830		                    if (reviewImage.complete) {
11831			                    if (isImageBroken(reviewImage)) {
11832				                    reloadImage(reviewImage, reviewImage.src);
11833			                    } else {
11834				                    reviewImage.style.display = 'block';
11835			                    }
11836		                    }
11837                    } else {
11838                        alert(tmp.error);
11839                    }
11840                });
11841            },
11842            fail: function (e, data) {
11843                alert(getLocalizeString("설명_업로드에실패하였습니다", "", "업로드에 실패 하였습니다."));
11844            }
11845        });
11846    };
11847
11848    var deleteReviewImage = function (code) {
11849        if (typeof images[code] != 'undefined') {
11850            delete images[code];
11851        }
11852        $("span._img_" + code).remove();
11853    };
11854
11855    var openAddReview = function (idx, prod_code, prod_order_code, is_c3 = false) {
11856        $.cocoaDialog.close();
11857        $('#myModalReview').modal("hide");//review_remind 모달 id분리로 인한 처리
11858        $.ajax({
11859            type: 'POST',
11860            data: { 'idx': idx, 'prod_code': prod_code, 'prod_order_code': prod_order_code },
11861            url: ('/shop/open_add_review.cm'),
11862            dataType: 'json',
11863            cache: false,
11864            async: false,
11865            success: function (result) {
11866                if (result.msg === 'SUCCESS') {
11867                    $.cocoaDialog.open({
11868                        type: 'add_review', custom_popup: result.html, 'close_block': true, width: 800, hide_event: function () {
11869                            images = {};
11870                        }
11871                    });
11872                } else {
11873                    alert(result.msg);
11874                }
11875            }
11876        });
11877    };
11878
11879    var convertReviewImage = function (image_list) {
11880        $review_form.find('._btn_add_image').toggleClass('no-margin', false);
11881        for (var i = 0; i < image_list.length; i++) {
11882            let html;
11883			images[i] = image_list[i];
11884	        if (images[i].indexOf('.mp4') > -1) {
11885		        html = '<span class="file-add _img_' + i + '"><input type="hidden" name="img" value="' + i + '"><div class="img-thumb-wrap tw-flex tw-flex-col tw-items-center tw-justify-center tw-gap-[8px] tw-bg-[#F8F9FB]"><img style="border-width: 0px; width: 24px; height: 24px;" src="' + VENDOR_DOMAIN + '/images/circle-warning.png"><p class="tw-text-[10px] tw-leading-[15px] tw-text-center !tw-mb-0 tw-text-[#717680]">동영상은<br/>표시할 수 없습니다.</p></div><a class="del" href="javascript:;" onclick="SITE_SHOP_REVIEW.deleteReviewImage(\'' + i + '\')"><i class="btm bt-times vertical-middle" aria-hidden="true"></i></a></span>';
11886	        } else {
11887                html = '<span class="file-add _img_' + i + '"><input type="hidden" name="img" value="' + i + '"><div class="img-thumb-wrap"><img src="' + CDN_UPLOAD_URL + image_list[i] + '"></div><a class="del" href="javascript:;" onclick="SITE_SHOP_REVIEW.deleteReviewImage(\'' + i + '\')"><i class="btm bt-times vertical-middle" aria-hidden="true"></i></a></span>';
11888	        }
11889            $review_form.find("#review_image_box ._btn_add_image").before(html);
11890        }
11891    };
11892
11893    function resizeStickyContainer() {
11894        var s_top = $(this).scrollTop();
11895        if (isIOS && isSafari) {
11896            $write_header.css({ '-webkit-transition': 'top 100ms', 'transition': 'top 100ms', 'top': s_top + 'px' });
11897            $fr_m_custom.toggleClass('m_sticky_container', s_top > m_sticky_container_trigger_top);
11898            $fr_m_custom.toggleClass('m_sticky_container_ios', s_top > m_sticky_container_trigger_top);
11899            if (s_top > m_sticky_container_trigger_top) {
11900                $toolbarContainer.css({ '-webkit-transition': 'top 100ms', 'transition': 'top 100ms', 'top': s_top + 'px' });
11901                review_body.find('.fr-view').css('padding-top', '90px');
11902            } else {
11903                $toolbarContainer.css({ '-webkit-transition': 'none', 'transition': 'none', 'top': 'auto' });
11904                review_body.find('.fr-view').css('padding-top', '');
11905            }
11906        } else {
11907            $fr_m_custom.toggleClass('m_sticky_container', s_top > m_sticky_container_trigger_top);
11908        }
11909        $toolbarContainer.find('.fr-toolbar').css('width', review_body.find('.fr-view').width());
11910        if ($(window).width() >= 768) {
11911            if ($review_container.hasClass('bg_on'))
11912                $review_container.find('#toolbarContainer').toggleClass('pc_sticky_toolbar', s_top > 487);
11913            else
11914                $review_container.find('#toolbarContainer').toggleClass('pc_sticky_toolbar', s_top > 180);
11915        }
11916    }
11917
11918    var initMobileReview = function () {
11919        $mobile_review_wrap = $('#prod_detail_content_mobile');
11920        $mobile_form = $mobile_review_wrap.find('#mobile_review_form');
11921        $m_rating = $mobile_form.find('#mobile_rating');
11922        $m_star = $mobile_form.find('._star');
11923        $mobile_form.find("#mobile_review_image_box").hide();
11924
11925        $mobile_form.find('#mobile_review_image_upload_btn').setUploadImage({
11926            url: '/shop/upload_image.cm',
11927            dropZone: 'icon_img_upload_wrap',
11928            singleFileUploads: true,
11929            formData: { temp: 'Y' }
11930        }, function (res, data) {
11931            $("#mobile_review_image_box").show();
11932            $.each(data, function (e, tmp) {
11933                if (tmp.error == "" || tmp.error == null) {
11934                    var url = CDN_UPLOAD_URL + tmp.url;
11935                    var html = '<span class="file-add"><input type="hidden" name="img" value="' + tmp.name + '"><div class="file-add-bg" style="background: url(' + url + ') no-repeat center center;"></div><em class="del" onclick="POST_COMMENT.removeCommentImg($(this))"></em></span>';
11936                    $("#mobile_review_image_box").append(html);
11937                } else {
11938                    alert(tmp.error);
11939                }
11940            });
11941        });
11942        autosize($('.textarea_block textarea'));
11943    };
11944
11945    var changeRating = function (t, n) {
11946
11947        if (t == 'desktop') {
11948            $rating.val(n + 1);
11949            $star.each(function (e) {
11950                if (n <= 0 && e == 0) {
11951                    if (n == -1) {
11952                        $(this).removeClass('active');
11953                    } else {
11954                        $(this).addClass('active');
11955                    }
11956                } else {
11957                    $(this).removeClass('active');
11958                    if (e <= n) {
11959                        $(this).addClass('active');
11960                    }
11961                }
11962            });
11963        } else {
11964            $m_rating.val(n + 1);
11965            $m_star.each(function (e) {
11966                if (n <= 0 && e == 0) {
11967                    if (n == -1) {
11968                        $(this).removeClass('active');
11969                    } else {
11970                        $(this).addClass('active');
11971                    }
11972                } else {
11973                    $(this).removeClass('active');
11974                    if (e <= n) {
11975                        $(this).addClass('active');
11976                    }
11977                }
11978            });
11979        }
11980    };
11981
11982    var reviewFormShow = function (t) {
11983        var sub_form = $("._sub_form_" + t);
11984
11985        sub_form.data('show', 'Y');
11986        sub_form.show();
11987        var comment_add_body = sub_form.find('._comment_add_body_' + t);
11988
11989        $('body').off('mouseup.sub_comment')
11990            .on('mouseup.sub_comment', function (e) {
11991                var $c_target = $(e.target);
11992                var $s_form = $c_target.closest('._sub_form_' + t + ', ._show_sub_form_btn_' + t);
11993                if ($s_form.length == 0) {
11994
11995                    var text = comment_add_body.val();
11996                    sub_form.data('show', 'N');
11997                    if (text == '') {
11998                        $('body').off('mouseup.sub_comment');
11999                        reviewFormHide();
12000                    }
12001                }
12002            });
12003    };
12004
12005    var reviewEditShow = function (t) {
12006        var editor_form = $("._sub_form_editor_" + t);
12007        editor_form.siblings().hide();
12008
12009        editor_form.data('show', 'Y');
12010        editor_form.show();
12011        autosize.update(editor_form.find('textarea'));
12012
12013    };
12014
12015    var reviewEditHide = function (t) {
12016        var editor_form = $("._sub_form_editor_" + t);
12017        editor_form.hide();
12018    };
12019
12020    var reviewFormHide = function () {
12021        $("._sub_review_form").hide();
12022    };
12023
12024    var reviewDelete = function (t, c, r_p, only_photo, buyer_permission, secret_pass) {
12025        only_photo = only_photo == 'Y' ? true : false;
12026        $.ajax({
12027            type: 'POST',
12028            data: { code: t, prod_code: c },
12029            url: ('/shop/check_review_point.cm'),
12030            dataType: 'json',
12031            success: function (res) {
12032                if (res.msg == 'SUCCESS') {
12033                    var msg = buyer_permission == 'Y' ? LOCALIZE.설명_삭제하시겠습니까삭제후재등록불가() : LOCALIZE.설명_삭제하시겠습니까();
12034                    if (res.alert_msg != '' && res.alert_msg != null) msg = res.alert_msg;
12035                    if (confirm(msg)) {
12036                        $.ajax({
12037                            type: 'POST',
12038                            data: { code: t, prod_code: c, change_member_point: res.change_member_point, review_pass: secret_pass || '' },
12039                            url: ('/shop/delete_review.cm'),
12040                            dataType: 'json',
12041                            success: function (result) {
12042                                if (result.msg == 'SUCCESS') {
12043                                    if (IS_MOBILE) SITE_SHOP_DETAIL.changeContentTab(SHOP_CONST.TAB_TYPE.REVIEW, r_p);
12044                                    else SITE_SHOP_DETAIL.changeContentPCTab(SHOP_CONST.TAB_TYPE.REVIEW, r_p, 0, false, only_photo);
12045
12046
12047                                    SITE_SHOP_DETAIL.deleteReviewInTabDisplay(c, only_photo);
12048                                    SITE_SHOP_DETAIL.getReviewQnaCount(c);
12049
12050
12051                                    $.cocoaDialog.close();
12052                                } else
12053                                    alert(result.msg);
12054                            }
12055                        });
12056                    }
12057                } else
12058                    alert(res.msg);
12059            }
12060        });
12061    };
12062
12063    var reviewHide = function (t, c, r_p) {
12064        if (confirm(LOCALIZE.설명_숨기시겠습니까())) {
12065            $.ajax({
12066                type: 'POST',
12067                data: { code: t, prod_code: c, is_visible: false },
12068                url: ('/shop/delete_review.cm'),
12069                dataType: 'json',
12070                success: function (result) {
12071                    if (result.msg == 'SUCCESS') {
12072                        if (IS_MOBILE) SITE_SHOP_DETAIL.changeContentTab(SHOP_CONST.TAB_TYPE.REVIEW, r_p);
12073                        else SITE_SHOP_DETAIL.changeContentPCTab(SHOP_CONST.TAB_TYPE.REVIEW, r_p);
12074                        $.cocoaDialog.close();
12075                    } else
12076                        alert(result.msg);
12077                }
12078            });
12079        }
12080    };
12081
12082    var CheckSecret = function (code, secret_pass, callback) {
12083        $.ajax({
12084            type: 'post',
12085            data: { code: code, secret_pass: secret_pass, type: 'review' },
12086            url: '/ajax/check_review_pass.cm',
12087            dataType: 'json',
12088            success: function (result) {
12089                if (result.msg == 'SUCCESS') {
12090                    if (typeof callback == 'function')
12091                        callback();
12092                } else {
12093                    alert(result.msg);
12094                }
12095            }
12096        });
12097    };
12098    var EditReviewShow = function (t, c, idx) {
12099        var $show_secret_password = $('#show_secret_password');
12100        var $show_link = $(event.target);
12101        if ($show_secret_password.length == 0) {
12102            $show_secret_password = $('<div class="remove-pop" id="show_secret_password" onclick="event.cancelBubble = true;" tabindex="0" style="position:absolute;z-index:99999;"><p>' + LOCALIZE.설명_작성시등록하신비밀번호를입력해주세요() + '</p><div class="input_area"><input type="password" placeholder="' + LOCALIZE.설명_비밀번호() + '"><button class="btn btn-primary _confirm">' + LOCALIZE.버튼_확인닫기() + '</button></div></div>').hide();
12103            $show_link.after($show_secret_password);
12104        }
12105        $show_secret_password.find('input').val('');
12106        $show_secret_password.show();
12107        $show_secret_password.off('click', '._confirm')
12108            .on('click', '._confirm', function () {
12109                var secret_pass = $show_secret_password.find('input').val();
12110                CheckSecret(t, secret_pass, function () {
12111                    SITE_SHOP_REVIEW.openAddReview(idx, c);
12112                });
12113                $show_secret_password.remove();
12114            });
12115        $('body').off('mousedown.show_secret')
12116            .on('mousedown.show_secret', function (e) {
12117                var $tmp = $(e.target).closest('#show_secret_password');
12118                if ($tmp.length == 0) {
12119					$show_secret_password.remove();
12120                    $('body').off('click.show_secret');
12121                }
12122            });
12123    };
12124
12125    var reviewDeleteShow = function (t, c, only_photo) {
12126        var $show_secret_password = $('#show_secret_password');
12127        var $show_link = $(event.target);
12128        if ($show_secret_password.length == 0) {
12129            $show_secret_password = $('<div class="remove-pop" id="show_secret_password" onclick="event.cancelBubble = true;" tabindex="0" style="position:absolute;z-index:99999;"><p>' + LOCALIZE.설명_작성시등록하신비밀번호를입력해주세요() + '</p><div class="input_area"><input type="password" placeholder="' + LOCALIZE.설명_비밀번호() + '"><button class="btn btn-primary _confirm">' + LOCALIZE.버튼_확인닫기() + '</button></div></div>').hide();
12130            $show_link.after($show_secret_password);
12131        }
12132        $show_secret_password.find('input').val('');
12133        $show_secret_password.show();
12134        $show_secret_password.off('click', '._confirm')
12135            .on('click', '._confirm', function () {
12136                var secret_pass = $show_secret_password.find('input').val();
12137                CheckSecret(t, secret_pass, function () {
12138                    // 확인한 비밀번호를 삭제 요청에도 그대로 싣는다. 서버가 일괄등록 행에서
12139                    // 소유권 세션을 근거로 삼지 않으므로, 이 값이 없으면 정상 삭제도 거절된다.
12140                    reviewDelete(t, c, '', only_photo, undefined, secret_pass);
12141                });
12142				$show_secret_password.remove();
12143            });
12144        $('body').off('mousedown.show_secret')
12145            .on('mousedown.show_secret', function (e) {
12146                var $tmp = $(e.target).closest('#show_secret_password');
12147                if ($tmp.length == 0) {
12148					$show_secret_password.remove();
12149                    $('body').off('click.show_secret');
12150                }
12151            });
12152    };
12153
12154    var imageUploadInit = function (n) {
12155
12156        const $wrapper = $('.categorize, .categorize-mobile');
12157        $("#sub_review_image_box_" + n).hide();
12158        //$("#editor_review_image_box_"+n).hide();
12159
12160        $('#sub_review_image_upload_btn_' + n).setUploadImage({
12161            url: '/shop/upload_image.cm',
12162            dropZone: 'icon_img_upload_wrap',
12163            singleFileUploads: true,
12164            formData: { temp: 'Y' }
12165        }, function (res, data) {
12166            $("#sub_review_image_box_" + n).show();
12167            $.each(data, function (e, tmp) {
12168                if (tmp.error == "" || tmp.error == null) {
12169                    var url = CDN_UPLOAD_URL + tmp.url;
12170                    var html = '<span class="file-add"><input type="hidden" name="img" value="' + tmp.url + '"><div class="file-add-bg" style="background: url(' + url + ') no-repeat center center;"></div><em class="del" onclick="POST_COMMENT.removeCommentImg($(this))"></em></span>';
12171                    $("#sub_review_image_box_" + n).append(html);
12172                } else {
12173                    alert(tmp.error);
12174                }
12175            });
12176        });
12177
12178        $('#editor_review_image_upload_btn_' + n).setUploadImage({
12179            url: '/shop/upload_image.cm',
12180            dropZone: 'icon_img_upload_wrap',
12181            singleFileUploads: true,
12182            formData: { temp: 'Y' }
12183        }, function (res, data) {
12184            $("#editor_review_image_box_" + n).show();
12185            $.each(data, function (e, tmp) {
12186                if (tmp.error == "" || tmp.error == null) {
12187                    var url = CDN_UPLOAD_URL + tmp.url;
12188                    var html = '<span class="file-add"><input type="hidden" name="img" value="' + tmp.url + '"><div class="file-add-bg" style="background: url(' + url + ') no-repeat center center;"></div><em class="del" onclick="POST_COMMENT.removeCommentImg($(this))"></em></span>';
12189                    $("#editor_review_image_box_" + n).append(html);
12190                } else {
12191                    alert(tmp.error);
12192                }
12193            });
12194        });
12195    };
12196
12197    var submit = function () {
12198        if (is_submit) return false;
12199        is_submit = true;
12200        var data = $review_form.serializeObject();
12201        data.body = data.body.replace('/(?!br\\s*\\/?)[^>]+>/gi', '&gt;').replace('/<(?!img\\s*\\/?)[^>]/gi', '&lt;');
12202        data.images = images;
12203        $.ajax({
12204            type: 'POST',
12205            data: data,
12206            url: ('/shop/add_review.cm'),
12207            dataType: 'json',
12208            success: function (res) {
12209                if (res.review_token && res.review_token_key) {
12210                    $review_form.find('[name="review_token"]').val(res.review_token);
12211                    $review_form.find('[name="review_token_key"]').val(res.review_token_key);
12212                }
12213                if (res.review_result === 'RECOVERED') {
12214                    alert(res.msg);
12215                    $.cocoaDialog.close();
12216                    location.reload();
12217                } else if (res.msg == 'SUCCESS') {
12218                    IMWEB_SESSIONSTORAGE.clear("PROD_REVIEW.*");
12219                    reviewCompleted(res.received_point, res.check_received_point);
12220                    if (typeof CHANNEL_PLUGIN != "undefined") CHANNEL_PLUGIN.CompleteReview();
12221                } else {
12222                    is_submit = false;
12223                    alert(res.msg);
12224                }
12225            },
12226            error: function () {
12227                // 응답 유실 시 잠금 해제만 클라이언트 기능키로 제한한다. 자동 재시도하지 않는다.
12228                if (typeof DeployStrategy !== 'undefined' && DeployStrategy.isFeatureEnabled('IMOPS-15383')) {
12229                    is_submit = false;
12230                }
12231            }
12232        });
12233    };
12234
12235    var reviewCompleted = function (received_point, check_received_point) {
12236        $.cocoaDialog.close();
12237        $.ajax({
12238            type: 'POST',
12239            data: { 'received_point': received_point, 'check_received_point': check_received_point },
12240            url: ('/shop/add_review_completed.cm'),
12241            dataType: 'html',
12242            async: false,
12243            cache: false,
12244            success: function (html) {
12245                $.cocoaDialog.open({
12246                    type: 'prod_review_completed',
12247                    custom_popup: html,
12248                    'close_block': false,
12249                    hide_event: function () {
12250                        location.reload();
12251                    }
12252                });
12253            }
12254        });
12255    };
12256
12257    var reviewCancel = function () {
12258        if (isIOS && isSafari) {
12259            var s_top = $(this).scrollTop();
12260            $write_header.css({ '-webkit-transition': 'none', 'transition': 'none', 'position': 'fixed', 'top': 0 });
12261            $fr_m_custom.toggleClass('m_sticky_container', s_top > m_sticky_container_trigger_top);
12262            $fr_m_custom.toggleClass('m_sticky_container_ios', s_top > m_sticky_container_trigger_top);
12263            if (s_top > m_sticky_container_trigger_top) {
12264                $toolbarContainer.css({ '-webkit-transition': 'none', 'transition': 'none', 'position': 'fixed', 'top': $write_header.height() + 'px' });
12265            } else {
12266                $toolbarContainer.css({ '-webkit-transition': 'none', 'transition': 'none', 'top': 'auto' });
12267            }
12268        }
12269        history.go(-1);
12270    };
12271
12272
12273    var createHtml = function (prod_idx, review_page, qna_page, paging_on, only_photo, rating) {
12274        const isMobile_width = (window.innerWidth < SHOP_CONST.WIDTH_MOBILE);
12275
12276        $review_wrap = $('._review_wrap');
12277
12278        const qna_url = isMobile_width ? '/shop/prod_review_mobile_html.cm' : '/shop/prod_review_pc_html.cm';
12279
12280        if ($review_wrap.length && !($review_wrap.hasClass('one_page_mode'))) {
12281            review_page = review_page || 0;
12282            qna_page = qna_page || 0;
12283
12284            var is_method_get = (review_page == 0 && qna_page == 0);
12285
12286            $.ajax({
12287                type: (is_method_get ? 'GET' : 'POST'),
12288                data: { prod_idx: prod_idx, review_page: review_page, qna_page: qna_page, only_photo: only_photo, rating: rating },
12289                url: qna_url,
12290                dataType: 'html',
12291                success: function (result) {
12292                    $review_wrap.html(result);
12293
12294                    if(isMobile_width) {
12295                        SITE_SHOP_DETAIL.getReviewSummary(document.querySelector(".detail_review_wrap_mobile ._review_summary_wrap_mobile"));
12296                    }
12297                }
12298            });
12299        }
12300
12301    };
12302
12303    var checkReviewData = function () {
12304        var check = Object.keys(images).length > 0 || review_body.val() != '';
12305        if (check) {
12306            if (confirm(LOCALIZE.설명_작성한내용이모두사라집니다())) {
12307                $.cocoaDialog.close();
12308            } else {
12309                return false;
12310            }
12311        } else {
12312            $.cocoaDialog.close();
12313        }
12314    };
12315
12316    var addBlockPost = function (login_member_code, review_code) {
12317        if (confirm(getLocalizeString('설명_게시글차단안내', '', '게시글을 차단하시겠습니까? 작성자의 다른 글과 댓글도 확인할 수 없습니다.'))) {
12318            addBlock(login_member_code, review_code, 'post');
12319        }
12320    };
12321
12322    var addBlockComment = function (login_member_code, review_code) {
12323        if (confirm(getLocalizeString('설명_님을차단안내', '', '댓글을 차단하시겠습니까? 작성자의 다른 댓글도 확인할 수 없습니다.'))) {
12324            addBlock(login_member_code, review_code, 'comment');
12325        }
12326    };
12327
12328    var switchToBlockCancelButton = function (login_member_code, review_code, review_member_code, type) {
12329        $('._review_button._block_' + review_member_code)
12330            .text(getLocalizeString("설명_차단해제", "", "차단해제"))
12331            .removeAttr('onclick')
12332            .off('click')
12333            .on('click', function (e) {
12334                e.stopPropagation();
12335                if (type === 'comment') {
12336                    SITE_SHOP_REVIEW.deleteBlockComment(login_member_code, review_code, review_member_code);
12337                } else {
12338                    SITE_SHOP_REVIEW.deleteBlockPost(login_member_code, review_code, review_member_code);
12339                }
12340            });
12341
12342    };
12343
12344    var switchToBlockCancelBody = function (review_member_code) {
12345        $('._review_body._block_' + review_member_code)
12346            .html("<p class='text-gray'><i aria-hidden='true' class='icon icon-exclamation' style='margin-right: 5px;'></i>" + getLocalizeString("설명_차단한작성자의구매평입니다", "", "차단한 작성자의 구매평입니다.") + "</p>")
12347            .attr('block', 'true');
12348
12349        //이미지 안보이게 수정
12350        $('._review_img._block_' + review_member_code).css('display', 'none');
12351
12352        //모달 리뷰 차단하였을 경우 이미지 변경
12353        $('._block_no_img._block_' + review_member_code)
12354            .css('display', 'block');
12355    };
12356
12357    var addBlock = function (login_member_code, review_code, type) {
12358        $.ajax({
12359            type: 'POST',
12360            data: {
12361                'login_member_code': login_member_code,
12362                'review_code': review_code,
12363                'type': type,
12364            },
12365            url: ('/shop/add_review_block.cm'),
12366            dataType: 'json',
12367            success: function (res) {
12368                if (res.msg === 'SUCCESS') {
12369                    switchToBlockCancelButton(login_member_code, review_code, res.review_member_code, type);
12370                    switchToBlockCancelBody(res.review_member_code);
12371
12372                    $('._block_review_command_' + res.review_member_code).css("display", "none");
12373                    // 댓글은 위에서 화면을 갱신한다. 목록 조회에는 후기 원문 코드만 보낸다.
12374                    if (type === 'post') {
12375                        SHOP_REVIEW_COMMENT.getReviewCommentHtml(review_code, 'accordion', $(this));
12376                    }
12377                } else {
12378                    alert(res.msg);
12379                }
12380            }
12381        });
12382    };
12383
12384    var deleteBlockPost = function (login_member_code, review_code, block_member_code) {
12385        if (confirm(getLocalizeString('설명_게시글차단해제안내', '', '게시글을 차단 해제하시겠습니까? 작성자의 다른 글과 댓글을 다시 확인할 수 있습니다.'))) {
12386            deleteBlock(login_member_code, review_code, block_member_code, 'post');
12387        }
12388    };
12389
12390    var deleteBlockComment = function (login_member_code, review_code, block_member_code) {
12391        var block_review_list_code = [];
12392
12393        if (confirm(getLocalizeString('설명_님을차단해제안내', '', '댓글을 차단 해제하시겠습니까? 작성자의 다른 댓글을 다시 확인할 수 있습니다.'))) {
12394            deleteBlock(login_member_code, review_code, block_member_code, 'comment');
12395        }
12396    };
12397
12398    var deleteBlock = function (login_member_code, review_code, block_member_code, t
12398ype) {
12399        var block_review_list_code = [];
12400
12401        block_review_list_code = get_block_review_list_code(block_member_code);
12402
12403        $.ajax({
12404            type: 'POST',
12405            data: {
12406                'login_member_code': login_member_code,
12407                'review_code': review_code,
12408                'block_review_list_code': block_review_list_code,
12409                'type': type
12410            },
12411            url: ('/shop/delete_review_block.cm'),
12412            dataType: 'json',
12413            success: function (res) {
12414                if (res.msg === 'SUCCESS') {
12415                    switchToBlockButton(login_member_code, review_code, res.review_member_code, type);
12416                    switchToBlockBody(res.review_member_code, block_review_list_code, res.block_review_data);
12417
12418                    $('._block_review_command_' + res.review_member_code).css("display", "block");
12419
12420                    if (type === 'post') {
12421                        SHOP_REVIEW_COMMENT.getReviewCommentHtml(review_code, 'accordion', $(this));
12422                    }
12423                } else {
12424                    alert(res.msg);
12425                }
12426            }
12427        });
12428    };
12429
12430    var switchToBlockButton = function (login_member_code, review_code, review_member_code, type) {
12431        $('._review_button._block_' + review_member_code)
12432            .text(getLocalizeString("설명_차단", "", "차단"))
12433            .removeAttr('onclick')
12434            .off('click')
12435            .on('click', function (e) {
12436                e.stopPropagation();
12437                if (type === 'comment') {
12438                    SITE_SHOP_REVIEW.addBlockComment(login_member_code, review_code);
12439                } else {
12440                    SITE_SHOP_REVIEW.addBlockPost(login_member_code, review_code);
12441                }
12442            });
12443    };
12444
12445    var switchToBlockBody = function (review_member_code, block_review_list_code, block_review_data) {
12446        //일반 이미지
12447        $('._review_img._block_' + review_member_code).removeAttr('style');
12448
12449        block_review_list_code.forEach(block_review_code => {
12450            var review_body_text = '';
12451            if (block_review_data && block_review_data[block_review_code] && block_review_data[block_review_code]['body']) {
12452                review_body_text = block_review_data[block_review_code]['body'];
12453            }
12454            var safe_review_body = $('<div>').text(review_body_text).html().replace(/(\n|\r\n)/g, '<br>');
12455            $('._review_body._review_code_' + block_review_code)
12456                .html(safe_review_body)
12457                .attr('block', 'false');
12458        });
12459        //모달 리뷰 이미지
12460        $('._block_no_img._block_' + review_member_code)
12461            .css('display', 'none');
12462    };
12463
12464    var get_block_review_list_code = function (block_member_code) {
12465        //차단 리스트 review code 가져오기
12466        var block_review_list_code = [];
12467
12468        $("._review_body._block_" + block_member_code + "[block='true']").each(function () {
12469            block_review_list_code.push($(this).attr('block-review-code'));
12470        });
12471
12472        return block_review_list_code;
12473    };
12474
12475    return {
12476        init: function (code) {
12477            init(code);
12478        },
12479        deleteReviewImage: function (code) {
12480            deleteReviewImage(code);
12481        },
12482        openAddReview: function (idx, prod_code, prod_order_code) {
12483            return openAddReview(idx, prod_code, prod_order_code);
12484        },
12485        convertReviewImage: function (image_list) {
12486            return convertReviewImage(image_list);
12487        },
12488        initMobileReview: function () {
12489            initMobileReview();
12490        },
12491        submit: function () {
12492            submit();
12493        },
12494        reviewCompleted: function (received_point, check_received_point) {
12495            reviewCompleted(received_point, check_received_point);
12496        },
12497        reviewCancel: function () {
12498            reviewCancel();
12499        },
12500        changeRating: function (t, n) {
12501            changeRating(t, n);
12502        },
12503        FormShow: function (t) {
12504            reviewFormShow(t);
12505        },
12506        Delete: function (t, c, r_p, only_photo, buyer_permission, secret_pass) {
12507            reviewDelete(t, c, r_p, only_photo, buyer_permission, secret_pass);
12508        },
12509        EditShow: function (t) {
12510            reviewEditShow(t);
12511        },
12512        EditReviewShow: function (t, c, idx) {
12513            EditReviewShow(t, c, idx);
12514        },
12515        DeleteShow: function (t, c, only_photo) {
12516            reviewDeleteShow(t, c, only_photo);
12517        },
12518        Hide: function (t, c, r_p) {
12519            reviewHide(t, c, r_p);
12520        },
12521        EditHide: function (t) {
12522            reviewEditHide(t);
12523        },
12524        imageUploadInit: function (n) {
12525            imageUploadInit(n);
12526        },
12527        createHtml: function (prod_idx, review_page, qna_page, paging_on, only_photo) {
12528            createHtml(prod_idx, review_page, qna_page, paging_on, only_photo);
12529        },
12530        checkReviewData: function () {
12531            checkReviewData();
12532        },
12533        addBlockPost: function (login_member_code, review_code) {
12534            addBlockPost(login_member_code, review_code);
12535        },
12536        addBlockComment: function (login_member_code, review_code) {
12537            addBlockComment(login_member_code, review_code);
12538        },
12539        deleteBlockPost: function (login_member_code, review_code, block_member_code) {
12540            deleteBlockPost(login_member_code, review_code, block_member_code);
12541        },
12542        deleteBlockComment: function (login_member_code, review_code, block_member_code) {
12543            deleteBlockComment(login_member_code, review_code, block_member_code);
12544        },
12545		C3_openAddReview : function(idx,prod_code,prod_order_code){
12546			return openAddReview(idx,prod_code,prod_order_code, true);
12547		},
12548    };
12549}();
12550
12551var SHOP_REVIEW_COMMENT = function () {
12552    var $review_comment_wrap;
12553    var $form;
12554    var $secret;
12555    var review_code;
12556    var device;
12557    var load_type; // 아코디언인지, 모달인지 체크
12558
12559    var init = function (code, type, device_type) {
12560        device = device_type;
12561        load_type = type;
12562        review_code = code;
12563        $form = $('.categorize-mobile #' + device + "_" + load_type + '_review_form_' + code + ', .categorize.review-box #' + device + "_" + load_type + '_review_form_' + code);
12564        $secret = $('._secret_btn');
12565        $secret.on('click', function () {
12566            if ($secret.hasClass('active')) {
12567                $secret.removeClass('active');
12568                $('#use_secret_review').val('N');
12569            } else {
12570                $secret.addClass('active');
12571                $('#use_secret_review').val('Y');
12572            }
12573        });
12574
12575        $form.each(function(){
12576            const $this = $(this);
12577            $this.find('#review_image_upload_btn_' + code).setUploadImage({
12578                url : '/shop/upload_image.cm',
12579                dropZone : 'icon_img_upload_wrap',
12580                singleFileUploads : true,
12581                formData : {temp : 'Y'}
12582            }, function(res, data){
12583                $this.find("#review_comment_image_box_" + code).show();
12584                $.each(data, function(e, tmp){
12585                    if(tmp.error == "" || tmp.error == null){
12586                        var url = CDN_UPLOAD_URL + tmp.url;
12587                        var html = '<span class="file-add"><input type="hidden" name="img" value="' + tmp.url + '"><div class="file-add-bg" style="background: url(' + url + ') no-repeat center center;"></div><em class="del" onclick="POST_COMMENT.removeCommentImg($(this))"></em></span>';
12588                        $this.find("#review_comment_image_box_" + code).append(html);
12589                    }else{
12590                        alert(tmp.error);
12591                    }
12592                });
12593            })
12594        });
12595    };
12596
12597    var imageUploadInit = function (idx) {
12598
12599        const $wrapper = $('.categorize-mobile, .categorize.review-box');
12600
12601        $wrapper.each(function(){
12602           const $this = $(this);
12603
12604            $this.find("#sub_review_image_box_" + idx).hide();
12605
12606            $this.find('#sub_review_image_upload_btn_' + idx).setUploadImage({
12607                url: '/shop/upload_image.cm',
12608                dropZone: 'icon_img_upload_wrap',
12609                singleFileUploads: true,
12610                formData: { temp: 'Y' }
12611            }, function (res, data) {
12612                $(".categorize #sub_review_image_box_" + idx).show();
12613                $(".categorize-mobile #sub_review_image_box_" + idx).show();
12614                $.each(data, function (e, tmp) {
12615                    if (tmp.error == "" || tmp.error == null) {
12616                        var url = CDN_UPLOAD_URL + tmp.url;
12617                        var html = '<span class="file-add"><input type="hidden" name="img" value="' + tmp.url + '"><img src="' + url + '"><em class="del" onclick="POST_COMMENT.removeCommentImg($(this))"></em></span>';
12618                        $this.find("#sub_review_image_box_" + idx).append(html);
12619                    } else {
12620                        alert(tmp.error);
12621                    }
12622                });
12623            });
12624
12625            $this.find('#editor_review_image_upload_btn_' + idx).setUploadImage({
12626                url: '/shop/upload_image.cm',
12627                dropZone: 'icon_img_upload_wrap',
12628                singleFileUploads: true,
12629                formData: { temp: 'Y' }
12630            }, function (res, data) {
12631                $(".categorize #editor_review_image_box_" + idx).show();
12632                $(".categorize-mobile #editor_review_image_box_" + idx).show();
12633                $.each(data, function (e, tmp) {
12634                    if (tmp.error == "" || tmp.error == null) {
12635                        var url = CDN_UPLOAD_URL + tmp.url;
12636                        var html = '<span class="file-add"><input type="hidden" name="img" value="' + tmp.url + '"><img src="' + url + '"><em class="del" onclick="POST_COMMENT.removeCommentImg($(this))"></em></span>';
12637                        $this.find("#editor_review_image_box_" + idx).append(html);
12638                    } else {
12639                        alert(tmp.error);
12640                    }
12641                });
12642            });
12643
12644        });
12645    };
12646
12647    var getReviewCommentHtml = function (code, type, object) {
12648        if (type == 'accordion') {
12649            $review_comment_wrap = $('.review_comment_wrap_' + code);
12650            if (!object.hasClass('active')) {
12651                $.ajax({
12652                    type: 'POST',
12653                    data: { 'code': code, 'type': type },
12654                    url: ('/shop/get_review_comment_list.cm'),
12655                    dataType: 'json',
12656                    async: false,
12657                    cache: false,
12658                    success: function (result) {
12659                        if (result.msg === 'SUCCESS') {
12660                            $review_comment_wrap.html(result.html);
12661                            $review_comment_wrap.show();
12662                        } else {
12663                            alert(result.msg);
12664                        }
12665                    }
12666                });
12667            } else {
12668                $review_comment_wrap.hide();
12669            }
12670        } else {
12671            $review_comment_wrap = $('#review_comment_section_' + code);
12672            $.ajax({
12673                type: 'POST',
12674                data: { 'code': code, 'type': type },
12675                url: ('/shop/get_review_comment_list.cm'),
12676                dataType: 'json',
12677                async: false,
12678                cache: false,
12679                success: function (result) {
12680                    if (result.msg === 'SUCCESS') {
12681                        $review_comment_wrap.html(result.html);
12682                    } else {
12683                        alert(result.msg);
12684                    }
12685                }
12686            });
12687        }
12688    };
12689    var submit = function (t, type, i) {
12690		// mobile 및 pc 에서 같은 id 값 공유 해당 부분 수정 필요...
12691		const isMobile_width = (window.innerWidth < SHOP_CONST.WIDTH_MOBILE);
12692		const isModal = document.querySelector('#cocoaModal').classList.contains('review');
12693        let data = {};
12694		switch (type) {
12695            case 'main': // pc 리뷰 댓글
12696	            if (isModal) {
12697					data = $('#_modal_review_form_' + review_code).serializeObject();
12698					break;
12699	            }
12700	            if (isMobile_width) {
12701		            data = $('.categorize-mobile #' + $form.attr('id')).serializeObject();
12702	            } else {
12703		            data = $('.categorize.review-box #' + $form.attr('id')).serializeObject();
12704	            }
12705                break;
12706            case 'sub_form': //pc 리뷰 대댓글
12707	            if (isModal) {
12708		            data = $('#sub_review_form_' + i).serializeObject();
12709		            break;
12710	            }
12711	            if (isMobile_width) {
12712					data = $('.categorize-mobile #sub_review_form_' + i).serializeObject();
12713	            } else {
12714					data = $('.categorize.review-box #sub_review_form_' + i).serializeObject();
12715	            }
12716                break;
12717            case 'editor': // pc 리뷰 댓글 수정
12718	            if (isModal) {
12719					data = $('#sub_review_editor_form_' + i).serializeObject();
12720					break;
12721	            }
12722                if (isMobile_width) {
12723		            data = $('.categorize-mobile #sub_review_editor_form_' + i).serializeObject();
12724	            } else {
12725		            data = $('.categorize.review-box #sub_review_editor_form_' + i).serializeObject();
12726	            }
12727                break;
12728            case 'mobile': // Mobile 리뷰 댓글
12729                data = $('#mobile_review_form').serializeObject();
12730                break;
12731            case 'mobile_sub_form': // Mobile 리뷰 대댓글
12732                data = $('#mobile_sub_review_form_' + i).serializeObject();
12733                break;
12734            case 'mobile_editor': // Mobile 리뷰 댓글 수정
12735                data = $('#mobile_sub_review_editor_form_' + i).serializeObject();
12736                break;
12737        }
12738        if (!t.hasClass("btn-writing")) {
12739            t.addClass("btn-writing");
12740        }
12741        $.ajax({
12742            type: 'POST',
12743            data: { data: data, review_code: review_code },
12744            url: ('/shop/add_review_comment.cm'),
12745            dataType: 'json',
12746            async: false,
12747            cache: false,
12748            success: function (result) {
12749                if (t.hasClass("btn-writing")) {
12750                    t.removeClass("btn-writing");
12751                }
12752                if (result.msg == 'SUCCESS') {
12753                    if (load_type == 'modal') {
12754                        $('#comment_count').text(" " + result.comment_count);
12755                        getReviewCommentHtml(review_code, 'modal', t);
12756                    } else {
12757                        $('.categorize #comment_count_' + review_code).text(" " + result.comment_count);
12758                        $('.categorize #comment_count_inner_' + review_code).text(" " + result.comment_count);
12759                        $('.categorize-mobile #comment_count_' + review_code).text(result.comment_count);
12760                        $('.categorize-mobile #comment_count_inner_' + review_code).text(" " + result.comment_count);
12761                        getReviewCommentHtml(review_code, 'accordion', t);
12762                    }
12763                    $("div[id^='sub_review_image_box_']").hide();
12764                } else
12765                    alert(result.msg);
12766            }
12767        });
12768    };
12769    var EditHide = function (idx) {
12770        var editor_form = $("._sub_form_editor_" + idx);
12771        editor_form.hide();
12772        $('.tools').show();
12773        editor_form.siblings('._comment_body').show();
12774        editor_form.closest('.comment_area').siblings().show();
12775        editor_form.closest('.comment_area').css('padding-top', '');
12776        editor_form.css('margin-top', '');
12777    };
12778
12779    var EditShow = function (idx) {
12780        var editor_form = $("._sub_form_editor_" + idx);
12781        editor_form.siblings().hide();
12782        editor_form.closest('.comment_area').siblings().hide();
12783        editor_form.closest('.comment_area').css('padding-top', 0);
12784        editor_form.css('margin-top', 0);
12785        editor_form.data('show', 'Y');
12786        editor_form.show();
12787        autosize.update(editor_form.find('textarea'));
12788    };
12789
12790    var reviewFormHide = function () {
12791        $("._sub_review_form").hide();
12792    };
12793
12794    var FormShow = function (idx) {
12795        var sub_form = $("._sub_form_" + idx);
12796
12797        sub_form.data('show', 'Y');
12798        sub_form.show();
12799        var comment_add_body = sub_form.find('._comment_add_body_' + idx);
12800
12801        $('body').off('mouseup.sub_comment')
12802            .on('mouseup.sub_comment', function (e) {
12803                var $c_target = $(e.target);
12804                var $s_form = $c_target.closest('._sub_form_' + idx + ', ._show_sub_form_btn_' + idx);
12805                if ($s_form.length == 0) {
12806
12807                    var text = comment_add_body.val();
12808                    sub_form.data('show', 'N');
12809                    if (text == '') {
12810                        $('body').off('mouseup.sub_comment');
12811                        reviewFormHide();
12812                    }
12813                }
12814            });
12815    };
12816
12817    var reviewCommentDelete = function (code) {
12818        if (confirm(LOCALIZE.설명_삭제하시겠습니까())) {
12819            $.ajax({
12820                type: 'POST',
12821                data: { code: code, review_code: review_code },
12822                url: ('/shop/delete_review_comment.cm'),
12823                async: false,
12824                dataType: 'json',
12825                success: function (result) {
12826                    if (result.msg == 'SUCCESS') {
12827                        if (load_type == 'modal') {
12828                            $('#comment_count').text(result.comment_count);
12829                            getReviewCommentHtml(review_code, 'modal', $(this));
12830                        } else {
12831                            $('.categorize #comment_count_' + review_code).text(result.comment_count);
12832                            $('.categorize #comment_count_inner_' + review_code).text(result.comment_count);
12833                            $('.categorize-mobile #comment_count_' + review_code).text(result.comment_count);
12834                            $('.categorize-mobile #comment_count_inner_' + review_code).text(result.comment_count);
12835                            getReviewCommentHtml(review_code, 'accordion', $(this));
12836                        }
12837
12838
12839                    } else
12840                        alert(result.msg);
12841                }
12842            });
12843        }
12844    };
12845    return {
12846        init: function (code, type, device_type) {
12847            init(code, type, device_type);
12848        },
12849        imageUploadInit: function (idx) {
12850            imageUploadInit(idx);
12851        },
12852        getReviewCommentHtml: function (code, type, object) {
12853            getReviewCommentHtml(code, type, object);
12854        },
12855        submit: function (t, type, i) {
12856            submit(t, type, i);
12857        },
12858        Delete: function (code) {
12859            reviewCommentDelete(code);
12860        },
12861        EditShow: function (idx) {
12862            EditShow(idx);
12863        },
12864        EditHide: function (idx) {
12865            EditHide(idx);
12866        },
12867        FormShow: function (idx) {
12868            FormShow(idx);
12869        }
12870    };
12871}();
12872
12873var SITE_QNA_COMMENT = function () {
12874    var $form;
12875    var $secret;
12876    var $qna_comment_section;
12877    var qna_code;
12878
12879    var init = function (code) {
12880        qna_code = code;
12881        var $comment_area = $('.comment_textarea');
12882        $secret = $('._secret_btn');
12883        $secret.on('click', function () {
12884            if ($secret.hasClass('active')) {
12885                $secret.removeClass('active');
12886                $('#use_secret_qna').val('N');
12887            } else {
12888                $secret.addClass('active');
12889                $('#use_secret_qna').val('Y');
12890            }
12891        });
12892
12893        $form = $('#qna_form');
12894        $form.find('#qna_image_upload_btn').setUploadImage({
12895            url: '/shop/upload_image.cm',
12896            dropZone: 'icon_img_upload_wrap',
12897            singleFileUploads: true,
12898            formData: { temp: 'Y' }
12899        }, function (res, data) {
12900            $("#qna_image_box").show();
12901            $.each(data, function (e, tmp) {
12902                if (tmp.error == "" || tmp.error == null) {
12903                    var url = CDN_UPLOAD_URL + tmp.url;
12904                    var html = '<span class="file-add"><input type="hidden" name="img" value="' + tmp.name + '"><div class="file-add-bg" style="background: url(' + url + ') no-repeat center center;"></div><em class="del" onclick="POST_COMMENT.removeCommentImg($(this))"></em></span>';
12905                    $("#qna_image_box").append(html);
12906                } else {
12907                    alert(tmp.error);
12908                }
12909            });
12910        });
12911
12912    };
12913
12914    var QnaCommentDelete = function (code, is_visible) {
12915        if (confirm(LOCALIZE.설명_삭제하시겠습니까())) {
12916            $.ajax({
12917                type: 'POST',
12918                data: { code: code, is_visible: is_visible },
12919                url: ('/shop/delete_qna_comment.cm'),
12920                async: false,
12921                dataType: 'json',
12922                success: function (result) {
12923                    if (result.msg == 'SUCCESS') {
12924                        getQnaCommentHtml(qna_code);
12925                    } else
12926                        alert(result.msg);
12927                }
12928            });
12929        }
12930    };
12931
12932    var imageUploadInit = function (idx) {
12933        $form = $('#qna_form');
12934        $form.find('#qna_image_upload_btn').setUploadImage({
12935            url: '/shop/upload_image.cm',
12936            dropZone: 'icon_img_upload_wrap',
12937            singleFileUploads: true,
12938            formData: { temp: 'Y' }
12939        }, function (res, data) {
12940            $("#qna_image_box").show();
12941            $.each(data, function (e, tmp) {
12942                if (tmp.error == "" || tmp.error == null) {
12943                    var url = CDN_UPLOAD_URL + tmp.url;
12944                    var html = '<span class="file-add"><input type="hidden" name="img" value="' + tmp.name + '"><div class="file-add-bg" style="background: url(' + url + ') no-repeat center center;"></div><em class="del" onclick="POST_COMMENT.removeCommentImg($(this))"></em></span>';
12945                    $("#qna_image_box").append(html);
12946                } else {
12947                    alert(tmp.error);
12948                }
12949            });
12950        });
12951
12952        $("#sub_qna_image_box_" + idx).hide();
12953
12954        $('#sub_qna_image_upload_btn_' + idx).setUploadImage({
12955            url: '/shop/upload_image.cm',
12956            dropZone: 'icon_img_upload_wrap',
12957            singleFileUploads: true,
12958            formData: { temp: 'Y' }
12959        }, function (res, data) {
12960            $("#sub_qna_image_box_" + idx).show();
12961            $.each(data, function (e, tmp) {
12962                if (tmp.error == "" || tmp.error == null) {
12963                    var url = CDN_UPLOAD_URL + tmp.url;
12964                    var html = '<span class="file-add"><input type="hidden" name="img" value="' + tmp.name + '"><div class="file-add-bg" style="background: url(' + url + ') no-repeat center center;"></div><em class="del" onclick="POST_COMMENT.removeCommentImg($(this))"></em></span>';
12965                    $("#sub_qna_image_box_" + idx).append(html);
12966                } else {
12967                    alert(tmp.error);
12968                }
12969            });
12970        });
12971
12972        $('#editor_qna_image_upload_btn_' + idx).setUploadImage({
12973            url: '/shop/upload_image.cm',
12974            dropZone: 'icon_img_upload_wrap',
12975            singleFileUploads: true,
12976            formData: { temp: 'Y' }
12977        }, function (res, data) {
12978            $("#editor_qna_image_box_" + idx).show();
12979            $.each(data, function (e, tmp) {
12980                if (tmp.error == "" || tmp.error == null) {
12981                    var url = CDN_UPLOAD_URL + tmp.url;
12982                    var html = '<span class="file-add"><input type="hidden" name="img" value="' + tmp.name + '"><div class="file-add-bg" style="background: url(' + url + ') no-repeat center center;"></div><em class="del" onclick="POST_COMMENT.removeCommentImg($(this))"></em></span>';
12983                    $("#editor_qna_image_box_" + idx).append(html);
12984                } else {
12985                    alert(tmp.error);
12986                }
12987            });
12988        });
12989    };
12990
12991
12992    var getQnaCommentHtml = function (qna_code) {
12993        $qna_comment_section = $('#qna_comment_section');
12994        $.ajax({
12995            type: 'POST',
12996            data: { qna_code: qna_code },
12997            url: ('/ajax/get_qna_comment_list.cm'),
12998            dataType: 'html',
12999            cache: false,
13000            success: function (result) {
13001                $qna_comment_section.html(result);
13002            }
13003        });
13004    };
13005    var submit = function (t, type, i) {
13006        switch (type) {
13007            case 'main': // 1:1 문의 페이지에서 바로 작성하는 폼
13008                var data = $form.serializeObject();
13009                break;
13010            case 'sub_form': // 등록되어 있는 qna에 답변을 다는 폼
13011                var data = $('#sub_qna_form_' + i).serializeObject();
13012                break;
13013            case 'editor': // 등록되어 있는 qna를 수정하는 폼
13014                var data = $('#sub_qna_editor_form_' + i).serializeObject();
13015                break;
13016        }
13017        if (!t.hasClass("btn-writing")) {
13018            t.addClass("btn-writing");
13019        }
13020        $.ajax({
13021            type: 'POST',
13022            data: { data: data, type: type },
13023            url: ('/shop/add_qna_comment.cm'),
13024            dataType: 'json',
13025            cache: false,
13026            success: function (result) {
13027                if (t.hasClass("btn-writing")) {
13028                    t.removeClass("btn-writing");
13029                }
13030                if (result.msg == 'SUCCESS') {
13031                    getQnaCommentHtml(qna_code);
13032                    $("div[id^='sub_qna_image_box_']").hide();
13033                } else
13034                    alert(result.msg);
13035            }
13036        });
13037    };
13038    var EditShow = function (idx) {
13039        var editor_form = $("._sub_form_editor_" + idx);
13040        editor_form.siblings().hide();
13041        editor_form.closest('.comment_area').siblings().hide();
13042        editor_form.closest('.comment_area').css('padding-top', 0);
13043        editor_form.css('margin-top', 0);
13044        editor_form.data('show', 'Y');
13045        editor_form.show();
13046        autosize.update(editor_form.find('textarea'));
13047
13048    };
13049
13050    var EditHide = function (idx) {
13051        var editor_form = $("._sub_form_editor_" + idx);
13052        editor_form.hide();
13053        $('.tools').show();
13054        editor_form.siblings('._comment_body').show();
13055        editor_form.closest('.comment_area').siblings().show();
13056        editor_form.closest('.comment_area').css('padding-top', '');
13057        editor_form.css('margin-top', '');
13058    };
13059
13060    var qnaFormHide = function () {
13061        $("._sub_qna_form").hide();
13062    };
13063
13064    var FormShow = function (idx) {
13065        var sub_form = $("._sub_form_" + idx);
13066
13067        sub_form.data('show', 'Y');
13068        sub_form.show();
13069        var comment_add_body = sub_form.find('._comment_add_body_' + idx);
13070
13071        $('body').off('mouseup.sub_comment')
13072            .on('mouseup.sub_comment', function (e) {
13073                var $c_target = $(e.target);
13074                var $s_form = $c_target.closest('._sub_form_' + idx + ', ._show_sub_form_btn_' + idx);
13075                if ($s_form.length == 0) {
13076
13077                    var text = comment_add_body.val();
13078                    sub_form.data('show', 'N');
13079                    if (text == '') {
13080                        $('body').off('mouseup.sub_comment');
13081                        qnaFormHide();
13082                    }
13083                }
13084            });
13085    };
13086    return {
13087        init: function (code) {
13088            init(code);
13089        },
13090        imageUploadInit: function (idx) {
13091            imageUploadInit(idx);
13092        },
13093        getQnaCommentHtml: function (qna_code) {
13094            getQnaCommentHtml(qna_code);
13095        },
13096        submit: function (t, type, i) {
13097            submit(t, type, i);
13098        },
13099        Delete: function (code, is_visible) {
13100            QnaCommentDelete(code, is_visible);
13101        },
13102        EditShow: function (idx) {
13103            EditShow(idx);
13104        },
13105        EditHide: function (idx) {
13106            EditHide(idx);
13107        },
13108        FormShow: function (idx) {
13109            FormShow(idx);
13110        }
13111    };
13112}();
13113
13114var SITE_SHOP_QNA = function () {
13115    var $qna_wrap;
13116    var $mobile_qna_wrap;
13117    var $form;
13118    var $mobile_form;
13119    var $comment_body;
13120    var $qna_image_box;
13121    var $comment_area;
13122    var $secret;
13123    var $m_secret;
13124    var qna_body;
13125    var $qna_container;
13126    var $qna_form;
13127    var body_input;
13128    var $show_secret_password;
13129    var isIOS, isSafari, $fr_m_custom, $write_header, m_sticky_container_trigger_top, $toolbarContainer;
13130
13131    var init = function (code, qna_secret_type) {
13132        $qna_wrap = $('._qna_wrap');
13133        $qna_form = $('#qna_form');
13134        qna_body = $('#qna_body');
13135        $qna_container = $('#qna_container');
13136        $secret = $('._secret_btn');
13137        $qna_form.find("#qna_image_box").hide();
13138        body_input = $('#body_input');
13139        if (qna_secret_type == 'secret') {
13140            $secret.addClass('active');
13141            $('._secret').val('Y');
13142        } else {
13143            $secret.on('click', function () {
13144                if ($secret.hasClass('active')) {
13145                    $secret.removeClass('active');
13146                    $('._secret').val('N');
13147                } else {
13148                    $secret.addClass('active');
13149                    $('._secret').val('Y');
13150                }
13151            });
13152        }
13153        if ($('._secret').val() != '') {//수정일 경우 비밀글 체크
13154            if ($('._secret').val() == 'Y') {
13155                $secret.addClass('active');
13156                $('._secret').val('Y');
13157            } else {
13158                $secret.removeClass('active');
13159                $('._secret').val('N');
13160            }
13161        }
13162
13163
13164        if (IE_VERSION < 10) {
13165            CKEDITOR.replace('qna_body', {
13166                filebrowserImageUploadUrl: '/ajax/post_image_upload.cm?board_code=' + code
13167            });
13168        } else {
13169            if (android_version() == 4) {
13170                qna_body.addClass('legacy_webview');
13171            }
13172            var image_insert_key2 = 'image_insert_key2';
13173            setFroala('#qna_body', {
13174                code: '',
13175                placeholderText: getLocalizeString('설명_문의작성시개인정보입력주의', '', '개인정보(휴대폰번호, 계좌번호 등)는 입력하지 않도록 주의해주세요.'),
13176                // 파일 첨부, 비디오, 테이블 제외
13177                toolbarButtons: {
13178                    'moreText': {
13179                        'buttons': ['bold', 'italic', 'underline', 'fontSize', 'textColor', 'strikeThrough', 'backgroundColor', 'clearFormatting', '|', 'align', 'formatOL', 'formatUL', '|', image_insert_key2, 'insertLink', '|', 'undo', 'redo', 'html', 'emoticons'],
13180                        'buttonsVisible': 50
13181                    }
13182                },
13183                toolbarButtonsMobile: {
13184                    'moreText': {
13185                        'buttons': [image_insert_key2, 'insertLink', '|', "fontSize", "bold", "italic", "underline", "strikeThrough", "align", "textColor", "clearFormatting", '|', "formatOL", "formatUL"],
13186                        'buttonsVisible': 50
13187                    }
13188                },
13189                image_upload_url: "/ajax/post_image_upload.cm",
13190                image_insert_key: image_insert_key2,
13191                image_align: 'center',
13192                image_display: 'block',
13193                toolbarStickyOffset: 38,
13194                heightMin: 200,
13195                heightMax: 600,
13196                mobile_custom: true,
13197                linkAlwaysBlank: true
13198            });
13199        }
13200
13201        isIOS = /(iPad|iPhone|iPod)/g.test(navigator.userAgent);
13202        isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
13203        $fr_m_custom = $qna_container.find('._fr-m-custom');
13204        $write_header = $qna_container.find('._write_header');
13205        m_sticky_container_trigger_top = $fr_m_custom.offset().top - $fr_m_custom.height();
13206        $toolbarContainer = $fr_m_custom.find('#toolbarContainer');
13207        if (isIOS && isSafari) {
13208            $write_header.css('position', 'absolute');
13209        }
13210        var timeoutTime = isIOS && isSafari ? 100 : 10;
13211        var resize_time;
13212        resizeStickyContainer();
13213        $(window).off('scroll.mobile_write resize.mobile_write').on('scroll.mobile_write resize.mobile_write', function () {
13214            var s_top = $(this).scrollTop();
13215            if (isIOS && isSafari) {
13216                $write_header.css({ '-webkit-transition': 'none', 'transition': 'none', 'top': 0 });
13217                if (s_top > m_sticky_container_trigger_top) {
13218                    $toolbarContainer.css({ '-webkit-transition': 'none', 'transition': 'none', 'top': 0 });
13219                }
13220            }
13221            if (resize_time) {
13222                clearTimeout(resize_time);
13223            }
13224            resize_time = setTimeout(function () {
13225                resizeStickyContainer();
13226            }, timeoutTime);
13227        });
13228        autosize($('.textarea_block textarea'));
13229    };
13230
13231    function resizeStickyContainer() {
13232        var s_top = $(this).scrollTop();
13233        if (isIOS && isSafari) {
13234            $write_header.css({ '-webkit-transition': 'top 100ms', 'transition': 'top 100ms', 'top': s_top + 'px' });
13235            $fr_m_custom.toggleClass('m_sticky_container', s_top > m_sticky_container_trigger_top);
13236            $fr_m_custom.toggleClass('m_sticky_container_ios', s_top > m_sticky_container_trigger_top);
13237            if (s_top > m_sticky_container_trigger_top) {
13238                $toolbarContainer.css({ '-webkit-transition': 'top 100ms', 'transition': 'top 100ms', 'top': s_top + 'px' });
13239                qna_body.find('.fr-view').css('padding-top', '90px');
13240            } else {
13241                $toolbarContainer.css({ '-webkit-transition': 'none', 'transition': 'none', 'top': 'auto' });
13242                qna_body.find('.fr-view').css('padding-top', '');
13243            }
13244        } else {
13245            $fr_m_custom.toggleClass('m_sticky_container', s_top > m_sticky_container_trigger_top);
13246        }
13247        $toolbarContainer.find('.fr-toolbar').css('width', qna_body.find('.fr-view').width());
13248        if ($(window).width() >= 768) {
13249            if ($qna_container.hasClass('bg_on'))
13250                $qna_container.find('#toolbarContainer').toggleClass('pc_sticky_toolbar', s_top > 487);
13251            else
13252                $qna_container.find('#toolbarContainer').toggleClass('pc_sticky_toolbar', s_top > 180);
13253        }
13254    }
13255
13256    var initMobileQna = function () {
13257        $mobile_qna_wrap = $('#prod_detail_content_mobile');
13258        $mobile_form = $mobile_qna_wrap.find('#mobile_qna_form');
13259        $m_secret = $mobile_form.find('._secret_btn');
13260        $mobile_form.find("#mobile_qna_image_box").hide();
13261
13262        $m_secret.on('click', function () {
13263            if ($m_secret.hasClass('active')) {
13264                $m_secret.removeClass('active');
13265                $mobile_form.find('._secret').val('N');
13266            } else {
13267                $m_secret.addClass('active');
13268                $mobile_form.find('._secret').val('Y');
13269            }
13270        });
13271
13272        $mobile_form.find('#mobile_qna_image_upload_btn').setUploadImage({
13273            url: '/shop/upload_image.cm',
13274            dropZone: 'icon_img_upload_wrap',
13275            singleFileUploads: true,
13276            formData: { temp: 'Y' }
13277        }, function (res, data) {
13278            $("#mobile_qna_image_box").show();
13279            $.each(data, function (e, tmp) {
13280                if (tmp.error == "" || tmp.error == null) {
13281                    var url = CDN_UPLOAD_URL + tmp.url;
13282                    var html = '<span class="file-add"><input type="hidden" name="img" value="' + tmp.name + '"><div class="file-add-bg" style="background: url(' + url + ') no-repeat center center;"></div><em class="del" onclick="POST_COMMENT.removeCommentImg($(this))"></em></span>';
13283                    $("#mobile_qna_image_box").append(html);
13284                } else {
13285                    alert(tmp.error);
13286                }
13287            });
13288        });
13289        autosize($('.textarea_block textarea'));
13290    };
13291
13292    var qnaFormShow = function (t) {
13293        var sub_form = $("._sub_form_" + t);
13294
13295        sub_form.data('show', 'Y');
13296        sub_form.show();
13297        var comment_add_body = sub_form.find('._comment_add_body_' + t);
13298
13299        $('body').off('mouseup.sub_comment')
13300            .on('mouseup.sub_comment', function (e) {
13301                var $c_target = $(e.target);
13302                var $s_form = $c_target.closest('._sub_form_' + t + ', ._show_sub_form_btn_' + t);
13303                if ($s_form.length == 0) {
13304
13305                    var text = comment_add_body.val();
13306                    sub_form.data('show', 'N');
13307                    if (text == '') {
13308                        $('body').off('mouseup.sub_comment');
13309                        qnaFormHide();
13310                    }
13311                }
13312            });
13313    };
13314
13315    var EditQnaShow = function (t, c, idx) {
13316        $show_secret_password = $('#show_secret_password');
13317        var $show_link = $(event.target);
13318        if ($show_secret_password.length == 0) {
13319            var left = $(window).width() >= 768 ? 200 : ($(window).width() - 325) / 2;
13320            $show_secret_password = $('<div class="remove-pop" id="show_secret_password" tabindex="0" style="position:absolute;left:' + left + 'px;top:80px;z-index:99999;"><p>' + LOCALIZE.설명_작성시등록하신비밀번호를입력해주세요() + '</p><div class="input_area"><input type="password" placeholder="' + LOCALIZE.설명_비밀번호() + '"><button class="btn btn-primary _confirm">' + LOCALIZE.버튼_확인닫기() + '</button></div></div>').hide();
13321            $show_link.after($show_secret_password);
13322        }
13323        $show_secret_password.find('input').val('');
13324        $show_secret_password.show();
13325        $show_secret_password.off('click', '._confirm')
13326            .on('click', '._confirm', function () {
13327                var secret_pass = $show_secret_password.find('input').val();
13328                CheckSecret(t, secret_pass, function () {
13329                    window.location.href = "?prod_code=" + c + "&qmode=write&back_url=&idx=" + idx;
13330                });
13331                $show_secret_password.hide();
13332            });
13333        $('body').off('mousedown.show_secret')
13334            .on('mousedown.show_secret', function (e) {
13335                var $tmp = $(e.target).closest('#show_secret_password');
13336                if ($tmp.length == 0) {
13337                    $show_secret_password.hide();
13338                    $('body').off('click.show_secret');
13339                }
13340            });
13341    };
13342
13343    var ViewQnaShow = function (t, c, idx, qna_page) {
13344        $show_secret_password = $('#show_secret_password');
13345        var $show_link = $(event.target);
13346        if ($show_secret_password.length == 0) {
13347            var left = $(window).width() >= 768 ? 200 : ($(window).width() - 325) / 2;
13348            $show_secret_password = $('<div class="remove-pop" id="show_secret_password" tabindex="0" style="position:absolute;
13348left:' + left + 'px;top:80px;z-index:99999;"><p>' + LOCALIZE.설명_작성시등록하신비밀번호를입력해주세요() + '</p><div class="input_area"><input type="password" placeholder="' + LOCALIZE.설명_비밀번호() + '"><button class="btn btn-primary _confirm">' + LOCALIZE.버튼_확인닫기() + '</button></div></div>').hide();
13349            $show_link.after($show_secret_password);
13350        }
13351        $show_secret_password.find('input').val('');
13352        $show_secret_password.show();
13353        $show_secret_password.off('click', '._confirm')
13354            .on('click', '._confirm', function () {
13355                var secret_pass = $show_secret_password.find('input').val();
13356                CheckSecret(t, secret_pass, function () {
13357                    SITE_SHOP_DETAIL.viewQnaDetail(idx, qna_page);
13358                    $show_secret_password.hide();
13359                });
13360                $show_secret_password.hide();
13361            });
13362        $('body').off('mousedown.show_secret')
13363            .on('mousedown.show_secret', function (e) {
13364                var $tmp = $(e.target).closest('#show_secret_password');
13365                if ($tmp.length == 0) {
13366                    $show_secret_password.hide();
13367                    $('body').off('click.show_secret');
13368                }
13369            });
13370    };
13371
13372    var CheckSecret = function (code, secret_pass, callback) {
13373        $.ajax({
13374            type: 'post',
13375            data: { code: code, secret_pass: secret_pass, type: 'qna' },
13376            url: '/ajax/check_review_pass.cm',
13377            dataType: 'json',
13378            success: function (result) {
13379                if (result.msg == 'SUCCESS') {
13380                    if (typeof callback == 'function')
13381                        callback();
13382                } else {
13383                    alert(result.msg);
13384                }
13385            }
13386        });
13387    };
13388
13389    var qnaEditShow = function (t) {
13390        var editor_form = $("._sub_form_editor_" + t);
13391        editor_form.siblings().hide();
13392
13393        editor_form.data('show', 'Y');
13394        editor_form.show();
13395        autosize.update(editor_form.find('textarea'));
13396
13397    };
13398
13399    var qnaEditHide = function (t) {
13400        var editor_form = $("._sub_form_editor_" + t);
13401        editor_form.hide();
13402    };
13403
13404    var qnaFormHide = function () {
13405        $("._sub_qna_form").hide();
13406    };
13407
13408    var qnaDelete = function (code, prod_code, secret_pass, q_p) {
13409        if (confirm(LOCALIZE.설명_삭제하시겠습니까())) {
13410            $.ajax({
13411                type: 'POST',
13412                data: { code: code, prod_code: prod_code, secret_pass: secret_pass, qna_page: q_p },
13413                url: ('/shop/delete_qna.cm'),
13414                dataType: 'json',
13415                success: function (result) {
13416                    if (result.msg == 'SUCCESS') {
13417                        if (IS_MOBILE) SITE_SHOP_DETAIL.changeContentTab('qna', 0, q_p);
13418                        else SITE_SHOP_DETAIL.changeContentPCTab('qna', 0, q_p);
13419                        if (typeof CHANNEL_PLUGIN != 'undefined') CHANNEL_PLUGIN.addCountUserProfileAttr('qnaCount', -1);
13420
13421                        SITE_SHOP_DETAIL.getReviewQnaCount(prod_code);
13422
13423                        $.cocoaDialog.close();
13424                    } else
13425                        alert(result.msg);
13426                }
13427            });
13428        }
13429    };
13430
13431    var qnaModify = function (idx, prod_code, secret_pass, is_book, code) {
13432        $.ajax({
13433            type: 'POST',
13434            data: { idx: idx, prod_code: prod_code, secret_pass: secret_pass, is_book: is_book, code: code },
13435            url: ('/shop/show_secret_qna.cm'),
13436            dataType: 'json',
13437            success: function (result) {
13438                if (result.msg == 'SUCCESS') {
13439                    qnaEditShow(idx);
13440                } else
13441                    alert(result.msg);
13442            }
13443        });
13444    };
13445
13446    var qnaShow = function (idx, prod_code, secret_pass, is_book, code) {
13447        $.ajax({
13448            type: 'POST',
13449            data: { idx: idx, prod_code: prod_code, secret_pass: secret_pass, is_book: is_book, code: code },
13450            url: ('/shop/show_secret_qna.cm'),
13451            dataType: 'json',
13452            success: function (result) {
13453                if (result.msg == 'SUCCESS') {
13454                    $("._comment_body_" + idx).html(result.html);
13455                    $('._comment_body_' + idx).closest('.comment_area').find('#_show_content_btn').hide();		// 보기 버튼 제거
13456                    if (result.isSubComment) {
13457                        for (var i in result.sub_comment) {
13458                            var sub_data = result.sub_comment[i];
13459                            $("._comment_child_" + idx + "_" + sub_data.idx).html(sub_data.html);
13460                        }
13461                    }
13462                } else
13463                    alert(result.msg);
13464            }
13465        });
13466    };
13467
13468    var qnaConfirmShow = function (e, idx, prod_code, type, code) {
13469        $show_secret_password = $('#show_secret_password');
13470        var $show_link = $(event.target);
13471        if ($show_secret_password.length == 0) {
13472            var left = $(window).width() >= 768 ? 200 : ($(window).width() - 325) / 2;
13473            $show_secret_password = $('<div class="remove-pop" id="show_secret_password" tabindex="0" style="position:absolute;
13473left:' + left + 'px;top:80px;z-index:99999;"><p>' + LOCALIZE.설명_작성시등록하신비밀번호를입력해주세요() + '</p><div class="input_area"><input type="password" placeholder="' + LOCALIZE.설명_비밀번호() + '"><button class="btn btn-primary _confirm">' + LOCALIZE.버튼_확인닫기() + '</button></div></div>').hide();
13474            $show_link.after($show_secret_password);
13475        }
13476
13477        $show_secret_password.find('input').val('');
13478        $show_secret_password.show();
13479        $show_secret_password.off('click', '._confirm')
13480            .on('click', '._confirm', function () {
13481                var secret_pass = $show_secret_password.find('input').val();
13482                $show_secret_password.hide();
13483                switch (type) {
13484                    case 'show':
13485                        qnaShow(idx, prod_code, secret_pass, 'N', code);
13486                        break;
13487                    case 'modify':
13488                        qnaModify(idx, prod_code, secret_pass, 'N', code);
13489                        break;
13490                    case 'delete':
13491                        qnaDelete(code, prod_code, secret_pass);
13492                        break;
13493
13494                }
13495            });
13496        $('body').off('mousedown.show_secret')
13497            .on('mousedown.show_secret', function (e) {
13498                var $tmp = $(e.target).closest('#show_secret_password');
13499                if ($tmp.length == 0) {
13500                    $show_secret_password.hide();
13501                    $('body').off('click.show_secret');
13502                }
13503            });
13504    };
13505
13506    var imageUploadInit = function (n) {
13507        $("#sub_qna_image_box_" + n).hide();
13508
13509        $('#sub_qna_image_upload_btn_' + n).setUploadImage({
13510            url: '/shop/upload_image.cm',
13511            dropZone: 'icon_img_upload_wrap',
13512            singleFileUploads: true,
13513            formData: { temp: 'Y' }
13514        }, function (res, data) {
13515            $("#sub_qna_image_box_" + n).show();
13516            $.each(data, function (e, tmp) {
13517                if (tmp.error == "" || tmp.error == null) {
13518                    var url = CDN_UPLOAD_URL + tmp.url;
13519                    var html = '<span class="file-add"><input type="hidden" name="img" value="' + tmp.name + '"><div class="file-add-bg" style="background: url(' + url + ') no-repeat center center;"></div><em class="del" onclick="POST_COMMENT.removeCommentImg($(this))"></em></span>';
13520                    $("#sub_qna_image_box_" + n).append(html);
13521                } else {
13522                    alert(tmp.error);
13523                }
13524            });
13525        });
13526
13527        $('#editor_qna_image_upload_btn_' + n).setUploadImage({
13528            url: '/shop/upload_image.cm',
13529            dropZone: 'icon_img_upload_wrap',
13530            singleFileUploads: true,
13531            formData: { temp: 'Y' }
13532        }, function (res, data) {
13533            $("#editor_qna_image_box_" + n).show();
13534            $.each(data, function (e, tmp) {
13535                if (tmp.error == "" || tmp.error == null) {
13536                    var url = CDN_UPLOAD_URL + tmp.url;
13537                    var html = '<span class="file-add"><input type="hidden" name="img" value="' + tmp.name + '"><div class="file-add-bg" style="background: url(' + url + ') no-repeat center center;"></div><em class="del" onclick="POST_COMMENT.removeCommentImg($(this))"></em></span>';
13538                    $("#editor_qna_image_box_" + n).append(html);
13539                } else {
13540                    alert(tmp.error);
13541                }
13542            });
13543        });
13544    };
13545
13546    var submit = function () {
13547        if (IE_VERSION < 10) {
13548            var body = CKEDITOR.instances.qna_body.getData();
13549            body_input.val(body);
13550            $qna_form.submit();
13551        } else {
13552            if (qna_body.hasClass('fr-code-view'))
13553                FroalaEditor('#qna_body').codeView.toggle();
13554            var body = FroalaEditor('#qna_body').html.get(true);
13555            body_input.val(body);
13556            $qna_form.submit();
13557        }
13558    };
13559
13560    var qnaCancel = function () {
13561        if (isIOS && isSafari) {
13562            var s_top = $(this).scrollTop();
13563            $write_header.css({ '-webkit-transition': 'none', 'transition': 'none', 'position': 'fixed', 'top': 0 });
13564            $fr_m_custom.toggleClass('m_sticky_container', s_top > m_sticky_container_trigger_top);
13565            $fr_m_custom.toggleClass('m_sticky_container_ios', s_top > m_sticky_container_trigger_top);
13566            if (s_top > m_sticky_container_trigger_top) {
13567                $toolbarContainer.css({ '-webkit-transition': 'none', 'transition': 'none', 'position': 'fixed', 'top': $write_header.height() + 'px' });
13568            } else {
13569                $toolbarContainer.css({ '-webkit-transition': 'none', 'transition': 'none', 'top': 'auto' });
13570            }
13571        }
13572        history.go(-1);
13573    };
13574
13575    var createHtml = function (prod_idx, review_page, qna_page, paging_on) {
13576        const isMobile_width = (window.innerWidth < SHOP_CONST.WIDTH_MOBILE);
13577        const review_url = isMobile_width ? "/shop/prod_qna_mobile_html.cm" : "/shop/prod_qna_pc_html.cm";
13578
13579        $qna_wrap = $('._qna_wrap');
13580        if ($qna_wrap.length) {
13581            var is_cache = false;
13582            var callback = function (result) {
13583                $qna_wrap.html(result);
13584                if (!is_cache) IMWEB_SESSIONSTORAGE.set(`${isMobile_width ? "PROD_QNA_MOBILE_" : "PROD_QNA_PC_"}` + prod_idx + "_" + review_page + '_' + qna_page, result.replace(/\t+/g, '').trim(), 60);
13585            };
13586
13587            var html = IMWEB_SESSIONSTORAGE.get(`${isMobile_width ? "PROD_QNA_MOBILE_" : "PROD_QNA_PC_"}` + prod_idx + "_" + review_page + '_' + qna_page);
13588            if (html) {
13589                is_cache = true;
13590                callback(html);
13591            } else {
13592                $.ajax({
13593                    type: 'POST',
13594                    data: { prod_idx: prod_idx, review_page: review_page, qna_page: qna_page },
13595                    url: review_url,
13596                    dataType: 'html',
13597                    cache: false,
13598                    success: callback
13599                });
13600            }
13601        }
13602    };
13603
13604    var qnaHide = function (t, c, q_p) {
13605        if (confirm(LOCALIZE.설명_숨기시겠습니까())) {
13606            $.ajax({
13607                type: 'POST',
13608                data: { code: t, prod_code: c, is_visible: false },
13609                url: ('/shop/delete_qna.cm'),
13610                dataType: 'json',
13611                cache: false,
13612                success: function (result) {
13613                    if (result.msg == 'SUCCESS') {
13614                        if (IS_MOBILE) SITE_SHOP_DETAIL.changeContentTab('qna', q_p);
13615                        else SITE_SHOP_DETAIL.changeContentPCTab('qna', q_p);
13616                        if (typeof CHANNEL_PLUGIN != 'undefined') CHANNEL_PLUGIN.addCountUserProfileAttr('qnaCount', -1);
13617                        $.cocoaDialog.close();
13618                    } else
13619                        alert(result.msg);
13620                }
13621            });
13622        }
13623    };
13624
13625    var qnaDeleteShow = function (qna_code, prod_code) {
13626        var $show_secret_password = $('#show_secret_password');
13627        var $show_link = $(event.target);
13628        if ($show_secret_password.length == 0) {
13629            var left = $(window).width() >= 768 ? 200 : ($(window).width() - 325) / 2;
13630            $show_secret_password = $('<div class="remove-pop" id="show_secret_password" tabindex="0" style="position:absolute;left:' + left + 'px;top:80px;z-index:99999;"><p>' + LOCALIZE.설명_작성시등록하신비밀번호를입력해주세요() + '</p><div class="input_area"><input type="password" placeholder="' + LOCALIZE.설명_비밀번호() + '"><button class="btn btn-primary _confirm">' + LOCALIZE.버튼_확인닫기() + '</button></div></div>').hide();
13631            $show_link.after($show_secret_password);
13632        }
13633        $show_secret_password.find('input').val('');
13634        $show_secret_password.show();
13635        $show_secret_password.off('click', '._confirm')
13636            .on('click', '._confirm', function () {
13637                var secret_pass = $show_secret_password.find('input').val();
13638                CheckSecret(qna_code, secret_pass, function () {
13639                    qnaDelete(qna_code, prod_code, secret_pass);
13640                });
13641                $show_secret_password.hide();
13642            });
13643        $('body').off('mousedown.show_secret')
13644            .on('mousedown.show_secret', function (e) {
13645                var $tmp = $(e.target).closest('#show_secret_password');
13646                if ($tmp.length == 0) {
13647                    $show_secret_password.hide();
13648                    $('body').off('click.show_secret');
13649                }
13650            });
13651    };
13652
13653
13654    return {
13655        init: function (code, qna_secret_type) {
13656            init(code, qna_secret_type);
13657        },
13658        initMobileQna: function () {
13659            initMobileQna();
13660        },
13661        submit: function () {
13662            submit();
13663        },
13664        qnaCancel: function () {
13665            qnaCancel();
13666        },
13667        FormShow: function (t) {
13668            qnaFormShow(t);
13669        },
13670        EditQnaShow: function (t, c, idx) {
13671            EditQnaShow(t, c, idx);
13672        },
13673        ViewQnaShow: function (t, c, idx, qna_page) {
13674            ViewQnaShow(t, c, idx, qna_page);
13675        },
13676        Delete: function (code, prod_code, q_p) {
13677            qnaDelete(code, prod_code, q_p);
13678        },
13679        EditShow: function (t) {
13680            qnaEditShow(t);
13681        },
13682        EditHide: function (t) {
13683            qnaEditHide(t);
13684        },
13685        imageUploadInit: function (n) {
13686            imageUploadInit(n);
13687        },
13688        createHtml: function (prod_idx, review_page, qna_page, paging_on) {
13689            createHtml(prod_idx, review_page, qna_page, paging_on);
13690        },
13691        confirmShow: function (e, idx, prod_code, type, code) {
13692            qnaConfirmShow(e, idx, prod_code, type, code);
13693        },
13694        Hide: function (t, c, q_p) {
13695            qnaHide(t, c, q_p);
13696        },
13697        DeleteShow: function (qna_code, prod_code) {
13698            qnaDeleteShow(qna_code, prod_code);
13699        },
13700    };
13701}();
13702
13703var SITE_PERSONAL_QNA = function () {
13704    var $personal_qna_wrap;
13705    var $mobile_qna_wrap;
13706    var $form;
13707    var $mobile_form;
13708    var $comment_body;
13709    var $qna_image_box;
13710
13711
13712    var $comment_area;
13713    var $secret;
13714
13715    var init = function () {
13716        $form = $('#qna_form');
13717        $secret = $form.find('._secret');
13718
13719        $secret.on('click', function () {
13720            if ($secret.hasClass('active')) {
13721                $secret.removeClass('active');
13722                $form.find('#secret').val('N');
13723            } else {
13724                $secret.addClass('active');
13725                $form.find('#secret').val('Y');
13726            }
13727        });
13728
13729        $('#qna_image_upload_btn').setUploadImage({
13730            url: '/shop/upload_image.cm',
13731            dropZone: 'icon_img_upload_wrap',
13732            singleFileUploads: true,
13733            formData: { temp: 'Y' }
13734        }, function (res, data) {
13735            $("#qna_image_box").show();
13736            $.each(data, function (e, tmp) {
13737                if (tmp.error == "" || tmp.error == null) {
13738                    var url = CDN_UPLOAD_URL + tmp.url;
13739                    var html = '<span class="file-add"><input type="hidden" name="img" value="' + tmp.name + '"><div class="file-add-bg" style="background: url(' + url + ') no-repeat center center;"></div><em class="del" onclick="POST_COMMENT.removeCommentImg($(this))"></em></span>';
13740                    $("#qna_image_box").append(html);
13741                } else {
13742                    alert(tmp.error);
13743                }
13744            });
13745        });
13746        autosize($('.textarea_block textarea'));
13747    };
13748
13749    var submit = function (t, type, i) {
13750        switch (type) {
13751            case 'main': // 1:1 문의 페이지에서 바로 작성하는 폼
13752                var data = $form.serializeObject();
13753                break;
13754            case 'sub_form': // 등록되어 있는 qna에 답변을 다는 폼
13755                var data = $('#sub_qna_form_' + i).serializeObject();
13756                break;
13757            case 'editor': // 등록되어 있는 qna를 수정하는 폼
13758                var data = $('#sub_qna_editor_form_' + i).serializeObject();
13759                break;
13760        }
13761        if (!t.hasClass("btn-writing")) {
13762            t.addClass("btn-writing");
13763        }
13764        $.ajax({
13765            type: 'POST',
13766            data: { data: data, type: type, personal_qna: 'Y' },
13767            url: ('/shop/add_qna.cm'),
13768            dataType: 'json',
13769            cache: false,
13770            success: function (result) {
13771                if (t.hasClass("btn-writing")) {
13772                    t.removeClass("btn-writing");
13773                }
13774                if (result.msg == 'SUCCESS') {
13775                    createHtml(result.page);
13776                } else
13777                    alert(result.msg);
13778            }
13779        });
13780    };
13781
13782    var Delete = function (code, prod_code) {
13783        if (confirm(LOCALIZE.설명_삭제하시겠습니까())) {
13784            $.ajax({
13785                type: 'POST',
13786                data: { code: code, prod_code: prod_code },
13787                url: ('/shop/delete_qna.cm'),
13788                dataType: 'json',
13789                success: function (result) {
13790                    if (result.msg == 'SUCCESS') {
13791                        if (typeof CHANNEL_PLUGIN != 'undefined') CHANNEL_PLUGIN.addCountUserProfileAttr('qnaCount', -1);
13792                        createHtml();
13793                    } else
13794                        alert(result.msg);
13795                }
13796            });
13797        }
13798    };
13799
13800    var FormShow = function (t) {
13801        var sub_form = $("._sub_form_" + t);
13802
13803        sub_form.data('show', 'Y');
13804        sub_form.show();
13805        var comment_add_body = sub_form.find('._comment_add_body_' + t);
13806
13807        $('body').off('mouseup.sub_comment')
13808            .on('mouseup.sub_comment', function (e) {
13809                var $c_target = $(e.target);
13810                var $s_form = $c_target.closest('._sub_form_' + t + ', ._show_sub_form_btn_' + t);
13811                if ($s_form.length == 0) {
13812
13813                    var text = comment_add_body.val();
13814                    sub_form.data('show', 'N');
13815                    if (text == '') {
13816                        $('body').off('mouseup.sub_comment');
13817                        FormHide();
13818                    }
13819                }
13820            });
13821    };
13822
13823    var FormHide = function () {
13824        $("._sub_qna_form").hide();
13825    };
13826
13827    var EditShow = function (t) {
13828        var editor_form = $("._sub_form_editor_" + t);
13829        editor_form.siblings('.block-postmeta').find('.write').hide();
13830        editor_form.siblings('.block-postmeta').find('.comment_area').hide();
13831        editor_form.data('show', 'Y');
13832        editor_form.show();
13833    };
13834
13835    var EditHide = function (t) {
13836        var editor_form = $("._sub_form_editor_" + t);
13837        editor_form.hide();
13838        editor_form.siblings('.block-postmeta').find('.write').show();
13839        editor_form.siblings('.block-postmeta').find('.comment_area').show();
13840    };
13841
13842    var imageUploadInit = function (n) {
13843        $("#sub_image_box_" + n).hide();
13844
13845        $('#sub_image_upload_btn_' + n).setUploadImage({
13846            url: '/shop/upload_image.cm',
13847            dropZone: 'icon_img_upload_wrap',
13848            singleFileUploads: true,
13849            formData: { temp: 'Y' }
13850        }, function (res, data) {
13851            $("#sub_image_box_" + n).show();
13852            $.each(data, function (e, tmp) {
13853                if (tmp.error == "" || tmp.error == null) {
13854                    var url = CDN_UPLOAD_URL + tmp.url;
13855                    var html = '<span class="file-add"><input type="hidden" name="img" value="' + tmp.name + '"><div class="file-add-bg" style="background: url(' + url + ') no-repeat center center;"></div><em class="del" onclick="POST_COMMENT.removeCommentImg($(this))"></em></span>';
13856                    $("#sub_image_box_" + n).append(html);
13857                } else {
13858                    alert(tmp.error);
13859                }
13860            });
13861        });
13862
13863        $('#editor_image_upload_btn_' + n).setUploadImage({
13864            url: '/shop/upload_image.cm',
13865            dropZone: 'icon_img_upload_wrap',
13866            singleFileUploads: true,
13867            formData: { temp: 'Y' }
13868        }, function (res, data) {
13869            $("#editor_image_box_" + n).show();
13870            $.each(data, function (e, tmp) {
13871                if (tmp.error == "" || tmp.error == null) {
13872                    var url = CDN_UPLOAD_URL + tmp.url;
13873                    var html = '<span class="file-add"><input type="hidden" name="img" value="' + tmp.name + '"><div class="file-add-bg" style="background: url(' + url + ') no-repeat center center;"></div><em class="del" onclick="POST_COMMENT.removeCommentImg($(this))"></em></span>';
13874                    $("#editor_image_box_" + n).append(html);
13875                } else {
13876                    alert(tmp.error);
13877                }
13878            });
13879        });
13880    };
13881
13882    var createHtml = function (page) {
13883        $personal_qna_wrap = $('._personal_qna_wrap');
13884        $.ajax({
13885            type: 'POST',
13886            data: { page: page },
13887            url: ('/shop/personal_qna_list.cm'),
13888            dataType: 'html',
13889            cache: false,
13890            success: function (result) {
13891                $personal_qna_wrap.html(result);
13892            }
13893        });
13894    };
13895
13896    return {
13897        init: function () {
13898            init();
13899        },
13900        submit: function (t, type, i) {
13901            submit(t, type, i);
13902        },
13903        Delete: function (code, prod_code) {
13904            Delete(code, prod_code);
13905        },
13906        FormShow: function (t) {
13907            FormShow(t);
13908        },
13909        EditShow: function (t) {
13910            EditShow(t);
13911        },
13912        EditHide: function (t) {
13913            EditHide(t);
13914        },
13915        imageUploadInit: function (n) {
13916            imageUploadInit(n);
13917        },
13918        createHtml: function (page) {
13919            createHtml(page);
13920        }
13921    };
13922}();
13923
13924/**
13925 * 배송 서비스 관련 기능
13926 */
13927const SHIPPING_SERVICE = function () {
13928    const isActive = function () {
13929        const activeShippingTab = document.querySelector('.shipping-service-tab.active');
13930        return activeShippingTab !== null && activeShippingTab.getAttribute('data-service-code') !== '';
13931    };
13932
13933    const isZipcodeAvailable = function () {
13934        const activeShippingTab = document.querySelector('.shipping-service-tab.active');
13935        return activeShippingTab && activeShippingTab.getAttribute('data-zipcode-available') === 'Y';
13936    };
13937
13938    /**
13939     * 현재 배송지가 설정되어 있는지 확인
13940     * @returns {boolean} - 배송지가 설정되어 있으면 true, 아니면 false
13941     */
13942    const hasAddress = function () {
13943        const addressLink = document.querySelector('.shipping-service-address-link');
13944        if (!addressLink) {
13945            return false;
13946        }
13947        const addressCode = addressLink.getAttribute('data-address-code');
13948        return addressCode !== null && addressCode !== '';
13949    };
13950
13951    /**
13952     * 비회원 여부 확인
13953     * @returns {boolean} - 비회원이면 true, 회원이면 false
13954     */
13955    const isGuest = function () {
13956        return IS_GUEST === true;
13957    };
13958
13959    /**
13960     * 비회원 일반배송 확인 모달
13961     * @returns {Promise<boolean>} - 일반배송 구매 시 true, 배송 다시 선택 시 false
13962     */
13963    const confirmGuest = async function () {
13964        // 회원인 경우 바로 true 반환
13965        if (!isGuest()) {
13966            return true;
13967        }
13968
13969        const confirmed = await confirmModal({
13970            title: '일반배송으로 구매가 진행돼요',
13971            description: [
13972                '비회원으로 구매 시 일반배송으로 자동 변경돼요.',
13973                '로그인하면 다른 배송 방법으로 주문할 수 있어요.'
13974            ],
13975            confirmText: '일반배송 구매',
13976            cancelText: '배송 다시 선택하기'
13977        });
13978
13979        return confirmed;
13980    };
13981
13982    /**
13983     * 외부채널 구매 시 일반배송 확인 모달
13984     * @returns {Promise<boolean>} - 일반배송 구매 시 true, 배송 다시 선택 시 false
13985     */
13986    const confirmExternalChannel = async function () {
13987        const confirmed = await confirmModal({
13988            title: '일반배송으로 구매가 진행돼요',
13989            description: '선택한 배송은 톡체크아웃·네이버페이와 같은 간편구매에서는 이용할 수 없어요. 구매는 일반배송으로 자동 변경돼요.',
13990            confirmText: '일반배송 구매',
13991            cancelText: '배송 다시 선택하기'
13992        });
13993        return confirmed;
13994    };
13995
13996    const validateAddress = async function () {
13997        if (hasAddress()) {
13998            return true;
13999        }
14000
14001        const confirmed = await confirmModal({
14002            title: '배송지를 등록해야 해당 배송방법을 이용할 수 있어요',
14003            description: '등록된 배송지가 없어서 다른 배송 방법을 선택할 수 없어요. 배송지를 추가하면 이용할 수 있어요.',
14004            confirmText: '배송지 추가',
14005            cancelText: '취소'
14006        });
14007        // 취소 버튼 클릭 시 - 아무것도 하지 않음 (사용자가 배송 다시 선택)
14008        if (confirmed) {
14009            // 모바일 체크 (화면 너비 768px 이하 )
14010            const isMobile = window.innerWidth <= 768;
14011            const redirectUrl = '/shop_mypage/address';
14012
14013            if (isMobile) {
14014                window.location.href = redirectUrl;
14015                return;
14016            }
14017
14018            // PC 환경: 팝업 열기
14019            const popupWidth = 500;
14020            const popupHeight = 800;
14021            const left = (screen.width - popupWidth) / 2;
14022            const top = (screen.height - popupHeight) / 2;
14023            const features = `width=${popupWidth},height=${popupHeight},left=${left},top=${top},scrollbars=yes,resizable=yes`;
14024            window.open(redirectUrl, 'addressPopup', features);
14025            return;
14026        }
14027    }
14028
14029    const confirmZipcode = async function () {
14030        if (isZipcodeAvailable()) {
14031            return true;
14032        }
14033        const confirmed = await confirmModal({
14034            title: '일반배송으로 구매가 진행돼요',
14035            description: [
14036                '선택한 배송은 배송 불가 지역이라 이용할 수 없어요.',
14037                '구매는 일반배송으로 자동 변경돼요.'
14038            ],
14039            confirmText: '일반배송 구매',
14040            cancelText: '배송 다시 선택하기'
14041        });
14042        // 취소 버튼 클릭 시 - 아무것도 하지 않음 (사용자가 배송 다시 선택)
14043        if (!confirmed) {
14044            return false;
14045        }
14046
14047        return true;
14048    };
14049    /**
14050     * 모달 닫기
14051     */
14052    const closeModal = function () {
14053        const $modal = $('#shipping_general_modal');
14054        if ($modal.length) {
14055            $modal.fadeOut(200, function() {
14056                $(this).remove();
14057            });
14058        }
14059    };
14060
14061    /**
14062     * 확인 모달을 표시하고 사용자의 선택을 Promise로 반환
14063     * @param {Object} options - 모달 옵션
14064     * @param {string} options.title - 모달 제목
14065     * @param {string|string[]} options.description - 모달 설명 (문자열 또는 문자열 배열)
14066     * @param {string} options.confirmText - 확인 버튼 텍스트
14067     * @param {string} options.cancelText - 취소 버튼 텍스트
14068     * @returns {Promise<boolean>} - 확인 시 true, 취소 시 false
14069     */
14070    const confirmModal = function (options) {
14071        if (options === undefined) {
14072            return Promise.resolve(true);
14073        }
14074        // description이 문자열이면 배열로 변환
14075        const descriptionArray = Array.isArray(options.description) ? options.description : [options.description];
14076
14077        // description HTML 생성
14078        const descriptionHtml = descriptionArray.map(function(text, index) {
14079            const marginBottom = index < descriptionArray.length - 1 ? '4px' : '0';
14080            return `<p style="margin: 0 0 ${marginBottom} 0;">${text}</p>`;
14081        }).join('');
14082
14083        return new Promise(function (resolve, reject) {
14084            // 기존 모달이 있으면 제거
14085            closeModal();
14086
14087            // 모달 HTML 생성
14088            const modalHtml = `\
14089                <div id="shipping_general_modal" style="\
14090                    position: fixed;\
14091                    top: 0;\
14092                    left: 0;\
14093                    width: 100%;\
14094                    height: 100%;\
14095                    z-index: 17000;\
14096                    display: none;\
14097                ">\
14098                    <!-- Overlay (ë°°ê²½) -->\
14099                    <div class="modal-overlay" style="\
14100                        position: absolute;\
14101                        top: 0;\
14102                        left: 0;\
14103                        width: 100%;\
14104                        height: 100%;\
14105                        background-color: rgba(0, 0, 0, 0.5);\
14106                        z-index: 1;\
14107                    "></div>\
14108                    <!-- Modal Container -->\
14109                    <div style="\
14110                        position: relative;\
14111                        width: 100%;\
14112                        height: 100%;\
14113                        display: flex;\
14114                        align-items: center;\
14115                        justify-content: center;\
14116                        z-index: 2;\
14117                    ">\
14118                        <!-- Modal Content -->\
14119                        <div style="\
14120                            background: #ffffff;\
14121                            border-radius: 4px;\
14122                            box-shadow: 0px 0px 1px 0px rgba(75,81,91,0.2), 0px 20px 40px 0px rgba(75,81,91,0.2);\
14123                            overflow: hidden;\
14124                            max-width: 448px;\
14125                            width: calc(100% - 40px);\
14126                            margin: 0 20px;\
14127                        ">\
14128                            <!-- Modal Header -->\
14129                            <div style="\
14130                                background: white;\
14131                                padding: 20px;\
14132                                display: flex;\
14133                                flex-direction: column;\
14134                                gap: 4px;\
14135                            ">\
14136                                <div style="width: 100%;">\
14137                                    <p style="\
14138                                        font-weight: 700;\
14139                                        font-size: 20px;\
14140                                        line-height: 1.6;\
14141                                        color: #15181e;\
14142                                        margin: 0;\
14143                                    ">${options.title}</p>\
14144                                </div>\
14145                                <div style="width: 100%; margin-top: 4px;">\
14146                                    <div style="\
14147                                        font-weight: 400;\
14148                                        font-size: 14px;\
14149                                        line-height: 1.6;\
14150                                        color: #717680;\
14151                                    ">${descriptionHtml}</div>\
14152                                </div>\
14153                            </div>\
14154                            <!-- Modal Footer -->\
14155                            <div style="\
14156                                padding: 0 20px 20px 20px;\
14157                                display: flex;\
14158                                flex-direction: column;\
14159                                gap: 8px;\
14160                            ">\
14161                                <!-- 확인 버튼 -->\
14162                                <button class="btn-confirm" style="\
14163                                    width: 100%;\
14164                                    height: 48px;\
14165                                    background-color: var(--unit-style-brand_color, #006fff);\
14166                                    border: none;\
14167                                    border-radius: 8px;\
14168                                    font-weight: 700;\
14169                                    font-size: 16px;\
14170                                    line-height: 1.6;\
14171                                    color: #ffffff;\
14172                                    cursor: pointer;\
14173                                    transition: background-color 0.2s;\
14174                                ">${options.confirmText}</button>\
14175                                <!-- 취소 버튼 -->\
14176                                <button class="btn-cancel" style="\
14177                                    width: 100%;\
14178                                    height: 48px;\
14179                                    background-color: #ffffff;\
14180                                    border: 1px solid #d2d7e0;\
14181                                    border-radius: 8px;\
14182                                    font-weight: 700;\
14183                                    font-size: 16px;\
14184                                    line-height: 1.6;\
14185                                    color: #15181e;\
14186                                    cursor: pointer;\
14187                                    transition: background-color 0.2s;\
14188                                ">${options.cancelText}</button>\
14189                            </div>\
14190                        </div>\
14191                    </div>\
14192                </div>`;
14193
14194            // DOM에 모달 추가
14195            $('body').append(modalHtml);
14196            const $modal = $('#shipping_general_modal');
14197
14198            // 확인 버튼 이벤트
14199            $modal.find('.btn-confirm').on('click', function() {
14200                closeModal();
14201                resolve(true);
14202            });
14203
14204            // 취소 버튼 이벤트
14205            $modal.find('.btn-cancel').on('click', function() {
14206                closeModal();
14207                resolve(false);
14208            });
14209
14210            // 배경 클릭 시 모달 닫기
14211            $modal.find('.modal-overlay').on('click', function() {
14212                closeModal();
14213                resolve(false);
14214            });
14215
14216            // ESC 키로 모달 닫기
14217            $(document).on('keydown.shipping_modal', function(e) {
14218                if (e.keyCode === 27) { // ESC
14219                    closeModal();
14220                    resolve(false);
14221                    $(document).off('keydown.shipping_modal');
14222                }
14223            });
14224
14225            // 모달 표시 (페이드인 효과)
14226            $modal.fadeIn(200);
14227        });
14228    };
14229
14230    /**
14231     * 선물하기 빠른배송 불가 확인 모달
14232     * 빠른배송이 활성화된 상태에서 선물하기 시도 시 일반배송으로 변경 안내
14233     * @returns {Promise<boolean>} - 일반배송 구매 시 true, 배송 다시 선택 시 false
14234     */
14235    const confirmGift = async function () {
14236        // 빠른배송이 활성화되어 있지 않으면 바로 true 반환
14237        if (!isActive()) {
14238            return true;
14239        }
14240
14241        const confirmed = await confirmModal({
14242            title: '일반배송으로 선물하기가 진행돼요',
14243            description: [
14244                '선택한 배송은 선물하기에서 사용할 수 없어요.',
14245                '선물하기는 일반배송으로 자동 변경돼요.'
14246            ],
14247            confirmText: '일반배송으로 선물하기',
14248            cancelText: '배송 다시 선택하기'
14249        });
14250
14251        return confirmed;
14252    };
14253
14254    const verify = async function ({ type } = {}) {
14255        const isShippingServiceActive = typeof SHIPPING_SERVICE !== 'undefined' && SHIPPING_SERVICE.isActive();
14256        if (!isShippingServiceActive) {
14257            return true;
14258        }
14259        if (isShippingServiceActive) {
14260            // 외부채널 구매 시 일반배송 확인 모달
14261            if (type === 'npay' || type === 'talkpay') {
14262                const confirmed = await SHIPPING_SERVICE.confirmExternalChannel();
14263                if (!confirmed) {
14264                    return false;
14265                }
14266                SITE_SHOP_DETAIL.setShippingTemplateCode('');
14267                return true;
14268            }
14269
14270
14271            // 비회원인 경우 일반배송 확인 모달
14272            const confirmedGuest = await SHIPPING_SERVICE.confirmGuest();
14273            if (confirmedGuest && SHIPPING_SERVICE.isGuest()) {
14274                // 비회원이 일반배송 구매 선택 시 일반배송으로 변경
14275                SITE_SHOP_DETAIL.setShippingTemplateCode('');
14276                return true;
14277            } else if (!confirmedGuest) {
14278                return false;
14279            }
14280
14281            // 회원인 경우 배송지 확인
14282            if (!SHIPPING_SERVICE.isGuest()) {
14283                const isValidAddress = await SHIPPING_SERVICE.validateAddress();
14284                if (!isValidAddress) {
14285                    return false;
14286                }
14287
14288                const isZipcodeAvailable = SHIPPING_SERVICE.isZipcodeAvailable();
14289                if (!isZipcodeAvailable) {
14290                    const confirmedZipcode = await SHIPPING_SERVICE.confirmZipcode();
14291                    // 일반배송으로 구매 시 일반배송 템플릿 코드 설정
14292                    if (confirmedZipcode) {
14293                        SITE_SHOP_DETAIL.setShippingTemplateCode('');
14294                        return true;
14295                    } else {
14296                        return false;
14297                    }
14298                }
14299            }
14300        }
14301        return true;
14302    };
14303
14304    return {
14305        isActive,
14306        isZipcodeAvailable,
14307        isGuest,
14308        validateAddress,
14309        confirmGuest,
14310        confirmZipcode,
14311        confirmGift,
14312        confirmExternalChannel,
14313        verify,
14314    };
14315}();
14316
14317/**
14318 * 크리에이터 혜택 종료일 카운트다운 배너
14319 *
14320 * 모바일은 기존 sticky 정책을 유지하고, 사이트 헤더 영역 하단에서 16px
14321 * 아래에 위치하도록 --creator-banner-top CSS 변수를 주입한다.
14322 * PC는 상품 이미지 영역(#prod_image_list) 내부로 이동해 absolute 배치한다.
14323 *
14324 * 측정 방식:
14325 *   #doz_header_wrap 의 getBoundingClientRect().bottom (viewport 기준 좌표) 사용.
14326 *   - 스크롤 전: wrap 의 실제 끝점 (자연 흐름)
14327 *   - 스크롤 후: wrap 이 viewport 위로 사라지면 wrap 안의 ._new_fixed_header
14328 *               (viewport 상단에 박힌 fixed 헤더) 의 bottom 으로 fallback
14329 *   - 운영자 추가 top_banner / fixed_header_section 도 동일 방식으로 max 누적
14330 *
14331 *   getBoundingClientRect 는 element 가 자연 흐름이든 fixed 든 동일하게
14332 *   viewport 기준 실시간 좌표를 반환하므로 body class 분기가 불필요.
14333 */
14334var CREATOR_BENEFIT_BANNER = (function () {
14335    var $banner = null;
14336    var $countdown = null;
14337    var $slides = null;
14338    var $placeholder = null;
14339    var slideIndex = 0;
14340    var endTimestamp = 0;
14341    var rafScheduled = false;
14342    var countdownTimer = null;
14343    var slideTimer = null;
14344    var BANNER_GAP_PX_MOBILE = 16; // 모바일:   헤더 하단으로부터 16px
14345    var MOBILE_BREAKPOINT_PX = (typeof SHOP_CONST !== 'undefined' && SHOP_CONST.WIDTH_MOBILE) ? SHOP_CONST.WIDTH_MOBILE : 768;
14346    var SLIDE_INTERVAL_MS = 3000;  // 텍스트 슬라이드 롤링 간격
14347
14348    function isMobileViewport() {
14349        return window.innerWidth < MOBILE_BREAKPOINT_PX;
14350    }
14351
14352    function getHeaderOffsetTop() {
14353        var maxBottom = 0;
14354
14355        // #doz_header_wrap — 사이트 빌더 헤더 본체
14356        var $wrap = $('#doz_header_wrap');
14357        if ($wrap.length) {
14358            var wrapRect = $wrap[0].getBoundingClientRect();
14359            if (wrapRect.bottom > maxBottom) {
14360                maxBottom = wrapRect.bottom;
14361            }
14362            // wrap 자체가 viewport 위로 사라진 경우, 안에 박힌 fixed 헤더의 bottom 으로 fallback
14363            var $fixed = $wrap.find('._new_fixed_header').filter(':visible');
14364            if ($fixed.length) {
14365                var fixedRect = $fixed[0].getBoundingClientRect();
14366                if (fixedRect.bottom > maxBottom) {
14367                    maxBottom = fixedRect.bottom;
14368                }
14369            }
14370        }
14371
14372        // 운영자 추가 상단 배너 (reseller_banner / 일반 banner-container)
14373        $('.top_banner_wrap:visible, .banner-container:visible').each(function () {
14374            var r = this.getBoundingClientRect();
14375            if (r.bottom > maxBottom) maxBottom = r.bottom;
14376        });
14377
14378        // 페이지 내 추가 fixed 섹션
14379        $('._fixed_header_section:visible').each(function () {
14380            var r = this.getBoundingClientRect();
14381            if (r.bottom > maxBottom) maxBottom = r.bottom;
14382        });
14383
14384        return Math.max(0, Math.ceil(maxBottom));
14385    }
14386
14387    function applyTop() {
14388        if (!$banner || !$banner.length) return;
14389        if (!isMobileViewport()) {
14390            document.documentElement.style.removeProperty('--creator-banner-top');
14391            return;
14392        }
14393        var topPx = getHeaderOffsetTop() + BANNER_GAP_PX_MOBILE;
14394        document.documentElement.style.setProperty('--creator-banner-top', topPx + 'px');
14395    }
14396
14397    function applyPlacement() {
14398        if (!$banner || !$banner.length) return;
14399
14400        if (!$placeholder) {
14401            $placeholder = $('<span class="_creator_benefit_banner_placeholder" style="display:none;"></span>');
14402            $banner.before($placeholder);
14403        }
14404
14405        if (isMobileViewport()) {
14406            if ($placeholder && $placeholder.length && !$banner.prev().is($placeholder)) {
14407                $placeholder.after($banner);
14408            }
14409            return;
14410        }
14411
14412        var $imageWrap = $('#prod_detail #prod_image_list:visible').first();
14413        if (!$imageWrap.length) {
14414            $imageWrap = $('#prod_image_list:visible').first();
14415        }
14416        if ($imageWrap.length && !$banner.parent().is($imageWrap)) {
14417            $imageWrap.append($banner);
14418        }
14419    }
14420
14421    function syncLayout() {
14422        applyPlacement();
14423        applyTop();
14424    }
14425
14426    function scheduleLayout() {
14427        if (rafScheduled) return;
14428        rafScheduled = true;
14429        window.requestAnimationFrame(function () {
14430            rafScheduled = false;
14431            syncLayout();
14432        });
14433    }
14434
14435    function pad2(n) {
14436        return n < 10 ? '0' + n : '' + n;
14437    }
14438
14439    function formatRemaining(ms) {
14440        if (ms <= 0) return '00:00:00';
14441        var total = Math.floor(ms / 1000);
14442        var days  = Math.floor(total / 86400);
14443        var hours = Math.floor((total % 86400) / 3600);
14444        var mins  = Math.floor((total % 3600) / 60);
14445        var secs  = total % 60;
14446        var time  = pad2(hours) + ':' + pad2(mins) + ':' + pad2(secs);
14447        return days > 0 ? (days + '일 ' + time) : time;
14448    }
14449
14450    function tickCountdown() {
14451        if (!$countdown || !$countdown.length) return;
14452        var remain = endTimestamp - Date.now();
14453        if (remain <= 0) {
14454            $countdown.text('00:00:00');
14455            destroy();
14456            return;
14457        }
14458        $countdown.text(formatRemaining(remain));
14459    }
14460
14461    // 두 슬라이드 vertical roll: 현재 → is-above (위로 빠짐), 다음 → is-active (아래에서 올라옴).
14462    // transition(0.4s) 종료 후 prev 를 transition off 한 채 is-below 로 reset → 다음 cycle 에 다시 아래에서 올라올 준비.
14463    function tickSlide() {
14464        if (!$slides || $slides.length < 2) return;
14465        var prevIndex = slideIndex;
14466        slideIndex = (slideIndex + 1) % $slides.length;
14467        var $prev = $slides.eq(prevIndex);
14468        var $next = $slides.eq(slideIndex);
14469
14470        $prev.removeClass('is-active is-below').addClass('is-above');
14471        $next.removeClass('is-below is-above').addClass('is-active');
14472
14473        setTimeout(function () {
14474            if (!$prev || !$prev.length) return;
14475            $prev.addClass('no-transition').removeClass('is-above').addClass('is-below');
14476            if ($prev[0]) {
14477                /* eslint-disable-next-line no-unused-expressions */
14478                $prev[0].offsetHeight;
14479            }
14480            $prev.removeClass('no-transition');
14481        }, 450);
14482    }
14483
14484    function clearTimers() {
14485        if (countdownTimer !== null) {
14486            clearInterval(countdownTimer);
14487            countdownTimer = null;
14488        }
14489        if (slideTimer !== null) {
14490            clearInterval(slideTimer);
14491            slideTimer = null;
14492        }
14493    }
14494
14495    function destroy() {
14496        clearTimers();
14497        $(window).off('scroll.creatorBanner resize.creatorBanner load.creatorBanner');
14498        $('#doz_header_wrap').off('transitionend.creatorBanner');
14499        if ($banner && $banner.length) {
14500            $banner.remove();
14501        }
14502        if ($placeholder && $placeholder.length) {
14503            $placeholder.remove();
14504        }
14505        $banner = null;
14506        $countdown = null;
14507        $slides = null;
14508        $placeholder = null;
14509        document.documentElement.style.removeProperty('--creator-banner-top');
14510    }
14511
14512    function init() {
14513        $banner = $('._creator_benefit_banner');
14514        if (!$banner.length) return;
14515
14516        // 카운트다운 영역은 end_date 가 유효한 미래일 때만 가동.
14517        // 시간 제한 없는 혜택(end_date 부재/과거)은 카운트다운만 비활성, 배너 자체는 그대로 표시.
14518        var endStr = $banner.attr('data-creator-end') || '';
14519        var parsed = endStr ? Date.parse(endStr.replace(' ', 'T')) : NaN;
14520        var hasCountdown = !!(parsed && !isNaN(parsed) && parsed > Date.now());
14521
14522        if (hasCountdown) {
14523            endTimestamp = parsed;
14524            $countdown = $banner.find('._creator_benefit_banner__countdown');
14525            tickCountdown();
14526            countdownTimer = setInterval(tickCountdown, 1000);
14527        }
14528
14529        $slides = $banner.find('._creator_benefit_banner__slide');
14530        slideIndex = Math.max(0, $slides.index($slides.filter('.is-active').eq(0)));
14531
14532        syncLayout();
14533        $banner.addClass('is-ready');
14534
14535        // 슬라이드가 2개 이상일 때만 롤링
14536        if ($slides.length >= 2) {
14537            slideTimer = setInterval(tickSlide, SLIDE_INTERVAL_MS);
14538        }
14539
14540        // 헤더 height 변동 및 PC/모바일 전환 트리거
14541        $(window).on('scroll.creatorBanner resize.creatorBanner', scheduleLayout);
14542        $(window).on('load.creatorBanner', scheduleLayout);
14543        $('#doz_header_wrap').on('transitionend.creatorBanner', scheduleLayout);
14544    }
14545
14546    return {
14547        init: init,
14548        destroy: destroy
14549    };
14550})();
14551
14552$(function () {
14553    CREATOR_BENEFIT_BANNER.init();
14554});

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.