@roottale/cms-renderer-next 0.63.0 → 0.64.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/dist/{chunk-SEPUIKVM.js → chunk-LUL73SKP.js} +192 -51
- package/dist/chunk-LUL73SKP.js.map +1 -0
- package/dist/cms-public.css +1 -0
- package/dist/exposure-client.js +1 -1
- package/dist/exposures.d.ts +10 -2
- package/dist/exposures.js +9 -3
- package/dist/routes.js +1 -1
- package/dist/server.js +1 -1
- package/package.json +4 -4
- package/dist/chunk-SEPUIKVM.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# @roottale/cms-renderer-next
|
|
2
2
|
|
|
3
|
+
## 0.64.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- faf7bb5: 관리자 편집 내용을 고객 사이트의 실제 팝업·배너 슬롯에서 미리 볼 수 있도록 출처·창·세션을 검증하는 iframe 연결을 추가합니다. 실제 CSS·배치·닫기 설정을 재사용하며 노출 판정 조회와 숨김 기록 없이 편집합니다. 미리보기 연결과 배포 조건을 문서·Next.js 예제에 설명합니다.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- c2222ee: 매번 표시 안내의 일반 닫기를 현재 화면에만 적용하고, 이전 세션 기록이 설정 변경 후 재표시를 막지 않도록 수정합니다. 오늘 그만 보기의 자정 만료는 유지합니다.
|
|
12
|
+
- cbcea75: 단일 이미지 팝업은 이미지 로딩이 끝난 뒤 표시하고 포커스를 이동해, 원본 비율 높이로 렌더링해도 로딩 중 창이 움직이지 않도록 합니다.
|
|
13
|
+
- 56a9bb5: 여러 팝업의 최대 콘텐츠 높이를 측정할 수 있는 비상호작용 영역을 제공하고 이미지 로딩 후 창을 표시합니다.
|
|
14
|
+
- 5bb9e98: 팝업 초기 포커스를 닫기 버튼 대신 대화상자로 옮겨 불필요한 버튼 강조를 없애고 키보드 탐색을 유지합니다.
|
|
15
|
+
- Updated dependencies [5ccf503]
|
|
16
|
+
- @roottale/cms-client@0.64.0
|
|
17
|
+
- @roottale/cms-core@0.64.0
|
|
18
|
+
|
|
3
19
|
## 0.63.0
|
|
4
20
|
|
|
5
21
|
### Minor Changes
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
} from "./chunk-L3I4W5NP.js";
|
|
7
7
|
|
|
8
8
|
// src/exposures.tsx
|
|
9
|
-
import { createContext, useCallback as
|
|
9
|
+
import { createContext, useCallback as useCallback3, useContext, useEffect as useEffect7, useRef as useRef5, useState as useState8 } from "react";
|
|
10
10
|
|
|
11
11
|
// src/exposure-decision-client.ts
|
|
12
12
|
import { useEffect, useState } from "react";
|
|
@@ -161,7 +161,7 @@ function useExposureDecision(endpoint, pathname, device, slots, enabled = true)
|
|
|
161
161
|
}
|
|
162
162
|
|
|
163
163
|
// src/exposure-group.tsx
|
|
164
|
-
import { useEffect as useEffect5, useLayoutEffect, useRef as useRef3, useState as
|
|
164
|
+
import { useEffect as useEffect5, useLayoutEffect as useLayoutEffect2, useRef as useRef3, useState as useState6 } from "react";
|
|
165
165
|
|
|
166
166
|
// src/exposure-content.tsx
|
|
167
167
|
import { useState as useState2 } from "react";
|
|
@@ -212,7 +212,7 @@ function contentVariant(campaign) {
|
|
|
212
212
|
}
|
|
213
213
|
|
|
214
214
|
// src/exposure-dialog.tsx
|
|
215
|
-
import { useEffect as useEffect2, useRef } from "react";
|
|
215
|
+
import { useEffect as useEffect2, useLayoutEffect, useRef, useState as useState3 } from "react";
|
|
216
216
|
import { createPortal } from "react-dom";
|
|
217
217
|
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
218
218
|
var activeDialog = null;
|
|
@@ -238,10 +238,38 @@ function exposureCanOpenPopup() {
|
|
|
238
238
|
return true;
|
|
239
239
|
});
|
|
240
240
|
}
|
|
241
|
-
function ExposureDialog({ title, className, children, onClose, device, size, themeElement }) {
|
|
241
|
+
function ExposureDialog({ title, className, children, onClose, onReady, device, size, themeElement, manageFocus = true }) {
|
|
242
242
|
const rootRef = useRef(null);
|
|
243
243
|
const dialogRef = useRef(null);
|
|
244
244
|
const closeRef = useRef(onClose);
|
|
245
|
+
const [ready, setReady] = useState3(false);
|
|
246
|
+
useEffect2(() => {
|
|
247
|
+
if (ready) onReady?.(true);
|
|
248
|
+
}, [ready, onReady]);
|
|
249
|
+
useLayoutEffect(() => {
|
|
250
|
+
const root = rootRef.current;
|
|
251
|
+
if (!root) return;
|
|
252
|
+
const pending = Array.from(root.querySelectorAll("img")).filter((image) => !image.complete);
|
|
253
|
+
if (!pending.length) {
|
|
254
|
+
setReady(true);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
const remaining = new Set(pending);
|
|
258
|
+
const settle = (event) => {
|
|
259
|
+
remaining.delete(event.currentTarget);
|
|
260
|
+
if (!remaining.size) setReady(true);
|
|
261
|
+
};
|
|
262
|
+
for (const image of pending) {
|
|
263
|
+
image.addEventListener("load", settle);
|
|
264
|
+
image.addEventListener("error", settle);
|
|
265
|
+
}
|
|
266
|
+
return () => {
|
|
267
|
+
for (const image of pending) {
|
|
268
|
+
image.removeEventListener("load", settle);
|
|
269
|
+
image.removeEventListener("error", settle);
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
}, []);
|
|
245
273
|
useEffect2(() => {
|
|
246
274
|
closeRef.current = onClose;
|
|
247
275
|
}, [onClose]);
|
|
@@ -259,7 +287,7 @@ function ExposureDialog({ title, className, children, onClose, device, size, the
|
|
|
259
287
|
useEffect2(() => {
|
|
260
288
|
const root = rootRef.current;
|
|
261
289
|
const dialog = dialogRef.current;
|
|
262
|
-
if (!root || !dialog) return;
|
|
290
|
+
if (!root || !dialog || !ready) return;
|
|
263
291
|
activeDialog = dialog;
|
|
264
292
|
const previous = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
265
293
|
const previousOverflow = document.body.style.overflow;
|
|
@@ -280,7 +308,7 @@ function ExposureDialog({ title, className, children, onClose, device, size, the
|
|
|
280
308
|
const observer = new MutationObserver(inertBackground);
|
|
281
309
|
observer.observe(document.body, { childList: true });
|
|
282
310
|
const focusable = () => Array.from(dialog.querySelectorAll('a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex="0"]')).filter((element) => !element.hidden && !element.closest('[hidden], [aria-hidden="true"]'));
|
|
283
|
-
(
|
|
311
|
+
if (manageFocus) dialog.focus({ preventScroll: true });
|
|
284
312
|
const handleKey = (event) => {
|
|
285
313
|
if (event.key === "Escape") {
|
|
286
314
|
event.preventDefault();
|
|
@@ -306,7 +334,7 @@ function ExposureDialog({ title, className, children, onClose, device, size, the
|
|
|
306
334
|
if (event.target instanceof Node && !dialog.contains(event.target)) (focusable()[0] ?? dialog).focus({ preventScroll: true });
|
|
307
335
|
};
|
|
308
336
|
document.addEventListener("keydown", handleKey, true);
|
|
309
|
-
document.addEventListener("focusin", handleFocus);
|
|
337
|
+
if (manageFocus) document.addEventListener("focusin", handleFocus);
|
|
310
338
|
return () => {
|
|
311
339
|
observer.disconnect();
|
|
312
340
|
document.removeEventListener("keydown", handleKey, true);
|
|
@@ -315,6 +343,7 @@ function ExposureDialog({ title, className, children, onClose, device, size, the
|
|
|
315
343
|
if (activeDialog === dialog) activeDialog = null;
|
|
316
344
|
document.body.style.overflow = previousOverflow;
|
|
317
345
|
document.body.style.paddingRight = previousPadding;
|
|
346
|
+
if (!manageFocus) return;
|
|
318
347
|
if (previous?.isConnected && !previous.inert) previous.focus({ preventScroll: true });
|
|
319
348
|
else {
|
|
320
349
|
const fallback = document.querySelector('main, [role="main"]') ?? document.body;
|
|
@@ -324,23 +353,23 @@ function ExposureDialog({ title, className, children, onClose, device, size, the
|
|
|
324
353
|
if (!hadTabIndex) fallback.removeAttribute("tabindex");
|
|
325
354
|
}
|
|
326
355
|
};
|
|
327
|
-
}, []);
|
|
328
|
-
return createPortal(/* @__PURE__ */ jsx2("div", { ref: rootRef, className: "rt-exposure-backdrop", "data-device": device, children: /* @__PURE__ */ jsx2("section", { ref: dialogRef, className, role: "dialog", "aria-modal": "true", "aria-label": title, tabIndex: -1, "data-size": size, children }) }), document.body);
|
|
356
|
+
}, [ready, manageFocus]);
|
|
357
|
+
return createPortal(/* @__PURE__ */ jsx2("div", { ref: rootRef, className: "rt-exposure-backdrop", "data-device": device, style: { visibility: ready ? void 0 : "hidden" }, children: /* @__PURE__ */ jsx2("section", { ref: dialogRef, className, role: "dialog", "aria-modal": "true", "aria-label": title, tabIndex: -1, "data-size": size, children }) }), document.body);
|
|
329
358
|
}
|
|
330
359
|
|
|
331
360
|
// src/exposure-carousel.tsx
|
|
332
|
-
import { useCallback, useEffect as useEffect3, useRef as useRef2, useState as
|
|
361
|
+
import { useCallback, useEffect as useEffect3, useRef as useRef2, useState as useState4 } from "react";
|
|
333
362
|
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
334
363
|
function useExposureCarousel(campaigns, enabled, durationMs, popup) {
|
|
335
|
-
const [selection, setSelection] =
|
|
336
|
-
const [elapsedState, setElapsed] =
|
|
337
|
-
const [userPaused, setUserPaused] =
|
|
338
|
-
const [motionAllowed, setMotionAllowed] =
|
|
339
|
-
const [reducedMotion, setReducedMotion] =
|
|
340
|
-
const [hovered, setHovered] =
|
|
341
|
-
const [focused, setFocused] =
|
|
342
|
-
const [touching, setTouching] =
|
|
343
|
-
const [environmentPaused, setEnvironmentPaused] =
|
|
364
|
+
const [selection, setSelection] = useState4({ id: campaigns[0]?.id, cycle: 0 });
|
|
365
|
+
const [elapsedState, setElapsed] = useState4({ id: campaigns[0]?.id, value: 0 });
|
|
366
|
+
const [userPaused, setUserPaused] = useState4(false);
|
|
367
|
+
const [motionAllowed, setMotionAllowed] = useState4(false);
|
|
368
|
+
const [reducedMotion, setReducedMotion] = useState4(false);
|
|
369
|
+
const [hovered, setHovered] = useState4(false);
|
|
370
|
+
const [focused, setFocused] = useState4(false);
|
|
371
|
+
const [touching, setTouching] = useState4(false);
|
|
372
|
+
const [environmentPaused, setEnvironmentPaused] = useState4(false);
|
|
344
373
|
const elapsedRef = useRef2(0);
|
|
345
374
|
const touchOrigin = useRef2(null);
|
|
346
375
|
const index = Math.max(0, campaigns.findIndex((campaign2) => campaign2.id === selection.id));
|
|
@@ -489,9 +518,9 @@ function ExposureCarouselControls({ carousel, campaigns }) {
|
|
|
489
518
|
}
|
|
490
519
|
|
|
491
520
|
// src/exposure-scroll.ts
|
|
492
|
-
import { useEffect as useEffect4, useState as
|
|
521
|
+
import { useEffect as useEffect4, useState as useState5 } from "react";
|
|
493
522
|
function useMobileExposureScroll(enabled) {
|
|
494
|
-
const [hidden, setHidden] =
|
|
523
|
+
const [hidden, setHidden] = useState5(false);
|
|
495
524
|
useEffect4(() => {
|
|
496
525
|
if (!enabled) {
|
|
497
526
|
setHidden(false);
|
|
@@ -596,7 +625,7 @@ function nextExposureMidnight(now, timeZone) {
|
|
|
596
625
|
function exposureWasHidden(campaign, now, legacyPrefixes = []) {
|
|
597
626
|
const persistent = readPreferences(preferencesCookie);
|
|
598
627
|
const preferences = [persistent.get(compactKey(campaign.slotKey, "g")), persistent.get(compactKey(campaign.id, "c")), readPreferences(sessionCookie).get(compactKey(campaign.id, "c"))];
|
|
599
|
-
if (preferences.some((preference) => preference?.startsWith("s:") || preference?.startsWith("v:") || preference === "n:1" || preference?.startsWith("u:") && Number(preference.slice(2)) > now)) return true;
|
|
628
|
+
if (preferences.some((preference) => campaign.repeat !== "always" && (preference?.startsWith("s:") || preference?.startsWith("v:") || preference === "n:1") || preference?.startsWith("u:") && Number(preference.slice(2)) > now)) return true;
|
|
600
629
|
const until = Number(read("local", `rt-exposure-until:${campaign.id}`));
|
|
601
630
|
if (Number.isFinite(until) && until > now) return true;
|
|
602
631
|
if (campaign.repeat === "always") return false;
|
|
@@ -617,6 +646,7 @@ function rememberExposureView(campaign, now) {
|
|
|
617
646
|
if (campaign.delivery && (campaign.repeat === "day" || campaign.repeat === "never")) rememberExposureDismissal(campaign, now);
|
|
618
647
|
}
|
|
619
648
|
function rememberExposureDismissal(campaign, now, forToday = false) {
|
|
649
|
+
if (campaign.repeat === "always" && !forToday) return;
|
|
620
650
|
if (forToday || campaign.repeat === "day") {
|
|
621
651
|
const until = forToday || campaign.delivery?.dayBoundary === "calendar" ? nextExposureMidnight(now, campaign.delivery?.timeZone ?? "Asia/Seoul") : now + 864e5;
|
|
622
652
|
writePreferenceKey(compactKey(campaign.id, "c"), `u:${until}`, true, now);
|
|
@@ -635,7 +665,7 @@ function rememberExposureGroupDismissal(campaigns, now, forToday = false) {
|
|
|
635
665
|
import { Fragment, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
636
666
|
function useSlideScrollReset(campaignId) {
|
|
637
667
|
const ref = useRef3(null);
|
|
638
|
-
|
|
668
|
+
useLayoutEffect2(() => {
|
|
639
669
|
if (ref.current) ref.current.scrollTop = 0;
|
|
640
670
|
}, [campaignId]);
|
|
641
671
|
return ref;
|
|
@@ -646,19 +676,20 @@ function exposureGroupSize(campaigns) {
|
|
|
646
676
|
return "small";
|
|
647
677
|
}
|
|
648
678
|
function ExposureCampaignGroup({ campaigns, runtime, placement, className }) {
|
|
649
|
-
const [visible, setVisible] =
|
|
650
|
-
const [dismissed, setDismissed] =
|
|
651
|
-
const [
|
|
652
|
-
const [
|
|
679
|
+
const [visible, setVisible] = useState6(Boolean(runtime.preview));
|
|
680
|
+
const [dismissed, setDismissed] = useState6(false);
|
|
681
|
+
const [popupReady, setPopupReady] = useState6(false);
|
|
682
|
+
const [height, setMeasuredHeight] = useState6(0);
|
|
683
|
+
const [inView, setInView] = useState6(() => placement !== "inline" || typeof IntersectionObserver === "undefined");
|
|
653
684
|
const measuredRef = useRef3(null);
|
|
654
685
|
const campaignIds = campaigns.map((campaign2) => campaign2.id).join(",");
|
|
655
|
-
const [admission, setAdmission] =
|
|
686
|
+
const [admission, setAdmission] = useState6(() => ({
|
|
656
687
|
ids: campaignIds,
|
|
657
|
-
allowed: new Map(campaigns.map((campaign2) => [campaign2.id, !exposureWasHidden(campaign2, runtime.now(), runtime.legacyPrefixes)]))
|
|
688
|
+
allowed: new Map(campaigns.map((campaign2) => [campaign2.id, Boolean(runtime.preview) || !exposureWasHidden(campaign2, runtime.now(), runtime.legacyPrefixes)]))
|
|
658
689
|
}));
|
|
659
690
|
if (admission.ids !== campaignIds) {
|
|
660
691
|
const allowed = new Map(admission.allowed);
|
|
661
|
-
for (const campaign2 of campaigns) if (!allowed.has(campaign2.id)) allowed.set(campaign2.id, !exposureWasHidden(campaign2, runtime.now(), runtime.legacyPrefixes));
|
|
692
|
+
for (const campaign2 of campaigns) if (!allowed.has(campaign2.id)) allowed.set(campaign2.id, Boolean(runtime.preview) || !exposureWasHidden(campaign2, runtime.now(), runtime.legacyPrefixes));
|
|
662
693
|
setAdmission({ ids: campaignIds, allowed });
|
|
663
694
|
}
|
|
664
695
|
const viewed = useRef3(/* @__PURE__ */ new Set());
|
|
@@ -673,12 +704,16 @@ function ExposureCampaignGroup({ campaigns, runtime, placement, className }) {
|
|
|
673
704
|
const slotKey = campaigns[0]?.slotKey ?? "";
|
|
674
705
|
const hiddenOnScroll = useMobileExposureScroll(runtime.device === "mobile" && runtime.hideMobileBannersOnScroll && (placement === "top" || placement === "bottom"));
|
|
675
706
|
const active = visible && !dismissed && eligible.length > 0;
|
|
676
|
-
const carousel = useExposureCarousel(eligible, active && !hiddenOnScroll && inView, runtime.slideDurationMs, placement === "popup");
|
|
707
|
+
const carousel = useExposureCarousel(eligible, active && (placement !== "popup" || popupReady) && !hiddenOnScroll && inView, runtime.slideDurationMs, placement === "popup");
|
|
677
708
|
const campaign = carousel.campaign;
|
|
678
709
|
const activeId = campaign?.id;
|
|
679
710
|
const setHeight = runtime.setHeight;
|
|
680
711
|
const device = runtime.device;
|
|
681
712
|
const slideRef = useSlideScrollReset(activeId);
|
|
713
|
+
const reportPreview = runtime.preview?.report;
|
|
714
|
+
useEffect5(() => {
|
|
715
|
+
if (active && (placement !== "popup" || popupReady)) reportPreview?.(true);
|
|
716
|
+
}, [active, placement, popupReady, reportPreview]);
|
|
682
717
|
useEffect5(() => {
|
|
683
718
|
const first2 = latest.current.campaigns.find((campaign2) => campaign2.id === firstId);
|
|
684
719
|
if (visible || dismissed || !first2) return;
|
|
@@ -732,10 +767,10 @@ function ExposureCampaignGroup({ campaigns, runtime, placement, className }) {
|
|
|
732
767
|
}, [placement, active]);
|
|
733
768
|
useEffect5(() => {
|
|
734
769
|
const campaign2 = latest.current.campaigns.find((item) => item.id === activeId);
|
|
735
|
-
if (!active || hiddenOnScroll || !inView || !campaign2 || viewed.current.has(campaign2.id)) return;
|
|
770
|
+
if (runtime.preview || !active || hiddenOnScroll || !inView || !campaign2 || viewed.current.has(campaign2.id)) return;
|
|
736
771
|
viewed.current.add(campaign2.id);
|
|
737
772
|
rememberExposureView(campaign2, latest.current.runtime.now());
|
|
738
|
-
}, [active, hiddenOnScroll, inView, activeId]);
|
|
773
|
+
}, [active, hiddenOnScroll, inView, activeId, runtime.preview]);
|
|
739
774
|
useEffect5(() => {
|
|
740
775
|
if (placement !== "top" && placement !== "bottom") return;
|
|
741
776
|
const element = measuredRef.current;
|
|
@@ -759,7 +794,7 @@ function ExposureCampaignGroup({ campaigns, runtime, placement, className }) {
|
|
|
759
794
|
}, [active, placement, slotKey, setHeight, hiddenOnScroll, device]);
|
|
760
795
|
function close(forToday = false) {
|
|
761
796
|
releaseExposurePopup(owner.current);
|
|
762
|
-
rememberExposureGroupDismissal(latest.current.campaigns, latest.current.runtime.now(), forToday);
|
|
797
|
+
if (!latest.current.runtime.preview) rememberExposureGroupDismissal(latest.current.campaigns, latest.current.runtime.now(), forToday);
|
|
763
798
|
setDismissed(true);
|
|
764
799
|
}
|
|
765
800
|
if (!active || !campaign) return null;
|
|
@@ -770,7 +805,8 @@ function ExposureCampaignGroup({ campaigns, runtime, placement, className }) {
|
|
|
770
805
|
const body = /* @__PURE__ */ jsxs3("div", { className: "rt-exposure-carousel", "aria-roledescription": eligible.length > 1 ? "\uCE90\uB7EC\uC140" : void 0, ...carousel.interactions, children: [
|
|
771
806
|
/* @__PURE__ */ jsx4("div", { ref: slideRef, className: "rt-exposure-slide", role: "group", "aria-roledescription": eligible.length > 1 ? "\uC2AC\uB77C\uC774\uB4DC" : void 0, "aria-label": `${carousel.index + 1} / ${eligible.length}`, children: /* @__PURE__ */ jsx4(ExposureContent, { campaign, device: runtime.device }, campaign.id) }),
|
|
772
807
|
closeControls,
|
|
773
|
-
/* @__PURE__ */ jsx4(ExposureCarouselControls, { carousel, campaigns: eligible })
|
|
808
|
+
/* @__PURE__ */ jsx4(ExposureCarouselControls, { carousel, campaigns: eligible }),
|
|
809
|
+
placement === "popup" && eligible.length > 1 ? /* @__PURE__ */ jsx4("div", { className: "rt-exposure-sizer", "aria-hidden": "true", inert: true, style: { visibility: "hidden", pointerEvents: "none" }, children: eligible.map((item) => /* @__PURE__ */ jsx4(ExposureContent, { campaign: item, device: runtime.device }, item.id)) }) : null
|
|
774
810
|
] });
|
|
775
811
|
const classes = `rt-exposure rt-exposure--${campaign.kind} rt-exposure--${contentVariant(campaign)}`;
|
|
776
812
|
if (placement === "popup") return /* @__PURE__ */ jsx4(
|
|
@@ -782,6 +818,8 @@ function ExposureCampaignGroup({ campaigns, runtime, placement, className }) {
|
|
|
782
818
|
device: runtime.device,
|
|
783
819
|
size: exposureGroupSize(eligible),
|
|
784
820
|
themeElement: runtime.themeElement,
|
|
821
|
+
manageFocus: !runtime.preview,
|
|
822
|
+
onReady: setPopupReady,
|
|
785
823
|
children: body
|
|
786
824
|
}
|
|
787
825
|
);
|
|
@@ -818,6 +856,97 @@ function RootTaleExposureCarouselPreview({ campaigns, placement = "popup", devic
|
|
|
818
856
|
] }) });
|
|
819
857
|
}
|
|
820
858
|
|
|
859
|
+
// src/exposure-site-preview.ts
|
|
860
|
+
import { useCallback as useCallback2, useEffect as useEffect6, useRef as useRef4, useState as useState7 } from "react";
|
|
861
|
+
var EXPOSURE_PREVIEW_PROTOCOL = "roottale:exposure-preview";
|
|
862
|
+
var defaultOrigins = ["https://admin.roottale.com"];
|
|
863
|
+
function exposureSitePreviewUrl(siteUrl, editorOrigin, session) {
|
|
864
|
+
try {
|
|
865
|
+
const url = new URL(siteUrl);
|
|
866
|
+
const local = ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname);
|
|
867
|
+
if (url.username || url.password || url.protocol !== "https:" && !(local && url.protocol === "http:")) return null;
|
|
868
|
+
url.pathname = "/";
|
|
869
|
+
url.search = "";
|
|
870
|
+
url.hash = new URLSearchParams({ "rt-exposure-preview": session, origin: editorOrigin }).toString();
|
|
871
|
+
return url.href;
|
|
872
|
+
} catch {
|
|
873
|
+
return null;
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
function previewConnection(origins) {
|
|
877
|
+
if (typeof window === "undefined" || window.parent === window) return null;
|
|
878
|
+
const params = new URLSearchParams(window.location.hash.slice(1));
|
|
879
|
+
const session = params.get("rt-exposure-preview");
|
|
880
|
+
const origin = params.get("origin");
|
|
881
|
+
if (!session || !/^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/i.test(session) || !origin || ![...defaultOrigins, ...origins].includes(origin)) return null;
|
|
882
|
+
return { session, origin };
|
|
883
|
+
}
|
|
884
|
+
function parseExposurePreviewCampaign(value, slots) {
|
|
885
|
+
try {
|
|
886
|
+
if (JSON.stringify(value).length > 32e3) return null;
|
|
887
|
+
const snapshot = readExposureDecision({ campaigns: [value], evaluatedAt: "2026-01-01T00:00:00Z", validUntil: "2026-01-01T00:01:00Z" }, slots, 0, 0);
|
|
888
|
+
const campaign = snapshot?.decision.campaigns[0];
|
|
889
|
+
if (!campaign?.delivery || campaign.content.title.length > 200 || campaign.content.variant.length > 100) return null;
|
|
890
|
+
for (const field of ["eyebrow", "description", "assetAlt", "mobileAssetAlt", "linkLabel"]) {
|
|
891
|
+
const text = campaign.content[field];
|
|
892
|
+
if (text !== void 0 && (typeof text !== "string" || text.length > 2e3)) return null;
|
|
893
|
+
}
|
|
894
|
+
if (campaign.content.linkTarget !== void 0 && !["self", "blank"].includes(campaign.content.linkTarget)) return null;
|
|
895
|
+
return campaign;
|
|
896
|
+
} catch {
|
|
897
|
+
return null;
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
function useExposureSitePreview(slots, origins = defaultOrigins) {
|
|
901
|
+
const [connection] = useState7(() => previewConnection(origins));
|
|
902
|
+
const [draft, setDraft] = useState7(null);
|
|
903
|
+
const revision = useRef4(-1);
|
|
904
|
+
const slotsKey = slots.join(",");
|
|
905
|
+
const reply = useCallback2((type, data = {}) => {
|
|
906
|
+
if (connection) window.parent.postMessage({ type: `${EXPOSURE_PREVIEW_PROTOCOL}:${type}`, session: connection.session, ...data }, connection.origin);
|
|
907
|
+
}, [connection]);
|
|
908
|
+
useEffect6(() => {
|
|
909
|
+
if (!connection) return;
|
|
910
|
+
function receive(event) {
|
|
911
|
+
if (event.source !== window.parent || event.origin !== connection.origin) return;
|
|
912
|
+
const data = event.data;
|
|
913
|
+
if (!data || data.session !== connection.session) return;
|
|
914
|
+
if (data.type === `${EXPOSURE_PREVIEW_PROTOCOL}:connect`) {
|
|
915
|
+
reply("ready");
|
|
916
|
+
return;
|
|
917
|
+
}
|
|
918
|
+
if (data.type !== `${EXPOSURE_PREVIEW_PROTOCOL}:render` || !Number.isSafeInteger(data.revision) || data.revision <= revision.current) return;
|
|
919
|
+
const campaign = parseExposurePreviewCampaign(data.campaign, slotsKey.split(","));
|
|
920
|
+
if (!campaign) {
|
|
921
|
+
reply("error", { revision: data.revision });
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
924
|
+
revision.current = data.revision;
|
|
925
|
+
setDraft({ revision: data.revision, campaign });
|
|
926
|
+
}
|
|
927
|
+
const preventNavigation = (event) => {
|
|
928
|
+
const target = event.target instanceof Element ? event.target : null;
|
|
929
|
+
const previewControl = target?.closest("[data-roottale-exposure-slot], .rt-exposure-backdrop");
|
|
930
|
+
if (event.type === "submit" || !previewControl || target?.closest("a[href]")) {
|
|
931
|
+
event.preventDefault();
|
|
932
|
+
event.stopImmediatePropagation();
|
|
933
|
+
}
|
|
934
|
+
};
|
|
935
|
+
window.addEventListener("message", receive);
|
|
936
|
+
document.addEventListener("click", preventNavigation, true);
|
|
937
|
+
document.addEventListener("submit", preventNavigation, true);
|
|
938
|
+
reply("ready");
|
|
939
|
+
return () => {
|
|
940
|
+
window.removeEventListener("message", receive);
|
|
941
|
+
document.removeEventListener("click", preventNavigation, true);
|
|
942
|
+
document.removeEventListener("submit", preventNavigation, true);
|
|
943
|
+
};
|
|
944
|
+
}, [connection, reply, slotsKey]);
|
|
945
|
+
const draftRevision = draft?.revision ?? -1;
|
|
946
|
+
const report = useCallback2((valid) => reply(valid ? "rendered" : "error", { revision: draftRevision }), [reply, draftRevision]);
|
|
947
|
+
return { active: connection !== null, draft, report };
|
|
948
|
+
}
|
|
949
|
+
|
|
821
950
|
// src/exposures.tsx
|
|
822
951
|
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
823
952
|
var Context = createContext(null);
|
|
@@ -831,12 +960,13 @@ function RootTaleExposureProvider({
|
|
|
831
960
|
slideDurationMs = 5e3,
|
|
832
961
|
hideMobileBannersOnScroll = true,
|
|
833
962
|
legacyDismissalKeyPrefixes = emptyPrefixes,
|
|
963
|
+
previewOrigins,
|
|
834
964
|
children
|
|
835
965
|
}) {
|
|
836
|
-
const [themeElement, setThemeElement] =
|
|
837
|
-
const [device, setDevice] =
|
|
838
|
-
const [heights, setHeights] =
|
|
839
|
-
|
|
966
|
+
const [themeElement, setThemeElement] = useState8(null);
|
|
967
|
+
const [device, setDevice] = useState8("desktop");
|
|
968
|
+
const [heights, setHeights] = useState8({});
|
|
969
|
+
useEffect7(() => {
|
|
840
970
|
const media = window.matchMedia(`(max-width: ${breakpoint - 1}px)`);
|
|
841
971
|
const update = () => setDevice(media.matches ? "mobile" : "desktop");
|
|
842
972
|
update();
|
|
@@ -846,23 +976,24 @@ function RootTaleExposureProvider({
|
|
|
846
976
|
const path = pathname.split(/[?#]/, 1)[0] || "/";
|
|
847
977
|
const home = isExposureHome(path, exposureHomePaths(homePaths));
|
|
848
978
|
const routeKey = `${path}:${device}`;
|
|
849
|
-
const
|
|
850
|
-
const
|
|
851
|
-
|
|
979
|
+
const preview = useExposureSitePreview(slots, previewOrigins);
|
|
980
|
+
const snapshot = useExposureDecision(endpoint, path, device, slots, home && !preview.active);
|
|
981
|
+
const popupVisit = useRef5({ key: routeKey });
|
|
982
|
+
useEffect7(() => {
|
|
852
983
|
popupVisit.current = { key: routeKey };
|
|
853
984
|
}, [routeKey]);
|
|
854
|
-
const now =
|
|
855
|
-
const claimPopup =
|
|
985
|
+
const now = useCallback3(() => snapshot ? exposureServerNow(snapshot) : Date.now(), [snapshot]);
|
|
986
|
+
const claimPopup = useCallback3((slot) => {
|
|
856
987
|
const claimed = popupVisit.current.slot;
|
|
857
988
|
if (claimed && claimed !== slot) return false;
|
|
858
989
|
popupVisit.current.slot = slot;
|
|
859
990
|
return true;
|
|
860
991
|
}, []);
|
|
861
|
-
const setHeight =
|
|
992
|
+
const setHeight = useCallback3((key, placement, height) => {
|
|
862
993
|
setHeights((previous) => previous[key]?.height === height && previous[key]?.placement === placement ? previous : { ...previous, [key]: { placement, height } });
|
|
863
994
|
}, []);
|
|
864
995
|
const value = {
|
|
865
|
-
campaigns: liveCampaigns(snapshot),
|
|
996
|
+
campaigns: preview.active ? preview.draft ? [preview.draft.campaign] : [] : liveCampaigns(snapshot),
|
|
866
997
|
device,
|
|
867
998
|
routeKey,
|
|
868
999
|
home,
|
|
@@ -872,7 +1003,8 @@ function RootTaleExposureProvider({
|
|
|
872
1003
|
claimPopup,
|
|
873
1004
|
setHeight,
|
|
874
1005
|
legacyPrefixes: legacyDismissalKeyPrefixes,
|
|
875
|
-
themeElement
|
|
1006
|
+
themeElement,
|
|
1007
|
+
preview: preview.active ? { revision: preview.draft?.revision ?? 0, report: preview.report } : void 0
|
|
876
1008
|
};
|
|
877
1009
|
const top = Math.max(0, ...Object.values(heights).filter((item) => item.placement === "top").map((item) => item.height));
|
|
878
1010
|
const bottom = Math.max(0, ...Object.values(heights).filter((item) => item.placement === "bottom").map((item) => item.height));
|
|
@@ -881,14 +1013,20 @@ function RootTaleExposureProvider({
|
|
|
881
1013
|
}
|
|
882
1014
|
function RootTaleExposureSlot({ slotKey, placement, allowedVariants, fallback = null, className = "" }) {
|
|
883
1015
|
const runtime = useContext(Context);
|
|
1016
|
+
const previewCampaign = runtime?.preview ? runtime.campaigns[0] : void 0;
|
|
1017
|
+
const reportPreview = runtime?.preview?.report;
|
|
1018
|
+
const validPreview = previewCampaign?.slotKey === slotKey && previewCampaign.kind === (placement === "popup" ? "popup" : "banner") && allowedVariants.includes(previewCampaign.content.variant);
|
|
1019
|
+
useEffect7(() => {
|
|
1020
|
+
if (previewCampaign?.slotKey === slotKey && !validPreview) reportPreview?.(false);
|
|
1021
|
+
}, [previewCampaign, slotKey, validPreview, reportPreview]);
|
|
884
1022
|
if (runtime && !runtime.home) return fallback;
|
|
885
1023
|
const campaigns = runtime?.campaigns.filter((item) => item.slotKey === slotKey && item.kind === (placement === "popup" ? "popup" : "banner") && allowedVariants.includes(item.content.variant)).slice(0, 20);
|
|
886
1024
|
if (!runtime || !campaigns?.length) return fallback;
|
|
887
|
-
return /* @__PURE__ */ jsx5(ExposureCampaignGroup, { campaigns, runtime, placement, className }, `${runtime.routeKey}:${slotKey}`);
|
|
1025
|
+
return /* @__PURE__ */ jsx5(ExposureCampaignGroup, { campaigns, runtime, placement, className }, `${runtime.routeKey}:${slotKey}:${runtime.preview?.revision ?? "live"}`);
|
|
888
1026
|
}
|
|
889
1027
|
function RootTaleLegacyExposure({ campaign, allowedVariants }) {
|
|
890
|
-
const [device, setDevice] =
|
|
891
|
-
|
|
1028
|
+
const [device, setDevice] = useState8("desktop");
|
|
1029
|
+
useEffect7(() => {
|
|
892
1030
|
const media = window.matchMedia("(max-width: 767px)");
|
|
893
1031
|
const update = () => setDevice(media.matches ? "mobile" : "desktop");
|
|
894
1032
|
update();
|
|
@@ -915,8 +1053,11 @@ function RootTaleLegacyExposure({ campaign, allowedVariants }) {
|
|
|
915
1053
|
export {
|
|
916
1054
|
RootTaleExposurePreview,
|
|
917
1055
|
RootTaleExposureCarouselPreview,
|
|
1056
|
+
EXPOSURE_PREVIEW_PROTOCOL,
|
|
1057
|
+
exposureSitePreviewUrl,
|
|
1058
|
+
parseExposurePreviewCampaign,
|
|
918
1059
|
RootTaleExposureProvider,
|
|
919
1060
|
RootTaleExposureSlot,
|
|
920
1061
|
RootTaleLegacyExposure
|
|
921
1062
|
};
|
|
922
|
-
//# sourceMappingURL=chunk-
|
|
1063
|
+
//# sourceMappingURL=chunk-LUL73SKP.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/exposures.tsx","../src/exposure-decision-client.ts","../src/exposure-runtime-data.ts","../src/exposure-group.tsx","../src/exposure-content.tsx","../src/exposure-dialog.tsx","../src/exposure-carousel.tsx","../src/exposure-scroll.ts","../src/exposure-storage.ts","../src/exposure-site-preview.ts"],"sourcesContent":["\"use client\";\n\nimport { createContext, useCallback, useContext, useEffect, useRef, useState, type CSSProperties, type ReactNode } from \"react\";\nimport type { CmsExposureCampaign } from \"@roottale/cms-client/server\";\nimport { useExposureDecision } from \"./exposure-decision-client\";\nimport type { ExposurePlacement } from \"./exposure-content\";\nimport { ExposureCampaignGroup } from \"./exposure-group\";\nimport { exposureHomePaths, isExposureHome } from \"./exposure-home\";\nimport { exposureServerNow, liveCampaigns } from \"./exposure-runtime-data\";\nimport type { ExposureRuntime } from \"./exposure-runtime-context\";\nimport { useExposureSitePreview } from \"./exposure-site-preview\";\n\nexport { RootTaleExposurePreview } from \"./exposure-content\";\nexport { RootTaleExposureCarouselPreview } from \"./exposure-group\";\nexport type { ExposurePlacement } from \"./exposure-content\";\nexport { exposureSitePreviewUrl, EXPOSURE_PREVIEW_PROTOCOL, parseExposurePreviewCampaign } from \"./exposure-site-preview\";\nexport type { CmsExposureCampaign } from \"./exposure-site-preview\";\n\nexport interface RootTaleExposureProviderProps {\n endpoint: string;\n /** 사이트의 usePathname() 값. API 키는 서버에만 둔다. */\n pathname: string;\n slots: readonly string[];\n breakpoint?: number;\n /** 메인 홈만 허용하며 locale 홈은 실제로 만든 exact 경로를 명시한다. */\n homePaths?: readonly string[];\n slideDurationMs?: number;\n hideMobileBannersOnScroll?: boolean;\n legacyDismissalKeyPrefixes?: readonly string[];\n /** 추가 관리자 출처가 필요한 사이트에서만 지정한다. 기본은 canonical ROOT-ADMIN. */\n previewOrigins?: readonly string[];\n children: ReactNode;\n}\n\nconst Context = createContext<ExposureRuntime | null>(null);\nconst emptyPrefixes: readonly string[] = [];\n\nexport function RootTaleExposureProvider({ endpoint, pathname, slots, breakpoint = 768, homePaths, slideDurationMs = 5000,\n hideMobileBannersOnScroll = true, legacyDismissalKeyPrefixes = emptyPrefixes, previewOrigins, children }: RootTaleExposureProviderProps) {\n const [themeElement, setThemeElement] = useState<HTMLDivElement | null>(null);\n const [device, setDevice] = useState<\"desktop\" | \"mobile\">(\"desktop\");\n const [heights, setHeights] = useState<Record<string, { placement: \"top\" | \"bottom\"; height: number }>>({});\n useEffect(() => {\n const media = window.matchMedia(`(max-width: ${breakpoint - 1}px)`);\n const update = () => setDevice(media.matches ? \"mobile\" : \"desktop\");\n update(); media.addEventListener(\"change\", update);\n return () => media.removeEventListener(\"change\", update);\n }, [breakpoint]);\n const path = pathname.split(/[?#]/, 1)[0] || \"/\";\n const home = isExposureHome(path, exposureHomePaths(homePaths));\n const routeKey = `${path}:${device}`;\n const preview = useExposureSitePreview(slots, previewOrigins);\n const snapshot = useExposureDecision(endpoint, path, device, slots, home && !preview.active);\n const popupVisit = useRef<{ key: string; slot?: string }>({ key: routeKey });\n useEffect(() => { popupVisit.current = { key: routeKey }; }, [routeKey]);\n const now = useCallback(() => snapshot ? exposureServerNow(snapshot) : Date.now(), [snapshot]);\n const claimPopup = useCallback((slot: string) => {\n const claimed = popupVisit.current.slot;\n if (claimed && claimed !== slot) return false;\n popupVisit.current.slot = slot;\n return true;\n }, []);\n const setHeight = useCallback((key: string, placement: \"top\" | \"bottom\", height: number) => {\n setHeights((previous) => previous[key]?.height === height && previous[key]?.placement === placement ? previous : { ...previous, [key]: { placement, height } });\n }, []);\n const value: ExposureRuntime = { campaigns: preview.active ? preview.draft ? [preview.draft.campaign] : [] : liveCampaigns(snapshot), device, routeKey, home,\n slideDurationMs: Number.isFinite(slideDurationMs) ? slideDurationMs : 5000, hideMobileBannersOnScroll,\n now, claimPopup, setHeight, legacyPrefixes: legacyDismissalKeyPrefixes, themeElement,\n preview: preview.active ? { revision: preview.draft?.revision ?? 0, report: preview.report } : undefined };\n const top = Math.max(0, ...Object.values(heights).filter((item) => item.placement === \"top\").map((item) => item.height));\n const bottom = Math.max(0, ...Object.values(heights).filter((item) => item.placement === \"bottom\").map((item) => item.height));\n const style = { display: \"contents\", \"--rt-exposure-top-height\": `${top}px`, \"--rt-exposure-bottom-height\": `${bottom}px` } as CSSProperties;\n return <Context.Provider value={value}><div ref={setThemeElement} data-roottale-exposure-root=\"\" style={style}>{children}</div></Context.Provider>;\n}\n\nexport interface RootTaleExposureSlotProps {\n slotKey: string;\n placement: ExposurePlacement;\n allowedVariants: readonly string[];\n /** 배너를 닫아도 같은 방문에서 이전 공지를 이어서 표시하지 않는다. */\n fallback?: ReactNode;\n className?: string;\n}\n\nexport function RootTaleExposureSlot({ slotKey, placement, allowedVariants, fallback = null, className = \"\" }: RootTaleExposureSlotProps) {\n const runtime = useContext(Context);\n const previewCampaign = runtime?.preview ? runtime.campaigns[0] : undefined;\n const reportPreview = runtime?.preview?.report;\n const validPreview = previewCampaign?.slotKey === slotKey && previewCampaign.kind === (placement === \"popup\" ? \"popup\" : \"banner\") && allowedVariants.includes(previewCampaign.content.variant);\n useEffect(() => {\n if (previewCampaign?.slotKey === slotKey && !validPreview) reportPreview?.(false);\n }, [previewCampaign, slotKey, validPreview, reportPreview]);\n if (runtime && !runtime.home) return fallback;\n const campaigns = runtime?.campaigns.filter((item) => item.slotKey === slotKey && item.kind === (placement === \"popup\" ? \"popup\" : \"banner\") && allowedVariants.includes(item.content.variant)).slice(0, 20);\n if (!runtime || !campaigns?.length) return fallback;\n return <ExposureCampaignGroup key={`${runtime.routeKey}:${slotKey}:${runtime.preview?.revision ?? \"live\"}`} campaigns={campaigns} runtime={runtime} placement={placement} className={className} />;\n}\n\n/** 기존 서버 슬롯의 client island. 신규 설치는 Provider/Slot을 사용한다. */\nexport function RootTaleLegacyExposure({ campaign, allowedVariants }: { campaign: CmsExposureCampaign; allowedVariants: readonly string[] }) {\n const [device, setDevice] = useState<\"desktop\" | \"mobile\">(\"desktop\");\n useEffect(() => { const media = window.matchMedia(\"(max-width: 767px)\"); const update = () => setDevice(media.matches ? \"mobile\" : \"desktop\"); update(); media.addEventListener(\"change\", update); return () => media.removeEventListener(\"change\", update); }, []);\n if (!allowedVariants.includes(campaign.content.variant)) return null;\n const runtime: ExposureRuntime = { campaigns: [campaign], device, routeKey: \"legacy\", home: true, slideDurationMs: 5000, hideMobileBannersOnScroll: true,\n now: Date.now, claimPopup: () => true, setHeight: () => undefined, legacyPrefixes: emptyPrefixes, themeElement: null };\n return <ExposureCampaignGroup key={campaign.id} campaigns={[campaign]} runtime={runtime} placement={campaign.kind === \"popup\" ? \"popup\" : \"inline\"} className=\"\" />;\n}\n","\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { readExposureDecision, type TimedExposureDecision } from \"./exposure-runtime-data\";\n\nexport function useExposureDecision(endpoint: string, pathname: string, device: \"mobile\" | \"desktop\", slots: readonly string[], enabled = true) {\n const slotsKey = slots.join(\",\");\n const requestKey = JSON.stringify([endpoint, pathname, device, slotsKey, enabled]);\n const [result, setResult] = useState<{ key: string; snapshot: TimedExposureDecision } | null>(null);\n\n useEffect(() => {\n if (!enabled) return;\n let disposed = false;\n let sequence = 0;\n let controller: AbortController | undefined;\n let pollTimer: ReturnType<typeof setTimeout> | undefined;\n let expiryTimer: ReturnType<typeof setTimeout> | undefined;\n let requestTimer: ReturnType<typeof setTimeout> | undefined;\n let current: TimedExposureDecision | null = null;\n const clearPoll = () => { clearTimeout(pollTimer); clearTimeout(requestTimer); controller?.abort(); sequence++; };\n\n function schedule(delay: number) {\n clearTimeout(pollTimer);\n if (!disposed && document.visibilityState !== \"hidden\") pollTimer = setTimeout(refresh, Math.max(100, Math.min(delay, 60_000)));\n }\n\n function expireKnownCampaigns() {\n if (!current) return;\n // Timer callbacks rerender at known expiry even while a replacement request is offline.\n setResult(performance.now() >= current.expiresAt ? null : { key: requestKey, snapshot: current });\n armExpiry();\n }\n\n function armExpiry() {\n clearTimeout(expiryTimer);\n if (!current || current.expiresAt <= performance.now()) return;\n const ends = current.decision.campaigns.flatMap((campaign) => campaign.endsAt ?\n [current!.receivedAt + Date.parse(campaign.endsAt) - current!.serverAtReceipt] : []);\n const future = [current.expiresAt, ...ends].filter((time) => time > performance.now());\n expiryTimer = setTimeout(expireKnownCampaigns, Math.max(1, Math.min(...future) - performance.now()));\n }\n\n async function refresh() {\n if (disposed || document.visibilityState === \"hidden\") return;\n clearPoll();\n if (current && performance.now() >= current.expiresAt) setResult(null);\n const seq = sequence;\n controller = new AbortController();\n const startedAt = performance.now();\n let refreshed = false;\n requestTimer = setTimeout(() => controller?.abort(), 10_000);\n try {\n const url = new URL(endpoint, window.location.origin);\n if (url.origin !== window.location.origin || url.username || url.password) throw new Error(\"Exposure endpoint must be same origin\");\n url.search = new URLSearchParams({ path: pathname, device }).toString();\n const response = await fetch(url, { cache: \"no-store\", credentials: \"same-origin\", signal: controller.signal });\n if (!response.ok) throw new Error(\"Exposure unavailable\");\n const payload: unknown = await response.json();\n if (disposed || seq !== sequence) return;\n const receivedAt = performance.now();\n current = readExposureDecision(payload, slotsKey.split(\",\"), receivedAt, receivedAt - startedAt);\n refreshed = current !== null;\n setResult(current ? { key: requestKey, snapshot: current } : null);\n armExpiry();\n } catch {\n if (disposed || seq !== sequence) return;\n if (!current || performance.now() >= current.expiresAt) { current = null; setResult(null); }\n } finally {\n if (!disposed && seq === sequence) {\n clearTimeout(requestTimer);\n const now = performance.now();\n const next = current?.nextAt;\n const boundaryDelay = next != null && next > now ? next - now : 60_000;\n // 오래 걸리는 요청에도 현재 노출이 만료 전에 갱신될 여유를 둔다.\n const refreshDelay = refreshed && current ? Math.max(1000, current.expiresAt - now - 10_000) : 60_000;\n schedule(Math.min(boundaryDelay, refreshDelay));\n }\n }\n }\n\n function onVisibility() {\n if (document.visibilityState === \"hidden\") clearPoll();\n else void refresh();\n }\n void refresh();\n document.addEventListener(\"visibilitychange\", onVisibility);\n window.addEventListener(\"focus\", refresh);\n return () => {\n disposed = true; clearPoll(); clearTimeout(expiryTimer);\n document.removeEventListener(\"visibilitychange\", onVisibility);\n window.removeEventListener(\"focus\", refresh);\n };\n }, [endpoint, pathname, device, slotsKey, requestKey, enabled]);\n\n // Layout survives client navigation: never paint the preceding route while effects catch up.\n return enabled && result?.key === requestKey ? result.snapshot : null;\n}\n","import type { CmsExposureCampaign, CmsExposureDecision } from \"@roottale/cms-client/server\";\n\nexport interface TimedExposureDecision {\n decision: CmsExposureDecision;\n receivedAt: number;\n serverAtReceipt: number;\n expiresAt: number;\n nextAt: number | null;\n}\n\nfunction validLink(value: unknown, internal = true): boolean {\n if (value == null) return true;\n if (typeof value !== \"string\" || /[\\u0000-\\u0020\\u007f\\\\]/.test(value)) return false;\n if (internal && value.startsWith(\"/\") && !value.startsWith(\"//\")) return true;\n try { const url = new URL(value); return url.protocol === \"https:\" && !url.username && !url.password; } catch { return false; }\n}\n\n/** 판정 응답이 손상돼도 페이지 본문은 유지하는 브라우저 입력 경계. */\nexport function readExposureDecision(value: unknown, slots: readonly string[], receivedAt: number, elapsed: number): TimedExposureDecision | null {\n if (!value || typeof value !== \"object\") return null;\n const data = value as CmsExposureDecision;\n const evaluated = Date.parse(data.evaluatedAt);\n const validUntil = Date.parse(data.validUntil);\n if (!Number.isFinite(evaluated) || !Number.isFinite(validUntil) || validUntil <= evaluated ||\n !Array.isArray(data.campaigns) || data.campaigns.length > 200) return null;\n // 편도 시간을 모르므로 왕복 전체를 차감한다. 늦은 응답으로 유효기간을 늘리지 않는다.\n const serverAtReceipt = evaluated + Math.max(0, elapsed);\n const expiresAt = receivedAt + Math.min(validUntil - serverAtReceipt, 120_000);\n if (expiresAt <= receivedAt) return null;\n const campaigns: CmsExposureCampaign[] = [];\n for (const item of data.campaigns) {\n if (!item || typeof item.id !== \"string\" || !slots.includes(item.slotKey) ||\n (item.kind !== \"banner\" && item.kind !== \"popup\") || !item.content ||\n typeof item.content.title !== \"string\" || typeof item.content.variant !== \"string\" ||\n ![\"always\", \"session\", \"day\", \"never\"].includes(item.repeat) ||\n !validLink(item.content.linkUrl) || !validLink(item.content.assetUrl, false) || !validLink(item.content.mobileAssetUrl, false)) return null;\n if (item.endsAt != null && !Number.isFinite(Date.parse(item.endsAt))) return null;\n if (item.delivery) {\n const delivery = item.delivery;\n if (delivery.version !== 2 || !Number.isFinite(delivery.delaySeconds) || delivery.delaySeconds < 0 || delivery.delaySeconds > 30 ||\n ![null, 25, 50, 75].includes(delivery.scrollPercent) || ![\"small\", \"medium\", \"large\"].includes(delivery.size) ||\n ![\"calendar\", \"rolling\"].includes(delivery.dayBoundary) || typeof delivery.dismissible !== \"boolean\" || typeof delivery.hideForToday !== \"boolean\") return null;\n try { new Intl.DateTimeFormat(\"en\", { timeZone: delivery.timeZone }); } catch { return null; }\n }\n const popupSlot = campaigns.find((existing) => existing.kind === \"popup\")?.slotKey;\n if (!campaigns.some((existing) => existing.id === item.id) &&\n campaigns.filter((existing) => existing.slotKey === item.slotKey).length < 20 &&\n (item.kind !== \"popup\" || popupSlot === undefined || popupSlot === item.slotKey)) campaigns.push(item);\n }\n const boundary = data.nextChangeAt == null ? null : Date.parse(data.nextChangeAt);\n if (boundary !== null && !Number.isFinite(boundary)) return null;\n return { decision: { ...data, campaigns }, receivedAt, serverAtReceipt, expiresAt,\n nextAt: boundary === null ? null : receivedAt + boundary - serverAtReceipt };\n}\n\nexport function exposureServerNow(snapshot: TimedExposureDecision): number {\n return snapshot.serverAtReceipt + performance.now() - snapshot.receivedAt;\n}\n\nexport function liveCampaigns(snapshot: TimedExposureDecision | null): CmsExposureCampaign[] {\n if (!snapshot || performance.now() >= snapshot.expiresAt) return [];\n const now = exposureServerNow(snapshot);\n return snapshot.decision.campaigns.filter((campaign) => !campaign.endsAt || Date.parse(campaign.endsAt) > now);\n}\n","\"use client\";\n\nimport { useEffect, useLayoutEffect, useRef, useState } from \"react\";\nimport type { CmsExposureCampaign } from \"@roottale/cms-client/server\";\nimport { ExposureContent, contentVariant, type ExposurePlacement } from \"./exposure-content\";\nimport { ExposureDialog, exposureCanOpenPopup, releaseExposurePopup, reserveExposurePopup } from \"./exposure-dialog\";\nimport { ExposureCarouselControls, useExposureCarousel } from \"./exposure-carousel\";\nimport type { ExposureRuntime } from \"./exposure-runtime-context\";\nimport { useMobileExposureScroll } from \"./exposure-scroll\";\nimport { exposureWasHidden, rememberExposureGroupDismissal, rememberExposureView } from \"./exposure-storage\";\n\nfunction useSlideScrollReset(campaignId: string | undefined) {\n const ref = useRef<HTMLDivElement>(null);\n useLayoutEffect(() => {\n // 모달·포커스는 유지하고 새 안내의 내용만 처음부터 읽을 수 있게 한다.\n if (ref.current) ref.current.scrollTop = 0;\n }, [campaignId]);\n return ref;\n}\n\nfunction exposureGroupSize(campaigns: readonly CmsExposureCampaign[]): \"small\" | \"medium\" | \"large\" {\n if (campaigns.some((campaign) => campaign.delivery?.size === \"large\")) return \"large\";\n if (campaigns.some((campaign) => (campaign.delivery?.size ?? \"medium\") === \"medium\")) return \"medium\";\n return \"small\";\n}\n\nexport function ExposureCampaignGroup({ campaigns, runtime, placement, className }: {\n campaigns: readonly CmsExposureCampaign[]; runtime: ExposureRuntime; placement: ExposurePlacement; className: string;\n}) {\n const [visible, setVisible] = useState(Boolean(runtime.preview));\n const [dismissed, setDismissed] = useState(false);\n const [popupReady, setPopupReady] = useState(false);\n const [height, setMeasuredHeight] = useState(0);\n const [inView, setInView] = useState(() => placement !== \"inline\" || typeof IntersectionObserver === \"undefined\");\n const measuredRef = useRef<HTMLDivElement>(null);\n const campaignIds = campaigns.map((campaign) => campaign.id).join(\",\");\n const [admission, setAdmission] = useState(() => ({ ids: campaignIds,\n allowed: new Map(campaigns.map((campaign) => [campaign.id, Boolean(runtime.preview) || !exposureWasHidden(campaign, runtime.now(), runtime.legacyPrefixes)])) }));\n if (admission.ids !== campaignIds) {\n const allowed = new Map(admission.allowed);\n for (const campaign of campaigns) if (!allowed.has(campaign.id)) allowed.set(campaign.id, Boolean(runtime.preview) || !exposureWasHidden(campaign, runtime.now(), runtime.legacyPrefixes));\n setAdmission({ ids: campaignIds, allowed });\n }\n const viewed = useRef(new Set<string>());\n const owner = useRef(Symbol(\"exposure-group\"));\n const latest = useRef({ campaigns, runtime });\n useEffect(() => { latest.current = { campaigns, runtime }; }, [campaigns, runtime]);\n const eligible = campaigns.filter((campaign) => admission.allowed.get(campaign.id));\n const first = eligible[0];\n const firstId = first?.id;\n const slotKey = campaigns[0]?.slotKey ?? \"\";\n const hiddenOnScroll = useMobileExposureScroll(runtime.device === \"mobile\" && runtime.hideMobileBannersOnScroll && (placement === \"top\" || placement === \"bottom\"));\n const active = visible && !dismissed && eligible.length > 0;\n const carousel = useExposureCarousel(eligible, active && (placement !== \"popup\" || popupReady) && !hiddenOnScroll && inView, runtime.slideDurationMs, placement === \"popup\");\n const campaign = carousel.campaign;\n const activeId = campaign?.id;\n const setHeight = runtime.setHeight;\n const device = runtime.device;\n const slideRef = useSlideScrollReset(activeId);\n const reportPreview = runtime.preview?.report;\n useEffect(() => { if (active && (placement !== \"popup\" || popupReady)) reportPreview?.(true); }, [active, placement, popupReady, reportPreview]);\n\n useEffect(() => {\n const first = latest.current.campaigns.find((campaign) => campaign.id === firstId);\n if (visible || dismissed || !first) return;\n let opened = false;\n let delayPassed = false;\n const scrollPercent = first.delivery?.scrollPercent;\n const tryOpen = () => {\n if (opened || !delayPassed || document.visibilityState === \"hidden\") return;\n if (scrollPercent) {\n const scrollable = document.documentElement.scrollHeight - window.innerHeight;\n if (scrollable <= 0 || window.scrollY / scrollable * 100 < scrollPercent) return;\n }\n if (placement === \"popup\") {\n if (!exposureCanOpenPopup() || !reserveExposurePopup(owner.current)) return;\n if (!latest.current.runtime.claimPopup(first.slotKey)) { releaseExposurePopup(owner.current); return; }\n }\n opened = true; setVisible(true); clearInterval(retry);\n };\n const timer = setTimeout(() => { delayPassed = true; tryOpen(); }, placement === \"popup\" ? (first.delivery?.delaySeconds ?? 0) * 1000 : 0);\n const retry = setInterval(tryOpen, 500);\n window.addEventListener(\"scroll\", tryOpen, { passive: true });\n window.addEventListener(\"resize\", tryOpen); document.addEventListener(\"visibilitychange\", tryOpen);\n return () => { clearTimeout(timer); clearInterval(retry); window.removeEventListener(\"scroll\", tryOpen); window.removeEventListener(\"resize\", tryOpen); document.removeEventListener(\"visibilitychange\", tryOpen); };\n }, [firstId, visible, dismissed, placement]);\n\n useEffect(() => { const token = owner.current; return () => releaseExposurePopup(token); }, []);\n useEffect(() => {\n if (placement !== \"inline\" || !active) return;\n if (typeof IntersectionObserver === \"undefined\") return;\n const element = measuredRef.current;\n const observer = new IntersectionObserver(([entry]) => setInView(Boolean(entry?.isIntersecting)), { threshold: 0.1 });\n if (element) observer.observe(element);\n return () => observer.disconnect();\n }, [placement, active]);\n useEffect(() => {\n const campaign = latest.current.campaigns.find((item) => item.id === activeId);\n if (runtime.preview || !active || hiddenOnScroll || !inView || !campaign || viewed.current.has(campaign.id)) return;\n // 현재 묶음의 방문 중 한 번 기록은 다음 슬라이드의 표시를 방해하지 않는다.\n viewed.current.add(campaign.id); rememberExposureView(campaign, latest.current.runtime.now());\n }, [active, hiddenOnScroll, inView, activeId, runtime.preview]);\n\n useEffect(() => {\n if (placement !== \"top\" && placement !== \"bottom\") return;\n const element = measuredRef.current;\n const measure = () => {\n const rect = element?.getBoundingClientRect();\n const fullHeight = active && rect ? Math.ceil(rect.height) : 0;\n setMeasuredHeight((previous) => active ? Math.max(previous, fullHeight) : 0);\n setHeight(slotKey, placement, hiddenOnScroll ? 0 : placement === \"top\" && device === \"desktop\" && rect ? Math.max(0, Math.min(fullHeight, Math.ceil(rect.bottom))) : fullHeight);\n };\n measure();\n const observer = typeof ResizeObserver === \"undefined\" ? null : new ResizeObserver(measure);\n if (element) observer?.observe(element);\n window.addEventListener(\"resize\", measure); window.addEventListener(\"scroll\", measure, { passive: true });\n return () => { observer?.disconnect(); window.removeEventListener(\"resize\", measure); window.removeEventListener(\"scroll\", measure); setHeight(slotKey, placement, 0); };\n }, [active, placement, slotKey, setHeight, hiddenOnScroll, device]);\n\n function close(forToday = false) {\n releaseExposurePopup(owner.current);\n if (!latest.current.runtime.preview) rememberExposureGroupDismissal(latest.current.campaigns, latest.current.runtime.now(), forToday);\n setDismissed(true);\n }\n if (!active || !campaign) return null;\n const closeControls = <>\n {placement === \"popup\" || placement === \"bottom\" || (campaign.delivery?.dismissible ?? true) ? <button type=\"button\" data-exposure-close=\"\" className=\"rt-exposure__close\" aria-label=\"안내 닫기\" onClick={() => close()}>×</button> : null}\n {placement === \"popup\" && campaigns.some((item) => item.delivery?.hideForToday) ? <button type=\"button\" className=\"rt-exposure__hide-today\" onClick={() => close(true)}>오늘 그만 보기</button> : null}\n </>;\n const body = <div className=\"rt-exposure-carousel\" aria-roledescription={eligible.length > 1 ? \"캐러셀\" : undefined} {...carousel.interactions}>\n <div ref={slideRef} className=\"rt-exposure-slide\" role=\"group\" aria-roledescription={eligible.length > 1 ? \"슬라이드\" : undefined} aria-label={`${carousel.index + 1} / ${eligible.length}`}>\n <ExposureContent key={campaign.id} campaign={campaign} device={runtime.device} />\n </div>\n {closeControls}\n <ExposureCarouselControls carousel={carousel} campaigns={eligible} />\n {placement === \"popup\" && eligible.length > 1 ? <div className=\"rt-exposure-sizer\" aria-hidden=\"true\" inert style={{ visibility: \"hidden\", pointerEvents: \"none\" }}>\n {eligible.map((item) => <ExposureContent key={item.id} campaign={item} device={runtime.device} />)}\n </div> : null}\n </div>;\n const classes = `rt-exposure rt-exposure--${campaign.kind} rt-exposure--${contentVariant(campaign)}`;\n if (placement === \"popup\") return <ExposureDialog title=\"홈페이지 안내\" className={`${classes} ${className}`} onClose={() => close()}\n device={runtime.device} size={exposureGroupSize(eligible)} themeElement={runtime.themeElement} manageFocus={!runtime.preview} onReady={setPopupReady}>{body}</ExposureDialog>;\n return <div className={className} data-roottale-exposure-slot={slotKey} data-roottale-exposure-placement={placement}\n style={placement === \"bottom\" || runtime.device === \"mobile\" && placement === \"top\" ? { height } : { minHeight: height }}>\n <div ref={measuredRef} className={`rt-exposure-position rt-exposure-position--${placement}`} data-device={runtime.device}\n data-scroll-hidden={hiddenOnScroll ? \"true\" : \"false\"} aria-hidden={hiddenOnScroll || undefined} inert={hiddenOnScroll || undefined}>\n <aside className={classes} aria-label=\"홈페이지 안내\">{body}</aside>\n </div>\n </div>;\n}\n\nexport function RootTaleExposureCarouselPreview({ campaigns, placement = \"popup\", device = \"desktop\", slideDurationMs = 5000 }: {\n campaigns: readonly CmsExposureCampaign[]; placement?: ExposurePlacement; device?: \"desktop\" | \"mobile\"; slideDurationMs?: number;\n}) {\n const carousel = useExposureCarousel(campaigns, true, slideDurationMs, true);\n const campaign = carousel.campaign;\n const slideRef = useSlideScrollReset(campaign?.id);\n if (!campaign) return null;\n return <div className={`rt-exposure rt-exposure--${campaign.kind} rt-exposure--preview`} data-placement={placement} data-device={device} data-size={exposureGroupSize(campaigns)}>\n <div className=\"rt-exposure-carousel\" {...carousel.interactions}>\n <div ref={slideRef} className=\"rt-exposure-slide\">\n <ExposureContent key={campaign.id} campaign={campaign} device={device} />\n </div>\n <ExposureCarouselControls carousel={carousel} campaigns={campaigns} />\n </div>\n </div>;\n}\n","\"use client\";\n\nimport { useState, type ReactNode } from \"react\";\nimport type { CmsExposureCampaign } from \"@roottale/cms-client/server\";\n\nexport type ExposurePlacement = \"top\" | \"inline\" | \"bottom\" | \"popup\";\n\nexport function ExposureContent({ campaign, device, controls }: {\n campaign: CmsExposureCampaign;\n device: \"mobile\" | \"desktop\";\n controls?: ReactNode;\n}) {\n const { content } = campaign;\n const [failedUrls, setFailedUrls] = useState<readonly string[]>([]);\n const preferred = device === \"mobile\" && content.mobileAssetUrl ? content.mobileAssetUrl : content.assetUrl;\n const textOnly = campaign.delivery?.version === 2 && [\"notice\", \"card\"].includes(content.variant);\n const assetUrl = textOnly ? undefined : preferred && !failedUrls.includes(preferred) ? preferred : content.assetUrl && !failedUrls.includes(content.assetUrl) ? content.assetUrl : undefined;\n const assetAlt = device === \"mobile\" && assetUrl === content.mobileAssetUrl ? content.mobileAssetAlt ?? content.assetAlt : content.assetAlt;\n const imageOnly = content.variant === \"image\" && Boolean(assetUrl);\n const linkProps = { href: content.linkUrl, ...(content.linkTarget === \"blank\" ? { target: \"_blank\", rel: \"noopener noreferrer\" } : {}) };\n const asset = assetUrl ? <img className=\"rt-exposure__asset\" src={assetUrl} alt={assetAlt ?? content.title}\n onError={() => setFailedUrls((previous) => [...previous, assetUrl])} /> : null;\n return <div className={`rt-exposure__body${imageOnly ? \" rt-exposure__body--image\" : \"\"}`}>\n {imageOnly && content.linkUrl ? <a {...linkProps} aria-label={content.linkLabel ?? content.title}>{asset}</a> : asset}\n <div className={`rt-exposure__copy${imageOnly ? \" rt-exposure__sr-only\" : \"\"}`}>\n {content.eyebrow ? <p className=\"rt-exposure__eyebrow\">{content.eyebrow}</p> : null}\n <h2 className=\"rt-exposure__title\">{content.title}</h2>\n {content.description ? <p className=\"rt-exposure__description\">{content.description}</p> : null}\n {!imageOnly && content.linkUrl && content.linkLabel ? <a className=\"rt-exposure__link\" {...linkProps}>{content.linkLabel}</a> : null}\n </div>\n {controls}\n </div>;\n}\n\n/** 실제 콘텐츠 출력과 같지만 방문 기록·조회·포커스 이동이 없는 편집기 미리보기. */\nexport function RootTaleExposurePreview({ campaign, placement = campaign.kind === \"popup\" ? \"popup\" : \"inline\", device = \"desktop\", className = \"\" }: {\n campaign: CmsExposureCampaign;\n placement?: ExposurePlacement;\n device?: \"mobile\" | \"desktop\";\n className?: string;\n}) {\n return <div className={`rt-exposure rt-exposure--${campaign.kind} rt-exposure--${contentVariant(campaign)} rt-exposure--preview ${className}`}\n data-placement={placement} data-device={device} data-size={campaign.delivery?.size ?? \"medium\"}>\n <ExposureContent campaign={campaign} device={device} />\n </div>;\n}\n\nexport function contentVariant(campaign: CmsExposureCampaign): string {\n // 공개 문자열을 임의 CSS 클래스 목록으로 확장하지 않는다.\n return campaign.content.variant.replace(/[^a-zA-Z0-9_-]/g, \"\");\n}\n","\"use client\";\n\nimport { useEffect, useLayoutEffect, useRef, useState, type ReactNode } from \"react\";\nimport { createPortal } from \"react-dom\";\n\nlet activeDialog: HTMLElement | null = null;\nlet openingOwner: symbol | null = null;\n\nexport function reserveExposurePopup(owner: symbol): boolean {\n if (openingOwner !== null && openingOwner !== owner) return false;\n openingOwner = owner;\n return true;\n}\n\nexport function releaseExposurePopup(owner: symbol) {\n if (openingOwner === owner) openingOwner = null;\n}\n\nexport function exposureCanOpenPopup(): boolean {\n const focused = document.activeElement;\n if (focused instanceof HTMLElement && (focused.matches(\"input, textarea, select\") || focused.isContentEditable)) return false;\n if (activeDialog?.isConnected) return false;\n return !Array.from(document.querySelectorAll<HTMLElement>('dialog[open], [role=\"dialog\"][aria-modal=\"true\"]'))\n .some((element) => {\n // keepMounted Drawer처럼 DOM에 남은 닫힌 모달을 활성 모달로 세지 않는다.\n for (let ancestor: HTMLElement | null = element; ancestor; ancestor = ancestor.parentElement) {\n if (ancestor.hidden || ancestor.inert || ancestor.getAttribute(\"aria-hidden\") === \"true\") return false;\n const style = getComputedStyle(ancestor);\n if (style.display === \"none\" || style.visibility === \"hidden\" || style.visibility === \"collapse\") return false;\n }\n return true;\n });\n}\n\nexport function ExposureDialog({ title, className, children, onClose, onReady, device, size, themeElement, manageFocus = true }: {\n title: string; className: string; children: ReactNode; onClose: () => void;\n device: \"desktop\" | \"mobile\"; size: \"small\" | \"medium\" | \"large\";\n themeElement: HTMLElement | null;\n onReady?: (ready: boolean) => void;\n manageFocus?: boolean;\n}) {\n const rootRef = useRef<HTMLDivElement>(null);\n const dialogRef = useRef<HTMLElement>(null);\n const closeRef = useRef(onClose);\n const [ready, setReady] = useState(false);\n useEffect(() => { if (ready) onReady?.(true); }, [ready, onReady]);\n useLayoutEffect(() => {\n const root = rootRef.current;\n if (!root) return;\n // 모든 안내의 이미지 높이가 정해진 뒤 공통 크기의 창을 보여 준다.\n const pending = Array.from(root.querySelectorAll(\"img\")).filter((image) => !image.complete);\n if (!pending.length) { setReady(true); return; }\n const remaining = new Set(pending);\n const settle = (event: Event) => {\n remaining.delete(event.currentTarget as HTMLImageElement);\n if (!remaining.size) setReady(true);\n };\n for (const image of pending) { image.addEventListener(\"load\", settle); image.addEventListener(\"error\", settle); }\n return () => { for (const image of pending) { image.removeEventListener(\"load\", settle); image.removeEventListener(\"error\", settle); } };\n }, []);\n useEffect(() => { closeRef.current = onClose; }, [onClose]);\n useEffect(() => {\n const target = rootRef.current;\n if (!themeElement || !target) return;\n const computed = getComputedStyle(themeElement);\n target.style.fontFamily = computed.fontFamily;\n target.style.color = computed.color;\n for (let index = 0; index < computed.length; index++) {\n const key = computed.item(index);\n if (key.startsWith(\"--\")) target.style.setProperty(key, computed.getPropertyValue(key));\n }\n }, [themeElement]);\n useEffect(() => {\n const root = rootRef.current;\n const dialog = dialogRef.current;\n if (!root || !dialog || !ready) return;\n activeDialog = dialog;\n const previous = document.activeElement instanceof HTMLElement ? document.activeElement : null;\n const previousOverflow = document.body.style.overflow;\n const previousPadding = document.body.style.paddingRight;\n const gutter = Math.max(0, window.innerWidth - document.documentElement.clientWidth);\n const padding = Number.parseFloat(getComputedStyle(document.body).paddingRight) || 0;\n document.body.style.overflow = \"hidden\";\n if (gutter > 0) document.body.style.paddingRight = `${padding + gutter}px`;\n const inertBefore = new Map<HTMLElement, boolean>();\n function inertBackground() {\n for (const child of document.body.children) {\n if (!(child instanceof HTMLElement) || child === root || child.contains(root) || inertBefore.has(child)) continue;\n inertBefore.set(child, child.inert); child.inert = true;\n }\n }\n inertBackground();\n const observer = new MutationObserver(inertBackground);\n observer.observe(document.body, { childList: true });\n const focusable = () => Array.from(dialog.querySelectorAll<HTMLElement>('a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex=\"0\"]'))\n .filter((element) => !element.hidden && !element.closest('[hidden], [aria-hidden=\"true\"]'));\n if (manageFocus) dialog.focus({ preventScroll: true });\n const handleKey = (event: KeyboardEvent) => {\n if (event.key === \"Escape\") { event.preventDefault(); event.stopPropagation(); closeRef.current(); }\n if (event.key !== \"Tab\") return;\n const items = focusable();\n const first = items[0]; const last = items[items.length - 1];\n if (!first || !last) { event.preventDefault(); dialog.focus(); }\n else if (event.shiftKey && (document.activeElement === first || document.activeElement === dialog)) { event.preventDefault(); last.focus(); }\n else if (!event.shiftKey && (document.activeElement === last || document.activeElement === dialog)) { event.preventDefault(); first.focus(); }\n };\n const handleFocus = (event: FocusEvent) => {\n if (event.target instanceof Node && !dialog.contains(event.target)) (focusable()[0] ?? dialog).focus({ preventScroll: true });\n };\n document.addEventListener(\"keydown\", handleKey, true);\n if (manageFocus) document.addEventListener(\"focusin\", handleFocus);\n return () => {\n observer.disconnect();\n document.removeEventListener(\"keydown\", handleKey, true);\n document.removeEventListener(\"focusin\", handleFocus);\n for (const [element, wasInert] of inertBefore) element.inert = wasInert;\n if (activeDialog === dialog) activeDialog = null;\n document.body.style.overflow = previousOverflow;\n document.body.style.paddingRight = previousPadding;\n if (!manageFocus) return;\n if (previous?.isConnected && !previous.inert) previous.focus({ preventScroll: true });\n else {\n const fallback = document.querySelector<HTMLElement>('main, [role=\"main\"]') ?? document.body;\n const hadTabIndex = fallback.hasAttribute(\"tabindex\");\n if (!hadTabIndex) fallback.tabIndex = -1;\n fallback.focus({ preventScroll: true });\n if (!hadTabIndex) fallback.removeAttribute(\"tabindex\");\n }\n };\n }, [ready, manageFocus]);\n\n return createPortal(<div ref={rootRef} className=\"rt-exposure-backdrop\" data-device={device} style={{ visibility: ready ? undefined : \"hidden\" }}>\n <section ref={dialogRef} className={className} role=\"dialog\" aria-modal=\"true\" aria-label={title} tabIndex={-1} data-size={size}>\n {children}\n </section>\n </div>, document.body);\n}\n","\"use client\";\n\nimport { useCallback, useEffect, useRef, useState, type FocusEvent, type TouchEvent } from \"react\";\nimport type { CmsExposureCampaign } from \"@roottale/cms-client/server\";\nimport { exposureCanOpenPopup } from \"./exposure-dialog\";\n\nexport function useExposureCarousel(campaigns: readonly CmsExposureCampaign[], enabled: boolean, durationMs: number, popup: boolean) {\n const [selection, setSelection] = useState({ id: campaigns[0]?.id, cycle: 0 });\n const [elapsedState, setElapsed] = useState({ id: campaigns[0]?.id, value: 0 });\n const [userPaused, setUserPaused] = useState(false);\n const [motionAllowed, setMotionAllowed] = useState(false);\n const [reducedMotion, setReducedMotion] = useState(false);\n const [hovered, setHovered] = useState(false);\n const [focused, setFocused] = useState(false);\n const [touching, setTouching] = useState(false);\n const [environmentPaused, setEnvironmentPaused] = useState(false);\n const elapsedRef = useRef(0);\n const touchOrigin = useRef<{ x: number; y: number } | null>(null);\n const index = Math.max(0, campaigns.findIndex((campaign) => campaign.id === selection.id));\n const campaign = campaigns[index];\n const elapsed = elapsedState.id === campaign?.id ? elapsedState.value : 0;\n const latest = useRef({ campaigns, index });\n useEffect(() => { latest.current = { campaigns, index }; }, [campaigns, index]);\n const duration = Math.max(3000, Math.min(30_000, durationMs));\n const paused = userPaused || hovered || focused || touching || environmentPaused || (reducedMotion && !motionAllowed);\n\n const select = useCallback((nextIndex: number) => {\n const items = latest.current.campaigns;\n if (!items.length) return;\n const normalized = (nextIndex + items.length) % items.length;\n latest.current.index = normalized;\n elapsedRef.current = 0; setElapsed({ id: items[normalized]!.id, value: 0 });\n setSelection((previous) => ({ id: items[normalized]!.id, cycle: previous.cycle + 1 }));\n }, []);\n const next = useCallback(() => select(latest.current.index + 1), [select]);\n const previous = useCallback(() => select(latest.current.index - 1), [select]);\n\n useEffect(() => {\n const media = window.matchMedia(\"(prefers-reduced-motion: reduce)\");\n const changed = () => setReducedMotion(media.matches);\n changed(); media.addEventListener(\"change\", changed);\n return () => media.removeEventListener(\"change\", changed);\n }, []);\n\n useEffect(() => {\n const changed = () => setEnvironmentPaused(document.visibilityState === \"hidden\" || (!popup && !exposureCanOpenPopup()));\n changed();\n const check = setInterval(changed, 250);\n document.addEventListener(\"visibilitychange\", changed);\n return () => { clearInterval(check); document.removeEventListener(\"visibilitychange\", changed); };\n }, [popup]);\n\n useEffect(() => { elapsedRef.current = 0; }, [campaign?.id, selection.cycle]);\n useEffect(() => {\n if (!enabled || paused || campaigns.length < 2) return;\n let previousTick = performance.now();\n const timer = setInterval(() => {\n const now = performance.now();\n elapsedRef.current += now - previousTick; previousTick = now;\n if (elapsedRef.current >= duration) next();\n else setElapsed({ id: campaign?.id, value: elapsedRef.current });\n }, 100);\n return () => clearInterval(timer);\n }, [campaign?.id, campaigns.length, enabled, paused, duration, next, selection.cycle]);\n\n function onTouchStart(event: TouchEvent<HTMLElement>) {\n const touch = event.touches[0];\n if (touch) { touchOrigin.current = { x: touch.clientX, y: touch.clientY }; setTouching(true); }\n }\n function onTouchEnd(event: TouchEvent<HTMLElement>) {\n const origin = touchOrigin.current; const touch = event.changedTouches[0];\n touchOrigin.current = null; setTouching(false);\n if (!origin || !touch || campaigns.length < 2) return;\n const dx = touch.clientX - origin.x; const dy = touch.clientY - origin.y;\n if (Math.abs(dx) >= 48 && Math.abs(dx) > Math.abs(dy) * 1.4) { if (dx < 0) next(); else previous(); }\n }\n function onFocus(event: FocusEvent<HTMLElement>) {\n // 모달을 열며 옮긴 닫기 버튼의 초기 포커스는 자동 전환을 막지 않는다.\n if (!(event.target instanceof HTMLElement)) return;\n if (event.target.closest(\"[data-exposure-close], .rt-exposure__hide-today\")) { setFocused(false); return; }\n setFocused(true);\n }\n const interactions = {\n onPointerEnter: (event: { pointerType: string }) => { if (event.pointerType === \"mouse\") setHovered(true); },\n onPointerLeave: () => setHovered(false),\n onFocusCapture: onFocus,\n onBlurCapture: (event: FocusEvent<HTMLElement>) => { if (!(event.relatedTarget instanceof Node) || !event.currentTarget.contains(event.relatedTarget)) setFocused(false); },\n onTouchStart, onTouchEnd,\n onTouchCancel: () => { touchOrigin.current = null; setTouching(false); },\n };\n const toggle = () => {\n // 재생을 명시적으로 누른 경우 그 버튼의 초점은 재생 요청을 다시 막지 않는다.\n setFocused(false);\n setHovered(false);\n if (reducedMotion && !motionAllowed) { setMotionAllowed(true); setUserPaused(false); }\n else setUserPaused((value) => !value);\n };\n return { campaign, index, paused, userPaused: userPaused || (reducedMotion && !motionAllowed), progress: Math.min(1, elapsed / duration),\n seconds: Math.max(1, Math.ceil((duration - elapsed) / 1000)), next, previous, select, toggle, interactions };\n}\n\nexport function ExposureCarouselControls({ carousel, campaigns }: { carousel: ReturnType<typeof useExposureCarousel>; campaigns: readonly CmsExposureCampaign[] }) {\n if (campaigns.length < 2) return null;\n const firstDot = Math.max(0, Math.min(campaigns.length - 5, carousel.index - 2));\n return <div className=\"rt-exposure-carousel-controls\">\n <div className=\"rt-exposure-carousel-meta\">\n <span className=\"rt-exposure-carousel-count\" aria-live={carousel.paused ? \"polite\" : \"off\"}><strong>{String(carousel.index + 1).padStart(2, \"0\")}</strong><span> / {String(campaigns.length).padStart(2, \"0\")}</span></span>\n <span className=\"rt-exposure-carousel-time\">{carousel.paused ? \"전환 일시정지\" : `${carousel.seconds}초 후 다음 안내`}</span>\n <div className=\"rt-exposure-carousel-actions\">\n <button type=\"button\" aria-label=\"이전 안내\" onClick={carousel.previous}><svg viewBox=\"0 0 24 24\" aria-hidden=\"true\"><path d=\"m14 6-6 6 6 6\" /></svg></button>\n <button type=\"button\" aria-label={carousel.userPaused ? \"자동 전환 재생\" : \"자동 전환 일시정지\"} onClick={carousel.toggle} aria-pressed={carousel.userPaused}>\n <svg viewBox=\"0 0 24 24\" aria-hidden=\"true\">{carousel.userPaused ? <path d=\"m9 5 10 7-10 7Z\" /> : <path d=\"M9 5v14M15 5v14\" />}</svg>\n </button>\n <button type=\"button\" aria-label=\"다음 안내\" onClick={carousel.next}><svg viewBox=\"0 0 24 24\" aria-hidden=\"true\"><path d=\"m10 6 6 6-6 6\" /></svg></button>\n </div>\n </div>\n <div className=\"rt-exposure-carousel-progress\" role=\"progressbar\" aria-label=\"다음 안내까지 진행\" aria-valuemin={0} aria-valuemax={100} aria-valuenow={Math.round(carousel.progress * 100)}>\n <span style={{ transform: `scaleX(${carousel.progress})` }} />\n </div>\n <div className=\"rt-exposure-carousel-dots\" aria-label=\"안내 선택\">\n {campaigns.slice(firstDot, firstDot + 5).map((campaign, offset) => { const index = firstDot + offset; return <button key={campaign.id} type=\"button\" aria-label={`${index + 1}번째 안내: ${campaign.content.title}`} aria-current={index === carousel.index ? \"true\" : undefined} onClick={() => carousel.select(index)}><span /></button>; })}\n </div>\n </div>;\n}\n","\"use client\";\nimport { useEffect, useState } from \"react\";\n\nexport function useMobileExposureScroll(enabled: boolean): boolean {\n const [hidden, setHidden] = useState(false);\n useEffect(() => {\n if (!enabled) { setHidden(false); return; }\n let previous = Math.max(0, window.scrollY);\n let distance = 0;\n const onScroll = () => {\n const current = Math.max(0, window.scrollY);\n const delta = current - previous; previous = current;\n if (current < 32) { distance = 0; setHidden(false); return; }\n if (Math.sign(delta) !== Math.sign(distance)) distance = 0;\n distance += delta;\n if (distance >= 12) { setHidden(true); distance = 0; }\n else if (distance <= -8) { setHidden(false); distance = 0; }\n };\n window.addEventListener(\"scroll\", onScroll, { passive: true });\n return () => window.removeEventListener(\"scroll\", onScroll);\n }, [enabled]);\n return enabled && hidden;\n}\n","import type { CmsExposureCampaign } from \"@roottale/cms-client/server\";\n\nfunction read(kind: \"session\" | \"local\", key: string): string | null {\n try {\n const store = kind === \"session\" ? sessionStorage : localStorage;\n return store.getItem(key);\n } catch { return null; }\n}\n\nconst cookieFallback = new Map<string, string>();\nconst sessionCookie = \"rt_exposure_session\";\nconst preferencesCookie = \"rt_exposure_preferences\";\n\nfunction compactKey(value: string, scope: \"c\" | \"g\"): string {\n // 비밀이나 권한이 아닌 UI 기록 식별자. 64bit로 줄여 다수 슬라이드도 쿠키 한도 안에 둔다.\n let left = 2166136261; let right = 2246822519;\n for (let index = 0; index < value.length; index++) {\n left = Math.imul(left ^ value.charCodeAt(index), 16777619);\n right = Math.imul(right ^ value.charCodeAt(index), 3266489917);\n }\n return `${scope}${(left >>> 0).toString(16).padStart(8, \"0\")}${(right >>> 0).toString(16).padStart(8, \"0\")}`;\n}\n\nfunction rawCookie(name: string): string | null {\n const entry = document.cookie.split(\";\").map((part) => part.trim()).find((part) => part.startsWith(`${name}=`));\n return entry ? entry.slice(name.length + 1) : null;\n}\n\nfunction readPreferences(name: string): Map<string, string> {\n let raw: string | null;\n try {\n raw = cookieFallback.get(name) ?? rawCookie(name) ?? null;\n } catch { raw = cookieFallback.get(name) ?? null; }\n const result = new Map<string, string>();\n for (const entry of raw?.split(\".\") ?? []) {\n const match = /^([cg][a-f0-9]{16})_([svun])([0-9a-z]+)$/.exec(entry);\n if (match) result.set(match[1]!, `${match[2]}:${match[2] === \"u\" ? Number.parseInt(match[3]!, 36) * 1000 : 1}`);\n }\n return result;\n}\n\nfunction writePreferenceKey(key: string, value: string, persistent: boolean, now: number) {\n const name = persistent ? preferencesCookie : sessionCookie;\n const values = readPreferences(name);\n for (const [id, preference] of values) if (preference.startsWith(\"u:\") && Number(preference.slice(2)) <= now) values.delete(id);\n values.delete(key); values.set(key, value);\n const packed = [...values].slice(-100).map(([id, preference]) => `${id}_${preference[0]}${preference.startsWith(\"u:\") ? Math.ceil(Number(preference.slice(2)) / 1000).toString(36) : \"1\"}`).join(\".\");\n try {\n const secure = location.protocol === \"https:\" ? \"; Secure\" : \"\";\n document.cookie = `${name}=${packed}; Path=/; SameSite=Lax${secure}${persistent ? \"; Max-Age=34560000\" : \"\"}`;\n if (rawCookie(name) !== packed) cookieFallback.set(name, packed);\n else cookieFallback.delete(name);\n } catch { cookieFallback.set(name, packed); }\n}\n\nfunction dateInZone(epoch: number, timeZone: string): string {\n return new Intl.DateTimeFormat(\"en-CA\", { timeZone, year: \"numeric\", month: \"2-digit\", day: \"2-digit\" }).format(epoch);\n}\n\n/** DST로 하루가 23/25시간인 곳도 날짜가 바뀌는 첫 시각을 찾는다. */\nexport function nextExposureMidnight(now: number, timeZone: string): number {\n const today = dateInZone(now, timeZone);\n let low = now;\n let high = now + 48 * 3_600_000;\n while (high - low > 1) {\n const mid = Math.floor((low + high) / 2);\n if (dateInZone(mid, timeZone) === today) low = mid;\n else high = mid;\n }\n return high;\n}\n\nexport function exposureWasHidden(campaign: CmsExposureCampaign, now: number, legacyPrefixes: readonly string[] = []): boolean {\n const persistent = readPreferences(preferencesCookie);\n const preferences = [persistent.get(compactKey(campaign.slotKey, \"g\")), persistent.get(compactKey(campaign.id, \"c\")), readPreferences(sessionCookie).get(compactKey(campaign.id, \"c\"))];\n if (preferences.some((preference) => (campaign.repeat !== \"always\" && (preference?.startsWith(\"s:\") || preference?.startsWith(\"v:\") || preference === \"n:1\")) || (preference?.startsWith(\"u:\") && Number(preference.slice(2)) > now))) return true;\n // 종전 local/sessionStorage 기록은 읽어서 호환한다. 새 기록은 쿠키만 쓴다.\n const until = Number(read(\"local\", `rt-exposure-until:${campaign.id}`));\n if (Number.isFinite(until) && until > now) return true;\n if (campaign.repeat === \"always\") return false;\n if (campaign.repeat === \"session\" && read(\"session\", `rt-exposure-viewed:${campaign.id}`)) return true;\n const kind = campaign.repeat === \"session\" ? \"session\" : \"local\";\n for (const prefix of [\"rt-exposure-dismissed:\", ...legacyPrefixes]) {\n const value = read(kind, `${prefix}${campaign.id}`);\n if (value === null) continue;\n if (campaign.repeat !== \"day\") return true;\n const dismissedAt = Number(value);\n // 레거시 접두어·delivery 없는 기존 항목은 종전의 24시간 의미를 보존한다.\n const calendar = prefix === \"rt-exposure-dismissed:\" && campaign.delivery?.dayBoundary === \"calendar\";\n if (Number.isFinite(dismissedAt) && (calendar ? dateInZone(dismissedAt, campaign.delivery!.timeZone) === dateInZone(now, campaign.delivery!.timeZone) : now - dismissedAt < 86_400_000)) return true;\n }\n return false;\n}\n\nexport function rememberExposureView(campaign: CmsExposureCampaign, now: number) {\n if (campaign.repeat === \"session\") writePreferenceKey(compactKey(campaign.id, \"c\"), \"v:1\", false, now);\n // 새 표시 규칙의 일/영구 빈도는 방문자가 닫지 않고 이동해도 실제 노출을 센다.\n if (campaign.delivery && (campaign.repeat === \"day\" || campaign.repeat === \"never\")) rememberExposureDismissal(campaign, now);\n}\n\nexport function rememberExposureDismissal(campaign: CmsExposureCampaign, now: number, forToday = false) {\n // 매번 표시의 일반 닫기는 현재 마운트의 dismissed 상태만 사용한다.\n if (campaign.repeat === \"always\" && !forToday) return;\n if (forToday || campaign.repeat === \"day\") {\n const until = forToday || campaign.delivery?.dayBoundary === \"calendar\" ? nextExposureMidnight(now, campaign.delivery?.timeZone ?? \"Asia/Seoul\") : now + 86_400_000;\n writePreferenceKey(compactKey(campaign.id, \"c\"), `u:${until}`, true, now);\n } else if (campaign.repeat === \"never\") writePreferenceKey(compactKey(campaign.id, \"c\"), \"n:1\", true, now);\n else writePreferenceKey(compactKey(campaign.id, \"c\"), \"s:1\", false, now);\n}\n\n/** 현재 보이는 묶음 전체를 닫는다. 새 캠페인 ID의 다음 방문까지 숨기지는 않는다. */\nexport function rememberExposureGroupDismissal(campaigns: readonly CmsExposureCampaign[], now: number, forToday = false) {\n if (forToday && campaigns[0]) {\n writePreferenceKey(compactKey(campaigns[0].slotKey, \"g\"), `u:${nextExposureMidnight(now, campaigns[0].delivery?.timeZone ?? \"Asia/Seoul\")}`, true, now);\n return;\n }\n for (const campaign of campaigns) rememberExposureDismissal(campaign, now, forToday);\n}\n","\"use client\";\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport type { CmsExposureCampaign } from \"@roottale/cms-client/server\";\nimport { readExposureDecision } from \"./exposure-runtime-data\";\n\nexport type { CmsExposureCampaign } from \"@roottale/cms-client/server\";\nexport const EXPOSURE_PREVIEW_PROTOCOL = \"roottale:exposure-preview\";\nconst defaultOrigins: readonly string[] = [\"https://admin.roottale.com\"];\n\n/** 편집 내용은 URL에 싣지 않는다. fragment는 브라우저 간 연결 식별자만 전달한다. */\nexport function exposureSitePreviewUrl(siteUrl: string, editorOrigin: string, session: string): string | null {\n try {\n const url = new URL(siteUrl);\n const local = [\"localhost\", \"127.0.0.1\", \"[::1]\"].includes(url.hostname);\n if (url.username || url.password || (url.protocol !== \"https:\" && !(local && url.protocol === \"http:\"))) return null;\n url.pathname = \"/\"; url.search = \"\";\n url.hash = new URLSearchParams({ \"rt-exposure-preview\": session, origin: editorOrigin }).toString();\n return url.href;\n } catch { return null; }\n}\n\nfunction previewConnection(origins: readonly string[]) {\n if (typeof window === \"undefined\" || window.parent === window) return null;\n const params = new URLSearchParams(window.location.hash.slice(1));\n const session = params.get(\"rt-exposure-preview\");\n const origin = params.get(\"origin\");\n if (!session || !/^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/i.test(session) || !origin || ![...defaultOrigins, ...origins].includes(origin)) return null;\n return { session, origin };\n}\n\nexport function parseExposurePreviewCampaign(value: unknown, slots: readonly string[]): CmsExposureCampaign | null {\n try {\n if (JSON.stringify(value).length > 32_000) return null;\n const snapshot = readExposureDecision({ campaigns: [value], evaluatedAt: \"2026-01-01T00:00:00Z\", validUntil: \"2026-01-01T00:01:00Z\" }, slots, 0, 0);\n const campaign = snapshot?.decision.campaigns[0];\n if (!campaign?.delivery || campaign.content.title.length > 200 || campaign.content.variant.length > 100) return null;\n for (const field of [\"eyebrow\", \"description\", \"assetAlt\", \"mobileAssetAlt\", \"linkLabel\"] as const) {\n const text = campaign.content[field];\n if (text !== undefined && (typeof text !== \"string\" || text.length > 2000)) return null;\n }\n if (campaign.content.linkTarget !== undefined && ![\"self\", \"blank\"].includes(campaign.content.linkTarget)) return null;\n return campaign;\n } catch { return null; }\n}\n\nexport function useExposureSitePreview(slots: readonly string[], origins: readonly string[] = defaultOrigins) {\n const [connection] = useState(() => previewConnection(origins));\n const [draft, setDraft] = useState<{ revision: number; campaign: CmsExposureCampaign } | null>(null);\n const revision = useRef(-1);\n const slotsKey = slots.join(\",\");\n const reply = useCallback((type: string, data: Record<string, unknown> = {}) => {\n if (connection) window.parent.postMessage({ type: `${EXPOSURE_PREVIEW_PROTOCOL}:${type}`, session: connection.session, ...data }, connection.origin);\n }, [connection]);\n\n useEffect(() => {\n if (!connection) return;\n function receive(event: MessageEvent) {\n if (event.source !== window.parent || event.origin !== connection!.origin) return;\n const data = event.data;\n if (!data || data.session !== connection!.session) return;\n if (data.type === `${EXPOSURE_PREVIEW_PROTOCOL}:connect`) { reply(\"ready\"); return; }\n if (data.type !== `${EXPOSURE_PREVIEW_PROTOCOL}:render` || !Number.isSafeInteger(data.revision) || data.revision <= revision.current) return;\n const campaign = parseExposurePreviewCampaign(data.campaign, slotsKey.split(\",\"));\n if (!campaign) { reply(\"error\", { revision: data.revision }); return; }\n revision.current = data.revision;\n setDraft({ revision: data.revision, campaign });\n }\n // 사이트 링크를 눌러 편집 세션을 이탈하거나 제출하지 않는다.\n const preventNavigation = (event: Event) => {\n const target = event.target instanceof Element ? event.target : null;\n const previewControl = target?.closest(\"[data-roottale-exposure-slot], .rt-exposure-backdrop\");\n if (event.type === \"submit\" || !previewControl || target?.closest(\"a[href]\")) {\n event.preventDefault(); event.stopImmediatePropagation();\n }\n };\n window.addEventListener(\"message\", receive);\n document.addEventListener(\"click\", preventNavigation, true);\n document.addEventListener(\"submit\", preventNavigation, true);\n reply(\"ready\");\n return () => {\n window.removeEventListener(\"message\", receive);\n document.removeEventListener(\"click\", preventNavigation, true);\n document.removeEventListener(\"submit\", preventNavigation, true);\n };\n }, [connection, reply, slotsKey]);\n const draftRevision = draft?.revision ?? -1;\n const report = useCallback((valid: boolean) => reply(valid ? \"rendered\" : \"error\", { revision: draftRevision }), [reply, draftRevision]);\n return { active: connection !== null, draft, report };\n}\n"],"mappings":"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","names":["useCallback","useEffect","useRef","useState","useEffect","useLayoutEffect","useRef","useState","useState","useEffect","useState","jsx","useEffect","useRef","useState","jsx","jsxs","useState","useRef","campaign","useEffect","previous","useEffect","useState","jsx","jsxs","useRef","useLayoutEffect","useState","campaign","useEffect","first","useCallback","useEffect","useRef","useState","useState","useRef","useCallback","useEffect","jsx","useState","useEffect","useRef","useCallback"]}
|
package/dist/cms-public.css
CHANGED
|
@@ -58,6 +58,7 @@
|
|
|
58
58
|
.rt-exposure--preview { position: relative; max-inline-size: 100%; }
|
|
59
59
|
.rt-exposure-carousel { position: relative; font: inherit; touch-action: pan-y pinch-zoom; }
|
|
60
60
|
.rt-exposure-slide { min-inline-size: 0; }
|
|
61
|
+
.rt-exposure-sizer { display: none; }
|
|
61
62
|
.rt-exposure-slide > .rt-exposure__body { animation: rt-exposure-reveal 180ms ease-out; }
|
|
62
63
|
.rt-exposure-carousel-controls { padding: .375rem 1rem .5rem; border-block-start: 1px solid var(--rt-color-border, rgb(0 0 0 / .1)); }
|
|
63
64
|
.rt-exposure-carousel-meta { display: flex; align-items: center; gap: .75rem; min-block-size: 2.75rem; }
|
package/dist/exposure-client.js
CHANGED
package/dist/exposures.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import * as react from 'react';
|
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
3
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
4
4
|
import { CmsExposureCampaign } from '@roottale/cms-client/server';
|
|
5
|
+
export { CmsExposureCampaign } from '@roottale/cms-client/server';
|
|
5
6
|
|
|
6
7
|
type ExposurePlacement = "top" | "inline" | "bottom" | "popup";
|
|
7
8
|
/** 실제 콘텐츠 출력과 같지만 방문 기록·조회·포커스 이동이 없는 편집기 미리보기. */
|
|
@@ -19,6 +20,11 @@ declare function RootTaleExposureCarouselPreview({ campaigns, placement, device,
|
|
|
19
20
|
slideDurationMs?: number;
|
|
20
21
|
}): react_jsx_runtime.JSX.Element | null;
|
|
21
22
|
|
|
23
|
+
declare const EXPOSURE_PREVIEW_PROTOCOL = "roottale:exposure-preview";
|
|
24
|
+
/** 편집 내용은 URL에 싣지 않는다. fragment는 브라우저 간 연결 식별자만 전달한다. */
|
|
25
|
+
declare function exposureSitePreviewUrl(siteUrl: string, editorOrigin: string, session: string): string | null;
|
|
26
|
+
declare function parseExposurePreviewCampaign(value: unknown, slots: readonly string[]): CmsExposureCampaign | null;
|
|
27
|
+
|
|
22
28
|
interface RootTaleExposureProviderProps {
|
|
23
29
|
endpoint: string;
|
|
24
30
|
/** 사이트의 usePathname() 값. API 키는 서버에만 둔다. */
|
|
@@ -30,9 +36,11 @@ interface RootTaleExposureProviderProps {
|
|
|
30
36
|
slideDurationMs?: number;
|
|
31
37
|
hideMobileBannersOnScroll?: boolean;
|
|
32
38
|
legacyDismissalKeyPrefixes?: readonly string[];
|
|
39
|
+
/** 추가 관리자 출처가 필요한 사이트에서만 지정한다. 기본은 canonical ROOT-ADMIN. */
|
|
40
|
+
previewOrigins?: readonly string[];
|
|
33
41
|
children: ReactNode;
|
|
34
42
|
}
|
|
35
|
-
declare function RootTaleExposureProvider({ endpoint, pathname, slots, breakpoint, homePaths, slideDurationMs, hideMobileBannersOnScroll, legacyDismissalKeyPrefixes, children }: RootTaleExposureProviderProps): react_jsx_runtime.JSX.Element;
|
|
43
|
+
declare function RootTaleExposureProvider({ endpoint, pathname, slots, breakpoint, homePaths, slideDurationMs, hideMobileBannersOnScroll, legacyDismissalKeyPrefixes, previewOrigins, children }: RootTaleExposureProviderProps): react_jsx_runtime.JSX.Element;
|
|
36
44
|
interface RootTaleExposureSlotProps {
|
|
37
45
|
slotKey: string;
|
|
38
46
|
placement: ExposurePlacement;
|
|
@@ -48,4 +56,4 @@ declare function RootTaleLegacyExposure({ campaign, allowedVariants }: {
|
|
|
48
56
|
allowedVariants: readonly string[];
|
|
49
57
|
}): react_jsx_runtime.JSX.Element | null;
|
|
50
58
|
|
|
51
|
-
export { type ExposurePlacement, RootTaleExposureCarouselPreview, RootTaleExposurePreview, RootTaleExposureProvider, type RootTaleExposureProviderProps, RootTaleExposureSlot, type RootTaleExposureSlotProps, RootTaleLegacyExposure };
|
|
59
|
+
export { EXPOSURE_PREVIEW_PROTOCOL, type ExposurePlacement, RootTaleExposureCarouselPreview, RootTaleExposurePreview, RootTaleExposureProvider, type RootTaleExposureProviderProps, RootTaleExposureSlot, type RootTaleExposureSlotProps, RootTaleLegacyExposure, exposureSitePreviewUrl, parseExposurePreviewCampaign };
|
package/dist/exposures.js
CHANGED
|
@@ -1,17 +1,23 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import {
|
|
3
|
+
EXPOSURE_PREVIEW_PROTOCOL,
|
|
3
4
|
RootTaleExposureCarouselPreview,
|
|
4
5
|
RootTaleExposurePreview,
|
|
5
6
|
RootTaleExposureProvider,
|
|
6
7
|
RootTaleExposureSlot,
|
|
7
|
-
RootTaleLegacyExposure
|
|
8
|
-
|
|
8
|
+
RootTaleLegacyExposure,
|
|
9
|
+
exposureSitePreviewUrl,
|
|
10
|
+
parseExposurePreviewCampaign
|
|
11
|
+
} from "./chunk-LUL73SKP.js";
|
|
9
12
|
import "./chunk-L3I4W5NP.js";
|
|
10
13
|
export {
|
|
14
|
+
EXPOSURE_PREVIEW_PROTOCOL,
|
|
11
15
|
RootTaleExposureCarouselPreview,
|
|
12
16
|
RootTaleExposurePreview,
|
|
13
17
|
RootTaleExposureProvider,
|
|
14
18
|
RootTaleExposureSlot,
|
|
15
|
-
RootTaleLegacyExposure
|
|
19
|
+
RootTaleLegacyExposure,
|
|
20
|
+
exposureSitePreviewUrl,
|
|
21
|
+
parseExposurePreviewCampaign
|
|
16
22
|
};
|
|
17
23
|
//# sourceMappingURL=exposures.js.map
|
package/dist/routes.js
CHANGED
|
@@ -964,7 +964,7 @@ function createFleetInfoRoute(config = {}) {
|
|
|
964
964
|
site: config.site ?? env.VERCEL_PROJECT_PRODUCTION_URL ?? null,
|
|
965
965
|
framework: "next",
|
|
966
966
|
cms_client: CMS_CLIENT_VERSION,
|
|
967
|
-
cms_renderer: true ? "0.
|
|
967
|
+
cms_renderer: true ? "0.64.0" : "dev",
|
|
968
968
|
build_sha: env.VERCEL_GIT_COMMIT_SHA?.slice(0, 7) ?? null,
|
|
969
969
|
...config.exposures ? { exposures: config.exposures } : {}
|
|
970
970
|
},
|
package/dist/server.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@roottale/cms-renderer-next",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.64.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "RootTale CMS public-render React/Next.js Server Components. SSR-only RSC components (RootTaleBlogList / RootTaleBlogPost / RootTaleLeadForm) for external customer sites.",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -53,8 +53,8 @@
|
|
|
53
53
|
"CHANGELOG.md"
|
|
54
54
|
],
|
|
55
55
|
"dependencies": {
|
|
56
|
-
"@roottale/cms-
|
|
57
|
-
"@roottale/cms-
|
|
56
|
+
"@roottale/cms-core": "^0.64.0",
|
|
57
|
+
"@roottale/cms-client": "^0.64.0"
|
|
58
58
|
},
|
|
59
59
|
"peerDependencies": {
|
|
60
60
|
"react": "^19.0.0",
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
"typescript": "^5.7.0",
|
|
72
72
|
"vitest": "^2.1.0",
|
|
73
73
|
"@roottale/cms-i18n": "^0.2.0",
|
|
74
|
-
"@roottale/ui-react": "^0.5.
|
|
74
|
+
"@roottale/ui-react": "^0.5.1"
|
|
75
75
|
},
|
|
76
76
|
"publishConfig": {
|
|
77
77
|
"access": "public"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/exposures.tsx","../src/exposure-decision-client.ts","../src/exposure-runtime-data.ts","../src/exposure-group.tsx","../src/exposure-content.tsx","../src/exposure-dialog.tsx","../src/exposure-carousel.tsx","../src/exposure-scroll.ts","../src/exposure-storage.ts"],"sourcesContent":["\"use client\";\n\nimport { createContext, useCallback, useContext, useEffect, useRef, useState, type CSSProperties, type ReactNode } from \"react\";\nimport type { CmsExposureCampaign } from \"@roottale/cms-client/server\";\nimport { useExposureDecision } from \"./exposure-decision-client\";\nimport type { ExposurePlacement } from \"./exposure-content\";\nimport { ExposureCampaignGroup } from \"./exposure-group\";\nimport { exposureHomePaths, isExposureHome } from \"./exposure-home\";\nimport { exposureServerNow, liveCampaigns } from \"./exposure-runtime-data\";\nimport type { ExposureRuntime } from \"./exposure-runtime-context\";\n\nexport { RootTaleExposurePreview } from \"./exposure-content\";\nexport { RootTaleExposureCarouselPreview } from \"./exposure-group\";\nexport type { ExposurePlacement } from \"./exposure-content\";\n\nexport interface RootTaleExposureProviderProps {\n endpoint: string;\n /** 사이트의 usePathname() 값. API 키는 서버에만 둔다. */\n pathname: string;\n slots: readonly string[];\n breakpoint?: number;\n /** 메인 홈만 허용하며 locale 홈은 실제로 만든 exact 경로를 명시한다. */\n homePaths?: readonly string[];\n slideDurationMs?: number;\n hideMobileBannersOnScroll?: boolean;\n legacyDismissalKeyPrefixes?: readonly string[];\n children: ReactNode;\n}\n\nconst Context = createContext<ExposureRuntime | null>(null);\nconst emptyPrefixes: readonly string[] = [];\n\nexport function RootTaleExposureProvider({ endpoint, pathname, slots, breakpoint = 768, homePaths, slideDurationMs = 5000,\n hideMobileBannersOnScroll = true, legacyDismissalKeyPrefixes = emptyPrefixes, children }: RootTaleExposureProviderProps) {\n const [themeElement, setThemeElement] = useState<HTMLDivElement | null>(null);\n const [device, setDevice] = useState<\"desktop\" | \"mobile\">(\"desktop\");\n const [heights, setHeights] = useState<Record<string, { placement: \"top\" | \"bottom\"; height: number }>>({});\n useEffect(() => {\n const media = window.matchMedia(`(max-width: ${breakpoint - 1}px)`);\n const update = () => setDevice(media.matches ? \"mobile\" : \"desktop\");\n update(); media.addEventListener(\"change\", update);\n return () => media.removeEventListener(\"change\", update);\n }, [breakpoint]);\n const path = pathname.split(/[?#]/, 1)[0] || \"/\";\n const home = isExposureHome(path, exposureHomePaths(homePaths));\n const routeKey = `${path}:${device}`;\n const snapshot = useExposureDecision(endpoint, path, device, slots, home);\n const popupVisit = useRef<{ key: string; slot?: string }>({ key: routeKey });\n useEffect(() => { popupVisit.current = { key: routeKey }; }, [routeKey]);\n const now = useCallback(() => snapshot ? exposureServerNow(snapshot) : Date.now(), [snapshot]);\n const claimPopup = useCallback((slot: string) => {\n const claimed = popupVisit.current.slot;\n if (claimed && claimed !== slot) return false;\n popupVisit.current.slot = slot;\n return true;\n }, []);\n const setHeight = useCallback((key: string, placement: \"top\" | \"bottom\", height: number) => {\n setHeights((previous) => previous[key]?.height === height && previous[key]?.placement === placement ? previous : { ...previous, [key]: { placement, height } });\n }, []);\n const value: ExposureRuntime = { campaigns: liveCampaigns(snapshot), device, routeKey, home,\n slideDurationMs: Number.isFinite(slideDurationMs) ? slideDurationMs : 5000, hideMobileBannersOnScroll,\n now, claimPopup, setHeight, legacyPrefixes: legacyDismissalKeyPrefixes, themeElement };\n const top = Math.max(0, ...Object.values(heights).filter((item) => item.placement === \"top\").map((item) => item.height));\n const bottom = Math.max(0, ...Object.values(heights).filter((item) => item.placement === \"bottom\").map((item) => item.height));\n const style = { display: \"contents\", \"--rt-exposure-top-height\": `${top}px`, \"--rt-exposure-bottom-height\": `${bottom}px` } as CSSProperties;\n return <Context.Provider value={value}><div ref={setThemeElement} data-roottale-exposure-root=\"\" style={style}>{children}</div></Context.Provider>;\n}\n\nexport interface RootTaleExposureSlotProps {\n slotKey: string;\n placement: ExposurePlacement;\n allowedVariants: readonly string[];\n /** 배너를 닫아도 같은 방문에서 이전 공지를 이어서 표시하지 않는다. */\n fallback?: ReactNode;\n className?: string;\n}\n\nexport function RootTaleExposureSlot({ slotKey, placement, allowedVariants, fallback = null, className = \"\" }: RootTaleExposureSlotProps) {\n const runtime = useContext(Context);\n if (runtime && !runtime.home) return fallback;\n const campaigns = runtime?.campaigns.filter((item) => item.slotKey === slotKey && item.kind === (placement === \"popup\" ? \"popup\" : \"banner\") && allowedVariants.includes(item.content.variant)).slice(0, 20);\n if (!runtime || !campaigns?.length) return fallback;\n return <ExposureCampaignGroup key={`${runtime.routeKey}:${slotKey}`} campaigns={campaigns} runtime={runtime} placement={placement} className={className} />;\n}\n\n/** 기존 서버 슬롯의 client island. 신규 설치는 Provider/Slot을 사용한다. */\nexport function RootTaleLegacyExposure({ campaign, allowedVariants }: { campaign: CmsExposureCampaign; allowedVariants: readonly string[] }) {\n const [device, setDevice] = useState<\"desktop\" | \"mobile\">(\"desktop\");\n useEffect(() => { const media = window.matchMedia(\"(max-width: 767px)\"); const update = () => setDevice(media.matches ? \"mobile\" : \"desktop\"); update(); media.addEventListener(\"change\", update); return () => media.removeEventListener(\"change\", update); }, []);\n if (!allowedVariants.includes(campaign.content.variant)) return null;\n const runtime: ExposureRuntime = { campaigns: [campaign], device, routeKey: \"legacy\", home: true, slideDurationMs: 5000, hideMobileBannersOnScroll: true,\n now: Date.now, claimPopup: () => true, setHeight: () => undefined, legacyPrefixes: emptyPrefixes, themeElement: null };\n return <ExposureCampaignGroup key={campaign.id} campaigns={[campaign]} runtime={runtime} placement={campaign.kind === \"popup\" ? \"popup\" : \"inline\"} className=\"\" />;\n}\n","\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { readExposureDecision, type TimedExposureDecision } from \"./exposure-runtime-data\";\n\nexport function useExposureDecision(endpoint: string, pathname: string, device: \"mobile\" | \"desktop\", slots: readonly string[], enabled = true) {\n const slotsKey = slots.join(\",\");\n const requestKey = JSON.stringify([endpoint, pathname, device, slotsKey, enabled]);\n const [result, setResult] = useState<{ key: string; snapshot: TimedExposureDecision } | null>(null);\n\n useEffect(() => {\n if (!enabled) return;\n let disposed = false;\n let sequence = 0;\n let controller: AbortController | undefined;\n let pollTimer: ReturnType<typeof setTimeout> | undefined;\n let expiryTimer: ReturnType<typeof setTimeout> | undefined;\n let requestTimer: ReturnType<typeof setTimeout> | undefined;\n let current: TimedExposureDecision | null = null;\n const clearPoll = () => { clearTimeout(pollTimer); clearTimeout(requestTimer); controller?.abort(); sequence++; };\n\n function schedule(delay: number) {\n clearTimeout(pollTimer);\n if (!disposed && document.visibilityState !== \"hidden\") pollTimer = setTimeout(refresh, Math.max(100, Math.min(delay, 60_000)));\n }\n\n function expireKnownCampaigns() {\n if (!current) return;\n // Timer callbacks rerender at known expiry even while a replacement request is offline.\n setResult(performance.now() >= current.expiresAt ? null : { key: requestKey, snapshot: current });\n armExpiry();\n }\n\n function armExpiry() {\n clearTimeout(expiryTimer);\n if (!current || current.expiresAt <= performance.now()) return;\n const ends = current.decision.campaigns.flatMap((campaign) => campaign.endsAt ?\n [current!.receivedAt + Date.parse(campaign.endsAt) - current!.serverAtReceipt] : []);\n const future = [current.expiresAt, ...ends].filter((time) => time > performance.now());\n expiryTimer = setTimeout(expireKnownCampaigns, Math.max(1, Math.min(...future) - performance.now()));\n }\n\n async function refresh() {\n if (disposed || document.visibilityState === \"hidden\") return;\n clearPoll();\n if (current && performance.now() >= current.expiresAt) setResult(null);\n const seq = sequence;\n controller = new AbortController();\n const startedAt = performance.now();\n let refreshed = false;\n requestTimer = setTimeout(() => controller?.abort(), 10_000);\n try {\n const url = new URL(endpoint, window.location.origin);\n if (url.origin !== window.location.origin || url.username || url.password) throw new Error(\"Exposure endpoint must be same origin\");\n url.search = new URLSearchParams({ path: pathname, device }).toString();\n const response = await fetch(url, { cache: \"no-store\", credentials: \"same-origin\", signal: controller.signal });\n if (!response.ok) throw new Error(\"Exposure unavailable\");\n const payload: unknown = await response.json();\n if (disposed || seq !== sequence) return;\n const receivedAt = performance.now();\n current = readExposureDecision(payload, slotsKey.split(\",\"), receivedAt, receivedAt - startedAt);\n refreshed = current !== null;\n setResult(current ? { key: requestKey, snapshot: current } : null);\n armExpiry();\n } catch {\n if (disposed || seq !== sequence) return;\n if (!current || performance.now() >= current.expiresAt) { current = null; setResult(null); }\n } finally {\n if (!disposed && seq === sequence) {\n clearTimeout(requestTimer);\n const now = performance.now();\n const next = current?.nextAt;\n const boundaryDelay = next != null && next > now ? next - now : 60_000;\n // 오래 걸리는 요청에도 현재 노출이 만료 전에 갱신될 여유를 둔다.\n const refreshDelay = refreshed && current ? Math.max(1000, current.expiresAt - now - 10_000) : 60_000;\n schedule(Math.min(boundaryDelay, refreshDelay));\n }\n }\n }\n\n function onVisibility() {\n if (document.visibilityState === \"hidden\") clearPoll();\n else void refresh();\n }\n void refresh();\n document.addEventListener(\"visibilitychange\", onVisibility);\n window.addEventListener(\"focus\", refresh);\n return () => {\n disposed = true; clearPoll(); clearTimeout(expiryTimer);\n document.removeEventListener(\"visibilitychange\", onVisibility);\n window.removeEventListener(\"focus\", refresh);\n };\n }, [endpoint, pathname, device, slotsKey, requestKey, enabled]);\n\n // Layout survives client navigation: never paint the preceding route while effects catch up.\n return enabled && result?.key === requestKey ? result.snapshot : null;\n}\n","import type { CmsExposureCampaign, CmsExposureDecision } from \"@roottale/cms-client/server\";\n\nexport interface TimedExposureDecision {\n decision: CmsExposureDecision;\n receivedAt: number;\n serverAtReceipt: number;\n expiresAt: number;\n nextAt: number | null;\n}\n\nfunction validLink(value: unknown, internal = true): boolean {\n if (value == null) return true;\n if (typeof value !== \"string\" || /[\\u0000-\\u0020\\u007f\\\\]/.test(value)) return false;\n if (internal && value.startsWith(\"/\") && !value.startsWith(\"//\")) return true;\n try { const url = new URL(value); return url.protocol === \"https:\" && !url.username && !url.password; } catch { return false; }\n}\n\n/** 판정 응답이 손상돼도 페이지 본문은 유지하는 브라우저 입력 경계. */\nexport function readExposureDecision(value: unknown, slots: readonly string[], receivedAt: number, elapsed: number): TimedExposureDecision | null {\n if (!value || typeof value !== \"object\") return null;\n const data = value as CmsExposureDecision;\n const evaluated = Date.parse(data.evaluatedAt);\n const validUntil = Date.parse(data.validUntil);\n if (!Number.isFinite(evaluated) || !Number.isFinite(validUntil) || validUntil <= evaluated ||\n !Array.isArray(data.campaigns) || data.campaigns.length > 200) return null;\n // 편도 시간을 모르므로 왕복 전체를 차감한다. 늦은 응답으로 유효기간을 늘리지 않는다.\n const serverAtReceipt = evaluated + Math.max(0, elapsed);\n const expiresAt = receivedAt + Math.min(validUntil - serverAtReceipt, 120_000);\n if (expiresAt <= receivedAt) return null;\n const campaigns: CmsExposureCampaign[] = [];\n for (const item of data.campaigns) {\n if (!item || typeof item.id !== \"string\" || !slots.includes(item.slotKey) ||\n (item.kind !== \"banner\" && item.kind !== \"popup\") || !item.content ||\n typeof item.content.title !== \"string\" || typeof item.content.variant !== \"string\" ||\n ![\"always\", \"session\", \"day\", \"never\"].includes(item.repeat) ||\n !validLink(item.content.linkUrl) || !validLink(item.content.assetUrl, false) || !validLink(item.content.mobileAssetUrl, false)) return null;\n if (item.endsAt != null && !Number.isFinite(Date.parse(item.endsAt))) return null;\n if (item.delivery) {\n const delivery = item.delivery;\n if (delivery.version !== 2 || !Number.isFinite(delivery.delaySeconds) || delivery.delaySeconds < 0 || delivery.delaySeconds > 30 ||\n ![null, 25, 50, 75].includes(delivery.scrollPercent) || ![\"small\", \"medium\", \"large\"].includes(delivery.size) ||\n ![\"calendar\", \"rolling\"].includes(delivery.dayBoundary) || typeof delivery.dismissible !== \"boolean\" || typeof delivery.hideForToday !== \"boolean\") return null;\n try { new Intl.DateTimeFormat(\"en\", { timeZone: delivery.timeZone }); } catch { return null; }\n }\n const popupSlot = campaigns.find((existing) => existing.kind === \"popup\")?.slotKey;\n if (!campaigns.some((existing) => existing.id === item.id) &&\n campaigns.filter((existing) => existing.slotKey === item.slotKey).length < 20 &&\n (item.kind !== \"popup\" || popupSlot === undefined || popupSlot === item.slotKey)) campaigns.push(item);\n }\n const boundary = data.nextChangeAt == null ? null : Date.parse(data.nextChangeAt);\n if (boundary !== null && !Number.isFinite(boundary)) return null;\n return { decision: { ...data, campaigns }, receivedAt, serverAtReceipt, expiresAt,\n nextAt: boundary === null ? null : receivedAt + boundary - serverAtReceipt };\n}\n\nexport function exposureServerNow(snapshot: TimedExposureDecision): number {\n return snapshot.serverAtReceipt + performance.now() - snapshot.receivedAt;\n}\n\nexport function liveCampaigns(snapshot: TimedExposureDecision | null): CmsExposureCampaign[] {\n if (!snapshot || performance.now() >= snapshot.expiresAt) return [];\n const now = exposureServerNow(snapshot);\n return snapshot.decision.campaigns.filter((campaign) => !campaign.endsAt || Date.parse(campaign.endsAt) > now);\n}\n","\"use client\";\n\nimport { useEffect, useLayoutEffect, useRef, useState } from \"react\";\nimport type { CmsExposureCampaign } from \"@roottale/cms-client/server\";\nimport { ExposureContent, contentVariant, type ExposurePlacement } from \"./exposure-content\";\nimport { ExposureDialog, exposureCanOpenPopup, releaseExposurePopup, reserveExposurePopup } from \"./exposure-dialog\";\nimport { ExposureCarouselControls, useExposureCarousel } from \"./exposure-carousel\";\nimport type { ExposureRuntime } from \"./exposure-runtime-context\";\nimport { useMobileExposureScroll } from \"./exposure-scroll\";\nimport { exposureWasHidden, rememberExposureGroupDismissal, rememberExposureView } from \"./exposure-storage\";\n\nfunction useSlideScrollReset(campaignId: string | undefined) {\n const ref = useRef<HTMLDivElement>(null);\n useLayoutEffect(() => {\n // 모달·포커스는 유지하고 새 안내의 내용만 처음부터 읽을 수 있게 한다.\n if (ref.current) ref.current.scrollTop = 0;\n }, [campaignId]);\n return ref;\n}\n\nfunction exposureGroupSize(campaigns: readonly CmsExposureCampaign[]): \"small\" | \"medium\" | \"large\" {\n if (campaigns.some((campaign) => campaign.delivery?.size === \"large\")) return \"large\";\n if (campaigns.some((campaign) => (campaign.delivery?.size ?? \"medium\") === \"medium\")) return \"medium\";\n return \"small\";\n}\n\nexport function ExposureCampaignGroup({ campaigns, runtime, placement, className }: {\n campaigns: readonly CmsExposureCampaign[]; runtime: ExposureRuntime; placement: ExposurePlacement; className: string;\n}) {\n const [visible, setVisible] = useState(false);\n const [dismissed, setDismissed] = useState(false);\n const [height, setMeasuredHeight] = useState(0);\n const [inView, setInView] = useState(() => placement !== \"inline\" || typeof IntersectionObserver === \"undefined\");\n const measuredRef = useRef<HTMLDivElement>(null);\n const campaignIds = campaigns.map((campaign) => campaign.id).join(\",\");\n const [admission, setAdmission] = useState(() => ({ ids: campaignIds,\n allowed: new Map(campaigns.map((campaign) => [campaign.id, !exposureWasHidden(campaign, runtime.now(), runtime.legacyPrefixes)])) }));\n if (admission.ids !== campaignIds) {\n const allowed = new Map(admission.allowed);\n for (const campaign of campaigns) if (!allowed.has(campaign.id)) allowed.set(campaign.id, !exposureWasHidden(campaign, runtime.now(), runtime.legacyPrefixes));\n setAdmission({ ids: campaignIds, allowed });\n }\n const viewed = useRef(new Set<string>());\n const owner = useRef(Symbol(\"exposure-group\"));\n const latest = useRef({ campaigns, runtime });\n useEffect(() => { latest.current = { campaigns, runtime }; }, [campaigns, runtime]);\n const eligible = campaigns.filter((campaign) => admission.allowed.get(campaign.id));\n const first = eligible[0];\n const firstId = first?.id;\n const slotKey = campaigns[0]?.slotKey ?? \"\";\n const hiddenOnScroll = useMobileExposureScroll(runtime.device === \"mobile\" && runtime.hideMobileBannersOnScroll && (placement === \"top\" || placement === \"bottom\"));\n const active = visible && !dismissed && eligible.length > 0;\n const carousel = useExposureCarousel(eligible, active && !hiddenOnScroll && inView, runtime.slideDurationMs, placement === \"popup\");\n const campaign = carousel.campaign;\n const activeId = campaign?.id;\n const setHeight = runtime.setHeight;\n const device = runtime.device;\n const slideRef = useSlideScrollReset(activeId);\n\n useEffect(() => {\n const first = latest.current.campaigns.find((campaign) => campaign.id === firstId);\n if (visible || dismissed || !first) return;\n let opened = false;\n let delayPassed = false;\n const scrollPercent = first.delivery?.scrollPercent;\n const tryOpen = () => {\n if (opened || !delayPassed || document.visibilityState === \"hidden\") return;\n if (scrollPercent) {\n const scrollable = document.documentElement.scrollHeight - window.innerHeight;\n if (scrollable <= 0 || window.scrollY / scrollable * 100 < scrollPercent) return;\n }\n if (placement === \"popup\") {\n if (!exposureCanOpenPopup() || !reserveExposurePopup(owner.current)) return;\n if (!latest.current.runtime.claimPopup(first.slotKey)) { releaseExposurePopup(owner.current); return; }\n }\n opened = true; setVisible(true); clearInterval(retry);\n };\n const timer = setTimeout(() => { delayPassed = true; tryOpen(); }, placement === \"popup\" ? (first.delivery?.delaySeconds ?? 0) * 1000 : 0);\n const retry = setInterval(tryOpen, 500);\n window.addEventListener(\"scroll\", tryOpen, { passive: true });\n window.addEventListener(\"resize\", tryOpen); document.addEventListener(\"visibilitychange\", tryOpen);\n return () => { clearTimeout(timer); clearInterval(retry); window.removeEventListener(\"scroll\", tryOpen); window.removeEventListener(\"resize\", tryOpen); document.removeEventListener(\"visibilitychange\", tryOpen); };\n }, [firstId, visible, dismissed, placement]);\n\n useEffect(() => { const token = owner.current; return () => releaseExposurePopup(token); }, []);\n useEffect(() => {\n if (placement !== \"inline\" || !active) return;\n if (typeof IntersectionObserver === \"undefined\") return;\n const element = measuredRef.current;\n const observer = new IntersectionObserver(([entry]) => setInView(Boolean(entry?.isIntersecting)), { threshold: 0.1 });\n if (element) observer.observe(element);\n return () => observer.disconnect();\n }, [placement, active]);\n useEffect(() => {\n const campaign = latest.current.campaigns.find((item) => item.id === activeId);\n if (!active || hiddenOnScroll || !inView || !campaign || viewed.current.has(campaign.id)) return;\n // 현재 묶음의 방문 중 한 번 기록은 다음 슬라이드의 표시를 방해하지 않는다.\n viewed.current.add(campaign.id); rememberExposureView(campaign, latest.current.runtime.now());\n }, [active, hiddenOnScroll, inView, activeId]);\n\n useEffect(() => {\n if (placement !== \"top\" && placement !== \"bottom\") return;\n const element = measuredRef.current;\n const measure = () => {\n const rect = element?.getBoundingClientRect();\n const fullHeight = active && rect ? Math.ceil(rect.height) : 0;\n setMeasuredHeight((previous) => active ? Math.max(previous, fullHeight) : 0);\n setHeight(slotKey, placement, hiddenOnScroll ? 0 : placement === \"top\" && device === \"desktop\" && rect ? Math.max(0, Math.min(fullHeight, Math.ceil(rect.bottom))) : fullHeight);\n };\n measure();\n const observer = typeof ResizeObserver === \"undefined\" ? null : new ResizeObserver(measure);\n if (element) observer?.observe(element);\n window.addEventListener(\"resize\", measure); window.addEventListener(\"scroll\", measure, { passive: true });\n return () => { observer?.disconnect(); window.removeEventListener(\"resize\", measure); window.removeEventListener(\"scroll\", measure); setHeight(slotKey, placement, 0); };\n }, [active, placement, slotKey, setHeight, hiddenOnScroll, device]);\n\n function close(forToday = false) {\n releaseExposurePopup(owner.current);\n rememberExposureGroupDismissal(latest.current.campaigns, latest.current.runtime.now(), forToday);\n setDismissed(true);\n }\n if (!active || !campaign) return null;\n const closeControls = <>\n {placement === \"popup\" || placement === \"bottom\" || (campaign.delivery?.dismissible ?? true) ? <button type=\"button\" data-exposure-close=\"\" className=\"rt-exposure__close\" aria-label=\"안내 닫기\" onClick={() => close()}>×</button> : null}\n {placement === \"popup\" && campaigns.some((item) => item.delivery?.hideForToday) ? <button type=\"button\" className=\"rt-exposure__hide-today\" onClick={() => close(true)}>오늘 그만 보기</button> : null}\n </>;\n const body = <div className=\"rt-exposure-carousel\" aria-roledescription={eligible.length > 1 ? \"캐러셀\" : undefined} {...carousel.interactions}>\n <div ref={slideRef} className=\"rt-exposure-slide\" role=\"group\" aria-roledescription={eligible.length > 1 ? \"슬라이드\" : undefined} aria-label={`${carousel.index + 1} / ${eligible.length}`}>\n <ExposureContent key={campaign.id} campaign={campaign} device={runtime.device} />\n </div>\n {closeControls}\n <ExposureCarouselControls carousel={carousel} campaigns={eligible} />\n </div>;\n const classes = `rt-exposure rt-exposure--${campaign.kind} rt-exposure--${contentVariant(campaign)}`;\n if (placement === \"popup\") return <ExposureDialog title=\"홈페이지 안내\" className={`${classes} ${className}`} onClose={() => close()}\n device={runtime.device} size={exposureGroupSize(eligible)} themeElement={runtime.themeElement}>{body}</ExposureDialog>;\n return <div className={className} data-roottale-exposure-slot={slotKey} data-roottale-exposure-placement={placement}\n style={placement === \"bottom\" || runtime.device === \"mobile\" && placement === \"top\" ? { height } : { minHeight: height }}>\n <div ref={measuredRef} className={`rt-exposure-position rt-exposure-position--${placement}`} data-device={runtime.device}\n data-scroll-hidden={hiddenOnScroll ? \"true\" : \"false\"} aria-hidden={hiddenOnScroll || undefined} inert={hiddenOnScroll || undefined}>\n <aside className={classes} aria-label=\"홈페이지 안내\">{body}</aside>\n </div>\n </div>;\n}\n\nexport function RootTaleExposureCarouselPreview({ campaigns, placement = \"popup\", device = \"desktop\", slideDurationMs = 5000 }: {\n campaigns: readonly CmsExposureCampaign[]; placement?: ExposurePlacement; device?: \"desktop\" | \"mobile\"; slideDurationMs?: number;\n}) {\n const carousel = useExposureCarousel(campaigns, true, slideDurationMs, true);\n const campaign = carousel.campaign;\n const slideRef = useSlideScrollReset(campaign?.id);\n if (!campaign) return null;\n return <div className={`rt-exposure rt-exposure--${campaign.kind} rt-exposure--preview`} data-placement={placement} data-device={device} data-size={exposureGroupSize(campaigns)}>\n <div className=\"rt-exposure-carousel\" {...carousel.interactions}>\n <div ref={slideRef} className=\"rt-exposure-slide\">\n <ExposureContent key={campaign.id} campaign={campaign} device={device} />\n </div>\n <ExposureCarouselControls carousel={carousel} campaigns={campaigns} />\n </div>\n </div>;\n}\n","\"use client\";\n\nimport { useState, type ReactNode } from \"react\";\nimport type { CmsExposureCampaign } from \"@roottale/cms-client/server\";\n\nexport type ExposurePlacement = \"top\" | \"inline\" | \"bottom\" | \"popup\";\n\nexport function ExposureContent({ campaign, device, controls }: {\n campaign: CmsExposureCampaign;\n device: \"mobile\" | \"desktop\";\n controls?: ReactNode;\n}) {\n const { content } = campaign;\n const [failedUrls, setFailedUrls] = useState<readonly string[]>([]);\n const preferred = device === \"mobile\" && content.mobileAssetUrl ? content.mobileAssetUrl : content.assetUrl;\n const textOnly = campaign.delivery?.version === 2 && [\"notice\", \"card\"].includes(content.variant);\n const assetUrl = textOnly ? undefined : preferred && !failedUrls.includes(preferred) ? preferred : content.assetUrl && !failedUrls.includes(content.assetUrl) ? content.assetUrl : undefined;\n const assetAlt = device === \"mobile\" && assetUrl === content.mobileAssetUrl ? content.mobileAssetAlt ?? content.assetAlt : content.assetAlt;\n const imageOnly = content.variant === \"image\" && Boolean(assetUrl);\n const linkProps = { href: content.linkUrl, ...(content.linkTarget === \"blank\" ? { target: \"_blank\", rel: \"noopener noreferrer\" } : {}) };\n const asset = assetUrl ? <img className=\"rt-exposure__asset\" src={assetUrl} alt={assetAlt ?? content.title}\n onError={() => setFailedUrls((previous) => [...previous, assetUrl])} /> : null;\n return <div className={`rt-exposure__body${imageOnly ? \" rt-exposure__body--image\" : \"\"}`}>\n {imageOnly && content.linkUrl ? <a {...linkProps} aria-label={content.linkLabel ?? content.title}>{asset}</a> : asset}\n <div className={`rt-exposure__copy${imageOnly ? \" rt-exposure__sr-only\" : \"\"}`}>\n {content.eyebrow ? <p className=\"rt-exposure__eyebrow\">{content.eyebrow}</p> : null}\n <h2 className=\"rt-exposure__title\">{content.title}</h2>\n {content.description ? <p className=\"rt-exposure__description\">{content.description}</p> : null}\n {!imageOnly && content.linkUrl && content.linkLabel ? <a className=\"rt-exposure__link\" {...linkProps}>{content.linkLabel}</a> : null}\n </div>\n {controls}\n </div>;\n}\n\n/** 실제 콘텐츠 출력과 같지만 방문 기록·조회·포커스 이동이 없는 편집기 미리보기. */\nexport function RootTaleExposurePreview({ campaign, placement = campaign.kind === \"popup\" ? \"popup\" : \"inline\", device = \"desktop\", className = \"\" }: {\n campaign: CmsExposureCampaign;\n placement?: ExposurePlacement;\n device?: \"mobile\" | \"desktop\";\n className?: string;\n}) {\n return <div className={`rt-exposure rt-exposure--${campaign.kind} rt-exposure--${contentVariant(campaign)} rt-exposure--preview ${className}`}\n data-placement={placement} data-device={device} data-size={campaign.delivery?.size ?? \"medium\"}>\n <ExposureContent campaign={campaign} device={device} />\n </div>;\n}\n\nexport function contentVariant(campaign: CmsExposureCampaign): string {\n // 공개 문자열을 임의 CSS 클래스 목록으로 확장하지 않는다.\n return campaign.content.variant.replace(/[^a-zA-Z0-9_-]/g, \"\");\n}\n","\"use client\";\n\nimport { useEffect, useRef, type ReactNode } from \"react\";\nimport { createPortal } from \"react-dom\";\n\nlet activeDialog: HTMLElement | null = null;\nlet openingOwner: symbol | null = null;\n\nexport function reserveExposurePopup(owner: symbol): boolean {\n if (openingOwner !== null && openingOwner !== owner) return false;\n openingOwner = owner;\n return true;\n}\n\nexport function releaseExposurePopup(owner: symbol) {\n if (openingOwner === owner) openingOwner = null;\n}\n\nexport function exposureCanOpenPopup(): boolean {\n const focused = document.activeElement;\n if (focused instanceof HTMLElement && (focused.matches(\"input, textarea, select\") || focused.isContentEditable)) return false;\n if (activeDialog?.isConnected) return false;\n return !Array.from(document.querySelectorAll<HTMLElement>('dialog[open], [role=\"dialog\"][aria-modal=\"true\"]'))\n .some((element) => {\n // keepMounted Drawer처럼 DOM에 남은 닫힌 모달을 활성 모달로 세지 않는다.\n for (let ancestor: HTMLElement | null = element; ancestor; ancestor = ancestor.parentElement) {\n if (ancestor.hidden || ancestor.inert || ancestor.getAttribute(\"aria-hidden\") === \"true\") return false;\n const style = getComputedStyle(ancestor);\n if (style.display === \"none\" || style.visibility === \"hidden\" || style.visibility === \"collapse\") return false;\n }\n return true;\n });\n}\n\nexport function ExposureDialog({ title, className, children, onClose, device, size, themeElement }: {\n title: string; className: string; children: ReactNode; onClose: () => void;\n device: \"desktop\" | \"mobile\"; size: \"small\" | \"medium\" | \"large\";\n themeElement: HTMLElement | null;\n}) {\n const rootRef = useRef<HTMLDivElement>(null);\n const dialogRef = useRef<HTMLElement>(null);\n const closeRef = useRef(onClose);\n useEffect(() => { closeRef.current = onClose; }, [onClose]);\n useEffect(() => {\n const target = rootRef.current;\n if (!themeElement || !target) return;\n const computed = getComputedStyle(themeElement);\n target.style.fontFamily = computed.fontFamily;\n target.style.color = computed.color;\n for (let index = 0; index < computed.length; index++) {\n const key = computed.item(index);\n if (key.startsWith(\"--\")) target.style.setProperty(key, computed.getPropertyValue(key));\n }\n }, [themeElement]);\n useEffect(() => {\n const root = rootRef.current;\n const dialog = dialogRef.current;\n if (!root || !dialog) return;\n activeDialog = dialog;\n const previous = document.activeElement instanceof HTMLElement ? document.activeElement : null;\n const previousOverflow = document.body.style.overflow;\n const previousPadding = document.body.style.paddingRight;\n const gutter = Math.max(0, window.innerWidth - document.documentElement.clientWidth);\n const padding = Number.parseFloat(getComputedStyle(document.body).paddingRight) || 0;\n document.body.style.overflow = \"hidden\";\n if (gutter > 0) document.body.style.paddingRight = `${padding + gutter}px`;\n const inertBefore = new Map<HTMLElement, boolean>();\n function inertBackground() {\n for (const child of document.body.children) {\n if (!(child instanceof HTMLElement) || child === root || child.contains(root) || inertBefore.has(child)) continue;\n inertBefore.set(child, child.inert); child.inert = true;\n }\n }\n inertBackground();\n const observer = new MutationObserver(inertBackground);\n observer.observe(document.body, { childList: true });\n const focusable = () => Array.from(dialog.querySelectorAll<HTMLElement>('a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex=\"0\"]'))\n .filter((element) => !element.hidden && !element.closest('[hidden], [aria-hidden=\"true\"]'));\n (dialog.querySelector<HTMLElement>('[data-exposure-close]') ?? dialog).focus({ preventScroll: true });\n const handleKey = (event: KeyboardEvent) => {\n if (event.key === \"Escape\") { event.preventDefault(); event.stopPropagation(); closeRef.current(); }\n if (event.key !== \"Tab\") return;\n const items = focusable();\n const first = items[0]; const last = items[items.length - 1];\n if (!first || !last) { event.preventDefault(); dialog.focus(); }\n else if (event.shiftKey && (document.activeElement === first || document.activeElement === dialog)) { event.preventDefault(); last.focus(); }\n else if (!event.shiftKey && (document.activeElement === last || document.activeElement === dialog)) { event.preventDefault(); first.focus(); }\n };\n const handleFocus = (event: FocusEvent) => {\n if (event.target instanceof Node && !dialog.contains(event.target)) (focusable()[0] ?? dialog).focus({ preventScroll: true });\n };\n document.addEventListener(\"keydown\", handleKey, true);\n document.addEventListener(\"focusin\", handleFocus);\n return () => {\n observer.disconnect();\n document.removeEventListener(\"keydown\", handleKey, true);\n document.removeEventListener(\"focusin\", handleFocus);\n for (const [element, wasInert] of inertBefore) element.inert = wasInert;\n if (activeDialog === dialog) activeDialog = null;\n document.body.style.overflow = previousOverflow;\n document.body.style.paddingRight = previousPadding;\n if (previous?.isConnected && !previous.inert) previous.focus({ preventScroll: true });\n else {\n const fallback = document.querySelector<HTMLElement>('main, [role=\"main\"]') ?? document.body;\n const hadTabIndex = fallback.hasAttribute(\"tabindex\");\n if (!hadTabIndex) fallback.tabIndex = -1;\n fallback.focus({ preventScroll: true });\n if (!hadTabIndex) fallback.removeAttribute(\"tabindex\");\n }\n };\n }, []);\n\n return createPortal(<div ref={rootRef} className=\"rt-exposure-backdrop\" data-device={device}>\n <section ref={dialogRef} className={className} role=\"dialog\" aria-modal=\"true\" aria-label={title} tabIndex={-1} data-size={size}>\n {children}\n </section>\n </div>, document.body);\n}\n","\"use client\";\n\nimport { useCallback, useEffect, useRef, useState, type FocusEvent, type TouchEvent } from \"react\";\nimport type { CmsExposureCampaign } from \"@roottale/cms-client/server\";\nimport { exposureCanOpenPopup } from \"./exposure-dialog\";\n\nexport function useExposureCarousel(campaigns: readonly CmsExposureCampaign[], enabled: boolean, durationMs: number, popup: boolean) {\n const [selection, setSelection] = useState({ id: campaigns[0]?.id, cycle: 0 });\n const [elapsedState, setElapsed] = useState({ id: campaigns[0]?.id, value: 0 });\n const [userPaused, setUserPaused] = useState(false);\n const [motionAllowed, setMotionAllowed] = useState(false);\n const [reducedMotion, setReducedMotion] = useState(false);\n const [hovered, setHovered] = useState(false);\n const [focused, setFocused] = useState(false);\n const [touching, setTouching] = useState(false);\n const [environmentPaused, setEnvironmentPaused] = useState(false);\n const elapsedRef = useRef(0);\n const touchOrigin = useRef<{ x: number; y: number } | null>(null);\n const index = Math.max(0, campaigns.findIndex((campaign) => campaign.id === selection.id));\n const campaign = campaigns[index];\n const elapsed = elapsedState.id === campaign?.id ? elapsedState.value : 0;\n const latest = useRef({ campaigns, index });\n useEffect(() => { latest.current = { campaigns, index }; }, [campaigns, index]);\n const duration = Math.max(3000, Math.min(30_000, durationMs));\n const paused = userPaused || hovered || focused || touching || environmentPaused || (reducedMotion && !motionAllowed);\n\n const select = useCallback((nextIndex: number) => {\n const items = latest.current.campaigns;\n if (!items.length) return;\n const normalized = (nextIndex + items.length) % items.length;\n latest.current.index = normalized;\n elapsedRef.current = 0; setElapsed({ id: items[normalized]!.id, value: 0 });\n setSelection((previous) => ({ id: items[normalized]!.id, cycle: previous.cycle + 1 }));\n }, []);\n const next = useCallback(() => select(latest.current.index + 1), [select]);\n const previous = useCallback(() => select(latest.current.index - 1), [select]);\n\n useEffect(() => {\n const media = window.matchMedia(\"(prefers-reduced-motion: reduce)\");\n const changed = () => setReducedMotion(media.matches);\n changed(); media.addEventListener(\"change\", changed);\n return () => media.removeEventListener(\"change\", changed);\n }, []);\n\n useEffect(() => {\n const changed = () => setEnvironmentPaused(document.visibilityState === \"hidden\" || (!popup && !exposureCanOpenPopup()));\n changed();\n const check = setInterval(changed, 250);\n document.addEventListener(\"visibilitychange\", changed);\n return () => { clearInterval(check); document.removeEventListener(\"visibilitychange\", changed); };\n }, [popup]);\n\n useEffect(() => { elapsedRef.current = 0; }, [campaign?.id, selection.cycle]);\n useEffect(() => {\n if (!enabled || paused || campaigns.length < 2) return;\n let previousTick = performance.now();\n const timer = setInterval(() => {\n const now = performance.now();\n elapsedRef.current += now - previousTick; previousTick = now;\n if (elapsedRef.current >= duration) next();\n else setElapsed({ id: campaign?.id, value: elapsedRef.current });\n }, 100);\n return () => clearInterval(timer);\n }, [campaign?.id, campaigns.length, enabled, paused, duration, next, selection.cycle]);\n\n function onTouchStart(event: TouchEvent<HTMLElement>) {\n const touch = event.touches[0];\n if (touch) { touchOrigin.current = { x: touch.clientX, y: touch.clientY }; setTouching(true); }\n }\n function onTouchEnd(event: TouchEvent<HTMLElement>) {\n const origin = touchOrigin.current; const touch = event.changedTouches[0];\n touchOrigin.current = null; setTouching(false);\n if (!origin || !touch || campaigns.length < 2) return;\n const dx = touch.clientX - origin.x; const dy = touch.clientY - origin.y;\n if (Math.abs(dx) >= 48 && Math.abs(dx) > Math.abs(dy) * 1.4) { if (dx < 0) next(); else previous(); }\n }\n function onFocus(event: FocusEvent<HTMLElement>) {\n // 모달을 열며 옮긴 닫기 버튼의 초기 포커스는 자동 전환을 막지 않는다.\n if (!(event.target instanceof HTMLElement)) return;\n if (event.target.closest(\"[data-exposure-close], .rt-exposure__hide-today\")) { setFocused(false); return; }\n setFocused(true);\n }\n const interactions = {\n onPointerEnter: (event: { pointerType: string }) => { if (event.pointerType === \"mouse\") setHovered(true); },\n onPointerLeave: () => setHovered(false),\n onFocusCapture: onFocus,\n onBlurCapture: (event: FocusEvent<HTMLElement>) => { if (!(event.relatedTarget instanceof Node) || !event.currentTarget.contains(event.relatedTarget)) setFocused(false); },\n onTouchStart, onTouchEnd,\n onTouchCancel: () => { touchOrigin.current = null; setTouching(false); },\n };\n const toggle = () => {\n // 재생을 명시적으로 누른 경우 그 버튼의 초점은 재생 요청을 다시 막지 않는다.\n setFocused(false);\n setHovered(false);\n if (reducedMotion && !motionAllowed) { setMotionAllowed(true); setUserPaused(false); }\n else setUserPaused((value) => !value);\n };\n return { campaign, index, paused, userPaused: userPaused || (reducedMotion && !motionAllowed), progress: Math.min(1, elapsed / duration),\n seconds: Math.max(1, Math.ceil((duration - elapsed) / 1000)), next, previous, select, toggle, interactions };\n}\n\nexport function ExposureCarouselControls({ carousel, campaigns }: { carousel: ReturnType<typeof useExposureCarousel>; campaigns: readonly CmsExposureCampaign[] }) {\n if (campaigns.length < 2) return null;\n const firstDot = Math.max(0, Math.min(campaigns.length - 5, carousel.index - 2));\n return <div className=\"rt-exposure-carousel-controls\">\n <div className=\"rt-exposure-carousel-meta\">\n <span className=\"rt-exposure-carousel-count\" aria-live={carousel.paused ? \"polite\" : \"off\"}><strong>{String(carousel.index + 1).padStart(2, \"0\")}</strong><span> / {String(campaigns.length).padStart(2, \"0\")}</span></span>\n <span className=\"rt-exposure-carousel-time\">{carousel.paused ? \"전환 일시정지\" : `${carousel.seconds}초 후 다음 안내`}</span>\n <div className=\"rt-exposure-carousel-actions\">\n <button type=\"button\" aria-label=\"이전 안내\" onClick={carousel.previous}><svg viewBox=\"0 0 24 24\" aria-hidden=\"true\"><path d=\"m14 6-6 6 6 6\" /></svg></button>\n <button type=\"button\" aria-label={carousel.userPaused ? \"자동 전환 재생\" : \"자동 전환 일시정지\"} onClick={carousel.toggle} aria-pressed={carousel.userPaused}>\n <svg viewBox=\"0 0 24 24\" aria-hidden=\"true\">{carousel.userPaused ? <path d=\"m9 5 10 7-10 7Z\" /> : <path d=\"M9 5v14M15 5v14\" />}</svg>\n </button>\n <button type=\"button\" aria-label=\"다음 안내\" onClick={carousel.next}><svg viewBox=\"0 0 24 24\" aria-hidden=\"true\"><path d=\"m10 6 6 6-6 6\" /></svg></button>\n </div>\n </div>\n <div className=\"rt-exposure-carousel-progress\" role=\"progressbar\" aria-label=\"다음 안내까지 진행\" aria-valuemin={0} aria-valuemax={100} aria-valuenow={Math.round(carousel.progress * 100)}>\n <span style={{ transform: `scaleX(${carousel.progress})` }} />\n </div>\n <div className=\"rt-exposure-carousel-dots\" aria-label=\"안내 선택\">\n {campaigns.slice(firstDot, firstDot + 5).map((campaign, offset) => { const index = firstDot + offset; return <button key={campaign.id} type=\"button\" aria-label={`${index + 1}번째 안내: ${campaign.content.title}`} aria-current={index === carousel.index ? \"true\" : undefined} onClick={() => carousel.select(index)}><span /></button>; })}\n </div>\n </div>;\n}\n","\"use client\";\nimport { useEffect, useState } from \"react\";\n\nexport function useMobileExposureScroll(enabled: boolean): boolean {\n const [hidden, setHidden] = useState(false);\n useEffect(() => {\n if (!enabled) { setHidden(false); return; }\n let previous = Math.max(0, window.scrollY);\n let distance = 0;\n const onScroll = () => {\n const current = Math.max(0, window.scrollY);\n const delta = current - previous; previous = current;\n if (current < 32) { distance = 0; setHidden(false); return; }\n if (Math.sign(delta) !== Math.sign(distance)) distance = 0;\n distance += delta;\n if (distance >= 12) { setHidden(true); distance = 0; }\n else if (distance <= -8) { setHidden(false); distance = 0; }\n };\n window.addEventListener(\"scroll\", onScroll, { passive: true });\n return () => window.removeEventListener(\"scroll\", onScroll);\n }, [enabled]);\n return enabled && hidden;\n}\n","import type { CmsExposureCampaign } from \"@roottale/cms-client/server\";\n\nfunction read(kind: \"session\" | \"local\", key: string): string | null {\n try {\n const store = kind === \"session\" ? sessionStorage : localStorage;\n return store.getItem(key);\n } catch { return null; }\n}\n\nconst cookieFallback = new Map<string, string>();\nconst sessionCookie = \"rt_exposure_session\";\nconst preferencesCookie = \"rt_exposure_preferences\";\n\nfunction compactKey(value: string, scope: \"c\" | \"g\"): string {\n // 비밀이나 권한이 아닌 UI 기록 식별자. 64bit로 줄여 다수 슬라이드도 쿠키 한도 안에 둔다.\n let left = 2166136261; let right = 2246822519;\n for (let index = 0; index < value.length; index++) {\n left = Math.imul(left ^ value.charCodeAt(index), 16777619);\n right = Math.imul(right ^ value.charCodeAt(index), 3266489917);\n }\n return `${scope}${(left >>> 0).toString(16).padStart(8, \"0\")}${(right >>> 0).toString(16).padStart(8, \"0\")}`;\n}\n\nfunction rawCookie(name: string): string | null {\n const entry = document.cookie.split(\";\").map((part) => part.trim()).find((part) => part.startsWith(`${name}=`));\n return entry ? entry.slice(name.length + 1) : null;\n}\n\nfunction readPreferences(name: string): Map<string, string> {\n let raw: string | null;\n try {\n raw = cookieFallback.get(name) ?? rawCookie(name) ?? null;\n } catch { raw = cookieFallback.get(name) ?? null; }\n const result = new Map<string, string>();\n for (const entry of raw?.split(\".\") ?? []) {\n const match = /^([cg][a-f0-9]{16})_([svun])([0-9a-z]+)$/.exec(entry);\n if (match) result.set(match[1]!, `${match[2]}:${match[2] === \"u\" ? Number.parseInt(match[3]!, 36) * 1000 : 1}`);\n }\n return result;\n}\n\nfunction writePreferenceKey(key: string, value: string, persistent: boolean, now: number) {\n const name = persistent ? preferencesCookie : sessionCookie;\n const values = readPreferences(name);\n for (const [id, preference] of values) if (preference.startsWith(\"u:\") && Number(preference.slice(2)) <= now) values.delete(id);\n values.delete(key); values.set(key, value);\n const packed = [...values].slice(-100).map(([id, preference]) => `${id}_${preference[0]}${preference.startsWith(\"u:\") ? Math.ceil(Number(preference.slice(2)) / 1000).toString(36) : \"1\"}`).join(\".\");\n try {\n const secure = location.protocol === \"https:\" ? \"; Secure\" : \"\";\n document.cookie = `${name}=${packed}; Path=/; SameSite=Lax${secure}${persistent ? \"; Max-Age=34560000\" : \"\"}`;\n if (rawCookie(name) !== packed) cookieFallback.set(name, packed);\n else cookieFallback.delete(name);\n } catch { cookieFallback.set(name, packed); }\n}\n\nfunction dateInZone(epoch: number, timeZone: string): string {\n return new Intl.DateTimeFormat(\"en-CA\", { timeZone, year: \"numeric\", month: \"2-digit\", day: \"2-digit\" }).format(epoch);\n}\n\n/** DST로 하루가 23/25시간인 곳도 날짜가 바뀌는 첫 시각을 찾는다. */\nexport function nextExposureMidnight(now: number, timeZone: string): number {\n const today = dateInZone(now, timeZone);\n let low = now;\n let high = now + 48 * 3_600_000;\n while (high - low > 1) {\n const mid = Math.floor((low + high) / 2);\n if (dateInZone(mid, timeZone) === today) low = mid;\n else high = mid;\n }\n return high;\n}\n\nexport function exposureWasHidden(campaign: CmsExposureCampaign, now: number, legacyPrefixes: readonly string[] = []): boolean {\n const persistent = readPreferences(preferencesCookie);\n const preferences = [persistent.get(compactKey(campaign.slotKey, \"g\")), persistent.get(compactKey(campaign.id, \"c\")), readPreferences(sessionCookie).get(compactKey(campaign.id, \"c\"))];\n if (preferences.some((preference) => preference?.startsWith(\"s:\") || preference?.startsWith(\"v:\") || preference === \"n:1\" || (preference?.startsWith(\"u:\") && Number(preference.slice(2)) > now))) return true;\n // 종전 local/sessionStorage 기록은 읽어서 호환한다. 새 기록은 쿠키만 쓴다.\n const until = Number(read(\"local\", `rt-exposure-until:${campaign.id}`));\n if (Number.isFinite(until) && until > now) return true;\n if (campaign.repeat === \"always\") return false;\n if (campaign.repeat === \"session\" && read(\"session\", `rt-exposure-viewed:${campaign.id}`)) return true;\n const kind = campaign.repeat === \"session\" ? \"session\" : \"local\";\n for (const prefix of [\"rt-exposure-dismissed:\", ...legacyPrefixes]) {\n const value = read(kind, `${prefix}${campaign.id}`);\n if (value === null) continue;\n if (campaign.repeat !== \"day\") return true;\n const dismissedAt = Number(value);\n // 레거시 접두어·delivery 없는 기존 항목은 종전의 24시간 의미를 보존한다.\n const calendar = prefix === \"rt-exposure-dismissed:\" && campaign.delivery?.dayBoundary === \"calendar\";\n if (Number.isFinite(dismissedAt) && (calendar ? dateInZone(dismissedAt, campaign.delivery!.timeZone) === dateInZone(now, campaign.delivery!.timeZone) : now - dismissedAt < 86_400_000)) return true;\n }\n return false;\n}\n\nexport function rememberExposureView(campaign: CmsExposureCampaign, now: number) {\n if (campaign.repeat === \"session\") writePreferenceKey(compactKey(campaign.id, \"c\"), \"v:1\", false, now);\n // 새 표시 규칙의 일/영구 빈도는 방문자가 닫지 않고 이동해도 실제 노출을 센다.\n if (campaign.delivery && (campaign.repeat === \"day\" || campaign.repeat === \"never\")) rememberExposureDismissal(campaign, now);\n}\n\nexport function rememberExposureDismissal(campaign: CmsExposureCampaign, now: number, forToday = false) {\n if (forToday || campaign.repeat === \"day\") {\n const until = forToday || campaign.delivery?.dayBoundary === \"calendar\" ? nextExposureMidnight(now, campaign.delivery?.timeZone ?? \"Asia/Seoul\") : now + 86_400_000;\n writePreferenceKey(compactKey(campaign.id, \"c\"), `u:${until}`, true, now);\n } else if (campaign.repeat === \"never\") writePreferenceKey(compactKey(campaign.id, \"c\"), \"n:1\", true, now);\n else writePreferenceKey(compactKey(campaign.id, \"c\"), \"s:1\", false, now);\n}\n\n/** 현재 보이는 묶음 전체를 닫는다. 새 캠페인 ID의 다음 방문까지 숨기지는 않는다. */\nexport function rememberExposureGroupDismissal(campaigns: readonly CmsExposureCampaign[], now: number, forToday = false) {\n if (forToday && campaigns[0]) {\n writePreferenceKey(compactKey(campaigns[0].slotKey, \"g\"), `u:${nextExposureMidnight(now, campaigns[0].delivery?.timeZone ?? \"Asia/Seoul\")}`, true, now);\n return;\n }\n for (const campaign of campaigns) rememberExposureDismissal(campaign, now, forToday);\n}\n"],"mappings":"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","names":["useCallback","useEffect","useRef","useState","useEffect","useRef","useState","useState","useEffect","jsx","useEffect","useRef","useState","jsx","jsxs","useState","useRef","campaign","useEffect","previous","useEffect","useState","jsx","jsxs","useRef","useState","campaign","useEffect","first","jsx","useState","useEffect","useRef","useCallback"]}
|