@roottale/cms-renderer-next 0.44.0 → 0.45.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/{chunk-75LHSTKC.js → chunk-45KYMMDY.js} +68 -17
  3. package/dist/chunk-45KYMMDY.js.map +1 -0
  4. package/dist/{chunk-4LQPZDXZ.js → chunk-6MFHA555.js} +24 -6
  5. package/dist/chunk-6MFHA555.js.map +1 -0
  6. package/dist/chunk-FZK3EXYZ.js +103 -0
  7. package/dist/chunk-FZK3EXYZ.js.map +1 -0
  8. package/dist/{chunk-6PDZSFVZ.js → chunk-GUV5WHD5.js} +33 -2
  9. package/dist/chunk-GUV5WHD5.js.map +1 -0
  10. package/dist/chunk-LEHHSI6G.js +64 -0
  11. package/dist/chunk-LEHHSI6G.js.map +1 -0
  12. package/dist/chunk-OMXEU4LS.js +414 -0
  13. package/dist/chunk-OMXEU4LS.js.map +1 -0
  14. package/dist/chunk-YLSQAT6B.js +59 -0
  15. package/dist/chunk-YLSQAT6B.js.map +1 -0
  16. package/dist/{chunk-WWS43XCT.js → chunk-Z3ZSCPG4.js} +82 -20
  17. package/dist/chunk-Z3ZSCPG4.js.map +1 -0
  18. package/dist/concerns-client.d.ts +1 -1
  19. package/dist/contact-map-client.d.ts +27 -0
  20. package/dist/contact-map-client.js +8 -0
  21. package/dist/contact-map-client.js.map +1 -0
  22. package/dist/expertise-client.d.ts +7 -1
  23. package/dist/expertise-client.js +2 -1
  24. package/dist/hero-carousel-client.d.ts +18 -2
  25. package/dist/hero-carousel-client.js +2 -1
  26. package/dist/routes.js +1 -1
  27. package/dist/server.js +8 -5
  28. package/dist/{site-config-BHcVF993.d.ts → site-config-kqurIH-k.d.ts} +38 -0
  29. package/dist/spaces-client.d.ts +1 -1
  30. package/dist/team-expanding-stage-client.d.ts +35 -0
  31. package/dist/team-expanding-stage-client.js +8 -0
  32. package/dist/team-expanding-stage-client.js.map +1 -0
  33. package/dist/templates.css +457 -5
  34. package/dist/templates.d.ts +26 -3
  35. package/dist/templates.js +13 -6
  36. package/dist/templates.js.map +1 -1
  37. package/dist/ui-react-motion-client.d.ts +1 -0
  38. package/dist/ui-react-motion-client.js +3 -1
  39. package/dist/validate.js +1 -1
  40. package/package.json +4 -4
  41. package/dist/chunk-4LQPZDXZ.js.map +0 -1
  42. package/dist/chunk-6PDZSFVZ.js.map +0 -1
  43. package/dist/chunk-75LHSTKC.js.map +0 -1
  44. package/dist/chunk-F3G6RVIR.js +0 -220
  45. package/dist/chunk-F3G6RVIR.js.map +0 -1
  46. package/dist/chunk-WWS43XCT.js.map +0 -1
@@ -1,220 +0,0 @@
1
- "use client";
2
-
3
- // src/templates/hero-carousel-client.tsx
4
- import { useCallback, useEffect, useRef, useState } from "react";
5
- import useEmblaCarousel from "embla-carousel-react";
6
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
7
- function usePrefersReducedMotion() {
8
- const [reduced, setReduced] = useState(false);
9
- useEffect(() => {
10
- const query = window.matchMedia("(prefers-reduced-motion: reduce)");
11
- setReduced(query.matches);
12
- const onChange = (event) => setReduced(event.matches);
13
- query.addEventListener("change", onChange);
14
- return () => query.removeEventListener("change", onChange);
15
- }, []);
16
- return reduced;
17
- }
18
- function useDocumentVisible() {
19
- const [visible, setVisible] = useState(true);
20
- useEffect(() => {
21
- const onVisibility = () => setVisible(document.visibilityState === "visible");
22
- onVisibility();
23
- document.addEventListener("visibilitychange", onVisibility);
24
- return () => document.removeEventListener("visibilitychange", onVisibility);
25
- }, []);
26
- return visible;
27
- }
28
- function HeroCarouselViewport({
29
- slides,
30
- autoplay,
31
- intervalMs,
32
- scrim
33
- }) {
34
- const reducedMotion = usePrefersReducedMotion();
35
- const tabVisible = useDocumentVisible();
36
- const [emblaRef, emblaApi] = useEmblaCarousel({
37
- loop: true,
38
- duration: reducedMotion ? 1 : 20
39
- });
40
- const [selectedIndex, setSelectedIndex] = useState(0);
41
- const rootRef = useRef(null);
42
- const onSelect = useCallback(() => {
43
- if (!emblaApi) return;
44
- setSelectedIndex(emblaApi.selectedScrollSnap());
45
- }, [emblaApi]);
46
- useEffect(() => {
47
- if (!emblaApi) return;
48
- onSelect();
49
- emblaApi.on("select", onSelect);
50
- emblaApi.on("reInit", onSelect);
51
- return () => {
52
- emblaApi.off("select", onSelect);
53
- emblaApi.off("reInit", onSelect);
54
- };
55
- }, [emblaApi, onSelect]);
56
- const shouldAutoplay = autoplay && !reducedMotion && tabVisible;
57
- useEffect(() => {
58
- if (!emblaApi || !shouldAutoplay) return;
59
- const id = window.setInterval(() => {
60
- emblaApi.scrollNext();
61
- }, intervalMs);
62
- return () => window.clearInterval(id);
63
- }, [emblaApi, shouldAutoplay, intervalMs]);
64
- const scrollPrev = useCallback(() => {
65
- emblaApi?.scrollPrev(reducedMotion);
66
- }, [emblaApi, reducedMotion]);
67
- const scrollNext = useCallback(() => {
68
- emblaApi?.scrollNext(reducedMotion);
69
- }, [emblaApi, reducedMotion]);
70
- const scrollTo = useCallback(
71
- (index) => {
72
- emblaApi?.scrollTo(index, reducedMotion);
73
- },
74
- [emblaApi, reducedMotion]
75
- );
76
- const onKeyDown = useCallback(
77
- (event) => {
78
- if (event.key === "ArrowLeft") {
79
- event.preventDefault();
80
- scrollPrev();
81
- } else if (event.key === "ArrowRight") {
82
- event.preventDefault();
83
- scrollNext();
84
- }
85
- },
86
- [scrollPrev, scrollNext]
87
- );
88
- const selectedSlide = slides[selectedIndex];
89
- const hasSlidePanel = Boolean(
90
- selectedSlide && (selectedSlide.eyebrow || selectedSlide.title || selectedSlide.subtitle || selectedSlide.description || selectedSlide.primaryCta || selectedSlide.secondaryCta || selectedSlide.metrics && selectedSlide.metrics.length > 0)
91
- );
92
- const hasTabLabels = slides.some((slide) => slide.tabLabel || slide.tabMeta);
93
- return /* @__PURE__ */ jsxs(
94
- "div",
95
- {
96
- ref: rootRef,
97
- className: "rt-tpl-hero-carousel",
98
- role: "region",
99
- "aria-roledescription": "carousel",
100
- "aria-label": "\uBC30\uACBD \uC774\uBBF8\uC9C0 \uCE90\uB7EC\uC140",
101
- tabIndex: 0,
102
- onKeyDown,
103
- "data-reduced-motion": reducedMotion ? "true" : "false",
104
- "data-scrim": scrim ?? "soft",
105
- children: [
106
- /* @__PURE__ */ jsx("div", { className: "rt-tpl-hero-carousel-viewport", ref: emblaRef, children: /* @__PURE__ */ jsx("div", { className: "rt-tpl-hero-carousel-track", children: slides.map((slide, index) => /* @__PURE__ */ jsx(
107
- "div",
108
- {
109
- className: "rt-tpl-hero-carousel-slide",
110
- "aria-roledescription": "slide",
111
- "aria-label": `Slide ${index + 1} of ${slides.length}`,
112
- "aria-hidden": index === selectedIndex ? void 0 : true,
113
- children: /* @__PURE__ */ jsx(
114
- "img",
115
- {
116
- className: "rt-tpl-hero-carousel-img",
117
- src: slide.image,
118
- alt: slide.alt,
119
- "data-active": index === selectedIndex ? "true" : "false",
120
- loading: index === 0 ? "eager" : "lazy"
121
- }
122
- )
123
- },
124
- `${slide.image}-${index}`
125
- )) }) }),
126
- /* @__PURE__ */ jsx(
127
- "button",
128
- {
129
- type: "button",
130
- className: "rt-tpl-hero-carousel-arrow rt-tpl-hero-carousel-arrow--prev",
131
- "aria-label": "\uC774\uC804 \uC2AC\uB77C\uC774\uB4DC",
132
- onClick: scrollPrev,
133
- children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", "aria-hidden": "true", focusable: "false", children: /* @__PURE__ */ jsx("path", { d: "M15 5 8 12l7 7", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) })
134
- }
135
- ),
136
- /* @__PURE__ */ jsx(
137
- "button",
138
- {
139
- type: "button",
140
- className: "rt-tpl-hero-carousel-arrow rt-tpl-hero-carousel-arrow--next",
141
- "aria-label": "\uB2E4\uC74C \uC2AC\uB77C\uC774\uB4DC",
142
- onClick: scrollNext,
143
- children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", "aria-hidden": "true", focusable: "false", children: /* @__PURE__ */ jsx("path", { d: "m9 5 7 7-7 7", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) })
144
- }
145
- ),
146
- hasSlidePanel && selectedSlide ? /* @__PURE__ */ jsxs("div", { className: "rt-tpl-hero-carousel-slide-panel", children: [
147
- selectedSlide.eyebrow ? /* @__PURE__ */ jsx("p", { className: "rt-tpl-hero-carousel-slide-eyebrow", children: selectedSlide.eyebrow }) : null,
148
- selectedSlide.title ? /* @__PURE__ */ jsx("p", { className: "rt-tpl-hero-carousel-slide-title", children: selectedSlide.title }) : null,
149
- selectedSlide.subtitle ? /* @__PURE__ */ jsx("p", { className: "rt-tpl-hero-carousel-slide-subtitle", children: selectedSlide.subtitle }) : null,
150
- selectedSlide.description ? /* @__PURE__ */ jsx("p", { className: "rt-tpl-hero-carousel-slide-desc", children: selectedSlide.description }) : null,
151
- selectedSlide.metrics && selectedSlide.metrics.length > 0 ? /* @__PURE__ */ jsx("dl", { className: "rt-tpl-hero-carousel-slide-metrics", children: selectedSlide.metrics.map((metric) => /* @__PURE__ */ jsxs("div", { children: [
152
- /* @__PURE__ */ jsx("dt", { children: metric.label }),
153
- /* @__PURE__ */ jsxs("dd", { children: [
154
- metric.value,
155
- metric.unit ? /* @__PURE__ */ jsx("span", { children: metric.unit }) : null
156
- ] }),
157
- metric.source ? /* @__PURE__ */ jsx("dd", { children: metric.source }) : null
158
- ] }, `${metric.value}-${metric.label}`)) }) : null,
159
- selectedSlide.primaryCta || selectedSlide.secondaryCta ? /* @__PURE__ */ jsxs("div", { className: "rt-tpl-hero-carousel-slide-actions", children: [
160
- selectedSlide.primaryCta ? /* @__PURE__ */ jsx(
161
- "a",
162
- {
163
- className: "rt-tpl-btn rt-tpl-btn--primary",
164
- href: selectedSlide.primaryCta.href,
165
- "data-track": "cta_hero",
166
- children: selectedSlide.primaryCta.label
167
- }
168
- ) : null,
169
- selectedSlide.secondaryCta ? /* @__PURE__ */ jsx(
170
- "a",
171
- {
172
- className: "rt-tpl-btn rt-tpl-btn--secondary",
173
- href: selectedSlide.secondaryCta.href,
174
- "data-track": "cta_hero",
175
- children: selectedSlide.secondaryCta.label
176
- }
177
- ) : null
178
- ] }) : null
179
- ] }) : null,
180
- /* @__PURE__ */ jsxs("div", { className: "rt-tpl-hero-carousel-controls", children: [
181
- /* @__PURE__ */ jsx(
182
- "div",
183
- {
184
- className: hasTabLabels ? "rt-tpl-hero-carousel-dots rt-tpl-hero-carousel-dots--tabs" : "rt-tpl-hero-carousel-dots",
185
- role: "tablist",
186
- "aria-label": "\uC2AC\uB77C\uC774\uB4DC \uC120\uD0DD",
187
- children: slides.map((_slide, index) => /* @__PURE__ */ jsx(
188
- "button",
189
- {
190
- type: "button",
191
- role: "tab",
192
- className: "rt-tpl-hero-carousel-dot",
193
- "aria-selected": index === selectedIndex,
194
- "aria-label": `${index + 1}\uBC88 \uC2AC\uB77C\uC774\uB4DC\uB85C \uC774\uB3D9`,
195
- "data-active": index === selectedIndex ? "true" : "false",
196
- onClick: () => scrollTo(index),
197
- children: hasTabLabels ? /* @__PURE__ */ jsxs(Fragment, { children: [
198
- /* @__PURE__ */ jsx("span", { children: _slide.tabLabel ?? `${index + 1}` }),
199
- _slide.tabMeta ? /* @__PURE__ */ jsx("small", { children: _slide.tabMeta }) : null
200
- ] }) : null
201
- },
202
- index
203
- ))
204
- }
205
- ),
206
- /* @__PURE__ */ jsxs("p", { className: "rt-tpl-hero-carousel-counter", "aria-hidden": "true", children: [
207
- /* @__PURE__ */ jsx("span", { className: "rt-tpl-hero-carousel-counter-current", children: selectedIndex + 1 }),
208
- "/",
209
- /* @__PURE__ */ jsx("span", { className: "rt-tpl-hero-carousel-counter-total", children: slides.length })
210
- ] })
211
- ] })
212
- ]
213
- }
214
- );
215
- }
216
-
217
- export {
218
- HeroCarouselViewport
219
- };
220
- //# sourceMappingURL=chunk-F3G6RVIR.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/templates/hero-carousel-client.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, ds-rule-pack.md):\n * - `prefers-reduced-motion: reduce` → autoplay 강제 off + 전환은 embla\n * `jump=true`(무전환) + Ken Burns 스케일 없음.\n * - Page Visibility API → 탭 비활성 시 autoplay 일시정지.\n * - 좌/우 화살표 키보드 이동(포커스가 캐러셀 안에 있을 때).\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 useEmblaCarousel from \"embla-carousel-react\";\n\nexport interface HeroCarouselSlideInput {\n readonly image: string;\n readonly alt: string;\n readonly caption?: string;\n readonly eyebrow?: string;\n readonly title?: string;\n readonly subtitle?: string;\n readonly description?: string;\n readonly primaryCta?: { readonly label: string; readonly href: string };\n readonly secondaryCta?: { readonly label: string; readonly href: string };\n readonly tabLabel?: string;\n readonly tabMeta?: string;\n readonly metrics?: readonly {\n readonly value: string;\n readonly unit?: string;\n readonly label: string;\n readonly source?: string;\n }[];\n}\n\nexport interface HeroCarouselViewportProps {\n readonly slides: readonly HeroCarouselSlideInput[];\n readonly autoplay: boolean;\n readonly intervalMs: number;\n readonly scrim?: \"none\" | \"soft\" | \"strong\";\n}\n\nfunction 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\nfunction 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 function HeroCarouselViewport({\n slides,\n autoplay,\n intervalMs,\n scrim,\n}: HeroCarouselViewportProps) {\n const reducedMotion = usePrefersReducedMotion();\n const tabVisible = useDocumentVisible();\n const [emblaRef, emblaApi] = useEmblaCarousel({\n loop: true,\n duration: reducedMotion ? 1 : 20,\n });\n const [selectedIndex, setSelectedIndex] = useState(0);\n const rootRef = useRef<HTMLDivElement | null>(null);\n\n const onSelect = useCallback(() => {\n if (!emblaApi) return;\n setSelectedIndex(emblaApi.selectedScrollSnap());\n }, [emblaApi]);\n\n useEffect(() => {\n if (!emblaApi) return;\n onSelect();\n emblaApi.on(\"select\", onSelect);\n emblaApi.on(\"reInit\", onSelect);\n return () => {\n emblaApi.off(\"select\", onSelect);\n emblaApi.off(\"reInit\", onSelect);\n };\n }, [emblaApi, onSelect]);\n\n const shouldAutoplay = autoplay && !reducedMotion && tabVisible;\n\n useEffect(() => {\n if (!emblaApi || !shouldAutoplay) return;\n const id = window.setInterval(() => {\n emblaApi.scrollNext();\n }, intervalMs);\n return () => window.clearInterval(id);\n }, [emblaApi, shouldAutoplay, intervalMs]);\n\n const scrollPrev = useCallback(() => {\n emblaApi?.scrollPrev(reducedMotion);\n }, [emblaApi, reducedMotion]);\n\n const scrollNext = useCallback(() => {\n emblaApi?.scrollNext(reducedMotion);\n }, [emblaApi, reducedMotion]);\n\n const scrollTo = useCallback(\n (index: number) => {\n emblaApi?.scrollTo(index, reducedMotion);\n },\n [emblaApi, reducedMotion],\n );\n\n const onKeyDown = useCallback(\n (event: React.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 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\n return (\n <div\n ref={rootRef}\n className=\"rt-tpl-hero-carousel\"\n role=\"region\"\n aria-roledescription=\"carousel\"\n aria-label=\"배경 이미지 캐러셀\"\n tabIndex={0}\n onKeyDown={onKeyDown}\n data-reduced-motion={reducedMotion ? \"true\" : \"false\"}\n data-scrim={scrim ?? \"soft\"}\n >\n <div className=\"rt-tpl-hero-carousel-viewport\" ref={emblaRef}>\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 <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 className=\"rt-tpl-hero-carousel-slide-panel\">\n {selectedSlide.eyebrow ? (\n <p className=\"rt-tpl-hero-carousel-slide-eyebrow\">\n {selectedSlide.eyebrow}\n </p>\n ) : null}\n {selectedSlide.title ? (\n <p className=\"rt-tpl-hero-carousel-slide-title\">\n {selectedSlide.title}\n </p>\n ) : null}\n {selectedSlide.subtitle ? (\n <p className=\"rt-tpl-hero-carousel-slide-subtitle\">\n {selectedSlide.subtitle}\n </p>\n ) : null}\n {selectedSlide.description ? (\n <p className=\"rt-tpl-hero-carousel-slide-desc\">\n {selectedSlide.description}\n </p>\n ) : null}\n {selectedSlide.metrics && selectedSlide.metrics.length > 0 ? (\n <dl className=\"rt-tpl-hero-carousel-slide-metrics\">\n {selectedSlide.metrics.map((metric) => (\n <div key={`${metric.value}-${metric.label}`}>\n <dt>{metric.label}</dt>\n <dd>\n {metric.value}\n {metric.unit ? <span>{metric.unit}</span> : null}\n </dd>\n {metric.source ? <dd>{metric.source}</dd> : null}\n </div>\n ))}\n </dl>\n ) : null}\n {selectedSlide.primaryCta || selectedSlide.secondaryCta ? (\n <div className=\"rt-tpl-hero-carousel-slide-actions\">\n {selectedSlide.primaryCta ? (\n <a\n className=\"rt-tpl-btn rt-tpl-btn--primary\"\n href={selectedSlide.primaryCta.href}\n data-track=\"cta_hero\"\n >\n {selectedSlide.primaryCta.label}\n </a>\n ) : null}\n {selectedSlide.secondaryCta ? (\n <a\n className=\"rt-tpl-btn rt-tpl-btn--secondary\"\n href={selectedSlide.secondaryCta.href}\n data-track=\"cta_hero\"\n >\n {selectedSlide.secondaryCta.label}\n </a>\n ) : null}\n </div>\n ) : null}\n </div>\n ) : null}\n\n <div className=\"rt-tpl-hero-carousel-controls\">\n <div\n className={\n hasTabLabels\n ? \"rt-tpl-hero-carousel-dots rt-tpl-hero-carousel-dots--tabs\"\n : \"rt-tpl-hero-carousel-dots\"\n }\n role=\"tablist\"\n aria-label=\"슬라이드 선택\"\n >\n {slides.map((_slide, index) => (\n <button\n key={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 <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 );\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../ui-react/src/components/scroll/AnimateOnScroll.tsx","../../ui-react/src/components/motion/variants.ts","../../ui-react/src/components/motion/Marquee.tsx","../../ui-react/src/components/scroll/StaggerGroup.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * `AnimateOnScroll` — whileInView 진입 애니메이션 래퍼(ddd `scroll`).\n *\n * 모션 규율(M5, ds-rule-pack.md):\n * - `useReducedMotion()` true → 애니메이션 없이 즉시 표시.\n * - **마운트 전(SSR·하이드레이션 전)에는 initial=\"hidden\" 을 걸지 않는다** —\n * 서버 마크업은 항상 최종(보이는) 상태로 렌더한다. JS 미로드·모션 실패 시에도\n * 콘텐츠가 보여야 하는 계약(M6)을 지키기 위함. 진입 애니메이션은 하이드레이션\n * 이후에만 활성화되며, 이 컴포넌트를 쓰는 섹션은 통상 fold 아래라 \"보였다가\n * 숨는\" 플래시가 사용자에게 노출되지 않는다.\n * - **`IntersectionObserver` 가 없는 환경(구형 브라우저·테스트 스텁 삭제)도\n * 동일 계약을 지킨다.** motion/react 의 `whileInView` Feature 는 마운트 시\n * 무조건 `new IntersectionObserver(...)` 를 호출하므로, 그 생성자가 없으면\n * 관찰이 아예 시작되지 않아 `initial=\"hidden\"` 상태(opacity 0)에서 영원히\n * 멈춘다 — motion 라이브러리 자체는 이 마운트 경로에 폴백이 없다. 여기서\n * `IntersectionObserver` 존재 여부를 직접 확인해 없으면 reduced-motion과\n * 동일하게 최종 상태로 렌더한다(M6).\n *\n * variant(구조 상이 ≥2, M4-f): `fade-up` · `fade` · `scale-in` — 정의는\n * `../motion/variants`(C5, 손 복사 금지) 한 곳에서 import.\n */\nimport {\n useEffect,\n createElement,\n useState,\n type CSSProperties,\n type ReactNode,\n} from \"react\";\nimport { motion, useReducedMotion } from \"motion/react\";\nimport {\n DEFAULT_VIEWPORT_MARGIN,\n ENTRANCE_VARIANTS,\n type EntranceVariant,\n} from \"../motion/variants\";\n\ntype AnimateTag = \"div\" | \"section\" | \"article\" | \"li\" | \"span\";\n\nexport interface AnimateOnScrollProps {\n readonly children?: ReactNode;\n readonly className?: string;\n readonly style?: CSSProperties;\n /** 진입 애니메이션 종류(구조 상이). 기본 `fade-up`. */\n readonly variant?: EntranceVariant;\n /** 렌더할 시맨틱 엘리먼트(래퍼 div 추가 없이 그 자리를 대체). 기본 `div`. */\n readonly as?: AnimateTag;\n /** 한 번만 트리거(재진입 시 재생 안 함). 기본 true. */\n readonly once?: boolean;\n /** whileInView viewport root margin. 기본 `-80px`. */\n readonly margin?: string;\n}\n\nexport function AnimateOnScroll({\n children,\n className,\n style,\n variant = \"fade-up\",\n as = \"div\",\n once = true,\n margin = DEFAULT_VIEWPORT_MARGIN,\n}: AnimateOnScrollProps) {\n const reducedMotion = useReducedMotion();\n const [mounted, setMounted] = useState(false);\n useEffect(() => setMounted(true), []);\n\n // `mounted` already gates the SSR/pre-hydration pass, so this check only\n // ever runs client-side — safe to read `window` synchronously here.\n const supportsInView =\n mounted && typeof window !== \"undefined\" && typeof window.IntersectionObserver !== \"undefined\";\n\n if (reducedMotion || !mounted || !supportsInView) {\n return createElement(as, { className, style }, children);\n }\n\n const MotionTag = motion[as] as typeof motion.div;\n return (\n <MotionTag\n className={className}\n style={style}\n initial=\"hidden\"\n whileInView=\"visible\"\n viewport={{ once, margin }}\n variants={ENTRANCE_VARIANTS[variant]}\n >\n {children}\n </MotionTag>\n );\n}\n","/**\n * 공유 모션 variants — C5(생성물 손 복사 금지)의 정신을 따라 모든 `scroll`/`motion`\n * 프리미티브가 이 한 파일에서 variants 를 파생한다. 각 컴포넌트가 hidden/visible\n * keyframe 객체를 손으로 복사하지 않는다(ds-rule-pack.md C5).\n *\n * 타이밍 출처(C1 — 임의값 대신 토큰 참조):\n * - `ENTRANCE_EASE` = DTCG `--ease-out`(packages/tokens/src/motion.dtcg.json:\n * `cubic-bezier(0, 0, 0.2, 1)`)과 동일 곡선. 등장(entrance)에 쓰는 표준 감속.\n * - `ENTRANCE_DURATION`(0.5s)·`STAGGER_STEP`(0.08s) = 앰비언트 등장 스케일.\n * motion.dtcg 의 100~200ms 는 INP 예산이 걸린 마이크로 인터랙션\n * (hover/focus/button)용 cap 이며(M5), 스크롤 진입 리빌은 입력 지연(INP)\n * 경로가 아니라 그 cap 대상이 아니다 — 별도 앰비언트 스케일로 문서화한다.\n */\nimport type { Variants } from \"motion/react\";\n\n/** = DTCG `--ease-out` (packages/tokens/src/motion.dtcg.json). */\nexport const ENTRANCE_EASE = [0, 0, 0.2, 1] as const;\n/** 앰비언트 등장 지속(초). INP cap(200ms) 대상 아님 — 상단 주석 참조. */\nexport const ENTRANCE_DURATION = 0.5;\n/** 자식 순차 지연(초). */\nexport const STAGGER_STEP = 0.08;\n/** whileInView viewport 기본 margin — 뷰포트 하단 80px 전 트리거. */\nexport const DEFAULT_VIEWPORT_MARGIN = \"-80px\";\n\nconst entranceTransition = {\n duration: ENTRANCE_DURATION,\n ease: ENTRANCE_EASE,\n} as const;\n\nexport type EntranceVariant = \"fade-up\" | \"fade\" | \"scale-in\";\n\n/** 모션 유무와 관계없이 공유하는 진입 시작/완료 상태의 단일 원본. */\nexport const ENTRANCE_KEYFRAMES = {\n \"fade-up\": { hidden: { opacity: 0, y: 16 }, visible: { opacity: 1, y: 0 } },\n fade: { hidden: { opacity: 0 }, visible: { opacity: 1 } },\n \"scale-in\": { hidden: { opacity: 0, scale: 0.98 }, visible: { opacity: 1, scale: 1 } },\n} as const;\n\nfunction withEntranceTransition(keyframes: (typeof ENTRANCE_KEYFRAMES)[EntranceVariant]): Variants {\n return {\n hidden: keyframes.hidden,\n visible: { ...keyframes.visible, transition: entranceTransition },\n };\n}\n\n/** fade + 상승(구조: 이동 O). */\nexport const fadeUp: Variants = withEntranceTransition(ENTRANCE_KEYFRAMES[\"fade-up\"]);\n\n/** fade only(구조: 이동 X). */\nexport const fade: Variants = withEntranceTransition(ENTRANCE_KEYFRAMES.fade);\n\n/** subtle scale(구조: 축소→기본, AI-slop #10 준수 — 0.98 미만 스케일 금지). */\nexport const scaleIn: Variants = withEntranceTransition(ENTRANCE_KEYFRAMES[\"scale-in\"]);\n\n/** `AnimateOnScroll` 의 3 구조 상이 variant(M4-f) — 여기서만 정의. */\nexport const ENTRANCE_VARIANTS: Record<EntranceVariant, Variants> = {\n \"fade-up\": fadeUp,\n fade,\n \"scale-in\": scaleIn,\n};\n\n/** stagger 컨테이너 — 자식 진입을 `step` 간격으로 순차 트리거. */\nexport function staggerContainer(step: number = STAGGER_STEP): Variants {\n return {\n hidden: {},\n visible: { transition: { staggerChildren: step } },\n };\n}\n\n/** stagger 자식 — 컨테이너의 hidden/visible 라벨을 상속받아 fadeUp 으로 진입. */\nexport const staggerItem: Variants = fadeUp;\n","\"use client\";\n\n/**\n * `Marquee` — 수평 무한 루프(ddd `motion`). 로고 클라우드·신뢰 배지 띠용.\n *\n * 모션 규율(M5·AI-slop):\n * - `useReducedMotion()` true 또는 마운트 전 → **정지**. 스크롤 없이\n * `flex-wrap` 으로 항목 전체를 한 번에 노출(콘텐츠 손실 없음, M6).\n * - 진행 방식 = `useAnimationFrame` 로 매 프레임 균일 이동(선형). hover 시\n * 일시정지(pause-on-hover) — 정지/재개가 위치 점프 없이 이어진다.\n * - 과한 효과 금지(AI-slop #10) — scale·회전 없음, 등속 이동만.\n *\n * 트랙은 자식을 2벌 복제해 -50% 이동 시 이음매 없이 순환한다.\n */\nimport {\n useEffect,\n useState,\n type CSSProperties,\n type ReactNode,\n} from \"react\";\nimport {\n motion,\n useAnimationFrame,\n useMotionValue,\n useReducedMotion,\n useTransform,\n} from \"motion/react\";\n\nexport interface MarqueeProps {\n readonly children?: ReactNode;\n readonly className?: string;\n /** 한 바퀴(자식 1벌 폭) 이동에 걸리는 시간(초). 기본 24. */\n readonly speedSec?: number;\n /** 항목 간격(CSS length). 기본 `2rem`(구조 간격 — 브랜드 토큰 아님). */\n readonly gap?: string;\n /** hover 시 일시정지. 기본 true. */\n readonly pauseOnHover?: boolean;\n /** 스크린리더용 그룹 레이블. */\n readonly ariaLabel?: string;\n}\n\nexport function Marquee({\n children,\n className,\n speedSec = 24,\n gap = \"2rem\",\n pauseOnHover = true,\n ariaLabel,\n}: MarqueeProps) {\n const reducedMotion = useReducedMotion();\n const [mounted, setMounted] = useState(false);\n const [paused, setPaused] = useState(false);\n useEffect(() => setMounted(true), []);\n\n const x = useMotionValue(0);\n const transform = useTransform(x, (v) => `translateX(${v}%)`);\n\n useAnimationFrame((_t, delta) => {\n if (reducedMotion || paused) return;\n const percentPerMs = 50 / (speedSec * 1000); // 자식 1벌 = 트랙의 50%\n let next = x.get() - percentPerMs * delta;\n if (next <= -50) next += 50; // 이음매 없는 순환\n x.set(next);\n });\n\n const staticStyle: CSSProperties = {\n display: \"flex\",\n flexWrap: \"wrap\",\n alignItems: \"center\",\n gap,\n };\n if (reducedMotion || !mounted) {\n return (\n <div className={className} aria-label={ariaLabel} style={staticStyle}>\n {children}\n </div>\n );\n }\n\n const copyStyle: CSSProperties = {\n display: \"flex\",\n alignItems: \"center\",\n gap,\n flexShrink: 0,\n paddingInlineEnd: gap,\n };\n return (\n <div\n className={className}\n aria-label={ariaLabel}\n style={{ overflow: \"hidden\", inlineSize: \"100%\" }}\n onMouseEnter={pauseOnHover ? () => setPaused(true) : undefined}\n onMouseLeave={pauseOnHover ? () => setPaused(false) : undefined}\n >\n <motion.div\n style={{ display: \"flex\", inlineSize: \"max-content\", willChange: \"transform\", transform }}\n >\n <div style={copyStyle}>{children}</div>\n <div style={copyStyle} aria-hidden>\n {children}\n </div>\n </motion.div>\n </div>\n );\n}\n","\"use client\";\n\n/**\n * `StaggerGroup` + `StaggerItem` — 자식 순차 진입(ddd `scroll`). 카드 그리드용.\n *\n * `StaggerGroup` 이 컨테이너(초기 hidden→whileInView visible)를, 각 `StaggerItem`\n * 은 컨테이너의 애니메이션 라벨을 상속받아 `STAGGER_STEP` 간격으로 진입한다.\n * variants 정의는 `../motion/variants` 한 곳(C5). 모션 규율은 `AnimateOnScroll`\n * 와 동일 — reduce·마운트 전에는 애니메이션 없이 시맨틱 엘리먼트를 그대로 렌더\n * (래퍼 div 추가 없음 → SSR 마크업 불변, M6).\n */\nimport {\n useEffect,\n createElement,\n useState,\n type CSSProperties,\n type ReactNode,\n} from \"react\";\nimport { motion, useReducedMotion } from \"motion/react\";\nimport {\n DEFAULT_VIEWPORT_MARGIN,\n STAGGER_STEP,\n staggerContainer,\n staggerItem,\n} from \"../motion/variants\";\n\ntype GroupTag = \"div\" | \"ul\" | \"ol\" | \"section\";\ntype ItemTag = \"div\" | \"li\" | \"article\";\n\nexport interface StaggerGroupProps {\n readonly children?: ReactNode;\n readonly className?: string;\n readonly style?: CSSProperties;\n /** 렌더할 시맨틱 컨테이너 엘리먼트. 기본 `div`. */\n readonly as?: GroupTag;\n /** 자식 순차 지연(초). 기본 0.08. */\n readonly staggerChildren?: number;\n readonly once?: boolean;\n readonly margin?: string;\n}\n\nexport function StaggerGroup({\n children,\n className,\n style,\n as = \"div\",\n staggerChildren = STAGGER_STEP,\n once = true,\n margin = DEFAULT_VIEWPORT_MARGIN,\n}: StaggerGroupProps) {\n const reducedMotion = useReducedMotion();\n const [mounted, setMounted] = useState(false);\n useEffect(() => setMounted(true), []);\n\n if (reducedMotion || !mounted) {\n return createElement(as, { className, style }, children);\n }\n\n const MotionTag = motion[as] as typeof motion.div;\n return (\n <MotionTag\n className={className}\n style={style}\n initial=\"hidden\"\n whileInView=\"visible\"\n viewport={{ once, margin }}\n variants={staggerContainer(staggerChildren)}\n >\n {children}\n </MotionTag>\n );\n}\n\nexport interface StaggerItemProps {\n readonly children?: ReactNode;\n readonly className?: string;\n readonly style?: CSSProperties;\n /** 렌더할 시맨틱 아이템 엘리먼트. 기본 `div`. */\n readonly as?: ItemTag;\n}\n\nexport function StaggerItem({ children, className, style, as = \"div\" }: StaggerItemProps) {\n const reducedMotion = useReducedMotion();\n const [mounted, setMounted] = useState(false);\n useEffect(() => setMounted(true), []);\n\n if (reducedMotion || !mounted) {\n return createElement(as, { className, style }, children);\n }\n\n // initial/whileInView 없음 — 부모 StaggerGroup 의 hidden/visible 라벨을 상속.\n const MotionTag = motion[as] as typeof motion.div;\n return (\n <MotionTag className={className} style={style} variants={staggerItem}>\n {children}\n </MotionTag>\n );\n}\n"],"mappings":"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","names":["useEffect","useState","motion","useReducedMotion","jsx","useEffect","createElement","useState","motion","useReducedMotion","jsx","useReducedMotion","useState","useEffect","createElement","motion"]}