@roottale/cms-renderer-next 0.41.2 → 0.42.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.
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-YUT4BRJY.js → chunk-F3G6RVIR.js} +68 -15
  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 +175 -55
  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 +22 -1
  31. package/dist/hero-carousel-client.js +1 -1
  32. package/dist/routes.d.ts +51 -5
  33. package/dist/routes.js +109 -62
  34. package/dist/routes.js.map +1 -1
  35. package/dist/seo-checks.d.ts +87 -0
  36. package/dist/seo-checks.js +27 -0
  37. package/dist/seo-checks.js.map +1 -0
  38. package/dist/server.d.ts +51 -2
  39. package/dist/server.js +8 -3
  40. package/dist/site-config-DgYGGwu1.d.ts +3910 -0
  41. package/dist/spaces-client.d.ts +10 -0
  42. package/dist/spaces-client.js +8 -0
  43. package/dist/spaces-client.js.map +1 -0
  44. package/dist/templates.css +3200 -344
  45. package/dist/templates.d.ts +19 -1127
  46. package/dist/templates.js +70 -3
  47. package/dist/templates.js.map +1 -1
  48. package/dist/{theme-css-vars-BiyHBsJG.d.ts → theme-css-vars-CD3jcEkp.d.ts} +2 -2
  49. package/dist/ui-react-motion-client.d.ts +73 -0
  50. package/dist/ui-react-motion-client.js +14 -0
  51. package/dist/ui-react-motion-client.js.map +1 -0
  52. package/dist/validate.d.ts +15 -1
  53. package/dist/validate.js +399 -15
  54. package/dist/validate.js.map +1 -1
  55. package/package.json +15 -5
  56. package/dist/chunk-5R6APJZ2.js +0 -1688
  57. package/dist/chunk-5R6APJZ2.js.map +0 -1
  58. package/dist/chunk-QKZ4HRBB.js +0 -300
  59. package/dist/chunk-QKZ4HRBB.js.map +0 -1
  60. package/dist/chunk-YUT4BRJY.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,35 +679,49 @@
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
- /* 표(table) — 좁은 화면에서 가로 스크롤(wrap), 셀 경계선·머리행 강조. */
690
+ /* 표(table) — 좁은 화면에서 가로 스크롤(wrap), 셀 경계선·머리행 강조.
691
+ 에디터(ProseMirror)는 맨 table — 발행 .rt-table 과 같은 룰을 element 로도
692
+ 적용(에디터-뷰어 단일 소스). 발행 HTML 의 table 은 .rt-table 이면서 table 이므로
693
+ 중복 적용돼도 동일 값이라 무해함. */
616
694
  [data-roottale-cms] :where(.rt-cms-article [data-roottale-cms="body"] .rt-table-wrap) {
617
695
  overflow-x: auto;
618
696
  }
619
697
 
620
- [data-roottale-cms] :where(.rt-cms-article [data-roottale-cms="body"] .rt-table) {
698
+ [data-roottale-cms] :where(.rt-cms-article [data-roottale-cms="body"] .rt-table),
699
+ [data-roottale-cms] :where(.rt-cms-article [data-roottale-cms="body"] table) {
621
700
  inline-size: 100%;
622
701
  border-collapse: collapse;
623
702
  font-size: 0.95em;
624
703
  }
625
704
 
626
705
  [data-roottale-cms] :where(.rt-cms-article [data-roottale-cms="body"] .rt-table th),
627
- [data-roottale-cms] :where(.rt-cms-article [data-roottale-cms="body"] .rt-table td) {
706
+ [data-roottale-cms] :where(.rt-cms-article [data-roottale-cms="body"] .rt-table td),
707
+ [data-roottale-cms] :where(.rt-cms-article [data-roottale-cms="body"] table th),
708
+ [data-roottale-cms] :where(.rt-cms-article [data-roottale-cms="body"] table td) {
628
709
  border: 1px solid var(--rt-color-border, #dceef3);
629
710
  padding: var(--rt-spacing-2, 0.5rem) var(--rt-spacing-3, 0.75rem);
630
711
  text-align: start;
631
712
  vertical-align: top;
632
713
  }
633
714
 
634
- [data-roottale-cms] :where(.rt-cms-article [data-roottale-cms="body"] .rt-table th) {
715
+ [data-roottale-cms] :where(.rt-cms-article [data-roottale-cms="body"] .rt-table th),
716
+ [data-roottale-cms] :where(.rt-cms-article [data-roottale-cms="body"] table th) {
635
717
  background: color-mix(in srgb, var(--rt-color-muted, #f7f8f7) 70%, white);
636
718
  font-weight: 600;
637
719
  }
638
720
 
639
721
  /* 글 하단 고정 CTA — 본문 끝 리드 전환 블록. */
640
722
  [data-roottale-cms] :where(.rt-cms-post-cta) {
723
+ max-inline-size: var(--rt-cms-measure, 42.5rem);
724
+ margin-inline: auto;
641
725
  margin-block-start: var(--rt-spacing-6, 1.5rem);
642
726
  padding: var(--rt-spacing-5, 1.25rem);
643
727
  border: 1px solid color-mix(in srgb, var(--rt-color-border, #dceef3) 82%, transparent);
@@ -681,7 +765,8 @@
681
765
  border: 1px solid color-mix(in srgb, var(--rt-color-border, #dceef3) 88%, transparent);
682
766
  border-radius: min(var(--rt-radius-md, 0.5rem), 0.75rem);
683
767
  overflow: hidden;
684
- margin: var(--rt-spacing-6, 1.5rem) 0;
768
+ /* 본문 최상위 블록 — margin 단축 금지(중앙 정렬 유지), margin-block 만. */
769
+ margin-block: var(--rt-spacing-6, 1.5rem);
685
770
  }
686
771
  [data-roottale-cms] :where(.rt-cms-faq-item) {
687
772
  border-block-start: 1px solid color-mix(in srgb, var(--rt-color-border, #dceef3) 88%, transparent);
@@ -737,7 +822,9 @@
737
822
 
738
823
  /* 관련 글 (rt-cms-related) — 글 하단 같은 카테고리 최근 글. */
739
824
  [data-roottale-cms] :where(.rt-cms-related) {
740
- margin: var(--rt-spacing-8, 2.5rem) 0 var(--rt-spacing-6, 1.5rem);
825
+ max-inline-size: var(--rt-cms-measure, 42.5rem);
826
+ margin-inline: auto;
827
+ margin-block: var(--rt-spacing-8, 2.5rem) var(--rt-spacing-6, 1.5rem);
741
828
  }
742
829
  [data-roottale-cms] :where(.rt-cms-related-title) {
743
830
  font-size: var(--rt-font-size-lg, 1.125rem);
@@ -786,13 +873,14 @@
786
873
  [data-roottale-cms] :where(.rt-cms-toc) {
787
874
  position: relative;
788
875
  /* 본문 measure 와 동일 폭으로 정렬(본문 위 인라인 배치). */
789
- max-inline-size: 72ch;
876
+ max-inline-size: var(--rt-cms-measure, 42.5rem);
877
+ margin-inline: auto;
790
878
  background: color-mix(in srgb, var(--rt-color-muted, #f7f8f7) 34%, white);
791
879
  border: 1px solid color-mix(in srgb, var(--rt-color-border, #dceef3) 86%, transparent);
792
880
  border-radius: min(var(--rt-radius-md, 0.5rem), 0.5rem);
793
881
  box-shadow: inset 0 1px 0 rgb(255 255 255 / 0.76);
794
882
  padding: 0.875rem 1rem;
795
- margin: 0 0 var(--rt-spacing-6, 1.5rem);
883
+ margin-block: 0 var(--rt-spacing-6, 1.5rem);
796
884
  }
797
885
 
798
886
  [data-roottale-cms] :where(.rt-cms-toc)::before {
@@ -935,6 +1023,8 @@
935
1023
  display: flex;
936
1024
  align-items: center;
937
1025
  gap: var(--rt-spacing-4, 1rem);
1026
+ max-inline-size: var(--rt-cms-measure, 42.5rem);
1027
+ margin-inline: auto;
938
1028
  margin-block-start: var(--rt-spacing-8, 3rem);
939
1029
  border: 1px solid color-mix(in srgb, var(--rt-color-border, #dceef3) 82%, transparent);
940
1030
  border-radius: calc(var(--rt-radius-md, 0.5rem) * 1.25);
@@ -1001,15 +1091,34 @@
1001
1091
  max-inline-size: 42rem;
1002
1092
  }
1003
1093
 
1094
+ /* 사이드바 목차 모드 — 본문 컬럼(그리드 유닛 중앙 정렬)이 왼쪽으로 이동하므로,
1095
+ 헤더·하단 요소도 같은 왼쪽 라인에 정렬해야 세로 라인이 맞는다. 각 요소의
1096
+ 기본 margin-inline: auto 를 덮어야 해서 소스 순서상 기본 룰들 뒤에 둔다.
1097
+ 100cqi = article 컨테이너 폭. */
1098
+ @container rt-cms-article (min-width: 60rem) {
1099
+ [data-roottale-cms] :where(.rt-cms-article:has(.rt-cms-layout--toc-side) .rt-cms-post-header),
1100
+ [data-roottale-cms] :where(.rt-cms-article:has(.rt-cms-layout--toc-side) .rt-cms-author-card),
1101
+ [data-roottale-cms] :where(.rt-cms-article:has(.rt-cms-layout--toc-side) .rt-cms-post-cta),
1102
+ [data-roottale-cms] :where(.rt-cms-article:has(.rt-cms-layout--toc-side) .rt-cms-related),
1103
+ [data-roottale-cms] :where(.rt-cms-article:has(.rt-cms-layout--toc-side) .rt-archive-related),
1104
+ [data-roottale-cms] :where(.rt-cms-article:has(.rt-cms-layout--toc-side) .rt-archive-facts) {
1105
+ margin-inline: max(
1106
+ 0rem,
1107
+ calc(
1108
+ (100cqi - var(--rt-cms-measure, 42.5rem) - 15rem - var(--rt-spacing-6, 1.5rem)) / 2
1109
+ )
1110
+ )
1111
+ auto;
1112
+ }
1113
+ }
1114
+
1004
1115
  @media (max-width: 640px) {
1005
1116
  [data-roottale-cms] :where(.rt-cms-title) {
1006
1117
  font-size: var(--rt-font-size-2xl, 1.5rem);
1007
1118
  }
1008
1119
 
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
- }
1120
+ /* .rt-cms-post-header .rt-cms-title 는 clamp() 가 이미 좁은 화면 크기를
1121
+ 처리하므로 별도 오버라이드 불필요(제거됨). */
1013
1122
 
1014
1123
  [data-roottale-cms] :where(.rt-cms-toc) {
1015
1124
  padding: 1.125rem;
@@ -1022,6 +1131,12 @@
1022
1131
  [data-roottale-cms] :where(.rt-cms-author-card) {
1023
1132
  align-items: flex-start;
1024
1133
  }
1134
+
1135
+ /* 모바일 본문 — 데스크톱보다 살짝 낮춘 스케일(1.0625 = 17px 기준). */
1136
+ [data-roottale-cms] :where(.rt-cms-body),
1137
+ [data-roottale-cms] :where([data-roottale-cms="body"]) {
1138
+ font-size: var(--rt-cms-body-size, calc(var(--rt-font-size-base, 1rem) * 1.0625));
1139
+ }
1025
1140
  }
1026
1141
 
1027
1142
  /* RootTaleFloatingCta */
@@ -1135,6 +1250,11 @@
1135
1250
  outline-offset: 2px;
1136
1251
  }
1137
1252
 
1253
+ [data-roottale-cms="lead-form"] :where(.rt-cms-field-textarea) {
1254
+ min-block-size: 6rem;
1255
+ resize: vertical;
1256
+ }
1257
+
1138
1258
  [data-roottale-cms="lead-form"] :where(.rt-cms-field-hint) {
1139
1259
  font-size: var(--rt-font-size-xs, 0.75rem);
1140
1260
  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":[]}
@@ -4,12 +4,33 @@ interface HeroCarouselSlideInput {
4
4
  readonly image: string;
5
5
  readonly alt: string;
6
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
+ }[];
7
27
  }
8
28
  interface HeroCarouselViewportProps {
9
29
  readonly slides: readonly HeroCarouselSlideInput[];
10
30
  readonly autoplay: boolean;
11
31
  readonly intervalMs: number;
32
+ readonly scrim?: "none" | "soft" | "strong";
12
33
  }
13
- declare function HeroCarouselViewport({ slides, autoplay, intervalMs, }: HeroCarouselViewportProps): react_jsx_runtime.JSX.Element;
34
+ declare function HeroCarouselViewport({ slides, autoplay, intervalMs, scrim, }: HeroCarouselViewportProps): react_jsx_runtime.JSX.Element;
14
35
 
15
36
  export { type HeroCarouselSlideInput, HeroCarouselViewport, type HeroCarouselViewportProps };
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import {
3
3
  HeroCarouselViewport
4
- } from "./chunk-YUT4BRJY.js";
4
+ } from "./chunk-F3G6RVIR.js";
5
5
  export {
6
6
  HeroCarouselViewport
7
7
  };
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 };