@roottale/cms-renderer-next 0.52.0 → 0.53.1

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 (80) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/README.md +33 -4
  3. package/dist/chunk-GVKENK6H.js +75 -0
  4. package/dist/chunk-GVKENK6H.js.map +1 -0
  5. package/dist/{chunk-U2IM6OCQ.js → chunk-HM5VP7SR.js} +1 -1
  6. package/dist/{chunk-U2IM6OCQ.js.map → chunk-HM5VP7SR.js.map} +1 -1
  7. package/dist/chunk-KSPPYX22.js +14 -0
  8. package/dist/chunk-KSPPYX22.js.map +1 -0
  9. package/dist/cms-public.css +65 -0
  10. package/dist/exposure-client.d.ts +9 -0
  11. package/dist/exposure-client.js +8 -0
  12. package/dist/routes.d.ts +10 -1
  13. package/dist/routes.js +36 -16
  14. package/dist/routes.js.map +1 -1
  15. package/dist/section-runtime.d.ts +46 -0
  16. package/dist/section-runtime.js +9 -0
  17. package/dist/server.d.ts +49 -3
  18. package/dist/server.js +1764 -34
  19. package/dist/server.js.map +1 -1
  20. package/package.json +8 -24
  21. package/dist/before-after-client.d.ts +0 -15
  22. package/dist/before-after-client.js +0 -8
  23. package/dist/chunk-27RT6B3O.js +0 -93
  24. package/dist/chunk-27RT6B3O.js.map +0 -1
  25. package/dist/chunk-2QU4JU46.js +0 -3927
  26. package/dist/chunk-2QU4JU46.js.map +0 -1
  27. package/dist/chunk-4RQ3SEDL.js +0 -106
  28. package/dist/chunk-4RQ3SEDL.js.map +0 -1
  29. package/dist/chunk-6GQCFFHU.js +0 -540
  30. package/dist/chunk-6GQCFFHU.js.map +0 -1
  31. package/dist/chunk-H46JUIHZ.js +0 -79
  32. package/dist/chunk-H46JUIHZ.js.map +0 -1
  33. package/dist/chunk-K62RR4UQ.js +0 -53
  34. package/dist/chunk-K62RR4UQ.js.map +0 -1
  35. package/dist/chunk-LEHHSI6G.js +0 -64
  36. package/dist/chunk-LEHHSI6G.js.map +0 -1
  37. package/dist/chunk-QYNEOFL7.js +0 -418
  38. package/dist/chunk-QYNEOFL7.js.map +0 -1
  39. package/dist/chunk-U5AKACPN.js +0 -113
  40. package/dist/chunk-U5AKACPN.js.map +0 -1
  41. package/dist/chunk-UM37DOIK.js +0 -961
  42. package/dist/chunk-UM37DOIK.js.map +0 -1
  43. package/dist/chunk-YLSQAT6B.js +0 -59
  44. package/dist/chunk-YLSQAT6B.js.map +0 -1
  45. package/dist/chunk-ZL2NA3QT.js +0 -313
  46. package/dist/chunk-ZL2NA3QT.js.map +0 -1
  47. package/dist/concerns-client.d.ts +0 -12
  48. package/dist/concerns-client.js +0 -8
  49. package/dist/contact-map-client.d.ts +0 -27
  50. package/dist/contact-map-client.js +0 -8
  51. package/dist/contact-map-client.js.map +0 -1
  52. package/dist/expertise-client.d.ts +0 -16
  53. package/dist/expertise-client.js +0 -9
  54. package/dist/expertise-client.js.map +0 -1
  55. package/dist/hero-carousel-client.d.ts +0 -52
  56. package/dist/hero-carousel-client.js +0 -9
  57. package/dist/hero-carousel-client.js.map +0 -1
  58. package/dist/seo-checks.d.ts +0 -87
  59. package/dist/seo-checks.js +0 -27
  60. package/dist/seo-checks.js.map +0 -1
  61. package/dist/site-config-DNpvlqA5.d.ts +0 -3961
  62. package/dist/spaces-client.d.ts +0 -10
  63. package/dist/spaces-client.js +0 -8
  64. package/dist/spaces-client.js.map +0 -1
  65. package/dist/team-expanding-stage-client.d.ts +0 -35
  66. package/dist/team-expanding-stage-client.js +0 -8
  67. package/dist/team-expanding-stage-client.js.map +0 -1
  68. package/dist/templates.css +0 -4145
  69. package/dist/templates.d.ts +0 -171
  70. package/dist/templates.js +0 -291
  71. package/dist/templates.js.map +0 -1
  72. package/dist/theme-css-vars-CD3jcEkp.d.ts +0 -7
  73. package/dist/ui-react-motion-client.d.ts +0 -76
  74. package/dist/ui-react-motion-client.js +0 -16
  75. package/dist/ui-react-motion-client.js.map +0 -1
  76. package/dist/validate.d.ts +0 -104
  77. package/dist/validate.js +0 -1204
  78. package/dist/validate.js.map +0 -1
  79. /package/dist/{before-after-client.js.map → exposure-client.js.map} +0 -0
  80. /package/dist/{concerns-client.js.map → section-runtime.js.map} +0 -0
@@ -1,418 +0,0 @@
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
- // eslint-disable-next-line react-hooks/exhaustive-deps -- 위와 같은 이유(안정화된 메서드만 의존).
240
- [isCurtain, curtain.goTo, emblaApi, reducedMotion]
241
- );
242
- const onKeyDown = useCallback3(
243
- (event) => {
244
- if (event.key === "ArrowLeft") {
245
- event.preventDefault();
246
- scrollPrev();
247
- } else if (event.key === "ArrowRight") {
248
- event.preventDefault();
249
- scrollNext();
250
- }
251
- },
252
- [scrollPrev, scrollNext]
253
- );
254
- const selectedSlide = slides[selectedIndex];
255
- const hasSlidePanel = Boolean(
256
- selectedSlide && (selectedSlide.eyebrow || selectedSlide.title || selectedSlide.subtitle || selectedSlide.description || selectedSlide.primaryCta || selectedSlide.secondaryCta || selectedSlide.metrics && selectedSlide.metrics.length > 0)
257
- );
258
- const hasTabLabels = slides.some((slide) => slide.tabLabel || slide.tabMeta);
259
- const showPlayPause = autoplay && !reducedMotion;
260
- const { getTabProps, onKeyDown: onTablistKeyDown } = useRovingTablist({
261
- count: slides.length,
262
- selectedIndex,
263
- onSelect: scrollTo,
264
- orientation: "horizontal"
265
- });
266
- return (
267
- // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions -- WAI-ARIA 캐러셀 패턴. region 이 좌우 키·포인터를 받고 실제 이동은 안쪽 컨트롤이 담당한다.
268
- /* @__PURE__ */ jsxs2(
269
- "div",
270
- {
271
- ref: rootRef,
272
- className: "rt-tpl-hero-carousel",
273
- role: "region",
274
- "aria-roledescription": "carousel",
275
- "aria-label": "\uBC30\uACBD \uC774\uBBF8\uC9C0 \uCE90\uB7EC\uC140",
276
- tabIndex: 0,
277
- onKeyDown,
278
- "data-reduced-motion": reducedMotion ? "true" : "false",
279
- "data-scrim": scrim ?? "soft",
280
- "data-transition": transition,
281
- onMouseEnter: autoplayGate.rootHandlers.onMouseEnter,
282
- onMouseLeave: autoplayGate.rootHandlers.onMouseLeave,
283
- onFocus: autoplayGate.rootHandlers.onFocus,
284
- onBlur: autoplayGate.rootHandlers.onBlur,
285
- children: [
286
- isCurtain ? /* @__PURE__ */ jsx3(
287
- CurtainTrack,
288
- {
289
- slides,
290
- selectedIndex: curtain.selectedIndex,
291
- leavingIndex: curtain.leavingIndex
292
- }
293
- ) : /* @__PURE__ */ jsx3(
294
- SlideTrack,
295
- {
296
- viewportRef: emblaRef,
297
- slides,
298
- selectedIndex: emblaSelectedIndex
299
- }
300
- ),
301
- /* @__PURE__ */ jsx3(
302
- "button",
303
- {
304
- type: "button",
305
- className: "rt-tpl-hero-carousel-arrow rt-tpl-hero-carousel-arrow--prev",
306
- "aria-label": "\uC774\uC804 \uC2AC\uB77C\uC774\uB4DC",
307
- onClick: scrollPrev,
308
- 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" }) })
309
- }
310
- ),
311
- /* @__PURE__ */ jsx3(
312
- "button",
313
- {
314
- type: "button",
315
- className: "rt-tpl-hero-carousel-arrow rt-tpl-hero-carousel-arrow--next",
316
- "aria-label": "\uB2E4\uC74C \uC2AC\uB77C\uC774\uB4DC",
317
- onClick: scrollNext,
318
- 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" }) })
319
- }
320
- ),
321
- hasSlidePanel && selectedSlide ? /* @__PURE__ */ jsxs2(
322
- "div",
323
- {
324
- className: "rt-tpl-hero-carousel-slide-panel",
325
- children: [
326
- selectedSlide.eyebrow ? /* @__PURE__ */ jsx3("p", { className: "rt-tpl-hero-carousel-slide-eyebrow", children: selectedSlide.eyebrow }) : null,
327
- selectedSlide.title ? /* @__PURE__ */ jsx3("p", { className: "rt-tpl-hero-carousel-slide-title", children: selectedSlide.title }) : null,
328
- selectedSlide.subtitle ? /* @__PURE__ */ jsx3("p", { className: "rt-tpl-hero-carousel-slide-subtitle", children: selectedSlide.subtitle }) : null,
329
- selectedSlide.description ? /* @__PURE__ */ jsx3("p", { className: "rt-tpl-hero-carousel-slide-desc", children: selectedSlide.description }) : null,
330
- 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: [
331
- /* @__PURE__ */ jsx3("dt", { children: metric.label }),
332
- /* @__PURE__ */ jsxs2("dd", { children: [
333
- metric.value,
334
- metric.unit ? /* @__PURE__ */ jsx3("span", { children: metric.unit }) : null
335
- ] }),
336
- metric.source ? /* @__PURE__ */ jsx3("dd", { children: metric.source }) : null
337
- ] }, `${metric.value}-${metric.label}`)) }) : null,
338
- selectedSlide.primaryCta || selectedSlide.secondaryCta ? /* @__PURE__ */ jsxs2("div", { className: "rt-tpl-hero-carousel-slide-actions", children: [
339
- selectedSlide.primaryCta ? /* @__PURE__ */ jsx3(
340
- "a",
341
- {
342
- className: "rt-tpl-btn rt-tpl-btn--primary",
343
- href: selectedSlide.primaryCta.href,
344
- "data-track": "cta_hero",
345
- children: selectedSlide.primaryCta.label
346
- }
347
- ) : null,
348
- selectedSlide.secondaryCta ? /* @__PURE__ */ jsx3(
349
- "a",
350
- {
351
- className: "rt-tpl-btn rt-tpl-btn--secondary",
352
- href: selectedSlide.secondaryCta.href,
353
- "data-track": "cta_hero",
354
- children: selectedSlide.secondaryCta.label
355
- }
356
- ) : null
357
- ] }) : null
358
- ]
359
- },
360
- isCurtain ? `panel-${selectedIndex}` : "panel"
361
- ) : null,
362
- /* @__PURE__ */ jsxs2("div", { className: "rt-tpl-hero-carousel-controls", children: [
363
- /* @__PURE__ */ jsx3(
364
- "div",
365
- {
366
- className: hasTabLabels ? "rt-tpl-hero-carousel-dots rt-tpl-hero-carousel-dots--tabs" : "rt-tpl-hero-carousel-dots",
367
- role: "tablist",
368
- "aria-label": "\uC2AC\uB77C\uC774\uB4DC \uC120\uD0DD",
369
- onKeyDown: onTablistKeyDown,
370
- children: slides.map((_slide, index) => /* @__PURE__ */ jsx3(
371
- "button",
372
- {
373
- ...getTabProps(index),
374
- type: "button",
375
- role: "tab",
376
- className: "rt-tpl-hero-carousel-dot",
377
- "aria-selected": index === selectedIndex,
378
- "aria-label": `${index + 1}\uBC88 \uC2AC\uB77C\uC774\uB4DC\uB85C \uC774\uB3D9`,
379
- "data-active": index === selectedIndex ? "true" : "false",
380
- onClick: () => scrollTo(index),
381
- children: hasTabLabels ? /* @__PURE__ */ jsxs2(Fragment, { children: [
382
- /* @__PURE__ */ jsx3("span", { children: _slide.tabLabel ?? `${index + 1}` }),
383
- _slide.tabMeta ? /* @__PURE__ */ jsx3("small", { children: _slide.tabMeta }) : null
384
- ] }) : null
385
- },
386
- index
387
- ))
388
- }
389
- ),
390
- /* @__PURE__ */ jsxs2("div", { className: "rt-tpl-hero-carousel-controls-right", children: [
391
- showPlayPause ? /* @__PURE__ */ jsx3(
392
- "button",
393
- {
394
- type: "button",
395
- className: "rt-tpl-hero-carousel-playpause",
396
- "aria-pressed": autoplayGate.userPaused,
397
- "aria-label": autoplayGate.userPaused ? "\uC790\uB3D9 \uC804\uD658 \uC7AC\uC0DD" : "\uC790\uB3D9 \uC804\uD658 \uC77C\uC2DC\uC815\uC9C0",
398
- onClick: autoplayGate.togglePaused,
399
- children: autoplayGate.userPaused ? /* @__PURE__ */ jsx3(PlayIcon, {}) : /* @__PURE__ */ jsx3(PauseIcon, {})
400
- }
401
- ) : null,
402
- /* @__PURE__ */ jsxs2("p", { className: "rt-tpl-hero-carousel-counter", "aria-hidden": "true", children: [
403
- /* @__PURE__ */ jsx3("span", { className: "rt-tpl-hero-carousel-counter-current", children: selectedIndex + 1 }),
404
- "/",
405
- /* @__PURE__ */ jsx3("span", { className: "rt-tpl-hero-carousel-counter-total", children: slides.length })
406
- ] })
407
- ] })
408
- ] })
409
- ]
410
- }
411
- )
412
- );
413
- }
414
-
415
- export {
416
- HeroCarouselViewport
417
- };
418
- //# sourceMappingURL=chunk-QYNEOFL7.js.map
@@ -1 +0,0 @@
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 // eslint-disable-next-line react-hooks/set-state-in-effect -- 캐러셀 인스턴스가 준비된 뒤 현재 위치를 한 번 읽어 맞춘다(이후는 select 이벤트가 갱신). 외부 라이브러리 상태 구독이다.\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 // curtain 객체 전체가 아니라 쓰는 메서드만 의존성에 둔다 — 메서드는\n // useCallback 으로 안정화돼 있고(hero-carousel-curtain.tsx), 객체는 매 렌더\n // 새로 만들어져 전체를 넣으면 타이머가 매번 다시 걸린다.\n // eslint-disable-next-line react-hooks/exhaustive-deps\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 // eslint-disable-next-line react-hooks/exhaustive-deps -- 위와 같은 이유(안정화된 메서드만 의존).\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 // eslint-disable-next-line react-hooks/exhaustive-deps -- 위와 같은 이유(안정화된 메서드만 의존).\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 // eslint-disable-next-line react-hooks/exhaustive-deps -- 위와 같은 이유(안정화된 메서드만 의존).\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 // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions -- WAI-ARIA 캐러셀 패턴. region 이 좌우 키·포인터를 받고 실제 이동은 안쪽 컨트롤이 담당한다.\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 {/* eslint-disable-next-line jsx-a11y/interactive-supports-focus -- WAI-ARIA 탭 패턴(roving tabindex). 초점은 개별 인디케이터가 갖고 컨테이너는 키 이벤트만 위임받는다. */}\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,113 +0,0 @@
1
- "use client";
2
-
3
- // src/templates/before-after-client.tsx
4
- import { useCallback, useRef, useState } from "react";
5
- import { jsx, jsxs } from "react/jsx-runtime";
6
- var MIN_VALUE = 0;
7
- var MAX_VALUE = 100;
8
- var INITIAL_VALUE = 50;
9
- var KEY_STEP = 5;
10
- function clampValue(value) {
11
- return Math.min(MAX_VALUE, Math.max(MIN_VALUE, value));
12
- }
13
- function BeforeAfterSlider({
14
- beforeImage,
15
- afterImage,
16
- beforeLabel,
17
- afterLabel
18
- }) {
19
- const [value, setValue] = useState(INITIAL_VALUE);
20
- const [dragging, setDragging] = useState(false);
21
- const frameRef = useRef(null);
22
- const updateFromClientX = useCallback((clientX) => {
23
- const frame = frameRef.current;
24
- if (!frame) return;
25
- const rect = frame.getBoundingClientRect();
26
- if (rect.width === 0) return;
27
- setValue(clampValue((clientX - rect.left) / rect.width * MAX_VALUE));
28
- }, []);
29
- const onKeyDown = useCallback((event) => {
30
- if (event.key === "ArrowLeft" || event.key === "ArrowDown") {
31
- event.preventDefault();
32
- setValue((current) => clampValue(current - KEY_STEP));
33
- return;
34
- }
35
- if (event.key === "ArrowRight" || event.key === "ArrowUp") {
36
- event.preventDefault();
37
- setValue((current) => clampValue(current + KEY_STEP));
38
- return;
39
- }
40
- if (event.key === "Home") {
41
- event.preventDefault();
42
- setValue(MIN_VALUE);
43
- return;
44
- }
45
- if (event.key === "End") {
46
- event.preventDefault();
47
- setValue(MAX_VALUE);
48
- }
49
- }, []);
50
- return /* @__PURE__ */ jsx(
51
- "div",
52
- {
53
- className: "rt-tpl-before-after-slider",
54
- onPointerCancel: () => setDragging(false),
55
- onPointerDown: (event) => {
56
- event.currentTarget.setPointerCapture(event.pointerId);
57
- setDragging(true);
58
- updateFromClientX(event.clientX);
59
- },
60
- onPointerMove: (event) => {
61
- if (dragging) updateFromClientX(event.clientX);
62
- },
63
- onPointerUp: () => setDragging(false),
64
- children: /* @__PURE__ */ jsxs("div", { className: "rt-tpl-before-after-frame", ref: frameRef, children: [
65
- /* @__PURE__ */ jsx(
66
- "img",
67
- {
68
- className: "rt-tpl-before-after-img",
69
- src: afterImage.src,
70
- alt: afterImage.alt,
71
- draggable: false,
72
- loading: "lazy"
73
- }
74
- ),
75
- /* @__PURE__ */ jsx(
76
- "img",
77
- {
78
- className: "rt-tpl-before-after-img rt-tpl-before-after-img--before",
79
- src: beforeImage.src,
80
- alt: beforeImage.alt,
81
- draggable: false,
82
- loading: "lazy",
83
- style: { clipPath: `inset(0 ${MAX_VALUE - value}% 0 0)` }
84
- }
85
- ),
86
- /* @__PURE__ */ jsx(
87
- "div",
88
- {
89
- className: "rt-tpl-before-after-handle",
90
- role: "slider",
91
- tabIndex: 0,
92
- "aria-label": "\uC2DC\uACF5 \uC804\uD6C4 \uBE44\uAD50 \uC2AC\uB77C\uC774\uB354",
93
- "aria-valuemin": MIN_VALUE,
94
- "aria-valuemax": MAX_VALUE,
95
- "aria-valuenow": Math.round(value),
96
- onKeyDown,
97
- style: { left: `${value}%` },
98
- children: /* @__PURE__ */ jsx("span", { className: "rt-tpl-before-after-thumb", "aria-hidden": "true" })
99
- }
100
- ),
101
- /* @__PURE__ */ jsxs("div", { className: "rt-tpl-before-after-labels", "aria-hidden": "true", children: [
102
- /* @__PURE__ */ jsx("span", { className: "rt-tpl-before-after-label", children: beforeLabel }),
103
- /* @__PURE__ */ jsx("span", { className: "rt-tpl-before-after-label", children: afterLabel })
104
- ] })
105
- ] })
106
- }
107
- );
108
- }
109
-
110
- export {
111
- BeforeAfterSlider
112
- };
113
- //# sourceMappingURL=chunk-U5AKACPN.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/templates/before-after-client.tsx"],"sourcesContent":["\"use client\";\n\nimport { useCallback, useRef, useState, type KeyboardEvent } from \"react\";\n\nexport interface BeforeAfterClientImage {\n readonly src: string;\n readonly alt: string;\n}\n\nexport interface BeforeAfterSliderProps {\n readonly beforeImage: BeforeAfterClientImage;\n readonly afterImage: BeforeAfterClientImage;\n readonly beforeLabel: string;\n readonly afterLabel: string;\n}\n\nconst MIN_VALUE = 0;\nconst MAX_VALUE = 100;\nconst INITIAL_VALUE = 50;\nconst KEY_STEP = 5;\n\nfunction clampValue(value: number): number {\n return Math.min(MAX_VALUE, Math.max(MIN_VALUE, value));\n}\n\nexport function BeforeAfterSlider({\n beforeImage,\n afterImage,\n beforeLabel,\n afterLabel,\n}: BeforeAfterSliderProps) {\n const [value, setValue] = useState(INITIAL_VALUE);\n const [dragging, setDragging] = useState(false);\n const frameRef = useRef<HTMLDivElement | null>(null);\n\n const updateFromClientX = useCallback((clientX: number): void => {\n const frame = frameRef.current;\n if (!frame) return;\n const rect = frame.getBoundingClientRect();\n if (rect.width === 0) return;\n setValue(clampValue(((clientX - rect.left) / rect.width) * MAX_VALUE));\n }, []);\n\n const onKeyDown = useCallback((event: KeyboardEvent<HTMLDivElement>) => {\n if (event.key === \"ArrowLeft\" || event.key === \"ArrowDown\") {\n event.preventDefault();\n setValue((current) => clampValue(current - KEY_STEP));\n return;\n }\n if (event.key === \"ArrowRight\" || event.key === \"ArrowUp\") {\n event.preventDefault();\n setValue((current) => clampValue(current + KEY_STEP));\n return;\n }\n if (event.key === \"Home\") {\n event.preventDefault();\n setValue(MIN_VALUE);\n return;\n }\n if (event.key === \"End\") {\n event.preventDefault();\n setValue(MAX_VALUE);\n }\n }, []);\n\n return (\n <div\n className=\"rt-tpl-before-after-slider\"\n onPointerCancel={() => setDragging(false)}\n onPointerDown={(event) => {\n event.currentTarget.setPointerCapture(event.pointerId);\n setDragging(true);\n updateFromClientX(event.clientX);\n }}\n onPointerMove={(event) => {\n if (dragging) updateFromClientX(event.clientX);\n }}\n onPointerUp={() => setDragging(false)}\n >\n <div className=\"rt-tpl-before-after-frame\" ref={frameRef}>\n <img\n className=\"rt-tpl-before-after-img\"\n src={afterImage.src}\n alt={afterImage.alt}\n draggable={false}\n loading=\"lazy\"\n />\n <img\n className=\"rt-tpl-before-after-img rt-tpl-before-after-img--before\"\n src={beforeImage.src}\n alt={beforeImage.alt}\n draggable={false}\n loading=\"lazy\"\n style={{ clipPath: `inset(0 ${MAX_VALUE - value}% 0 0)` }}\n />\n <div\n className=\"rt-tpl-before-after-handle\"\n role=\"slider\"\n tabIndex={0}\n aria-label=\"시공 전후 비교 슬라이더\"\n aria-valuemin={MIN_VALUE}\n aria-valuemax={MAX_VALUE}\n aria-valuenow={Math.round(value)}\n onKeyDown={onKeyDown}\n style={{ left: `${value}%` }}\n >\n <span className=\"rt-tpl-before-after-thumb\" aria-hidden=\"true\" />\n </div>\n <div className=\"rt-tpl-before-after-labels\" aria-hidden=\"true\">\n <span className=\"rt-tpl-before-after-label\">{beforeLabel}</span>\n <span className=\"rt-tpl-before-after-label\">{afterLabel}</span>\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":[]}