@roottale/cms-renderer-next 0.45.0 → 0.47.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 +37 -0
- package/dist/{chunk-GUV5WHD5.js → chunk-UM37DOIK.js} +12 -1
- package/dist/{chunk-GUV5WHD5.js.map → chunk-UM37DOIK.js.map} +1 -1
- package/dist/chunk-VG6UUF4D.js +55 -0
- package/dist/chunk-VG6UUF4D.js.map +1 -0
- package/dist/{chunk-45KYMMDY.js → chunk-ZHXYCABN.js} +69 -12
- package/dist/chunk-ZHXYCABN.js.map +1 -0
- package/dist/{chunk-Z3ZSCPG4.js → chunk-ZL2NA3QT.js} +63 -18
- package/dist/chunk-ZL2NA3QT.js.map +1 -0
- package/dist/concerns-client.d.ts +1 -1
- package/dist/expertise-client.d.ts +1 -1
- package/dist/lead-form-meta-client.d.ts +19 -0
- package/dist/lead-form-meta-client.js +8 -0
- package/dist/lead-form-meta-client.js.map +1 -0
- package/dist/routes.js +6 -4
- package/dist/routes.js.map +1 -1
- package/dist/server.js +4 -3
- package/dist/{site-config-kqurIH-k.d.ts → site-config-Dicv57oY.d.ts} +71 -58
- package/dist/spaces-client.d.ts +1 -1
- package/dist/team-expanding-stage-client.d.ts +1 -1
- package/dist/templates.d.ts +2 -2
- package/dist/templates.js +4 -3
- package/dist/templates.js.map +1 -1
- package/dist/ui-react-motion-client.d.ts +3 -1
- package/dist/ui-react-motion-client.js +1 -1
- package/dist/validate.js +1 -1
- package/package.json +7 -4
- package/dist/chunk-45KYMMDY.js.map +0 -1
- package/dist/chunk-Z3ZSCPG4.js.map +0 -1
|
@@ -141,6 +141,7 @@ function CountUpStat({ value, prefix = "", suffix = "", decimals = DEFAULT_DECIM
|
|
|
141
141
|
// ../ui-react/src/components/motion/Marquee.tsx
|
|
142
142
|
import {
|
|
143
143
|
useEffect as useEffect3,
|
|
144
|
+
useRef as useRef2,
|
|
144
145
|
useState as useState3
|
|
145
146
|
} from "react";
|
|
146
147
|
import {
|
|
@@ -157,19 +158,46 @@ function Marquee({
|
|
|
157
158
|
speedSec = 24,
|
|
158
159
|
gap = "2rem",
|
|
159
160
|
pauseOnHover = true,
|
|
160
|
-
ariaLabel
|
|
161
|
+
ariaLabel,
|
|
162
|
+
direction = "left"
|
|
161
163
|
}) {
|
|
162
164
|
const reducedMotion = useReducedMotion3();
|
|
163
165
|
const [mounted, setMounted] = useState3(false);
|
|
164
166
|
const [paused, setPaused] = useState3(false);
|
|
167
|
+
const [fillCount, setFillCount] = useState3(1);
|
|
168
|
+
const wrapperRef = useRef2(null);
|
|
169
|
+
const measureRef = useRef2(null);
|
|
165
170
|
useEffect3(() => setMounted(true), []);
|
|
166
|
-
|
|
171
|
+
useEffect3(() => {
|
|
172
|
+
const wrapper = wrapperRef.current;
|
|
173
|
+
const measure = measureRef.current;
|
|
174
|
+
if (!wrapper || !measure || typeof ResizeObserver === "undefined") return;
|
|
175
|
+
const calculate = () => {
|
|
176
|
+
const containerWidth = wrapper.offsetWidth;
|
|
177
|
+
const setWidth = measure.offsetWidth;
|
|
178
|
+
if (setWidth === 0) return;
|
|
179
|
+
setFillCount(Math.max(1, Math.ceil(containerWidth / setWidth)));
|
|
180
|
+
};
|
|
181
|
+
calculate();
|
|
182
|
+
const ro = new ResizeObserver(calculate);
|
|
183
|
+
ro.observe(wrapper);
|
|
184
|
+
return () => ro.disconnect();
|
|
185
|
+
}, [children, gap, mounted, reducedMotion]);
|
|
186
|
+
const x = useMotionValue(direction === "right" ? -50 : 0);
|
|
167
187
|
const transform = useTransform(x, (v) => `translateX(${v}%)`);
|
|
188
|
+
useEffect3(() => {
|
|
189
|
+
x.set(direction === "right" ? -50 : 0);
|
|
190
|
+
}, [direction, x]);
|
|
168
191
|
useAnimationFrame((_t, delta) => {
|
|
169
192
|
if (reducedMotion || paused) return;
|
|
170
193
|
const percentPerMs = 50 / (speedSec * 1e3);
|
|
171
|
-
|
|
172
|
-
|
|
194
|
+
const sign = direction === "right" ? 1 : -1;
|
|
195
|
+
let next = x.get() + sign * percentPerMs * delta;
|
|
196
|
+
if (direction === "right") {
|
|
197
|
+
if (next >= 0) next -= 50;
|
|
198
|
+
} else if (next <= -50) {
|
|
199
|
+
next += 50;
|
|
200
|
+
}
|
|
173
201
|
x.set(next);
|
|
174
202
|
});
|
|
175
203
|
const staticStyle = {
|
|
@@ -188,24 +216,39 @@ function Marquee({
|
|
|
188
216
|
flexShrink: 0,
|
|
189
217
|
paddingInlineEnd: gap
|
|
190
218
|
};
|
|
191
|
-
|
|
219
|
+
const reps = Array.from({ length: fillCount }, (_, i) => i);
|
|
220
|
+
return /* @__PURE__ */ jsxs(
|
|
192
221
|
"div",
|
|
193
222
|
{
|
|
223
|
+
ref: wrapperRef,
|
|
194
224
|
className,
|
|
195
225
|
"aria-label": ariaLabel,
|
|
196
226
|
style: { overflow: "hidden", inlineSize: "100%" },
|
|
197
227
|
onMouseEnter: pauseOnHover ? () => setPaused(true) : void 0,
|
|
198
228
|
onMouseLeave: pauseOnHover ? () => setPaused(false) : void 0,
|
|
199
|
-
children:
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
229
|
+
children: [
|
|
230
|
+
/* @__PURE__ */ jsx2(
|
|
231
|
+
"div",
|
|
232
|
+
{
|
|
233
|
+
ref: measureRef,
|
|
234
|
+
"data-marquee-measure": "",
|
|
235
|
+
"aria-hidden": true,
|
|
236
|
+
style: { ...copyStyle, position: "absolute", visibility: "hidden", pointerEvents: "none" },
|
|
237
|
+
children
|
|
238
|
+
}
|
|
239
|
+
),
|
|
240
|
+
/* @__PURE__ */ jsxs(
|
|
241
|
+
motion2.div,
|
|
242
|
+
{
|
|
243
|
+
"data-marquee-track": "",
|
|
244
|
+
style: { display: "flex", inlineSize: "max-content", willChange: "transform", transform },
|
|
245
|
+
children: [
|
|
246
|
+
/* @__PURE__ */ jsx2("div", { style: { display: "flex" }, "data-marquee-block": "a", children: reps.map((i) => /* @__PURE__ */ jsx2("div", { style: copyStyle, "aria-hidden": i > 0 ? true : void 0, children }, `a-${i}`)) }),
|
|
247
|
+
/* @__PURE__ */ jsx2("div", { style: { display: "flex" }, "data-marquee-block": "b", "aria-hidden": true, children: reps.map((i) => /* @__PURE__ */ jsx2("div", { style: copyStyle, children }, `b-${i}`)) })
|
|
248
|
+
]
|
|
249
|
+
}
|
|
250
|
+
)
|
|
251
|
+
]
|
|
209
252
|
}
|
|
210
253
|
);
|
|
211
254
|
}
|
|
@@ -230,7 +273,8 @@ function StaggerGroup({
|
|
|
230
273
|
const reducedMotion = useReducedMotion4();
|
|
231
274
|
const [mounted, setMounted] = useState4(false);
|
|
232
275
|
useEffect4(() => setMounted(true), []);
|
|
233
|
-
|
|
276
|
+
const supportsInView = mounted && typeof window !== "undefined" && typeof window.IntersectionObserver !== "undefined";
|
|
277
|
+
if (reducedMotion || !mounted || !supportsInView) {
|
|
234
278
|
return createElement3(as, { className, style }, children);
|
|
235
279
|
}
|
|
236
280
|
const MotionTag = motion3[as];
|
|
@@ -251,7 +295,8 @@ function StaggerItem({ children, className, style, as = "div" }) {
|
|
|
251
295
|
const reducedMotion = useReducedMotion4();
|
|
252
296
|
const [mounted, setMounted] = useState4(false);
|
|
253
297
|
useEffect4(() => setMounted(true), []);
|
|
254
|
-
|
|
298
|
+
const supportsInView = mounted && typeof window !== "undefined" && typeof window.IntersectionObserver !== "undefined";
|
|
299
|
+
if (reducedMotion || !mounted || !supportsInView) {
|
|
255
300
|
return createElement3(as, { className, style }, children);
|
|
256
301
|
}
|
|
257
302
|
const MotionTag = motion3[as];
|
|
@@ -265,4 +310,4 @@ export {
|
|
|
265
310
|
StaggerGroup,
|
|
266
311
|
StaggerItem
|
|
267
312
|
};
|
|
268
|
-
//# sourceMappingURL=chunk-
|
|
313
|
+
//# sourceMappingURL=chunk-ZL2NA3QT.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 * `useMotionValue` 갱신이라 `direction` 전환 외에는 값이 리셋되지 않음).\n * - 과한 효과 금지(AI-slop #10) — scale·회전 없음, 등속 이동만.\n *\n * 트랙은 자식을 2벌(\"block\")로 나눠 -50%(또는 `direction=\"right\"` 는\n * 0 ↔ -50% 반대 위상) 이동 시 이음매 없이 순환한다. 각 block 내부는\n * `fillCount` 만큼 자식 세트를 반복 렌더 — 1세트 폭이 컨테이너보다 좁으면\n * (예: 로고 3개짜리 마퀴) 루프 중간에 빈틈이 생기는 문제를 막는다.\n * `fillCount` 는 ResizeObserver 로 컨테이너 폭 ÷ 1세트 실측 폭(올림)을 매\n * 리사이즈마다 재계산(vibe-design-starter-kit `MarqueeContainer` 참조,\n * 단 이 컴포넌트의 rAF/모션값 엔진은 그대로 유지). SSR 또는\n * `ResizeObserver` 부재 환경은 `fillCount=1`(기존 2벌 렌더)로 폴백한다.\n */\nimport {\n useEffect,\n useRef,\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 /** 이동 방향. 기본 `left`(기존 동작 불변). `right` 는 반대 방향 이중 마퀴 패턴용. */\n readonly direction?: \"left\" | \"right\";\n}\n\nexport function Marquee({\n children,\n className,\n speedSec = 24,\n gap = \"2rem\",\n pauseOnHover = true,\n ariaLabel,\n direction = \"left\",\n}: MarqueeProps) {\n const reducedMotion = useReducedMotion();\n const [mounted, setMounted] = useState(false);\n const [paused, setPaused] = useState(false);\n // 자식 1세트 반복 횟수. 1 = 기존 동작(block 당 1벌, 총 2벌).\n const [fillCount, setFillCount] = useState(1);\n const wrapperRef = useRef<HTMLDivElement | null>(null);\n const measureRef = useRef<HTMLDivElement | null>(null);\n\n useEffect(() => setMounted(true), []);\n\n // 동적 채움: 컨테이너 폭 대비 1세트 실측 폭으로 반복 횟수 계산.\n // SSR·ResizeObserver 부재 환경은 이 effect 가 그대로 no-op → fillCount=1 유지.\n useEffect(() => {\n const wrapper = wrapperRef.current;\n const measure = measureRef.current;\n if (!wrapper || !measure || typeof ResizeObserver === \"undefined\") return;\n\n const calculate = () => {\n const containerWidth = wrapper.offsetWidth;\n const setWidth = measure.offsetWidth;\n if (setWidth === 0) return;\n setFillCount(Math.max(1, Math.ceil(containerWidth / setWidth)));\n };\n\n calculate();\n const ro = new ResizeObserver(calculate);\n ro.observe(wrapper);\n return () => ro.disconnect();\n // mounted·reducedMotion 은 wrapper/measure DOM 이 실제로 존재하는 렌더를 잡기\n // 위한 트리거다(정적 폴백 렌더에서는 refs 가 비어 있어 no-op) — children/gap\n // 변경 시 재측정도 필요하므로 넷 다 의존성에 둔다.\n }, [children, gap, mounted, reducedMotion]);\n\n const x = useMotionValue(direction === \"right\" ? -50 : 0);\n const transform = useTransform(x, (v) => `translateX(${v}%)`);\n\n // direction 전환 시에만 위상을 리셋(정상 정지/재개 흐름과는 무관).\n useEffect(() => {\n x.set(direction === \"right\" ? -50 : 0);\n }, [direction, x]);\n\n useAnimationFrame((_t, delta) => {\n if (reducedMotion || paused) return;\n const percentPerMs = 50 / (speedSec * 1000); // 자식 1벌 = 트랙의 50%\n const sign = direction === \"right\" ? 1 : -1;\n let next = x.get() + sign * percentPerMs * delta;\n if (direction === \"right\") {\n if (next >= 0) next -= 50; // 이음매 없는 순환(반대 위상)\n } else if (next <= -50) {\n next += 50; // 이음매 없는 순환\n }\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 const reps = Array.from({ length: fillCount }, (_, i) => i);\n\n return (\n <div\n ref={wrapperRef}\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 {/* 측정 전용: 자식 1세트의 실제 폭(gap 포함) — 화면 비노출, fillCount 계산에만 사용 */}\n <div\n ref={measureRef}\n data-marquee-measure=\"\"\n aria-hidden\n style={{ ...copyStyle, position: \"absolute\", visibility: \"hidden\", pointerEvents: \"none\" }}\n >\n {children}\n </div>\n <motion.div\n data-marquee-track=\"\"\n style={{ display: \"flex\", inlineSize: \"max-content\", willChange: \"transform\", transform }}\n >\n <div style={{ display: \"flex\" }} data-marquee-block=\"a\">\n {reps.map((i) => (\n <div key={`a-${i}`} style={copyStyle} aria-hidden={i > 0 ? true : undefined}>\n {children}\n </div>\n ))}\n </div>\n <div style={{ display: \"flex\" }} data-marquee-block=\"b\" aria-hidden>\n {reps.map((i) => (\n <div key={`b-${i}`} style={copyStyle}>\n {children}\n </div>\n ))}\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). `IntersectionObserver` 가 없는\n * 환경(구형 브라우저·일부 헤드리스/캡처 도구)도 `AnimateOnScroll` 과 동일하게\n * 최종(보이는) 상태로 렌더한다 — motion/react 의 `whileInView` 는 그 생성자가\n * 없으면 관찰 자체가 시작되지 않아 `initial=\"hidden\"` 에서 영원히 멈춘다.\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 // `mounted` 가 이미 SSR/하이드레이션-전 패스를 게이트하므로, 아래 체크는\n // 클라이언트에서만 실행된다 — `window` 동기 참조가 안전하다.\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={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 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 // 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","useRef","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-
|
|
2
|
+
import { C as ConcernsSection } from './site-config-Dicv57oY.js';
|
|
3
3
|
import 'zod';
|
|
4
4
|
|
|
5
5
|
type ConcernRegion = ConcernsSection["props"]["regions"][number];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
-
import { E as ExpertiseSection } from './site-config-
|
|
2
|
+
import { E as ExpertiseSection } from './site-config-Dicv57oY.js';
|
|
3
3
|
import 'zod';
|
|
4
4
|
|
|
5
5
|
type ExpertiseItem = ExpertiseSection["props"]["items"][number];
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ReactElement } from 'react';
|
|
2
|
+
|
|
3
|
+
declare global {
|
|
4
|
+
interface Window {
|
|
5
|
+
/** beacon.ts RUNTIME 이 노출하는 `send()` — 비콘 미설정 사이트에선 undefined. */
|
|
6
|
+
__rtTrack?: (ev: string, extra?: Record<string, unknown>) => void;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
interface LeadFormMetaProps {
|
|
10
|
+
/** `form_start`/`form_submit_attempt` 차원(form_id). 예: `"contact"`. */
|
|
11
|
+
formId: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* `RootTaleLeadForm` 의 `<form>` 내부에 렌더 — hidden input 2개(어트리뷰션·
|
|
15
|
+
* 여정) + 폼 계측 리스너. 시각 출력 없음(레이아웃 영향 0).
|
|
16
|
+
*/
|
|
17
|
+
declare function LeadFormMeta({ formId }: LeadFormMetaProps): ReactElement;
|
|
18
|
+
|
|
19
|
+
export { LeadFormMeta, type LeadFormMetaProps };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/dist/routes.js
CHANGED
|
@@ -130,7 +130,7 @@ function createFeedRoute(config) {
|
|
|
130
130
|
const blogBase = config.blogBasePath ?? "/blog";
|
|
131
131
|
const posts = inCollectionMode ? allPosts.filter((p) => {
|
|
132
132
|
const c = resolvePostCollection2(p, collections);
|
|
133
|
-
return c ? c.feed === true && c.detail !== false : false;
|
|
133
|
+
return c ? c.feed === true && c.detail !== false && c.basePath !== "" : false;
|
|
134
134
|
}) : allPosts;
|
|
135
135
|
const xml = generateRssXml({
|
|
136
136
|
title: config.title,
|
|
@@ -240,7 +240,7 @@ function postEntries(config, siteUrl, posts, collections, settings, defaultLocal
|
|
|
240
240
|
}
|
|
241
241
|
function categoryEntries(config, siteUrl, posts, collections) {
|
|
242
242
|
if (isInCollectionMode(collections)) {
|
|
243
|
-
return collections.filter((c) => c.archives).flatMap((c) => {
|
|
243
|
+
return collections.filter((c) => c.archives && c.basePath).flatMap((c) => {
|
|
244
244
|
const cats = collectCollectionCategories(posts, collections, c.key);
|
|
245
245
|
if (!cats.length) return [];
|
|
246
246
|
const base = `${siteUrl}${trimSlash(c.basePath)}`;
|
|
@@ -423,7 +423,9 @@ function createRevalidateRoute(config) {
|
|
|
423
423
|
} else if (isInCollectionMode3(collections)) {
|
|
424
424
|
alsoRevalidate = [
|
|
425
425
|
...INVARIANTS,
|
|
426
|
-
|
|
426
|
+
// 주소 없는 유형(basePath === "")은 라우트가 없으므로 revalidate 대상에서
|
|
427
|
+
// 제외 — 안 그러면 빈 경로("")를 무효화 요청하게 된다.
|
|
428
|
+
...collections.filter((c) => c.basePath).flatMap(
|
|
427
429
|
(c) => c.archives ? [c.basePath, `${c.basePath}/categories`] : [c.basePath]
|
|
428
430
|
)
|
|
429
431
|
];
|
|
@@ -669,7 +671,7 @@ function createFleetInfoRoute(config = {}) {
|
|
|
669
671
|
site: config.site ?? env.VERCEL_PROJECT_PRODUCTION_URL ?? null,
|
|
670
672
|
framework: "next",
|
|
671
673
|
cms_client: CMS_CLIENT_VERSION,
|
|
672
|
-
cms_renderer: true ? "0.
|
|
674
|
+
cms_renderer: true ? "0.47.0" : "dev",
|
|
673
675
|
build_sha: env.VERCEL_GIT_COMMIT_SHA?.slice(0, 7) ?? null
|
|
674
676
|
},
|
|
675
677
|
{ headers: { "cache-control": "public, max-age=300" } }
|