@roottale/cms-renderer-next 0.11.0 → 0.13.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # @roottale/cms-renderer-next
2
2
 
3
+ ## 0.13.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 3002dc1: `@roottale/cms-renderer-next/mui` 추가 — `rootTaleThemeToMui(theme, overrides?)`.
8
+
9
+ admin /design 테마(`RootTaleTheme`, `cms-client` `fetchTheme`)를 MUI `ThemeOptions`
10
+ 호환 객체로 브리지. MUI 기반 고객 사이트(예: kjmtax)가 자체 하드코딩 palette 를
11
+ 버리고 admin 토큰을 단일 SSoT 로 사용하도록 한다(ADR-0040 §Decisions, P0.5 RFC §③).
12
+ - **런타임/타입 모두 MUI 미의존** — 반환값은 `createTheme()` 에 assignable 한 평범한
13
+ 객체. 패키지는 MUI-free 유지(비-MUI 고객사 roottale-web 무영향, `/mui` 미import).
14
+ - 의미가 명확한 매핑만(primary/background/text/divider/font/radius). 모호한 `muted`
15
+ 배경은 CSS 변수(`RootTaleThemeProvider`) 소비로 남김.
16
+
17
+ ## 0.12.0
18
+
19
+ ### Patch Changes
20
+
21
+ - Updated dependencies [de4b90a]
22
+ - @roottale/cms-client@0.12.0
23
+
3
24
  ## 0.11.0
4
25
 
5
26
  ### Minor Changes
package/README.md CHANGED
@@ -39,6 +39,30 @@ import "@roottale/cms-renderer-next/styles";
39
39
 
40
40
  The CSS is scoped under `[data-roottale-cms]` and uses `:where()` so customer styles win without `!important`. Every `--rt-*` variable has a static fallback.
41
41
 
42
+ ## Revalidation webhook
43
+
44
+ For near-real-time blog updates, expose `POST /api/revalidate` on the customer
45
+ site and register that URL in ADMIN. ISR settings such as `revalidate = 1800`
46
+ are fallback only.
47
+
48
+ ```ts
49
+ // app/api/revalidate/route.ts
50
+ import { revalidatePath } from "next/cache";
51
+
52
+ import { createRevalidateRoute } from "@roottale/cms-renderer-next/routes";
53
+
54
+ export const POST = createRevalidateRoute({
55
+ apiKey: process.env.ROOTTALE_API_KEY!,
56
+ apiBase: process.env.ROOTTALE_API_BASE,
57
+ revalidate: revalidatePath,
58
+ });
59
+ ```
60
+
61
+ ADMIN setup: open `/s/{tenant-slug}/sites/{site-id}`, set **Webhook URL** to
62
+ `https://<customer-domain>/api/revalidate`, keep it enabled, and save. See
63
+ [`docs/cms-revalidation-webhooks.md`](../../docs/cms-revalidation-webhooks.md)
64
+ for the full operational contract.
65
+
42
66
  ## Blog list — `app/blog/page.tsx`
43
67
 
44
68
  ```tsx
@@ -15,52 +15,159 @@
15
15
  */
16
16
 
17
17
  [data-roottale-cms] :where(.rt-cms-list) {
18
- display: flex;
19
- flex-direction: column;
20
- gap: var(--rt-spacing-6, 1.5rem);
18
+ display: grid;
19
+ grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr));
20
+ gap: var(--rt-spacing-5, 1.25rem);
21
21
  padding: 0;
22
22
  margin: 0;
23
23
  list-style: none;
24
24
  }
25
25
 
26
26
  [data-roottale-cms] :where(.rt-cms-list-item) {
27
- border-bottom: 1px solid var(--rt-color-border, #e5e7eb);
28
- padding-bottom: var(--rt-spacing-4, 1rem);
29
- }
30
-
31
- [data-roottale-cms] :where(.rt-cms-list-item:last-child) {
32
- border-bottom: 0;
33
- padding-bottom: 0;
27
+ min-inline-size: 0;
34
28
  }
35
29
 
36
30
  [data-roottale-cms] :where(.rt-cms-card-link) {
31
+ position: relative;
32
+ overflow: hidden;
37
33
  color: inherit;
38
34
  text-decoration: none;
39
- display: block;
35
+ display: grid;
36
+ align-content: start;
37
+ min-block-size: 100%;
38
+ border: 1px solid color-mix(in srgb, var(--rt-color-border, #e5e7eb) 78%, transparent);
39
+ border-radius: var(--rt-radius-md, 0.5rem);
40
+ background:
41
+ linear-gradient(
42
+ 180deg,
43
+ rgb(255 255 255 / 0.94),
44
+ color-mix(in srgb, var(--rt-color-muted, #f8fafc) 70%, white)
45
+ ),
46
+ radial-gradient(
47
+ circle at top right,
48
+ color-mix(in srgb, var(--rt-color-primary, #4f46e5) 8%, transparent),
49
+ transparent 46%
50
+ );
51
+ box-shadow:
52
+ 0 16px 38px rgb(17 24 39 / 0.045),
53
+ inset 0 1px 0 rgb(255 255 255 / 0.78);
54
+ padding: var(--rt-spacing-5, 1.25rem);
55
+ transition:
56
+ border-color 180ms ease,
57
+ box-shadow 180ms ease,
58
+ transform 180ms ease;
59
+ }
60
+
61
+ [data-roottale-cms] :where(.rt-cms-card-link)::before {
62
+ content: "";
63
+ position: absolute;
64
+ inset-inline: 1px;
65
+ inset-block-start: 0;
66
+ block-size: 1px;
67
+ background: linear-gradient(
68
+ 90deg,
69
+ transparent,
70
+ color-mix(in srgb, var(--rt-color-primary, #4f46e5) 30%, transparent),
71
+ transparent
72
+ );
73
+ }
74
+
75
+ [data-roottale-cms] :where(.rt-cms-card-link:hover),
76
+ [data-roottale-cms] :where(.rt-cms-card-link:focus-visible) {
77
+ border-color: color-mix(in srgb, var(--rt-color-primary, #4f46e5) 32%, var(--rt-color-border, #e5e7eb));
78
+ box-shadow:
79
+ 0 20px 46px rgb(17 24 39 / 0.07),
80
+ inset 0 1px 0 rgb(255 255 255 / 0.82);
81
+ transform: translateY(-0.125rem);
82
+ }
83
+
84
+ [data-roottale-cms] :where(.rt-cms-card .rt-cms-title) {
85
+ font-size: var(--rt-font-size-xl, 1.375rem);
86
+ font-weight: var(--rt-font-weight-bold, 800);
87
+ line-height: 1.28;
88
+ margin-block-end: var(--rt-spacing-3, 0.75rem);
89
+ }
90
+
91
+ [data-roottale-cms] :where(.rt-cms-card .rt-cms-excerpt) {
92
+ font-size: var(--rt-font-size-base, 1rem);
93
+ line-height: 1.7;
94
+ }
95
+
96
+ [data-roottale-cms] :where(.rt-cms-card .rt-cms-meta) {
97
+ margin-block-end: var(--rt-spacing-4, 1rem);
98
+ }
99
+
100
+ [data-roottale-cms] :where(.rt-cms-card-action) {
101
+ display: inline-flex;
102
+ align-items: center;
103
+ gap: 0.375rem;
104
+ justify-self: start;
105
+ margin-block-start: var(--rt-spacing-5, 1.25rem);
106
+ color: color-mix(in srgb, var(--rt-color-foreground, #111827) 62%, transparent);
107
+ font-family: var(--rt-font-body, system-ui, sans-serif);
108
+ font-size: 0.8125rem;
109
+ font-weight: 800;
110
+ line-height: 1.2;
111
+ }
112
+
113
+ [data-roottale-cms] :where(.rt-cms-card-action)::after {
114
+ content: "";
115
+ inline-size: 1.75rem;
116
+ block-size: 1px;
117
+ background: color-mix(in srgb, var(--rt-color-primary, #4f46e5) 46%, transparent);
118
+ transition: inline-size 180ms ease;
119
+ }
120
+
121
+ [data-roottale-cms] :where(.rt-cms-card-link:hover .rt-cms-card-action)::after,
122
+ [data-roottale-cms] :where(.rt-cms-card-link:focus-visible .rt-cms-card-action)::after {
123
+ inline-size: 2.25rem;
40
124
  }
41
125
 
42
126
  [data-roottale-cms] :where(.rt-cms-title) {
43
127
  font-family: var(--rt-font-display, system-ui, sans-serif);
44
128
  font-size: var(--rt-font-size-2xl, 1.5rem);
45
129
  font-weight: var(--rt-font-weight-bold, 700);
46
- color: var(--rt-color-foreground, #0f172a);
47
- margin: 0 0 var(--rt-spacing-2, 0.5rem);
130
+ color: var(--rt-color-foreground, #111827);
131
+ letter-spacing: 0;
48
132
  line-height: 1.25;
133
+ margin: 0 0 var(--rt-spacing-2, 0.5rem);
49
134
  }
50
135
 
51
136
  [data-roottale-cms] :where(.rt-cms-excerpt) {
52
137
  font-family: var(--rt-font-body, system-ui, sans-serif);
53
- font-size: var(--rt-font-size-base, 1rem);
54
- color: var(--rt-color-muted-foreground, #64748b);
138
+ font-size: var(--rt-font-size-base, 1.0625rem);
139
+ color: color-mix(in srgb, var(--rt-color-foreground, #111827) 68%, transparent);
55
140
  margin: 0;
56
- line-height: 1.5;
141
+ line-height: 1.75;
142
+ max-inline-size: 42rem;
57
143
  }
58
144
 
59
145
  [data-roottale-cms] :where(.rt-cms-meta) {
60
146
  font-family: var(--rt-font-body, system-ui, sans-serif);
61
- font-size: var(--rt-font-size-sm, 0.875rem);
62
- color: var(--rt-color-muted-foreground, #64748b);
63
- margin: 0 0 var(--rt-spacing-2, 0.5rem);
147
+ display: flex;
148
+ flex-wrap: wrap;
149
+ align-items: center;
150
+ gap: 0.375rem 0.75rem;
151
+ font-size: var(--rt-font-size-sm, 0.8125rem);
152
+ font-weight: 600;
153
+ color: color-mix(in srgb, var(--rt-color-foreground, #111827) 56%, transparent);
154
+ margin: 0 0 var(--rt-spacing-3, 0.75rem);
155
+ }
156
+
157
+ [data-roottale-cms] :where(.rt-cms-meta-item) {
158
+ display: inline-flex;
159
+ align-items: center;
160
+ min-block-size: 1.625rem;
161
+ white-space: nowrap;
162
+ }
163
+
164
+ [data-roottale-cms] :where(.rt-cms-meta-item + .rt-cms-meta-item)::before {
165
+ content: "";
166
+ inline-size: 0.25rem;
167
+ block-size: 0.25rem;
168
+ border-radius: 999px;
169
+ background: color-mix(in srgb, var(--rt-color-primary, #4f46e5) 70%, white);
170
+ margin-inline-end: 0.75rem;
64
171
  }
65
172
 
66
173
  [data-roottale-cms] :where(.rt-cms-empty) {
@@ -71,14 +178,54 @@
71
178
 
72
179
  [data-roottale-cms] :where(.rt-cms-article) {
73
180
  font-family: var(--rt-font-body, system-ui, sans-serif);
74
- color: var(--rt-color-foreground, #0f172a);
181
+ color: var(--rt-color-foreground, #111827);
182
+ }
183
+
184
+ [data-roottale-cms] :where(.rt-cms-body) {
185
+ min-inline-size: 0;
186
+ }
187
+
188
+ [data-roottale-cms] :where(.rt-cms-post-header) {
189
+ position: relative;
190
+ overflow: hidden;
191
+ border: 1px solid color-mix(in srgb, var(--rt-color-border, #e5e7eb) 78%, transparent);
192
+ border-radius: calc(var(--rt-radius-md, 0.5rem) * 1.5);
193
+ background:
194
+ linear-gradient(135deg, rgb(255 255 255 / 0.92), color-mix(in srgb, var(--rt-color-muted, #f8fafc) 70%, white)),
195
+ radial-gradient(circle at top right, color-mix(in srgb, var(--rt-color-primary, #4f46e5) 10%, transparent), transparent 42%);
196
+ box-shadow: 0 20px 55px rgb(17 24 39 / 0.05);
197
+ padding: clamp(1.5rem, 3vw, 2.5rem);
198
+ margin-block-end: var(--rt-spacing-6, 1.5rem);
199
+ }
200
+
201
+ [data-roottale-cms] :where(.rt-cms-post-header .rt-cms-title) {
202
+ font-size: var(--rt-font-size-2xl, 2.75rem);
203
+ font-weight: var(--rt-font-weight-bold, 800);
204
+ line-height: 1.12;
205
+ margin-block-end: var(--rt-spacing-3, 0.75rem);
206
+ max-inline-size: 15ch;
207
+ }
208
+
209
+ [data-roottale-cms] :where(.rt-cms-post-header)::after {
210
+ content: "";
211
+ position: absolute;
212
+ inset-inline-start: 0;
213
+ inset-block: 0;
214
+ inline-size: 0.25rem;
215
+ background: linear-gradient(
216
+ 180deg,
217
+ color-mix(in srgb, var(--rt-color-primary, #4f46e5) 72%, white),
218
+ transparent
219
+ );
75
220
  }
76
221
 
77
- [data-roottale-cms] :where(.rt-cms-article h1),
78
222
  [data-roottale-cms] :where(.rt-cms-article h2),
79
- [data-roottale-cms] :where(.rt-cms-article h3) {
223
+ [data-roottale-cms] :where(.rt-cms-article h3),
224
+ [data-roottale-cms] :where(.rt-cms-article h4) {
80
225
  font-family: var(--rt-font-display, system-ui, sans-serif);
81
- margin-top: var(--rt-spacing-6, 1.5rem);
226
+ color: var(--rt-color-foreground, #111827);
227
+ letter-spacing: 0;
228
+ margin-top: var(--rt-spacing-7, 2rem);
82
229
  margin-bottom: var(--rt-spacing-3, 0.75rem);
83
230
  }
84
231
 
@@ -90,6 +237,37 @@
90
237
  [data-roottale-cms] :where(.rt-cms-article a) {
91
238
  color: var(--rt-color-primary, #0070f3);
92
239
  text-decoration: underline;
240
+ text-underline-offset: 0.16em;
241
+ }
242
+
243
+ [data-roottale-cms] :where(.rt-cms-article mark) {
244
+ border-radius: 0.2em;
245
+ padding-inline: 0.08em;
246
+ background: var(--rt-color-highlight, #fef3c7);
247
+ box-decoration-break: clone;
248
+ -webkit-box-decoration-break: clone;
249
+ }
250
+
251
+ [data-roottale-cms] :where(.rt-cms-article u) {
252
+ text-underline-offset: 0.16em;
253
+ text-decoration-thickness: 0.08em;
254
+ }
255
+
256
+ [data-roottale-cms] :where(.rt-cms-article s) {
257
+ text-decoration-thickness: 0.08em;
258
+ }
259
+
260
+ [data-roottale-cms] :where(.rt-cms-article code) {
261
+ border-radius: 0.25rem;
262
+ background: color-mix(in srgb, var(--rt-color-muted, #f8fafc) 72%, var(--rt-color-foreground, #111827) 6%);
263
+ padding: 0.1em 0.3em;
264
+ font-size: 0.92em;
265
+ }
266
+
267
+ [data-roottale-cms] :where(.rt-cms-article pre code) {
268
+ display: block;
269
+ padding: 0;
270
+ background: transparent;
93
271
  }
94
272
 
95
273
  /* RootTalePostCard */
@@ -99,50 +277,242 @@
99
277
 
100
278
  /* RootTaleTableOfContents */
101
279
  [data-roottale-cms] :where(.rt-cms-toc) {
102
- background: var(--rt-color-muted, #f8fafc);
103
- border: 1px solid var(--rt-color-border, #e5e7eb);
104
- border-radius: var(--rt-radius-md, 0.5rem);
105
- padding: var(--rt-spacing-4, 1rem);
280
+ position: relative;
281
+ background: color-mix(in srgb, var(--rt-color-muted, #f8fafc) 34%, white);
282
+ border: 1px solid color-mix(in srgb, var(--rt-color-border, #e5e7eb) 86%, transparent);
283
+ border-radius: min(var(--rt-radius-md, 0.5rem), 0.5rem);
284
+ box-shadow: inset 0 1px 0 rgb(255 255 255 / 0.76);
285
+ padding: 0.875rem 1rem;
106
286
  margin: 0 0 var(--rt-spacing-6, 1.5rem);
107
287
  }
108
288
 
289
+ [data-roottale-cms] :where(.rt-cms-toc)::before {
290
+ content: "";
291
+ position: absolute;
292
+ inset-block: 0.875rem;
293
+ inset-inline-start: 0;
294
+ inline-size: 3px;
295
+ border-radius: 999px;
296
+ background: color-mix(in srgb, var(--rt-color-primary, #4f46e5) 58%, var(--rt-color-border, #e5e7eb));
297
+ }
298
+
299
+ [data-roottale-cms] :where(.rt-cms-toc-details) {
300
+ position: relative;
301
+ }
302
+
303
+ [data-roottale-cms] :where(.rt-cms-toc-summary) {
304
+ display: flex;
305
+ align-items: center;
306
+ gap: 0.625rem;
307
+ cursor: pointer;
308
+ list-style: none;
309
+ min-block-size: 2rem;
310
+ }
311
+
312
+ [data-roottale-cms] :where(.rt-cms-toc-summary)::-webkit-details-marker {
313
+ display: none;
314
+ }
315
+
109
316
  [data-roottale-cms] :where(.rt-cms-toc-title) {
110
317
  font-family: var(--rt-font-display, system-ui, sans-serif);
111
- font-size: var(--rt-font-size-sm, 0.875rem);
112
- font-weight: var(--rt-font-weight-bold, 700);
113
- text-transform: uppercase;
114
- letter-spacing: 0.05em;
115
- margin: 0 0 var(--rt-spacing-3, 0.75rem);
116
- color: var(--rt-color-muted-foreground, #64748b);
318
+ display: flex;
319
+ align-items: center;
320
+ flex: 1 1 auto;
321
+ font-size: 0.9375rem;
322
+ font-weight: var(--rt-font-weight-bold, 800);
323
+ letter-spacing: 0;
324
+ margin: 0;
325
+ color: color-mix(in srgb, var(--rt-color-foreground, #111827) 72%, transparent);
326
+ }
327
+
328
+ [data-roottale-cms] :where(.rt-cms-toc-title)::after {
329
+ content: none;
330
+ }
331
+
332
+ [data-roottale-cms] :where(.rt-cms-toc-toggle) {
333
+ position: relative;
334
+ flex: 0 0 auto;
335
+ inline-size: 1.625rem;
336
+ block-size: 1.625rem;
337
+ border: 1px solid color-mix(in srgb, var(--rt-color-border, #e5e7eb) 90%, transparent);
338
+ border-radius: 999px;
339
+ background: var(--rt-color-background, #ffffff);
340
+ }
341
+
342
+ [data-roottale-cms] :where(.rt-cms-toc-toggle)::before,
343
+ [data-roottale-cms] :where(.rt-cms-toc-toggle)::after {
344
+ content: "";
345
+ position: absolute;
346
+ inset-block-start: 50%;
347
+ inset-inline-start: 50%;
348
+ inline-size: 0.625rem;
349
+ block-size: 1px;
350
+ background: color-mix(in srgb, var(--rt-color-foreground, #111827) 66%, transparent);
351
+ transform: translate(-50%, -50%);
352
+ }
353
+
354
+ [data-roottale-cms] :where(.rt-cms-toc-toggle)::after {
355
+ transform: translate(-50%, -50%) rotate(90deg);
356
+ transition: transform 160ms ease;
357
+ }
358
+
359
+ [data-roottale-cms] :where(.rt-cms-toc-details[open] .rt-cms-toc-toggle)::after {
360
+ transform: translate(-50%, -50%) rotate(0deg);
117
361
  }
118
362
 
119
363
  [data-roottale-cms] :where(.rt-cms-toc-list) {
120
- margin: 0;
121
- padding-left: var(--rt-spacing-4, 1rem);
122
- display: flex;
123
- flex-direction: column;
124
- gap: var(--rt-spacing-1, 0.25rem);
364
+ position: relative;
365
+ margin: 0.5rem 0 0;
366
+ padding: 0;
367
+ display: grid;
368
+ gap: 0.0625rem;
369
+ list-style: none;
370
+ counter-reset: rt-cms-toc;
371
+ }
372
+
373
+ [data-roottale-cms] :where(.rt-cms-toc-list)::before {
374
+ content: none;
125
375
  }
126
376
 
127
377
  [data-roottale-cms] :where(.rt-cms-toc-item) {
378
+ position: relative;
128
379
  font-family: var(--rt-font-body, system-ui, sans-serif);
129
380
  font-size: var(--rt-font-size-sm, 0.875rem);
130
- line-height: 1.5;
381
+ line-height: 1.35;
382
+ counter-increment: rt-cms-toc;
383
+ }
384
+
385
+ [data-roottale-cms] :where(.rt-cms-toc-item)::before {
386
+ content: counter(rt-cms-toc, decimal-leading-zero);
387
+ position: absolute;
388
+ inset-block-start: 0.5rem;
389
+ inset-inline-start: 0.625rem;
390
+ inline-size: 1.375rem;
391
+ color: color-mix(in srgb, var(--rt-color-foreground, #111827) 36%, transparent);
392
+ font-size: 0.6875rem;
393
+ font-variant-numeric: tabular-nums;
394
+ font-weight: 700;
131
395
  }
132
396
 
133
397
  [data-roottale-cms] :where(.rt-cms-toc-level-3) {
134
- padding-left: var(--rt-spacing-4, 1rem);
398
+ margin-left: 0.75rem;
399
+ }
400
+
401
+ [data-roottale-cms] :where(.rt-cms-toc-level-4) {
402
+ margin-left: 1.5rem;
135
403
  }
136
404
 
137
405
  [data-roottale-cms] :where(.rt-cms-toc-item a) {
138
- color: var(--rt-color-foreground, #0f172a);
406
+ display: block;
407
+ color: color-mix(in srgb, var(--rt-color-foreground, #111827) 76%, transparent);
139
408
  text-decoration: none;
409
+ border-radius: min(calc(var(--rt-radius-md, 0.5rem) * 0.75), 0.375rem);
410
+ padding: 0.375rem 0.5rem 0.375rem 2.375rem;
411
+ transition:
412
+ background-color 160ms ease,
413
+ color 160ms ease,
414
+ transform 160ms ease;
140
415
  }
141
416
 
142
417
  [data-roottale-cms] :where(.rt-cms-toc-item a:hover),
143
- [data-roottale-cms] :where(.rt-cms-toc-item a:focus) {
144
- color: var(--rt-color-primary, #0070f3);
145
- text-decoration: underline;
418
+ [data-roottale-cms] :where(.rt-cms-toc-item a:focus-visible) {
419
+ color: var(--rt-color-foreground, #111827);
420
+ background: color-mix(in srgb, var(--rt-color-primary, #4f46e5) 7%, var(--rt-color-background, #ffffff));
421
+ text-decoration: none;
422
+ transform: translateX(0.125rem);
423
+ }
424
+
425
+ [data-roottale-cms] :where(.rt-cms-author-card) {
426
+ display: flex;
427
+ align-items: center;
428
+ gap: var(--rt-spacing-4, 1rem);
429
+ margin-block-start: var(--rt-spacing-8, 3rem);
430
+ border: 1px solid color-mix(in srgb, var(--rt-color-border, #e5e7eb) 82%, transparent);
431
+ border-radius: calc(var(--rt-radius-md, 0.5rem) * 1.25);
432
+ background:
433
+ linear-gradient(135deg, rgb(255 255 255 / 0.92), color-mix(in srgb, var(--rt-color-muted, #f8fafc) 82%, white)),
434
+ radial-gradient(circle at bottom left, color-mix(in srgb, var(--rt-color-primary, #4f46e5) 9%, transparent), transparent 48%);
435
+ box-shadow: 0 18px 44px rgb(17 24 39 / 0.055);
436
+ padding: var(--rt-spacing-5, 1.25rem);
437
+ }
438
+
439
+ [data-roottale-cms] :where(.rt-cms-author-card__avatar),
440
+ [data-roottale-cms] :where(.rt-cms-author-card__image) {
441
+ flex: 0 0 auto;
442
+ inline-size: 3.25rem;
443
+ block-size: 3.25rem;
444
+ border-radius: 999px;
445
+ }
446
+
447
+ [data-roottale-cms] :where(.rt-cms-author-card__avatar) {
448
+ display: grid;
449
+ place-items: center;
450
+ background:
451
+ linear-gradient(135deg, var(--rt-color-primary, #4f46e5), color-mix(in srgb, var(--rt-color-primary, #4f46e5) 42%, #111827));
452
+ color: var(--rt-color-primary-foreground, #ffffff);
453
+ font-family: var(--rt-font-display, system-ui, sans-serif);
454
+ font-size: 0.9375rem;
455
+ font-weight: 800;
456
+ letter-spacing: 0;
457
+ box-shadow: 0 12px 30px color-mix(in srgb, var(--rt-color-primary, #4f46e5) 18%, transparent);
458
+ }
459
+
460
+ [data-roottale-cms] :where(.rt-cms-author-card__image) {
461
+ display: block;
462
+ object-fit: cover;
463
+ box-shadow: 0 12px 30px rgb(17 24 39 / 0.12);
464
+ }
465
+
466
+ [data-roottale-cms] :where(.rt-cms-author-card__body) {
467
+ min-inline-size: 0;
468
+ }
469
+
470
+ [data-roottale-cms] :where(.rt-cms-author-card__eyebrow) {
471
+ margin: 0 0 0.125rem;
472
+ color: color-mix(in srgb, var(--rt-color-foreground, #111827) 46%, transparent);
473
+ font-size: 0.75rem;
474
+ font-weight: 700;
475
+ line-height: 1.2;
476
+ }
477
+
478
+ [data-roottale-cms] :where(.rt-cms-author-card__name) {
479
+ margin: 0;
480
+ color: var(--rt-color-foreground, #111827);
481
+ font-family: var(--rt-font-display, system-ui, sans-serif);
482
+ font-size: 1.0625rem;
483
+ font-weight: 800;
484
+ line-height: 1.3;
485
+ }
486
+
487
+ [data-roottale-cms] :where(.rt-cms-author-card__description) {
488
+ margin: 0.375rem 0 0;
489
+ color: color-mix(in srgb, var(--rt-color-foreground, #111827) 62%, transparent);
490
+ font-size: 0.9375rem;
491
+ line-height: 1.55;
492
+ max-inline-size: 42rem;
493
+ }
494
+
495
+ @media (max-width: 640px) {
496
+ [data-roottale-cms] :where(.rt-cms-title) {
497
+ font-size: var(--rt-font-size-2xl, 1.5rem);
498
+ }
499
+
500
+ [data-roottale-cms] :where(.rt-cms-post-header .rt-cms-title) {
501
+ font-size: var(--rt-font-size-2xl, 2.125rem);
502
+ max-inline-size: 100%;
503
+ }
504
+
505
+ [data-roottale-cms] :where(.rt-cms-toc) {
506
+ padding: 1.125rem;
507
+ }
508
+
509
+ [data-roottale-cms] :where(.rt-cms-toc-item a) {
510
+ padding-inline: 0.5rem;
511
+ }
512
+
513
+ [data-roottale-cms] :where(.rt-cms-author-card) {
514
+ align-items: flex-start;
515
+ }
146
516
  }
147
517
 
148
518
  /* RootTaleFloatingCta */
package/dist/mui.d.ts ADDED
@@ -0,0 +1,93 @@
1
+ import { RootTaleTheme } from '@roottale/cms-client/server';
2
+
3
+ /**
4
+ * `@roottale/cms-renderer-next/mui` — `RootTaleTheme`(admin /design 에서 fetch 하는
5
+ * 런타임 테마 객체) → MUI `ThemeOptions` 호환 객체 브리지.
6
+ *
7
+ * 목적(ADR-0040 §Decisions, P0.5 RFC §③): MUI 기반 고객 사이트(예: kjmtax)가
8
+ * 자체 하드코딩 palette 를 버리고 admin /design 토큰을 **단일 SSoT** 로 쓰도록.
9
+ * 탈-MUI 가 아니라 *이중 토큰 제거 + 공존 충돌 최소화* 가 목적이다.
10
+ *
11
+ * 설계:
12
+ * - **런타임 MUI 의존 없음**. 반환값은 MUI `ThemeOptions` 에 구조적으로 assignable 한
13
+ * 평범한 객체 — `@mui/material` 의 `createTheme()` 에 그대로 넘긴다. 이 패키지는
14
+ * MUI-free 를 유지(비-MUI 고객사 roottale-web 은 이 서브패스를 import 하지 않음).
15
+ * - 의미가 명확한 매핑만 수행. 모호한 토큰(`muted` 배경 등)은 CSS 변수
16
+ * (`<RootTaleThemeProvider>`) 소비로 남긴다 — MUI 에 억지 매핑하지 않는다.
17
+ *
18
+ * ```ts
19
+ * import { createTheme } from "@mui/material/styles";
20
+ * import { fetchTheme } from "@roottale/cms-client/server";
21
+ * import { rootTaleThemeToMui } from "@roottale/cms-renderer-next/mui";
22
+ *
23
+ * const rt = await fetchTheme({ apiKey: process.env.ROOTTALE_API_KEY! });
24
+ * const theme = createTheme(rootTaleThemeToMui(rt, {
25
+ * // 브랜드 고유값은 overrides 로 (deep-merge, overrides 우선).
26
+ * palette: { secondary: { main: "#0B1F49" } },
27
+ * }));
28
+ * ```
29
+ */
30
+
31
+ /** MUI `ThemeOptions` 의 구조적 부분집합 — `createTheme()` 에 assignable. */
32
+ interface RootTaleMuiThemeOptions {
33
+ palette?: {
34
+ primary?: {
35
+ main: string;
36
+ contrastText?: string;
37
+ };
38
+ background?: {
39
+ default?: string;
40
+ };
41
+ text?: {
42
+ primary?: string;
43
+ secondary?: string;
44
+ };
45
+ divider?: string;
46
+ [k: string]: unknown;
47
+ };
48
+ typography?: {
49
+ fontFamily?: string;
50
+ h1?: {
51
+ fontFamily?: string;
52
+ };
53
+ h2?: {
54
+ fontFamily?: string;
55
+ };
56
+ h3?: {
57
+ fontFamily?: string;
58
+ };
59
+ h4?: {
60
+ fontFamily?: string;
61
+ };
62
+ h5?: {
63
+ fontFamily?: string;
64
+ };
65
+ h6?: {
66
+ fontFamily?: string;
67
+ };
68
+ [k: string]: unknown;
69
+ };
70
+ shape?: {
71
+ borderRadius?: number;
72
+ };
73
+ [k: string]: unknown;
74
+ }
75
+ /**
76
+ * `RootTaleTheme` → MUI `ThemeOptions` 호환 객체.
77
+ *
78
+ * 매핑:
79
+ * - `colors.primary` → `palette.primary.main`, `colors.primaryForeground` → `…contrastText`
80
+ * - `colors.background` → `palette.background.default`
81
+ * - `colors.foreground` → `palette.text.primary`, `colors.mutedForeground` → `…secondary`
82
+ * - `colors.border` → `palette.divider`
83
+ * - `fonts.body` → `typography.fontFamily`, `fonts.display` → `typography.h1..h6.fontFamily`
84
+ * - `radius.md` → `shape.borderRadius`(px)
85
+ *
86
+ * `colors.muted` 는 명확한 MUI 대응이 없어 의도적으로 미매핑(CSS 변수로 소비).
87
+ *
88
+ * @param theme admin /design 테마(빈/부분 객체 허용)
89
+ * @param overrides 브랜드 고유값(deep-merge, overrides 우선)
90
+ */
91
+ declare function rootTaleThemeToMui(theme: RootTaleTheme | null | undefined, overrides?: RootTaleMuiThemeOptions): RootTaleMuiThemeOptions;
92
+
93
+ export { type RootTaleMuiThemeOptions, rootTaleThemeToMui };