@roottale/cms-renderer-next 0.41.1 → 0.42.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/dist/before-after-client.d.ts +15 -0
  3. package/dist/before-after-client.js +8 -0
  4. package/dist/before-after-client.js.map +1 -0
  5. package/dist/chunk-4LQPZDXZ.js +75 -0
  6. package/dist/chunk-4LQPZDXZ.js.map +1 -0
  7. package/dist/chunk-6GQCFFHU.js +540 -0
  8. package/dist/chunk-6GQCFFHU.js.map +1 -0
  9. package/dist/chunk-F3G6RVIR.js +220 -0
  10. package/dist/chunk-F3G6RVIR.js.map +1 -0
  11. package/dist/chunk-H46JUIHZ.js +79 -0
  12. package/dist/chunk-H46JUIHZ.js.map +1 -0
  13. package/dist/chunk-K62RR4UQ.js +53 -0
  14. package/dist/chunk-K62RR4UQ.js.map +1 -0
  15. package/dist/chunk-NUUTTSGT.js +916 -0
  16. package/dist/chunk-NUUTTSGT.js.map +1 -0
  17. package/dist/chunk-RSNUZ4BF.js +3378 -0
  18. package/dist/chunk-RSNUZ4BF.js.map +1 -0
  19. package/dist/chunk-U5AKACPN.js +113 -0
  20. package/dist/chunk-U5AKACPN.js.map +1 -0
  21. package/dist/chunk-WWS43XCT.js +206 -0
  22. package/dist/chunk-WWS43XCT.js.map +1 -0
  23. package/dist/cms-public.css +164 -51
  24. package/dist/concerns-client.d.ts +12 -0
  25. package/dist/concerns-client.js +8 -0
  26. package/dist/concerns-client.js.map +1 -0
  27. package/dist/expertise-client.d.ts +10 -0
  28. package/dist/expertise-client.js +8 -0
  29. package/dist/expertise-client.js.map +1 -0
  30. package/dist/hero-carousel-client.d.ts +36 -0
  31. package/dist/hero-carousel-client.js +8 -0
  32. package/dist/hero-carousel-client.js.map +1 -0
  33. package/dist/routes.d.ts +51 -5
  34. package/dist/routes.js +109 -62
  35. package/dist/routes.js.map +1 -1
  36. package/dist/seo-checks.d.ts +87 -0
  37. package/dist/seo-checks.js +27 -0
  38. package/dist/seo-checks.js.map +1 -0
  39. package/dist/server.d.ts +51 -2
  40. package/dist/server.js +8 -2
  41. package/dist/site-config-DgYGGwu1.d.ts +3910 -0
  42. package/dist/spaces-client.d.ts +10 -0
  43. package/dist/spaces-client.js +8 -0
  44. package/dist/spaces-client.js.map +1 -0
  45. package/dist/templates.css +3246 -194
  46. package/dist/templates.d.ts +20 -1028
  47. package/dist/templates.js +70 -2
  48. package/dist/templates.js.map +1 -1
  49. package/dist/{theme-css-vars-BiyHBsJG.d.ts → theme-css-vars-CD3jcEkp.d.ts} +2 -2
  50. package/dist/ui-react-motion-client.d.ts +73 -0
  51. package/dist/ui-react-motion-client.js +14 -0
  52. package/dist/ui-react-motion-client.js.map +1 -0
  53. package/dist/validate.d.ts +15 -1
  54. package/dist/validate.js +806 -17
  55. package/dist/validate.js.map +1 -1
  56. package/package.json +18 -6
  57. package/dist/chunk-GOHP7RWD.js +0 -1647
  58. package/dist/chunk-GOHP7RWD.js.map +0 -1
  59. package/dist/chunk-IOYBEH6E.js +0 -271
  60. package/dist/chunk-IOYBEH6E.js.map +0 -1
@@ -323,9 +323,13 @@
323
323
  order: -1; /* 좁은 화면: 목차를 본문 위로 */
324
324
  }
325
325
  @container rt-cms-article (min-width: 60rem) {
326
+ /* 본문 컬럼+사이드바 유닛을 통째로 중앙 정렬 — 본문 컬럼만 measure 로 캡하면
327
+ 유닛 전체가 페이지 왼쪽으로 쏠려 보인다. */
326
328
  [data-roottale-cms] :where(.rt-cms-layout--toc-side) {
327
329
  grid-template-columns: minmax(0, 1fr) 15rem;
328
330
  align-items: start;
331
+ max-inline-size: calc(var(--rt-cms-measure, 42.5rem) + 15rem + var(--rt-spacing-6, 1.5rem));
332
+ margin-inline: auto;
329
333
  }
330
334
  [data-roottale-cms] :where(.rt-cms-layout--toc-side .rt-cms-toc-side) {
331
335
  order: 0; /* 넓은 화면: 오른쪽 컬럼 */
@@ -345,8 +349,11 @@
345
349
  [data-roottale-cms] :where([data-roottale-cms="body"]) {
346
350
  min-inline-size: 0;
347
351
  font-family: var(--rt-font-body, system-ui, sans-serif);
348
- font-size: var(--rt-font-size-base, 1rem);
349
- line-height: 1.7;
352
+ /* 읽기 표면 전용 스케일 — 네이버 프리미엄콘텐츠 기준 18px 기본, 테마 base 에
353
+ 비례(테마가 base 를 키우면 본문도 같이 커진다). */
354
+ font-size: var(--rt-cms-body-size, calc(var(--rt-font-size-base, 1rem) * 1.125));
355
+ line-height: 1.8;
356
+ letter-spacing: -0.01em; /* 한글 본문 대활자 자간 보정 */
350
357
  color: var(--rt-color-foreground, #06162f);
351
358
  /* 한글 조판 — 어절 단위 줄바꿈. 긴 URL·토큰만 anywhere 로 넘침 방지.
352
359
  에디터(admin globals .tiptap-content)와 1:1 파리티. astro 는 rt-cms-body
@@ -355,54 +362,54 @@
355
362
  overflow-wrap: anywhere;
356
363
  }
357
364
 
365
+ /* 네이버 프리미엄콘텐츠 류 크롬리스 헤더 — 장식 카드 대신 타이포+헤어라인. */
358
366
  [data-roottale-cms] :where(.rt-cms-post-header) {
359
- position: relative;
360
- overflow: hidden;
361
- border: 1px solid color-mix(in srgb, var(--rt-color-border, #dceef3) 78%, transparent);
362
- border-radius: calc(var(--rt-radius-md, 0.5rem) * 1.5);
363
- background:
364
- linear-gradient(135deg, rgb(255 255 255 / 0.92), color-mix(in srgb, var(--rt-color-muted, #f7f8f7) 70%, white)),
365
- radial-gradient(circle at top right, color-mix(in srgb, var(--rt-color-primary, #06162f) 10%, transparent), transparent 42%);
366
- box-shadow: 0 20px 55px rgb(17 24 39 / 0.05);
367
- padding: clamp(1.5rem, 3vw, 2.5rem);
368
- margin-block-end: var(--rt-spacing-6, 1.5rem);
367
+ max-inline-size: var(--rt-cms-measure, 42.5rem);
368
+ margin-inline: auto;
369
+ margin-block-end: var(--rt-spacing-7, 2rem);
370
+ padding-block-end: var(--rt-spacing-5, 1.25rem);
371
+ border-block-end: 1px solid color-mix(in srgb, var(--rt-color-border, #dceef3) 72%, transparent);
369
372
  }
370
373
 
371
374
  [data-roottale-cms] :where(.rt-cms-post-header .rt-cms-title) {
372
- font-size: var(--rt-font-size-2xl, 2.75rem);
375
+ font-size: clamp(1.75rem, 1.35rem + 1.9vw, 2.375rem);
373
376
  font-weight: var(--rt-font-weight-bold, 800);
374
- line-height: 1.12;
377
+ line-height: 1.3;
375
378
  margin-block-end: var(--rt-spacing-3, 0.75rem);
376
- max-inline-size: 15ch;
379
+ word-break: keep-all;
380
+ overflow-wrap: anywhere;
381
+ text-wrap: balance;
377
382
  }
378
383
 
379
- [data-roottale-cms] :where(.rt-cms-post-header)::after {
380
- content: "";
381
- position: absolute;
382
- inset-inline-start: 0;
383
- inset-block: 0;
384
- inline-size: 0.25rem;
385
- background: linear-gradient(
386
- 180deg,
387
- color-mix(in srgb, var(--rt-color-primary, #06162f) 72%, white),
388
- transparent
389
- );
384
+ [data-roottale-cms] :where(.rt-cms-post-header .rt-cms-excerpt) {
385
+ max-inline-size: none; /* 헤더가 이미 measure 로 캡 */
386
+ font-size: 1.125rem;
387
+ line-height: 1.7;
388
+ }
389
+
390
+ [data-roottale-cms] :where(.rt-cms-post-header .rt-cms-meta) {
391
+ margin: var(--rt-spacing-4, 1rem) 0 0;
392
+ font-variant-numeric: tabular-nums;
390
393
  }
391
394
 
392
395
  [data-roottale-cms] :where(.rt-cms-article [data-roottale-cms="body"] > * + *) {
393
- margin-block-start: 0.65em;
396
+ margin-block-start: 0.9em;
394
397
  }
395
398
 
396
- /* 읽기 measure — 본문의 모든 최상위 블록(문단·리스트·제목·표·FAQ)을 같은 폭으로
397
- 줄바꿈. 어드민 에디터(.tiptap-content > *)와 1:1 파리티. */
399
+ /* 읽기 measure — rem 고정값. 72ch 는 각 블록의 font-size 기준으로 해석돼
400
+ h2 같은 대활자 블록이 문단보다 넓어지는 문제가 있었다(ch 는 해당 요소의
401
+ 0 글자 폭 기준) — 컬럼 폭을 블록마다 다르게 만들지 않으려면 폰트 크기와
402
+ 무관한 고정 단위가 필요. 어드민 에디터(.tiptap-content > *)와 1:1 파리티. */
398
403
  [data-roottale-cms] :where(.rt-cms-article [data-roottale-cms="body"] > *) {
399
- max-inline-size: 72ch;
404
+ max-inline-size: var(--rt-cms-measure, 42.5rem);
405
+ margin-inline: auto;
400
406
  }
401
407
 
402
408
  [data-roottale-cms] :where(.rt-archive-related) {
403
409
  display: grid;
404
410
  gap: var(--rt-spacing-4, 1rem);
405
- max-inline-size: 72ch;
411
+ max-inline-size: var(--rt-cms-measure, 42.5rem);
412
+ margin-inline: auto;
406
413
  margin-block: var(--rt-spacing-6, 1.5rem) var(--rt-spacing-7, 2rem);
407
414
  padding: var(--rt-spacing-4, 1rem);
408
415
  border: 1px solid var(--rt-color-border, currentColor);
@@ -470,9 +477,55 @@
470
477
  -webkit-line-clamp: 2;
471
478
  }
472
479
 
473
- [data-roottale-cms] :where(.rt-cms-article [data-roottale-cms="body"] p) {
480
+ /* W5-2 — 양식(필드 그룹) 스칼라 값 정보 패널 (연관 박스 아래). */
481
+ [data-roottale-cms] :where(.rt-archive-facts) {
482
+ max-inline-size: var(--rt-cms-measure, 42.5rem);
483
+ margin-inline: auto;
484
+ margin-block: var(--rt-spacing-5, 1.25rem) var(--rt-spacing-7, 2rem);
485
+ padding: var(--rt-spacing-4, 1rem);
486
+ border: 1px solid var(--rt-color-border, currentColor);
487
+ border-radius: var(--rt-radius-md, 0.5rem);
488
+ background: color-mix(in srgb, var(--rt-color-muted, transparent) 40%, transparent);
489
+ }
490
+
491
+ [data-roottale-cms] :where(.rt-archive-facts__list) {
492
+ display: grid;
493
+ gap: var(--rt-spacing-3, 0.75rem);
474
494
  margin: 0;
475
- line-height: 1.7;
495
+ }
496
+
497
+ [data-roottale-cms] :where(.rt-archive-facts__row) {
498
+ display: grid;
499
+ gap: 0.25rem;
500
+ }
501
+
502
+ [data-roottale-cms] :where(.rt-archive-facts__label) {
503
+ color: var(--rt-color-muted-foreground, currentColor);
504
+ font-family: var(--rt-font-body, system-ui, sans-serif);
505
+ font-size: var(--rt-font-size-sm, 0.875rem);
506
+ font-weight: var(--rt-font-weight-semibold, 600);
507
+ line-height: 1.35;
508
+ }
509
+
510
+ [data-roottale-cms] :where(.rt-archive-facts__value) {
511
+ margin: 0;
512
+ overflow-wrap: anywhere;
513
+ color: var(--rt-color-foreground, CanvasText);
514
+ font-size: var(--rt-font-size-base, 1rem);
515
+ line-height: 1.6;
516
+ word-break: keep-all;
517
+ }
518
+
519
+ [data-roottale-cms] :where(.rt-archive-facts__value--multiline) {
520
+ white-space: pre-line;
521
+ }
522
+
523
+ /* 주의 — 본문 최상위 블록의 margin 은 단축 속성 금지: inline 축을 0 으로 덮으면
524
+ 컬럼 중앙 정렬(> * 의 margin-inline: auto)이 풀린다. margin-block 만 쓴다. */
525
+ [data-roottale-cms] :where(.rt-cms-article [data-roottale-cms="body"] p) {
526
+ margin-block: 0;
527
+ /* line-height 는 .rt-cms-body 의 1.8 을 그대로 상속 */
528
+ text-wrap: pretty;
476
529
  }
477
530
 
478
531
  [data-roottale-cms] :where(.rt-cms-article [data-roottale-cms="body"] h2),
@@ -482,23 +535,26 @@
482
535
  color: var(--rt-color-foreground, #06162f);
483
536
  letter-spacing: 0;
484
537
  line-height: 1.4;
538
+ text-wrap: balance;
539
+ /* 목차 앵커 점프 시 sticky 헤더에 가리지 않게. */
540
+ scroll-margin-block-start: var(--rt-cms-scroll-offset, 5rem);
485
541
  }
486
542
 
487
543
  [data-roottale-cms] :where(.rt-cms-article [data-roottale-cms="body"] h2) {
488
- margin: 1.3em 0 0.45em;
544
+ margin-block: 1.8em 0.55em;
489
545
  font-size: var(--rt-font-size-3xl, 1.875rem);
490
546
  font-weight: var(--rt-font-weight-bold, 700);
491
547
  }
492
548
 
493
549
  [data-roottale-cms] :where(.rt-cms-article [data-roottale-cms="body"] h3) {
494
- margin: 1.05em 0 0.35em;
550
+ margin-block: 1.4em 0.4em;
495
551
  font-family: var(--rt-font-body, system-ui, sans-serif);
496
552
  font-size: var(--rt-font-size-xl, 1.25rem);
497
553
  font-weight: var(--rt-font-weight-semibold, 600);
498
554
  }
499
555
 
500
556
  [data-roottale-cms] :where(.rt-cms-article [data-roottale-cms="body"] h4) {
501
- margin: 0.9em 0 0.25em;
557
+ margin-block: 1.15em 0.3em;
502
558
  font-family: var(--rt-font-body, system-ui, sans-serif);
503
559
  font-size: var(--rt-font-size-lg, 1.125rem);
504
560
  font-weight: var(--rt-font-weight-semibold, 600);
@@ -519,7 +575,7 @@
519
575
  [data-roottale-cms] :where(.rt-cms-article [data-roottale-cms="body"] ul),
520
576
  [data-roottale-cms] :where(.rt-cms-article [data-roottale-cms="body"] ol) {
521
577
  padding-inline-start: 1.5em;
522
- margin: 0;
578
+ margin-block: 0;
523
579
  }
524
580
 
525
581
  /* 고객 사이트의 CSS 리셋(Preflight 등)이 list-style 을 지워도 불릿/번호 유지. */
@@ -539,21 +595,34 @@
539
595
  list-style: square;
540
596
  }
541
597
 
598
+ [data-roottale-cms] :where(.rt-cms-article [data-roottale-cms="body"] li) {
599
+ text-wrap: pretty;
600
+ }
601
+
542
602
  [data-roottale-cms] :where(.rt-cms-article [data-roottale-cms="body"] li > p) {
543
603
  margin: 0;
544
604
  }
545
605
 
546
606
  [data-roottale-cms] :where(.rt-cms-article [data-roottale-cms="body"] blockquote) {
547
- border-inline-start: 3px solid var(--rt-color-border, #dceef3);
548
- padding-inline-start: var(--rt-spacing-3, 0.75rem);
549
- color: var(--rt-color-muted-foreground, #6e6e6e);
550
- font-style: italic;
607
+ border-inline-start: 2px solid color-mix(in srgb, var(--rt-color-primary, #06162f) 52%, var(--rt-color-border, #dceef3));
608
+ padding-inline-start: var(--rt-spacing-4, 1rem);
609
+ padding-block: 0.125rem;
610
+ color: color-mix(in srgb, var(--rt-color-foreground, #06162f) 76%, transparent);
611
+ /* font-style: italic 제거 — 한글 가짜 기울임 방지. */
551
612
  }
552
613
 
553
614
  [data-roottale-cms] :where(.rt-cms-article [data-roottale-cms="body"] a) {
554
615
  color: var(--rt-color-primary, #06162f);
555
616
  text-decoration: underline;
556
- text-underline-offset: 0.15em;
617
+ text-decoration-thickness: 1px;
618
+ text-decoration-color: color-mix(in srgb, var(--rt-color-primary, #06162f) 42%, transparent);
619
+ text-underline-offset: 0.18em;
620
+ transition: text-decoration-color 140ms ease;
621
+ }
622
+
623
+ [data-roottale-cms] :where(.rt-cms-article [data-roottale-cms="body"] a:hover),
624
+ [data-roottale-cms] :where(.rt-cms-article [data-roottale-cms="body"] a:focus-visible) {
625
+ text-decoration-color: var(--rt-color-primary, #06162f);
557
626
  }
558
627
 
559
628
  [data-roottale-cms] :where(.rt-cms-article [data-roottale-cms="body"] mark) {
@@ -601,6 +670,7 @@
601
670
  }
602
671
 
603
672
  [data-roottale-cms] :where(.rt-cms-article [data-roottale-cms="body"] img) {
673
+ display: block; /* 인라인 하단 갭 제거 */
604
674
  max-inline-size: 100%;
605
675
  block-size: auto;
606
676
  border-radius: var(--rt-radius-md, 0.5rem);
@@ -609,7 +679,12 @@
609
679
  [data-roottale-cms] :where(.rt-cms-article [data-roottale-cms="body"] hr) {
610
680
  border: 0;
611
681
  border-block-start: 1px solid var(--rt-color-border, #dceef3);
612
- margin-block: var(--rt-spacing-4, 1rem);
682
+ margin-block: var(--rt-spacing-7, 2.25rem);
683
+ }
684
+
685
+ /* 본문 텍스트 선택 색 — 프라이머리 색으로 브랜드 일관성. */
686
+ [data-roottale-cms] :where(.rt-cms-article) ::selection {
687
+ background: color-mix(in srgb, var(--rt-color-primary, #06162f) 14%, transparent);
613
688
  }
614
689
 
615
690
  /* 표(table) — 좁은 화면에서 가로 스크롤(wrap), 셀 경계선·머리행 강조. */
@@ -638,6 +713,8 @@
638
713
 
639
714
  /* 글 하단 고정 CTA — 본문 끝 리드 전환 블록. */
640
715
  [data-roottale-cms] :where(.rt-cms-post-cta) {
716
+ max-inline-size: var(--rt-cms-measure, 42.5rem);
717
+ margin-inline: auto;
641
718
  margin-block-start: var(--rt-spacing-6, 1.5rem);
642
719
  padding: var(--rt-spacing-5, 1.25rem);
643
720
  border: 1px solid color-mix(in srgb, var(--rt-color-border, #dceef3) 82%, transparent);
@@ -681,7 +758,8 @@
681
758
  border: 1px solid color-mix(in srgb, var(--rt-color-border, #dceef3) 88%, transparent);
682
759
  border-radius: min(var(--rt-radius-md, 0.5rem), 0.75rem);
683
760
  overflow: hidden;
684
- margin: var(--rt-spacing-6, 1.5rem) 0;
761
+ /* 본문 최상위 블록 — margin 단축 금지(중앙 정렬 유지), margin-block 만. */
762
+ margin-block: var(--rt-spacing-6, 1.5rem);
685
763
  }
686
764
  [data-roottale-cms] :where(.rt-cms-faq-item) {
687
765
  border-block-start: 1px solid color-mix(in srgb, var(--rt-color-border, #dceef3) 88%, transparent);
@@ -737,7 +815,9 @@
737
815
 
738
816
  /* 관련 글 (rt-cms-related) — 글 하단 같은 카테고리 최근 글. */
739
817
  [data-roottale-cms] :where(.rt-cms-related) {
740
- margin: var(--rt-spacing-8, 2.5rem) 0 var(--rt-spacing-6, 1.5rem);
818
+ max-inline-size: var(--rt-cms-measure, 42.5rem);
819
+ margin-inline: auto;
820
+ margin-block: var(--rt-spacing-8, 2.5rem) var(--rt-spacing-6, 1.5rem);
741
821
  }
742
822
  [data-roottale-cms] :where(.rt-cms-related-title) {
743
823
  font-size: var(--rt-font-size-lg, 1.125rem);
@@ -786,13 +866,14 @@
786
866
  [data-roottale-cms] :where(.rt-cms-toc) {
787
867
  position: relative;
788
868
  /* 본문 measure 와 동일 폭으로 정렬(본문 위 인라인 배치). */
789
- max-inline-size: 72ch;
869
+ max-inline-size: var(--rt-cms-measure, 42.5rem);
870
+ margin-inline: auto;
790
871
  background: color-mix(in srgb, var(--rt-color-muted, #f7f8f7) 34%, white);
791
872
  border: 1px solid color-mix(in srgb, var(--rt-color-border, #dceef3) 86%, transparent);
792
873
  border-radius: min(var(--rt-radius-md, 0.5rem), 0.5rem);
793
874
  box-shadow: inset 0 1px 0 rgb(255 255 255 / 0.76);
794
875
  padding: 0.875rem 1rem;
795
- margin: 0 0 var(--rt-spacing-6, 1.5rem);
876
+ margin-block: 0 var(--rt-spacing-6, 1.5rem);
796
877
  }
797
878
 
798
879
  [data-roottale-cms] :where(.rt-cms-toc)::before {
@@ -935,6 +1016,8 @@
935
1016
  display: flex;
936
1017
  align-items: center;
937
1018
  gap: var(--rt-spacing-4, 1rem);
1019
+ max-inline-size: var(--rt-cms-measure, 42.5rem);
1020
+ margin-inline: auto;
938
1021
  margin-block-start: var(--rt-spacing-8, 3rem);
939
1022
  border: 1px solid color-mix(in srgb, var(--rt-color-border, #dceef3) 82%, transparent);
940
1023
  border-radius: calc(var(--rt-radius-md, 0.5rem) * 1.25);
@@ -1001,15 +1084,34 @@
1001
1084
  max-inline-size: 42rem;
1002
1085
  }
1003
1086
 
1087
+ /* 사이드바 목차 모드 — 본문 컬럼(그리드 유닛 중앙 정렬)이 왼쪽으로 이동하므로,
1088
+ 헤더·하단 요소도 같은 왼쪽 라인에 정렬해야 세로 라인이 맞는다. 각 요소의
1089
+ 기본 margin-inline: auto 를 덮어야 해서 소스 순서상 기본 룰들 뒤에 둔다.
1090
+ 100cqi = article 컨테이너 폭. */
1091
+ @container rt-cms-article (min-width: 60rem) {
1092
+ [data-roottale-cms] :where(.rt-cms-article:has(.rt-cms-layout--toc-side) .rt-cms-post-header),
1093
+ [data-roottale-cms] :where(.rt-cms-article:has(.rt-cms-layout--toc-side) .rt-cms-author-card),
1094
+ [data-roottale-cms] :where(.rt-cms-article:has(.rt-cms-layout--toc-side) .rt-cms-post-cta),
1095
+ [data-roottale-cms] :where(.rt-cms-article:has(.rt-cms-layout--toc-side) .rt-cms-related),
1096
+ [data-roottale-cms] :where(.rt-cms-article:has(.rt-cms-layout--toc-side) .rt-archive-related),
1097
+ [data-roottale-cms] :where(.rt-cms-article:has(.rt-cms-layout--toc-side) .rt-archive-facts) {
1098
+ margin-inline: max(
1099
+ 0rem,
1100
+ calc(
1101
+ (100cqi - var(--rt-cms-measure, 42.5rem) - 15rem - var(--rt-spacing-6, 1.5rem)) / 2
1102
+ )
1103
+ )
1104
+ auto;
1105
+ }
1106
+ }
1107
+
1004
1108
  @media (max-width: 640px) {
1005
1109
  [data-roottale-cms] :where(.rt-cms-title) {
1006
1110
  font-size: var(--rt-font-size-2xl, 1.5rem);
1007
1111
  }
1008
1112
 
1009
- [data-roottale-cms] :where(.rt-cms-post-header .rt-cms-title) {
1010
- font-size: var(--rt-font-size-2xl, 2.125rem);
1011
- max-inline-size: 100%;
1012
- }
1113
+ /* .rt-cms-post-header .rt-cms-title 는 clamp() 가 이미 좁은 화면 크기를
1114
+ 처리하므로 별도 오버라이드 불필요(제거됨). */
1013
1115
 
1014
1116
  [data-roottale-cms] :where(.rt-cms-toc) {
1015
1117
  padding: 1.125rem;
@@ -1022,6 +1124,12 @@
1022
1124
  [data-roottale-cms] :where(.rt-cms-author-card) {
1023
1125
  align-items: flex-start;
1024
1126
  }
1127
+
1128
+ /* 모바일 본문 — 데스크톱보다 살짝 낮춘 스케일(1.0625 = 17px 기준). */
1129
+ [data-roottale-cms] :where(.rt-cms-body),
1130
+ [data-roottale-cms] :where([data-roottale-cms="body"]) {
1131
+ font-size: var(--rt-cms-body-size, calc(var(--rt-font-size-base, 1rem) * 1.0625));
1132
+ }
1025
1133
  }
1026
1134
 
1027
1135
  /* RootTaleFloatingCta */
@@ -1135,6 +1243,11 @@
1135
1243
  outline-offset: 2px;
1136
1244
  }
1137
1245
 
1246
+ [data-roottale-cms="lead-form"] :where(.rt-cms-field-textarea) {
1247
+ min-block-size: 6rem;
1248
+ resize: vertical;
1249
+ }
1250
+
1138
1251
  [data-roottale-cms="lead-form"] :where(.rt-cms-field-hint) {
1139
1252
  font-size: var(--rt-font-size-xs, 0.75rem);
1140
1253
  color: var(--rt-color-muted-foreground, #6e6e6e);
@@ -0,0 +1,12 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { C as ConcernsSection } from './site-config-DgYGGwu1.js';
3
+ import 'zod';
4
+
5
+ type ConcernRegion = ConcernsSection["props"]["regions"][number];
6
+ declare function ConcernsFigureMap({ figureImage, figureAlt, regions, }: {
7
+ readonly figureImage: string;
8
+ readonly figureAlt: string;
9
+ readonly regions: readonly ConcernRegion[];
10
+ }): react_jsx_runtime.JSX.Element | null;
11
+
12
+ export { ConcernsFigureMap };
@@ -0,0 +1,8 @@
1
+ "use client";
2
+ import {
3
+ ConcernsFigureMap
4
+ } from "./chunk-H46JUIHZ.js";
5
+ export {
6
+ ConcernsFigureMap
7
+ };
8
+ //# sourceMappingURL=concerns-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,10 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { E as ExpertiseSection } from './site-config-DgYGGwu1.js';
3
+ import 'zod';
4
+
5
+ type ExpertiseItem = ExpertiseSection["props"]["items"][number];
6
+ declare function ExpertiseTabStage({ items, }: {
7
+ readonly items: readonly ExpertiseItem[];
8
+ }): react_jsx_runtime.JSX.Element | null;
9
+
10
+ export { ExpertiseTabStage };
@@ -0,0 +1,8 @@
1
+ "use client";
2
+ import {
3
+ ExpertiseTabStage
4
+ } from "./chunk-4LQPZDXZ.js";
5
+ export {
6
+ ExpertiseTabStage
7
+ };
8
+ //# sourceMappingURL=expertise-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,36 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ interface HeroCarouselSlideInput {
4
+ readonly image: string;
5
+ readonly alt: string;
6
+ readonly caption?: string;
7
+ readonly eyebrow?: string;
8
+ readonly title?: string;
9
+ readonly subtitle?: string;
10
+ readonly description?: string;
11
+ readonly primaryCta?: {
12
+ readonly label: string;
13
+ readonly href: string;
14
+ };
15
+ readonly secondaryCta?: {
16
+ readonly label: string;
17
+ readonly href: string;
18
+ };
19
+ readonly tabLabel?: string;
20
+ readonly tabMeta?: string;
21
+ readonly metrics?: readonly {
22
+ readonly value: string;
23
+ readonly unit?: string;
24
+ readonly label: string;
25
+ readonly source?: string;
26
+ }[];
27
+ }
28
+ interface HeroCarouselViewportProps {
29
+ readonly slides: readonly HeroCarouselSlideInput[];
30
+ readonly autoplay: boolean;
31
+ readonly intervalMs: number;
32
+ readonly scrim?: "none" | "soft" | "strong";
33
+ }
34
+ declare function HeroCarouselViewport({ slides, autoplay, intervalMs, scrim, }: HeroCarouselViewportProps): react_jsx_runtime.JSX.Element;
35
+
36
+ export { type HeroCarouselSlideInput, HeroCarouselViewport, type HeroCarouselViewportProps };
@@ -0,0 +1,8 @@
1
+ "use client";
2
+ import {
3
+ HeroCarouselViewport
4
+ } from "./chunk-F3G6RVIR.js";
5
+ export {
6
+ HeroCarouselViewport
7
+ };
8
+ //# sourceMappingURL=hero-carousel-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
package/dist/routes.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { CmsPostContent, RootTaleRedirect } from '@roottale/cms-client/server';
1
+ import { CmsPostContent, RootTaleRedirect, CmsPostTranslationRef } from '@roottale/cms-client/server';
2
2
  import { RouteCollection } from '@roottale/cms-core';
3
3
  export { RouteCollection, resolvePostCollection, resolvePostUrl } from '@roottale/cms-core';
4
4
 
@@ -244,15 +244,19 @@ interface OgImageRouteConfig extends RootTaleSiteConfig {
244
244
  /** 하단 브랜드 라벨. 기본 = `title`. */
245
245
  brandLabel?: string;
246
246
  }
247
- /** `next/og` 의 ImageResponse 와 구조 호환(직접 의존 회피 — revalidate 콜백 주입과 동일 원칙). */
247
+ /**
248
+ * `next/og` 의 ImageResponse 와 구조 호환(직접 의존 회피 — revalidate 콜백 주입과
249
+ * 동일 원칙). 생성자 파라미터는 반변이라 `unknown` 으로 두면 ReactElement 를
250
+ * 요구하는 실제 ImageResponse 가 대입 불가(고객 연동 TS 에러) — `any` 필요.
251
+ */
248
252
  interface ImageResponseLike {
249
- new (element: unknown, options?: {
253
+ new (element: any, options?: {
250
254
  width?: number;
251
255
  height?: number;
252
256
  fonts?: {
253
257
  name: string;
254
258
  data: ArrayBuffer;
255
- weight?: number;
259
+ weight?: 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900;
256
260
  style?: "normal";
257
261
  }[];
258
262
  }): Response;
@@ -416,6 +420,12 @@ interface PostMetadataInput {
416
420
  * 공지/블로그로 구분해 해석한다(`collectionKey:"notice"` → `/notice`).
417
421
  */
418
422
  collectionKey?: string | null;
423
+ /**
424
+ * ADR-0052 A안 (W4-6 PR C1) — 이 글의 발행된 언어판 목록(자기 자신 포함, PR B
425
+ * 응답 `CmsPostContent.translations`). `options.locales` 와 함께 주면 hreflang
426
+ * `alternates.languages` 를 계산한다. 없거나 자기 자신 1건뿐이면 languages 생략.
427
+ */
428
+ translations?: readonly CmsPostTranslationRef[];
419
429
  }
420
430
  interface BuildPostMetadataOptions {
421
431
  /**
@@ -436,6 +446,26 @@ interface BuildPostMetadataOptions {
436
446
  * 안 속하면(미설정·미존재 key) canonical 을 생략한다. `post.slug` 필요.
437
447
  */
438
448
  collections?: readonly RouteCollection[];
449
+ /**
450
+ * ADR-0052 A안 (W4-6 PR C1) — hreflang alternates.languages 계산 옵션. 주지
451
+ * 않으면(undefined) `translations` 가 있어도 languages 를 계산하지 않는다
452
+ * (opt-in — 명시 안 한 기존 소비자는 canonical-only 로 diff 0).
453
+ */
454
+ locales?: PostLocaleOptions;
455
+ }
456
+ /**
457
+ * ADR-0052 A안 (W4-6 PR C1) — hreflang 경로 계산 옵션. `buildPostMetadata` 와
458
+ * `buildHreflangLanguages` 가 공유한다.
459
+ */
460
+ interface PostLocaleOptions {
461
+ /** 사이트 기본 locale — prefix 없는 경로, `x-default` 겸함. */
462
+ defaultLocale: string;
463
+ /**
464
+ * locale + slug → path(선행 슬래시 포함). 기본값: 기본 locale 은 `/blog/{slug}`,
465
+ * 그 외는 `/{locale}/blog/{slug}` (starter 참조 구현 관용구, `tenant-site-i18n-plan.md`
466
+ * §2③). 블로그가 아닌 라우트(CMS `[slug]` 페이지 등)는 override 필수.
467
+ */
468
+ pathFor?: (locale: string, slug: string) => string;
439
469
  }
440
470
  /** Next `Metadata` 의 구조적 부분집합 — 소비자는 그대로 return 가능. */
441
471
  interface PostMetadata {
@@ -443,6 +473,7 @@ interface PostMetadata {
443
473
  description?: string;
444
474
  alternates?: {
445
475
  canonical: string;
476
+ languages?: Record<string, string>;
446
477
  };
447
478
  robots?: {
448
479
  index: boolean;
@@ -488,6 +519,21 @@ interface PostMetadata {
488
519
  * ```
489
520
  */
490
521
  declare function buildPostMetadata(post: PostMetadataInput, options?: BuildPostMetadataOptions): PostMetadata;
522
+ /**
523
+ * ADR-0052 A안 (W4-6 PR C1) — 글의 `translations[]`(PR B 응답, 자기 자신 포함)를
524
+ * hreflang `alternates.languages` 로 변환. **양방향 + `x-default` = 기본 locale**
525
+ * (Google 요건 — 기본판도 자기 자신을 가리키는 entry 를 갖는다).
526
+ *
527
+ * `buildPostMetadata` 가 내부적으로 쓰지만, OG `type:"article"` 이 부적절한
528
+ * 라우트(CMS `[slug]` 페이지 등)는 이 함수를 직접 호출해 plain `Metadata.alternates`
529
+ * 를 조립한다.
530
+ *
531
+ * 번역이 없거나(자기 자신 1건뿐) 기본 locale 판 URL 을 못 만들면(예: `translations`
532
+ * 에 기본 locale 항목 자체가 없음 — 데이터 이상) undefined 를 반환해 languages
533
+ * 방출을 생략한다(부분 hreflang 은 검색엔진이 무시하거나 오해할 수 있어 전부 생략이
534
+ * 더 안전 — fail-closed).
535
+ */
536
+ declare function buildHreflangLanguages(translations: readonly CmsPostTranslationRef[] | undefined, siteUrl: string, locales: PostLocaleOptions): Record<string, string> | undefined;
491
537
 
492
538
  /**
493
539
  * `@roottale/cms-renderer-next/routes` — Next.js App Router 통합 라우트 팩토리.
@@ -548,4 +594,4 @@ interface FleetInfoRouteConfig {
548
594
  */
549
595
  declare function createFleetInfoRoute(config?: FleetInfoRouteConfig): () => Promise<Response>;
550
596
 
551
- export { type BuildPostMetadataOptions, type CollectionsInput, type FleetInfoRouteConfig, type ImageResponseLike, type LlmsTxtLink, type LlmsTxtRouteConfig, type LlmsTxtSection, OG_IMAGE_CONTENT_TYPE, OG_IMAGE_SIZE, type OgImageRouteConfig, type PostMetadata, type PostMetadataInput, type PostSeoOverrides, type RedirectMiddlewareConfig, type RevalidateRouteConfig, type RootTaleSiteConfig, type SitemapEntry, type SitemapIndexOptions, type SitemapSectionId, buildPostMetadata, createFeedRoute, createFleetInfoRoute, createLlmsTxtRoute, createPostOgImage, createRedirectMiddleware, createRevalidateRoute, createSitemap, createSitemapIndex, matchRedirect, normalizeRedirectPath, postRedirectPath, resolveCollections, resolveCollectionsResult, safeFetchPosts, trimSlash };
597
+ export { type BuildPostMetadataOptions, type CollectionsInput, type FleetInfoRouteConfig, type ImageResponseLike, type LlmsTxtLink, type LlmsTxtRouteConfig, type LlmsTxtSection, OG_IMAGE_CONTENT_TYPE, OG_IMAGE_SIZE, type OgImageRouteConfig, type PostLocaleOptions, type PostMetadata, type PostMetadataInput, type PostSeoOverrides, type RedirectMiddlewareConfig, type RevalidateRouteConfig, type RootTaleSiteConfig, type SitemapEntry, type SitemapIndexOptions, type SitemapSectionId, buildHreflangLanguages, buildPostMetadata, createFeedRoute, createFleetInfoRoute, createLlmsTxtRoute, createPostOgImage, createRedirectMiddleware, createRevalidateRoute, createSitemap, createSitemapIndex, matchRedirect, normalizeRedirectPath, postRedirectPath, resolveCollections, resolveCollectionsResult, safeFetchPosts, trimSlash };