@roottale/cms-renderer-next 0.40.0 → 0.41.1

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.
@@ -0,0 +1,1647 @@
1
+ import {
2
+ collectCategories,
3
+ collectCategoriesWithCounts,
4
+ collectCollectionCategories,
5
+ defaultCategoryHref,
6
+ filterPostsByCategory
7
+ } from "./chunk-FUA5H6JE.js";
8
+ import {
9
+ SectionSchema
10
+ } from "./chunk-IOYBEH6E.js";
11
+
12
+ // src/server.tsx
13
+ import {
14
+ DEFAULT_BLOG_SETTINGS,
15
+ fetchBlogSettings,
16
+ fetchPost,
17
+ fetchPosts,
18
+ fetchTheme,
19
+ resolvePostDisplay,
20
+ selectRelatedPosts
21
+ } from "@roottale/cms-client/server";
22
+ import {
23
+ bindingSrcValue,
24
+ bindingTextValue,
25
+ boundFieldValue,
26
+ extractFaqEntries,
27
+ faqPageJsonLd,
28
+ isAllowedEmbedSrc as isAllowedEmbedSrc2,
29
+ postsForCollection,
30
+ resolvePostPath
31
+ } from "@roottale/cms-core";
32
+
33
+ // src/LeadForm.tsx
34
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
35
+ var VERTICAL_LABEL = {
36
+ consulting: "\uCEE8\uC124\uD305",
37
+ medical: "\uC758\uB8CC",
38
+ tax: "\uC138\uBB34",
39
+ legal: "\uBC95\uB960"
40
+ };
41
+ var VERTICAL_OPTIONS = [
42
+ { value: "consulting", label: "\uCEE8\uC124\uD305 / \uC77C\uBC18" },
43
+ { value: "medical", label: "\uC758\uB8CC (\uBCD1\uC758\uC6D0\xB7\uCE58\uACFC\xB7\uD55C\uC758\uC6D0)" },
44
+ { value: "tax", label: "\uC138\uBB34 (\uC138\uBB34\uC0AC\xB7\uD68C\uACC4\uC0AC)" },
45
+ { value: "legal", label: "\uBC95\uB960 (\uBC95\uBB34\uBC95\uC778\xB7\uBCC0\uD638\uC0AC)" }
46
+ ];
47
+ function RootTaleLeadForm(props) {
48
+ const {
49
+ action,
50
+ vertical,
51
+ redirectUrl,
52
+ heading,
53
+ description,
54
+ submitLabel = "\uC9C4\uB2E8 \uC2E0\uCCAD",
55
+ turnstileSiteKey,
56
+ className
57
+ } = props;
58
+ const formClass = ["rt-cms-lead-form", className].filter(Boolean).join(" ");
59
+ return /* @__PURE__ */ jsxs(
60
+ "form",
61
+ {
62
+ method: "post",
63
+ action,
64
+ "data-roottale-cms": "lead-form",
65
+ className: formClass,
66
+ children: [
67
+ heading ? /* @__PURE__ */ jsx("h2", { className: "rt-cms-lead-heading", children: heading }) : null,
68
+ description ? /* @__PURE__ */ jsx("p", { className: "rt-cms-lead-description", children: description }) : null,
69
+ redirectUrl ? /* @__PURE__ */ jsx("input", { type: "hidden", name: "_redirect_url", value: redirectUrl }) : null,
70
+ vertical ? /* @__PURE__ */ jsxs(Fragment, { children: [
71
+ /* @__PURE__ */ jsx("input", { type: "hidden", name: "vertical", value: vertical }),
72
+ /* @__PURE__ */ jsxs("p", { className: "rt-cms-lead-vertical-label", children: [
73
+ "\uBD84\uC57C: ",
74
+ /* @__PURE__ */ jsx("strong", { children: VERTICAL_LABEL[vertical] })
75
+ ] })
76
+ ] }) : /* @__PURE__ */ jsxs("label", { className: "rt-cms-field", children: [
77
+ /* @__PURE__ */ jsxs("span", { className: "rt-cms-field-label", children: [
78
+ "\uBD84\uC57C ",
79
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "*" })
80
+ ] }),
81
+ /* @__PURE__ */ jsxs(
82
+ "select",
83
+ {
84
+ name: "vertical",
85
+ required: true,
86
+ className: "rt-cms-field-input rt-cms-field-select",
87
+ defaultValue: "",
88
+ children: [
89
+ /* @__PURE__ */ jsx("option", { value: "", disabled: true, children: "\uC120\uD0DD\uD558\uC138\uC694" }),
90
+ VERTICAL_OPTIONS.map((opt) => /* @__PURE__ */ jsx("option", { value: opt.value, children: opt.label }, opt.value))
91
+ ]
92
+ }
93
+ )
94
+ ] }),
95
+ /* @__PURE__ */ jsxs("label", { className: "rt-cms-field", children: [
96
+ /* @__PURE__ */ jsxs("span", { className: "rt-cms-field-label", children: [
97
+ "\uC774\uB984 ",
98
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "*" })
99
+ ] }),
100
+ /* @__PURE__ */ jsx(
101
+ "input",
102
+ {
103
+ type: "text",
104
+ name: "contact_name",
105
+ required: true,
106
+ autoComplete: "name",
107
+ className: "rt-cms-field-input"
108
+ }
109
+ )
110
+ ] }),
111
+ /* @__PURE__ */ jsxs("label", { className: "rt-cms-field", children: [
112
+ /* @__PURE__ */ jsxs("span", { className: "rt-cms-field-label", children: [
113
+ "\uC0AC\uC5C5\uCCB4\uBA85 ",
114
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "*" })
115
+ ] }),
116
+ /* @__PURE__ */ jsx(
117
+ "input",
118
+ {
119
+ type: "text",
120
+ name: "business_name",
121
+ required: true,
122
+ autoComplete: "organization",
123
+ className: "rt-cms-field-input"
124
+ }
125
+ )
126
+ ] }),
127
+ /* @__PURE__ */ jsxs("label", { className: "rt-cms-field", children: [
128
+ /* @__PURE__ */ jsxs("span", { className: "rt-cms-field-label", children: [
129
+ "\uC774\uBA54\uC77C ",
130
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "*" })
131
+ ] }),
132
+ /* @__PURE__ */ jsx(
133
+ "input",
134
+ {
135
+ type: "email",
136
+ name: "email",
137
+ required: true,
138
+ autoComplete: "email",
139
+ className: "rt-cms-field-input"
140
+ }
141
+ )
142
+ ] }),
143
+ /* @__PURE__ */ jsxs("label", { className: "rt-cms-field", children: [
144
+ /* @__PURE__ */ jsxs("span", { className: "rt-cms-field-label", children: [
145
+ "\uC804\uD654\uBC88\uD638 ",
146
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "*" })
147
+ ] }),
148
+ /* @__PURE__ */ jsx(
149
+ "input",
150
+ {
151
+ type: "tel",
152
+ name: "phone",
153
+ required: true,
154
+ autoComplete: "tel",
155
+ inputMode: "tel",
156
+ pattern: "0\\d{1,2}-?\\d{3,4}-?\\d{4}",
157
+ placeholder: "010-1234-5678",
158
+ className: "rt-cms-field-input"
159
+ }
160
+ )
161
+ ] }),
162
+ /* @__PURE__ */ jsxs("label", { className: "rt-cms-field", children: [
163
+ /* @__PURE__ */ jsx("span", { className: "rt-cms-field-label", children: "\uD604\uC7AC \uC0AC\uC774\uD2B8 URL (\uC120\uD0DD)" }),
164
+ /* @__PURE__ */ jsx(
165
+ "input",
166
+ {
167
+ type: "url",
168
+ name: "current_site_url",
169
+ placeholder: "https://",
170
+ className: "rt-cms-field-input"
171
+ }
172
+ ),
173
+ /* @__PURE__ */ jsx("span", { className: "rt-cms-field-hint", children: "\uC5C6\uC73C\uBA74 \uBE44\uC6CC\uB450\uC138\uC694." })
174
+ ] }),
175
+ /* @__PURE__ */ jsxs("label", { className: "rt-cms-lead-consent", children: [
176
+ /* @__PURE__ */ jsx("input", { type: "checkbox", name: "privacy_consent", required: true }),
177
+ /* @__PURE__ */ jsxs("span", { children: [
178
+ /* @__PURE__ */ jsx("strong", { children: "(\uD544\uC218)" }),
179
+ " \uAC1C\uC778\uC815\uBCF4 \uC218\uC9D1\xB7\uC774\uC6A9\uC5D0 \uB3D9\uC758\uD569\uB2C8\uB2E4. (\uC218\uC9D1 \uD56D\uBAA9: \uC774\uB984\xB7\uC5F0\uB77D\uCC98\xB7\uC774\uBA54\uC77C\xB7\uC0AC\uC5C5\uCCB4\uBA85. \uBCF4\uAD00 \uAE30\uAC04: \uBB38\uC758 \uC885\uB8CC \uD6C4 3\uB144)"
180
+ ] })
181
+ ] }),
182
+ vertical === "medical" ? /* @__PURE__ */ jsxs("label", { className: "rt-cms-lead-consent", children: [
183
+ /* @__PURE__ */ jsx(
184
+ "input",
185
+ {
186
+ type: "checkbox",
187
+ name: "overseas_transfer_consent",
188
+ required: true
189
+ }
190
+ ),
191
+ /* @__PURE__ */ jsxs("span", { children: [
192
+ /* @__PURE__ */ jsx("strong", { children: "(\uD544\uC218, \uC758\uB8CC)" }),
193
+ " \uC758\uB8CC PII \uC758 \uAD6D\uC678\uC774\uC804(\uBCF4\uAD00\xB7\uCC98\uB9AC)\uC5D0 \uB3D9\uC758\uD569\uB2C8\uB2E4. (\uC758\uB8CC\uBC95 \xA721\xB7\uAC1C\uC778\uC815\uBCF4\uBCF4\uD638\uBC95 \xA728 \u2014 ADR-0018)"
194
+ ] })
195
+ ] }) : null,
196
+ turnstileSiteKey ? /* @__PURE__ */ jsxs(Fragment, { children: [
197
+ /* @__PURE__ */ jsx(
198
+ "script",
199
+ {
200
+ src: "https://challenges.cloudflare.com/turnstile/v0/api.js",
201
+ async: true,
202
+ defer: true
203
+ }
204
+ ),
205
+ /* @__PURE__ */ jsx(
206
+ "div",
207
+ {
208
+ className: "cf-turnstile",
209
+ "data-sitekey": turnstileSiteKey,
210
+ "data-action": "turnstile-spin-v1"
211
+ }
212
+ )
213
+ ] }) : null,
214
+ /* @__PURE__ */ jsx("div", { className: "rt-cms-lead-actions", children: /* @__PURE__ */ jsx("button", { type: "submit", className: "rt-cms-lead-submit", children: submitLabel }) })
215
+ ]
216
+ }
217
+ );
218
+ }
219
+
220
+ // src/templates/sections.tsx
221
+ import { isAllowedEmbedSrc } from "@roottale/cms-core";
222
+ import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
223
+ function sectionThemeStyle(theme) {
224
+ if (!theme) return void 0;
225
+ const style = {};
226
+ for (const [key, value] of Object.entries(theme)) {
227
+ if (key.startsWith("--")) style[key] = value;
228
+ }
229
+ return Object.keys(style).length > 0 ? style : void 0;
230
+ }
231
+ function CtaLink({
232
+ cta,
233
+ className,
234
+ track
235
+ }) {
236
+ return /* @__PURE__ */ jsx2(
237
+ "a",
238
+ {
239
+ className: className ?? "rt-tpl-btn rt-tpl-btn--primary",
240
+ href: cta.href,
241
+ "data-track": track,
242
+ children: cta.label
243
+ }
244
+ );
245
+ }
246
+ function TemplateHero(section) {
247
+ const { eyebrow, title, description, imageSrc, imageAlt, cta } = section.props;
248
+ const variant = section.variant;
249
+ const showImage = variant !== "minimal" && imageSrc;
250
+ return /* @__PURE__ */ jsx2(
251
+ "section",
252
+ {
253
+ "data-roottale-tpl": "hero",
254
+ "data-variant": variant,
255
+ id: section.id,
256
+ className: "rt-tpl-section rt-tpl-hero",
257
+ style: sectionThemeStyle(section.theme),
258
+ children: /* @__PURE__ */ jsxs2("div", { className: "rt-tpl-container rt-tpl-hero-inner", children: [
259
+ /* @__PURE__ */ jsxs2("div", { className: "rt-tpl-hero-copy", children: [
260
+ eyebrow ? /* @__PURE__ */ jsx2("p", { className: "rt-tpl-hero-eyebrow", children: eyebrow }) : null,
261
+ /* @__PURE__ */ jsx2("h1", { className: "rt-tpl-hero-title", children: title.split("\n").map((line, i) => /* @__PURE__ */ jsx2("span", { className: "rt-tpl-hero-title-line", children: line }, i)) }),
262
+ description ? /* @__PURE__ */ jsx2("p", { className: "rt-tpl-hero-desc", children: description }) : null,
263
+ cta ? /* @__PURE__ */ jsx2(CtaLink, { cta, track: "cta_hero" }) : null
264
+ ] }),
265
+ showImage ? /* @__PURE__ */ jsx2("div", { className: "rt-tpl-hero-media", children: /* @__PURE__ */ jsx2(
266
+ "img",
267
+ {
268
+ className: "rt-tpl-hero-img",
269
+ src: imageSrc,
270
+ alt: imageAlt ?? "",
271
+ loading: "eager"
272
+ }
273
+ ) }) : null
274
+ ] })
275
+ }
276
+ );
277
+ }
278
+ function TemplateRichText(section) {
279
+ const { heading, body } = section.props;
280
+ return /* @__PURE__ */ jsx2(
281
+ "section",
282
+ {
283
+ "data-roottale-tpl": "rich-text",
284
+ id: section.id,
285
+ className: "rt-tpl-section rt-tpl-rich-text",
286
+ style: sectionThemeStyle(section.theme),
287
+ children: /* @__PURE__ */ jsxs2("div", { className: "rt-tpl-container", children: [
288
+ heading ? /* @__PURE__ */ jsx2("h2", { className: "rt-tpl-heading", children: heading }) : null,
289
+ body.split("\n\n").map((para, i) => /* @__PURE__ */ jsx2("p", { className: "rt-tpl-rich-text-p", children: para }, i))
290
+ ] })
291
+ }
292
+ );
293
+ }
294
+ function TemplateServices(section) {
295
+ const { heading, description, items } = section.props;
296
+ return /* @__PURE__ */ jsx2(
297
+ "section",
298
+ {
299
+ "data-roottale-tpl": "services",
300
+ "data-variant": section.variant,
301
+ id: section.id,
302
+ className: "rt-tpl-section rt-tpl-services",
303
+ style: sectionThemeStyle(section.theme),
304
+ children: /* @__PURE__ */ jsxs2("div", { className: "rt-tpl-container", children: [
305
+ heading ? /* @__PURE__ */ jsx2("h2", { className: "rt-tpl-heading", children: heading }) : null,
306
+ description ? /* @__PURE__ */ jsx2("p", { className: "rt-tpl-section-desc", children: description }) : null,
307
+ /* @__PURE__ */ jsx2("ul", { className: "rt-tpl-services-list", children: items.map((item, i) => {
308
+ const body = /* @__PURE__ */ jsxs2(Fragment2, { children: [
309
+ /* @__PURE__ */ jsx2("h3", { className: "rt-tpl-services-title", children: item.title }),
310
+ item.description ? /* @__PURE__ */ jsx2("p", { className: "rt-tpl-services-desc", children: item.description }) : null
311
+ ] });
312
+ return /* @__PURE__ */ jsx2("li", { className: "rt-tpl-services-item", children: item.href ? /* @__PURE__ */ jsx2(
313
+ "a",
314
+ {
315
+ className: "rt-tpl-services-link",
316
+ href: item.href,
317
+ "data-track": "service_card",
318
+ children: body
319
+ }
320
+ ) : body }, i);
321
+ }) })
322
+ ] })
323
+ }
324
+ );
325
+ }
326
+ function TemplateFaq(section) {
327
+ const { heading, items, jsonLd } = section.props;
328
+ return /* @__PURE__ */ jsxs2(
329
+ "section",
330
+ {
331
+ "data-roottale-tpl": "faq",
332
+ id: section.id,
333
+ className: "rt-tpl-section rt-tpl-faq",
334
+ style: sectionThemeStyle(section.theme),
335
+ children: [
336
+ /* @__PURE__ */ jsxs2("div", { className: "rt-tpl-container", children: [
337
+ heading ? /* @__PURE__ */ jsx2("h2", { className: "rt-tpl-heading", children: heading }) : null,
338
+ /* @__PURE__ */ jsx2("div", { className: "rt-tpl-faq-list", children: items.map((item, i) => /* @__PURE__ */ jsxs2("details", { className: "rt-tpl-faq-item", children: [
339
+ /* @__PURE__ */ jsx2("summary", { className: "rt-tpl-faq-q", children: item.question }),
340
+ /* @__PURE__ */ jsx2("p", { className: "rt-tpl-faq-a", children: item.answer })
341
+ ] }, i)) })
342
+ ] }),
343
+ jsonLd ? /* @__PURE__ */ jsx2(
344
+ "script",
345
+ {
346
+ type: "application/ld+json",
347
+ dangerouslySetInnerHTML: {
348
+ __html: JSON.stringify({
349
+ "@context": "https://schema.org",
350
+ "@type": "FAQPage",
351
+ mainEntity: items.map((item) => ({
352
+ "@type": "Question",
353
+ name: item.question,
354
+ acceptedAnswer: { "@type": "Answer", text: item.answer }
355
+ }))
356
+ })
357
+ }
358
+ }
359
+ ) : null
360
+ ]
361
+ }
362
+ );
363
+ }
364
+ function TemplateCtaBanner(section) {
365
+ const { title, description, cta } = section.props;
366
+ return /* @__PURE__ */ jsx2(
367
+ "section",
368
+ {
369
+ "data-roottale-tpl": "cta-banner",
370
+ id: section.id,
371
+ className: "rt-tpl-section rt-tpl-cta-banner",
372
+ style: sectionThemeStyle(section.theme),
373
+ children: /* @__PURE__ */ jsxs2("div", { className: "rt-tpl-container rt-tpl-cta-banner-inner", children: [
374
+ /* @__PURE__ */ jsx2("h2", { className: "rt-tpl-cta-banner-title", children: title }),
375
+ description ? /* @__PURE__ */ jsx2("p", { className: "rt-tpl-cta-banner-desc", children: description }) : null,
376
+ cta ? /* @__PURE__ */ jsx2(CtaLink, { cta, track: "cta_banner" }) : null
377
+ ] })
378
+ }
379
+ );
380
+ }
381
+ function TrustCredentialIcon() {
382
+ return /* @__PURE__ */ jsxs2(
383
+ "svg",
384
+ {
385
+ className: "rt-cred__icon",
386
+ viewBox: "0 0 16 16",
387
+ "aria-hidden": "true",
388
+ focusable: "false",
389
+ children: [
390
+ /* @__PURE__ */ jsx2("path", { d: "M8 1.75 13 3.5v3.75c0 3-2 5.45-5 7-3-1.55-5-4-5-7V3.5z" }),
391
+ /* @__PURE__ */ jsx2("path", { d: "m5.75 8 1.45 1.45 3.2-3.4" })
392
+ ]
393
+ }
394
+ );
395
+ }
396
+ function TemplateTrust(section) {
397
+ const { heading, metrics, credentials, logos, disclaimer } = section.props;
398
+ return /* @__PURE__ */ jsx2(
399
+ "section",
400
+ {
401
+ "data-roottale-tpl": "trust",
402
+ "data-variant": section.variant,
403
+ id: section.id,
404
+ className: "rt-tpl-section rt-tpl-trust",
405
+ style: sectionThemeStyle(section.theme),
406
+ children: /* @__PURE__ */ jsxs2("div", { className: "rt-tpl-container rt-tpl-trust-inner", children: [
407
+ heading ? /* @__PURE__ */ jsx2("h2", { className: "rt-tpl-heading", children: heading }) : null,
408
+ section.variant === "stat-band" ? /* @__PURE__ */ jsx2("div", { className: "rt-tpl-trust-stat-band", children: (metrics ?? []).map((metric) => /* @__PURE__ */ jsxs2(
409
+ "div",
410
+ {
411
+ className: "rt-stat",
412
+ "data-variant": "band-item",
413
+ children: [
414
+ /* @__PURE__ */ jsxs2("span", { className: "rt-stat__value", children: [
415
+ metric.value,
416
+ metric.unit ? /* @__PURE__ */ jsx2("span", { className: "rt-stat__unit", children: metric.unit }) : null
417
+ ] }),
418
+ /* @__PURE__ */ jsx2("span", { className: "rt-stat__label", children: metric.label }),
419
+ metric.source ? /* @__PURE__ */ jsx2("span", { className: "rt-stat__source", children: metric.source }) : null
420
+ ]
421
+ },
422
+ `${metric.value}-${metric.label}`
423
+ )) }) : null,
424
+ section.variant === "logo-cloud" ? /* @__PURE__ */ jsx2("ul", { className: "rt-tpl-trust-logo-list", children: (logos ?? []).map((logo) => /* @__PURE__ */ jsx2("li", { className: "rt-tpl-trust-logo-item", children: logo.href ? /* @__PURE__ */ jsx2("a", { className: "rt-tpl-trust-logo-link", href: logo.href, children: /* @__PURE__ */ jsx2(
425
+ "img",
426
+ {
427
+ className: "rt-tpl-trust-logo",
428
+ src: logo.src,
429
+ alt: logo.alt,
430
+ loading: "lazy"
431
+ }
432
+ ) }) : /* @__PURE__ */ jsx2(
433
+ "img",
434
+ {
435
+ className: "rt-tpl-trust-logo",
436
+ src: logo.src,
437
+ alt: logo.alt,
438
+ loading: "lazy"
439
+ }
440
+ ) }, `${logo.src}-${logo.alt}`)) }) : null,
441
+ section.variant === "feature-grid" ? /* @__PURE__ */ jsx2("ul", { className: "rt-tpl-trust-cred-list", children: (credentials ?? []).map((credential) => /* @__PURE__ */ jsx2("li", { children: /* @__PURE__ */ jsxs2("span", { className: "rt-cred", "data-kind": credential.kind, children: [
442
+ /* @__PURE__ */ jsx2(TrustCredentialIcon, {}),
443
+ /* @__PURE__ */ jsx2("span", { className: "rt-cred__label", children: credential.name }),
444
+ credential.issuer ? /* @__PURE__ */ jsx2("span", { className: "rt-cred__issuer", children: credential.issuer }) : null
445
+ ] }) }, `${credential.name}-${credential.issuer ?? ""}`)) }) : null,
446
+ disclaimer ? /* @__PURE__ */ jsx2("p", { className: "rt-tpl-trust-disclaimer", children: disclaimer }) : null
447
+ ] })
448
+ }
449
+ );
450
+ }
451
+ function telHref(phone) {
452
+ return `tel:${phone.replace(/[^\d+]/g, "")}`;
453
+ }
454
+ function ContactMap({ url }) {
455
+ if (isAllowedEmbedSrc(url)) {
456
+ return /* @__PURE__ */ jsx2("div", { className: "rt-tpl-contact-map", children: /* @__PURE__ */ jsx2(
457
+ "iframe",
458
+ {
459
+ src: url,
460
+ title: "\uC624\uC2DC\uB294\uAE38 \uC9C0\uB3C4",
461
+ loading: "lazy",
462
+ referrerPolicy: "no-referrer-when-downgrade"
463
+ }
464
+ ) });
465
+ }
466
+ return /* @__PURE__ */ jsx2("p", { className: "rt-tpl-contact-map-fallback", children: /* @__PURE__ */ jsx2("a", { href: url, rel: "noopener noreferrer", target: "_blank", children: "\uAE38\uCC3E\uAE30" }) });
467
+ }
468
+ function TemplateContact(section) {
469
+ const { heading, form, phone, address, hours, mapEmbedUrl } = section.props;
470
+ return /* @__PURE__ */ jsx2(
471
+ "section",
472
+ {
473
+ "data-roottale-tpl": "contact",
474
+ "data-variant": section.variant,
475
+ id: section.id,
476
+ className: "rt-tpl-section rt-tpl-contact",
477
+ style: sectionThemeStyle(section.theme),
478
+ children: /* @__PURE__ */ jsxs2("div", { className: "rt-tpl-container rt-tpl-contact-inner", children: [
479
+ /* @__PURE__ */ jsxs2("div", { className: "rt-tpl-contact-info", children: [
480
+ heading ? /* @__PURE__ */ jsx2("h2", { className: "rt-tpl-heading", children: heading }) : null,
481
+ phone ? /* @__PURE__ */ jsx2("a", { className: "rt-tpl-contact-phone", href: telHref(phone), "data-track": "cta_call", children: phone }) : null,
482
+ address ? /* @__PURE__ */ jsx2("address", { className: "rt-tpl-contact-address", children: address }) : null,
483
+ hours ? /* @__PURE__ */ jsxs2("table", { className: "rt-tpl-contact-hours", children: [
484
+ /* @__PURE__ */ jsx2("caption", { className: "rt-tpl-sr-only", children: "\uC9C4\uB8CC\uC2DC\uAC04" }),
485
+ /* @__PURE__ */ jsx2("tbody", { children: hours.map((row) => /* @__PURE__ */ jsxs2("tr", { children: [
486
+ /* @__PURE__ */ jsx2("th", { scope: "row", children: row.day }),
487
+ /* @__PURE__ */ jsxs2("td", { children: [
488
+ /* @__PURE__ */ jsx2("span", { children: row.open }),
489
+ /* @__PURE__ */ jsx2("span", { "aria-hidden": "true", children: "-" }),
490
+ /* @__PURE__ */ jsx2("span", { children: row.close }),
491
+ row.note ? /* @__PURE__ */ jsx2("span", { className: "rt-tpl-contact-hours-note", children: row.note }) : null
492
+ ] })
493
+ ] }, `${row.day}-${row.open}-${row.close}`)) })
494
+ ] }) : null,
495
+ mapEmbedUrl ? /* @__PURE__ */ jsx2(ContactMap, { url: mapEmbedUrl }) : null
496
+ ] }),
497
+ form.enabled ? /* @__PURE__ */ jsx2("div", { className: "rt-tpl-contact-form", children: /* @__PURE__ */ jsx2(RootTaleLeadForm, { action: "/api/inquiry", vertical: form.vertical }) }) : null
498
+ ] })
499
+ }
500
+ );
501
+ }
502
+
503
+ // src/render-section-node.tsx
504
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
505
+ function warnSkippedCmsBound(sectionType, isDev) {
506
+ if (isDev()) {
507
+ console.warn(
508
+ `[cms-renderer-next] section \uB178\uB4DC sectionType=${sectionType} \uB294 apiKey \uCEE8\uD14D\uC2A4\uD2B8\uAC00 \uC5C6\uC5B4 \uB80C\uB354 skip`
509
+ );
510
+ }
511
+ }
512
+ function renderSectionNode(node, key, context) {
513
+ const attrs = node.attrs ?? {};
514
+ const input = {
515
+ type: typeof attrs.sectionType === "string" ? attrs.sectionType : "",
516
+ variant: attrs.variant ?? void 0,
517
+ theme: attrs.theme ?? void 0,
518
+ props: attrs.props ?? {}
519
+ };
520
+ const parsed = SectionSchema.safeParse(input);
521
+ if (!parsed.success) {
522
+ if (context.isDev()) {
523
+ console.warn(
524
+ `[cms-renderer-next] section \uB178\uB4DC parse \uC2E4\uD328 (sectionType=${input.type}) \u2014 \uB80C\uB354 skip:`,
525
+ parsed.error.issues
526
+ );
527
+ }
528
+ return null;
529
+ }
530
+ const section = parsed.data;
531
+ switch (section.type) {
532
+ case "hero":
533
+ return /* @__PURE__ */ jsx3(TemplateHero, { ...section }, key);
534
+ case "rich-text":
535
+ return /* @__PURE__ */ jsx3(TemplateRichText, { ...section }, key);
536
+ case "services":
537
+ return /* @__PURE__ */ jsx3(TemplateServices, { ...section }, key);
538
+ case "faq":
539
+ return /* @__PURE__ */ jsx3(TemplateFaq, { ...section }, key);
540
+ case "cta-banner":
541
+ return /* @__PURE__ */ jsx3(TemplateCtaBanner, { ...section }, key);
542
+ case "trust":
543
+ return /* @__PURE__ */ jsx3(TemplateTrust, { ...section }, key);
544
+ case "contact":
545
+ if (!context.cms) {
546
+ warnSkippedCmsBound(section.type, context.isDev);
547
+ return null;
548
+ }
549
+ return /* @__PURE__ */ jsx3(TemplateContact, { ...section }, key);
550
+ case "blog-list": {
551
+ if (!context.cms) {
552
+ warnSkippedCmsBound(section.type, context.isDev);
553
+ return null;
554
+ }
555
+ const s = section;
556
+ const base = s.props.blogBasePath ?? "/blog";
557
+ return /* @__PURE__ */ jsx3(
558
+ "section",
559
+ {
560
+ "data-roottale-tpl": "blog-list",
561
+ "data-roottale-cms": "list",
562
+ id: s.id,
563
+ className: "rt-tpl-section rt-tpl-blog-list",
564
+ style: sectionThemeStyle(s.theme),
565
+ children: /* @__PURE__ */ jsxs3("div", { className: "rt-tpl-container", children: [
566
+ s.props.heading ? /* @__PURE__ */ jsx3("h2", { className: "rt-tpl-heading", children: s.props.heading }) : null,
567
+ context.renderBlogList({
568
+ apiKey: context.cms.apiKey,
569
+ baseUrl: context.cms.baseUrl,
570
+ limit: s.props.limit,
571
+ emptyMessage: s.props.emptyMessage,
572
+ postHref: (post) => `${base}/${post.slug}`
573
+ })
574
+ ] })
575
+ },
576
+ key
577
+ );
578
+ }
579
+ case "lead-form": {
580
+ if (!context.cms) {
581
+ warnSkippedCmsBound(section.type, context.isDev);
582
+ return null;
583
+ }
584
+ const s = section;
585
+ return /* @__PURE__ */ jsx3(
586
+ "section",
587
+ {
588
+ "data-roottale-tpl": "lead-form",
589
+ "data-roottale-cms": "lead-form",
590
+ id: s.id,
591
+ className: "rt-tpl-section rt-tpl-lead-form",
592
+ style: sectionThemeStyle(s.theme),
593
+ children: /* @__PURE__ */ jsx3("div", { className: "rt-tpl-container", children: /* @__PURE__ */ jsx3(
594
+ RootTaleLeadForm,
595
+ {
596
+ action: s.props.action,
597
+ vertical: s.props.vertical,
598
+ heading: s.props.heading,
599
+ description: s.props.description,
600
+ submitLabel: s.props.submitLabel
601
+ }
602
+ ) })
603
+ },
604
+ key
605
+ );
606
+ }
607
+ default:
608
+ if (context.isDev()) {
609
+ console.warn(
610
+ `[cms-renderer-next] section \uB178\uB4DC sectionType=${section.type} \uB294 renderNode \uC704\uC784 \uB300\uC0C1\uC774 \uC544\uB2D8(cms-bound/\uBBF8\uC9C0) \u2014 skip`
611
+ );
612
+ }
613
+ return null;
614
+ }
615
+ }
616
+
617
+ // src/PostCard.tsx
618
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
619
+ function firstCategoryName(terms) {
620
+ if (!terms) return null;
621
+ const cat = terms.find((t) => t.taxonomy === "category");
622
+ return cat ? cat.name : null;
623
+ }
624
+ function RootTalePostCard(props) {
625
+ const { post, href = defaultHref, display } = props;
626
+ const showDate = display ? display.showDate : true;
627
+ const showAuthor = display ? display.showAuthor : false;
628
+ const showImage = display?.showImage !== false;
629
+ const showCategory = display?.showCategory !== false;
630
+ const authorName = post.authorName;
631
+ const publishedDate = showDate ? formatPublishedDate(post.publishedAt) : "";
632
+ const views = typeof post.viewCount === "number" ? post.viewCount : null;
633
+ const imageUrl = showImage ? post.featuredImageUrl : null;
634
+ const category = showCategory ? firstCategoryName(post.terms) : null;
635
+ return /* @__PURE__ */ jsx4("article", { "data-roottale-cms": "card", className: "rt-cms-card", children: /* @__PURE__ */ jsxs4("a", { className: "rt-cms-card-link", href: href(post), children: [
636
+ imageUrl ? (
637
+ // eslint-disable-next-line @next/next/no-img-element -- SSR string URL, framework-agnostic
638
+ /* @__PURE__ */ jsx4(
639
+ "img",
640
+ {
641
+ className: "rt-cms-card-image",
642
+ src: imageUrl,
643
+ alt: "",
644
+ loading: "lazy",
645
+ decoding: "async"
646
+ }
647
+ )
648
+ ) : null,
649
+ category ? /* @__PURE__ */ jsx4("span", { className: "rt-cms-card-category", children: category }) : null,
650
+ publishedDate || showAuthor && authorName || views !== null ? /* @__PURE__ */ jsxs4("p", { className: "rt-cms-meta", children: [
651
+ publishedDate ? /* @__PURE__ */ jsx4("span", { className: "rt-cms-meta-item", "data-rt-cms-meta": "date", children: publishedDate }) : null,
652
+ showAuthor && authorName ? /* @__PURE__ */ jsxs4("span", { className: "rt-cms-meta-item", "data-rt-cms-meta": "author", children: [
653
+ "\uC791\uC131\uC790 ",
654
+ authorName
655
+ ] }) : null,
656
+ views !== null ? /* @__PURE__ */ jsxs4("span", { className: "rt-cms-meta-item", "data-rt-cms-meta": "views", children: [
657
+ "\uC870\uD68C ",
658
+ views.toLocaleString("ko-KR")
659
+ ] }) : null
660
+ ] }) : null,
661
+ /* @__PURE__ */ jsx4("h2", { className: "rt-cms-title", children: post.title }),
662
+ post.excerpt ? /* @__PURE__ */ jsx4("p", { className: "rt-cms-excerpt", children: post.excerpt }) : null,
663
+ /* @__PURE__ */ jsx4("span", { className: "rt-cms-card-action", "aria-hidden": "true", children: "\uC77D\uAE30" })
664
+ ] }) });
665
+ }
666
+ function defaultHref(post) {
667
+ return `/blog/${post.slug}`;
668
+ }
669
+ function formatPublishedDate(iso) {
670
+ const d = new Date(iso);
671
+ if (Number.isNaN(d.getTime())) return "";
672
+ return new Intl.DateTimeFormat("ko-KR", {
673
+ year: "numeric",
674
+ month: "2-digit",
675
+ day: "2-digit"
676
+ }).format(d);
677
+ }
678
+
679
+ // src/TableOfContents.tsx
680
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
681
+ function RootTaleTableOfContents(props) {
682
+ const { entries, title = "\uBAA9\uCC28" } = props;
683
+ if (entries.length === 0) return null;
684
+ return /* @__PURE__ */ jsx5("nav", { "data-roottale-cms": "toc", className: "rt-cms-toc", "aria-label": title, children: /* @__PURE__ */ jsxs5("details", { className: "rt-cms-toc-details", open: true, children: [
685
+ /* @__PURE__ */ jsxs5("summary", { className: "rt-cms-toc-summary", children: [
686
+ title ? /* @__PURE__ */ jsx5("span", { className: "rt-cms-toc-title", children: title }) : null,
687
+ /* @__PURE__ */ jsx5("span", { className: "rt-cms-toc-toggle", "aria-hidden": "true" })
688
+ ] }),
689
+ /* @__PURE__ */ jsx5("ol", { className: "rt-cms-toc-list", children: entries.map((entry) => /* @__PURE__ */ jsx5(
690
+ "li",
691
+ {
692
+ className: `rt-cms-toc-item rt-cms-toc-level-${entry.level}`,
693
+ children: /* @__PURE__ */ jsx5("a", { href: `#${entry.id}`, children: entry.text })
694
+ },
695
+ entry.id
696
+ )) })
697
+ ] }) });
698
+ }
699
+
700
+ // src/theme-css-vars.ts
701
+ function themeToCssVars(theme) {
702
+ if (!theme) return null;
703
+ const vars = {};
704
+ const c = theme.colors ?? {};
705
+ if (c.primary) vars["--rt-color-primary"] = c.primary;
706
+ if (c.primaryForeground) vars["--rt-color-primary-foreground"] = c.primaryForeground;
707
+ if (c.foreground) vars["--rt-color-foreground"] = c.foreground;
708
+ if (c.background) vars["--rt-color-background"] = c.background;
709
+ if (c.secondary) vars["--rt-color-secondary"] = c.secondary;
710
+ if (c.secondaryForeground) vars["--rt-color-secondary-foreground"] = c.secondaryForeground;
711
+ if (c.accent) vars["--rt-color-accent"] = c.accent;
712
+ if (c.accentForeground) vars["--rt-color-accent-foreground"] = c.accentForeground;
713
+ if (c.muted) vars["--rt-color-muted"] = c.muted;
714
+ if (c.mutedForeground) vars["--rt-color-muted-foreground"] = c.mutedForeground;
715
+ if (c.destructive) vars["--rt-color-destructive"] = c.destructive;
716
+ if (c.destructiveForeground) vars["--rt-color-destructive-foreground"] = c.destructiveForeground;
717
+ if (c.border) vars["--rt-color-border"] = c.border;
718
+ if (c.input) vars["--rt-color-input"] = c.input;
719
+ if (c.ring) vars["--rt-color-ring"] = c.ring;
720
+ const f = theme.fonts ?? {};
721
+ if (f.body) vars["--rt-font-body"] = f.body;
722
+ if (f.display) vars["--rt-font-display"] = f.display;
723
+ const r = theme.radius ?? {};
724
+ if (r.sm) vars["--rt-radius-sm"] = r.sm;
725
+ if (r.md) vars["--rt-radius-md"] = r.md;
726
+ if (r.lg) vars["--rt-radius-lg"] = r.lg;
727
+ if (r.xl) vars["--rt-radius-xl"] = r.xl;
728
+ if (Object.keys(vars).length === 0) return null;
729
+ return vars;
730
+ }
731
+
732
+ // src/toc.ts
733
+ function tocLevel(rawLevel) {
734
+ if (rawLevel < 1 || rawLevel > 4) return null;
735
+ if (rawLevel < 3) return 2;
736
+ return rawLevel === 3 ? 3 : 4;
737
+ }
738
+ function headingToId(text) {
739
+ return text.trim().toLowerCase().replace(/[^\p{L}\p{N}\s-]/gu, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
740
+ }
741
+ function textOf(node) {
742
+ if (typeof node.text === "string") return node.text;
743
+ if (!Array.isArray(node.content)) return "";
744
+ return node.content.map(textOf).join("");
745
+ }
746
+ function extractToc(doc) {
747
+ const root = doc;
748
+ const content = Array.isArray(root.content) ? root.content : [];
749
+ const entries = [];
750
+ const idCounts = /* @__PURE__ */ new Map();
751
+ for (const node of content) {
752
+ if (node?.type !== "heading") continue;
753
+ const rawLevel = Number(node.attrs?.level ?? 2);
754
+ const level = tocLevel(rawLevel);
755
+ if (level === null) continue;
756
+ const text = textOf(node).trim();
757
+ if (!text) continue;
758
+ const base = headingToId(text) || "heading";
759
+ const count = idCounts.get(base) ?? 0;
760
+ const id = count === 0 ? base : `${base}-${count}`;
761
+ idCounts.set(base, count + 1);
762
+ entries.push({ level, text, id });
763
+ }
764
+ return entries;
765
+ }
766
+ function attachHeadingIds(doc, entries) {
767
+ const root = doc;
768
+ const content = Array.isArray(root.content) ? root.content : [];
769
+ let cursor = 0;
770
+ const nextContent = content.map((node) => {
771
+ if (node?.type !== "heading") return node;
772
+ const rawLevel = Number(node.attrs?.level ?? 2);
773
+ if (rawLevel < 1 || rawLevel > 4) return node;
774
+ if (!textOf(node).trim()) return node;
775
+ const entry = entries[cursor++];
776
+ if (!entry) return node;
777
+ return {
778
+ ...node,
779
+ attrs: { ...node.attrs ?? {}, id: entry.id }
780
+ };
781
+ });
782
+ return { ...root, content: nextContent };
783
+ }
784
+
785
+ // src/FloatingCta.tsx
786
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
787
+ function RootTaleFloatingCta(props) {
788
+ const { buttons, position = "bottom-right" } = props;
789
+ if (!buttons || buttons.length === 0) return null;
790
+ return /* @__PURE__ */ jsx6(
791
+ "div",
792
+ {
793
+ "data-roottale-cms": "floating-cta",
794
+ className: `rt-cms-floating-cta rt-cms-floating-cta--${position}`,
795
+ children: buttons.map((btn, idx) => {
796
+ const isExternal = btn.type === "kakao" || btn.type === "custom";
797
+ return /* @__PURE__ */ jsxs6(
798
+ "a",
799
+ {
800
+ href: btn.href,
801
+ className: `rt-cms-floating-cta__btn rt-cms-floating-cta__btn--${btn.type}`,
802
+ target: isExternal ? "_blank" : void 0,
803
+ rel: isExternal ? "noopener noreferrer" : void 0,
804
+ children: [
805
+ /* @__PURE__ */ jsx6("span", { "aria-hidden": "true", className: "rt-cms-floating-cta__icon", children: btn.icon ?? defaultIcon(btn.type) }),
806
+ /* @__PURE__ */ jsx6("span", { className: "rt-cms-floating-cta__label", children: btn.label })
807
+ ]
808
+ },
809
+ `${btn.type}-${idx}`
810
+ );
811
+ })
812
+ }
813
+ );
814
+ }
815
+ function defaultIcon(type) {
816
+ switch (type) {
817
+ case "phone":
818
+ return "\u260E";
819
+ case "contact":
820
+ return "\u2709";
821
+ case "kakao":
822
+ return "\u{1F4AC}";
823
+ case "custom":
824
+ return "\u2192";
825
+ }
826
+ }
827
+
828
+ // src/block-to-react.tsx
829
+ import {
830
+ applyBlockBindings,
831
+ createFieldBindingResolver,
832
+ sanitizeHtml
833
+ } from "@roottale/cms-core";
834
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
835
+ function isString(value) {
836
+ return typeof value === "string";
837
+ }
838
+ function htmlContent(html) {
839
+ return { __html: sanitizeHtml(html) };
840
+ }
841
+ function renderInner(blocks) {
842
+ return blocks.map((b, i) => renderBlock(b, i));
843
+ }
844
+ function renderBlock(block, key = 0) {
845
+ const dataBlockId = block._id;
846
+ switch (block.name) {
847
+ case "core/paragraph": {
848
+ const text = isString(block.attributes.content) ? block.attributes.content : block.rawHtml ?? "";
849
+ return /* @__PURE__ */ jsx7(
850
+ "p",
851
+ {
852
+ "data-block-id": dataBlockId,
853
+ dangerouslySetInnerHTML: htmlContent(text)
854
+ },
855
+ key
856
+ );
857
+ }
858
+ case "core/heading": {
859
+ const levelRaw = block.attributes.level;
860
+ const level = typeof levelRaw === "number" && levelRaw >= 2 && levelRaw <= 4 ? levelRaw : 2;
861
+ const text = isString(block.attributes.content) ? block.attributes.content : block.rawHtml ?? "";
862
+ const props = {
863
+ "data-block-id": dataBlockId,
864
+ dangerouslySetInnerHTML: htmlContent(text)
865
+ };
866
+ if (level === 2) return /* @__PURE__ */ jsx7("h2", { ...props }, key);
867
+ if (level === 3) return /* @__PURE__ */ jsx7("h3", { ...props }, key);
868
+ return /* @__PURE__ */ jsx7("h4", { ...props }, key);
869
+ }
870
+ case "core/image": {
871
+ const src = isString(block.attributes.url) ? block.attributes.url : "";
872
+ const alt = isString(block.attributes.alt) ? block.attributes.alt : "";
873
+ const caption = isString(block.attributes.caption) ? block.attributes.caption : null;
874
+ return /* @__PURE__ */ jsxs7("figure", { "data-block-id": dataBlockId, children: [
875
+ /* @__PURE__ */ jsx7("img", { src, alt, loading: "lazy" }),
876
+ caption && /* @__PURE__ */ jsx7("figcaption", { children: caption })
877
+ ] }, key);
878
+ }
879
+ case "core/list": {
880
+ const ordered = block.attributes.ordered === true;
881
+ const items = (block.innerBlocks ?? []).map(
882
+ (item, i) => item.rawHtml ? /* @__PURE__ */ jsx7(
883
+ "li",
884
+ {
885
+ dangerouslySetInnerHTML: htmlContent(item.rawHtml)
886
+ },
887
+ i
888
+ ) : /* @__PURE__ */ jsx7("li", { children: renderInner(item.innerBlocks ?? []) }, i)
889
+ );
890
+ return ordered ? /* @__PURE__ */ jsx7("ol", { "data-block-id": dataBlockId, children: items }, key) : /* @__PURE__ */ jsx7("ul", { "data-block-id": dataBlockId, children: items }, key);
891
+ }
892
+ case "core/quote": {
893
+ const inner = renderInner(block.innerBlocks ?? []);
894
+ const citation = isString(block.attributes.citation) ? block.attributes.citation : null;
895
+ return /* @__PURE__ */ jsxs7("blockquote", { "data-block-id": dataBlockId, children: [
896
+ inner,
897
+ citation && /* @__PURE__ */ jsx7("cite", { children: citation })
898
+ ] }, key);
899
+ }
900
+ case "core/code": {
901
+ const code = isString(block.attributes.content) ? block.attributes.content : block.rawHtml ?? "";
902
+ const lang = isString(block.attributes.language) ? block.attributes.language : void 0;
903
+ return /* @__PURE__ */ jsx7(
904
+ "pre",
905
+ {
906
+ "data-block-id": dataBlockId,
907
+ "data-language": lang,
908
+ children: /* @__PURE__ */ jsx7("code", { children: code })
909
+ },
910
+ key
911
+ );
912
+ }
913
+ case "core/columns": {
914
+ return /* @__PURE__ */ jsx7(
915
+ "div",
916
+ {
917
+ className: "rt-columns",
918
+ "data-block-id": dataBlockId,
919
+ children: renderInner(block.innerBlocks ?? [])
920
+ },
921
+ key
922
+ );
923
+ }
924
+ case "core/separator":
925
+ return /* @__PURE__ */ jsx7("hr", { "data-block-id": dataBlockId }, key);
926
+ case "core/spacer": {
927
+ const heightRaw = block.attributes.height;
928
+ const height = isString(heightRaw) ? heightRaw : "32px";
929
+ return /* @__PURE__ */ jsx7(
930
+ "div",
931
+ {
932
+ className: "rt-spacer",
933
+ "data-block-id": dataBlockId,
934
+ style: { height }
935
+ },
936
+ key
937
+ );
938
+ }
939
+ case "core/group": {
940
+ return /* @__PURE__ */ jsx7(
941
+ "div",
942
+ {
943
+ className: "rt-group",
944
+ "data-block-id": dataBlockId,
945
+ children: renderInner(block.innerBlocks ?? [])
946
+ },
947
+ key
948
+ );
949
+ }
950
+ default: {
951
+ const rawHtml = block.rawHtml;
952
+ return /* @__PURE__ */ jsx7(
953
+ "div",
954
+ {
955
+ className: "rt-unknown-block",
956
+ "data-block-id": dataBlockId,
957
+ "data-block-name": block.name,
958
+ "data-rawhtml-suppressed": rawHtml ? "true" : void 0,
959
+ children: renderInner(block.innerBlocks ?? [])
960
+ },
961
+ key
962
+ );
963
+ }
964
+ }
965
+ }
966
+ function renderBlocks(blocks) {
967
+ return blocks.map((b, i) => renderBlock(b, i));
968
+ }
969
+ function renderBoundBlocks(blocks, opts = {}) {
970
+ const resolver = opts.resolver ?? createFieldBindingResolver(opts.fields);
971
+ return renderBlocks(applyBlockBindings(blocks, resolver));
972
+ }
973
+
974
+ // src/server.tsx
975
+ import { Fragment as Fragment3, jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
976
+ function authorImageRadiusCss(radius) {
977
+ if (radius === "circle") return "999px";
978
+ if (radius === "rounded") return "0.875rem";
979
+ return "0";
980
+ }
981
+ function RootTaleThemeProvider(props) {
982
+ const style = themeToCssVars(props.theme ?? null);
983
+ if (!style) return /* @__PURE__ */ jsx8(Fragment3, { children: props.children });
984
+ return /* @__PURE__ */ jsx8("div", { "data-roottale-cms": "theme-root", "data-rt-theme": "", style, children: props.children });
985
+ }
986
+ async function RootTaleBlogList(props) {
987
+ const {
988
+ apiKey,
989
+ baseUrl,
990
+ limit,
991
+ type = "post",
992
+ collection,
993
+ collections,
994
+ emptyMessage = "\uC544\uC9C1 \uBC1C\uD589\uB41C \uAE00\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.",
995
+ showCategoryFilter = false,
996
+ activeCategory,
997
+ categoryHref = defaultCategoryHref,
998
+ allCategoriesLabel = "\uC804\uCCB4",
999
+ author,
1000
+ theme: themeProp
1001
+ } = props;
1002
+ const postHref = props.postHref ?? (collections ? makeCollectionPostHref(collections) : defaultPostHref);
1003
+ const [page, themeStyle, blogSettings] = await Promise.all([
1004
+ fetchPosts({ apiKey, baseUrl, limit, type, ...author ? { author } : {} }),
1005
+ resolveThemeStyle({ themeProp, apiKey, baseUrl }),
1006
+ fetchBlogSettings({ apiKey, baseUrl }).catch(() => DEFAULT_BLOG_SETTINGS)
1007
+ ]);
1008
+ const items = collection && collections ? postsForCollection(page.items, collections, collection) : page.items;
1009
+ if (items.length === 0) {
1010
+ return /* @__PURE__ */ jsx8("div", { "data-roottale-cms": "list", style: themeStyle ?? void 0, children: /* @__PURE__ */ jsx8("p", { className: "rt-cms-empty", children: emptyMessage }) });
1011
+ }
1012
+ const categories = showCategoryFilter ? collectCategories(items) : [];
1013
+ const visiblePosts = showCategoryFilter ? filterPostsByCategory(items, activeCategory) : items;
1014
+ return /* @__PURE__ */ jsxs8("div", { "data-roottale-cms": "list", style: themeStyle ?? void 0, children: [
1015
+ showCategoryFilter && categories.length > 0 ? /* @__PURE__ */ jsxs8("nav", { className: "rt-cms-filter", "aria-label": "\uCE74\uD14C\uACE0\uB9AC \uD544\uD130", children: [
1016
+ /* @__PURE__ */ jsx8(
1017
+ "a",
1018
+ {
1019
+ className: "rt-cms-filter-chip",
1020
+ href: categoryHref(null),
1021
+ "data-active": !activeCategory ? "" : void 0,
1022
+ "aria-current": !activeCategory ? "true" : void 0,
1023
+ children: allCategoriesLabel
1024
+ }
1025
+ ),
1026
+ categories.map((c) => /* @__PURE__ */ jsx8(
1027
+ "a",
1028
+ {
1029
+ className: "rt-cms-filter-chip",
1030
+ href: categoryHref(c.slug),
1031
+ "data-active": activeCategory === c.slug ? "" : void 0,
1032
+ "aria-current": activeCategory === c.slug ? "true" : void 0,
1033
+ children: c.name
1034
+ },
1035
+ c.slug
1036
+ ))
1037
+ ] }) : null,
1038
+ visiblePosts.length === 0 ? /* @__PURE__ */ jsx8("p", { className: "rt-cms-empty", children: emptyMessage }) : /* @__PURE__ */ jsx8("ul", { className: "rt-cms-list", children: visiblePosts.map((post) => /* @__PURE__ */ jsx8("li", { className: "rt-cms-list-item", children: /* @__PURE__ */ jsx8(
1039
+ RootTalePostCard,
1040
+ {
1041
+ post,
1042
+ href: postHref,
1043
+ display: resolvePostDisplay(post, blogSettings)
1044
+ }
1045
+ ) }, post.id)) })
1046
+ ] });
1047
+ }
1048
+ async function RootTaleBlogCategories(props) {
1049
+ const {
1050
+ apiKey,
1051
+ baseUrl,
1052
+ limit,
1053
+ collection,
1054
+ collections,
1055
+ categoryHref = (slug) => defaultCategoryHref(slug),
1056
+ emptyMessage = "\uC544\uC9C1 \uCE74\uD14C\uACE0\uB9AC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.",
1057
+ theme: themeProp
1058
+ } = props;
1059
+ const [page, themeStyle] = await Promise.all([
1060
+ fetchPosts({ apiKey, baseUrl, limit, type: "post" }),
1061
+ resolveThemeStyle({ themeProp, apiKey, baseUrl })
1062
+ ]);
1063
+ const categories = collection && collections ? collectCollectionCategories(page.items, collections, collection) : collectCategoriesWithCounts(page.items);
1064
+ return /* @__PURE__ */ jsx8(
1065
+ "nav",
1066
+ {
1067
+ "data-roottale-cms": "categories",
1068
+ className: "rt-cms-categories",
1069
+ "aria-label": "\uBE14\uB85C\uADF8 \uCE74\uD14C\uACE0\uB9AC",
1070
+ style: themeStyle ?? void 0,
1071
+ children: categories.length === 0 ? /* @__PURE__ */ jsx8("p", { className: "rt-cms-empty", children: emptyMessage }) : /* @__PURE__ */ jsx8("ul", { className: "rt-cms-category-list", children: categories.map((category) => /* @__PURE__ */ jsx8("li", { className: "rt-cms-category-list-item", children: /* @__PURE__ */ jsxs8(
1072
+ "a",
1073
+ {
1074
+ className: "rt-cms-category-link",
1075
+ href: categoryHref(category.slug),
1076
+ children: [
1077
+ /* @__PURE__ */ jsx8("span", { className: "rt-cms-category-name", children: category.name }),
1078
+ /* @__PURE__ */ jsx8("span", { className: "rt-cms-category-count", children: category.count })
1079
+ ]
1080
+ }
1081
+ ) }, category.slug)) })
1082
+ }
1083
+ );
1084
+ }
1085
+ async function RootTaleBlogPost(props) {
1086
+ const {
1087
+ apiKey,
1088
+ slugOrId,
1089
+ baseUrl,
1090
+ notFoundElement,
1091
+ showTableOfContents,
1092
+ tableOfContentsTitle,
1093
+ relatedPostsCount = 0,
1094
+ relatedPostsTitle = "\uAD00\uB828 \uAE00",
1095
+ collections,
1096
+ theme: themeProp
1097
+ } = props;
1098
+ const [post, themeStyle, blogSettings] = await Promise.all([
1099
+ fetchPost({ apiKey, slugOrId, baseUrl }),
1100
+ resolveThemeStyle({ themeProp, apiKey, baseUrl }),
1101
+ // 사이트 기본 표시 설정 — 실패해도 렌더는 진행 (안전 기본값).
1102
+ fetchBlogSettings({ apiKey, baseUrl }).catch(() => DEFAULT_BLOG_SETTINGS)
1103
+ ]);
1104
+ if (!post) {
1105
+ return notFoundElement ?? /* @__PURE__ */ jsx8("div", { "data-roottale-cms": "post-missing", style: themeStyle ?? void 0, children: /* @__PURE__ */ jsx8("p", { className: "rt-cms-empty", children: "\uAE00\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4." }) });
1106
+ }
1107
+ const display = resolvePostDisplay(post, blogSettings, {
1108
+ showTableOfContents,
1109
+ tableOfContentsTitle
1110
+ });
1111
+ const toc = display.showTableOfContents ? extractToc(post.bodyJson) : [];
1112
+ const renderDoc = toc.length > 0 ? attachHeadingIds(post.bodyJson, toc) : post.bodyJson;
1113
+ let related = [];
1114
+ if (relatedPostsCount > 0) {
1115
+ try {
1116
+ const candidates = await fetchPosts({
1117
+ apiKey,
1118
+ baseUrl,
1119
+ type: "post",
1120
+ limit: 30
1121
+ });
1122
+ related = selectRelatedPosts(post, candidates.items, relatedPostsCount);
1123
+ } catch {
1124
+ }
1125
+ }
1126
+ const relatedHref = props.postHref ?? (collections ? makeCollectionPostHref(collections) : defaultPostHref);
1127
+ const authorName = post.authorName;
1128
+ const authorImageUrl = post.authorImageUrl;
1129
+ const postAuthorBio = post.authorBio;
1130
+ const views = typeof post.viewCount === "number" ? post.viewCount : null;
1131
+ const publishedDate = display.showDate ? formatPublishedDate2(post.publishedAt) : "";
1132
+ const showTopAuthor = display.showAuthor && Boolean(authorName);
1133
+ const displayAuthorName = authorName ?? display.authorProfileName;
1134
+ const authorBio = postAuthorBio ?? display.authorProfileBio ?? display.authorCardDescription;
1135
+ const authorImageUrlForCard = authorImageUrl ?? display.authorProfileImageUrl;
1136
+ const authorImageAlt = `${displayAuthorName ?? ""} \uD504\uB85C\uD544 \uC0AC\uC9C4`.trim();
1137
+ const authorImageStyle = {
1138
+ borderRadius: authorImageRadiusCss(display.authorProfileImageRadius),
1139
+ objectPosition: `${display.authorProfileImagePositionX}% ${display.authorProfileImagePositionY}%`
1140
+ };
1141
+ const showAuthorCard = display.showAuthorCard && Boolean(displayAuthorName);
1142
+ return /* @__PURE__ */ jsxs8(
1143
+ "article",
1144
+ {
1145
+ "data-roottale-cms": "post",
1146
+ className: "rt-cms-article",
1147
+ style: themeStyle ?? void 0,
1148
+ children: [
1149
+ /* @__PURE__ */ jsxs8("header", { className: "rt-cms-post-header", children: [
1150
+ publishedDate || showTopAuthor || views !== null ? /* @__PURE__ */ jsxs8("p", { className: "rt-cms-meta", children: [
1151
+ publishedDate ? /* @__PURE__ */ jsx8("span", { className: "rt-cms-meta-item", "data-rt-cms-meta": "date", children: publishedDate }) : null,
1152
+ showTopAuthor ? /* @__PURE__ */ jsxs8("span", { className: "rt-cms-meta-item", "data-rt-cms-meta": "author", children: [
1153
+ "\uC791\uC131\uC790 ",
1154
+ authorName
1155
+ ] }) : null,
1156
+ views !== null ? /* @__PURE__ */ jsxs8("span", { className: "rt-cms-meta-item", "data-rt-cms-meta": "views", children: [
1157
+ "\uC870\uD68C ",
1158
+ views.toLocaleString("ko-KR")
1159
+ ] }) : null
1160
+ ] }) : null,
1161
+ /* @__PURE__ */ jsx8("h1", { className: "rt-cms-title", children: post.title }),
1162
+ post.excerpt ? /* @__PURE__ */ jsx8("p", { className: "rt-cms-excerpt", children: post.excerpt }) : null
1163
+ ] }),
1164
+ renderArchiveRelated(post.archiveRelated, collections),
1165
+ (() => {
1166
+ const tocEl = toc.length > 0 ? /* @__PURE__ */ jsx8(RootTaleTableOfContents, { entries: toc, title: display.tocTitle }) : null;
1167
+ const bodyEl = /* @__PURE__ */ jsx8("div", { "data-roottale-cms": "body", className: "rt-cms-body", children: /* @__PURE__ */ jsx8(
1168
+ RenderTiptap,
1169
+ {
1170
+ doc: renderDoc,
1171
+ fields: post.fields,
1172
+ apiKey,
1173
+ baseUrl
1174
+ }
1175
+ ) });
1176
+ if (display.tocPosition === "sidebar" && tocEl) {
1177
+ return /* @__PURE__ */ jsxs8("div", { className: "rt-cms-layout rt-cms-layout--toc-side", children: [
1178
+ bodyEl,
1179
+ /* @__PURE__ */ jsx8("aside", { className: "rt-cms-toc-side", children: tocEl })
1180
+ ] });
1181
+ }
1182
+ return /* @__PURE__ */ jsxs8(Fragment3, { children: [
1183
+ tocEl,
1184
+ bodyEl
1185
+ ] });
1186
+ })(),
1187
+ showAuthorCard && displayAuthorName ? /* @__PURE__ */ jsxs8("section", { className: "rt-cms-author-card", "aria-label": "\uC791\uC131\uC790", children: [
1188
+ authorImageUrlForCard ? /* @__PURE__ */ jsx8(
1189
+ "img",
1190
+ {
1191
+ className: "rt-cms-author-card__image",
1192
+ src: authorImageUrlForCard,
1193
+ alt: authorImageAlt,
1194
+ loading: "lazy",
1195
+ style: authorImageStyle
1196
+ }
1197
+ ) : /* @__PURE__ */ jsx8("div", { className: "rt-cms-author-card__avatar", "aria-hidden": "true", children: authorInitials(displayAuthorName) }),
1198
+ /* @__PURE__ */ jsxs8("div", { className: "rt-cms-author-card__body", children: [
1199
+ /* @__PURE__ */ jsx8("p", { className: "rt-cms-author-card__eyebrow", children: display.authorCardEyebrow }),
1200
+ /* @__PURE__ */ jsx8("p", { className: "rt-cms-author-card__name", children: displayAuthorName }),
1201
+ authorBio ? /* @__PURE__ */ jsx8("p", { className: "rt-cms-author-card__description", children: authorBio }) : null
1202
+ ] })
1203
+ ] }) : null,
1204
+ renderRelated(related, relatedHref, relatedPostsTitle),
1205
+ renderPostCta(blogSettings.postCta),
1206
+ renderFaqJsonLd(post.bodyJson)
1207
+ ]
1208
+ }
1209
+ );
1210
+ }
1211
+ function renderArchiveRelated(groups, collections) {
1212
+ const visible = groups.filter((group) => group.items.length > 0);
1213
+ if (visible.length === 0) return null;
1214
+ return /* @__PURE__ */ jsx8("aside", { className: "rt-archive-related", "aria-label": "\uC5F0\uAD00 \uC815\uBCF4", children: visible.map((group) => /* @__PURE__ */ jsxs8("section", { className: "rt-archive-related__group", children: [
1215
+ /* @__PURE__ */ jsx8("h2", { className: "rt-archive-related__title", children: archiveGroupTitle(group) }),
1216
+ /* @__PURE__ */ jsx8("ul", { className: "rt-archive-related__list", children: group.items.map((item) => /* @__PURE__ */ jsx8("li", { className: "rt-archive-related__item", children: /* @__PURE__ */ jsxs8(
1217
+ "a",
1218
+ {
1219
+ className: "rt-archive-related__link",
1220
+ href: archivePostHref(item.collectionKey, item.slug, collections),
1221
+ children: [
1222
+ /* @__PURE__ */ jsx8("span", { className: "rt-archive-related__item-title", children: item.title }),
1223
+ item.excerpt ? /* @__PURE__ */ jsx8("span", { className: "rt-archive-related__item-excerpt", children: item.excerpt }) : null
1224
+ ]
1225
+ }
1226
+ ) }, item.id)) })
1227
+ ] }, group.collectionKey)) });
1228
+ }
1229
+ function archiveGroupTitle(group) {
1230
+ if (group.collectionKey === "conditions") {
1231
+ return `\uC774 \uC99D\uC0C1\uACFC \uAD00\uB828\uB41C \uC9C8\uD658 ${group.items.length}`;
1232
+ }
1233
+ if (group.collectionKey === "treatments") {
1234
+ return `\uAD00\uB828 \uCE58\uB8CC\uBC95 ${group.items.length}`;
1235
+ }
1236
+ if (group.collectionKey === "symptoms") {
1237
+ return `\uAD00\uB828 \uC99D\uC0C1 ${group.items.length}`;
1238
+ }
1239
+ return `${group.label} ${group.items.length}`;
1240
+ }
1241
+ function archivePostHref(collectionKey, slug, collections) {
1242
+ const collection = collections?.find((item) => item.key === collectionKey);
1243
+ const basePath = collection?.basePath ?? defaultArchiveBasePath(collectionKey);
1244
+ return `${basePath.replace(/\/+$/, "")}/${slug}`;
1245
+ }
1246
+ function defaultArchiveBasePath(collectionKey) {
1247
+ if (collectionKey === "symptoms") return "/symptoms";
1248
+ if (collectionKey === "conditions") return "/conditions";
1249
+ if (collectionKey === "treatments") return "/treatments";
1250
+ return "/blog";
1251
+ }
1252
+ function renderPostCta(cta) {
1253
+ if (!cta) return null;
1254
+ const href = cta.buttonHref.trim();
1255
+ const safe = href.startsWith("/") || href.startsWith("#") || /^(https?:|mailto:|tel:)/i.test(href);
1256
+ if (!safe) return null;
1257
+ const external = /^https?:/i.test(href);
1258
+ return /* @__PURE__ */ jsxs8("aside", { "data-roottale-cms": "post-cta", className: "rt-cms-post-cta", children: [
1259
+ cta.title ? /* @__PURE__ */ jsx8("p", { className: "rt-cms-post-cta__title", children: cta.title }) : null,
1260
+ cta.description ? /* @__PURE__ */ jsx8("p", { className: "rt-cms-post-cta__desc", children: cta.description }) : null,
1261
+ /* @__PURE__ */ jsx8(
1262
+ "a",
1263
+ {
1264
+ className: "rt-cms-post-cta__btn",
1265
+ href,
1266
+ target: external ? "_blank" : void 0,
1267
+ rel: external ? "noopener noreferrer" : void 0,
1268
+ children: cta.buttonLabel
1269
+ }
1270
+ )
1271
+ ] });
1272
+ }
1273
+ function renderRelated(posts, href, title) {
1274
+ if (posts.length === 0) return null;
1275
+ return /* @__PURE__ */ jsxs8("nav", { "data-roottale-cms": "related", className: "rt-cms-related", "aria-label": title, children: [
1276
+ /* @__PURE__ */ jsx8("h2", { className: "rt-cms-related-title", children: title }),
1277
+ /* @__PURE__ */ jsx8("ul", { className: "rt-cms-related-list", children: posts.map((p) => /* @__PURE__ */ jsx8("li", { className: "rt-cms-related-item", children: /* @__PURE__ */ jsxs8("a", { className: "rt-cms-related-link", href: href(p), children: [
1278
+ p.featuredImageUrl ? /* @__PURE__ */ jsx8(
1279
+ "img",
1280
+ {
1281
+ className: "rt-cms-related-thumb",
1282
+ src: p.featuredImageUrl,
1283
+ alt: "",
1284
+ loading: "lazy"
1285
+ }
1286
+ ) : null,
1287
+ /* @__PURE__ */ jsx8("span", { className: "rt-cms-related-post-title", children: p.title })
1288
+ ] }) }, p.id)) })
1289
+ ] });
1290
+ }
1291
+ function renderFaqJsonLd(doc) {
1292
+ const ld = faqPageJsonLd(extractFaqEntries(doc));
1293
+ if (!ld) return null;
1294
+ const json = JSON.stringify(ld).replace(/</g, "\\u003c");
1295
+ return /* @__PURE__ */ jsx8(
1296
+ "script",
1297
+ {
1298
+ type: "application/ld+json",
1299
+ dangerouslySetInnerHTML: { __html: json }
1300
+ }
1301
+ );
1302
+ }
1303
+ function RootTaleFaqJsonLd({
1304
+ doc
1305
+ }) {
1306
+ return renderFaqJsonLd(doc);
1307
+ }
1308
+ async function RootTalePage(props) {
1309
+ const {
1310
+ apiKey,
1311
+ slugOrId,
1312
+ baseUrl,
1313
+ notFoundElement,
1314
+ showTitle = true,
1315
+ theme: themeProp
1316
+ } = props;
1317
+ const [post, themeStyle] = await Promise.all([
1318
+ fetchPost({ apiKey, slugOrId, baseUrl }),
1319
+ resolveThemeStyle({ themeProp, apiKey, baseUrl })
1320
+ ]);
1321
+ if (!post) {
1322
+ return notFoundElement ?? /* @__PURE__ */ jsx8("div", { "data-roottale-cms": "page-missing", style: themeStyle ?? void 0, children: /* @__PURE__ */ jsx8("p", { className: "rt-cms-empty", children: "\uD398\uC774\uC9C0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4." }) });
1323
+ }
1324
+ return /* @__PURE__ */ jsxs8(
1325
+ "article",
1326
+ {
1327
+ "data-roottale-cms": "page",
1328
+ className: "rt-cms-article",
1329
+ style: themeStyle ?? void 0,
1330
+ children: [
1331
+ showTitle ? /* @__PURE__ */ jsx8("header", { className: "rt-cms-post-header", children: /* @__PURE__ */ jsx8("h1", { className: "rt-cms-title", children: post.title }) }) : null,
1332
+ /* @__PURE__ */ jsx8("div", { "data-roottale-cms": "body", className: "rt-cms-body", children: /* @__PURE__ */ jsx8(
1333
+ RenderTiptap,
1334
+ {
1335
+ doc: post.bodyJson,
1336
+ fields: post.fields,
1337
+ apiKey,
1338
+ baseUrl
1339
+ }
1340
+ ) })
1341
+ ]
1342
+ }
1343
+ );
1344
+ }
1345
+ function authorInitials(name) {
1346
+ const trimmed = name.trim();
1347
+ if (!trimmed) return "";
1348
+ const parts = trimmed.split(/\s+/).filter(Boolean);
1349
+ if (parts.length >= 2) {
1350
+ return `${parts[0]?.[0] ?? ""}${parts[1]?.[0] ?? ""}`.toUpperCase();
1351
+ }
1352
+ return [...trimmed].slice(0, 2).join("").toUpperCase();
1353
+ }
1354
+ async function resolveThemeStyle(input) {
1355
+ const { themeProp, apiKey, baseUrl } = input;
1356
+ if (themeProp === null) return null;
1357
+ if (themeProp !== void 0) return themeToCssVars(themeProp);
1358
+ try {
1359
+ const fetched = await fetchTheme({ apiKey, baseUrl });
1360
+ return themeToCssVars(fetched);
1361
+ } catch {
1362
+ return null;
1363
+ }
1364
+ }
1365
+ function defaultPostHref(post) {
1366
+ return `/blog/${post.slug}`;
1367
+ }
1368
+ function makeCollectionPostHref(collections) {
1369
+ return (post) => resolvePostPath(post, collections) ?? defaultPostHref(post);
1370
+ }
1371
+ function formatPublishedDate2(iso) {
1372
+ const d = new Date(iso);
1373
+ if (Number.isNaN(d.getTime())) return "";
1374
+ return new Intl.DateTimeFormat("ko-KR", {
1375
+ year: "numeric",
1376
+ month: "2-digit",
1377
+ day: "2-digit"
1378
+ }).format(d);
1379
+ }
1380
+ function RenderTiptap({
1381
+ doc,
1382
+ fields,
1383
+ apiKey,
1384
+ baseUrl
1385
+ }) {
1386
+ const rawContent = doc.content;
1387
+ const content = Array.isArray(rawContent) ? rawContent : [];
1388
+ const hasToc = content.some((n) => n?.type === "tableOfContents");
1389
+ const docRecord = doc;
1390
+ const entries = hasToc ? extractToc(docRecord) : [];
1391
+ const renderDoc = hasToc && entries.length > 0 ? attachHeadingIds(docRecord, entries) : docRecord;
1392
+ const nodes = Array.isArray(renderDoc.content) ? renderDoc.content : content;
1393
+ const cms = apiKey ? { apiKey, baseUrl } : null;
1394
+ const context = {
1395
+ fields: fields ?? null,
1396
+ section: {
1397
+ cms,
1398
+ isDev: isDevEnv,
1399
+ renderBlogList: (props) => /* @__PURE__ */ jsx8(RootTaleBlogList, { ...props })
1400
+ }
1401
+ };
1402
+ return /* @__PURE__ */ jsx8(Fragment3, { children: nodes.map(
1403
+ (node, i) => node?.type === "tableOfContents" ? /* @__PURE__ */ jsx8(
1404
+ RootTaleTableOfContents,
1405
+ {
1406
+ entries,
1407
+ title: typeof node.attrs?.title === "string" ? node.attrs.title : "\uBAA9\uCC28"
1408
+ },
1409
+ i
1410
+ ) : renderNode(node, i, context)
1411
+ ) });
1412
+ }
1413
+ function boundContentText(node, fields) {
1414
+ return bindingTextValue(
1415
+ boundFieldValue(node.attrs?.bindings, "content", fields)
1416
+ );
1417
+ }
1418
+ function isSafeHref(value) {
1419
+ if (typeof value !== "string") return false;
1420
+ const v = value.trim();
1421
+ if (v.length === 0) return false;
1422
+ if (v.startsWith("/") || v.startsWith("#") || v.startsWith("?")) return true;
1423
+ return /^(https?:|mailto:|tel:)/i.test(v);
1424
+ }
1425
+ function alignStyle(node) {
1426
+ const align = node.attrs?.textAlign;
1427
+ if (typeof align !== "string" || align === "" || align === "left") return void 0;
1428
+ if (align === "center" || align === "right" || align === "justify") {
1429
+ return { textAlign: align };
1430
+ }
1431
+ return void 0;
1432
+ }
1433
+ function tableCellSpans(node) {
1434
+ const colspan = Number(node.attrs?.colspan);
1435
+ const rowspan = Number(node.attrs?.rowspan);
1436
+ return {
1437
+ colSpan: Number.isFinite(colspan) && colspan > 1 ? colspan : void 0,
1438
+ rowSpan: Number.isFinite(rowspan) && rowspan > 1 ? rowspan : void 0
1439
+ };
1440
+ }
1441
+ function isDevEnv() {
1442
+ const env = globalThis.process?.env ?? {};
1443
+ return env.NODE_ENV !== "production";
1444
+ }
1445
+ function renderNode(node, key, context = {
1446
+ fields: null,
1447
+ section: {
1448
+ cms: null,
1449
+ isDev: isDevEnv,
1450
+ renderBlogList: (props) => /* @__PURE__ */ jsx8(RootTaleBlogList, { ...props })
1451
+ }
1452
+ }) {
1453
+ if (!node || typeof node !== "object") return null;
1454
+ const children = Array.isArray(node.content) ? node.content.map((child, i) => renderNode(child, i, context)) : void 0;
1455
+ switch (node.type) {
1456
+ case "paragraph": {
1457
+ const bound = boundContentText(node, context.fields);
1458
+ return /* @__PURE__ */ jsx8("p", { style: alignStyle(node), children: bound ?? children }, key);
1459
+ }
1460
+ case "heading": {
1461
+ const rawLevel = Number(node.attrs?.level ?? 2);
1462
+ const level = Math.min(Math.max(Number.isFinite(rawLevel) ? rawLevel : 2, 2), 4);
1463
+ const idAttr = node.attrs?.id;
1464
+ const id = typeof idAttr === "string" && idAttr.length > 0 ? idAttr : void 0;
1465
+ const style = alignStyle(node);
1466
+ const inner = boundContentText(node, context.fields) ?? children;
1467
+ if (level === 2) return /* @__PURE__ */ jsx8("h2", { id, style, children: inner }, key);
1468
+ if (level === 3) return /* @__PURE__ */ jsx8("h3", { id, style, children: inner }, key);
1469
+ return /* @__PURE__ */ jsx8("h4", { id, style, children: inner }, key);
1470
+ }
1471
+ case "bulletList":
1472
+ return /* @__PURE__ */ jsx8("ul", { children }, key);
1473
+ case "orderedList":
1474
+ return /* @__PURE__ */ jsx8("ol", { children }, key);
1475
+ case "listItem":
1476
+ return /* @__PURE__ */ jsx8("li", { children }, key);
1477
+ case "blockquote":
1478
+ return /* @__PURE__ */ jsx8("blockquote", { children }, key);
1479
+ case "codeBlock":
1480
+ return /* @__PURE__ */ jsx8("pre", { children: /* @__PURE__ */ jsx8("code", { children }) }, key);
1481
+ case "horizontalRule":
1482
+ return /* @__PURE__ */ jsx8("hr", {}, key);
1483
+ case "hardBreak":
1484
+ return /* @__PURE__ */ jsx8("br", {}, key);
1485
+ case "image": {
1486
+ const boundSrc = bindingSrcValue(
1487
+ boundFieldValue(node.attrs?.bindings, "src", context.fields)
1488
+ );
1489
+ const src = boundSrc ?? node.attrs?.src;
1490
+ if (typeof src !== "string" || src.length === 0) return null;
1491
+ const alt = typeof node.attrs?.alt === "string" ? node.attrs.alt : "";
1492
+ const title = typeof node.attrs?.title === "string" ? node.attrs.title : void 0;
1493
+ const width = typeof node.attrs?.width === "number" && node.attrs.width > 0 ? node.attrs.width : void 0;
1494
+ const height = typeof node.attrs?.height === "number" && node.attrs.height > 0 ? node.attrs.height : void 0;
1495
+ return /* @__PURE__ */ jsx8(
1496
+ "img",
1497
+ {
1498
+ src,
1499
+ alt,
1500
+ title,
1501
+ width,
1502
+ height,
1503
+ loading: "lazy"
1504
+ },
1505
+ key
1506
+ );
1507
+ }
1508
+ case "columns":
1509
+ return /* @__PURE__ */ jsx8("div", { className: "rt-columns", children }, key);
1510
+ case "column":
1511
+ return /* @__PURE__ */ jsx8("div", { className: "rt-column", children }, key);
1512
+ case "table":
1513
+ return /* @__PURE__ */ jsx8("div", { className: "rt-table-wrap", children: /* @__PURE__ */ jsx8("table", { className: "rt-table", children: /* @__PURE__ */ jsx8("tbody", { children }) }) }, key);
1514
+ case "tableRow":
1515
+ return /* @__PURE__ */ jsx8("tr", { children }, key);
1516
+ case "tableHeader": {
1517
+ const { colSpan, rowSpan } = tableCellSpans(node);
1518
+ return /* @__PURE__ */ jsx8("th", { colSpan, rowSpan, children }, key);
1519
+ }
1520
+ case "tableCell": {
1521
+ const { colSpan, rowSpan } = tableCellSpans(node);
1522
+ return /* @__PURE__ */ jsx8("td", { colSpan, rowSpan, children }, key);
1523
+ }
1524
+ case "embed":
1525
+ return renderEmbed(node, key);
1526
+ case "faq":
1527
+ return /* @__PURE__ */ jsx8("div", { "data-roottale-cms": "faq", className: "rt-cms-faq", children }, key);
1528
+ case "faqItem":
1529
+ return /* @__PURE__ */ jsx8("details", { className: "rt-cms-faq-item", children }, key);
1530
+ case "faqQuestion":
1531
+ return /* @__PURE__ */ jsx8("summary", { className: "rt-cms-faq-q", children }, key);
1532
+ case "faqAnswer":
1533
+ return /* @__PURE__ */ jsx8("div", { className: "rt-cms-faq-a", children }, key);
1534
+ case "tableOfContents":
1535
+ return null;
1536
+ case "section":
1537
+ return renderSectionNode(node, key, context.section);
1538
+ case "text":
1539
+ return renderText(node, key);
1540
+ default:
1541
+ return children ? /* @__PURE__ */ jsx8("span", { children }, key) : null;
1542
+ }
1543
+ }
1544
+ function renderEmbed(node, key) {
1545
+ const src = node.attrs?.src;
1546
+ if (isAllowedEmbedSrc2(src)) {
1547
+ const provider = typeof node.attrs?.provider === "string" ? node.attrs.provider : void 0;
1548
+ return /* @__PURE__ */ jsx8(
1549
+ "div",
1550
+ {
1551
+ className: "rt-embed",
1552
+ "data-provider": provider,
1553
+ style: { aspectRatio: "16 / 9" },
1554
+ children: /* @__PURE__ */ jsx8(
1555
+ "iframe",
1556
+ {
1557
+ src,
1558
+ title: "\uC784\uBCA0\uB4DC\uB41C \uC601\uC0C1",
1559
+ loading: "lazy",
1560
+ allowFullScreen: true,
1561
+ referrerPolicy: "strict-origin-when-cross-origin",
1562
+ allow: "accelerometer; clipboard-write; encrypted-media; picture-in-picture; web-share",
1563
+ style: { width: "100%", height: "100%", border: 0 }
1564
+ }
1565
+ )
1566
+ },
1567
+ key
1568
+ );
1569
+ }
1570
+ const orig = node.attrs?.originalUrl;
1571
+ if (isSafeHref(orig)) {
1572
+ return /* @__PURE__ */ jsx8("p", { className: "rt-embed-fallback", children: /* @__PURE__ */ jsx8("a", { href: orig, rel: "noopener noreferrer", target: "_blank", children: orig }) }, key);
1573
+ }
1574
+ return null;
1575
+ }
1576
+ function renderText(node, key) {
1577
+ const text = node.text ?? "";
1578
+ const marks = node.marks ?? [];
1579
+ if (marks.length === 0) return text;
1580
+ let element = text;
1581
+ for (const mark of marks) {
1582
+ element = applyMark(mark, element);
1583
+ }
1584
+ return /* @__PURE__ */ jsx8("span", { children: element }, key);
1585
+ }
1586
+ function applyMark(mark, child) {
1587
+ switch (mark.type) {
1588
+ case "bold":
1589
+ return /* @__PURE__ */ jsx8("strong", { children: child });
1590
+ case "italic":
1591
+ return /* @__PURE__ */ jsx8("em", { children: child });
1592
+ case "underline":
1593
+ return /* @__PURE__ */ jsx8("u", { children: child });
1594
+ case "strike":
1595
+ return /* @__PURE__ */ jsx8("s", { children: child });
1596
+ case "code":
1597
+ return /* @__PURE__ */ jsx8("code", { children: child });
1598
+ case "highlight": {
1599
+ const color = mark.attrs?.color;
1600
+ const style = typeof color === "string" && color.length > 0 ? { background: color } : void 0;
1601
+ return /* @__PURE__ */ jsx8("mark", { style, children: child });
1602
+ }
1603
+ case "textStyle": {
1604
+ const color = mark.attrs?.color;
1605
+ const style = typeof color === "string" && color.length > 0 ? { color } : void 0;
1606
+ if (!style) return /* @__PURE__ */ jsx8(Fragment3, { children: child });
1607
+ return /* @__PURE__ */ jsx8("span", { style, children: child });
1608
+ }
1609
+ case "link": {
1610
+ const raw = mark.attrs?.href;
1611
+ const href = isSafeHref(raw) ? raw : "#";
1612
+ return /* @__PURE__ */ jsx8("a", { href, rel: "noopener noreferrer", target: "_blank", children: child });
1613
+ }
1614
+ default:
1615
+ return /* @__PURE__ */ jsx8("span", { children: child });
1616
+ }
1617
+ }
1618
+
1619
+ export {
1620
+ RootTaleLeadForm,
1621
+ sectionThemeStyle,
1622
+ TemplateHero,
1623
+ TemplateRichText,
1624
+ TemplateServices,
1625
+ TemplateFaq,
1626
+ TemplateCtaBanner,
1627
+ TemplateTrust,
1628
+ TemplateContact,
1629
+ RootTalePostCard,
1630
+ RootTaleTableOfContents,
1631
+ themeToCssVars,
1632
+ headingToId,
1633
+ extractToc,
1634
+ attachHeadingIds,
1635
+ RootTaleFloatingCta,
1636
+ renderBlock,
1637
+ renderBlocks,
1638
+ renderBoundBlocks,
1639
+ RootTaleThemeProvider,
1640
+ RootTaleBlogList,
1641
+ RootTaleBlogCategories,
1642
+ RootTaleBlogPost,
1643
+ RootTaleFaqJsonLd,
1644
+ RootTalePage,
1645
+ RenderTiptap
1646
+ };
1647
+ //# sourceMappingURL=chunk-GOHP7RWD.js.map