@roottale/cms-renderer-next 0.51.1 → 0.53.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 (76) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/README.md +54 -4
  3. package/dist/{chunk-U2IM6OCQ.js → chunk-HM5VP7SR.js} +1 -1
  4. package/dist/{chunk-U2IM6OCQ.js.map → chunk-HM5VP7SR.js.map} +1 -1
  5. package/dist/chunk-KSPPYX22.js +14 -0
  6. package/dist/chunk-KSPPYX22.js.map +1 -0
  7. package/dist/cms-public.css +46 -0
  8. package/dist/routes.d.ts +25 -1
  9. package/dist/routes.js +72 -14
  10. package/dist/routes.js.map +1 -1
  11. package/dist/section-runtime.d.ts +46 -0
  12. package/dist/section-runtime.js +9 -0
  13. package/dist/server.d.ts +5 -3
  14. package/dist/server.js +1679 -34
  15. package/dist/server.js.map +1 -1
  16. package/package.json +7 -23
  17. package/dist/before-after-client.d.ts +0 -15
  18. package/dist/before-after-client.js +0 -8
  19. package/dist/chunk-27RT6B3O.js +0 -93
  20. package/dist/chunk-27RT6B3O.js.map +0 -1
  21. package/dist/chunk-2QU4JU46.js +0 -3927
  22. package/dist/chunk-2QU4JU46.js.map +0 -1
  23. package/dist/chunk-4RQ3SEDL.js +0 -106
  24. package/dist/chunk-4RQ3SEDL.js.map +0 -1
  25. package/dist/chunk-6GQCFFHU.js +0 -540
  26. package/dist/chunk-6GQCFFHU.js.map +0 -1
  27. package/dist/chunk-H46JUIHZ.js +0 -79
  28. package/dist/chunk-H46JUIHZ.js.map +0 -1
  29. package/dist/chunk-K62RR4UQ.js +0 -53
  30. package/dist/chunk-K62RR4UQ.js.map +0 -1
  31. package/dist/chunk-LEHHSI6G.js +0 -64
  32. package/dist/chunk-LEHHSI6G.js.map +0 -1
  33. package/dist/chunk-QYNEOFL7.js +0 -418
  34. package/dist/chunk-QYNEOFL7.js.map +0 -1
  35. package/dist/chunk-U5AKACPN.js +0 -113
  36. package/dist/chunk-U5AKACPN.js.map +0 -1
  37. package/dist/chunk-UM37DOIK.js +0 -961
  38. package/dist/chunk-UM37DOIK.js.map +0 -1
  39. package/dist/chunk-YLSQAT6B.js +0 -59
  40. package/dist/chunk-YLSQAT6B.js.map +0 -1
  41. package/dist/chunk-ZL2NA3QT.js +0 -313
  42. package/dist/chunk-ZL2NA3QT.js.map +0 -1
  43. package/dist/concerns-client.d.ts +0 -12
  44. package/dist/concerns-client.js +0 -8
  45. package/dist/concerns-client.js.map +0 -1
  46. package/dist/contact-map-client.d.ts +0 -27
  47. package/dist/contact-map-client.js +0 -8
  48. package/dist/contact-map-client.js.map +0 -1
  49. package/dist/expertise-client.d.ts +0 -16
  50. package/dist/expertise-client.js +0 -9
  51. package/dist/expertise-client.js.map +0 -1
  52. package/dist/hero-carousel-client.d.ts +0 -52
  53. package/dist/hero-carousel-client.js +0 -9
  54. package/dist/hero-carousel-client.js.map +0 -1
  55. package/dist/seo-checks.d.ts +0 -87
  56. package/dist/seo-checks.js +0 -27
  57. package/dist/seo-checks.js.map +0 -1
  58. package/dist/site-config-BvUyDx_f.d.ts +0 -3961
  59. package/dist/spaces-client.d.ts +0 -10
  60. package/dist/spaces-client.js +0 -8
  61. package/dist/spaces-client.js.map +0 -1
  62. package/dist/team-expanding-stage-client.d.ts +0 -35
  63. package/dist/team-expanding-stage-client.js +0 -8
  64. package/dist/team-expanding-stage-client.js.map +0 -1
  65. package/dist/templates.css +0 -4145
  66. package/dist/templates.d.ts +0 -171
  67. package/dist/templates.js +0 -291
  68. package/dist/templates.js.map +0 -1
  69. package/dist/theme-css-vars-CD3jcEkp.d.ts +0 -7
  70. package/dist/ui-react-motion-client.d.ts +0 -76
  71. package/dist/ui-react-motion-client.js +0 -16
  72. package/dist/ui-react-motion-client.js.map +0 -1
  73. package/dist/validate.d.ts +0 -104
  74. package/dist/validate.js +0 -1204
  75. package/dist/validate.js.map +0 -1
  76. /package/dist/{before-after-client.js.map → section-runtime.js.map} +0 -0
@@ -1,961 +0,0 @@
1
- // src/templates/site-config-version.ts
2
- var SITE_CONFIG_SCHEMA_VERSION = 1;
3
- var LEGACY_SITE_CONFIG_SCHEMA_VERSION = 1;
4
- function resolveSiteConfigSchemaVersion(config) {
5
- return config.schemaVersion ?? LEGACY_SITE_CONFIG_SCHEMA_VERSION;
6
- }
7
-
8
- // src/templates/site-config.ts
9
- import { z } from "zod";
10
- var JsonValue = z.lazy(
11
- () => z.union([
12
- z.string(),
13
- z.number(),
14
- z.boolean(),
15
- z.null(),
16
- z.array(JsonValue),
17
- z.record(JsonValue)
18
- ])
19
- );
20
- var NavItemSchema = z.object({
21
- label: z.string(),
22
- href: z.string(),
23
- children: z.array(z.object({ label: z.string(), href: z.string() })).optional()
24
- });
25
- var CtaSchema = z.object({ label: z.string(), href: z.string() });
26
- var MetricSchema = z.object({
27
- value: z.string(),
28
- unit: z.string().optional(),
29
- label: z.string(),
30
- source: z.string().optional(),
31
- imageSrc: z.string().optional(),
32
- imageAlt: z.string().optional()
33
- });
34
- var SectionBase = z.object({
35
- /** 안정적 anchor·dedup·preview 매핑용. 페이지 내 유일. */
36
- id: z.string().optional(),
37
- /** 빌트인 또는 customSections registry 키. kebab-case. */
38
- type: z.string(),
39
- /** 같은 type 의 비주얼 변형. 빌트인 type 은 enum 으로 좁힘. */
40
- variant: z.string().optional(),
41
- /** 섹션 스코프 토큰 override (`--rt-*` CSS 변수 → 값). */
42
- theme: z.record(z.string()).optional(),
43
- /** 섹션별 직렬화 props. */
44
- props: z.record(JsonValue).default({})
45
- });
46
- var SECTION_VARIANTS = {
47
- hero: ["split", "image", "minimal", "carousel", "overlay-panel"],
48
- services: [
49
- "grid",
50
- "list",
51
- "index-rows",
52
- "split-featured",
53
- "action-rail",
54
- "photo-cards"
55
- ],
56
- faq: ["default", "two-column", "with-contact", "content-accordion"],
57
- "cta-banner": ["default", "photo-overlay"],
58
- trust: [
59
- "stat-band",
60
- "logo-cloud",
61
- "feature-grid",
62
- "logo-marquee",
63
- "credential-list",
64
- "dashboard"
65
- ],
66
- contact: ["form-map-5050", "split", "channel-cards", "visit-band"],
67
- hours: ["table", "compact"],
68
- pricing: ["table", "cards"],
69
- process: ["horizontal", "vertical", "timeline-marked"],
70
- "before-after": ["slider", "side-by-side"],
71
- testimonials: ["editorial-quote", "quote-rail", "star-compact"],
72
- team: ["grid", "spotlight", "expanding-stage", "row"],
73
- expertise: ["tab-stage", "cards"],
74
- spaces: ["stage", "mosaic"],
75
- concerns: ["figure-map", "list"],
76
- footer: ["columns", "minimal"]
77
- };
78
- var HeroCarouselSlideSchema = z.object({
79
- image: z.string(),
80
- alt: z.string(),
81
- caption: z.string().optional(),
82
- eyebrow: z.string().optional(),
83
- title: z.string().optional(),
84
- subtitle: z.string().optional(),
85
- description: z.string().optional(),
86
- primaryCta: CtaSchema.optional(),
87
- secondaryCta: CtaSchema.optional(),
88
- tabLabel: z.string().optional(),
89
- tabMeta: z.string().optional(),
90
- metrics: z.array(MetricSchema).max(4).optional()
91
- });
92
- var HeroProofMetricsSchema = z.object({
93
- items: z.array(MetricSchema).min(1).max(4),
94
- position: z.enum(["inline", "overlay"]).default("inline")
95
- });
96
- var HeroSectionSchema = SectionBase.extend({
97
- type: z.literal("hero"),
98
- variant: z.enum(SECTION_VARIANTS.hero).default("split"),
99
- props: z.object({
100
- eyebrow: z.string().optional(),
101
- /** `\n` 으로 줄바꿈. */
102
- title: z.string(),
103
- description: z.string().optional(),
104
- imageSrc: z.string().optional(),
105
- imageAlt: z.string().optional(),
106
- cta: CtaSchema.optional(),
107
- /** `carousel` 전용 — 고정 카피 아래 배경만 전환되는 슬라이드. 2-8개. */
108
- slides: z.array(HeroCarouselSlideSchema).min(2).max(8).optional(),
109
- /** `carousel` 전용 — 부제(슬라이드와 무관한 고정 카피). */
110
- subtitle: z.string().optional(),
111
- /** `carousel` 전용. 기본 true — `useReducedMotion` 시 렌더러가 강제 off. */
112
- autoplay: z.boolean().optional(),
113
- /** `carousel` 전용, ms. 기본 6000. */
114
- intervalMs: z.number().min(3e3).max(1e4).optional(),
115
- /**
116
- * `carousel` 전용 배경 전환 모션(ADR-0086 P2 D3). 기본 "slide"(기존 embla
117
- * 가로 스크롤). "curtain" = clip-path 와이프 + 신 스윕(chambarun 이식).
118
- * variant 는 그대로 "carousel" — 신규 variant 아님("variant=구조,
119
- * motion=인상" 규칙).
120
- */
121
- transition: z.enum(["slide", "curtain"]).optional(),
122
- proofMetrics: HeroProofMetricsSchema.optional(),
123
- scrim: z.enum(["none", "soft", "strong"]).optional()
124
- })
125
- }).superRefine((section, ctx) => {
126
- if (section.variant === "overlay-panel") {
127
- if (!section.props.imageSrc) {
128
- ctx.addIssue({
129
- code: z.ZodIssueCode.custom,
130
- path: ["props", "imageSrc"],
131
- message: "overlay-panel hero \uB294 imageSrc \uAC00 \uD544\uC218\uC785\uB2C8\uB2E4."
132
- });
133
- }
134
- if (!section.props.imageAlt) {
135
- ctx.addIssue({
136
- code: z.ZodIssueCode.custom,
137
- path: ["props", "imageAlt"],
138
- message: "overlay-panel hero \uB294 imageAlt \uAC00 \uD544\uC218\uC785\uB2C8\uB2E4."
139
- });
140
- }
141
- }
142
- if (section.variant !== "carousel") return;
143
- if (section.props.title.length < 1 || section.props.title.length > 80) {
144
- ctx.addIssue({
145
- code: z.ZodIssueCode.custom,
146
- path: ["props", "title"],
147
- message: "carousel hero \uC81C\uBAA9\uC740 1-80\uC790\uC5EC\uC57C \uD569\uB2C8\uB2E4."
148
- });
149
- }
150
- if (!section.props.slides || section.props.slides.length < 2) {
151
- ctx.addIssue({
152
- code: z.ZodIssueCode.custom,
153
- path: ["props", "slides"],
154
- message: "carousel hero \uB294 slides 2-8\uAC1C\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4."
155
- });
156
- }
157
- });
158
- var RichTextSectionSchema = SectionBase.extend({
159
- type: z.literal("rich-text"),
160
- props: z.object({
161
- heading: z.string().optional(),
162
- /** `\n\n` 으로 문단 분리. */
163
- body: z.string()
164
- })
165
- });
166
- var ServicesSectionSchema = SectionBase.extend({
167
- type: z.literal("services"),
168
- /**
169
- * C10 §1-2 — `index-rows`(hairline 인덱스 행) · `split-featured`(비대칭
170
- * 5/7, 대표 서비스 1개 + 나머지 컴팩트 리스트) 추가. AI-slop 금지 #1/#2
171
- * (아이콘 그리드·균등 카드) 대체 문법.
172
- */
173
- variant: z.enum(SECTION_VARIANTS.services).default("grid"),
174
- props: z.object({
175
- heading: z.string().optional(),
176
- description: z.string().optional(),
177
- items: z.array(
178
- z.object({
179
- title: z.string(),
180
- subtitle: z.string().optional(),
181
- description: z.string().optional(),
182
- href: z.string().optional(),
183
- iconSrc: z.string().optional(),
184
- iconAlt: z.string().optional(),
185
- imageSrc: z.string().optional(),
186
- imageAlt: z.string().optional(),
187
- kind: z.string().optional(),
188
- ctaLabel: z.string().optional(),
189
- chips: z.array(z.string()).max(6).optional(),
190
- meta: z.array(z.string()).max(8).optional()
191
- })
192
- ).min(1).max(12)
193
- })
194
- }).superRefine((section, ctx) => {
195
- if (section.variant === "split-featured" && section.props.items.length < 2) {
196
- ctx.addIssue({
197
- code: z.ZodIssueCode.custom,
198
- path: ["props", "items"],
199
- message: "split-featured \uB294 \uB300\uD45C \uC11C\uBE44\uC2A4 1\uAC1C + \uB098\uBA38\uC9C0 \uCD5C\uC18C 1\uAC1C, \uCD1D 2\uAC1C \uC774\uC0C1\uC758 items \uAC00 \uD544\uC694\uD569\uB2C8\uB2E4."
200
- });
201
- }
202
- section.props.items.forEach((item, index) => {
203
- if (item.iconSrc && !item.iconAlt) {
204
- ctx.addIssue({
205
- code: z.ZodIssueCode.custom,
206
- path: ["props", "items", index, "iconAlt"],
207
- message: "iconSrc \uAC00 \uC788\uC73C\uBA74 iconAlt \uAC00 \uD544\uC694\uD569\uB2C8\uB2E4."
208
- });
209
- }
210
- if (section.variant === "photo-cards" && !item.imageSrc) {
211
- ctx.addIssue({
212
- code: z.ZodIssueCode.custom,
213
- path: ["props", "items", index, "imageSrc"],
214
- message: "photo-cards \uC11C\uBE44\uC2A4 \uD56D\uBAA9\uC740 imageSrc \uAC00 \uD544\uC694\uD569\uB2C8\uB2E4."
215
- });
216
- }
217
- if (section.variant === "photo-cards" && !item.imageAlt) {
218
- ctx.addIssue({
219
- code: z.ZodIssueCode.custom,
220
- path: ["props", "items", index, "imageAlt"],
221
- message: "photo-cards \uC11C\uBE44\uC2A4 \uD56D\uBAA9\uC740 imageAlt \uAC00 \uD544\uC694\uD569\uB2C8\uB2E4."
222
- });
223
- }
224
- });
225
- });
226
- var FooterLogoSchema = z.object({
227
- src: z.string(),
228
- alt: z.string()
229
- });
230
- var FooterLinkSchema = z.object({
231
- label: z.string(),
232
- href: z.string()
233
- });
234
- var FooterColumnSchema = z.object({
235
- title: z.string(),
236
- links: z.array(FooterLinkSchema).min(1).max(12)
237
- });
238
- var FooterContactInfoSchema = z.object({
239
- address: z.string().optional(),
240
- phone: z.string().optional(),
241
- email: z.string().optional()
242
- });
243
- var FooterSocialLinkSchema = z.object({
244
- kind: z.string(),
245
- href: z.string()
246
- });
247
- var FooterSectionSchema = SectionBase.extend({
248
- type: z.literal("footer"),
249
- variant: z.enum(SECTION_VARIANTS.footer).default("columns"),
250
- props: z.object({
251
- businessName: z.string(),
252
- logo: FooterLogoSchema.optional(),
253
- description: z.string().optional(),
254
- columns: z.array(FooterColumnSchema).max(6).optional(),
255
- contactInfo: FooterContactInfoSchema.optional(),
256
- legalInfo: z.string().optional(),
257
- privacyHref: z.string().optional(),
258
- termsHref: z.string().optional(),
259
- socialLinks: z.array(FooterSocialLinkSchema).max(8).optional(),
260
- copyright: z.string().optional(),
261
- /** 'hidden' 은 화이트라벨 plan gate 통과 시만 유효 (미지정 = 'default' 렌더). tenant 커스터마이즈 대상 아님 — 에디터 폼에 노출하지 않는다. */
262
- credit: z.enum(["default", "hidden"]).optional()
263
- })
264
- }).superRefine((section, ctx) => {
265
- if (section.variant === "columns" && (!section.props.columns || section.props.columns.length === 0)) {
266
- ctx.addIssue({
267
- code: z.ZodIssueCode.custom,
268
- path: ["props", "columns"],
269
- message: "columns footer \uB294 columns 1\uAC1C \uC774\uC0C1\uC774 \uD544\uC694\uD569\uB2C8\uB2E4."
270
- });
271
- }
272
- });
273
- var FaqItemSchema = z.object({ question: z.string(), answer: z.string() });
274
- var FaqGroupSchema = z.object({
275
- title: z.string(),
276
- items: z.array(FaqItemSchema).min(1).max(12)
277
- });
278
- var FaqContactSchema = z.object({
279
- title: z.string(),
280
- description: z.string().optional(),
281
- phone: z.string().optional(),
282
- cta: CtaSchema.optional()
283
- });
284
- var FaqContentItemSchema = z.object({
285
- title: z.string(),
286
- summary: z.string().optional(),
287
- body: z.string(),
288
- signs: z.array(z.string()).max(8).optional(),
289
- href: z.string().optional()
290
- });
291
- var FaqContentGroupSchema = z.object({
292
- title: z.string(),
293
- items: z.array(FaqContentItemSchema).min(1).max(12)
294
- });
295
- var FaqSectionSchema = SectionBase.extend({
296
- type: z.literal("faq"),
297
- variant: z.enum(SECTION_VARIANTS.faq).default("default"),
298
- props: z.object({
299
- heading: z.string().optional(),
300
- items: z.array(FaqItemSchema).min(1).max(20).optional(),
301
- /** `two-column` 전용 — 그룹 헤더 + 항목. 없으면 기존 flat items 를 사용한다. */
302
- groups: z.array(FaqGroupSchema).min(1).max(6).optional(),
303
- /** `with-contact` 전용 — 우측 sticky 상담 유도 카드. */
304
- contact: FaqContactSchema.optional(),
305
- /** `content-accordion` 전용 — FAQPage가 아닌 질환/서비스 설명 accordion. */
306
- contentGroups: z.array(FaqContentGroupSchema).min(1).max(6).optional(),
307
- /** FAQPage JSON-LD 주입 (기본 true — SEO 3 pillars). 페이지에 FAQ 섹션이 여럿이면 하나만 true 로. */
308
- jsonLd: z.boolean().default(true)
309
- })
310
- }).superRefine((section, ctx) => {
311
- const hasContentGroups = Boolean(
312
- section.props.contentGroups && section.props.contentGroups.length > 0
313
- );
314
- if (section.variant === "content-accordion") {
315
- if (!hasContentGroups) {
316
- ctx.addIssue({
317
- code: z.ZodIssueCode.custom,
318
- path: ["props", "contentGroups"],
319
- message: "content-accordion faq sections require contentGroups"
320
- });
321
- }
322
- return;
323
- }
324
- const hasItems = Boolean(section.props.items && section.props.items.length > 0);
325
- const hasGroups = Boolean(section.props.groups && section.props.groups.length > 0);
326
- if (!hasItems && !hasGroups) {
327
- ctx.addIssue({
328
- code: z.ZodIssueCode.custom,
329
- path: ["props", "items"],
330
- message: "faq sections require items or groups"
331
- });
332
- }
333
- if (section.variant === "with-contact" && !section.props.contact) {
334
- ctx.addIssue({
335
- code: z.ZodIssueCode.custom,
336
- path: ["props", "contact"],
337
- message: "with-contact faq sections require contact"
338
- });
339
- }
340
- });
341
- var CtaBannerSectionSchema = SectionBase.extend({
342
- type: z.literal("cta-banner"),
343
- /** C10 §1-5 — `photo-overlay`: 풀블리드 사진 + 다크 오버레이 극단 대비 CTA. */
344
- variant: z.enum(SECTION_VARIANTS["cta-banner"]).default("default"),
345
- props: z.object({
346
- title: z.string(),
347
- description: z.string().optional(),
348
- cta: CtaSchema.optional(),
349
- /** `photo-overlay` 전용 — 필수(사진 없으면 렌더 거부). */
350
- image: z.string().optional(),
351
- imageAlt: z.string().optional()
352
- })
353
- }).superRefine((section, ctx) => {
354
- if (section.variant !== "photo-overlay") return;
355
- if (!section.props.image) {
356
- ctx.addIssue({
357
- code: z.ZodIssueCode.custom,
358
- path: ["props", "image"],
359
- message: "photo-overlay cta-banner \uB294 image \uAC00 \uD544\uC218\uC785\uB2C8\uB2E4."
360
- });
361
- }
362
- if (!section.props.imageAlt) {
363
- ctx.addIssue({
364
- code: z.ZodIssueCode.custom,
365
- path: ["props", "imageAlt"],
366
- message: "photo-overlay cta-banner \uB294 imageAlt \uAC00 \uD544\uC218\uC785\uB2C8\uB2E4."
367
- });
368
- }
369
- });
370
- var BlogListSectionSchema = SectionBase.extend({
371
- type: z.literal("blog-list"),
372
- props: z.object({
373
- heading: z.string().optional(),
374
- limit: z.number().int().positive().optional(),
375
- /** 포스트 링크 베이스. 기본 `/blog`. */
376
- blogBasePath: z.string().optional(),
377
- emptyMessage: z.string().optional()
378
- })
379
- });
380
- var LeadFormSectionSchema = SectionBase.extend({
381
- type: z.literal("lead-form"),
382
- props: z.object({
383
- /** 사이트 자체 server 엔드포인트 (예: `/api/inquiry`). intake API 직접 지정 금지. */
384
- action: z.string(),
385
- vertical: z.enum(["consulting", "medical", "tax", "legal"]).optional(),
386
- heading: z.string().optional(),
387
- description: z.string().optional(),
388
- submitLabel: z.string().optional()
389
- })
390
- });
391
- var TrustMetricSchema = MetricSchema;
392
- var TrustCredentialSchema = z.object({
393
- name: z.string(),
394
- issuer: z.string().optional(),
395
- kind: z.enum(["license", "education", "association"]).optional(),
396
- image: z.string().optional(),
397
- /** `credential-list` 전용 — hairline 행의 tabular 연도. */
398
- year: z.string().optional()
399
- });
400
- var TrustLogoSchema = z.object({
401
- src: z.string(),
402
- alt: z.string(),
403
- href: z.string().optional()
404
- });
405
- var LegacyTrustLogoSchema = z.object({
406
- image: z.string(),
407
- alt: z.string(),
408
- href: z.string().optional()
409
- }).transform((logo) => ({
410
- src: logo.image,
411
- alt: logo.alt,
412
- ...logo.href ? { href: logo.href } : {}
413
- }));
414
- var TrustLogoSlotSchema = z.union([TrustLogoSchema, LegacyTrustLogoSchema]);
415
- var TrustChartSchema = z.object({
416
- type: z.enum(["bar", "line"]).default("bar"),
417
- title: z.string(),
418
- description: z.string().optional(),
419
- points: z.array(
420
- z.object({
421
- label: z.string(),
422
- value: z.number(),
423
- display: z.string().optional()
424
- })
425
- ).min(1).max(12)
426
- });
427
- var TrustSectionSchema = SectionBase.extend({
428
- type: z.literal("trust"),
429
- /**
430
- * C10 §1-3/1-4 — `logo-marquee`(C8 Marquee 소비, 흑백/저채도 로고 스트립) ·
431
- * `credential-list`(연도 tabular + hairline 자격 리스트) 추가.
432
- */
433
- variant: z.enum(SECTION_VARIANTS.trust).default("stat-band"),
434
- props: z.object({
435
- heading: z.string().optional(),
436
- metrics: z.array(TrustMetricSchema).min(2).max(4).optional(),
437
- credentials: z.array(TrustCredentialSchema).max(8).optional(),
438
- logos: z.array(TrustLogoSlotSchema).max(8).optional(),
439
- /** `logo-marquee` 전용 — logo-cloud 와 동일한 `{src, alt, href?}` 슬롯. 최소 3개. */
440
- marqueeLogos: z.array(TrustLogoSlotSchema).min(3).max(16).optional(),
441
- chart: TrustChartSchema.optional(),
442
- disclaimer: z.string().optional()
443
- })
444
- }).superRefine((section, ctx) => {
445
- if (section.variant === "stat-band" && !section.props.metrics) {
446
- ctx.addIssue({
447
- code: z.ZodIssueCode.custom,
448
- path: ["props", "metrics"],
449
- message: "stat-band trust sections require 2-4 metrics"
450
- });
451
- }
452
- if (section.variant === "logo-cloud" && !section.props.logos) {
453
- ctx.addIssue({
454
- code: z.ZodIssueCode.custom,
455
- path: ["props", "logos"],
456
- message: "logo-cloud trust sections require logos"
457
- });
458
- }
459
- if ((section.variant === "feature-grid" || section.variant === "credential-list") && !section.props.credentials) {
460
- ctx.addIssue({
461
- code: z.ZodIssueCode.custom,
462
- path: ["props", "credentials"],
463
- message: `${section.variant} trust sections require credentials`
464
- });
465
- }
466
- if (section.variant === "logo-marquee" && !section.props.marqueeLogos) {
467
- ctx.addIssue({
468
- code: z.ZodIssueCode.custom,
469
- path: ["props", "marqueeLogos"],
470
- message: "logo-marquee trust sections require marqueeLogos (min 3)"
471
- });
472
- }
473
- if (section.variant === "dashboard" && !section.props.chart) {
474
- ctx.addIssue({
475
- code: z.ZodIssueCode.custom,
476
- path: ["props", "chart"],
477
- message: "dashboard trust sections require chart"
478
- });
479
- }
480
- });
481
- var ContactHoursSchema = z.object({
482
- day: z.string(),
483
- open: z.string(),
484
- close: z.string(),
485
- note: z.string().optional()
486
- });
487
- var ContactChannelSchema = z.object({
488
- label: z.string(),
489
- description: z.string().optional(),
490
- value: z.string().optional(),
491
- href: z.string().optional(),
492
- actionLabel: z.string().optional(),
493
- iconSrc: z.string().optional(),
494
- iconAlt: z.string().optional(),
495
- kind: z.string().optional()
496
- });
497
- var ContactMapSchema = z.object({
498
- facadeImageSrc: z.string().optional(),
499
- facadeImageAlt: z.string().optional(),
500
- provider: z.string().optional(),
501
- embedUrl: z.string().optional(),
502
- externalUrl: z.string().optional(),
503
- lat: z.number().optional(),
504
- lng: z.number().optional(),
505
- /**
506
- * 지도 로드 모드(ADR-0086 P2 D3, chambarun `.map-facade` 클릭-투-로드 이식).
507
- * 기본 "lazy"(기존 동작 그대로 — 실지도면 즉시 iframe `loading="lazy"`).
508
- * "click" 은 정적 프리뷰(주소·지도 열기 버튼)만 먼저 그리고, 클릭 시에만
509
- * iframe 을 마운트한다(외부 지도 스크립트/네트워크 요청을 사용자 의도
510
- * 전까지 미루는 성능·프라이버시 절충 — additive, 기존 사이트는 영향 없음).
511
- */
512
- mapLoad: z.enum(["lazy", "click"]).optional()
513
- });
514
- var ContactActionSchema = z.object({
515
- label: z.string(),
516
- href: z.string(),
517
- kind: z.string().optional()
518
- });
519
- var ContactSectionSchema = SectionBase.extend({
520
- type: z.literal("contact"),
521
- variant: z.enum(SECTION_VARIANTS.contact).default("form-map-5050"),
522
- props: z.object({
523
- heading: z.string().optional(),
524
- form: z.object({
525
- enabled: z.boolean(),
526
- vertical: z.enum(["medical", "tax", "legal", "consulting"]).optional()
527
- }),
528
- phone: z.string().optional(),
529
- address: z.string().optional(),
530
- hours: z.array(ContactHoursSchema).max(8).optional(),
531
- mapEmbedUrl: z.string().optional(),
532
- map: ContactMapSchema.optional(),
533
- actions: z.array(ContactActionSchema).max(6).optional(),
534
- /** `channel-cards` 전용 — 카드당 하나의 행동. */
535
- channels: z.array(ContactChannelSchema).min(3).max(6).optional()
536
- })
537
- }).superRefine((section, ctx) => {
538
- if (section.variant !== "channel-cards") return;
539
- if (!section.props.channels) {
540
- ctx.addIssue({
541
- code: z.ZodIssueCode.custom,
542
- path: ["props", "channels"],
543
- message: "channel-cards contact sections require 3-6 channels"
544
- });
545
- }
546
- section.props.channels?.forEach((channel, index) => {
547
- if (channel.iconSrc && !channel.iconAlt) {
548
- ctx.addIssue({
549
- code: z.ZodIssueCode.custom,
550
- path: ["props", "channels", index, "iconAlt"],
551
- message: "iconSrc \uAC00 \uC788\uC73C\uBA74 iconAlt \uAC00 \uD544\uC694\uD569\uB2C8\uB2E4."
552
- });
553
- }
554
- });
555
- });
556
- var PracticeDaySchema = z.enum([
557
- "monday",
558
- "tuesday",
559
- "wednesday",
560
- "thursday",
561
- "friday",
562
- "saturday",
563
- "sunday"
564
- ]);
565
- var HoursBadgeSlotsSchema = z.object({
566
- lunch: z.string().optional(),
567
- evening: z.string().optional(),
568
- closed: z.string().optional(),
569
- extra: z.array(z.string()).max(4).optional()
570
- });
571
- var HoursRowSchema = z.object({
572
- day: PracticeDaySchema,
573
- label: z.string(),
574
- hours: z.string(),
575
- lunchHours: z.string().optional(),
576
- badges: HoursBadgeSlotsSchema.optional(),
577
- note: z.string().optional()
578
- });
579
- var HoursSectionSchema = SectionBase.extend({
580
- type: z.literal("hours"),
581
- variant: z.enum(SECTION_VARIANTS.hours).default("table"),
582
- props: z.object({
583
- eyebrow: z.string().optional(),
584
- heading: z.string().optional(),
585
- description: z.string().optional(),
586
- rows: z.array(HoursRowSchema).min(1).max(14),
587
- note: z.string().optional(),
588
- disclaimer: z.string().optional()
589
- })
590
- });
591
- var PricingItemSchema = z.object({
592
- name: z.string(),
593
- description: z.string().optional(),
594
- price: z.string(),
595
- condition: z.string().optional()
596
- });
597
- var PricingGroupSchema = z.object({
598
- label: z.string(),
599
- items: z.array(PricingItemSchema).min(1).max(12)
600
- });
601
- var PricingSectionSchema = SectionBase.extend({
602
- type: z.literal("pricing"),
603
- variant: z.enum(SECTION_VARIANTS.pricing).default("table"),
604
- props: z.object({
605
- eyebrow: z.string().optional(),
606
- heading: z.string().optional(),
607
- description: z.string().optional(),
608
- groups: z.array(PricingGroupSchema).min(1).max(6),
609
- disclaimer: z.string().min(1)
610
- })
611
- });
612
- var ProcessTimelineItemSchema = z.object({
613
- eyebrow: z.string().optional(),
614
- title: z.string(),
615
- description: z.string(),
616
- duration: z.string().optional(),
617
- status: z.enum(["complete", "current", "pending"]).optional(),
618
- statusLabel: z.string().optional(),
619
- mark: z.enum(["check", "dot", "none"]).optional(),
620
- imageSrc: z.string().optional(),
621
- imageAlt: z.string().optional()
622
- });
623
- var ProcessSectionSchema = SectionBase.extend({
624
- type: z.literal("process"),
625
- variant: z.enum(SECTION_VARIANTS.process).default("horizontal"),
626
- props: z.object({
627
- eyebrow: z.string().optional(),
628
- heading: z.string().optional(),
629
- description: z.string().optional(),
630
- items: z.array(ProcessTimelineItemSchema).min(2).max(8),
631
- connector: z.enum(["line", "rail"]).optional(),
632
- disclaimer: z.string().optional()
633
- })
634
- }).superRefine((section, ctx) => {
635
- section.props.items.forEach((item, index) => {
636
- if (item.imageSrc && !item.imageAlt) {
637
- ctx.addIssue({
638
- code: z.ZodIssueCode.custom,
639
- path: ["props", "items", index, "imageAlt"],
640
- message: "imageSrc \uAC00 \uC788\uC73C\uBA74 imageAlt \uAC00 \uD544\uC694\uD569\uB2C8\uB2E4."
641
- });
642
- }
643
- });
644
- });
645
- var BeforeAfterImageSchema = z.object({
646
- src: z.string(),
647
- alt: z.string()
648
- });
649
- var BeforeAfterSectionSchema = SectionBase.extend({
650
- type: z.literal("before-after"),
651
- variant: z.enum(SECTION_VARIANTS["before-after"]).default("slider"),
652
- props: z.object({
653
- beforeImage: BeforeAfterImageSchema,
654
- afterImage: BeforeAfterImageSchema,
655
- beforeLabel: z.string().default("\uC2DC\uACF5 \uC804"),
656
- afterLabel: z.string().default("\uC2DC\uACF5 \uD6C4"),
657
- heading: z.string().optional(),
658
- description: z.string().optional(),
659
- disclaimer: z.string().optional()
660
- })
661
- });
662
- var TestimonialItemSchema = z.object({
663
- quote: z.string().min(1).max(280),
664
- author: z.string().min(1),
665
- /** 케이스 라벨(예: "허리디스크 · 비수술 치료 3개월"). */
666
- context: z.string().optional(),
667
- rating: z.number().min(0).max(5).optional(),
668
- source: z.string().optional(),
669
- visitedAt: z.string().optional(),
670
- item: z.string().optional(),
671
- reply: z.string().optional()
672
- });
673
- var TestimonialsSectionSchema = SectionBase.extend({
674
- type: z.literal("testimonials"),
675
- /**
676
- * `editorial-quote`(기본): 대형 인용 타이포 에디토리얼 — 1개면 중앙 정렬
677
- * 스테이지, 2개 이상이면 세로 스택(hairline 구분). `quote-rail`: 가로
678
- * 스크롤 레일(hairline 좌측 보더 컬럼, 균등 그림자 카드 금지).
679
- * `star-compact`(W8-7): 별점 중심 컴팩트 그리드 — 평균 별점 요약 밴드 +
680
- * hairline 행. 별점은 CSS 타이포 글리프(이모지·이미지 금지), 색은 전부
681
- * 토큰(variant×토큰 직교, 채움 폭만 데이터 주도 커스텀 프로퍼티).
682
- */
683
- variant: z.enum(SECTION_VARIANTS.testimonials).default("editorial-quote"),
684
- props: z.object({
685
- heading: z.string(),
686
- description: z.string().optional(),
687
- items: z.array(TestimonialItemSchema).min(1).max(12),
688
- marquee: z.boolean().optional(),
689
- /** 법정 고지 슬롯("개인 경험이며 효과는 다를 수 있습니다" 류). */
690
- disclaimer: z.string().optional()
691
- })
692
- });
693
- var TeamMemberSchema = z.object({
694
- name: z.string().min(1),
695
- title: z.string().min(1),
696
- photoSrc: z.string().optional(),
697
- photoAlt: z.string().optional(),
698
- specialties: z.array(z.string().min(1)).min(1).max(6).optional(),
699
- credentials: z.array(z.string().min(1)).min(1).max(8).optional(),
700
- bio: z.string().max(360).optional(),
701
- href: z.string().optional(),
702
- ctaLabel: z.string().optional()
703
- }).superRefine((member, ctx) => {
704
- if (member.photoSrc && !member.photoAlt) {
705
- ctx.addIssue({
706
- code: z.ZodIssueCode.custom,
707
- path: ["photoAlt"],
708
- message: "photoSrc \uAC00 \uC788\uC73C\uBA74 photoAlt \uAC00 \uD544\uC694\uD569\uB2C8\uB2E4."
709
- });
710
- }
711
- });
712
- var TeamSectionSchema = SectionBase.extend({
713
- type: z.literal("team"),
714
- variant: z.enum(SECTION_VARIANTS.team).default("grid"),
715
- props: z.object({
716
- heading: z.string().optional(),
717
- description: z.string().optional(),
718
- members: z.array(TeamMemberSchema).min(2).max(8)
719
- })
720
- });
721
- var ExpertiseItemSchema = z.object({
722
- id: z.string().min(1),
723
- label: z.string().min(1),
724
- kicker: z.string().optional(),
725
- title: z.string().min(1),
726
- body: z.string().min(1),
727
- imageSrc: z.string().optional(),
728
- imageAlt: z.string().optional(),
729
- tags: z.array(z.string().min(1)).max(8).optional(),
730
- href: z.string().optional(),
731
- ctaLabel: z.string().optional()
732
- }).superRefine((item, ctx) => {
733
- if (item.imageSrc && !item.imageAlt) {
734
- ctx.addIssue({
735
- code: z.ZodIssueCode.custom,
736
- path: ["imageAlt"],
737
- message: "imageSrc \uAC00 \uC788\uC73C\uBA74 imageAlt \uAC00 \uD544\uC694\uD569\uB2C8\uB2E4."
738
- });
739
- }
740
- });
741
- var ExpertiseSectionSchema = SectionBase.extend({
742
- type: z.literal("expertise"),
743
- variant: z.enum(SECTION_VARIANTS.expertise).default("tab-stage"),
744
- props: z.object({
745
- heading: z.string().optional(),
746
- description: z.string().optional(),
747
- items: z.array(ExpertiseItemSchema).min(2).max(6)
748
- })
749
- });
750
- var SpaceItemSchema = z.object({
751
- id: z.string().min(1),
752
- label: z.string().min(1),
753
- kicker: z.string().optional(),
754
- title: z.string().optional(),
755
- description: z.string().optional(),
756
- imageSrc: z.string().min(1),
757
- imageAlt: z.string().min(1),
758
- tags: z.array(z.string().min(1)).max(8).optional()
759
- });
760
- var SpacesSectionSchema = SectionBase.extend({
761
- type: z.literal("spaces"),
762
- variant: z.enum(SECTION_VARIANTS.spaces).default("stage"),
763
- props: z.object({
764
- heading: z.string().optional(),
765
- description: z.string().optional(),
766
- spaces: z.array(SpaceItemSchema).min(2).max(8),
767
- disclaimer: z.string().optional()
768
- })
769
- });
770
- var ConcernRegionSchema = z.object({
771
- id: z.string().min(1),
772
- label: z.string().min(1),
773
- x: z.number().min(0).max(100).optional(),
774
- y: z.number().min(0).max(100).optional(),
775
- title: z.string().min(1),
776
- description: z.string().min(1),
777
- items: z.array(z.string().min(1)).max(8).optional(),
778
- href: z.string().optional(),
779
- ctaLabel: z.string().optional()
780
- });
781
- var ConcernsSectionSchema = SectionBase.extend({
782
- type: z.literal("concerns"),
783
- variant: z.enum(SECTION_VARIANTS.concerns).default("figure-map"),
784
- props: z.object({
785
- heading: z.string().optional(),
786
- description: z.string().optional(),
787
- figureImage: z.string().optional(),
788
- figureAlt: z.string().optional(),
789
- regions: z.array(ConcernRegionSchema).min(3).max(10),
790
- disclaimer: z.string().min(1)
791
- })
792
- }).superRefine((section, ctx) => {
793
- if (section.variant !== "figure-map") return;
794
- if (!section.props.figureImage) {
795
- ctx.addIssue({
796
- code: z.ZodIssueCode.custom,
797
- path: ["props", "figureImage"],
798
- message: "figure-map concerns \uB294 figureImage \uAC00 \uD544\uC694\uD569\uB2C8\uB2E4."
799
- });
800
- }
801
- if (!section.props.figureAlt) {
802
- ctx.addIssue({
803
- code: z.ZodIssueCode.custom,
804
- path: ["props", "figureAlt"],
805
- message: "figure-map concerns \uB294 figureAlt \uAC00 \uD544\uC694\uD569\uB2C8\uB2E4."
806
- });
807
- }
808
- section.props.regions.forEach((region, index) => {
809
- if (typeof region.x !== "number") {
810
- ctx.addIssue({
811
- code: z.ZodIssueCode.custom,
812
- path: ["props", "regions", index, "x"],
813
- message: "figure-map concerns region \uC740 x \uC88C\uD45C\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4."
814
- });
815
- }
816
- if (typeof region.y !== "number") {
817
- ctx.addIssue({
818
- code: z.ZodIssueCode.custom,
819
- path: ["props", "regions", index, "y"],
820
- message: "figure-map concerns region \uC740 y \uC88C\uD45C\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4."
821
- });
822
- }
823
- });
824
- });
825
- var CustomSectionSchema = SectionBase;
826
- var BUILTIN_SECTION_SCHEMAS = {
827
- hero: HeroSectionSchema,
828
- "rich-text": RichTextSectionSchema,
829
- services: ServicesSectionSchema,
830
- faq: FaqSectionSchema,
831
- "cta-banner": CtaBannerSectionSchema,
832
- "blog-list": BlogListSectionSchema,
833
- "lead-form": LeadFormSectionSchema,
834
- trust: TrustSectionSchema,
835
- contact: ContactSectionSchema,
836
- hours: HoursSectionSchema,
837
- pricing: PricingSectionSchema,
838
- process: ProcessSectionSchema,
839
- "before-after": BeforeAfterSectionSchema,
840
- testimonials: TestimonialsSectionSchema,
841
- team: TeamSectionSchema,
842
- expertise: ExpertiseSectionSchema,
843
- spaces: SpacesSectionSchema,
844
- concerns: ConcernsSectionSchema,
845
- footer: FooterSectionSchema
846
- };
847
- var BUILTIN_SECTION_TYPES = Object.keys(
848
- BUILTIN_SECTION_SCHEMAS
849
- );
850
- var SectionSchema = z.object({ type: z.string() }).passthrough().transform((section, ctx) => {
851
- const schema = BUILTIN_SECTION_SCHEMAS[section.type] ?? CustomSectionSchema;
852
- const result = schema.safeParse(section);
853
- if (!result.success) {
854
- for (const issue of result.error.issues) ctx.addIssue(issue);
855
- return z.NEVER;
856
- }
857
- return result.data;
858
- });
859
- var CollectionSchema = z.object({
860
- /** 안정 식별자 (라우트/가드/파생 키). */
861
- key: z.string(),
862
- /** 작성자 카드·nav 라벨. */
863
- label: z.string(),
864
- /** 라우트 base path (예: "/notice" | "/blog"). 앞 슬래시 포함. */
865
- basePath: z.string(),
866
- /** 레이아웃 레지스트리 키 (board=게시판, grid=카드 그리드, …). 미지 키는 렌더러 fallback. */
867
- layout: z.string().default("grid"),
868
- /** 이 스트림에 속하는 category slug 들. */
869
- categories: z.array(z.string()).default([]),
870
- /** RSS 피드 포함. */
871
- feed: z.boolean().default(false),
872
- /** `${basePath}/categories/{slug}` 아카이브 생성. */
873
- archives: z.boolean().default(false),
874
- /** 글별 동적 OG. */
875
- og: z.boolean().default(false),
876
- /** 작성 UX: 하위 카테고리 1개 필수(미선택 발행 차단). */
877
- requireSubcategory: z.boolean().default(false),
878
- /** 작성 UX: 상단고정 토글 노출. */
879
- pinning: z.boolean().default(false),
880
- /** nav/카드 아이콘 키 (선택). */
881
- icon: z.string().optional()
882
- });
883
- var SiteConfigSchema = z.object({
884
- /**
885
- * `SiteConfig` 계약 버전 (ADR-0086 D2) — 팩 manifest 의 `siteConfigVersion`
886
- * 과 비교해 팩↔renderer 호환성을 표현한다. optional — 하위 호환(기존 고객
887
- * `site.config.ts` 에는 이 필드가 없다). 미지정은 버전 1 로 해석한다
888
- * (`resolveSiteConfigSchemaVersion`).
889
- */
890
- schemaVersion: z.number().int().positive().optional(),
891
- site: z.object({
892
- /** canonical origin. 생략 시 소비측이 `SITE_URL` env 등으로 해석. */
893
- url: z.string().url().optional(),
894
- title: z.string(),
895
- description: z.string().optional(),
896
- locale: z.string().default("ko-KR"),
897
- og: z.object({ image: z.string().optional() }).optional()
898
- }),
899
- /**
900
- * 콘텐츠 스트림 분리 (ADR-0060). 비우면 단일 /blog(레거시 동작). 선언 순서 =
901
- * 라우팅 우선순위. `routes.ts` 팩토리에 `collections` 로 전달해 sitemap/feed/
902
- * 아카이브를 스트림별로 파생.
903
- */
904
- collections: z.array(CollectionSchema).default([]),
905
- /** 사이트 전역 토큰 override (`--rt-*` CSS 변수 → 값). 비우면 admin /design SSoT. */
906
- theme: z.record(z.string()).default({}),
907
- /**
908
- * 테마 방향(Theme Directions, `@roottale/tokens` 프리셋 id — 예: "d1-editorial").
909
- * root layout 이 `<html data-rt-direction="…">` 로 부여해
910
- * `@roottale/tokens/theme-directions.css` 의 `[data-rt-direction="…"]` 규칙을
911
- * 활성화한다(roottale-docs `docs/design/theme-directions.md`). 값은 문자열로만
912
- * 검증한다 — `@roottale/cms-renderer-next` 는 `@roottale/tokens` 를 직접
913
- * 의존하지 않으므로 유효 id 집합은 `@roottale/tokens/theme-directions` 의
914
- * `ThemeDirectionId` 를 참조해 `site.config.ts` 작성 시점에 타입으로 좁힌다.
915
- * 미지정 시 속성 자체가 안 붙어 완전히 무변화(기존 동작).
916
- */
917
- direction: z.string().optional(),
918
- nav: z.array(NavItemSchema).default([]),
919
- /** path("/", "/services", …) → 섹션 배열. */
920
- pages: z.record(z.array(SectionSchema))
921
- });
922
- function parseSiteConfig(input) {
923
- return SiteConfigSchema.parse(input);
924
- }
925
- function defineSiteConfig(input) {
926
- return parseSiteConfig(input);
927
- }
928
-
929
- export {
930
- SITE_CONFIG_SCHEMA_VERSION,
931
- resolveSiteConfigSchemaVersion,
932
- NavItemSchema,
933
- SECTION_VARIANTS,
934
- HeroSectionSchema,
935
- RichTextSectionSchema,
936
- ServicesSectionSchema,
937
- FooterSectionSchema,
938
- FaqSectionSchema,
939
- CtaBannerSectionSchema,
940
- BlogListSectionSchema,
941
- LeadFormSectionSchema,
942
- TrustSectionSchema,
943
- ContactSectionSchema,
944
- HoursSectionSchema,
945
- PricingSectionSchema,
946
- ProcessSectionSchema,
947
- BeforeAfterSectionSchema,
948
- TestimonialsSectionSchema,
949
- TeamSectionSchema,
950
- ExpertiseSectionSchema,
951
- SpacesSectionSchema,
952
- ConcernsSectionSchema,
953
- CustomSectionSchema,
954
- BUILTIN_SECTION_SCHEMAS,
955
- BUILTIN_SECTION_TYPES,
956
- SectionSchema,
957
- SiteConfigSchema,
958
- parseSiteConfig,
959
- defineSiteConfig
960
- };
961
- //# sourceMappingURL=chunk-UM37DOIK.js.map