@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.
- package/CHANGELOG.md +54 -0
- package/dist/before-after-client.d.ts +15 -0
- package/dist/before-after-client.js +8 -0
- package/dist/before-after-client.js.map +1 -0
- package/dist/chunk-4LQPZDXZ.js +75 -0
- package/dist/chunk-4LQPZDXZ.js.map +1 -0
- package/dist/chunk-6GQCFFHU.js +540 -0
- package/dist/chunk-6GQCFFHU.js.map +1 -0
- package/dist/chunk-F3G6RVIR.js +220 -0
- package/dist/chunk-F3G6RVIR.js.map +1 -0
- package/dist/chunk-H46JUIHZ.js +79 -0
- package/dist/chunk-H46JUIHZ.js.map +1 -0
- package/dist/chunk-K62RR4UQ.js +53 -0
- package/dist/chunk-K62RR4UQ.js.map +1 -0
- package/dist/chunk-NUUTTSGT.js +916 -0
- package/dist/chunk-NUUTTSGT.js.map +1 -0
- package/dist/chunk-RSNUZ4BF.js +3378 -0
- package/dist/chunk-RSNUZ4BF.js.map +1 -0
- package/dist/chunk-U5AKACPN.js +113 -0
- package/dist/chunk-U5AKACPN.js.map +1 -0
- package/dist/chunk-WWS43XCT.js +206 -0
- package/dist/chunk-WWS43XCT.js.map +1 -0
- package/dist/cms-public.css +164 -51
- package/dist/concerns-client.d.ts +12 -0
- package/dist/concerns-client.js +8 -0
- package/dist/concerns-client.js.map +1 -0
- package/dist/expertise-client.d.ts +10 -0
- package/dist/expertise-client.js +8 -0
- package/dist/expertise-client.js.map +1 -0
- package/dist/hero-carousel-client.d.ts +36 -0
- package/dist/hero-carousel-client.js +8 -0
- package/dist/hero-carousel-client.js.map +1 -0
- package/dist/routes.d.ts +51 -5
- package/dist/routes.js +109 -62
- package/dist/routes.js.map +1 -1
- package/dist/seo-checks.d.ts +87 -0
- package/dist/seo-checks.js +27 -0
- package/dist/seo-checks.js.map +1 -0
- package/dist/server.d.ts +51 -2
- package/dist/server.js +8 -2
- package/dist/site-config-DgYGGwu1.d.ts +3910 -0
- package/dist/spaces-client.d.ts +10 -0
- package/dist/spaces-client.js +8 -0
- package/dist/spaces-client.js.map +1 -0
- package/dist/templates.css +3246 -194
- package/dist/templates.d.ts +20 -1028
- package/dist/templates.js +70 -2
- package/dist/templates.js.map +1 -1
- package/dist/{theme-css-vars-BiyHBsJG.d.ts → theme-css-vars-CD3jcEkp.d.ts} +2 -2
- package/dist/ui-react-motion-client.d.ts +73 -0
- package/dist/ui-react-motion-client.js +14 -0
- package/dist/ui-react-motion-client.js.map +1 -0
- package/dist/validate.d.ts +15 -1
- package/dist/validate.js +806 -17
- package/dist/validate.js.map +1 -1
- package/package.json +18 -6
- package/dist/chunk-GOHP7RWD.js +0 -1647
- package/dist/chunk-GOHP7RWD.js.map +0 -1
- package/dist/chunk-IOYBEH6E.js +0 -271
- 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":[]}
|