@roottale/cms-renderer-next 0.41.1 → 0.42.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 (60) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/dist/before-after-client.d.ts +15 -0
  3. package/dist/before-after-client.js +8 -0
  4. package/dist/before-after-client.js.map +1 -0
  5. package/dist/chunk-4LQPZDXZ.js +75 -0
  6. package/dist/chunk-4LQPZDXZ.js.map +1 -0
  7. package/dist/chunk-6GQCFFHU.js +540 -0
  8. package/dist/chunk-6GQCFFHU.js.map +1 -0
  9. package/dist/chunk-F3G6RVIR.js +220 -0
  10. package/dist/chunk-F3G6RVIR.js.map +1 -0
  11. package/dist/chunk-H46JUIHZ.js +79 -0
  12. package/dist/chunk-H46JUIHZ.js.map +1 -0
  13. package/dist/chunk-K62RR4UQ.js +53 -0
  14. package/dist/chunk-K62RR4UQ.js.map +1 -0
  15. package/dist/chunk-NUUTTSGT.js +916 -0
  16. package/dist/chunk-NUUTTSGT.js.map +1 -0
  17. package/dist/chunk-RSNUZ4BF.js +3378 -0
  18. package/dist/chunk-RSNUZ4BF.js.map +1 -0
  19. package/dist/chunk-U5AKACPN.js +113 -0
  20. package/dist/chunk-U5AKACPN.js.map +1 -0
  21. package/dist/chunk-WWS43XCT.js +206 -0
  22. package/dist/chunk-WWS43XCT.js.map +1 -0
  23. package/dist/cms-public.css +164 -51
  24. package/dist/concerns-client.d.ts +12 -0
  25. package/dist/concerns-client.js +8 -0
  26. package/dist/concerns-client.js.map +1 -0
  27. package/dist/expertise-client.d.ts +10 -0
  28. package/dist/expertise-client.js +8 -0
  29. package/dist/expertise-client.js.map +1 -0
  30. package/dist/hero-carousel-client.d.ts +36 -0
  31. package/dist/hero-carousel-client.js +8 -0
  32. package/dist/hero-carousel-client.js.map +1 -0
  33. package/dist/routes.d.ts +51 -5
  34. package/dist/routes.js +109 -62
  35. package/dist/routes.js.map +1 -1
  36. package/dist/seo-checks.d.ts +87 -0
  37. package/dist/seo-checks.js +27 -0
  38. package/dist/seo-checks.js.map +1 -0
  39. package/dist/server.d.ts +51 -2
  40. package/dist/server.js +8 -2
  41. package/dist/site-config-DgYGGwu1.d.ts +3910 -0
  42. package/dist/spaces-client.d.ts +10 -0
  43. package/dist/spaces-client.js +8 -0
  44. package/dist/spaces-client.js.map +1 -0
  45. package/dist/templates.css +3246 -194
  46. package/dist/templates.d.ts +20 -1028
  47. package/dist/templates.js +70 -2
  48. package/dist/templates.js.map +1 -1
  49. package/dist/{theme-css-vars-BiyHBsJG.d.ts → theme-css-vars-CD3jcEkp.d.ts} +2 -2
  50. package/dist/ui-react-motion-client.d.ts +73 -0
  51. package/dist/ui-react-motion-client.js +14 -0
  52. package/dist/ui-react-motion-client.js.map +1 -0
  53. package/dist/validate.d.ts +15 -1
  54. package/dist/validate.js +806 -17
  55. package/dist/validate.js.map +1 -1
  56. package/package.json +18 -6
  57. package/dist/chunk-GOHP7RWD.js +0 -1647
  58. package/dist/chunk-GOHP7RWD.js.map +0 -1
  59. package/dist/chunk-IOYBEH6E.js +0 -271
  60. package/dist/chunk-IOYBEH6E.js.map +0 -1
@@ -0,0 +1,220 @@
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
@@ -0,0 +1 @@
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":[]}
@@ -0,0 +1,79 @@
1
+ "use client";
2
+
3
+ // src/templates/concerns-client.tsx
4
+ import { useState } from "react";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
+ function ConcernsFigureMap({
7
+ figureImage,
8
+ figureAlt,
9
+ regions
10
+ }) {
11
+ const [selectedId, setSelectedId] = useState(regions[0]?.id ?? "");
12
+ const selected = regions.find((region) => region.id === selectedId) ?? regions[0];
13
+ if (!selected) return null;
14
+ return /* @__PURE__ */ jsxs("div", { className: "rt-tpl-concerns-map-layout", children: [
15
+ /* @__PURE__ */ jsxs("div", { className: "rt-tpl-concerns-figure-wrap", children: [
16
+ /* @__PURE__ */ jsx(
17
+ "img",
18
+ {
19
+ className: "rt-tpl-concerns-figure",
20
+ src: figureImage,
21
+ alt: figureAlt,
22
+ loading: "lazy"
23
+ }
24
+ ),
25
+ /* @__PURE__ */ jsx("div", { className: "rt-tpl-concerns-hotspots", "aria-label": "\uBB38\uC81C \uC601\uC5ED \uD56B\uC2A4\uD31F", children: regions.map((region) => {
26
+ const active = region.id === selected.id;
27
+ return /* @__PURE__ */ jsx(
28
+ "button",
29
+ {
30
+ type: "button",
31
+ className: "rt-tpl-concerns-hotspot",
32
+ style: {
33
+ left: `${region.x ?? 50}%`,
34
+ top: `${region.y ?? 50}%`
35
+ },
36
+ "aria-label": region.label,
37
+ "aria-pressed": active,
38
+ "data-active": active ? "true" : "false",
39
+ onClick: () => setSelectedId(region.id),
40
+ onFocus: () => setSelectedId(region.id),
41
+ children: /* @__PURE__ */ jsx("span", { children: region.label })
42
+ },
43
+ region.id
44
+ );
45
+ }) })
46
+ ] }),
47
+ /* @__PURE__ */ jsxs("div", { className: "rt-tpl-concerns-side", children: [
48
+ /* @__PURE__ */ jsx("div", { className: "rt-tpl-concerns-button-list", "aria-label": "\uBB38\uC81C \uC601\uC5ED \uBAA9\uB85D", children: regions.map((region) => /* @__PURE__ */ jsx(
49
+ "button",
50
+ {
51
+ type: "button",
52
+ className: "rt-tpl-concerns-list-button",
53
+ "data-active": region.id === selected.id ? "true" : "false",
54
+ "aria-pressed": region.id === selected.id,
55
+ onClick: () => setSelectedId(region.id),
56
+ children: region.label
57
+ },
58
+ region.id
59
+ )) }),
60
+ /* @__PURE__ */ jsx(ConcernRegionPanel, { region: selected })
61
+ ] })
62
+ ] });
63
+ }
64
+ function ConcernRegionPanel({
65
+ region
66
+ }) {
67
+ return /* @__PURE__ */ jsxs("article", { className: "rt-tpl-concerns-panel", children: [
68
+ /* @__PURE__ */ jsx("p", { className: "rt-tpl-concerns-label", children: region.label }),
69
+ /* @__PURE__ */ jsx("h3", { className: "rt-tpl-concerns-title", children: region.title }),
70
+ /* @__PURE__ */ jsx("p", { className: "rt-tpl-concerns-desc", children: region.description }),
71
+ region.items ? /* @__PURE__ */ jsx("ul", { className: "rt-tpl-concerns-items", children: region.items.map((item) => /* @__PURE__ */ jsx("li", { children: item }, item)) }) : null,
72
+ region.href ? /* @__PURE__ */ jsx("a", { className: "rt-tpl-concerns-cta", href: region.href, children: region.ctaLabel ?? "\uC790\uC138\uD788 \uBCF4\uAE30" }) : null
73
+ ] });
74
+ }
75
+
76
+ export {
77
+ ConcernsFigureMap
78
+ };
79
+ //# sourceMappingURL=chunk-H46JUIHZ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/templates/concerns-client.tsx"],"sourcesContent":["\"use client\";\n\nimport { useState } from \"react\";\n\nimport type { ConcernsSection } from \"./site-config.js\";\n\ntype ConcernRegion = ConcernsSection[\"props\"][\"regions\"][number];\n\nexport function ConcernsFigureMap({\n figureImage,\n figureAlt,\n regions,\n}: {\n readonly figureImage: string;\n readonly figureAlt: string;\n readonly regions: readonly ConcernRegion[];\n}) {\n const [selectedId, setSelectedId] = useState(regions[0]?.id ?? \"\");\n const selected =\n regions.find((region) => region.id === selectedId) ?? regions[0];\n\n if (!selected) return null;\n\n return (\n <div className=\"rt-tpl-concerns-map-layout\">\n <div className=\"rt-tpl-concerns-figure-wrap\">\n <img\n className=\"rt-tpl-concerns-figure\"\n src={figureImage}\n alt={figureAlt}\n loading=\"lazy\"\n />\n <div className=\"rt-tpl-concerns-hotspots\" aria-label=\"문제 영역 핫스팟\">\n {regions.map((region) => {\n const active = region.id === selected.id;\n return (\n <button\n key={region.id}\n type=\"button\"\n className=\"rt-tpl-concerns-hotspot\"\n style={{\n left: `${region.x ?? 50}%`,\n top: `${region.y ?? 50}%`,\n }}\n aria-label={region.label}\n aria-pressed={active}\n data-active={active ? \"true\" : \"false\"}\n onClick={() => setSelectedId(region.id)}\n onFocus={() => setSelectedId(region.id)}\n >\n <span>{region.label}</span>\n </button>\n );\n })}\n </div>\n </div>\n <div className=\"rt-tpl-concerns-side\">\n <div className=\"rt-tpl-concerns-button-list\" aria-label=\"문제 영역 목록\">\n {regions.map((region) => (\n <button\n key={region.id}\n type=\"button\"\n className=\"rt-tpl-concerns-list-button\"\n data-active={region.id === selected.id ? \"true\" : \"false\"}\n aria-pressed={region.id === selected.id}\n onClick={() => setSelectedId(region.id)}\n >\n {region.label}\n </button>\n ))}\n </div>\n <ConcernRegionPanel region={selected} />\n </div>\n </div>\n );\n}\n\nfunction ConcernRegionPanel({\n region,\n}: {\n readonly region: ConcernRegion;\n}) {\n return (\n <article className=\"rt-tpl-concerns-panel\">\n <p className=\"rt-tpl-concerns-label\">{region.label}</p>\n <h3 className=\"rt-tpl-concerns-title\">{region.title}</h3>\n <p className=\"rt-tpl-concerns-desc\">{region.description}</p>\n {region.items ? (\n <ul className=\"rt-tpl-concerns-items\">\n {region.items.map((item) => (\n <li key={item}>{item}</li>\n ))}\n </ul>\n ) : null}\n {region.href ? (\n <a className=\"rt-tpl-concerns-cta\" href={region.href}>\n {region.ctaLabel ?? \"자세히 보기\"}\n </a>\n ) : null}\n </article>\n );\n}\n"],"mappings":";AAEA,SAAS,gBAAgB;AAuBnB,SACE,KADF;AAjBC,SAAS,kBAAkB;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,QAAM,CAAC,YAAY,aAAa,IAAI,SAAS,QAAQ,CAAC,GAAG,MAAM,EAAE;AACjE,QAAM,WACJ,QAAQ,KAAK,CAAC,WAAW,OAAO,OAAO,UAAU,KAAK,QAAQ,CAAC;AAEjE,MAAI,CAAC,SAAU,QAAO;AAEtB,SACE,qBAAC,SAAI,WAAU,8BACb;AAAA,yBAAC,SAAI,WAAU,+BACb;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,WAAU;AAAA,UACV,KAAK;AAAA,UACL,KAAK;AAAA,UACL,SAAQ;AAAA;AAAA,MACV;AAAA,MACA,oBAAC,SAAI,WAAU,4BAA2B,cAAW,gDAClD,kBAAQ,IAAI,CAAC,WAAW;AACvB,cAAM,SAAS,OAAO,OAAO,SAAS;AACtC,eACE;AAAA,UAAC;AAAA;AAAA,YAEC,MAAK;AAAA,YACL,WAAU;AAAA,YACV,OAAO;AAAA,cACL,MAAM,GAAG,OAAO,KAAK,EAAE;AAAA,cACvB,KAAK,GAAG,OAAO,KAAK,EAAE;AAAA,YACxB;AAAA,YACA,cAAY,OAAO;AAAA,YACnB,gBAAc;AAAA,YACd,eAAa,SAAS,SAAS;AAAA,YAC/B,SAAS,MAAM,cAAc,OAAO,EAAE;AAAA,YACtC,SAAS,MAAM,cAAc,OAAO,EAAE;AAAA,YAEtC,8BAAC,UAAM,iBAAO,OAAM;AAAA;AAAA,UAbf,OAAO;AAAA,QAcd;AAAA,MAEJ,CAAC,GACH;AAAA,OACF;AAAA,IACA,qBAAC,SAAI,WAAU,wBACb;AAAA,0BAAC,SAAI,WAAU,+BAA8B,cAAW,0CACrD,kBAAQ,IAAI,CAAC,WACZ;AAAA,QAAC;AAAA;AAAA,UAEC,MAAK;AAAA,UACL,WAAU;AAAA,UACV,eAAa,OAAO,OAAO,SAAS,KAAK,SAAS;AAAA,UAClD,gBAAc,OAAO,OAAO,SAAS;AAAA,UACrC,SAAS,MAAM,cAAc,OAAO,EAAE;AAAA,UAErC,iBAAO;AAAA;AAAA,QAPH,OAAO;AAAA,MAQd,CACD,GACH;AAAA,MACA,oBAAC,sBAAmB,QAAQ,UAAU;AAAA,OACxC;AAAA,KACF;AAEJ;AAEA,SAAS,mBAAmB;AAAA,EAC1B;AACF,GAEG;AACD,SACE,qBAAC,aAAQ,WAAU,yBACjB;AAAA,wBAAC,OAAE,WAAU,yBAAyB,iBAAO,OAAM;AAAA,IACnD,oBAAC,QAAG,WAAU,yBAAyB,iBAAO,OAAM;AAAA,IACpD,oBAAC,OAAE,WAAU,wBAAwB,iBAAO,aAAY;AAAA,IACvD,OAAO,QACN,oBAAC,QAAG,WAAU,yBACX,iBAAO,MAAM,IAAI,CAAC,SACjB,oBAAC,QAAe,kBAAP,IAAY,CACtB,GACH,IACE;AAAA,IACH,OAAO,OACN,oBAAC,OAAE,WAAU,uBAAsB,MAAM,OAAO,MAC7C,iBAAO,YAAY,mCACtB,IACE;AAAA,KACN;AAEJ;","names":[]}
@@ -0,0 +1,53 @@
1
+ "use client";
2
+
3
+ // src/templates/spaces-client.tsx
4
+ import { useState } from "react";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
+ function SpacesStage({
7
+ spaces
8
+ }) {
9
+ const [selectedId, setSelectedId] = useState(spaces[0]?.id ?? "");
10
+ const selected = spaces.find((space) => space.id === selectedId) ?? spaces[0];
11
+ if (!selected) return null;
12
+ return /* @__PURE__ */ jsxs("div", { className: "rt-tpl-spaces-stage", children: [
13
+ /* @__PURE__ */ jsxs("figure", { className: "rt-tpl-spaces-main", children: [
14
+ /* @__PURE__ */ jsx(
15
+ "img",
16
+ {
17
+ className: "rt-tpl-spaces-main-img",
18
+ src: selected.imageSrc,
19
+ alt: selected.imageAlt,
20
+ loading: "lazy"
21
+ }
22
+ ),
23
+ /* @__PURE__ */ jsxs("figcaption", { className: "rt-tpl-spaces-caption", children: [
24
+ selected.kicker ? /* @__PURE__ */ jsx("span", { className: "rt-tpl-spaces-kicker", children: selected.kicker }) : null,
25
+ /* @__PURE__ */ jsx("span", { className: "rt-tpl-spaces-title", children: selected.title ?? selected.label }),
26
+ selected.description ? /* @__PURE__ */ jsx("span", { className: "rt-tpl-spaces-desc", children: selected.description }) : null
27
+ ] })
28
+ ] }),
29
+ /* @__PURE__ */ jsx("div", { className: "rt-tpl-spaces-rail", "aria-label": "\uACF5\uAC04 \uC120\uD0DD", children: spaces.map((space) => {
30
+ const active = space.id === selected.id;
31
+ return /* @__PURE__ */ jsxs(
32
+ "button",
33
+ {
34
+ type: "button",
35
+ className: "rt-tpl-spaces-thumb",
36
+ "data-active": active ? "true" : "false",
37
+ "aria-pressed": active,
38
+ onClick: () => setSelectedId(space.id),
39
+ children: [
40
+ /* @__PURE__ */ jsx("img", { src: space.imageSrc, alt: "", loading: "lazy" }),
41
+ /* @__PURE__ */ jsx("span", { children: space.label })
42
+ ]
43
+ },
44
+ space.id
45
+ );
46
+ }) })
47
+ ] });
48
+ }
49
+
50
+ export {
51
+ SpacesStage
52
+ };
53
+ //# sourceMappingURL=chunk-K62RR4UQ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/templates/spaces-client.tsx"],"sourcesContent":["\"use client\";\n\nimport { useState } from \"react\";\n\nimport type { SpacesSection } from \"./site-config.js\";\n\ntype SpaceItem = SpacesSection[\"props\"][\"spaces\"][number];\n\nexport function SpacesStage({\n spaces,\n}: {\n readonly spaces: readonly SpaceItem[];\n}) {\n const [selectedId, setSelectedId] = useState(spaces[0]?.id ?? \"\");\n const selected = spaces.find((space) => space.id === selectedId) ?? spaces[0];\n\n if (!selected) return null;\n\n return (\n <div className=\"rt-tpl-spaces-stage\">\n <figure className=\"rt-tpl-spaces-main\">\n <img\n className=\"rt-tpl-spaces-main-img\"\n src={selected.imageSrc}\n alt={selected.imageAlt}\n loading=\"lazy\"\n />\n <figcaption className=\"rt-tpl-spaces-caption\">\n {selected.kicker ? (\n <span className=\"rt-tpl-spaces-kicker\">{selected.kicker}</span>\n ) : null}\n <span className=\"rt-tpl-spaces-title\">\n {selected.title ?? selected.label}\n </span>\n {selected.description ? (\n <span className=\"rt-tpl-spaces-desc\">{selected.description}</span>\n ) : null}\n </figcaption>\n </figure>\n <div className=\"rt-tpl-spaces-rail\" aria-label=\"공간 선택\">\n {spaces.map((space) => {\n const active = space.id === selected.id;\n return (\n <button\n key={space.id}\n type=\"button\"\n className=\"rt-tpl-spaces-thumb\"\n data-active={active ? \"true\" : \"false\"}\n aria-pressed={active}\n onClick={() => setSelectedId(space.id)}\n >\n <img src={space.imageSrc} alt=\"\" loading=\"lazy\" />\n <span>{space.label}</span>\n </button>\n );\n })}\n </div>\n </div>\n );\n}\n"],"mappings":";AAEA,SAAS,gBAAgB;AAmBjB,cAMA,YANA;AAbD,SAAS,YAAY;AAAA,EAC1B;AACF,GAEG;AACD,QAAM,CAAC,YAAY,aAAa,IAAI,SAAS,OAAO,CAAC,GAAG,MAAM,EAAE;AAChE,QAAM,WAAW,OAAO,KAAK,CAAC,UAAU,MAAM,OAAO,UAAU,KAAK,OAAO,CAAC;AAE5E,MAAI,CAAC,SAAU,QAAO;AAEtB,SACE,qBAAC,SAAI,WAAU,uBACb;AAAA,yBAAC,YAAO,WAAU,sBAChB;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,WAAU;AAAA,UACV,KAAK,SAAS;AAAA,UACd,KAAK,SAAS;AAAA,UACd,SAAQ;AAAA;AAAA,MACV;AAAA,MACA,qBAAC,gBAAW,WAAU,yBACnB;AAAA,iBAAS,SACR,oBAAC,UAAK,WAAU,wBAAwB,mBAAS,QAAO,IACtD;AAAA,QACJ,oBAAC,UAAK,WAAU,uBACb,mBAAS,SAAS,SAAS,OAC9B;AAAA,QACC,SAAS,cACR,oBAAC,UAAK,WAAU,sBAAsB,mBAAS,aAAY,IACzD;AAAA,SACN;AAAA,OACF;AAAA,IACA,oBAAC,SAAI,WAAU,sBAAqB,cAAW,6BAC5C,iBAAO,IAAI,CAAC,UAAU;AACrB,YAAM,SAAS,MAAM,OAAO,SAAS;AACrC,aACE;AAAA,QAAC;AAAA;AAAA,UAEC,MAAK;AAAA,UACL,WAAU;AAAA,UACV,eAAa,SAAS,SAAS;AAAA,UAC/B,gBAAc;AAAA,UACd,SAAS,MAAM,cAAc,MAAM,EAAE;AAAA,UAErC;AAAA,gCAAC,SAAI,KAAK,MAAM,UAAU,KAAI,IAAG,SAAQ,QAAO;AAAA,YAChD,oBAAC,UAAM,gBAAM,OAAM;AAAA;AAAA;AAAA,QARd,MAAM;AAAA,MASb;AAAA,IAEJ,CAAC,GACH;AAAA,KACF;AAEJ;","names":[]}