@roottale/cms-renderer-next 0.43.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 (48) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +4 -0
  3. package/dist/{chunk-FKDCPFYV.js → chunk-45KYMMDY.js} +156 -21
  4. package/dist/chunk-45KYMMDY.js.map +1 -0
  5. package/dist/{chunk-4LQPZDXZ.js → chunk-6MFHA555.js} +24 -6
  6. package/dist/chunk-6MFHA555.js.map +1 -0
  7. package/dist/chunk-FZK3EXYZ.js +103 -0
  8. package/dist/chunk-FZK3EXYZ.js.map +1 -0
  9. package/dist/{chunk-NUUTTSGT.js → chunk-GUV5WHD5.js} +37 -3
  10. package/dist/chunk-GUV5WHD5.js.map +1 -0
  11. package/dist/chunk-LEHHSI6G.js +64 -0
  12. package/dist/chunk-LEHHSI6G.js.map +1 -0
  13. package/dist/chunk-OMXEU4LS.js +414 -0
  14. package/dist/chunk-OMXEU4LS.js.map +1 -0
  15. package/dist/chunk-YLSQAT6B.js +59 -0
  16. package/dist/chunk-YLSQAT6B.js.map +1 -0
  17. package/dist/{chunk-WWS43XCT.js → chunk-Z3ZSCPG4.js} +82 -20
  18. package/dist/chunk-Z3ZSCPG4.js.map +1 -0
  19. package/dist/concerns-client.d.ts +1 -1
  20. package/dist/contact-map-client.d.ts +27 -0
  21. package/dist/contact-map-client.js +8 -0
  22. package/dist/contact-map-client.js.map +1 -0
  23. package/dist/expertise-client.d.ts +7 -1
  24. package/dist/expertise-client.js +2 -1
  25. package/dist/hero-carousel-client.d.ts +18 -2
  26. package/dist/hero-carousel-client.js +2 -1
  27. package/dist/routes.js +1 -1
  28. package/dist/server.d.ts +5 -0
  29. package/dist/server.js +8 -5
  30. package/dist/{site-config-DgYGGwu1.d.ts → site-config-kqurIH-k.d.ts} +42 -4
  31. package/dist/spaces-client.d.ts +1 -1
  32. package/dist/team-expanding-stage-client.d.ts +35 -0
  33. package/dist/team-expanding-stage-client.js +8 -0
  34. package/dist/team-expanding-stage-client.js.map +1 -0
  35. package/dist/templates.css +607 -5
  36. package/dist/templates.d.ts +30 -4
  37. package/dist/templates.js +13 -6
  38. package/dist/templates.js.map +1 -1
  39. package/dist/ui-react-motion-client.d.ts +1 -0
  40. package/dist/ui-react-motion-client.js +3 -1
  41. package/dist/validate.js +1 -1
  42. package/package.json +4 -4
  43. package/dist/chunk-4LQPZDXZ.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-FKDCPFYV.js.map +0 -1
  47. package/dist/chunk-NUUTTSGT.js.map +0 -1
  48. package/dist/chunk-WWS43XCT.js.map +0 -1
@@ -77,16 +77,77 @@ function AnimateOnScroll({
77
77
  );
78
78
  }
79
79
 
80
+ // ../ui-react/dist/components/kinetic/CountUpStat.js
81
+ import { createElement as createElement2, useEffect as useEffect2, useMemo, useRef, useState as useState2 } from "react";
82
+ import { useInView, useReducedMotion as useReducedMotion2 } from "motion/react";
83
+ var DEFAULT_LOCALE = "ko-KR";
84
+ var DEFAULT_DECIMALS = 0;
85
+ var DEFAULT_THRESHOLD = 0.4;
86
+ var LONG_DURATION_THRESHOLD = 1e3;
87
+ var SHORT_DURATION_MS = 1400;
88
+ var LONG_DURATION_MS = 2e3;
89
+ function easeOutQuart(progress) {
90
+ return 1 - (1 - progress) ** 4;
91
+ }
92
+ function defaultDurationFor(value) {
93
+ return Math.abs(value) >= LONG_DURATION_THRESHOLD ? LONG_DURATION_MS : SHORT_DURATION_MS;
94
+ }
95
+ function formatWithLocale(value, decimals, locale) {
96
+ return new Intl.NumberFormat(locale, {
97
+ minimumFractionDigits: decimals,
98
+ maximumFractionDigits: decimals
99
+ }).format(value);
100
+ }
101
+ function CountUpStat({ value, prefix = "", suffix = "", decimals = DEFAULT_DECIMALS, locale = DEFAULT_LOCALE, formatter, duration, easing = easeOutQuart, threshold = DEFAULT_THRESHOLD, as = "span", className, style }) {
102
+ const reducedMotion = useReducedMotion2();
103
+ const [mounted, setMounted] = useState2(false);
104
+ const ref = useRef(null);
105
+ const inView = useInView(ref, { once: true, amount: threshold });
106
+ const [displayValue, setDisplayValue] = useState2(0);
107
+ useEffect2(() => setMounted(true), []);
108
+ const format = useMemo(() => formatter ?? ((current) => formatWithLocale(current, decimals, locale)), [formatter, decimals, locale]);
109
+ useEffect2(() => {
110
+ if (reducedMotion || !mounted || !inView) {
111
+ return;
112
+ }
113
+ const durationMs = Math.max(0, duration ?? defaultDurationFor(value));
114
+ if (durationMs === 0) {
115
+ setDisplayValue(value);
116
+ return;
117
+ }
118
+ const startedAt = performance.now();
119
+ let frameId = 0;
120
+ function tick(now) {
121
+ const progress = Math.min((now - startedAt) / durationMs, 1);
122
+ setDisplayValue(value * easing(progress));
123
+ if (progress < 1) {
124
+ frameId = requestAnimationFrame(tick);
125
+ } else {
126
+ setDisplayValue(value);
127
+ }
128
+ }
129
+ frameId = requestAnimationFrame(tick);
130
+ return () => cancelAnimationFrame(frameId);
131
+ }, [duration, easing, inView, mounted, reducedMotion, value]);
132
+ const finalText = `${prefix}${format(value)}${suffix}`;
133
+ const wrapperStyle = { fontVariantNumeric: "tabular-nums", ...style };
134
+ const showAnimated = !reducedMotion && mounted;
135
+ if (!showAnimated) {
136
+ return createElement2(as, { ref, className, style: wrapperStyle }, finalText);
137
+ }
138
+ return createElement2(as, { ref, "aria-label": finalText, className, style: wrapperStyle }, createElement2("span", { "aria-hidden": true }, `${prefix}${format(displayValue)}${suffix}`));
139
+ }
140
+
80
141
  // ../ui-react/src/components/motion/Marquee.tsx
81
142
  import {
82
- useEffect as useEffect2,
83
- useState as useState2
143
+ useEffect as useEffect3,
144
+ useState as useState3
84
145
  } from "react";
85
146
  import {
86
147
  motion as motion2,
87
148
  useAnimationFrame,
88
149
  useMotionValue,
89
- useReducedMotion as useReducedMotion2,
150
+ useReducedMotion as useReducedMotion3,
90
151
  useTransform
91
152
  } from "motion/react";
92
153
  import { jsx as jsx2, jsxs } from "react/jsx-runtime";
@@ -98,10 +159,10 @@ function Marquee({
98
159
  pauseOnHover = true,
99
160
  ariaLabel
100
161
  }) {
101
- const reducedMotion = useReducedMotion2();
102
- const [mounted, setMounted] = useState2(false);
103
- const [paused, setPaused] = useState2(false);
104
- useEffect2(() => setMounted(true), []);
162
+ const reducedMotion = useReducedMotion3();
163
+ const [mounted, setMounted] = useState3(false);
164
+ const [paused, setPaused] = useState3(false);
165
+ useEffect3(() => setMounted(true), []);
105
166
  const x = useMotionValue(0);
106
167
  const transform = useTransform(x, (v) => `translateX(${v}%)`);
107
168
  useAnimationFrame((_t, delta) => {
@@ -151,11 +212,11 @@ function Marquee({
151
212
 
152
213
  // ../ui-react/src/components/scroll/StaggerGroup.tsx
153
214
  import {
154
- useEffect as useEffect3,
155
- createElement as createElement2,
156
- useState as useState3
215
+ useEffect as useEffect4,
216
+ createElement as createElement3,
217
+ useState as useState4
157
218
  } from "react";
158
- import { motion as motion3, useReducedMotion as useReducedMotion3 } from "motion/react";
219
+ import { motion as motion3, useReducedMotion as useReducedMotion4 } from "motion/react";
159
220
  import { jsx as jsx3 } from "react/jsx-runtime";
160
221
  function StaggerGroup({
161
222
  children,
@@ -166,11 +227,11 @@ function StaggerGroup({
166
227
  once = true,
167
228
  margin = DEFAULT_VIEWPORT_MARGIN
168
229
  }) {
169
- const reducedMotion = useReducedMotion3();
170
- const [mounted, setMounted] = useState3(false);
171
- useEffect3(() => setMounted(true), []);
230
+ const reducedMotion = useReducedMotion4();
231
+ const [mounted, setMounted] = useState4(false);
232
+ useEffect4(() => setMounted(true), []);
172
233
  if (reducedMotion || !mounted) {
173
- return createElement2(as, { className, style }, children);
234
+ return createElement3(as, { className, style }, children);
174
235
  }
175
236
  const MotionTag = motion3[as];
176
237
  return /* @__PURE__ */ jsx3(
@@ -187,11 +248,11 @@ function StaggerGroup({
187
248
  );
188
249
  }
189
250
  function StaggerItem({ children, className, style, as = "div" }) {
190
- const reducedMotion = useReducedMotion3();
191
- const [mounted, setMounted] = useState3(false);
192
- useEffect3(() => setMounted(true), []);
251
+ const reducedMotion = useReducedMotion4();
252
+ const [mounted, setMounted] = useState4(false);
253
+ useEffect4(() => setMounted(true), []);
193
254
  if (reducedMotion || !mounted) {
194
- return createElement2(as, { className, style }, children);
255
+ return createElement3(as, { className, style }, children);
195
256
  }
196
257
  const MotionTag = motion3[as];
197
258
  return /* @__PURE__ */ jsx3(MotionTag, { className, style, variants: staggerItem, children });
@@ -199,8 +260,9 @@ function StaggerItem({ children, className, style, as = "div" }) {
199
260
 
200
261
  export {
201
262
  AnimateOnScroll,
263
+ CountUpStat,
202
264
  Marquee,
203
265
  StaggerGroup,
204
266
  StaggerItem
205
267
  };
206
- //# sourceMappingURL=chunk-WWS43XCT.js.map
268
+ //# sourceMappingURL=chunk-Z3ZSCPG4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../ui-react/src/components/scroll/AnimateOnScroll.tsx","../../ui-react/src/components/motion/variants.ts","../../ui-react/dist/components/kinetic/CountUpStat.js","../../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 * `CountUpStat` — 뷰포트 진입 시 목표값까지 rAF 카운트업(ddd `kinetic`).\n *\n * 스펙 원본(ADR-0086 P2-크래프트 D3 — chambarun 목업 자체 구현 채택):\n * - `clients/chambarun/mockup/assets/site.js` 89~106행 — `IntersectionObserver`\n * 진입(threshold .4) 1회 트리거 → `requestAnimationFrame` 루프 + ease-out\n * quartic(`1-(1-t)**4`)으로 감속하며 목표값에 안착. 큰 값(≥1000)은 더 오래\n * (2000ms), 작은 값은 1400ms — 이 기본 지속시간 산식을 그대로 이식했다.\n * - `clients/chambarun/DESIGN.md` proof/counter 계약 — \"뷰포트 진입 1회 실행,\n * 이징 곡선\", `prefers-reduced-motion` 즉시 최종값.\n *\n * 접근성(정적 텍스트 + 시각 애니메이션 분리, aria-live 미사용):\n * 카운트가 진행되는 동안에도 outer 엘리먼트의 `aria-label` 은 항상 완성된\n * 최종 문자열이라 스크린리더는 애니메이션 중간값을 절대 읽지 않는다. 실제로\n * 숫자가 올라가는 시각 표현은 `aria-hidden` 내부 span 이 전담한다 —\n * `kinetic/utils.ts` 의 `animatedTextElement`/`staticTextElement` 와 동일한\n * 원칙이나, 이 컴포넌트는 `useInView` 관찰 대상 `ref` 가 필요해 그 헬퍼를\n * 그대로 재사용하지 않고 동일 패턴을 직접 구현한다.\n *\n * 마운트 게이팅은 `RevealText`/`ScrambleText`/`ScrollRevealText` 와 동일 원칙\n * (M6) — SSR·하이드레이션 전·모션 실패 시에도 항상 최종값이 보인다.\n *\n * CLS 방지: `font-variant-numeric: tabular-nums` 는 상속 속성이라 outer\n * wrapper 에 한 번만 걸어도 내부 텍스트까지 자릿수 폭이 고정된다(ui-css 클래스\n * 신설 불필요 — kinetic 형제 컴포넌트들도 전부 인라인 스타일만 쓰는 관습).\n */\nimport { createElement, useEffect, useMemo, useRef, useState, } from \"react\";\nimport { useInView, useReducedMotion } from \"motion/react\";\nconst DEFAULT_LOCALE = \"ko-KR\";\nconst DEFAULT_DECIMALS = 0;\nconst DEFAULT_THRESHOLD = 0.4;\n/** 스펙 원본 site.js 100행 — 목표값이 이 값 이상이면 더 오래 카운트한다. */\nconst LONG_DURATION_THRESHOLD = 1000;\nconst SHORT_DURATION_MS = 1400;\nconst LONG_DURATION_MS = 2000;\nfunction easeOutQuart(progress) {\n return 1 - (1 - progress) ** 4;\n}\nfunction defaultDurationFor(value) {\n return Math.abs(value) >= LONG_DURATION_THRESHOLD ? LONG_DURATION_MS : SHORT_DURATION_MS;\n}\nfunction formatWithLocale(value, decimals, locale) {\n return new Intl.NumberFormat(locale, {\n minimumFractionDigits: decimals,\n maximumFractionDigits: decimals,\n }).format(value);\n}\nexport function CountUpStat({ value, prefix = \"\", suffix = \"\", decimals = DEFAULT_DECIMALS, locale = DEFAULT_LOCALE, formatter, duration, easing = easeOutQuart, threshold = DEFAULT_THRESHOLD, as = \"span\", className, style, }) {\n const reducedMotion = useReducedMotion();\n const [mounted, setMounted] = useState(false);\n const ref = useRef(null);\n const inView = useInView(ref, { once: true, amount: threshold });\n const [displayValue, setDisplayValue] = useState(0);\n useEffect(() => setMounted(true), []);\n const format = useMemo(() => formatter ?? ((current) => formatWithLocale(current, decimals, locale)), [formatter, decimals, locale]);\n useEffect(() => {\n if (reducedMotion || !mounted || !inView) {\n return;\n }\n const durationMs = Math.max(0, duration ?? defaultDurationFor(value));\n if (durationMs === 0) {\n setDisplayValue(value);\n return;\n }\n const startedAt = performance.now();\n let frameId = 0;\n function tick(now) {\n const progress = Math.min((now - startedAt) / durationMs, 1);\n setDisplayValue(value * easing(progress));\n if (progress < 1) {\n frameId = requestAnimationFrame(tick);\n }\n else {\n setDisplayValue(value);\n }\n }\n frameId = requestAnimationFrame(tick);\n return () => cancelAnimationFrame(frameId);\n }, [duration, easing, inView, mounted, reducedMotion, value]);\n const finalText = `${prefix}${format(value)}${suffix}`;\n const wrapperStyle = { fontVariantNumeric: \"tabular-nums\", ...style };\n // `ref` 는 두 분기 모두에서 항상 붙인다 — reduced-motion/pre-mount 분기에서만\n // 빠뜨리면 `useInView` 내부 effect 가 최초 커밋 시 `ref.current` 를 null 로\n // 캡처한 뒤 deps(ref 객체 자체는 항상 동일 identity)가 안 바뀌어 재구독을\n // 영영 못 하는 버그가 된다(mounted 전환 후에도 IntersectionObserver 자체가\n // 생성되지 않음). 항상 붙여서 최초 커밋부터 관찰이 성립하게 한다.\n const showAnimated = !reducedMotion && mounted;\n if (!showAnimated) {\n return createElement(as, { ref, className, style: wrapperStyle }, finalText);\n }\n return createElement(as, { ref, \"aria-label\": finalText, className, style: wrapperStyle }, createElement(\"span\", { \"aria-hidden\": true }, `${prefix}${format(displayValue)}${suffix}`));\n}\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":["createElement","useEffect","useState","useReducedMotion","useEffect","useState","motion","useReducedMotion","jsx","useEffect","createElement","useState","motion","useReducedMotion","jsx","useReducedMotion","useState","useEffect","createElement","motion"]}
@@ -1,5 +1,5 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { C as ConcernsSection } from './site-config-DgYGGwu1.js';
2
+ import { C as ConcernsSection } from './site-config-kqurIH-k.js';
3
3
  import 'zod';
4
4
 
5
5
  type ConcernRegion = ConcernsSection["props"]["regions"][number];
@@ -0,0 +1,27 @@
1
+ import { ReactElement } from 'react';
2
+
3
+ /**
4
+ * `contact` 지도 블록의 "클릭-투-로드" 모드(ADR-0086 P2 D3) — chambarun
5
+ * mockup `.map-facade`/`site.js` "지도: 클릭-투-로드" 이식
6
+ * (`clients/chambarun/mockup/assets/site.js` "지도: 클릭-투-로드" 블록).
7
+ *
8
+ * 원본은 DOM 을 직접 갈아끼우는 vanilla 스크립트(`replaceWith`)였다 — 여기서는
9
+ * React state 로 같은 효과(정적 프리뷰 → 클릭 시 1회 iframe 마운트)를 낸다.
10
+ * `ContactMapBlock`(sections.tsx, RSC)이 embedUrl 이 allowlist 를 통과했을
11
+ * 때만 이 컴포넌트를 렌더하므로, 이 컴포넌트 자신은 allowlist 재검증을 하지
12
+ * 않는다(호출자 계약).
13
+ *
14
+ * 빌드: tsup 독립 entry(`contact-map-client`, tsup.config.ts) — "use client"
15
+ * 경계가 `templates.js`(서버 전용)로 새지 않게 격리(hero-carousel-client 류와
16
+ * 동일 계약).
17
+ */
18
+
19
+ interface ContactMapClickToLoadProps {
20
+ readonly embedUrl: string;
21
+ readonly externalUrl: string | undefined;
22
+ readonly facadeImageSrc: string | undefined;
23
+ readonly facadeImageAlt: string | undefined;
24
+ }
25
+ declare function ContactMapClickToLoad({ embedUrl, externalUrl, facadeImageSrc, facadeImageAlt, }: ContactMapClickToLoadProps): ReactElement;
26
+
27
+ export { ContactMapClickToLoad, type ContactMapClickToLoadProps };
@@ -0,0 +1,8 @@
1
+ "use client";
2
+ import {
3
+ ContactMapClickToLoad
4
+ } from "./chunk-LEHHSI6G.js";
5
+ export {
6
+ ContactMapClickToLoad
7
+ };
8
+ //# sourceMappingURL=contact-map-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -1,8 +1,14 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { E as ExpertiseSection } from './site-config-DgYGGwu1.js';
2
+ import { E as ExpertiseSection } from './site-config-kqurIH-k.js';
3
3
  import 'zod';
4
4
 
5
5
  type ExpertiseItem = ExpertiseSection["props"]["items"][number];
6
+ /**
7
+ * `expertise` `tab-stage` variant — 좌측 탭 목록(항상 세로 1열, templates.css
8
+ * `.rt-tpl-expertise-tabs`) + 우측 상세 패널. 키보드 계약은 WAI-ARIA APG
9
+ * tablist 패턴(roving tabindex, 단일 tab stop) — `use-roving-tablist.ts` 공용
10
+ * hook 사용(D4, 적대 리뷰 R1 실측 지적 대응).
11
+ */
6
12
  declare function ExpertiseTabStage({ items, }: {
7
13
  readonly items: readonly ExpertiseItem[];
8
14
  }): react_jsx_runtime.JSX.Element | null;
@@ -1,7 +1,8 @@
1
1
  "use client";
2
2
  import {
3
3
  ExpertiseTabStage
4
- } from "./chunk-4LQPZDXZ.js";
4
+ } from "./chunk-6MFHA555.js";
5
+ import "./chunk-YLSQAT6B.js";
5
6
  export {
6
7
  ExpertiseTabStage
7
8
  };
@@ -1,5 +1,10 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
 
3
+ /**
4
+ * `hero` `carousel` variant 공유 타입 — client 아일랜드 서브모듈들이 순환
5
+ * import 없이 참조하는 단일 소스 (hero-carousel-client.tsx 400줄 상한 분할,
6
+ * 2026-06-24 지시 — `feedback_max_400_lines`).
7
+ */
3
8
  interface HeroCarouselSlideInput {
4
9
  readonly image: string;
5
10
  readonly alt: string;
@@ -25,12 +30,23 @@ interface HeroCarouselSlideInput {
25
30
  readonly source?: string;
26
31
  }[];
27
32
  }
33
+ /**
34
+ * 배경 전환 모션 — "variant=구조, motion=인상" 규칙(ADR-0086 P2): `carousel`
35
+ * variant 구조는 그대로, 인상만 갈아끼운다.
36
+ * - `slide`(기본): 기존 embla 가로 스크롤 전환.
37
+ * - `curtain`: clip-path 와이프 + 신 스윕 + 카피 스태거(chambarun 이식,
38
+ * `hero-carousel-curtain.tsx`).
39
+ */
40
+ type HeroCarouselTransition = "slide" | "curtain";
28
41
  interface HeroCarouselViewportProps {
29
42
  readonly slides: readonly HeroCarouselSlideInput[];
30
43
  readonly autoplay: boolean;
31
44
  readonly intervalMs: number;
32
45
  readonly scrim?: "none" | "soft" | "strong";
46
+ /** 기본 "slide" — 하위 호환(기존 고객 config 에 필드 없음). */
47
+ readonly transition?: HeroCarouselTransition;
33
48
  }
34
- declare function HeroCarouselViewport({ slides, autoplay, intervalMs, scrim, }: HeroCarouselViewportProps): react_jsx_runtime.JSX.Element;
35
49
 
36
- export { type HeroCarouselSlideInput, HeroCarouselViewport, type HeroCarouselViewportProps };
50
+ declare function HeroCarouselViewport({ slides, autoplay, intervalMs, scrim, transition, }: HeroCarouselViewportProps): react_jsx_runtime.JSX.Element;
51
+
52
+ export { type HeroCarouselSlideInput, type HeroCarouselTransition, HeroCarouselViewport, type HeroCarouselViewportProps };
@@ -1,7 +1,8 @@
1
1
  "use client";
2
2
  import {
3
3
  HeroCarouselViewport
4
- } from "./chunk-F3G6RVIR.js";
4
+ } from "./chunk-OMXEU4LS.js";
5
+ import "./chunk-YLSQAT6B.js";
5
6
  export {
6
7
  HeroCarouselViewport
7
8
  };
package/dist/routes.js CHANGED
@@ -669,7 +669,7 @@ function createFleetInfoRoute(config = {}) {
669
669
  site: config.site ?? env.VERCEL_PROJECT_PRODUCTION_URL ?? null,
670
670
  framework: "next",
671
671
  cms_client: CMS_CLIENT_VERSION,
672
- cms_renderer: true ? "0.43.0" : "dev",
672
+ cms_renderer: true ? "0.45.0" : "dev",
673
673
  build_sha: env.VERCEL_GIT_COMMIT_SHA?.slice(0, 7) ?? null
674
674
  },
675
675
  { headers: { "cache-control": "public, max-age=300" } }
package/dist/server.d.ts CHANGED
@@ -373,6 +373,11 @@ type RootTaleBlogPostProps = {
373
373
  baseUrl?: string;
374
374
  /** Rendered when the slug is not found or not published. */
375
375
  notFoundElement?: ReactElement;
376
+ /**
377
+ * 글 제목 `<h1>` 표시 여부. 페이지가 제목을 직접 출력할 때는 false.
378
+ * 요약·발행일·작성자 등 나머지 헤더 정보는 그대로 유지한다.
379
+ */
380
+ showTitle?: boolean;
376
381
  /**
377
382
  * When true, prepends an auto-derived `<RootTaleTableOfContents>` before
378
383
  * the article body (H2-H4 headings only). Default: false — keep markup
package/dist/server.js CHANGED
@@ -17,20 +17,23 @@ import {
17
17
  renderBlocks,
18
18
  renderBoundBlocks,
19
19
  themeToCssVars
20
- } from "./chunk-FKDCPFYV.js";
20
+ } from "./chunk-45KYMMDY.js";
21
21
  import "./chunk-U5AKACPN.js";
22
- import "./chunk-4LQPZDXZ.js";
22
+ import "./chunk-LEHHSI6G.js";
23
+ import "./chunk-6MFHA555.js";
24
+ import "./chunk-FZK3EXYZ.js";
23
25
  import "./chunk-K62RR4UQ.js";
24
26
  import "./chunk-H46JUIHZ.js";
25
- import "./chunk-WWS43XCT.js";
27
+ import "./chunk-Z3ZSCPG4.js";
26
28
  import {
27
29
  collectCategories,
28
30
  collectCategoriesWithCounts,
29
31
  defaultCategoryHref,
30
32
  filterPostsByCategory
31
33
  } from "./chunk-FUA5H6JE.js";
32
- import "./chunk-NUUTTSGT.js";
33
- import "./chunk-F3G6RVIR.js";
34
+ import "./chunk-GUV5WHD5.js";
35
+ import "./chunk-OMXEU4LS.js";
36
+ import "./chunk-YLSQAT6B.js";
34
37
  export {
35
38
  RenderTiptap,
36
39
  RootTaleBlogCategories,
@@ -72,7 +72,7 @@ declare const SECTION_VARIANTS: {
72
72
  readonly pricing: readonly ["table", "cards"];
73
73
  readonly process: readonly ["horizontal", "vertical", "timeline-marked"];
74
74
  readonly "before-after": readonly ["slider", "side-by-side"];
75
- readonly testimonials: readonly ["editorial-quote", "quote-rail"];
75
+ readonly testimonials: readonly ["editorial-quote", "quote-rail", "star-compact"];
76
76
  readonly team: readonly ["grid", "spotlight", "expanding-stage", "row"];
77
77
  readonly expertise: readonly ["tab-stage", "cards"];
78
78
  readonly spaces: readonly ["stage", "mosaic"];
@@ -214,6 +214,13 @@ declare const HeroSectionSchema: z.ZodEffects<z.ZodObject<{
214
214
  autoplay: z.ZodOptional<z.ZodBoolean>;
215
215
  /** `carousel` 전용, ms. 기본 6000. */
216
216
  intervalMs: z.ZodOptional<z.ZodNumber>;
217
+ /**
218
+ * `carousel` 전용 배경 전환 모션(ADR-0086 P2 D3). 기본 "slide"(기존 embla
219
+ * 가로 스크롤). "curtain" = clip-path 와이프 + 신 스윕(chambarun 이식).
220
+ * variant 는 그대로 "carousel" — 신규 variant 아님("variant=구조,
221
+ * motion=인상" 규칙).
222
+ */
223
+ transition: z.ZodOptional<z.ZodEnum<["slide", "curtain"]>>;
217
224
  proofMetrics: z.ZodOptional<z.ZodObject<{
218
225
  items: z.ZodArray<z.ZodObject<{
219
226
  value: z.ZodString;
@@ -300,6 +307,7 @@ declare const HeroSectionSchema: z.ZodEffects<z.ZodObject<{
300
307
  }[] | undefined;
301
308
  autoplay?: boolean | undefined;
302
309
  intervalMs?: number | undefined;
310
+ transition?: "slide" | "curtain" | undefined;
303
311
  proofMetrics?: {
304
312
  position: "overlay" | "inline";
305
313
  items: {
@@ -352,6 +360,7 @@ declare const HeroSectionSchema: z.ZodEffects<z.ZodObject<{
352
360
  }[] | undefined;
353
361
  autoplay?: boolean | undefined;
354
362
  intervalMs?: number | undefined;
363
+ transition?: "slide" | "curtain" | undefined;
355
364
  proofMetrics?: {
356
365
  items: {
357
366
  value: string;
@@ -408,6 +417,7 @@ declare const HeroSectionSchema: z.ZodEffects<z.ZodObject<{
408
417
  }[] | undefined;
409
418
  autoplay?: boolean | undefined;
410
419
  intervalMs?: number | undefined;
420
+ transition?: "slide" | "curtain" | undefined;
411
421
  proofMetrics?: {
412
422
  position: "overlay" | "inline";
413
423
  items: {
@@ -465,6 +475,7 @@ declare const HeroSectionSchema: z.ZodEffects<z.ZodObject<{
465
475
  }[] | undefined;
466
476
  autoplay?: boolean | undefined;
467
477
  intervalMs?: number | undefined;
478
+ transition?: "slide" | "curtain" | undefined;
468
479
  proofMetrics?: {
469
480
  items: {
470
481
  value: string;
@@ -524,6 +535,7 @@ declare const HeroSectionSchema: z.ZodEffects<z.ZodObject<{
524
535
  }[] | undefined;
525
536
  autoplay?: boolean | undefined;
526
537
  intervalMs?: number | undefined;
538
+ transition?: "slide" | "curtain" | undefined;
527
539
  proofMetrics?: {
528
540
  position: "overlay" | "inline";
529
541
  items: {
@@ -581,6 +593,7 @@ declare const HeroSectionSchema: z.ZodEffects<z.ZodObject<{
581
593
  }[] | undefined;
582
594
  autoplay?: boolean | undefined;
583
595
  intervalMs?: number | undefined;
596
+ transition?: "slide" | "curtain" | undefined;
584
597
  proofMetrics?: {
585
598
  items: {
586
599
  value: string;
@@ -2123,6 +2136,14 @@ declare const ContactSectionSchema: z.ZodEffects<z.ZodObject<{
2123
2136
  externalUrl: z.ZodOptional<z.ZodString>;
2124
2137
  lat: z.ZodOptional<z.ZodNumber>;
2125
2138
  lng: z.ZodOptional<z.ZodNumber>;
2139
+ /**
2140
+ * 지도 로드 모드(ADR-0086 P2 D3, chambarun `.map-facade` 클릭-투-로드 이식).
2141
+ * 기본 "lazy"(기존 동작 그대로 — 실지도면 즉시 iframe `loading="lazy"`).
2142
+ * "click" 은 정적 프리뷰(주소·지도 열기 버튼)만 먼저 그리고, 클릭 시에만
2143
+ * iframe 을 마운트한다(외부 지도 스크립트/네트워크 요청을 사용자 의도
2144
+ * 전까지 미루는 성능·프라이버시 절충 — additive, 기존 사이트는 영향 없음).
2145
+ */
2146
+ mapLoad: z.ZodOptional<z.ZodEnum<["lazy", "click"]>>;
2126
2147
  }, "strip", z.ZodTypeAny, {
2127
2148
  provider?: string | undefined;
2128
2149
  facadeImageSrc?: string | undefined;
@@ -2131,6 +2152,7 @@ declare const ContactSectionSchema: z.ZodEffects<z.ZodObject<{
2131
2152
  externalUrl?: string | undefined;
2132
2153
  lat?: number | undefined;
2133
2154
  lng?: number | undefined;
2155
+ mapLoad?: "lazy" | "click" | undefined;
2134
2156
  }, {
2135
2157
  provider?: string | undefined;
2136
2158
  facadeImageSrc?: string | undefined;
@@ -2139,6 +2161,7 @@ declare const ContactSectionSchema: z.ZodEffects<z.ZodObject<{
2139
2161
  externalUrl?: string | undefined;
2140
2162
  lat?: number | undefined;
2141
2163
  lng?: number | undefined;
2164
+ mapLoad?: "lazy" | "click" | undefined;
2142
2165
  }>>;
2143
2166
  actions: z.ZodOptional<z.ZodArray<z.ZodObject<{
2144
2167
  label: z.ZodString;
@@ -2195,6 +2218,7 @@ declare const ContactSectionSchema: z.ZodEffects<z.ZodObject<{
2195
2218
  externalUrl?: string | undefined;
2196
2219
  lat?: number | undefined;
2197
2220
  lng?: number | undefined;
2221
+ mapLoad?: "lazy" | "click" | undefined;
2198
2222
  } | undefined;
2199
2223
  heading?: string | undefined;
2200
2224
  phone?: string | undefined;
@@ -2234,6 +2258,7 @@ declare const ContactSectionSchema: z.ZodEffects<z.ZodObject<{
2234
2258
  externalUrl?: string | undefined;
2235
2259
  lat?: number | undefined;
2236
2260
  lng?: number | undefined;
2261
+ mapLoad?: "lazy" | "click" | undefined;
2237
2262
  } | undefined;
2238
2263
  heading?: string | undefined;
2239
2264
  phone?: string | undefined;
@@ -2277,6 +2302,7 @@ declare const ContactSectionSchema: z.ZodEffects<z.ZodObject<{
2277
2302
  externalUrl?: string | undefined;
2278
2303
  lat?: number | undefined;
2279
2304
  lng?: number | undefined;
2305
+ mapLoad?: "lazy" | "click" | undefined;
2280
2306
  } | undefined;
2281
2307
  heading?: string | undefined;
2282
2308
  phone?: string | undefined;
@@ -2321,6 +2347,7 @@ declare const ContactSectionSchema: z.ZodEffects<z.ZodObject<{
2321
2347
  externalUrl?: string | undefined;
2322
2348
  lat?: number | undefined;
2323
2349
  lng?: number | undefined;
2350
+ mapLoad?: "lazy" | "click" | undefined;
2324
2351
  } | undefined;
2325
2352
  heading?: string | undefined;
2326
2353
  phone?: string | undefined;
@@ -2367,6 +2394,7 @@ declare const ContactSectionSchema: z.ZodEffects<z.ZodObject<{
2367
2394
  externalUrl?: string | undefined;
2368
2395
  lat?: number | undefined;
2369
2396
  lng?: number | undefined;
2397
+ mapLoad?: "lazy" | "click" | undefined;
2370
2398
  } | undefined;
2371
2399
  heading?: string | undefined;
2372
2400
  phone?: string | undefined;
@@ -2411,6 +2439,7 @@ declare const ContactSectionSchema: z.ZodEffects<z.ZodObject<{
2411
2439
  externalUrl?: string | undefined;
2412
2440
  lat?: number | undefined;
2413
2441
  lng?: number | undefined;
2442
+ mapLoad?: "lazy" | "click" | undefined;
2414
2443
  } | undefined;
2415
2444
  heading?: string | undefined;
2416
2445
  phone?: string | undefined;
@@ -2991,7 +3020,7 @@ declare const TestimonialsSectionSchema: z.ZodObject<{
2991
3020
  theme: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
2992
3021
  } & {
2993
3022
  type: z.ZodLiteral<"testimonials">;
2994
- variant: z.ZodDefault<z.ZodEnum<["editorial-quote", "quote-rail"]>>;
3023
+ variant: z.ZodDefault<z.ZodEnum<["editorial-quote", "quote-rail", "star-compact"]>>;
2995
3024
  props: z.ZodObject<{
2996
3025
  heading: z.ZodString;
2997
3026
  description: z.ZodOptional<z.ZodString>;
@@ -3060,7 +3089,7 @@ declare const TestimonialsSectionSchema: z.ZodObject<{
3060
3089
  }>;
3061
3090
  }, "strip", z.ZodTypeAny, {
3062
3091
  type: "testimonials";
3063
- variant: "editorial-quote" | "quote-rail";
3092
+ variant: "editorial-quote" | "quote-rail" | "star-compact";
3064
3093
  props: {
3065
3094
  heading: string;
3066
3095
  items: {
@@ -3099,7 +3128,7 @@ declare const TestimonialsSectionSchema: z.ZodObject<{
3099
3128
  };
3100
3129
  theme?: Record<string, string> | undefined;
3101
3130
  id?: string | undefined;
3102
- variant?: "editorial-quote" | "quote-rail" | undefined;
3131
+ variant?: "editorial-quote" | "quote-rail" | "star-compact" | undefined;
3103
3132
  }>;
3104
3133
  declare const TeamSectionSchema: z.ZodObject<{
3105
3134
  id: z.ZodOptional<z.ZodString>;
@@ -3708,6 +3737,13 @@ type SectionInput = z.input<typeof HeroSectionSchema> | z.input<typeof RichTextS
3708
3737
  */
3709
3738
  declare const SectionSchema: z.ZodType<Section, z.ZodTypeDef, SectionInput>;
3710
3739
  declare const SiteConfigSchema: z.ZodObject<{
3740
+ /**
3741
+ * `SiteConfig` 계약 버전 (ADR-0086 D2) — 팩 manifest 의 `siteConfigVersion`
3742
+ * 과 비교해 팩↔renderer 호환성을 표현한다. optional — 하위 호환(기존 고객
3743
+ * `site.config.ts` 에는 이 필드가 없다). 미지정은 버전 1 로 해석한다
3744
+ * (`resolveSiteConfigSchemaVersion`).
3745
+ */
3746
+ schemaVersion: z.ZodOptional<z.ZodNumber>;
3711
3747
  site: z.ZodObject<{
3712
3748
  /** canonical origin. 생략 시 소비측이 `SITE_URL` env 등으로 해석. */
3713
3749
  url: z.ZodOptional<z.ZodString>;
@@ -3856,6 +3892,7 @@ declare const SiteConfigSchema: z.ZodObject<{
3856
3892
  } | undefined;
3857
3893
  };
3858
3894
  pages: Record<string, Section[]>;
3895
+ schemaVersion?: number | undefined;
3859
3896
  }, {
3860
3897
  site: {
3861
3898
  title: string;
@@ -3889,6 +3926,7 @@ declare const SiteConfigSchema: z.ZodObject<{
3889
3926
  label: string;
3890
3927
  }[] | undefined;
3891
3928
  }[] | undefined;
3929
+ schemaVersion?: number | undefined;
3892
3930
  }>;
3893
3931
  type SiteConfig = z.output<typeof SiteConfigSchema>;
3894
3932
  /** config 작성용 (defaults 생략 가능). */
@@ -1,5 +1,5 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { S as SpacesSection } from './site-config-DgYGGwu1.js';
2
+ import { S as SpacesSection } from './site-config-kqurIH-k.js';
3
3
  import 'zod';
4
4
 
5
5
  type SpaceItem = SpacesSection["props"]["spaces"][number];
@@ -0,0 +1,35 @@
1
+ import { ReactElement } from 'react';
2
+ import { T as TeamSection } from './site-config-kqurIH-k.js';
3
+ import 'zod';
4
+
5
+ type TeamMember = TeamSection["props"]["members"][number];
6
+ /**
7
+ * `team` `expanding-stage` variant 의 실모션 client island (D3 크래프트).
8
+ * chambarun `.staff-stage`/`.staff-panel` 스펙(mockup/assets/site.js·site.css)
9
+ * 이식 — hover/focus/tap 한 인물이 확장되며 자격 정보가 드러난다.
10
+ *
11
+ * 현재 `sections.tsx` `TemplateTeamExpandingStage` 는 이 컴포넌트를 아직
12
+ * import 하지 않는다(정적 격자만 렌더) — `sections.tsx` 는 이 작업 소유 밖이라
13
+ * 와이어링은 별도 보고 대상(README/보고 참조). 이 파일은 그 자리에 그대로
14
+ * 끼워 넣을 수 있도록 `members` 하나만 받는 독립 컴포넌트로 설계했다.
15
+ *
16
+ * 접근성(계약, DESIGN.md "Medical Staff Cards" 원칙 확장):
17
+ * - 각 패널은 `tabIndex=0` 컨테이너(hover/focus/click 시 확장) — chambarun
18
+ * 원본과 동일하게 명시적 role 은 두지 않는다(내부에 실제 CTA 링크가 있어
19
+ * `role="button"` 을 얹으면 중첩 인터랙티브 요소가 된다).
20
+ * - 자격정보·경력·CTA 는 접힌 상태에서도 DOM 에 그대로 존재한다(opacity 로만
21
+ * 가림) — 스크린리더는 hover 없이 전체 내용을 읽을 수 있다("decorative hover
22
+ * layers do not replace content").
23
+ * - CTA 링크는 비활성 패널일 때 `tabIndex=-1` — 보이지 않는 링크가 키보드
24
+ * 포커스를 가로채지 않는다(chambarun 원본에는 없던, React 상태로만 가능한
25
+ * 개선).
26
+ * - `prefers-reduced-motion: reduce` → 전환 자체가 CSS 에서 제거된다
27
+ * (templates.css, `@media (prefers-reduced-motion: no-preference)` 안에만
28
+ * transition 선언 — ADR-0073 K3 C1 과 동일 관행). `data-reduced-motion`
29
+ * 속성은 시각 회귀 테스트/QA 스크린샷 식별용.
30
+ */
31
+ declare function TeamExpandingStageClient({ members, }: {
32
+ readonly members: readonly TeamMember[];
33
+ }): ReactElement | null;
34
+
35
+ export { TeamExpandingStageClient };
@@ -0,0 +1,8 @@
1
+ "use client";
2
+ import {
3
+ TeamExpandingStageClient
4
+ } from "./chunk-FZK3EXYZ.js";
5
+ export {
6
+ TeamExpandingStageClient
7
+ };
8
+ //# sourceMappingURL=team-expanding-stage-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}