@roottale/cms-renderer-next 0.44.0 → 0.46.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 (48) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/dist/{chunk-4LQPZDXZ.js → chunk-6MFHA555.js} +24 -6
  3. package/dist/chunk-6MFHA555.js.map +1 -0
  4. package/dist/chunk-FZK3EXYZ.js +103 -0
  5. package/dist/chunk-FZK3EXYZ.js.map +1 -0
  6. package/dist/{chunk-75LHSTKC.js → chunk-JXRHZGRJ.js} +72 -19
  7. package/dist/chunk-JXRHZGRJ.js.map +1 -0
  8. package/dist/chunk-LEHHSI6G.js +64 -0
  9. package/dist/chunk-LEHHSI6G.js.map +1 -0
  10. package/dist/chunk-OMXEU4LS.js +414 -0
  11. package/dist/chunk-OMXEU4LS.js.map +1 -0
  12. package/dist/{chunk-6PDZSFVZ.js → chunk-UM37DOIK.js} +44 -2
  13. package/dist/chunk-UM37DOIK.js.map +1 -0
  14. package/dist/chunk-YLSQAT6B.js +59 -0
  15. package/dist/chunk-YLSQAT6B.js.map +1 -0
  16. package/dist/chunk-ZL2NA3QT.js +313 -0
  17. package/dist/chunk-ZL2NA3QT.js.map +1 -0
  18. package/dist/concerns-client.d.ts +1 -1
  19. package/dist/contact-map-client.d.ts +27 -0
  20. package/dist/contact-map-client.js +8 -0
  21. package/dist/contact-map-client.js.map +1 -0
  22. package/dist/expertise-client.d.ts +7 -1
  23. package/dist/expertise-client.js +2 -1
  24. package/dist/hero-carousel-client.d.ts +18 -2
  25. package/dist/hero-carousel-client.js +2 -1
  26. package/dist/routes.js +6 -4
  27. package/dist/routes.js.map +1 -1
  28. package/dist/server.js +8 -5
  29. package/dist/{site-config-BHcVF993.d.ts → site-config-CeByrPJF.d.ts} +55 -4
  30. package/dist/spaces-client.d.ts +1 -1
  31. package/dist/team-expanding-stage-client.d.ts +35 -0
  32. package/dist/team-expanding-stage-client.js +8 -0
  33. package/dist/team-expanding-stage-client.js.map +1 -0
  34. package/dist/templates.css +457 -5
  35. package/dist/templates.d.ts +26 -3
  36. package/dist/templates.js +13 -6
  37. package/dist/templates.js.map +1 -1
  38. package/dist/ui-react-motion-client.d.ts +4 -1
  39. package/dist/ui-react-motion-client.js +3 -1
  40. package/dist/validate.js +1 -1
  41. package/package.json +7 -4
  42. package/dist/chunk-4LQPZDXZ.js.map +0 -1
  43. package/dist/chunk-6PDZSFVZ.js.map +0 -1
  44. package/dist/chunk-75LHSTKC.js.map +0 -1
  45. package/dist/chunk-F3G6RVIR.js +0 -220
  46. package/dist/chunk-F3G6RVIR.js.map +0 -1
  47. package/dist/chunk-WWS43XCT.js +0 -206
  48. package/dist/chunk-WWS43XCT.js.map +0 -1
@@ -1916,6 +1916,12 @@
1916
1916
  color: var(--rt-color-primary);
1917
1917
  }
1918
1918
 
1919
+ [data-roottale-tpl="expertise"] :where(.rt-tpl-expertise-tab:focus-visible),
1920
+ [data-roottale-tpl="expertise"] :where(.rt-tpl-expertise-panel:focus-visible) {
1921
+ outline: 2px solid var(--rt-color-ring, var(--rt-color-primary));
1922
+ outline-offset: -2px;
1923
+ }
1924
+
1919
1925
  [data-roottale-tpl="expertise"] :where(.rt-tpl-expertise-tab-label),
1920
1926
  [data-roottale-tpl="expertise"] :where(.rt-tpl-expertise-kicker) {
1921
1927
  margin: 0;
@@ -2399,6 +2405,101 @@
2399
2405
  }
2400
2406
  }
2401
2407
 
2408
+ /* ---- curtain transition (ADR-0086 P2 D3, chambarun 이식) ----
2409
+ * 절대배치 스택 — 각 슬라이드가 clip-path 와이프로 스스로를 드러낸다(embla
2410
+ * 가로 스크롤 없음). 타이밍·이징·클립 경로는 원본
2411
+ * chambarun `assets/site.css` `@keyframes curtainReveal`/`.curtain-sheen` 값을
2412
+ * 그대로 유지, 색만 하드코딩 헥스 대신 --rt-* 토큰으로 치환했다. */
2413
+ [data-roottale-tpl="hero"][data-variant="carousel"]
2414
+ :where(.rt-tpl-hero-carousel[data-transition="curtain"] .rt-tpl-hero-carousel-slide) {
2415
+ position: absolute;
2416
+ inset: 0;
2417
+ flex: none;
2418
+ opacity: 0;
2419
+ z-index: 0;
2420
+ pointer-events: none;
2421
+ clip-path: inset(0 0 0 0);
2422
+ }
2423
+
2424
+ [data-roottale-tpl="hero"][data-variant="carousel"]
2425
+ :where(.rt-tpl-hero-carousel[data-transition="curtain"] .rt-tpl-hero-carousel-slide[data-leaving="true"]) {
2426
+ opacity: 1;
2427
+ z-index: 1;
2428
+ }
2429
+
2430
+ [data-roottale-tpl="hero"][data-variant="carousel"]
2431
+ :where(.rt-tpl-hero-carousel[data-transition="curtain"] .rt-tpl-hero-carousel-slide[data-active="true"]) {
2432
+ opacity: 1;
2433
+ z-index: 2;
2434
+ pointer-events: auto;
2435
+ }
2436
+
2437
+ @media (prefers-reduced-motion: no-preference) {
2438
+ [data-roottale-tpl="hero"][data-variant="carousel"]
2439
+ :where(.rt-tpl-hero-carousel[data-transition="curtain"] .rt-tpl-hero-carousel-slide[data-active="true"]) {
2440
+ animation: rt-hero-carousel-curtain-reveal 0.92s cubic-bezier(0.76, 0, 0.24, 1) both;
2441
+ }
2442
+ }
2443
+
2444
+ @keyframes rt-hero-carousel-curtain-reveal {
2445
+ from {
2446
+ clip-path: inset(0 100% 0 0);
2447
+ }
2448
+ to {
2449
+ clip-path: inset(0 0 0 0);
2450
+ }
2451
+ }
2452
+
2453
+ /* 신 스윕 — 활성 슬라이드 위를 가로지르는 얇은 하이라이트 밴드. chambarun 은
2454
+ * teal/green 2색 그라디언트였으나 고객사 테마마다 브랜드색이 달라 토큰
2455
+ * 하나(primary-foreground)의 color-mix 농도 변화로 대체(대면적 채도 배색
2456
+ * 하드코딩 금지 관행과 동일 이유). */
2457
+ [data-roottale-tpl="hero"][data-variant="carousel"]
2458
+ :where(.rt-tpl-hero-carousel[data-transition="curtain"] .rt-tpl-hero-carousel-sheen) {
2459
+ position: absolute;
2460
+ inset-block: 0;
2461
+ inset-inline-start: 0;
2462
+ z-index: 1;
2463
+ inline-size: clamp(82px, 10cqi, 150px);
2464
+ pointer-events: none;
2465
+ opacity: 0;
2466
+ transform: translate3d(-160px, 0, 0);
2467
+ background: linear-gradient(
2468
+ 90deg,
2469
+ transparent 0%,
2470
+ color-mix(in srgb, var(--rt-color-primary-foreground, #ffffff) 22%, transparent) 30%,
2471
+ color-mix(in srgb, var(--rt-color-primary-foreground, #ffffff) 58%, transparent) 48%,
2472
+ color-mix(in srgb, var(--rt-color-primary-foreground, #ffffff) 18%, transparent) 62%,
2473
+ transparent 100%
2474
+ );
2475
+ mix-blend-mode: screen;
2476
+ will-change: transform, opacity;
2477
+ }
2478
+
2479
+ @media (prefers-reduced-motion: no-preference) {
2480
+ [data-roottale-tpl="hero"][data-variant="carousel"]
2481
+ :where(.rt-tpl-hero-carousel[data-transition="curtain"] .rt-tpl-hero-carousel-slide[data-active="true"] .rt-tpl-hero-carousel-sheen) {
2482
+ animation: rt-hero-carousel-curtain-sheen 0.9s cubic-bezier(0.76, 0, 0.24, 1) both;
2483
+ }
2484
+ }
2485
+
2486
+ @keyframes rt-hero-carousel-curtain-sheen {
2487
+ 0% {
2488
+ opacity: 0;
2489
+ transform: translate3d(-160px, 0, 0);
2490
+ }
2491
+ 14% {
2492
+ opacity: 0.82;
2493
+ }
2494
+ 76% {
2495
+ opacity: 0.68;
2496
+ }
2497
+ 100% {
2498
+ opacity: 0;
2499
+ transform: translate3d(100cqi, 0, 0);
2500
+ }
2501
+ }
2502
+
2402
2503
  /* 오버레이 eyebrow — 스크림 위에서는 테마 primary(잉크/민트)가 아니라 밝은
2403
2504
  * 전경색이어야 읽힌다(미감 라운드 2 — 민트 eyebrow 소실 실측). */
2404
2505
  [data-roottale-tpl="hero"][data-variant="carousel"]
@@ -2550,6 +2651,42 @@
2550
2651
  letter-spacing: 0.06em;
2551
2652
  }
2552
2653
 
2654
+ /* D4 — autoplay pause/재생 버튼: 도트·카운터와 같은 우측 클러스터에 절제된
2655
+ * 크기로 둔다(화살표와 동일 언어이나 더 작게 — 보조 컨트롤이라는 위계). */
2656
+ [data-roottale-tpl="hero"][data-variant="carousel"]
2657
+ :where(.rt-tpl-hero-carousel-controls-right) {
2658
+ display: flex;
2659
+ align-items: center;
2660
+ gap: 0.75rem;
2661
+ }
2662
+
2663
+ [data-roottale-tpl="hero"][data-variant="carousel"]
2664
+ :where(.rt-tpl-hero-carousel-playpause) {
2665
+ display: inline-flex;
2666
+ align-items: center;
2667
+ justify-content: center;
2668
+ inline-size: 1.75rem;
2669
+ block-size: 1.75rem;
2670
+ border-radius: 999px;
2671
+ border: 1px solid color-mix(in srgb, var(--rt-color-primary-foreground, #ffffff) 28%, transparent);
2672
+ background: color-mix(in srgb, var(--rt-color-foreground, #06162f) 24%, transparent);
2673
+ color: var(--rt-color-primary-foreground, #ffffff);
2674
+ cursor: pointer;
2675
+ transition: background 180ms ease, border-color 180ms ease;
2676
+ }
2677
+
2678
+ [data-roottale-tpl="hero"][data-variant="carousel"]
2679
+ :where(.rt-tpl-hero-carousel-playpause:hover) {
2680
+ background: color-mix(in srgb, var(--rt-color-foreground, #06162f) 48%, transparent);
2681
+ border-color: color-mix(in srgb, var(--rt-color-primary-foreground, #ffffff) 45%, transparent);
2682
+ }
2683
+
2684
+ [data-roottale-tpl="hero"][data-variant="carousel"]
2685
+ :where(.rt-tpl-hero-carousel-playpause svg) {
2686
+ inline-size: 0.85rem;
2687
+ block-size: 0.85rem;
2688
+ }
2689
+
2553
2690
  [data-roottale-tpl="hours"],
2554
2691
  [data-roottale-tpl="pricing"],
2555
2692
  [data-roottale-tpl="process"] {
@@ -3217,6 +3354,55 @@
3217
3354
  gap: 0.5rem;
3218
3355
  }
3219
3356
 
3357
+ /* 카피 스태거(chambarun `.hero-copy>*` 순차 등장, 타이밍 원본 유지) — curtain
3358
+ * 전환에서만, 슬라이드 전환마다 패널이 `key` 로 리마운트되어 매번 재생된다. */
3359
+ @media (prefers-reduced-motion: no-preference) {
3360
+ [data-roottale-tpl="hero"][data-variant="carousel"]
3361
+ :where(.rt-tpl-hero-carousel[data-transition="curtain"] .rt-tpl-hero-carousel-slide-panel > *) {
3362
+ animation:
3363
+ rt-hero-carousel-copy-in-opacity 0.68s cubic-bezier(0.45, 0, 0.2, 1) both,
3364
+ rt-hero-carousel-copy-in-transform 0.72s cubic-bezier(0.2, 0.75, 0.25, 1) both;
3365
+ }
3366
+
3367
+ [data-roottale-tpl="hero"][data-variant="carousel"]
3368
+ :where(.rt-tpl-hero-carousel[data-transition="curtain"] .rt-tpl-hero-carousel-slide-panel > *:nth-child(1)) {
3369
+ animation-delay: 0.34s;
3370
+ }
3371
+
3372
+ [data-roottale-tpl="hero"][data-variant="carousel"]
3373
+ :where(.rt-tpl-hero-carousel[data-transition="curtain"] .rt-tpl-hero-carousel-slide-panel > *:nth-child(2)) {
3374
+ animation-delay: 0.44s;
3375
+ }
3376
+
3377
+ [data-roottale-tpl="hero"][data-variant="carousel"]
3378
+ :where(.rt-tpl-hero-carousel[data-transition="curtain"] .rt-tpl-hero-carousel-slide-panel > *:nth-child(3)) {
3379
+ animation-delay: 0.54s;
3380
+ }
3381
+
3382
+ [data-roottale-tpl="hero"][data-variant="carousel"]
3383
+ :where(.rt-tpl-hero-carousel[data-transition="curtain"] .rt-tpl-hero-carousel-slide-panel > *:nth-child(n + 4)) {
3384
+ animation-delay: 0.64s;
3385
+ }
3386
+ }
3387
+
3388
+ @keyframes rt-hero-carousel-copy-in-opacity {
3389
+ from {
3390
+ opacity: 0;
3391
+ }
3392
+ to {
3393
+ opacity: 1;
3394
+ }
3395
+ }
3396
+
3397
+ @keyframes rt-hero-carousel-copy-in-transform {
3398
+ from {
3399
+ transform: translateY(18px);
3400
+ }
3401
+ to {
3402
+ transform: none;
3403
+ }
3404
+ }
3405
+
3220
3406
  [data-roottale-tpl="hero"][data-variant="carousel"]
3221
3407
  :where(.rt-tpl-hero-carousel-dots--tabs) {
3222
3408
  gap: 0.4rem;
@@ -3446,6 +3632,56 @@
3446
3632
  font-weight: 800;
3447
3633
  }
3448
3634
 
3635
+ /*
3636
+ * 지도 클릭-투-로드 프리뷰(ADR-0086 P2 D3, `map.mapLoad === "click"`) —
3637
+ * `.rt-tpl-contact-map` 와 같은 박스(aspect-ratio/border/radius)를 그대로
3638
+ * 쓰고, 여기서는 배경 이미지·버튼·바깥 링크 배치만 얹는다.
3639
+ */
3640
+ [data-roottale-tpl="contact"] :where(.rt-tpl-contact-map-click) {
3641
+ position: relative;
3642
+ display: grid;
3643
+ place-items: center;
3644
+ gap: 0.5rem;
3645
+ background: var(--muted);
3646
+ }
3647
+
3648
+ [data-roottale-tpl="contact"] :where(.rt-tpl-contact-map-click-bg) {
3649
+ position: absolute;
3650
+ inset: 0;
3651
+ inline-size: 100%;
3652
+ block-size: 100%;
3653
+ object-fit: cover;
3654
+ }
3655
+
3656
+ [data-roottale-tpl="contact"] :where(.rt-tpl-contact-map-click-btn) {
3657
+ position: relative;
3658
+ z-index: 1;
3659
+ display: inline-flex;
3660
+ align-items: center;
3661
+ gap: 0.4rem;
3662
+ padding: 0.55rem 1rem;
3663
+ border: 1px solid var(--rt-color-border);
3664
+ border-radius: var(--rt-radius-md);
3665
+ background: var(--card);
3666
+ color: var(--foreground);
3667
+ font-size: var(--font-size-sm);
3668
+ font-weight: var(--font-weight-semibold);
3669
+ cursor: pointer;
3670
+ }
3671
+
3672
+ [data-roottale-tpl="contact"] :where(.rt-tpl-contact-map-click-btn:hover) {
3673
+ background: color-mix(in srgb, var(--card) 85%, var(--rt-color-primary) 15%);
3674
+ }
3675
+
3676
+ [data-roottale-tpl="contact"] :where(.rt-tpl-contact-map-click-external) {
3677
+ position: relative;
3678
+ z-index: 1;
3679
+ font-size: 0.78rem;
3680
+ color: var(--accent-foreground);
3681
+ text-decoration: underline;
3682
+ text-underline-offset: var(--space-1);
3683
+ }
3684
+
3449
3685
  [data-roottale-tpl="contact"] :where(.rt-tpl-contact-actions) {
3450
3686
  display: flex;
3451
3687
  flex-wrap: wrap;
@@ -3500,22 +3736,238 @@
3500
3736
  text-decoration: none;
3501
3737
  }
3502
3738
 
3739
+ /* ============ team: expanding-stage — hover/focus/tap 확장 (D3, chambarun
3740
+ * `.staff-stage`/`.staff-panel` 이식, mockup/assets/site.js·site.css 원본
3741
+ * 참조). 접힌 패널은 이름 칩만, 활성 패널은 확장되며 자격정보 패널이 드러난다.
3742
+ * 전환(transition) 선언은 전부 `prefers-reduced-motion: no-preference` 안에만
3743
+ * 둔다 — reduce 시 애니메이션 없이 최종 상태로 즉시 전환(상태 자체는 그대로
3744
+ * 바뀐다, ADR-0073 K3 C1 과 동일 관행). ============ */
3745
+
3503
3746
  [data-roottale-tpl="team"] :where(.rt-tpl-team-expanding-stage) {
3504
- display: grid;
3505
- grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
3506
- gap: 0.75rem;
3747
+ display: flex;
3748
+ flex-direction: column;
3749
+ gap: 0.625rem;
3507
3750
  padding: 0;
3508
3751
  margin: 1.25rem 0 0;
3509
3752
  list-style: none;
3510
3753
  }
3511
3754
 
3755
+ @container (min-width: 46rem) {
3756
+ [data-roottale-tpl="team"] :where(.rt-tpl-team-expanding-stage) {
3757
+ flex-direction: row;
3758
+ block-size: clamp(24rem, 46cqi, 38rem);
3759
+ }
3760
+ }
3761
+
3762
+ [data-roottale-tpl="team"] :where(.rt-tpl-team-expanding-item) {
3763
+ position: relative;
3764
+ flex: none;
3765
+ block-size: 4.75rem;
3766
+ min-inline-size: 0;
3767
+ overflow: hidden;
3768
+ border-radius: var(--rt-radius-lg, 1.5rem);
3769
+ outline: none;
3770
+ cursor: pointer;
3771
+ background: var(--muted);
3772
+ box-shadow: 0 0.75rem 1.75rem -1rem color-mix(in srgb, var(--foreground) 45%, transparent);
3773
+ }
3774
+
3775
+ [data-roottale-tpl="team"] :where(.rt-tpl-team-expanding-item[data-active="true"]) {
3776
+ block-size: 26rem;
3777
+ }
3778
+
3779
+ @container (min-width: 46rem) {
3780
+ [data-roottale-tpl="team"] :where(.rt-tpl-team-expanding-item) {
3781
+ flex: 1 1 0;
3782
+ block-size: auto;
3783
+ }
3784
+
3785
+ [data-roottale-tpl="team"] :where(.rt-tpl-team-expanding-item[data-active="true"]) {
3786
+ flex-grow: 2.7;
3787
+ block-size: auto;
3788
+ }
3789
+ }
3790
+
3791
+ [data-roottale-tpl="team"] :where(.rt-tpl-team-expanding-item:focus-visible) {
3792
+ outline: 2px solid var(--primary);
3793
+ outline-offset: 3px;
3794
+ }
3795
+
3796
+ [data-roottale-tpl="team"] :where(.rt-tpl-team-expanding-photo) {
3797
+ position: absolute;
3798
+ inset: 0;
3799
+ inline-size: 100%;
3800
+ block-size: 100%;
3801
+ object-fit: cover;
3802
+ object-position: center top;
3803
+ }
3804
+
3805
+ [data-roottale-tpl="team"] :where(.rt-tpl-team-expanding-photo--initials) {
3806
+ display: grid;
3807
+ place-items: center;
3808
+ color: var(--primary);
3809
+ font-family: var(--rt-font-display, var(--rt-font-body, system-ui, sans-serif));
3810
+ font-size: clamp(1.5rem, 8cqi, 2.5rem);
3811
+ font-weight: 800;
3812
+ }
3813
+
3814
+ [data-roottale-tpl="team"]
3815
+ :where(.rt-tpl-team-expanding-item[data-active="true"] .rt-tpl-team-expanding-photo) {
3816
+ filter: saturate(1.04) brightness(1.01);
3817
+ }
3818
+
3819
+ [data-roottale-tpl="team"] :where(.rt-tpl-team-expanding-name) {
3820
+ position: absolute;
3821
+ inset-inline: 0;
3822
+ inset-block-end: 0;
3823
+ z-index: 2;
3824
+ display: flex;
3825
+ align-items: center;
3826
+ gap: 0.625rem;
3827
+ padding: 0.75rem 0.9rem;
3828
+ background: linear-gradient(
3829
+ 0deg,
3830
+ color-mix(in srgb, var(--foreground) 78%, transparent),
3831
+ transparent
3832
+ );
3833
+ }
3834
+
3835
+ [data-roottale-tpl="team"] :where(.rt-tpl-team-expanding-name b) {
3836
+ margin: 0;
3837
+ color: var(--background);
3838
+ font-family: var(--rt-font-display, var(--rt-font-body, system-ui, sans-serif));
3839
+ font-size: 0.95rem;
3840
+ font-weight: 800;
3841
+ }
3842
+
3843
+ [data-roottale-tpl="team"] :where(.rt-tpl-team-expanding-name span) {
3844
+ color: color-mix(in srgb, var(--background) 78%, transparent);
3845
+ font-size: 0.75rem;
3846
+ font-weight: 700;
3847
+ white-space: nowrap;
3848
+ overflow: hidden;
3849
+ text-overflow: ellipsis;
3850
+ }
3851
+
3852
+ [data-roottale-tpl="team"]
3853
+ :where(.rt-tpl-team-expanding-item[data-active="true"] .rt-tpl-team-expanding-name) {
3854
+ opacity: 0;
3855
+ }
3856
+
3857
+ [data-roottale-tpl="team"] :where(.rt-tpl-team-expanding-detail) {
3858
+ position: absolute;
3859
+ inset-inline: 0;
3860
+ inset-block-end: 0;
3861
+ z-index: 3;
3862
+ display: grid;
3863
+ gap: 0.5rem;
3864
+ padding: 1.75rem 1.25rem 1.25rem;
3865
+ background: linear-gradient(
3866
+ 0deg,
3867
+ color-mix(in srgb, var(--foreground) 92%, transparent) 0%,
3868
+ color-mix(in srgb, var(--foreground) 55%, transparent) 55%,
3869
+ transparent 100%
3870
+ );
3871
+ opacity: 0;
3872
+ transform: translateY(1rem);
3873
+ pointer-events: none;
3874
+ }
3875
+
3876
+ [data-roottale-tpl="team"]
3877
+ :where(.rt-tpl-team-expanding-item[data-active="true"] .rt-tpl-team-expanding-detail) {
3878
+ opacity: 1;
3879
+ transform: none;
3880
+ pointer-events: auto;
3881
+ }
3882
+
3883
+ [data-roottale-tpl="team"]
3884
+ :where(.rt-tpl-team-expanding-detail .rt-tpl-team-expanding-role) {
3885
+ margin: 0;
3886
+ color: color-mix(in srgb, var(--background) 70%, transparent);
3887
+ font-size: 0.75rem;
3888
+ font-weight: 800;
3889
+ letter-spacing: 0.08em;
3890
+ text-transform: uppercase;
3891
+ }
3892
+
3893
+ [data-roottale-tpl="team"] :where(.rt-tpl-team-expanding-detail .rt-tpl-team-name) {
3894
+ margin: 0;
3895
+ color: var(--background);
3896
+ }
3897
+
3898
+ [data-roottale-tpl="team"]
3899
+ :where(.rt-tpl-team-expanding-detail .rt-tpl-team-specialties li) {
3900
+ color: var(--background);
3901
+ background: color-mix(in srgb, var(--background) 18%, transparent);
3902
+ }
3903
+
3904
+ [data-roottale-tpl="team"] :where(.rt-tpl-team-expanding-detail .rt-tpl-team-credentials li),
3905
+ [data-roottale-tpl="team"] :where(.rt-tpl-team-expanding-detail .rt-tpl-team-bio) {
3906
+ color: color-mix(in srgb, var(--background) 85%, transparent);
3907
+ }
3908
+
3909
+ [data-roottale-tpl="team"]
3910
+ :where(.rt-tpl-team-expanding-detail .rt-tpl-team-credentials li)::before {
3911
+ background: var(--primary);
3912
+ }
3913
+
3914
+ [data-roottale-tpl="team"] :where(.rt-tpl-team-expanding-detail .rt-tpl-team-cta) {
3915
+ justify-self: start;
3916
+ margin-top: 0.25rem;
3917
+ padding: 0.55rem 0.9rem;
3918
+ border-radius: 999px;
3919
+ background: var(--primary);
3920
+ color: var(--primary-foreground, var(--background));
3921
+ }
3922
+
3923
+ [data-roottale-tpl="team"]
3924
+ :where(.rt-tpl-team-expanding-detail .rt-tpl-team-cta:focus-visible) {
3925
+ outline: 2px solid var(--background);
3926
+ outline-offset: 2px;
3927
+ }
3928
+
3512
3929
  [data-roottale-tpl="team"] :where(.rt-tpl-team-expanding-index) {
3513
- color: var(--rt-color-muted-foreground);
3930
+ position: absolute;
3931
+ inset-block-start: 0.75rem;
3932
+ inset-inline-start: 0.9rem;
3933
+ z-index: 2;
3934
+ color: color-mix(in srgb, var(--background) 85%, transparent);
3514
3935
  font-variant-numeric: tabular-nums;
3515
- font-size: 0.78rem;
3936
+ font-size: 0.75rem;
3516
3937
  font-weight: 800;
3517
3938
  }
3518
3939
 
3940
+ @media (prefers-reduced-motion: no-preference) {
3941
+ [data-roottale-tpl="team"] :where(.rt-tpl-team-expanding-item) {
3942
+ transition:
3943
+ flex-grow 0.72s cubic-bezier(0.22, 0.9, 0.24, 1),
3944
+ block-size 0.6s cubic-bezier(0.22, 0.9, 0.24, 1),
3945
+ box-shadow 0.4s ease;
3946
+ }
3947
+
3948
+ [data-roottale-tpl="team"] :where(.rt-tpl-team-expanding-photo) {
3949
+ transition: filter 0.55s ease;
3950
+ }
3951
+
3952
+ [data-roottale-tpl="team"] :where(.rt-tpl-team-expanding-name) {
3953
+ transition: opacity 0.35s ease;
3954
+ }
3955
+
3956
+ [data-roottale-tpl="team"] :where(.rt-tpl-team-expanding-detail) {
3957
+ /* 접힐 때는 빠르게 사라져 패널 축소와 텍스트 잔상이 겹치지 않게. */
3958
+ transition:
3959
+ opacity 0.16s ease,
3960
+ transform 0.3s ease;
3961
+ }
3962
+
3963
+ [data-roottale-tpl="team"]
3964
+ :where(.rt-tpl-team-expanding-item[data-active="true"] .rt-tpl-team-expanding-detail) {
3965
+ transition:
3966
+ opacity 0.45s ease 0.18s,
3967
+ transform 0.5s cubic-bezier(0.16, 1, 0.3, 1) 0.18s;
3968
+ }
3969
+ }
3970
+
3519
3971
  [data-roottale-tpl="footer"] {
3520
3972
  background: color-mix(
3521
3973
  in srgb,
@@ -1,11 +1,34 @@
1
- import { C as ConcernsSection, E as ExpertiseSection, S as SpacesSection, B as BeforeAfterSection, a as ContactSection, b as CtaBannerSection, F as FaqSection, c as FooterSection, H as HeroSection, d as HoursSection, P as PricingSection, e as ProcessSection, R as RichTextSection, f as ServicesSection, T as TeamSection, g as TestimonialsSection, h as TrustSection, i as SiteConfig, j as SiteConfigInput } from './site-config-BHcVF993.js';
2
- export { k as BUILTIN_SECTION_SCHEMAS, l as BUILTIN_SECTION_TYPES, m as BeforeAfterSectionSchema, n as BlogListSection, o as BlogListSectionSchema, p as ConcernsSectionSchema, q as ContactSectionSchema, r as CtaBannerSectionSchema, s as CustomSection, t as CustomSectionSchema, u as ExpertiseSectionSchema, v as FaqSectionSchema, w as FooterSectionSchema, x as HeroSectionSchema, y as HoursSectionSchema, L as LeadFormSection, z as LeadFormSectionSchema, N as NavItem, A as NavItemSchema, D as PricingSectionSchema, G as ProcessSectionSchema, I as RichTextSectionSchema, J as SECTION_VARIANTS, K as Section, M as SectionCta, O as SectionInput, Q as SectionSchema, U as ServicesSectionSchema, V as SiteConfigSchema, W as SpacesSectionSchema, X as TeamSectionSchema, Y as TestimonialsSectionSchema, Z as TrustSectionSchema, _ as defineSiteConfig, $ as parseSiteConfig } from './site-config-BHcVF993.js';
1
+ import { C as ConcernsSection, E as ExpertiseSection, S as SpacesSection, B as BeforeAfterSection, a as ContactSection, b as CtaBannerSection, F as FaqSection, c as FooterSection, H as HeroSection, d as HoursSection, P as PricingSection, e as ProcessSection, R as RichTextSection, f as ServicesSection, T as TeamSection, g as TestimonialsSection, h as TrustSection, i as SiteConfig, j as SiteConfigInput } from './site-config-CeByrPJF.js';
2
+ export { k as BUILTIN_SECTION_SCHEMAS, l as BUILTIN_SECTION_TYPES, m as BeforeAfterSectionSchema, n as BlogListSection, o as BlogListSectionSchema, p as ConcernsSectionSchema, q as ContactSectionSchema, r as CtaBannerSectionSchema, s as CustomSection, t as CustomSectionSchema, u as ExpertiseSectionSchema, v as FaqSectionSchema, w as FooterSectionSchema, x as HeroSectionSchema, y as HoursSectionSchema, L as LeadFormSection, z as LeadFormSectionSchema, N as NavItem, A as NavItemSchema, D as PricingSectionSchema, G as ProcessSectionSchema, I as RichTextSectionSchema, J as SECTION_VARIANTS, K as Section, M as SectionCta, O as SectionInput, Q as SectionSchema, U as ServicesSectionSchema, V as SiteConfigSchema, W as SpacesSectionSchema, X as TeamSectionSchema, Y as TestimonialsSectionSchema, Z as TrustSectionSchema, _ as defineSiteConfig, $ as parseSiteConfig } from './site-config-CeByrPJF.js';
3
3
  export { R as RootTaleCssVars, t as themeToCssVars } from './theme-css-vars-CD3jcEkp.js';
4
4
  import { ReactElement, CSSProperties, ReactNode } from 'react';
5
5
  import 'zod';
6
6
  import '@roottale/cms-client/server';
7
7
  import '@roottale/ui-react/theme';
8
8
 
9
+ /**
10
+ * `SiteConfig` 계약 버전 (ADR-0086 D2 — 팩 ↔ renderer 호환성 계약).
11
+ *
12
+ * `site-config.ts` 는 이미 SIZE_OK 상한 근처(canonical SectionSchema
13
+ * registry)라 버전 해석 로직은 별도 모듈에 두고 `site-config.ts` 에서
14
+ * re-export 한다.
15
+ *
16
+ * 팩 manifest(`packages/assembly/src/pack-manifest.ts`)의
17
+ * `siteConfigVersion` 필드가 이 버전과 비교 대상 — 팩이 요구하는 버전이
18
+ * renderer 가 실제로 해석하는 버전보다 높으면 조립 전 fail-fast 해야 한다.
19
+ */
20
+ /** 현재 `SiteConfig` 스키마 버전. 계약 필드 추가 등 breaking 변경 시에만 올린다. */
21
+ declare const SITE_CONFIG_SCHEMA_VERSION = 1;
22
+ /**
23
+ * `config.schemaVersion` 해석 헬퍼 — 필드 미지정(기존 고객
24
+ * `site.config.ts`, `schemaVersion` 도입 이전 작성분)은 버전 1 로 해석한다
25
+ * (하위 호환, ADR-0086 D2). 현재 버전 상수가 아니라 리터럴 1 — 후일
26
+ * `SITE_CONFIG_SCHEMA_VERSION` 이 올라도 미지정 config 는 계속 1 이다.
27
+ */
28
+ declare function resolveSiteConfigSchemaVersion(config: {
29
+ schemaVersion?: number;
30
+ }): number;
31
+
9
32
  declare function TemplateExpertise(section: ExpertiseSection): ReactElement;
10
33
  declare function TemplateSpaces(section: SpacesSection): ReactElement;
11
34
  declare function TemplateConcerns(section: ConcernsSection): ReactElement;
@@ -106,4 +129,4 @@ interface SiteRendererProps {
106
129
  */
107
130
  declare function SiteRenderer(props: SiteRendererProps): ReactElement;
108
131
 
109
- export { BeforeAfterSection, ConcernsSection, ContactSection, CtaBannerSection, ExpertiseSection, FaqSection, FooterSection, HeroSection, HoursSection, PricingSection, ProcessSection, RichTextSection, type SectionComponent, ServicesSection, SiteConfig, SiteConfigInput, SiteRenderer, type SiteRendererProps, SpacesSection, TeamSection, TemplateBeforeAfter, TemplateConcerns, TemplateContact, TemplateCtaBanner, TemplateExpertise, TemplateFaq, TemplateFooter, TemplateHero, TemplateHours, TemplatePricing, TemplateProcess, TemplateRichText, TemplateServices, TemplateSpaces, TemplateTeam, TemplateTestimonials, TemplateTrust, TestimonialsSection, TrustSection, sectionThemeStyle };
132
+ export { BeforeAfterSection, ConcernsSection, ContactSection, CtaBannerSection, ExpertiseSection, FaqSection, FooterSection, HeroSection, HoursSection, PricingSection, ProcessSection, RichTextSection, SITE_CONFIG_SCHEMA_VERSION, type SectionComponent, ServicesSection, SiteConfig, SiteConfigInput, SiteRenderer, type SiteRendererProps, SpacesSection, TeamSection, TemplateBeforeAfter, TemplateConcerns, TemplateContact, TemplateCtaBanner, TemplateExpertise, TemplateFaq, TemplateFooter, TemplateHero, TemplateHours, TemplatePricing, TemplateProcess, TemplateRichText, TemplateServices, TemplateSpaces, TemplateTeam, TemplateTestimonials, TemplateTrust, TestimonialsSection, TrustSection, resolveSiteConfigSchemaVersion, sectionThemeStyle };
package/dist/templates.js CHANGED
@@ -20,12 +20,14 @@ import {
20
20
  TemplateTrust,
21
21
  sectionThemeStyle,
22
22
  themeToCssVars
23
- } from "./chunk-75LHSTKC.js";
23
+ } from "./chunk-JXRHZGRJ.js";
24
24
  import "./chunk-U5AKACPN.js";
25
- import "./chunk-4LQPZDXZ.js";
25
+ import "./chunk-LEHHSI6G.js";
26
+ import "./chunk-6MFHA555.js";
27
+ import "./chunk-FZK3EXYZ.js";
26
28
  import "./chunk-K62RR4UQ.js";
27
29
  import "./chunk-H46JUIHZ.js";
28
- import "./chunk-WWS43XCT.js";
30
+ import "./chunk-ZL2NA3QT.js";
29
31
  import "./chunk-FUA5H6JE.js";
30
32
  import {
31
33
  BUILTIN_SECTION_SCHEMAS,
@@ -47,6 +49,7 @@ import {
47
49
  ProcessSectionSchema,
48
50
  RichTextSectionSchema,
49
51
  SECTION_VARIANTS,
52
+ SITE_CONFIG_SCHEMA_VERSION,
50
53
  SectionSchema,
51
54
  ServicesSectionSchema,
52
55
  SiteConfigSchema,
@@ -55,9 +58,11 @@ import {
55
58
  TestimonialsSectionSchema,
56
59
  TrustSectionSchema,
57
60
  defineSiteConfig,
58
- parseSiteConfig
59
- } from "./chunk-6PDZSFVZ.js";
60
- import "./chunk-F3G6RVIR.js";
61
+ parseSiteConfig,
62
+ resolveSiteConfigSchemaVersion
63
+ } from "./chunk-UM37DOIK.js";
64
+ import "./chunk-OMXEU4LS.js";
65
+ import "./chunk-YLSQAT6B.js";
61
66
 
62
67
  // src/templates/SiteRenderer.tsx
63
68
  import {
@@ -243,6 +248,7 @@ export {
243
248
  ProcessSectionSchema,
244
249
  RichTextSectionSchema,
245
250
  SECTION_VARIANTS,
251
+ SITE_CONFIG_SCHEMA_VERSION,
246
252
  SectionSchema,
247
253
  ServicesSectionSchema,
248
254
  SiteConfigSchema,
@@ -270,6 +276,7 @@ export {
270
276
  TrustSectionSchema,
271
277
  defineSiteConfig,
272
278
  parseSiteConfig,
279
+ resolveSiteConfigSchemaVersion,
273
280
  sectionThemeStyle,
274
281
  themeToCssVars
275
282
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/templates/SiteRenderer.tsx"],"sourcesContent":["/**\n * `SiteRenderer` — `SiteConfig.pages[path]` 섹션 배열을 registry 로 렌더.\n *\n * ADR-0040 P2 + P0.5 RFC §①:\n * - 빌트인 registry + 클라 주입 `customSections` 머지. **빌트인이 우선** —\n * 클라가 빌트인 type 을 가로채 계약이 갈라지는 것 방지 (고유 섹션은 고유\n * type 으로).\n * - **render-prop 금지**: 임의 JSX escape hatch 는 customSections registry 로만.\n * - **admin preview parity**: admin draft/preview 도 동일 `SiteRenderer` + 동일\n * 빌트인 registry 를 탄다. customSections 는 클라 repo 자산 — preview 에선\n * 미주입 → `unknownSection: \"placeholder\"` 로 skeleton 표시.\n * - cms-bound 섹션(blog-list/lead-form)은 `RootTaleBlogList`/`RootTaleLeadForm`\n * 위임 (단방향: templates → renderer 코어).\n */\nimport {\n Fragment,\n type CSSProperties,\n type ReactElement,\n type ReactNode,\n} from \"react\";\n\nimport { RootTaleLeadForm } from \"../LeadForm.js\";\nimport { RootTaleBlogList } from \"../server.js\";\nimport {\n TemplateConcerns,\n TemplateExpertise,\n TemplateSpaces,\n} from \"./professional-sections.js\";\nimport {\n TemplateBeforeAfter,\n TemplateContact,\n TemplateCtaBanner,\n TemplateFaq,\n TemplateFooter,\n TemplateHero,\n TemplateHours,\n TemplatePricing,\n TemplateProcess,\n TemplateRichText,\n TemplateServices,\n TemplateTeam,\n TemplateTestimonials,\n TemplateTrust,\n sectionThemeStyle,\n} from \"./sections.js\";\nimport {\n type BeforeAfterSection,\n type BlogListSection,\n type ConcernsSection,\n type ContactSection,\n type CtaBannerSection,\n type ExpertiseSection,\n type FaqSection,\n type FooterSection,\n type HeroSection,\n type HoursSection,\n type LeadFormSection,\n type PricingSection,\n type ProcessSection,\n type RichTextSection,\n type Section,\n type ServicesSection,\n type SpacesSection,\n type SiteConfig,\n type SiteConfigInput,\n type TeamSection,\n type TestimonialsSection,\n type TrustSection,\n parseSiteConfig,\n} from \"./site-config.js\";\n\n/** customSections registry 컴포넌트 계약 (RFC §①). */\nexport type SectionComponent = (p: {\n id?: string;\n props: Record<string, unknown>;\n variant?: string;\n theme?: Record<string, string>;\n}) => ReactNode;\n\nexport interface SiteRendererProps {\n /** `defineSiteConfig()` 결과 또는 raw config (내부에서 검증). */\n config: SiteConfig | SiteConfigInput;\n /** 현재 페이지 경로 — `config.pages` 의 key (\"/\", \"/services\", …). */\n path: string;\n /** cms-bound 섹션(blog-list/lead-form 의 theme fetch) 위임용. 서버 전용. */\n apiKey: string;\n /** self-host 등 API base override (`ROOTTALE_API_BASE`). */\n baseUrl?: string;\n /** 클라 repo 고유 섹션 registry (escape hatch). 빌트인 type 은 가로챌 수 없음. */\n customSections?: Record<string, SectionComponent>;\n /**\n * registry 에 없는 type 처리. 기본 \"warn-dev\" = dev 에서 경고 박스, prod 에서\n * 생략. admin preview 는 \"placeholder\" (customSections 미주입 환경).\n */\n unknownSection?: \"warn-dev\" | \"placeholder\" | \"ignore\";\n}\n\nfunction isDev(): boolean {\n // dts 빌드는 node types 없이 돈다 — process 는 globalThis 경유로 안전 접근.\n const env =\n (globalThis as { process?: { env?: Record<string, string | undefined> } })\n .process?.env ?? {};\n return env.NODE_ENV !== \"production\";\n}\n\nfunction UnknownSection({\n type,\n mode,\n}: {\n type: string;\n mode: \"warn-dev\" | \"placeholder\" | \"ignore\";\n}): ReactElement | null {\n if (mode === \"ignore\") return null;\n if (mode === \"placeholder\") {\n return (\n <section\n data-roottale-tpl=\"placeholder\"\n className=\"rt-tpl-section rt-tpl-placeholder\"\n aria-hidden\n >\n <div className=\"rt-tpl-container\">\n <div className=\"rt-tpl-placeholder-box\">{type}</div>\n </div>\n </section>\n );\n }\n if (!isDev()) return null;\n const style: CSSProperties = {\n padding: \"1rem\",\n border: \"1px dashed #c00\",\n color: \"#c00\",\n };\n return (\n <div style={style}>\n 알 수 없는 섹션 type: <code>{type}</code> — SiteRenderer 의 customSections\n 에 등록하세요.\n </div>\n );\n}\n\nfunction renderBuiltin(\n section: Section,\n ctx: { apiKey: string; baseUrl?: string },\n): ReactNode | undefined {\n switch (section.type) {\n case \"hero\":\n return <TemplateHero {...(section as HeroSection)} />;\n case \"rich-text\":\n return <TemplateRichText {...(section as RichTextSection)} />;\n case \"services\":\n return <TemplateServices {...(section as ServicesSection)} />;\n case \"faq\":\n return <TemplateFaq {...(section as FaqSection)} />;\n case \"footer\":\n return <TemplateFooter {...(section as FooterSection)} />;\n case \"cta-banner\":\n return <TemplateCtaBanner {...(section as CtaBannerSection)} />;\n case \"blog-list\": {\n const s = section as BlogListSection;\n const base = s.props.blogBasePath ?? \"/blog\";\n return (\n <section\n data-roottale-tpl=\"blog-list\"\n data-roottale-cms=\"list\"\n id={s.id}\n className=\"rt-tpl-section rt-tpl-blog-list\"\n style={sectionThemeStyle(s.theme)}\n >\n <div className=\"rt-tpl-container\">\n {s.props.heading ? (\n <h2 className=\"rt-tpl-heading\">{s.props.heading}</h2>\n ) : null}\n <RootTaleBlogList\n apiKey={ctx.apiKey}\n baseUrl={ctx.baseUrl}\n limit={s.props.limit}\n emptyMessage={s.props.emptyMessage}\n postHref={(post) => `${base}/${post.slug}`}\n />\n </div>\n </section>\n );\n }\n case \"lead-form\": {\n const s = section as LeadFormSection;\n return (\n <section\n data-roottale-tpl=\"lead-form\"\n data-roottale-cms=\"lead-form\"\n id={s.id}\n className=\"rt-tpl-section rt-tpl-lead-form\"\n style={sectionThemeStyle(s.theme)}\n >\n <div className=\"rt-tpl-container\">\n <RootTaleLeadForm\n action={s.props.action}\n vertical={s.props.vertical}\n heading={s.props.heading}\n description={s.props.description}\n submitLabel={s.props.submitLabel}\n />\n </div>\n </section>\n );\n }\n case \"trust\":\n return <TemplateTrust {...(section as TrustSection)} />;\n case \"contact\":\n return <TemplateContact {...(section as ContactSection)} />;\n case \"hours\":\n return <TemplateHours {...(section as HoursSection)} />;\n case \"pricing\":\n return <TemplatePricing {...(section as PricingSection)} />;\n case \"process\":\n return <TemplateProcess {...(section as ProcessSection)} />;\n case \"before-after\":\n return <TemplateBeforeAfter {...(section as BeforeAfterSection)} />;\n case \"testimonials\":\n return <TemplateTestimonials {...(section as TestimonialsSection)} />;\n case \"expertise\":\n return <TemplateExpertise {...(section as ExpertiseSection)} />;\n case \"spaces\":\n return <TemplateSpaces {...(section as SpacesSection)} />;\n case \"concerns\":\n return <TemplateConcerns {...(section as ConcernsSection)} />;\n case \"team\":\n return <TemplateTeam {...(section as TeamSection)} />;\n default:\n return undefined;\n }\n}\n\n/**\n * 한 페이지의 섹션 배열을 순서대로 렌더.\n *\n * ```tsx\n * // app/services/page.tsx\n * import config from \"@/site.config\";\n * import { SiteRenderer } from \"@roottale/cms-renderer-next/templates\";\n *\n * export default function ServicesPage() {\n * return (\n * <SiteRenderer config={config} path=\"/services\"\n * apiKey={process.env.ROOTTALE_API_KEY!} />\n * );\n * }\n * ```\n */\nexport function SiteRenderer(props: SiteRendererProps): ReactElement {\n const {\n path,\n apiKey,\n baseUrl,\n customSections = {},\n unknownSection = \"warn-dev\",\n } = props;\n const config = parseSiteConfig(props.config);\n const sections = config.pages[path] ?? [];\n return (\n <>\n {sections.length === 0 ? (\n <UnknownSection\n type={`pages[\"${path}\"] (섹션 0개)`}\n mode={unknownSection}\n />\n ) : null}\n {sections.map((section, i) => {\n const key = section.id ?? `${section.type}-${i}`;\n const builtin = renderBuiltin(section, { apiKey, baseUrl });\n if (builtin !== undefined) {\n return <Fragment key={key}>{builtin}</Fragment>;\n }\n const Custom = customSections[section.type];\n if (Custom) {\n return (\n <Fragment key={key}>\n {Custom({\n id: section.id,\n props: section.props as Record<string, unknown>,\n variant: section.variant,\n theme: section.theme,\n })}\n </Fragment>\n );\n }\n return (\n <UnknownSection key={key} type={section.type} mode={unknownSection} />\n );\n })}\n </>\n );\n}\n"],"mappings":"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","names":["Fragment"]}
1
+ {"version":3,"sources":["../src/templates/SiteRenderer.tsx"],"sourcesContent":["/**\n * `SiteRenderer` — `SiteConfig.pages[path]` 섹션 배열을 registry 로 렌더.\n *\n * ADR-0040 P2 + P0.5 RFC §①:\n * - 빌트인 registry + 클라 주입 `customSections` 머지. **빌트인이 우선** —\n * 클라가 빌트인 type 을 가로채 계약이 갈라지는 것 방지 (고유 섹션은 고유\n * type 으로).\n * - **render-prop 금지**: 임의 JSX escape hatch 는 customSections registry 로만.\n * - **admin preview parity**: admin draft/preview 도 동일 `SiteRenderer` + 동일\n * 빌트인 registry 를 탄다. customSections 는 클라 repo 자산 — preview 에선\n * 미주입 → `unknownSection: \"placeholder\"` 로 skeleton 표시.\n * - cms-bound 섹션(blog-list/lead-form)은 `RootTaleBlogList`/`RootTaleLeadForm`\n * 위임 (단방향: templates → renderer 코어).\n */\nimport {\n Fragment,\n type CSSProperties,\n type ReactElement,\n type ReactNode,\n} from \"react\";\n\nimport { RootTaleLeadForm } from \"../LeadForm.js\";\nimport { RootTaleBlogList } from \"../server.js\";\nimport {\n TemplateConcerns,\n TemplateExpertise,\n TemplateSpaces,\n} from \"./professional-sections.js\";\nimport {\n TemplateBeforeAfter,\n TemplateContact,\n TemplateCtaBanner,\n TemplateFaq,\n TemplateFooter,\n TemplateHero,\n TemplateHours,\n TemplatePricing,\n TemplateProcess,\n TemplateRichText,\n TemplateServices,\n TemplateTeam,\n TemplateTestimonials,\n TemplateTrust,\n sectionThemeStyle,\n} from \"./sections.js\";\nimport {\n type BeforeAfterSection,\n type BlogListSection,\n type ConcernsSection,\n type ContactSection,\n type CtaBannerSection,\n type ExpertiseSection,\n type FaqSection,\n type FooterSection,\n type HeroSection,\n type HoursSection,\n type LeadFormSection,\n type PricingSection,\n type ProcessSection,\n type RichTextSection,\n type Section,\n type ServicesSection,\n type SpacesSection,\n type SiteConfig,\n type SiteConfigInput,\n type TeamSection,\n type TestimonialsSection,\n type TrustSection,\n parseSiteConfig,\n} from \"./site-config.js\";\n\n/** customSections registry 컴포넌트 계약 (RFC §①). */\nexport type SectionComponent = (p: {\n id?: string;\n props: Record<string, unknown>;\n variant?: string;\n theme?: Record<string, string>;\n}) => ReactNode;\n\nexport interface SiteRendererProps {\n /** `defineSiteConfig()` 결과 또는 raw config (내부에서 검증). */\n config: SiteConfig | SiteConfigInput;\n /** 현재 페이지 경로 — `config.pages` 의 key (\"/\", \"/services\", …). */\n path: string;\n /** cms-bound 섹션(blog-list/lead-form 의 theme fetch) 위임용. 서버 전용. */\n apiKey: string;\n /** self-host 등 API base override (`ROOTTALE_API_BASE`). */\n baseUrl?: string;\n /** 클라 repo 고유 섹션 registry (escape hatch). 빌트인 type 은 가로챌 수 없음. */\n customSections?: Record<string, SectionComponent>;\n /**\n * registry 에 없는 type 처리. 기본 \"warn-dev\" = dev 에서 경고 박스, prod 에서\n * 생략. admin preview 는 \"placeholder\" (customSections 미주입 환경).\n */\n unknownSection?: \"warn-dev\" | \"placeholder\" | \"ignore\";\n}\n\nfunction isDev(): boolean {\n // dts 빌드는 node types 없이 돈다 — process 는 globalThis 경유로 안전 접근.\n const env =\n (globalThis as { process?: { env?: Record<string, string | undefined> } })\n .process?.env ?? {};\n return env.NODE_ENV !== \"production\";\n}\n\nfunction UnknownSection({\n type,\n mode,\n}: {\n type: string;\n mode: \"warn-dev\" | \"placeholder\" | \"ignore\";\n}): ReactElement | null {\n if (mode === \"ignore\") return null;\n if (mode === \"placeholder\") {\n return (\n <section\n data-roottale-tpl=\"placeholder\"\n className=\"rt-tpl-section rt-tpl-placeholder\"\n aria-hidden\n >\n <div className=\"rt-tpl-container\">\n <div className=\"rt-tpl-placeholder-box\">{type}</div>\n </div>\n </section>\n );\n }\n if (!isDev()) return null;\n const style: CSSProperties = {\n padding: \"1rem\",\n border: \"1px dashed #c00\",\n color: \"#c00\",\n };\n return (\n <div style={style}>\n 알 수 없는 섹션 type: <code>{type}</code> — SiteRenderer 의 customSections\n 에 등록하세요.\n </div>\n );\n}\n\nfunction renderBuiltin(\n section: Section,\n ctx: { apiKey: string; baseUrl?: string },\n): ReactNode | undefined {\n switch (section.type) {\n case \"hero\":\n return <TemplateHero {...(section as HeroSection)} />;\n case \"rich-text\":\n return <TemplateRichText {...(section as RichTextSection)} />;\n case \"services\":\n return <TemplateServices {...(section as ServicesSection)} />;\n case \"faq\":\n return <TemplateFaq {...(section as FaqSection)} />;\n case \"footer\":\n return <TemplateFooter {...(section as FooterSection)} />;\n case \"cta-banner\":\n return <TemplateCtaBanner {...(section as CtaBannerSection)} />;\n case \"blog-list\": {\n const s = section as BlogListSection;\n const base = s.props.blogBasePath ?? \"/blog\";\n return (\n <section\n data-roottale-tpl=\"blog-list\"\n data-roottale-cms=\"list\"\n id={s.id}\n className=\"rt-tpl-section rt-tpl-blog-list\"\n style={sectionThemeStyle(s.theme)}\n >\n <div className=\"rt-tpl-container\">\n {s.props.heading ? (\n <h2 className=\"rt-tpl-heading\">{s.props.heading}</h2>\n ) : null}\n <RootTaleBlogList\n apiKey={ctx.apiKey}\n baseUrl={ctx.baseUrl}\n limit={s.props.limit}\n emptyMessage={s.props.emptyMessage}\n postHref={(post) => `${base}/${post.slug}`}\n />\n </div>\n </section>\n );\n }\n case \"lead-form\": {\n const s = section as LeadFormSection;\n return (\n <section\n data-roottale-tpl=\"lead-form\"\n data-roottale-cms=\"lead-form\"\n id={s.id}\n className=\"rt-tpl-section rt-tpl-lead-form\"\n style={sectionThemeStyle(s.theme)}\n >\n <div className=\"rt-tpl-container\">\n <RootTaleLeadForm\n action={s.props.action}\n vertical={s.props.vertical}\n heading={s.props.heading}\n description={s.props.description}\n submitLabel={s.props.submitLabel}\n />\n </div>\n </section>\n );\n }\n case \"trust\":\n return <TemplateTrust {...(section as TrustSection)} />;\n case \"contact\":\n return <TemplateContact {...(section as ContactSection)} />;\n case \"hours\":\n return <TemplateHours {...(section as HoursSection)} />;\n case \"pricing\":\n return <TemplatePricing {...(section as PricingSection)} />;\n case \"process\":\n return <TemplateProcess {...(section as ProcessSection)} />;\n case \"before-after\":\n return <TemplateBeforeAfter {...(section as BeforeAfterSection)} />;\n case \"testimonials\":\n return <TemplateTestimonials {...(section as TestimonialsSection)} />;\n case \"expertise\":\n return <TemplateExpertise {...(section as ExpertiseSection)} />;\n case \"spaces\":\n return <TemplateSpaces {...(section as SpacesSection)} />;\n case \"concerns\":\n return <TemplateConcerns {...(section as ConcernsSection)} />;\n case \"team\":\n return <TemplateTeam {...(section as TeamSection)} />;\n default:\n return undefined;\n }\n}\n\n/**\n * 한 페이지의 섹션 배열을 순서대로 렌더.\n *\n * ```tsx\n * // app/services/page.tsx\n * import config from \"@/site.config\";\n * import { SiteRenderer } from \"@roottale/cms-renderer-next/templates\";\n *\n * export default function ServicesPage() {\n * return (\n * <SiteRenderer config={config} path=\"/services\"\n * apiKey={process.env.ROOTTALE_API_KEY!} />\n * );\n * }\n * ```\n */\nexport function SiteRenderer(props: SiteRendererProps): ReactElement {\n const {\n path,\n apiKey,\n baseUrl,\n customSections = {},\n unknownSection = \"warn-dev\",\n } = props;\n const config = parseSiteConfig(props.config);\n const sections = config.pages[path] ?? [];\n return (\n <>\n {sections.length === 0 ? (\n <UnknownSection\n type={`pages[\"${path}\"] (섹션 0개)`}\n mode={unknownSection}\n />\n ) : null}\n {sections.map((section, i) => {\n const key = section.id ?? `${section.type}-${i}`;\n const builtin = renderBuiltin(section, { apiKey, baseUrl });\n if (builtin !== undefined) {\n return <Fragment key={key}>{builtin}</Fragment>;\n }\n const Custom = customSections[section.type];\n if (Custom) {\n return (\n <Fragment key={key}>\n {Custom({\n id: section.id,\n props: section.props as Record<string, unknown>,\n variant: section.variant,\n theme: section.theme,\n })}\n </Fragment>\n );\n }\n return (\n <UnknownSection key={key} type={section.type} mode={unknownSection} />\n );\n })}\n </>\n );\n}\n"],"mappings":"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","names":["Fragment"]}