@roottale/cms-renderer-next 0.44.0 → 0.46.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.
Files changed (48) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/dist/{chunk-4LQPZDXZ.js → chunk-6MFHA555.js} +24 -6
  3. package/dist/chunk-6MFHA555.js.map +1 -0
  4. package/dist/chunk-FZK3EXYZ.js +103 -0
  5. package/dist/chunk-FZK3EXYZ.js.map +1 -0
  6. package/dist/{chunk-75LHSTKC.js → chunk-JXRHZGRJ.js} +72 -19
  7. package/dist/chunk-JXRHZGRJ.js.map +1 -0
  8. package/dist/chunk-LEHHSI6G.js +64 -0
  9. package/dist/chunk-LEHHSI6G.js.map +1 -0
  10. package/dist/chunk-OMXEU4LS.js +414 -0
  11. package/dist/chunk-OMXEU4LS.js.map +1 -0
  12. package/dist/{chunk-6PDZSFVZ.js → chunk-UM37DOIK.js} +44 -2
  13. package/dist/chunk-UM37DOIK.js.map +1 -0
  14. package/dist/chunk-YLSQAT6B.js +59 -0
  15. package/dist/chunk-YLSQAT6B.js.map +1 -0
  16. package/dist/chunk-ZL2NA3QT.js +313 -0
  17. package/dist/chunk-ZL2NA3QT.js.map +1 -0
  18. package/dist/concerns-client.d.ts +1 -1
  19. package/dist/contact-map-client.d.ts +27 -0
  20. package/dist/contact-map-client.js +8 -0
  21. package/dist/contact-map-client.js.map +1 -0
  22. package/dist/expertise-client.d.ts +7 -1
  23. package/dist/expertise-client.js +2 -1
  24. package/dist/hero-carousel-client.d.ts +18 -2
  25. package/dist/hero-carousel-client.js +2 -1
  26. package/dist/routes.js +6 -4
  27. package/dist/routes.js.map +1 -1
  28. package/dist/server.js +8 -5
  29. package/dist/{site-config-BHcVF993.d.ts → site-config-CeByrPJF.d.ts} +55 -4
  30. package/dist/spaces-client.d.ts +1 -1
  31. package/dist/team-expanding-stage-client.d.ts +35 -0
  32. package/dist/team-expanding-stage-client.js +8 -0
  33. package/dist/team-expanding-stage-client.js.map +1 -0
  34. package/dist/templates.css +457 -5
  35. package/dist/templates.d.ts +26 -3
  36. package/dist/templates.js +13 -6
  37. package/dist/templates.js.map +1 -1
  38. package/dist/ui-react-motion-client.d.ts +4 -1
  39. package/dist/ui-react-motion-client.js +3 -1
  40. package/dist/validate.js +1 -1
  41. package/package.json +7 -4
  42. package/dist/chunk-4LQPZDXZ.js.map +0 -1
  43. package/dist/chunk-6PDZSFVZ.js.map +0 -1
  44. package/dist/chunk-75LHSTKC.js.map +0 -1
  45. package/dist/chunk-F3G6RVIR.js +0 -220
  46. package/dist/chunk-F3G6RVIR.js.map +0 -1
  47. package/dist/chunk-WWS43XCT.js +0 -206
  48. package/dist/chunk-WWS43XCT.js.map +0 -1
@@ -0,0 +1,64 @@
1
+ "use client";
2
+
3
+ // src/templates/contact-map-client.tsx
4
+ import { useState } from "react";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
+ function ContactMapClickToLoad({
7
+ embedUrl,
8
+ externalUrl,
9
+ facadeImageSrc,
10
+ facadeImageAlt
11
+ }) {
12
+ const [loaded, setLoaded] = useState(false);
13
+ if (loaded) {
14
+ return /* @__PURE__ */ jsx("div", { className: "rt-tpl-contact-map", children: /* @__PURE__ */ jsx(
15
+ "iframe",
16
+ {
17
+ src: embedUrl,
18
+ title: "\uC624\uC2DC\uB294\uAE38 \uC9C0\uB3C4",
19
+ loading: "lazy",
20
+ referrerPolicy: "no-referrer-when-downgrade"
21
+ }
22
+ ) });
23
+ }
24
+ return (
25
+ // `rt-tpl-contact-map` 클래스를 함께 붙여 aspect-ratio/border 등 variant별
26
+ // 박스 규칙(channel-cards/visit-band 오버라이드 포함, templates.css)을
27
+ // 그대로 물려받는다 — 클릭 후 iframe 전환 시 레이아웃 시프트가 없다.
28
+ /* @__PURE__ */ jsxs("div", { className: "rt-tpl-contact-map rt-tpl-contact-map-click", children: [
29
+ facadeImageSrc ? /* @__PURE__ */ jsx(
30
+ "img",
31
+ {
32
+ className: "rt-tpl-contact-map-click-bg",
33
+ src: facadeImageSrc,
34
+ alt: facadeImageAlt ?? "",
35
+ loading: "lazy"
36
+ }
37
+ ) : null,
38
+ /* @__PURE__ */ jsx(
39
+ "button",
40
+ {
41
+ type: "button",
42
+ className: "rt-tpl-contact-map-click-btn",
43
+ onClick: () => setLoaded(true),
44
+ children: "\uC9C0\uB3C4 \uC5F4\uAE30"
45
+ }
46
+ ),
47
+ externalUrl ? /* @__PURE__ */ jsx(
48
+ "a",
49
+ {
50
+ className: "rt-tpl-contact-map-click-external",
51
+ href: externalUrl,
52
+ target: "_blank",
53
+ rel: "noopener noreferrer",
54
+ children: "\uC0C8 \uCC3D\uC5D0\uC11C \uD06C\uAC8C \uBCF4\uAE30"
55
+ }
56
+ ) : null
57
+ ] })
58
+ );
59
+ }
60
+
61
+ export {
62
+ ContactMapClickToLoad
63
+ };
64
+ //# sourceMappingURL=chunk-LEHHSI6G.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/templates/contact-map-client.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * `contact` 지도 블록의 \"클릭-투-로드\" 모드(ADR-0086 P2 D3) — chambarun\n * mockup `.map-facade`/`site.js` \"지도: 클릭-투-로드\" 이식\n * (`clients/chambarun/mockup/assets/site.js` \"지도: 클릭-투-로드\" 블록).\n *\n * 원본은 DOM 을 직접 갈아끼우는 vanilla 스크립트(`replaceWith`)였다 — 여기서는\n * React state 로 같은 효과(정적 프리뷰 → 클릭 시 1회 iframe 마운트)를 낸다.\n * `ContactMapBlock`(sections.tsx, RSC)이 embedUrl 이 allowlist 를 통과했을\n * 때만 이 컴포넌트를 렌더하므로, 이 컴포넌트 자신은 allowlist 재검증을 하지\n * 않는다(호출자 계약).\n *\n * 빌드: tsup 독립 entry(`contact-map-client`, tsup.config.ts) — \"use client\"\n * 경계가 `templates.js`(서버 전용)로 새지 않게 격리(hero-carousel-client 류와\n * 동일 계약).\n */\nimport { useState, type ReactElement } from \"react\";\n\nexport interface ContactMapClickToLoadProps {\n readonly embedUrl: string;\n readonly externalUrl: string | undefined;\n readonly facadeImageSrc: string | undefined;\n readonly facadeImageAlt: string | undefined;\n}\n\nexport function ContactMapClickToLoad({\n embedUrl,\n externalUrl,\n facadeImageSrc,\n facadeImageAlt,\n}: ContactMapClickToLoadProps): ReactElement {\n const [loaded, setLoaded] = useState(false);\n\n if (loaded) {\n return (\n <div className=\"rt-tpl-contact-map\">\n <iframe\n src={embedUrl}\n title=\"오시는길 지도\"\n loading=\"lazy\"\n referrerPolicy=\"no-referrer-when-downgrade\"\n />\n </div>\n );\n }\n\n return (\n // `rt-tpl-contact-map` 클래스를 함께 붙여 aspect-ratio/border 등 variant별\n // 박스 규칙(channel-cards/visit-band 오버라이드 포함, templates.css)을\n // 그대로 물려받는다 — 클릭 후 iframe 전환 시 레이아웃 시프트가 없다.\n <div className=\"rt-tpl-contact-map rt-tpl-contact-map-click\">\n {facadeImageSrc ? (\n <img\n className=\"rt-tpl-contact-map-click-bg\"\n src={facadeImageSrc}\n alt={facadeImageAlt ?? \"\"}\n loading=\"lazy\"\n />\n ) : null}\n <button\n type=\"button\"\n className=\"rt-tpl-contact-map-click-btn\"\n onClick={() => setLoaded(true)}\n >\n 지도 열기\n </button>\n {externalUrl ? (\n <a\n className=\"rt-tpl-contact-map-click-external\"\n href={externalUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n 새 창에서 크게 보기\n </a>\n ) : null}\n </div>\n );\n}\n"],"mappings":";AAiBA,SAAS,gBAAmC;AAoBpC,cAcJ,YAdI;AAXD,SAAS,sBAAsB;AAAA,EACpC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAA6C;AAC3C,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAS,KAAK;AAE1C,MAAI,QAAQ;AACV,WACE,oBAAC,SAAI,WAAU,sBACb;AAAA,MAAC;AAAA;AAAA,QACC,KAAK;AAAA,QACL,OAAM;AAAA,QACN,SAAQ;AAAA,QACR,gBAAe;AAAA;AAAA,IACjB,GACF;AAAA,EAEJ;AAEA;AAAA;AAAA;AAAA;AAAA,IAIE,qBAAC,SAAI,WAAU,+CACZ;AAAA,uBACC;AAAA,QAAC;AAAA;AAAA,UACC,WAAU;AAAA,UACV,KAAK;AAAA,UACL,KAAK,kBAAkB;AAAA,UACvB,SAAQ;AAAA;AAAA,MACV,IACE;AAAA,MACJ;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,WAAU;AAAA,UACV,SAAS,MAAM,UAAU,IAAI;AAAA,UAC9B;AAAA;AAAA,MAED;AAAA,MACC,cACC;AAAA,QAAC;AAAA;AAAA,UACC,WAAU;AAAA,UACV,MAAM;AAAA,UACN,QAAO;AAAA,UACP,KAAI;AAAA,UACL;AAAA;AAAA,MAED,IACE;AAAA,OACN;AAAA;AAEJ;","names":[]}
@@ -0,0 +1,414 @@
1
+ "use client";
2
+
3
+ import {
4
+ useRovingTablist
5
+ } from "./chunk-YLSQAT6B.js";
6
+
7
+ // src/templates/hero-carousel-client.tsx
8
+ import { useCallback as useCallback3, useEffect as useEffect2, useRef as useRef2, useState as useState3 } from "react";
9
+ import useEmblaCarousel from "embla-carousel-react";
10
+
11
+ // src/templates/hero-carousel-curtain.tsx
12
+ import { useCallback, useRef, useState } from "react";
13
+ import { jsx, jsxs } from "react/jsx-runtime";
14
+ var LEAVING_CLEAR_MS = 980;
15
+ function useCurtainCarousel(slideCount, reducedMotion) {
16
+ const [selectedIndex, setSelectedIndex] = useState(0);
17
+ const [leavingIndex, setLeavingIndex] = useState(null);
18
+ const selectedRef = useRef(0);
19
+ const runRef = useRef(0);
20
+ const clearTimerRef = useRef(void 0);
21
+ const goTo = useCallback(
22
+ (nextRaw) => {
23
+ if (slideCount < 1) return;
24
+ const nextIndex = (nextRaw % slideCount + slideCount) % slideCount;
25
+ const prevIndex = selectedRef.current;
26
+ if (nextIndex === prevIndex) return;
27
+ selectedRef.current = nextIndex;
28
+ setSelectedIndex(nextIndex);
29
+ clearTimeout(clearTimerRef.current);
30
+ if (reducedMotion) {
31
+ setLeavingIndex(null);
32
+ return;
33
+ }
34
+ setLeavingIndex(prevIndex);
35
+ runRef.current += 1;
36
+ const runId = runRef.current;
37
+ clearTimerRef.current = setTimeout(() => {
38
+ if (runId === runRef.current) setLeavingIndex(null);
39
+ }, LEAVING_CLEAR_MS);
40
+ },
41
+ [slideCount, reducedMotion]
42
+ );
43
+ const next = useCallback(() => goTo(selectedRef.current + 1), [goTo]);
44
+ const prev = useCallback(() => goTo(selectedRef.current - 1), [goTo]);
45
+ return { selectedIndex, leavingIndex, goTo, next, prev };
46
+ }
47
+ function CurtainTrack({
48
+ slides,
49
+ selectedIndex,
50
+ leavingIndex
51
+ }) {
52
+ return /* @__PURE__ */ jsx("div", { className: "rt-tpl-hero-carousel-viewport", children: /* @__PURE__ */ jsx("div", { className: "rt-tpl-hero-carousel-track", children: slides.map((slide, index) => {
53
+ const active = index === selectedIndex;
54
+ const leaving = index === leavingIndex;
55
+ return /* @__PURE__ */ jsxs(
56
+ "div",
57
+ {
58
+ className: "rt-tpl-hero-carousel-slide",
59
+ "aria-roledescription": "slide",
60
+ "aria-label": `Slide ${index + 1} of ${slides.length}`,
61
+ "aria-hidden": active ? void 0 : true,
62
+ "data-active": active ? "true" : "false",
63
+ "data-leaving": leaving ? "true" : "false",
64
+ children: [
65
+ /* @__PURE__ */ jsx(
66
+ "img",
67
+ {
68
+ className: "rt-tpl-hero-carousel-img",
69
+ src: slide.image,
70
+ alt: slide.alt,
71
+ "data-active": active ? "true" : "false",
72
+ loading: index === 0 ? "eager" : "lazy"
73
+ }
74
+ ),
75
+ active ? /* @__PURE__ */ jsx("span", { className: "rt-tpl-hero-carousel-sheen", "aria-hidden": "true" }) : null
76
+ ]
77
+ },
78
+ `${slide.image}-${index}`
79
+ );
80
+ }) }) });
81
+ }
82
+
83
+ // src/templates/hero-carousel-hooks.ts
84
+ import { useCallback as useCallback2, useState as useState2 } from "react";
85
+ import { useEffect } from "react";
86
+ function usePrefersReducedMotion() {
87
+ const [reduced, setReduced] = useState2(false);
88
+ useEffect(() => {
89
+ const query = window.matchMedia("(prefers-reduced-motion: reduce)");
90
+ setReduced(query.matches);
91
+ const onChange = (event) => setReduced(event.matches);
92
+ query.addEventListener("change", onChange);
93
+ return () => query.removeEventListener("change", onChange);
94
+ }, []);
95
+ return reduced;
96
+ }
97
+ function useDocumentVisible() {
98
+ const [visible, setVisible] = useState2(true);
99
+ useEffect(() => {
100
+ const onVisibility = () => setVisible(document.visibilityState === "visible");
101
+ onVisibility();
102
+ document.addEventListener("visibilitychange", onVisibility);
103
+ return () => document.removeEventListener("visibilitychange", onVisibility);
104
+ }, []);
105
+ return visible;
106
+ }
107
+ function useAutoplayGate(params) {
108
+ const { autoplay, reducedMotion, rootRef } = params;
109
+ const tabVisible = useDocumentVisible();
110
+ const [hovered, setHovered] = useState2(false);
111
+ const [focusedWithin, setFocusedWithin] = useState2(false);
112
+ const [userPaused, setUserPaused] = useState2(false);
113
+ const onBlur = useCallback2(
114
+ (event) => {
115
+ const next = event.relatedTarget;
116
+ if (!next || !rootRef.current?.contains(next)) {
117
+ setFocusedWithin(false);
118
+ }
119
+ },
120
+ [rootRef]
121
+ );
122
+ const shouldAutoplay = autoplay && !reducedMotion && tabVisible && !userPaused && !hovered && !focusedWithin;
123
+ return {
124
+ shouldAutoplay,
125
+ userPaused,
126
+ togglePaused: () => setUserPaused((value) => !value),
127
+ rootHandlers: {
128
+ onMouseEnter: () => setHovered(true),
129
+ onMouseLeave: () => setHovered(false),
130
+ onFocus: () => setFocusedWithin(true),
131
+ onBlur
132
+ }
133
+ };
134
+ }
135
+
136
+ // src/templates/hero-carousel-slide-track.tsx
137
+ import { jsx as jsx2 } from "react/jsx-runtime";
138
+ function SlideTrack({
139
+ viewportRef,
140
+ slides,
141
+ selectedIndex
142
+ }) {
143
+ return /* @__PURE__ */ jsx2("div", { className: "rt-tpl-hero-carousel-viewport", ref: viewportRef, children: /* @__PURE__ */ jsx2("div", { className: "rt-tpl-hero-carousel-track", children: slides.map((slide, index) => /* @__PURE__ */ jsx2(
144
+ "div",
145
+ {
146
+ className: "rt-tpl-hero-carousel-slide",
147
+ "aria-roledescription": "slide",
148
+ "aria-label": `Slide ${index + 1} of ${slides.length}`,
149
+ "aria-hidden": index === selectedIndex ? void 0 : true,
150
+ children: /* @__PURE__ */ jsx2(
151
+ "img",
152
+ {
153
+ className: "rt-tpl-hero-carousel-img",
154
+ src: slide.image,
155
+ alt: slide.alt,
156
+ "data-active": index === selectedIndex ? "true" : "false",
157
+ loading: index === 0 ? "eager" : "lazy"
158
+ }
159
+ )
160
+ },
161
+ `${slide.image}-${index}`
162
+ )) }) });
163
+ }
164
+
165
+ // src/templates/hero-carousel-client.tsx
166
+ import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
167
+ function PauseIcon() {
168
+ return /* @__PURE__ */ jsxs2("svg", { viewBox: "0 0 24 24", "aria-hidden": "true", focusable: "false", children: [
169
+ /* @__PURE__ */ jsx3("rect", { x: "6", y: "5", width: "4", height: "14", rx: "1", fill: "currentColor" }),
170
+ /* @__PURE__ */ jsx3("rect", { x: "14", y: "5", width: "4", height: "14", rx: "1", fill: "currentColor" })
171
+ ] });
172
+ }
173
+ function PlayIcon() {
174
+ return /* @__PURE__ */ jsx3("svg", { viewBox: "0 0 24 24", "aria-hidden": "true", focusable: "false", children: /* @__PURE__ */ jsx3("path", { d: "M8 5v14l11-7z", fill: "currentColor" }) });
175
+ }
176
+ function HeroCarouselViewport({
177
+ slides,
178
+ autoplay,
179
+ intervalMs,
180
+ scrim,
181
+ transition = "slide"
182
+ }) {
183
+ const isCurtain = transition === "curtain";
184
+ const reducedMotion = usePrefersReducedMotion();
185
+ const rootRef = useRef2(null);
186
+ const [emblaRef, emblaApi] = useEmblaCarousel({
187
+ loop: true,
188
+ duration: reducedMotion ? 1 : 20
189
+ });
190
+ const [emblaSelectedIndex, setEmblaSelectedIndex] = useState3(0);
191
+ const curtain = useCurtainCarousel(slides.length, reducedMotion);
192
+ const onEmblaSelect = useCallback3(() => {
193
+ if (!emblaApi) return;
194
+ setEmblaSelectedIndex(emblaApi.selectedScrollSnap());
195
+ }, [emblaApi]);
196
+ useEffect2(() => {
197
+ if (!emblaApi) return;
198
+ onEmblaSelect();
199
+ emblaApi.on("select", onEmblaSelect);
200
+ emblaApi.on("reInit", onEmblaSelect);
201
+ return () => {
202
+ emblaApi.off("select", onEmblaSelect);
203
+ emblaApi.off("reInit", onEmblaSelect);
204
+ };
205
+ }, [emblaApi, onEmblaSelect]);
206
+ const selectedIndex = isCurtain ? curtain.selectedIndex : emblaSelectedIndex;
207
+ const autoplayGate = useAutoplayGate({ autoplay, reducedMotion, rootRef });
208
+ useEffect2(() => {
209
+ if (!autoplayGate.shouldAutoplay) return;
210
+ if (!isCurtain && !emblaApi) return;
211
+ const id = window.setInterval(() => {
212
+ if (isCurtain) curtain.next();
213
+ else emblaApi?.scrollNext();
214
+ }, intervalMs);
215
+ return () => window.clearInterval(id);
216
+ }, [autoplayGate.shouldAutoplay, intervalMs, isCurtain, curtain.next, emblaApi]);
217
+ const scrollPrev = useCallback3(() => {
218
+ if (isCurtain) {
219
+ curtain.prev();
220
+ return;
221
+ }
222
+ emblaApi?.scrollPrev(reducedMotion);
223
+ }, [isCurtain, curtain.prev, emblaApi, reducedMotion]);
224
+ const scrollNext = useCallback3(() => {
225
+ if (isCurtain) {
226
+ curtain.next();
227
+ return;
228
+ }
229
+ emblaApi?.scrollNext(reducedMotion);
230
+ }, [isCurtain, curtain.next, emblaApi, reducedMotion]);
231
+ const scrollTo = useCallback3(
232
+ (index) => {
233
+ if (isCurtain) {
234
+ curtain.goTo(index);
235
+ return;
236
+ }
237
+ emblaApi?.scrollTo(index, reducedMotion);
238
+ },
239
+ [isCurtain, curtain.goTo, emblaApi, reducedMotion]
240
+ );
241
+ const onKeyDown = useCallback3(
242
+ (event) => {
243
+ if (event.key === "ArrowLeft") {
244
+ event.preventDefault();
245
+ scrollPrev();
246
+ } else if (event.key === "ArrowRight") {
247
+ event.preventDefault();
248
+ scrollNext();
249
+ }
250
+ },
251
+ [scrollPrev, scrollNext]
252
+ );
253
+ const selectedSlide = slides[selectedIndex];
254
+ const hasSlidePanel = Boolean(
255
+ selectedSlide && (selectedSlide.eyebrow || selectedSlide.title || selectedSlide.subtitle || selectedSlide.description || selectedSlide.primaryCta || selectedSlide.secondaryCta || selectedSlide.metrics && selectedSlide.metrics.length > 0)
256
+ );
257
+ const hasTabLabels = slides.some((slide) => slide.tabLabel || slide.tabMeta);
258
+ const showPlayPause = autoplay && !reducedMotion;
259
+ const { getTabProps, onKeyDown: onTablistKeyDown } = useRovingTablist({
260
+ count: slides.length,
261
+ selectedIndex,
262
+ onSelect: scrollTo,
263
+ orientation: "horizontal"
264
+ });
265
+ return /* @__PURE__ */ jsxs2(
266
+ "div",
267
+ {
268
+ ref: rootRef,
269
+ className: "rt-tpl-hero-carousel",
270
+ role: "region",
271
+ "aria-roledescription": "carousel",
272
+ "aria-label": "\uBC30\uACBD \uC774\uBBF8\uC9C0 \uCE90\uB7EC\uC140",
273
+ tabIndex: 0,
274
+ onKeyDown,
275
+ "data-reduced-motion": reducedMotion ? "true" : "false",
276
+ "data-scrim": scrim ?? "soft",
277
+ "data-transition": transition,
278
+ onMouseEnter: autoplayGate.rootHandlers.onMouseEnter,
279
+ onMouseLeave: autoplayGate.rootHandlers.onMouseLeave,
280
+ onFocus: autoplayGate.rootHandlers.onFocus,
281
+ onBlur: autoplayGate.rootHandlers.onBlur,
282
+ children: [
283
+ isCurtain ? /* @__PURE__ */ jsx3(
284
+ CurtainTrack,
285
+ {
286
+ slides,
287
+ selectedIndex: curtain.selectedIndex,
288
+ leavingIndex: curtain.leavingIndex
289
+ }
290
+ ) : /* @__PURE__ */ jsx3(
291
+ SlideTrack,
292
+ {
293
+ viewportRef: emblaRef,
294
+ slides,
295
+ selectedIndex: emblaSelectedIndex
296
+ }
297
+ ),
298
+ /* @__PURE__ */ jsx3(
299
+ "button",
300
+ {
301
+ type: "button",
302
+ className: "rt-tpl-hero-carousel-arrow rt-tpl-hero-carousel-arrow--prev",
303
+ "aria-label": "\uC774\uC804 \uC2AC\uB77C\uC774\uB4DC",
304
+ onClick: scrollPrev,
305
+ children: /* @__PURE__ */ jsx3("svg", { viewBox: "0 0 24 24", "aria-hidden": "true", focusable: "false", children: /* @__PURE__ */ jsx3("path", { d: "M15 5 8 12l7 7", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) })
306
+ }
307
+ ),
308
+ /* @__PURE__ */ jsx3(
309
+ "button",
310
+ {
311
+ type: "button",
312
+ className: "rt-tpl-hero-carousel-arrow rt-tpl-hero-carousel-arrow--next",
313
+ "aria-label": "\uB2E4\uC74C \uC2AC\uB77C\uC774\uB4DC",
314
+ onClick: scrollNext,
315
+ children: /* @__PURE__ */ jsx3("svg", { viewBox: "0 0 24 24", "aria-hidden": "true", focusable: "false", children: /* @__PURE__ */ jsx3("path", { d: "m9 5 7 7-7 7", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) })
316
+ }
317
+ ),
318
+ hasSlidePanel && selectedSlide ? /* @__PURE__ */ jsxs2(
319
+ "div",
320
+ {
321
+ className: "rt-tpl-hero-carousel-slide-panel",
322
+ children: [
323
+ selectedSlide.eyebrow ? /* @__PURE__ */ jsx3("p", { className: "rt-tpl-hero-carousel-slide-eyebrow", children: selectedSlide.eyebrow }) : null,
324
+ selectedSlide.title ? /* @__PURE__ */ jsx3("p", { className: "rt-tpl-hero-carousel-slide-title", children: selectedSlide.title }) : null,
325
+ selectedSlide.subtitle ? /* @__PURE__ */ jsx3("p", { className: "rt-tpl-hero-carousel-slide-subtitle", children: selectedSlide.subtitle }) : null,
326
+ selectedSlide.description ? /* @__PURE__ */ jsx3("p", { className: "rt-tpl-hero-carousel-slide-desc", children: selectedSlide.description }) : null,
327
+ selectedSlide.metrics && selectedSlide.metrics.length > 0 ? /* @__PURE__ */ jsx3("dl", { className: "rt-tpl-hero-carousel-slide-metrics", children: selectedSlide.metrics.map((metric) => /* @__PURE__ */ jsxs2("div", { children: [
328
+ /* @__PURE__ */ jsx3("dt", { children: metric.label }),
329
+ /* @__PURE__ */ jsxs2("dd", { children: [
330
+ metric.value,
331
+ metric.unit ? /* @__PURE__ */ jsx3("span", { children: metric.unit }) : null
332
+ ] }),
333
+ metric.source ? /* @__PURE__ */ jsx3("dd", { children: metric.source }) : null
334
+ ] }, `${metric.value}-${metric.label}`)) }) : null,
335
+ selectedSlide.primaryCta || selectedSlide.secondaryCta ? /* @__PURE__ */ jsxs2("div", { className: "rt-tpl-hero-carousel-slide-actions", children: [
336
+ selectedSlide.primaryCta ? /* @__PURE__ */ jsx3(
337
+ "a",
338
+ {
339
+ className: "rt-tpl-btn rt-tpl-btn--primary",
340
+ href: selectedSlide.primaryCta.href,
341
+ "data-track": "cta_hero",
342
+ children: selectedSlide.primaryCta.label
343
+ }
344
+ ) : null,
345
+ selectedSlide.secondaryCta ? /* @__PURE__ */ jsx3(
346
+ "a",
347
+ {
348
+ className: "rt-tpl-btn rt-tpl-btn--secondary",
349
+ href: selectedSlide.secondaryCta.href,
350
+ "data-track": "cta_hero",
351
+ children: selectedSlide.secondaryCta.label
352
+ }
353
+ ) : null
354
+ ] }) : null
355
+ ]
356
+ },
357
+ isCurtain ? `panel-${selectedIndex}` : "panel"
358
+ ) : null,
359
+ /* @__PURE__ */ jsxs2("div", { className: "rt-tpl-hero-carousel-controls", children: [
360
+ /* @__PURE__ */ jsx3(
361
+ "div",
362
+ {
363
+ className: hasTabLabels ? "rt-tpl-hero-carousel-dots rt-tpl-hero-carousel-dots--tabs" : "rt-tpl-hero-carousel-dots",
364
+ role: "tablist",
365
+ "aria-label": "\uC2AC\uB77C\uC774\uB4DC \uC120\uD0DD",
366
+ onKeyDown: onTablistKeyDown,
367
+ children: slides.map((_slide, index) => /* @__PURE__ */ jsx3(
368
+ "button",
369
+ {
370
+ ...getTabProps(index),
371
+ type: "button",
372
+ role: "tab",
373
+ className: "rt-tpl-hero-carousel-dot",
374
+ "aria-selected": index === selectedIndex,
375
+ "aria-label": `${index + 1}\uBC88 \uC2AC\uB77C\uC774\uB4DC\uB85C \uC774\uB3D9`,
376
+ "data-active": index === selectedIndex ? "true" : "false",
377
+ onClick: () => scrollTo(index),
378
+ children: hasTabLabels ? /* @__PURE__ */ jsxs2(Fragment, { children: [
379
+ /* @__PURE__ */ jsx3("span", { children: _slide.tabLabel ?? `${index + 1}` }),
380
+ _slide.tabMeta ? /* @__PURE__ */ jsx3("small", { children: _slide.tabMeta }) : null
381
+ ] }) : null
382
+ },
383
+ index
384
+ ))
385
+ }
386
+ ),
387
+ /* @__PURE__ */ jsxs2("div", { className: "rt-tpl-hero-carousel-controls-right", children: [
388
+ showPlayPause ? /* @__PURE__ */ jsx3(
389
+ "button",
390
+ {
391
+ type: "button",
392
+ className: "rt-tpl-hero-carousel-playpause",
393
+ "aria-pressed": autoplayGate.userPaused,
394
+ "aria-label": autoplayGate.userPaused ? "\uC790\uB3D9 \uC804\uD658 \uC7AC\uC0DD" : "\uC790\uB3D9 \uC804\uD658 \uC77C\uC2DC\uC815\uC9C0",
395
+ onClick: autoplayGate.togglePaused,
396
+ children: autoplayGate.userPaused ? /* @__PURE__ */ jsx3(PlayIcon, {}) : /* @__PURE__ */ jsx3(PauseIcon, {})
397
+ }
398
+ ) : null,
399
+ /* @__PURE__ */ jsxs2("p", { className: "rt-tpl-hero-carousel-counter", "aria-hidden": "true", children: [
400
+ /* @__PURE__ */ jsx3("span", { className: "rt-tpl-hero-carousel-counter-current", children: selectedIndex + 1 }),
401
+ "/",
402
+ /* @__PURE__ */ jsx3("span", { className: "rt-tpl-hero-carousel-counter-total", children: slides.length })
403
+ ] })
404
+ ] })
405
+ ] })
406
+ ]
407
+ }
408
+ );
409
+ }
410
+
411
+ export {
412
+ HeroCarouselViewport
413
+ };
414
+ //# sourceMappingURL=chunk-OMXEU4LS.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/templates/hero-carousel-client.tsx","../src/templates/hero-carousel-curtain.tsx","../src/templates/hero-carousel-hooks.ts","../src/templates/hero-carousel-slide-track.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * `hero` `carousel` variant — 배경 사진 전환 메커니즘만 담당하는 client\n * island (M6 서버 우선: 인터랙션 최소 단위 격리). 카피 오버레이(h1/subtitle/\n * eyebrow/CTA)는 `TemplateHero`(RSC, sections.tsx)가 서버에서 그린다 — 이\n * 컴포넌트는 배경 슬라이드 + 도트 + 화살표 + \"N/M\" 카운터만 렌더한다.\n *\n * 빌드 계약: 본 파일은 tsup 의 독립 entry(`hero-carousel-client`)로 번들되고\n * `esbuild-plugin-preserve-directives` 가 \"use client\" 지시어를 해당 청크\n * 최상단에 보존한다 — `templates.js`(서버 전용, 나머지 4개 hero variant +\n * rich-text/services/faq/cta-banner/trust/contact) 로 병합되지 않는다\n * (tsup.config.ts 참고).\n *\n * 모션 규율(M5 + ADR-0086 P2 D3/D4, ds-rule-pack.md):\n * - `transition`: \"slide\"(기본, embla 가로 스크롤) | \"curtain\"(clip-path 와이프\n * — chambarun 이식, `hero-carousel-curtain.tsx`). variant 는 그대로\n * \"carousel\" 하나 — \"variant=구조, motion=인상\" 규칙(신규 variant 금지).\n * - `prefers-reduced-motion: reduce` → autoplay 강제 off + 전환은 즉시(embla\n * `jump=true` 무전환 / curtain 은 leaving 단계 생략) + Ken Burns·신 스윕·카피\n * 스태거 없음.\n * - autoplay 정지 사유 4종(D4): hover, focus-within, pause/재생 버튼, Page\n * Visibility API(탭 비활성). 합성은 `hero-carousel-hooks.ts`\n * `useAutoplayGate`.\n * - 좌/우 화살표 키보드 이동(포커스가 캐러셀 안에 있을 때), 도트는\n * `role=\"tablist\"`/`role=\"tab\"` + roving tabindex(방향키/Home/End, D4) —\n * 공용 `useRovingTablist`(`expertise-client.tsx` 와 동일 계약 재사용).\n * - WAI-ARIA carousel 패턴: 컨테이너 `role=\"region\"` +\n * `aria-roledescription=\"carousel\"`, 슬라이드별 `aria-roledescription=\"slide\"`\n * + `aria-label=\"Slide N of M\"`.\n */\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport type { KeyboardEvent, ReactElement } from \"react\";\nimport useEmblaCarousel from \"embla-carousel-react\";\n\nimport { useCurtainCarousel, CurtainTrack } from \"./hero-carousel-curtain.js\";\nimport { useAutoplayGate, usePrefersReducedMotion } from \"./hero-carousel-hooks.js\";\nimport { SlideTrack } from \"./hero-carousel-slide-track.js\";\nimport type { HeroCarouselViewportProps } from \"./hero-carousel-types.js\";\nimport { useRovingTablist } from \"./use-roving-tablist.js\";\n\nexport type {\n HeroCarouselSlideInput,\n HeroCarouselTransition,\n HeroCarouselViewportProps,\n} from \"./hero-carousel-types.js\";\n\nfunction PauseIcon(): ReactElement {\n return (\n <svg viewBox=\"0 0 24 24\" aria-hidden=\"true\" focusable=\"false\">\n <rect x=\"6\" y=\"5\" width=\"4\" height=\"14\" rx=\"1\" fill=\"currentColor\" />\n <rect x=\"14\" y=\"5\" width=\"4\" height=\"14\" rx=\"1\" fill=\"currentColor\" />\n </svg>\n );\n}\n\nfunction PlayIcon(): ReactElement {\n return (\n <svg viewBox=\"0 0 24 24\" aria-hidden=\"true\" focusable=\"false\">\n <path d=\"M8 5v14l11-7z\" fill=\"currentColor\" />\n </svg>\n );\n}\n\nexport function HeroCarouselViewport({\n slides,\n autoplay,\n intervalMs,\n scrim,\n transition = \"slide\",\n}: HeroCarouselViewportProps) {\n const isCurtain = transition === \"curtain\";\n const reducedMotion = usePrefersReducedMotion();\n const rootRef = useRef<HTMLDivElement | null>(null);\n\n const [emblaRef, emblaApi] = useEmblaCarousel({\n loop: true,\n duration: reducedMotion ? 1 : 20,\n });\n const [emblaSelectedIndex, setEmblaSelectedIndex] = useState(0);\n const curtain = useCurtainCarousel(slides.length, reducedMotion);\n\n const onEmblaSelect = useCallback(() => {\n if (!emblaApi) return;\n setEmblaSelectedIndex(emblaApi.selectedScrollSnap());\n }, [emblaApi]);\n\n useEffect(() => {\n if (!emblaApi) return;\n onEmblaSelect();\n emblaApi.on(\"select\", onEmblaSelect);\n emblaApi.on(\"reInit\", onEmblaSelect);\n return () => {\n emblaApi.off(\"select\", onEmblaSelect);\n emblaApi.off(\"reInit\", onEmblaSelect);\n };\n }, [emblaApi, onEmblaSelect]);\n\n const selectedIndex = isCurtain ? curtain.selectedIndex : emblaSelectedIndex;\n\n const autoplayGate = useAutoplayGate({ autoplay, reducedMotion, rootRef });\n\n useEffect(() => {\n if (!autoplayGate.shouldAutoplay) return;\n if (!isCurtain && !emblaApi) return;\n const id = window.setInterval(() => {\n if (isCurtain) curtain.next();\n else emblaApi?.scrollNext();\n }, intervalMs);\n return () => window.clearInterval(id);\n }, [autoplayGate.shouldAutoplay, intervalMs, isCurtain, curtain.next, emblaApi]);\n\n const scrollPrev = useCallback(() => {\n if (isCurtain) {\n curtain.prev();\n return;\n }\n emblaApi?.scrollPrev(reducedMotion);\n }, [isCurtain, curtain.prev, emblaApi, reducedMotion]);\n\n const scrollNext = useCallback(() => {\n if (isCurtain) {\n curtain.next();\n return;\n }\n emblaApi?.scrollNext(reducedMotion);\n }, [isCurtain, curtain.next, emblaApi, reducedMotion]);\n\n const scrollTo = useCallback(\n (index: number) => {\n if (isCurtain) {\n curtain.goTo(index);\n return;\n }\n emblaApi?.scrollTo(index, reducedMotion);\n },\n [isCurtain, curtain.goTo, emblaApi, reducedMotion],\n );\n\n const onKeyDown = useCallback(\n (event: KeyboardEvent<HTMLDivElement>) => {\n if (event.key === \"ArrowLeft\") {\n event.preventDefault();\n scrollPrev();\n } else if (event.key === \"ArrowRight\") {\n event.preventDefault();\n scrollNext();\n }\n },\n [scrollPrev, scrollNext],\n );\n\n const selectedSlide = slides[selectedIndex];\n const hasSlidePanel = Boolean(\n selectedSlide &&\n (selectedSlide.eyebrow ||\n selectedSlide.title ||\n selectedSlide.subtitle ||\n selectedSlide.description ||\n selectedSlide.primaryCta ||\n selectedSlide.secondaryCta ||\n (selectedSlide.metrics && selectedSlide.metrics.length > 0)),\n );\n const hasTabLabels = slides.some((slide) => slide.tabLabel || slide.tabMeta);\n const showPlayPause = autoplay && !reducedMotion;\n\n const { getTabProps, onKeyDown: onTablistKeyDown } = useRovingTablist<HTMLButtonElement>({\n count: slides.length,\n selectedIndex,\n onSelect: scrollTo,\n orientation: \"horizontal\",\n });\n\n return (\n <div\n ref={rootRef}\n className=\"rt-tpl-hero-carousel\"\n role=\"region\"\n aria-roledescription=\"carousel\"\n aria-label=\"배경 이미지 캐러셀\"\n tabIndex={0}\n onKeyDown={onKeyDown}\n data-reduced-motion={reducedMotion ? \"true\" : \"false\"}\n data-scrim={scrim ?? \"soft\"}\n data-transition={transition}\n onMouseEnter={autoplayGate.rootHandlers.onMouseEnter}\n onMouseLeave={autoplayGate.rootHandlers.onMouseLeave}\n onFocus={autoplayGate.rootHandlers.onFocus}\n onBlur={autoplayGate.rootHandlers.onBlur}\n >\n {isCurtain ? (\n <CurtainTrack\n slides={slides}\n selectedIndex={curtain.selectedIndex}\n leavingIndex={curtain.leavingIndex}\n />\n ) : (\n <SlideTrack\n viewportRef={emblaRef}\n slides={slides}\n selectedIndex={emblaSelectedIndex}\n />\n )}\n\n <button\n type=\"button\"\n className=\"rt-tpl-hero-carousel-arrow rt-tpl-hero-carousel-arrow--prev\"\n aria-label=\"이전 슬라이드\"\n onClick={scrollPrev}\n >\n <svg viewBox=\"0 0 24 24\" aria-hidden=\"true\" focusable=\"false\">\n <path d=\"M15 5 8 12l7 7\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n </button>\n <button\n type=\"button\"\n className=\"rt-tpl-hero-carousel-arrow rt-tpl-hero-carousel-arrow--next\"\n aria-label=\"다음 슬라이드\"\n onClick={scrollNext}\n >\n <svg viewBox=\"0 0 24 24\" aria-hidden=\"true\" focusable=\"false\">\n <path d=\"m9 5 7 7-7 7\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n </button>\n\n {hasSlidePanel && selectedSlide ? (\n <div\n key={isCurtain ? `panel-${selectedIndex}` : \"panel\"}\n className=\"rt-tpl-hero-carousel-slide-panel\"\n >\n {selectedSlide.eyebrow ? (\n <p className=\"rt-tpl-hero-carousel-slide-eyebrow\">\n {selectedSlide.eyebrow}\n </p>\n ) : null}\n {selectedSlide.title ? (\n <p className=\"rt-tpl-hero-carousel-slide-title\">\n {selectedSlide.title}\n </p>\n ) : null}\n {selectedSlide.subtitle ? (\n <p className=\"rt-tpl-hero-carousel-slide-subtitle\">\n {selectedSlide.subtitle}\n </p>\n ) : null}\n {selectedSlide.description ? (\n <p className=\"rt-tpl-hero-carousel-slide-desc\">\n {selectedSlide.description}\n </p>\n ) : null}\n {selectedSlide.metrics && selectedSlide.metrics.length > 0 ? (\n <dl className=\"rt-tpl-hero-carousel-slide-metrics\">\n {selectedSlide.metrics.map((metric) => (\n <div key={`${metric.value}-${metric.label}`}>\n <dt>{metric.label}</dt>\n <dd>\n {metric.value}\n {metric.unit ? <span>{metric.unit}</span> : null}\n </dd>\n {metric.source ? <dd>{metric.source}</dd> : null}\n </div>\n ))}\n </dl>\n ) : null}\n {selectedSlide.primaryCta || selectedSlide.secondaryCta ? (\n <div className=\"rt-tpl-hero-carousel-slide-actions\">\n {selectedSlide.primaryCta ? (\n <a\n className=\"rt-tpl-btn rt-tpl-btn--primary\"\n href={selectedSlide.primaryCta.href}\n data-track=\"cta_hero\"\n >\n {selectedSlide.primaryCta.label}\n </a>\n ) : null}\n {selectedSlide.secondaryCta ? (\n <a\n className=\"rt-tpl-btn rt-tpl-btn--secondary\"\n href={selectedSlide.secondaryCta.href}\n data-track=\"cta_hero\"\n >\n {selectedSlide.secondaryCta.label}\n </a>\n ) : null}\n </div>\n ) : null}\n </div>\n ) : null}\n\n <div className=\"rt-tpl-hero-carousel-controls\">\n <div\n className={\n hasTabLabels\n ? \"rt-tpl-hero-carousel-dots rt-tpl-hero-carousel-dots--tabs\"\n : \"rt-tpl-hero-carousel-dots\"\n }\n role=\"tablist\"\n aria-label=\"슬라이드 선택\"\n onKeyDown={onTablistKeyDown}\n >\n {slides.map((_slide, index) => (\n <button\n key={index}\n {...getTabProps(index)}\n type=\"button\"\n role=\"tab\"\n className=\"rt-tpl-hero-carousel-dot\"\n aria-selected={index === selectedIndex}\n aria-label={`${index + 1}번 슬라이드로 이동`}\n data-active={index === selectedIndex ? \"true\" : \"false\"}\n onClick={() => scrollTo(index)}\n >\n {hasTabLabels ? (\n <>\n <span>{_slide.tabLabel ?? `${index + 1}`}</span>\n {_slide.tabMeta ? <small>{_slide.tabMeta}</small> : null}\n </>\n ) : null}\n </button>\n ))}\n </div>\n <div className=\"rt-tpl-hero-carousel-controls-right\">\n {showPlayPause ? (\n <button\n type=\"button\"\n className=\"rt-tpl-hero-carousel-playpause\"\n aria-pressed={autoplayGate.userPaused}\n aria-label={\n autoplayGate.userPaused ? \"자동 전환 재생\" : \"자동 전환 일시정지\"\n }\n onClick={autoplayGate.togglePaused}\n >\n {autoplayGate.userPaused ? <PlayIcon /> : <PauseIcon />}\n </button>\n ) : null}\n <p className=\"rt-tpl-hero-carousel-counter\" aria-hidden=\"true\">\n <span className=\"rt-tpl-hero-carousel-counter-current\">\n {selectedIndex + 1}\n </span>\n /\n <span className=\"rt-tpl-hero-carousel-counter-total\">{slides.length}</span>\n </p>\n </div>\n </div>\n </div>\n );\n}\n","\"use client\";\n\n/**\n * `curtain` transition — chambarun 이식(ADR-0086 P2 D3).\n * 모션 스펙 원본: chambarun mockup `assets/site.css` `@keyframes curtainReveal`\n * (clip-path 와이프) + `.curtain-sheen`(신 스윕) + `.hero-copy>*` 스태거, 엔진은\n * `assets/site.js` 36-85행 `show(n)`(is-active/is-leaving 상태 머신, 980ms 뒤\n * is-leaving 정리). 타이밍·이징·클립 경로는 원본 값을 그대로 유지하고 색은\n * 하드코딩 헥스 대신 `--rt-*` 토큰으로 치환했다(CSS, `templates.css` 참고).\n *\n * embla 를 쓰지 않는다 — embla 는 가로 스크롤 트랙 물리 이동이 전제라\n * clip-path 와이프(절대배치 스택)와 구조가 다르다. `hero-carousel-client.tsx`\n * 400줄 상한 분할.\n */\nimport { useCallback, useRef, useState } from \"react\";\nimport type { ReactElement } from \"react\";\n\nimport type { HeroCarouselSlideInput } from \"./hero-carousel-types.js\";\n\n/** chambarun site.js — is-leaving 클래스 정리 타이밍(커튼 리빌 .92s + 여유). */\nconst LEAVING_CLEAR_MS = 980;\n\nexport interface CurtainCarouselController {\n readonly selectedIndex: number;\n readonly leavingIndex: number | null;\n readonly goTo: (index: number) => void;\n readonly next: () => void;\n readonly prev: () => void;\n}\n\n/**\n * `curtain` 전용 인덱스 상태 머신 — chambarun `show(n)` 이식. `reducedMotion`\n * 이면 leaving 단계를 만들지 않는다(D4 — 커튼 대신 즉시 전환).\n */\nexport function useCurtainCarousel(\n slideCount: number,\n reducedMotion: boolean,\n): CurtainCarouselController {\n const [selectedIndex, setSelectedIndex] = useState(0);\n const [leavingIndex, setLeavingIndex] = useState<number | null>(null);\n const selectedRef = useRef(0);\n const runRef = useRef(0);\n const clearTimerRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n\n const goTo = useCallback(\n (nextRaw: number) => {\n if (slideCount < 1) return;\n const nextIndex = ((nextRaw % slideCount) + slideCount) % slideCount;\n const prevIndex = selectedRef.current;\n if (nextIndex === prevIndex) return;\n selectedRef.current = nextIndex;\n setSelectedIndex(nextIndex);\n clearTimeout(clearTimerRef.current);\n if (reducedMotion) {\n setLeavingIndex(null);\n return;\n }\n setLeavingIndex(prevIndex);\n runRef.current += 1;\n const runId = runRef.current;\n clearTimerRef.current = setTimeout(() => {\n if (runId === runRef.current) setLeavingIndex(null);\n }, LEAVING_CLEAR_MS);\n },\n [slideCount, reducedMotion],\n );\n\n const next = useCallback(() => goTo(selectedRef.current + 1), [goTo]);\n const prev = useCallback(() => goTo(selectedRef.current - 1), [goTo]);\n\n return { selectedIndex, leavingIndex, goTo, next, prev };\n}\n\nexport interface CurtainTrackProps {\n readonly slides: readonly HeroCarouselSlideInput[];\n readonly selectedIndex: number;\n readonly leavingIndex: number | null;\n}\n\n/**\n * 절대배치 스택 트랙(chambarun `.slide{position:absolute;inset:0}`) — 각\n * 슬라이드가 `data-active`/`data-leaving` 로 clip-path 와이프·오버랩을\n * 제어한다(CSS `templates.css` `[data-transition=\"curtain\"]` 스코프). 활성\n * 슬라이드에만 신 스윕 엘리먼트를 렌더(비활성 슬라이드는 DOM 비용 절감).\n */\nexport function CurtainTrack({\n slides,\n selectedIndex,\n leavingIndex,\n}: CurtainTrackProps): ReactElement {\n return (\n <div className=\"rt-tpl-hero-carousel-viewport\">\n <div className=\"rt-tpl-hero-carousel-track\">\n {slides.map((slide, index) => {\n const active = index === selectedIndex;\n const leaving = index === leavingIndex;\n return (\n <div\n key={`${slide.image}-${index}`}\n className=\"rt-tpl-hero-carousel-slide\"\n aria-roledescription=\"slide\"\n aria-label={`Slide ${index + 1} of ${slides.length}`}\n aria-hidden={active ? undefined : true}\n data-active={active ? \"true\" : \"false\"}\n data-leaving={leaving ? \"true\" : \"false\"}\n >\n <img\n className=\"rt-tpl-hero-carousel-img\"\n src={slide.image}\n alt={slide.alt}\n data-active={active ? \"true\" : \"false\"}\n loading={index === 0 ? \"eager\" : \"lazy\"}\n />\n {active ? (\n <span className=\"rt-tpl-hero-carousel-sheen\" aria-hidden=\"true\" />\n ) : null}\n </div>\n );\n })}\n </div>\n </div>\n );\n}\n","\"use client\";\n\n/**\n * `hero` `carousel` 공유 훅 — reduced-motion·document visibility·autoplay\n * 정지 사유 합성(D4 접근성 계약, ADR-0086 P2). `hero-carousel-client.tsx` 400줄\n * 상한 분할.\n */\nimport { useCallback, useState } from \"react\";\nimport type { FocusEvent, RefObject } from \"react\";\nimport { useEffect } from \"react\";\n\nexport function usePrefersReducedMotion(): boolean {\n const [reduced, setReduced] = useState(false);\n useEffect(() => {\n const query = window.matchMedia(\"(prefers-reduced-motion: reduce)\");\n setReduced(query.matches);\n const onChange = (event: MediaQueryListEvent) => setReduced(event.matches);\n query.addEventListener(\"change\", onChange);\n return () => query.removeEventListener(\"change\", onChange);\n }, []);\n return reduced;\n}\n\nexport function useDocumentVisible(): boolean {\n const [visible, setVisible] = useState(true);\n useEffect(() => {\n const onVisibility = () => setVisible(document.visibilityState === \"visible\");\n onVisibility();\n document.addEventListener(\"visibilitychange\", onVisibility);\n return () => document.removeEventListener(\"visibilitychange\", onVisibility);\n }, []);\n return visible;\n}\n\nexport interface AutoplayGate {\n /** 실제 autoplay 인터벌을 걸어도 되는지 — 모든 정지 사유의 합성 결과. */\n readonly shouldAutoplay: boolean;\n /** pause/재생 버튼이 반영하는 사용자 의도(수동 일시정지). */\n readonly userPaused: boolean;\n readonly togglePaused: () => void;\n /** 루트 엘리먼트에 그대로 스프레드 — hover/focus-within 일시정지. */\n readonly rootHandlers: {\n readonly onMouseEnter: () => void;\n readonly onMouseLeave: () => void;\n readonly onFocus: () => void;\n readonly onBlur: (event: FocusEvent<HTMLElement>) => void;\n };\n}\n\n/**\n * autoplay pause 사유 4종(D4): hover, focus-within, 사용자 버튼, 문서 비가시.\n * `reducedMotion` 은 autoplay 자체를 아예 시작하지 않는 별도 계약이라 이 훅\n * 밖(`autoplay && !reducedMotion`)에서 이미 걸러지지만, 재확인 차원에서 인자로\n * 받아 합성식에도 포함한다.\n */\nexport function useAutoplayGate(params: {\n readonly autoplay: boolean;\n readonly reducedMotion: boolean;\n readonly rootRef: RefObject<HTMLElement | null>;\n}): AutoplayGate {\n const { autoplay, reducedMotion, rootRef } = params;\n const tabVisible = useDocumentVisible();\n const [hovered, setHovered] = useState(false);\n const [focusedWithin, setFocusedWithin] = useState(false);\n const [userPaused, setUserPaused] = useState(false);\n\n const onBlur = useCallback(\n (event: FocusEvent<HTMLElement>) => {\n const next = event.relatedTarget as Node | null;\n if (!next || !rootRef.current?.contains(next)) {\n setFocusedWithin(false);\n }\n },\n [rootRef],\n );\n\n const shouldAutoplay =\n autoplay &&\n !reducedMotion &&\n tabVisible &&\n !userPaused &&\n !hovered &&\n !focusedWithin;\n\n return {\n shouldAutoplay,\n userPaused,\n togglePaused: () => setUserPaused((value) => !value),\n rootHandlers: {\n onMouseEnter: () => setHovered(true),\n onMouseLeave: () => setHovered(false),\n onFocus: () => setFocusedWithin(true),\n onBlur,\n },\n };\n}\n","\"use client\";\n\n/**\n * `slide` transition(기본값) 트랙 — 기존 embla 가로 스크롤 배경 전환, 회귀 없음.\n * `hero-carousel-client.tsx` 400줄 상한 분할(`curtain` transition 은\n * `hero-carousel-curtain.tsx`).\n */\nimport type { ReactElement } from \"react\";\nimport type { EmblaViewportRefType } from \"embla-carousel-react\";\n\nimport type { HeroCarouselSlideInput } from \"./hero-carousel-types.js\";\n\nexport interface SlideTrackProps {\n readonly viewportRef: EmblaViewportRefType;\n readonly slides: readonly HeroCarouselSlideInput[];\n readonly selectedIndex: number;\n}\n\nexport function SlideTrack({\n viewportRef,\n slides,\n selectedIndex,\n}: SlideTrackProps): ReactElement {\n return (\n <div className=\"rt-tpl-hero-carousel-viewport\" ref={viewportRef}>\n <div className=\"rt-tpl-hero-carousel-track\">\n {slides.map((slide, index) => (\n <div\n key={`${slide.image}-${index}`}\n className=\"rt-tpl-hero-carousel-slide\"\n aria-roledescription=\"slide\"\n aria-label={`Slide ${index + 1} of ${slides.length}`}\n aria-hidden={index === selectedIndex ? undefined : true}\n >\n <img\n className=\"rt-tpl-hero-carousel-img\"\n src={slide.image}\n alt={slide.alt}\n data-active={index === selectedIndex ? \"true\" : \"false\"}\n loading={index === 0 ? \"eager\" : \"lazy\"}\n />\n </div>\n ))}\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":["useCallback","useEffect","useRef","useState","useCallback","useState","jsx","jsx","jsxs","useRef","useState","useCallback","useEffect"]}
@@ -1,3 +1,10 @@
1
+ // src/templates/site-config-version.ts
2
+ var SITE_CONFIG_SCHEMA_VERSION = 1;
3
+ var LEGACY_SITE_CONFIG_SCHEMA_VERSION = 1;
4
+ function resolveSiteConfigSchemaVersion(config) {
5
+ return config.schemaVersion ?? LEGACY_SITE_CONFIG_SCHEMA_VERSION;
6
+ }
7
+
1
8
  // src/templates/site-config.ts
2
9
  import { z } from "zod";
3
10
  var JsonValue = z.lazy(
@@ -105,6 +112,13 @@ var HeroSectionSchema = SectionBase.extend({
105
112
  autoplay: z.boolean().optional(),
106
113
  /** `carousel` 전용, ms. 기본 6000. */
107
114
  intervalMs: z.number().min(3e3).max(1e4).optional(),
115
+ /**
116
+ * `carousel` 전용 배경 전환 모션(ADR-0086 P2 D3). 기본 "slide"(기존 embla
117
+ * 가로 스크롤). "curtain" = clip-path 와이프 + 신 스윕(chambarun 이식).
118
+ * variant 는 그대로 "carousel" — 신규 variant 아님("variant=구조,
119
+ * motion=인상" 규칙).
120
+ */
121
+ transition: z.enum(["slide", "curtain"]).optional(),
108
122
  proofMetrics: HeroProofMetricsSchema.optional(),
109
123
  scrim: z.enum(["none", "soft", "strong"]).optional()
110
124
  })
@@ -487,7 +501,15 @@ var ContactMapSchema = z.object({
487
501
  embedUrl: z.string().optional(),
488
502
  externalUrl: z.string().optional(),
489
503
  lat: z.number().optional(),
490
- lng: z.number().optional()
504
+ lng: z.number().optional(),
505
+ /**
506
+ * 지도 로드 모드(ADR-0086 P2 D3, chambarun `.map-facade` 클릭-투-로드 이식).
507
+ * 기본 "lazy"(기존 동작 그대로 — 실지도면 즉시 iframe `loading="lazy"`).
508
+ * "click" 은 정적 프리뷰(주소·지도 열기 버튼)만 먼저 그리고, 클릭 시에만
509
+ * iframe 을 마운트한다(외부 지도 스크립트/네트워크 요청을 사용자 의도
510
+ * 전까지 미루는 성능·프라이버시 절충 — additive, 기존 사이트는 영향 없음).
511
+ */
512
+ mapLoad: z.enum(["lazy", "click"]).optional()
491
513
  });
492
514
  var ContactActionSchema = z.object({
493
515
  label: z.string(),
@@ -859,6 +881,13 @@ var CollectionSchema = z.object({
859
881
  icon: z.string().optional()
860
882
  });
861
883
  var SiteConfigSchema = z.object({
884
+ /**
885
+ * `SiteConfig` 계약 버전 (ADR-0086 D2) — 팩 manifest 의 `siteConfigVersion`
886
+ * 과 비교해 팩↔renderer 호환성을 표현한다. optional — 하위 호환(기존 고객
887
+ * `site.config.ts` 에는 이 필드가 없다). 미지정은 버전 1 로 해석한다
888
+ * (`resolveSiteConfigSchemaVersion`).
889
+ */
890
+ schemaVersion: z.number().int().positive().optional(),
862
891
  site: z.object({
863
892
  /** canonical origin. 생략 시 소비측이 `SITE_URL` env 등으로 해석. */
864
893
  url: z.string().url().optional(),
@@ -875,6 +904,17 @@ var SiteConfigSchema = z.object({
875
904
  collections: z.array(CollectionSchema).default([]),
876
905
  /** 사이트 전역 토큰 override (`--rt-*` CSS 변수 → 값). 비우면 admin /design SSoT. */
877
906
  theme: z.record(z.string()).default({}),
907
+ /**
908
+ * 테마 방향(Theme Directions, `@roottale/tokens` 프리셋 id — 예: "d1-editorial").
909
+ * root layout 이 `<html data-rt-direction="…">` 로 부여해
910
+ * `@roottale/tokens/theme-directions.css` 의 `[data-rt-direction="…"]` 규칙을
911
+ * 활성화한다(roottale-docs `docs/design/theme-directions.md`). 값은 문자열로만
912
+ * 검증한다 — `@roottale/cms-renderer-next` 는 `@roottale/tokens` 를 직접
913
+ * 의존하지 않으므로 유효 id 집합은 `@roottale/tokens/theme-directions` 의
914
+ * `ThemeDirectionId` 를 참조해 `site.config.ts` 작성 시점에 타입으로 좁힌다.
915
+ * 미지정 시 속성 자체가 안 붙어 완전히 무변화(기존 동작).
916
+ */
917
+ direction: z.string().optional(),
878
918
  nav: z.array(NavItemSchema).default([]),
879
919
  /** path("/", "/services", …) → 섹션 배열. */
880
920
  pages: z.record(z.array(SectionSchema))
@@ -887,6 +927,8 @@ function defineSiteConfig(input) {
887
927
  }
888
928
 
889
929
  export {
930
+ SITE_CONFIG_SCHEMA_VERSION,
931
+ resolveSiteConfigSchemaVersion,
890
932
  NavItemSchema,
891
933
  SECTION_VARIANTS,
892
934
  HeroSectionSchema,
@@ -916,4 +958,4 @@ export {
916
958
  parseSiteConfig,
917
959
  defineSiteConfig
918
960
  };
919
- //# sourceMappingURL=chunk-6PDZSFVZ.js.map
961
+ //# sourceMappingURL=chunk-UM37DOIK.js.map