@roottale/cms-renderer-next 0.52.0 → 0.53.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -0
- package/README.md +23 -2
- package/dist/{chunk-U2IM6OCQ.js → chunk-HM5VP7SR.js} +1 -1
- package/dist/{chunk-U2IM6OCQ.js.map → chunk-HM5VP7SR.js.map} +1 -1
- package/dist/chunk-KSPPYX22.js +14 -0
- package/dist/chunk-KSPPYX22.js.map +1 -0
- package/dist/cms-public.css +38 -0
- package/dist/routes.d.ts +9 -0
- package/dist/routes.js +18 -5
- package/dist/routes.js.map +1 -1
- package/dist/section-runtime.d.ts +46 -0
- package/dist/section-runtime.js +9 -0
- package/dist/server.d.ts +5 -3
- package/dist/server.js +1679 -34
- package/dist/server.js.map +1 -1
- package/package.json +7 -23
- package/dist/before-after-client.d.ts +0 -15
- package/dist/before-after-client.js +0 -8
- package/dist/chunk-27RT6B3O.js +0 -93
- package/dist/chunk-27RT6B3O.js.map +0 -1
- package/dist/chunk-2QU4JU46.js +0 -3927
- package/dist/chunk-2QU4JU46.js.map +0 -1
- package/dist/chunk-4RQ3SEDL.js +0 -106
- package/dist/chunk-4RQ3SEDL.js.map +0 -1
- package/dist/chunk-6GQCFFHU.js +0 -540
- package/dist/chunk-6GQCFFHU.js.map +0 -1
- package/dist/chunk-H46JUIHZ.js +0 -79
- package/dist/chunk-H46JUIHZ.js.map +0 -1
- package/dist/chunk-K62RR4UQ.js +0 -53
- package/dist/chunk-K62RR4UQ.js.map +0 -1
- package/dist/chunk-LEHHSI6G.js +0 -64
- package/dist/chunk-LEHHSI6G.js.map +0 -1
- package/dist/chunk-QYNEOFL7.js +0 -418
- package/dist/chunk-QYNEOFL7.js.map +0 -1
- package/dist/chunk-U5AKACPN.js +0 -113
- package/dist/chunk-U5AKACPN.js.map +0 -1
- package/dist/chunk-UM37DOIK.js +0 -961
- package/dist/chunk-UM37DOIK.js.map +0 -1
- package/dist/chunk-YLSQAT6B.js +0 -59
- package/dist/chunk-YLSQAT6B.js.map +0 -1
- package/dist/chunk-ZL2NA3QT.js +0 -313
- package/dist/chunk-ZL2NA3QT.js.map +0 -1
- package/dist/concerns-client.d.ts +0 -12
- package/dist/concerns-client.js +0 -8
- package/dist/concerns-client.js.map +0 -1
- package/dist/contact-map-client.d.ts +0 -27
- package/dist/contact-map-client.js +0 -8
- package/dist/contact-map-client.js.map +0 -1
- package/dist/expertise-client.d.ts +0 -16
- package/dist/expertise-client.js +0 -9
- package/dist/expertise-client.js.map +0 -1
- package/dist/hero-carousel-client.d.ts +0 -52
- package/dist/hero-carousel-client.js +0 -9
- package/dist/hero-carousel-client.js.map +0 -1
- package/dist/seo-checks.d.ts +0 -87
- package/dist/seo-checks.js +0 -27
- package/dist/seo-checks.js.map +0 -1
- package/dist/site-config-DNpvlqA5.d.ts +0 -3961
- package/dist/spaces-client.d.ts +0 -10
- package/dist/spaces-client.js +0 -8
- package/dist/spaces-client.js.map +0 -1
- package/dist/team-expanding-stage-client.d.ts +0 -35
- package/dist/team-expanding-stage-client.js +0 -8
- package/dist/team-expanding-stage-client.js.map +0 -1
- package/dist/templates.css +0 -4145
- package/dist/templates.d.ts +0 -171
- package/dist/templates.js +0 -291
- package/dist/templates.js.map +0 -1
- package/dist/theme-css-vars-CD3jcEkp.d.ts +0 -7
- package/dist/ui-react-motion-client.d.ts +0 -76
- package/dist/ui-react-motion-client.js +0 -16
- package/dist/ui-react-motion-client.js.map +0 -1
- package/dist/validate.d.ts +0 -104
- package/dist/validate.js +0 -1204
- package/dist/validate.js.map +0 -1
- /package/dist/{before-after-client.js.map → section-runtime.js.map} +0 -0
package/dist/chunk-QYNEOFL7.js
DELETED
|
@@ -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"]}
|
package/dist/chunk-U5AKACPN.js
DELETED
|
@@ -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":[]}
|