@aeriz/wysiwyg 1.0.5 → 2.0.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 (129) hide show
  1. package/LICENSE +201 -201
  2. package/README.md +82 -573
  3. package/angular/README.md +10 -4
  4. package/angular/aeriz-editor.directive.ts +40 -18
  5. package/dist/adapters/jquery.cjs +1 -1
  6. package/dist/adapters/jquery.js +14 -14
  7. package/dist/adapters/shared.cjs +1 -1
  8. package/dist/adapters/shared.js +59 -56
  9. package/dist/adapters/vue.cjs +1 -1
  10. package/dist/adapters/vue.js +64 -56
  11. package/dist/adapters/vue2.cjs +1 -1
  12. package/dist/adapters/vue2.js +24 -17
  13. package/dist/adapters/wc.cjs +1 -1
  14. package/dist/adapters/wc.js +108 -72
  15. package/dist/aeriz-wysiwyg-adapter.js +24 -32
  16. package/dist/aeriz-wysiwyg-adapter.min.js +1 -1
  17. package/dist/aeriz-wysiwyg-highlight.js +964 -0
  18. package/dist/aeriz-wysiwyg-highlight.min.js +5 -0
  19. package/dist/aeriz-wysiwyg-jquery.js +25 -32
  20. package/dist/aeriz-wysiwyg-jquery.min.js +1 -1
  21. package/dist/aeriz-wysiwyg-wc.js +80 -43
  22. package/dist/aeriz-wysiwyg-wc.min.js +1 -1
  23. package/dist/aeriz-wysiwyg.cjs +3525 -1744
  24. package/dist/aeriz-wysiwyg.css +2104 -1904
  25. package/dist/aeriz-wysiwyg.esm.js +3525 -1744
  26. package/dist/aeriz-wysiwyg.esm.min.js +4840 -3581
  27. package/dist/aeriz-wysiwyg.js +3525 -1744
  28. package/dist/aeriz-wysiwyg.min.cjs +51 -46
  29. package/dist/aeriz-wysiwyg.min.css +1 -1
  30. package/dist/aeriz-wysiwyg.min.js +51 -46
  31. package/dist/highlight.cjs +5 -0
  32. package/dist/highlight.js +895 -0
  33. package/dist/server.cjs +24 -19
  34. package/dist/server.js +612 -251
  35. package/dist-types/adapters/shared.d.ts +0 -8
  36. package/dist-types/adapters/shared.d.ts.map +1 -1
  37. package/dist-types/adapters/vue.d.ts +62 -21
  38. package/dist-types/adapters/vue.d.ts.map +1 -1
  39. package/dist-types/adapters/vue2.d.ts +33 -14
  40. package/dist-types/adapters/vue2.d.ts.map +1 -1
  41. package/dist-types/adapters/wc.d.ts +15 -6
  42. package/dist-types/adapters/wc.d.ts.map +1 -1
  43. package/dist-types/cjs/adapters/shared.d.cts +0 -8
  44. package/dist-types/cjs/adapters/vue.d.cts +62 -21
  45. package/dist-types/cjs/adapters/vue2.d.cts +33 -14
  46. package/dist-types/cjs/adapters/wc.d.cts +15 -6
  47. package/dist-types/cjs/core/AerizEditor.d.cts +204 -48
  48. package/dist-types/cjs/core/AerizViewer.d.cts +7 -0
  49. package/dist-types/cjs/core/block-meta.d.cts +40 -0
  50. package/dist-types/cjs/core/block-tracker.d.cts +64 -0
  51. package/dist-types/cjs/core/blocks.d.cts +39 -4
  52. package/dist-types/cjs/core/fonts.d.cts +33 -46
  53. package/dist-types/cjs/core/history.d.cts +28 -10
  54. package/dist-types/cjs/core/html-to-blocks.d.cts +10 -0
  55. package/dist-types/cjs/core/i18n.d.cts +44 -0
  56. package/dist-types/cjs/core/import-html.d.cts +0 -13
  57. package/dist-types/cjs/core/insert-fragment.d.cts +21 -0
  58. package/dist-types/cjs/core/paste-clean.d.cts +36 -0
  59. package/dist-types/cjs/core/sanitize-dom.d.cts +21 -0
  60. package/dist-types/cjs/core/sanitize-html.d.cts +8 -0
  61. package/dist-types/cjs/core/sanitize-policy.d.cts +22 -0
  62. package/dist-types/cjs/core/selection.d.cts +5 -0
  63. package/dist-types/cjs/core/table-grid.d.cts +87 -0
  64. package/dist-types/cjs/core/table-ops.d.cts +2 -22
  65. package/dist-types/cjs/highlight/index.d.cts +5 -0
  66. package/dist-types/cjs/index.d.cts +19 -11
  67. package/dist-types/cjs/server/index.d.cts +4 -5
  68. package/dist-types/cjs/types/index.d.cts +63 -15
  69. package/dist-types/cjs/ui/Toolbar.d.cts +10 -0
  70. package/dist-types/cjs/ui/doc-tools.d.cts +58 -0
  71. package/dist-types/cjs/ui/popovers.d.cts +14 -25
  72. package/dist-types/core/AerizEditor.d.ts +204 -48
  73. package/dist-types/core/AerizEditor.d.ts.map +1 -1
  74. package/dist-types/core/AerizViewer.d.ts +7 -0
  75. package/dist-types/core/AerizViewer.d.ts.map +1 -1
  76. package/dist-types/core/block-meta.d.ts +40 -0
  77. package/dist-types/core/block-meta.d.ts.map +1 -1
  78. package/dist-types/core/block-policy.d.ts.map +1 -1
  79. package/dist-types/core/block-tracker.d.ts +64 -0
  80. package/dist-types/core/block-tracker.d.ts.map +1 -0
  81. package/dist-types/core/blocks.d.ts +39 -4
  82. package/dist-types/core/blocks.d.ts.map +1 -1
  83. package/dist-types/core/fonts.d.ts +33 -46
  84. package/dist-types/core/fonts.d.ts.map +1 -1
  85. package/dist-types/core/history.d.ts +28 -10
  86. package/dist-types/core/history.d.ts.map +1 -1
  87. package/dist-types/core/html-mini.d.ts.map +1 -1
  88. package/dist-types/core/html-to-blocks.d.ts +10 -0
  89. package/dist-types/core/html-to-blocks.d.ts.map +1 -0
  90. package/dist-types/core/i18n.d.ts +44 -0
  91. package/dist-types/core/i18n.d.ts.map +1 -1
  92. package/dist-types/core/import-html.d.ts +0 -13
  93. package/dist-types/core/import-html.d.ts.map +1 -1
  94. package/dist-types/core/import-markdown.d.ts.map +1 -1
  95. package/dist-types/core/insert-fragment.d.ts +21 -0
  96. package/dist-types/core/insert-fragment.d.ts.map +1 -0
  97. package/dist-types/core/markdown-export.d.ts.map +1 -1
  98. package/dist-types/core/paste-clean.d.ts +36 -0
  99. package/dist-types/core/paste-clean.d.ts.map +1 -0
  100. package/dist-types/core/render-string.d.ts.map +1 -1
  101. package/dist-types/core/sanitize-dom.d.ts +21 -0
  102. package/dist-types/core/sanitize-dom.d.ts.map +1 -0
  103. package/dist-types/core/sanitize-html.d.ts +8 -0
  104. package/dist-types/core/sanitize-html.d.ts.map +1 -0
  105. package/dist-types/core/sanitize-policy.d.ts +22 -0
  106. package/dist-types/core/sanitize-policy.d.ts.map +1 -0
  107. package/dist-types/core/selection.d.ts +5 -0
  108. package/dist-types/core/selection.d.ts.map +1 -1
  109. package/dist-types/core/snapshot.d.ts.map +1 -1
  110. package/dist-types/core/table-grid.d.ts +87 -0
  111. package/dist-types/core/table-grid.d.ts.map +1 -0
  112. package/dist-types/core/table-ops.d.ts +2 -22
  113. package/dist-types/core/table-ops.d.ts.map +1 -1
  114. package/dist-types/highlight/index.d.ts +5 -0
  115. package/dist-types/highlight/index.d.ts.map +1 -0
  116. package/dist-types/index.d.ts +19 -11
  117. package/dist-types/index.d.ts.map +1 -1
  118. package/dist-types/server/index.d.ts +4 -5
  119. package/dist-types/server/index.d.ts.map +1 -1
  120. package/dist-types/types/index.d.ts +63 -15
  121. package/dist-types/types/index.d.ts.map +1 -1
  122. package/dist-types/ui/Toolbar.d.ts +10 -0
  123. package/dist-types/ui/Toolbar.d.ts.map +1 -1
  124. package/dist-types/ui/doc-tools.d.ts +58 -0
  125. package/dist-types/ui/doc-tools.d.ts.map +1 -0
  126. package/dist-types/ui/icons.d.ts.map +1 -1
  127. package/dist-types/ui/popovers.d.ts +14 -25
  128. package/dist-types/ui/popovers.d.ts.map +1 -1
  129. package/package.json +70 -5
@@ -1,1904 +1,2104 @@
1
- .aeriz-wysiwyg {
2
- --aw-bg: #ffffff;
3
- --aw-fg: #1a1a1a;
4
- --aw-fg-muted: #6b7280;
5
- --aw-border: #e5e7eb;
6
- --aw-toolbar-bg: #f9fafb;
7
- --aw-toolbar-fg: #374151;
8
- --aw-btn-hover-bg: #e5e7eb;
9
- --aw-btn-active-bg: #dbeafe;
10
- --aw-btn-active-fg: #1d4ed8;
11
- --aw-primary: #03c75a;
12
- --aw-primary-fg: #ffffff;
13
- --aw-primary-hover: #02b150;
14
- --aw-divider: #e5e7eb;
15
- --aw-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
16
- --aw-shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
17
- --aw-shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
18
- --aw-radius: 6px;
19
- --aw-radius-sm: 4px;
20
- --aw-font: -apple-system, BlinkMacSystemFont, 'Apple SD Gothic Neo', 'Pretendard',
21
- 'Noto Sans KR', 'Malgun Gothic', sans-serif;
22
- --aw-font-mono: 'SF Mono', Menlo, Monaco, Consolas, 'D2Coding', monospace;
23
-
24
- /* ─── 모션 ──────────────────────────────────────────────────────────
25
- 블록 UI 의 전환은 전부 이 토큰들을 쓴다. 값이 한 군데 모여 있어야
26
- "누르면 즉각, 사라질 땐 느긋하게" 같은 규칙이 화면 전체에서 일관된다.
27
- · fast — 눌림/색 변화처럼 손끝에 바로 붙어야 하는 것
28
- · base — 나타남/사라짐
29
- · slow — 위치 이동처럼 눈으로 따라가는 것
30
- · ease-out : 등장(감속). ease-spring : 살짝 튕기며 자리잡는 등장. */
31
- --aw-dur-fast: 90ms;
32
- --aw-dur-base: 160ms;
33
- --aw-dur-slow: 240ms;
34
- --aw-ease-out: cubic-bezier(0.22, 0.85, 0.3, 1);
35
- --aw-ease-spring: cubic-bezier(0.2, 1.3, 0.4, 1);
36
-
37
- --aw-toolbar-height: 44px;
38
- --aw-z-toolbar: 10;
39
- --aw-z-popover: 100;
40
- --aw-z-dialog: 1000;
41
- }
42
-
43
- .aeriz-wysiwyg[data-theme='dark'] {
44
- --aw-bg: #0a0e14;
45
- --aw-fg: #e7eef5;
46
- --aw-fg-muted: #6a7a8a;
47
- --aw-border: rgba(100, 200, 255, 0.12);
48
- --aw-toolbar-bg: #0d1320;
49
- --aw-toolbar-fg: #cfd6dd;
50
- --aw-btn-hover-bg: rgba(100, 200, 255, 0.06);
51
- --aw-btn-active-bg: rgba(100, 200, 255, 0.14);
52
- --aw-btn-active-fg: #64c8ff;
53
- --aw-primary: #64c8ff;
54
- --aw-primary-fg: #050810;
55
- --aw-primary-hover: #8ad6ff;
56
- --aw-divider: rgba(100, 200, 255, 0.12);
57
- }
58
- .aeriz-wysiwyg {
59
- font-family: var(--aw-font);
60
- background-color: var(--aw-bg);
61
- color: var(--aw-fg);
62
- border: 1px solid var(--aw-border);
63
- border-radius: var(--aw-radius);
64
- overflow: hidden;
65
- display: flex;
66
- flex-direction: column;
67
- /* 부모가 명시적 높이를 주면 그 높이를 채우고 내부 컨텐츠가 스크롤되며,
68
- 부모가 auto 면 min-height 가 floor 로 작용한다. */
69
- height: 100%;
70
- min-height: 320px;
71
- box-shadow: var(--aw-shadow-sm);
72
- }
73
-
74
- .aeriz-wysiwyg *,
75
- .aeriz-wysiwyg *::before,
76
- .aeriz-wysiwyg *::after {
77
- box-sizing: border-box;
78
- }
79
-
80
- .aw-content-wrap {
81
- flex: 1;
82
- /* flex 자식이 기본 min-content 크기 이상으로 줄어들 수 있게 함 — 이게 없으면
83
- overflow-y:auto 가 부모를 밀어내 스크롤이 발생하지 않는다. */
84
- min-height: 0;
85
- overflow-y: auto;
86
- background-color: var(--aw-bg);
87
- position: relative; /* 드래그 핸들 / 드롭 인디케이터 절대 위치 기준 */
88
- }
89
-
90
- /* ─── 뷰어 모드 ─────────────────────────────────────────────────────
91
- AerizViewer 는 컨텐츠 길이만큼 자연스럽게 늘어난다 (스크롤 / 고정 높이 X).
92
- 에디터 기본 규칙을 viewer 클래스가 좁게 덮어쓴다. */
93
- .aeriz-wysiwyg-viewer {
94
- height: auto;
95
- min-height: 0;
96
- }
97
- .aeriz-wysiwyg-viewer .aw-content-wrap {
98
- flex: 0 0 auto;
99
- overflow: visible;
100
- min-height: 0;
101
- }
102
- .aeriz-wysiwyg-viewer .aw-content {
103
- min-height: 0;
104
- }
105
-
106
- /* ─── 드래그 핸들 오버레이 ───────────────────────────────────────────── */
107
-
108
- .aw-block-handles {
109
- position: absolute;
110
- top: 0;
111
- left: 0;
112
- width: 20px;
113
- height: 100%;
114
- pointer-events: none;
115
- z-index: 2;
116
- }
117
-
118
- /* 블록별 컨트롤 그룹 — 드래그 핸들 + 단락 추가 버튼을 세로로 묶는다.
119
- 지금 다루는 블록(포인터가 올라간 블록 / 캐럿이 있는 블록) 것만 드러난다.
120
- 본문 어디에나 마우스만 올리면 모든 블록의 핸들이 한꺼번에 떠오르던
121
- 예전 동작은, 글이 길어질수록 왼쪽에 점 기둥을 세워 놓는 꼴이었다. */
122
- .aw-block-controls {
123
- position: absolute;
124
- left: 3px;
125
- display: flex;
126
- flex-direction: column;
127
- gap: 0;
128
- pointer-events: none;
129
- opacity: 0;
130
- /* 살짝 왼쪽에서 밀려 들어온다 — 나타남에 방향이 생기면 어느 블록에
131
- 붙은 컨트롤인지가 눈으로 더 빨리 읽힌다. */
132
- transform: translateX(-3px);
133
- transition:
134
- opacity var(--aw-dur-base) var(--aw-ease-out),
135
- transform var(--aw-dur-base) var(--aw-ease-out);
136
- }
137
- /* 캐럿이 있는 블록 — 은은하게 상주. 포인터가 올라오면 완전히 또렷하게. */
138
- .aw-block-controls.is-active {
139
- opacity: 0.5;
140
- transform: none;
141
- }
142
- .aw-block-controls.is-hovered,
143
- .aw-block-controls:hover,
144
- .aw-block-controls:focus-within {
145
- opacity: 1;
146
- transform: none;
147
- }
148
-
149
- /* 높이는 AerizEditor.positionHandle 의 HANDLE_HEIGHT 와 짝을 이룬다 —
150
- 한쪽만 바꾸면 짧은 블록(hr) 위에서 핸들의 수직 정렬이 어긋난다. */
151
- .aw-block-handle,
152
- .aw-block-add {
153
- width: 14px;
154
- height: 18px;
155
- display: inline-flex;
156
- align-items: center;
157
- justify-content: center;
158
- background: transparent;
159
- border: 1px solid transparent;
160
- border-radius: 3px;
161
- color: var(--aw-fg-muted);
162
- pointer-events: auto;
163
- user-select: none;
164
- appearance: none;
165
- padding: 0;
166
- cursor: pointer;
167
- transition:
168
- color var(--aw-dur-fast) var(--aw-ease-out),
169
- background-color var(--aw-dur-fast) var(--aw-ease-out),
170
- border-color var(--aw-dur-fast) var(--aw-ease-out),
171
- transform var(--aw-dur-fast) var(--aw-ease-out);
172
- }
173
- .aw-block-handle { cursor: grab; }
174
-
175
- .aw-block-handle svg,
176
- .aw-block-add svg {
177
- width: 10px;
178
- height: 11px;
179
- }
180
-
181
- .aw-block-handle:hover,
182
- .aw-block-add:hover {
183
- color: var(--aw-btn-active-fg, var(--aw-primary));
184
- background-color: var(--aw-btn-hover-bg);
185
- border-color: var(--aw-border);
186
- }
187
- /* 눌린 순간 살짝 눌러앉는다 — 클릭이 먹었다는 신호가 색보다 빠르다. */
188
- .aw-block-handle:active,
189
- .aw-block-add:active {
190
- transform: scale(0.88);
191
- }
192
- /* 키보드 사용자에게도 같은 위치를 알려 준다. */
193
- .aw-block-handle:focus-visible,
194
- .aw-block-add:focus-visible {
195
- outline: 2px solid var(--aw-primary);
196
- outline-offset: 1px;
197
- color: var(--aw-primary);
198
- }
199
- /* 추가 버튼의 '+' 는 hover 시 1/4 바퀴 돌아 "여기서 새로 시작한다" 는
200
- 느낌을 준다. 회전은 8px 아이콘에서도 확실히 읽히는 몇 안 되는 변화다. */
201
- .aw-block-add svg {
202
- transition: transform var(--aw-dur-base) var(--aw-ease-spring);
203
- }
204
- .aw-block-add:hover svg {
205
- transform: rotate(90deg);
206
- }
207
-
208
- .aw-block-handle.is-dragging {
209
- cursor: grabbing;
210
- color: var(--aw-primary);
211
- background-color: var(--aw-btn-active-bg);
212
- border-color: var(--aw-primary);
213
- transform: scale(1.05);
214
- }
215
-
216
- /* 끌고 있는 블록 자신 — 자리만 남기고 물러난다. */
217
- .is-block-dragging {
218
- opacity: 0.35;
219
- filter: saturate(0.6);
220
- transition:
221
- opacity var(--aw-dur-base) var(--aw-ease-out),
222
- filter var(--aw-dur-base) var(--aw-ease-out);
223
- }
224
-
225
- /* ─── 드롭 위치 인디케이터 ─────────────────────────────────────────────
226
- 선 왼쪽 끝에 점을 하나 찍는다. 얇은 선만 있으면 어느 쪽 여백에 걸린
227
- 건지 애매한데, 점이 있으면 "여기서부터" 가 분명해진다. */
228
-
229
- .aw-block-drop-indicator {
230
- position: absolute;
231
- left: 28px;
232
- right: 28px;
233
- height: 2px;
234
- background: var(--aw-primary);
235
- border-radius: 1px;
236
- pointer-events: none;
237
- z-index: 3;
238
- transform: translateY(-1px);
239
- box-shadow: 0 0 6px color-mix(in srgb, var(--aw-primary) 70%, transparent);
240
- animation: aw-drop-in var(--aw-dur-base) var(--aw-ease-out);
241
- }
242
- .aw-block-drop-indicator::before {
243
- content: '';
244
- position: absolute;
245
- left: -3px;
246
- top: 50%;
247
- width: 7px;
248
- height: 7px;
249
- border-radius: 50%;
250
- background: var(--aw-primary);
251
- transform: translateY(-50%);
252
- }
253
- @keyframes aw-drop-in {
254
- from {
255
- opacity: 0;
256
- transform: translateY(-1px) scaleX(0.94);
257
- }
258
- to {
259
- opacity: 1;
260
- transform: translateY(-1px) scaleX(1);
261
- }
262
- }
263
-
264
- /* ─── Status Bar ─────────────────────────────────────────────────────
265
- 에디터 하단의 메타 정보 표시(활성 블록 / 블록 수 / 글자 수). 뷰어에는
266
- 불필요하므로 .aeriz-wysiwyg-viewer 안에서는 숨긴다. */
267
-
268
- .aw-status-bar {
269
- display: flex;
270
- align-items: center;
271
- justify-content: space-between;
272
- gap: 12px;
273
- padding: 5px 12px;
274
- border-top: 1px solid var(--aw-border);
275
- background-color: var(--aw-toolbar-bg);
276
- color: var(--aw-fg-muted);
277
- font-family: var(--aw-font-mono);
278
- font-size: 11px;
279
- letter-spacing: 0.02em;
280
- flex-shrink: 0;
281
- }
282
-
283
- .aw-status-left,
284
- .aw-status-right {
285
- display: inline-flex;
286
- align-items: center;
287
- gap: 12px;
288
- min-width: 0;
289
- }
290
-
291
- .aw-status-item {
292
- display: inline-flex;
293
- align-items: center;
294
- gap: 6px;
295
- white-space: nowrap;
296
- overflow: hidden;
297
- text-overflow: ellipsis;
298
- }
299
-
300
- .aw-status-item::before {
301
- content: '';
302
- width: 5px;
303
- height: 5px;
304
- border-radius: 50%;
305
- background: var(--aw-primary);
306
- opacity: 0.55;
307
- flex-shrink: 0;
308
- }
309
-
310
- /* status bar 안의 작은 아이콘 액션 버튼 (undo/redo 등) */
311
- .aw-status-action {
312
- appearance: none;
313
- cursor: pointer;
314
- background: transparent;
315
- border: 1px solid transparent;
316
- color: var(--aw-fg-muted);
317
- padding: 0;
318
- width: 22px;
319
- height: 22px;
320
- border-radius: 3px;
321
- display: inline-flex;
322
- align-items: center;
323
- justify-content: center;
324
- flex-shrink: 0;
325
- transition: color 0.12s, background-color 0.12s, border-color 0.12s;
326
- }
327
- .aw-status-action svg {
328
- width: 13px;
329
- height: 13px;
330
- }
331
- .aw-status-action:hover {
332
- color: var(--aw-btn-active-fg, var(--aw-primary));
333
- background-color: var(--aw-btn-hover-bg);
334
- border-color: var(--aw-border);
335
- }
336
- .aw-status-action:active {
337
- background-color: var(--aw-btn-active-bg);
338
- }
339
-
340
- .aeriz-wysiwyg-viewer .aw-status-bar {
341
- display: none;
342
- }
343
-
344
- /* ─── 블록 포커스 인디케이터 + 라벨 태그 ─────────────────────────────── */
345
-
346
- /* 활성 블록 표시는 **레일(왼쪽 세로선) + 옅은 바탕** 두 겹이다.
347
- 레일은 색이 아니라 길이로 자란다 — `box-shadow` 를 켜고 끄면 선이 툭
348
- 나타나는데, 세로로 자라 올라오면 "이 블록이 지금 잡혔다" 가 동작으로
349
- 읽힌다. 레일은 별도 pseudo 로 그려서 블록 자신의 배경(코드 블록 ·
350
- 인용구) 과 겹치지 않는다. 어떤 태그에도 레이아웃 영향을 주지 않도록
351
- position:absolute 만 쓰고 블록의 박스 크기는 건드리지 않는다. */
352
- .aw-content > [data-block-id] {
353
- position: relative;
354
- transition: background-color var(--aw-dur-base) var(--aw-ease-out);
355
- }
356
-
357
- .aw-content > [data-block-id]::before {
358
- content: '';
359
- position: absolute;
360
- left: -10px;
361
- top: 0;
362
- width: 3px;
363
- height: 100%;
364
- border-radius: 2px;
365
- background: var(--aw-primary);
366
- opacity: 0;
367
- transform: scaleY(0.25);
368
- transform-origin: center;
369
- pointer-events: none;
370
- transition:
371
- opacity var(--aw-dur-base) var(--aw-ease-out),
372
- transform var(--aw-dur-base) var(--aw-ease-spring);
373
- }
374
-
375
- .aw-content > [data-block-focused] {
376
- background-color: color-mix(in srgb, var(--aw-primary) 7%, transparent);
377
- border-radius: 3px;
378
- }
379
-
380
- .aw-content > [data-block-focused]::before {
381
- opacity: 1;
382
- transform: scaleY(1);
383
- }
384
-
385
- /* 포인터가 올라간 블록 — 아직 잡은 건 아니므로 레일 없이 바탕만 살짝.
386
- 활성 블록의 강조를 덮지 않도록 :not() 으로 비켜 준다. */
387
- .aw-content > [data-block-id]:hover:not([data-block-focused]) {
388
- background-color: color-mix(in srgb, var(--aw-fg) 3%, transparent);
389
- }
390
-
391
- /* 표는 pseudo 요소가 익명 테이블 박스로 감싸여 위치가 어긋난다. 레일 대신
392
- 테두리 강조로 같은 뜻을 전한다. */
393
- .aw-content > table::before {
394
- display: none;
395
- }
396
- .aw-content > table[data-block-focused] {
397
- outline: 1px solid color-mix(in srgb, var(--aw-primary) 55%, transparent);
398
- outline-offset: 3px;
399
- }
400
-
401
- /* 유형이 막 바뀐 블록 — 한 번만 반짝. AerizEditor 가 애니메이션이 끝나면
402
- 속성을 지운다. */
403
- .aw-content > [data-block-flash] {
404
- animation: aw-block-flash 520ms var(--aw-ease-out);
405
- }
406
- @keyframes aw-block-flash {
407
- 0% {
408
- background-color: color-mix(in srgb, var(--aw-primary) 26%, transparent);
409
- }
410
- 100% {
411
- background-color: transparent;
412
- }
413
- }
414
-
415
- /* 캐럿이 놓인 빈 본문 블록 — 무엇을 하면 되는지 한 줄로 알려 준다.
416
- 문서 전체가 비었을 때 뜨는 에디터 placeholder 와 겹치지 않도록, 블록이
417
- 둘 이상일 때만(`:has(> :nth-child(2))`) 나타난다. */
418
- .aw-content:has(> :nth-child(2))
419
- > p[data-block-focused]:has(> br:only-child)::after {
420
- content: var(--aw-empty-hint, '');
421
- position: absolute;
422
- left: 0;
423
- top: 0;
424
- color: var(--aw-fg-muted);
425
- opacity: 0;
426
- pointer-events: none;
427
- user-select: none;
428
- font-size: 0.95em;
429
- animation: aw-hint-in var(--aw-dur-slow) var(--aw-ease-out) 120ms forwards;
430
- }
431
- @keyframes aw-hint-in {
432
- from {
433
- opacity: 0;
434
- transform: translateX(-2px);
435
- }
436
- to {
437
- opacity: 0.45;
438
- transform: none;
439
- }
440
- }
441
-
442
- /* 구분선은 높이가 사실상 0 이라 레일이 안 보인다 — 최소 길이를 주고
443
- 세로 가운데에 건다 (핸들의 HANDLE_HEIGHT 정렬과 같은 규칙). */
444
- .aw-content > hr::before {
445
- height: 14px;
446
- top: 50%;
447
- margin-top: -7px;
448
- }
449
-
450
- /* 읽기 전용 뷰어에는 편집 상태 표시가 의미 없다. */
451
- .aeriz-wysiwyg-viewer .aw-content > [data-block-id]::before {
452
- display: none;
453
- }
454
- .aeriz-wysiwyg-viewer .aw-content > [data-block-id]:hover {
455
- background-color: transparent;
456
- }
457
-
458
- /* ─── 인터랙티브 블록 라벨 pill ───────────────────────────────────────
459
- 활성 블록 위에 떠 있는 작은 버튼. 정적 ::before pseudo 라벨을 대체하며
460
- 클릭 시 블록 유형 드롭다운을 열어 마이크로 인터랙션으로 유형 변경. */
461
- .aw-block-label-pill {
462
- position: absolute;
463
- z-index: 4;
464
- appearance: none;
465
- font-family: var(--aw-font-mono);
466
- font-size: 9px;
467
- font-weight: 700;
468
- letter-spacing: 0.08em;
469
- text-transform: uppercase;
470
- color: var(--aw-primary);
471
- background: var(--aw-bg);
472
- padding: 2px 5px 2px 4px;
473
- border: 1px solid color-mix(in srgb, var(--aw-primary) 45%, transparent);
474
- border-radius: 4px;
475
- cursor: pointer;
476
- user-select: none;
477
- display: inline-flex;
478
- align-items: center;
479
- gap: 4px;
480
- line-height: 1;
481
- white-space: nowrap;
482
- transition:
483
- transform var(--aw-dur-fast) var(--aw-ease-out),
484
- background-color var(--aw-dur-fast) var(--aw-ease-out),
485
- color var(--aw-dur-fast) var(--aw-ease-out),
486
- border-color var(--aw-dur-fast) var(--aw-ease-out),
487
- box-shadow var(--aw-dur-base) var(--aw-ease-out);
488
- }
489
-
490
- /* 유형 기호 — 라벨을 읽기 전에 눈이 먼저 잡는 부분이라 한 칸 더 진하게. */
491
- .aw-block-label-pill .aw-pill-glyph {
492
- display: inline-flex;
493
- align-items: center;
494
- justify-content: center;
495
- min-width: 13px;
496
- height: 13px;
497
- padding: 0 2px;
498
- border-radius: 3px;
499
- background: color-mix(in srgb, var(--aw-primary) 16%, transparent);
500
- font-size: 9px;
501
- letter-spacing: 0;
502
- transition: background-color var(--aw-dur-fast) var(--aw-ease-out);
503
- }
504
-
505
- .aw-block-label-pill::after {
506
- content: '▾';
507
- font-size: 9px;
508
- opacity: 0.55;
509
- transition:
510
- opacity var(--aw-dur-fast) var(--aw-ease-out),
511
- transform var(--aw-dur-base) var(--aw-ease-out);
512
- }
513
- .aw-block-label-pill:hover {
514
- background: var(--aw-primary);
515
- color: var(--aw-primary-fg);
516
- border-color: var(--aw-primary);
517
- transform: translateY(-1px);
518
- box-shadow: 0 3px 10px color-mix(in srgb, var(--aw-primary) 32%, transparent);
519
- }
520
- .aw-block-label-pill:hover .aw-pill-glyph {
521
- background: color-mix(in srgb, var(--aw-primary-fg) 22%, transparent);
522
- }
523
- .aw-block-label-pill:hover::after {
524
- opacity: 1;
525
- transform: translateY(1px);
526
- }
527
- .aw-block-label-pill:active {
528
- transform: translateY(0) scale(0.97);
529
- box-shadow: none;
530
- }
531
- .aw-block-label-pill:focus-visible {
532
- outline: 2px solid var(--aw-primary);
533
- outline-offset: 2px;
534
- }
535
-
536
- /* 다른 블록으로 캐럿이 옮겨갈 때만 재생된다 (AerizEditor 가 클래스를 다시
537
- 붙인다). 같은 블록에서 타이핑하는 동안에는 미동도 없어야 한다. */
538
- .aw-block-label-pill.is-entering {
539
- animation: aw-pill-in var(--aw-dur-slow) var(--aw-ease-spring);
540
- }
541
- /* pill 에서 연 블록 유형 메뉴가 떠 있는 동안 */
542
- .aw-block-label-pill.is-popover-open {
543
- background: var(--aw-primary);
544
- color: var(--aw-primary-fg);
545
- border-color: var(--aw-primary);
546
- }
547
- .aw-block-label-pill.is-popover-open::after {
548
- transform: rotate(180deg);
549
- opacity: 1;
550
- }
551
- @keyframes aw-pill-in {
552
- from {
553
- opacity: 0;
554
- transform: translateY(3px) scale(0.92);
555
- }
556
- to {
557
- opacity: 1;
558
- transform: translateY(0) scale(1);
559
- }
560
- }
561
-
562
- .aw-content {
563
- outline: none;
564
- /* 위쪽 여백은 라벨 pill(약 15px) + 여유가 들어갈 만큼 둔다 — 문서 첫
565
- 블록에서도 pill 이 글자를 덮지 않아야 한다. */
566
- padding: 28px 28px 24px;
567
- min-height: 240px;
568
- font-size: 15px;
569
- line-height: 1.7;
570
- color: var(--aw-fg);
571
- word-break: break-word;
572
- }
573
-
574
- .aw-content:empty::before,
575
- .aw-content[data-placeholder]:has(p:only-child > br:only-child)::before {
576
- content: attr(data-placeholder);
577
- color: var(--aw-fg-muted);
578
- pointer-events: none;
579
- position: absolute;
580
- }
581
-
582
- .aw-content > p {
583
- margin: 0 0 0.8em 0;
584
- }
585
- .aw-content > p:last-child {
586
- margin-bottom: 0;
587
- }
588
-
589
- /* 정렬 클래스 — getHTML({ styles: 'class' }) 출력이 style="text-align:…" 대신
590
- 사용한다. style 속성을 제거하는 서버 sanitizer 를 통과해도 정렬이 남는다.
591
- 에디터 자체는 계속 인라인 style 로 정렬하므로 편집 중 동작에는 영향이 없다. */
592
- .aw-content .aw-align-left {
593
- text-align: left;
594
- }
595
- .aw-content .aw-align-center {
596
- text-align: center;
597
- }
598
- .aw-content .aw-align-right {
599
- text-align: right;
600
- }
601
- .aw-content .aw-align-justify {
602
- text-align: justify;
603
- }
604
-
605
- .aw-content h1,
606
- .aw-content h2,
607
- .aw-content h3 {
608
- font-weight: 700;
609
- margin: 0.6em 0 0.4em;
610
- padding-bottom: 0.25em;
611
- border-bottom: 1px solid var(--aw-border);
612
- }
613
- .aw-content h1 { font-size: 1.875rem; line-height: 1.3; }
614
- .aw-content h2 { font-size: 1.5rem; line-height: 1.35; }
615
- .aw-content h3 { font-size: 1.25rem; line-height: 1.4; }
616
-
617
- .aw-content blockquote {
618
- margin: 1em 0;
619
- padding: 0.5em 1em;
620
- border-left: 4px solid var(--aw-primary);
621
- background-color: color-mix(in srgb, var(--aw-primary) 8%, transparent);
622
- color: var(--aw-fg);
623
- font-style: italic;
624
- }
625
-
626
- .aw-content pre {
627
- position: relative;
628
- background-color: color-mix(in srgb, var(--aw-fg) 6%, var(--aw-bg));
629
- border: 1px solid var(--aw-border);
630
- border-radius: var(--aw-radius-sm);
631
- padding: 12px 16px;
632
- font-family: var(--aw-font-mono);
633
- font-size: 13px;
634
- line-height: 1.6;
635
- overflow-x: auto;
636
- margin: 1em 0;
637
- }
638
-
639
- /* 코드 블록 우상단의 언어 라벨 — `<code data-language="javascript">` 의
640
- * data-language 가 있을 때만 표시. attr() 로 자동 동기화돼 별도 JS 갱신
641
- * 불필요. 사용자가 드롭다운으로 언어 변경 시 CSS 가 즉시 반영. */
642
- .aw-content > pre > code[data-language]:not([data-language=""])::before {
643
- content: attr(data-language);
644
- position: absolute;
645
- top: 6px;
646
- right: 10px;
647
- font-size: 10px;
648
- font-weight: 600;
649
- letter-spacing: 0.06em;
650
- text-transform: uppercase;
651
- color: var(--aw-fg-muted);
652
- background: color-mix(in srgb, var(--aw-fg) 10%, transparent);
653
- padding: 2px 8px;
654
- border-radius: 4px;
655
- pointer-events: none;
656
- user-select: none;
657
- }
658
-
659
- .aw-content code {
660
- font-family: var(--aw-font-mono);
661
- font-size: 0.9em;
662
- background-color: color-mix(in srgb, var(--aw-fg) 8%, transparent);
663
- padding: 0.15em 0.4em;
664
- border-radius: 3px;
665
- }
666
- .aw-content pre code {
667
- background: transparent;
668
- padding: 0;
669
- }
670
-
671
- /* ─── Syntax highlight 토큰 색 (자체 highlight.ts 출력) ──────────── */
672
- .aw-content pre code .hl-keyword { color: #c678dd; font-weight: 600; }
673
- .aw-content pre code .hl-string { color: #98c379; }
674
- .aw-content pre code .hl-number { color: #d19a66; }
675
- .aw-content pre code .hl-comment { color: var(--aw-fg-muted); font-style: italic; }
676
- .aw-content pre code .hl-fn { color: #61afef; }
677
- .aw-content pre code .hl-builtin { color: #56b6c2; }
678
- .aw-content pre code .hl-tag { color: #e06c75; }
679
- .aw-content pre code .hl-attr { color: #d19a66; }
680
- .aw-content pre code .hl-prop { color: #61afef; }
681
- .aw-content pre code .hl-punct { color: var(--aw-fg-muted); }
682
-
683
- /* light 테마 보정 */
684
- .aeriz-wysiwyg[data-theme='light'] .aw-content pre code .hl-keyword { color: #a626a4; }
685
- .aeriz-wysiwyg[data-theme='light'] .aw-content pre code .hl-string { color: #50a14f; }
686
- .aeriz-wysiwyg[data-theme='light'] .aw-content pre code .hl-number { color: #986801; }
687
- .aeriz-wysiwyg[data-theme='light'] .aw-content pre code .hl-fn { color: #4078f2; }
688
- .aeriz-wysiwyg[data-theme='light'] .aw-content pre code .hl-builtin { color: #0184bc; }
689
- .aeriz-wysiwyg[data-theme='light'] .aw-content pre code .hl-tag { color: #e45649; }
690
- .aeriz-wysiwyg[data-theme='light'] .aw-content pre code .hl-prop { color: #4078f2; }
691
-
692
- .aw-content ul,
693
- .aw-content ol {
694
- margin: 0.6em 0;
695
- padding-left: 1.6em;
696
- }
697
-
698
- .aw-content li {
699
- margin: 0.2em 0;
700
- }
701
-
702
- /* ─── 체크리스트 ─────────────────────────────────────────────────────
703
- 체크박스는 `<input>` 이 아니라 `<li>` 의 ::before 다. contentEditable 안의
704
- 폼 요소는 캐럿이 들어가고 서버 sanitizer 가 지우는데, ::before 는 둘 다
705
- 해당이 없다. 완료 여부는 `data-checked` 한 글자로 남는다. */
706
- .aw-content ul.aw-task-list {
707
- list-style: none;
708
- padding-left: 1.9em;
709
- }
710
- .aw-content ul.aw-task-list > li {
711
- position: relative;
712
- }
713
- .aw-content ul.aw-task-list > li::before {
714
- content: '';
715
- position: absolute;
716
- left: -1.5em;
717
- top: 0.28em;
718
- width: 15px;
719
- height: 15px;
720
- border: 1.5px solid var(--aw-fg-muted);
721
- border-radius: 4px;
722
- background: transparent;
723
- cursor: pointer;
724
- box-sizing: border-box;
725
- transition:
726
- background-color var(--aw-dur-fast) var(--aw-ease-out),
727
- border-color var(--aw-dur-fast) var(--aw-ease-out),
728
- transform var(--aw-dur-base) var(--aw-ease-spring);
729
- }
730
- .aw-content ul.aw-task-list > li:hover::before {
731
- border-color: var(--aw-primary);
732
- transform: scale(1.1);
733
- }
734
- /* 체크 표시는 두 번째 pseudo 로 그린다 — 네모와 따로 움직여야 "찍히는"
735
- 느낌이 난다. 회전한 ㄴ 자 두 획이 체크 모양이다. */
736
- .aw-content ul.aw-task-list > li::after {
737
- content: '';
738
- position: absolute;
739
- left: -1.16em;
740
- top: 0.42em;
741
- width: 4px;
742
- height: 8px;
743
- border: solid var(--aw-primary-fg);
744
- border-width: 0 2px 2px 0;
745
- transform: rotate(45deg) scale(0.4);
746
- opacity: 0;
747
- pointer-events: none;
748
- transition:
749
- opacity var(--aw-dur-fast) var(--aw-ease-out),
750
- transform var(--aw-dur-base) var(--aw-ease-spring);
751
- }
752
- .aw-content ul.aw-task-list > li[data-checked='true']::before {
753
- background: var(--aw-primary);
754
- border-color: var(--aw-primary);
755
- }
756
- .aw-content ul.aw-task-list > li[data-checked='true']::after {
757
- opacity: 1;
758
- transform: rotate(45deg) scale(1);
759
- }
760
- /* 완료한 항목은 한 걸음 물러난다. 지우지는 않는다 — 무엇을 끝냈는지도
761
- 목록의 일부다. */
762
- .aw-content ul.aw-task-list > li[data-checked='true'] {
763
- color: var(--aw-fg-muted);
764
- text-decoration: line-through;
765
- text-decoration-color: color-mix(in srgb, var(--aw-fg-muted) 60%, transparent);
766
- }
767
-
768
- .aw-content a {
769
- color: var(--aw-primary);
770
- text-decoration: underline;
771
- }
772
-
773
- .aw-content img {
774
- max-width: 100%;
775
- height: auto;
776
- border-radius: var(--aw-radius-sm);
777
- margin: 0.5em 0;
778
- }
779
-
780
- .aw-content hr {
781
- border: none;
782
- border-top: 1px solid var(--aw-border);
783
- margin: 1.5em 0;
784
- }
785
-
786
- /* ─── 유튜브 임베드 (16:9 반응형 컨테이너) ──────────────────── */
787
- .aw-content > div.aw-youtube-embed {
788
- position: relative;
789
- width: 100%;
790
- aspect-ratio: 16 / 9;
791
- margin: 1em 0;
792
- border-radius: 8px;
793
- /* overflow: hidden; */
794
- background: #000;
795
- border: 1px solid var(--aw-border);
796
- }
797
- .aw-content > div.aw-youtube-embed iframe {
798
- position: absolute;
799
- inset: 0;
800
- width: 100%;
801
- height: 100%;
802
- border: 0;
803
- display: block;
804
- }
805
-
806
- /* ─── 파일 첨부 (다운로드 카드) ──────────────────── */
807
- .aw-content > a.aw-file-attachment {
808
- display: flex;
809
- align-items: center;
810
- gap: 12px;
811
- margin: 1em 0;
812
- padding: 10px 14px;
813
- border: 1px solid var(--aw-border);
814
- border-radius: 8px;
815
- background: var(--aw-bg);
816
- color: var(--aw-fg);
817
- text-decoration: none;
818
- transition: background-color 0.08s, border-color 0.08s;
819
- cursor: pointer;
820
- user-select: none;
821
- }
822
- .aw-content > a.aw-file-attachment:hover {
823
- background: color-mix(in srgb, var(--aw-fg) 5%, var(--aw-bg));
824
- border-color: color-mix(in srgb, var(--aw-primary) 40%, var(--aw-border));
825
- }
826
- .aw-content > a.aw-file-attachment .aw-file-icon {
827
- flex: 0 0 auto;
828
- display: inline-flex;
829
- width: 36px;
830
- height: 36px;
831
- align-items: center;
832
- justify-content: center;
833
- border-radius: 6px;
834
- background: color-mix(in srgb, var(--aw-primary) 12%, transparent);
835
- color: var(--aw-primary);
836
- }
837
- .aw-content > a.aw-file-attachment .aw-file-icon svg {
838
- width: 20px;
839
- height: 20px;
840
- }
841
- .aw-content > a.aw-file-attachment .aw-file-meta {
842
- flex: 1 1 auto;
843
- min-width: 0;
844
- display: flex;
845
- flex-direction: column;
846
- gap: 2px;
847
- }
848
- .aw-content > a.aw-file-attachment .aw-file-name {
849
- font-weight: 600;
850
- font-size: 0.95rem;
851
- color: var(--aw-fg);
852
- word-break: break-all;
853
- line-height: 1.3;
854
- }
855
- .aw-content > a.aw-file-attachment .aw-file-info {
856
- font-size: 0.75rem;
857
- color: var(--aw-fg-muted);
858
- font-family: var(--aw-mono, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);
859
- letter-spacing: 0.02em;
860
- }
861
- .aw-content > a.aw-file-attachment .aw-file-action {
862
- flex: 0 0 auto;
863
- display: inline-flex;
864
- width: 32px;
865
- height: 32px;
866
- align-items: center;
867
- justify-content: center;
868
- color: var(--aw-fg-muted);
869
- }
870
- .aw-content > a.aw-file-attachment .aw-file-action svg {
871
- width: 18px;
872
- height: 18px;
873
- }
874
- .aw-content > a.aw-file-attachment:hover .aw-file-action {
875
- color: var(--aw-primary);
876
- }
877
-
878
- /* ─── 링크 카드 (Open Graph 스타일 미리보기) ──────────────────── */
879
- .aw-content > a.aw-link-card {
880
- display: flex;
881
- align-items: stretch;
882
- gap: 14px;
883
- text-decoration: none;
884
- margin: 1em 0;
885
- padding: 0;
886
- border: 1px solid var(--aw-border);
887
- border-radius: 8px;
888
- background-color: color-mix(in srgb, var(--aw-primary) 5%, var(--aw-bg));
889
- color: var(--aw-fg);
890
- overflow: hidden;
891
- transition: border-color 0.12s, background-color 0.12s;
892
- }
893
- .aw-content > a.aw-link-card:hover {
894
- border-color: var(--aw-primary);
895
- background-color: color-mix(in srgb, var(--aw-primary) 9%, var(--aw-bg));
896
- }
897
-
898
- /* 좌측 og:image 썸네일 — 있을 때만. */
899
- .aw-content > a.aw-link-card > .aw-link-card-image {
900
- flex: 0 0 auto;
901
- width: 120px;
902
- height: auto;
903
- object-fit: cover;
904
- align-self: stretch;
905
- background: color-mix(in srgb, var(--aw-fg) 5%, var(--aw-bg));
906
- }
907
-
908
- /* 우측 본문 — title / desc / url 행. */
909
- .aw-content > a.aw-link-card > .aw-link-card-body {
910
- flex: 1 1 auto;
911
- min-width: 0;
912
- display: flex;
913
- flex-direction: column;
914
- gap: 4px;
915
- padding: 12px 14px;
916
- }
917
- .aw-content > a.aw-link-card .aw-link-card-title {
918
- font-weight: 600;
919
- font-size: 1rem;
920
- line-height: 1.3;
921
- color: var(--aw-fg);
922
- display: -webkit-box;
923
- -webkit-line-clamp: 2;
924
- -webkit-box-orient: vertical;
925
- overflow: hidden;
926
- }
927
- .aw-content > a.aw-link-card .aw-link-card-desc {
928
- font-size: 0.85rem;
929
- line-height: 1.4;
930
- color: var(--aw-fg-muted);
931
- display: -webkit-box;
932
- -webkit-line-clamp: 2;
933
- -webkit-box-orient: vertical;
934
- overflow: hidden;
935
- }
936
- .aw-content > a.aw-link-card .aw-link-card-url {
937
- display: flex;
938
- align-items: center;
939
- gap: 6px;
940
- margin-top: 2px;
941
- font-size: 0.78rem;
942
- color: var(--aw-primary);
943
- font-family: var(--aw-font-mono);
944
- word-break: break-all;
945
- }
946
- .aw-content > a.aw-link-card .aw-link-card-favicon {
947
- flex: 0 0 auto;
948
- width: 14px;
949
- height: 14px;
950
- border-radius: 2px;
951
- background: transparent;
952
- }
953
- .aw-content > a.aw-link-card .aw-link-card-url-text {
954
- flex: 1 1 auto;
955
- min-width: 0;
956
- overflow: hidden;
957
- text-overflow: ellipsis;
958
- white-space: nowrap;
959
- }
960
-
961
- .aw-content table {
962
- border-collapse: collapse;
963
- width: 100%;
964
- margin: 1em 0;
965
- }
966
- .aw-content table td,
967
- .aw-content table th {
968
- border: 1px solid var(--aw-border);
969
- padding: 8px 12px;
970
- vertical-align: top;
971
- min-width: 40px;
972
- /* td/th 의 inline background-color 가 overflow 자식에 가려지지 않도록 */
973
- background-clip: padding-box;
974
- }
975
- .aw-content table th {
976
- background-color: color-mix(in srgb, var(--aw-fg) 5%, var(--aw-bg));
977
- font-weight: 600;
978
- }
979
-
980
- .aw-content table td,
981
- .aw-content table th {
982
- transition: background-color var(--aw-dur-fast) var(--aw-ease-out);
983
- }
984
-
985
- /* 커스텀 드래그-셀렉션으로 마킹된 표 셀 — 다중 선택 시각 강조. */
986
- .aw-content table td.aw-cell-selected,
987
- .aw-content table th.aw-cell-selected {
988
- outline: 2px solid var(--aw-primary);
989
- outline-offset: -2px;
990
- background-color: color-mix(in srgb, var(--aw-primary) 18%, transparent) !important;
991
- }
992
- .aeriz-wysiwyg-viewer .aw-cell-selected {
993
- outline: none;
994
- background-color: revert !important;
995
- }
996
-
997
- /* ─── 리사이즈 핸들 + 정렬 컨트롤 (이미지·유튜브) ──────────────────── */
998
- .aw-content > figure,
999
- .aw-content > div.aw-youtube-embed {
1000
- position: relative;
1001
- /* 인라인 width 가 본문보다 커도 넘치지 않게 막는다. 드래그 리사이즈는
1002
- JS 에서 본문 폭으로 상한을 두지만, 외부에서 들어온 값(HTML 가져오기,
1003
- 예전에 더 넓은 화면에서 저장된 스냅샷)까지 막아 주는 건 이 규칙이다.
1004
- 이게 없으면 본문이 가로로 넓어지면서 에디터 전체가 늘어난다. */
1005
- max-width: 100%;
1006
- }
1007
-
1008
- /* figure 의 좌우 여백을 0 으로 명시한다.
1009
- 브라우저 UA 기본값은 `figure { margin: 1em 40px }` 인데, getHTML() 이
1010
- 베이크하는 portable 스타일은 `margin: 1em 0` 이다. 그대로 두면 에디터에서는
1011
- 80px 좁게 보이고 내보낸 HTML 에서는 넓게 보이는 WYSIWYG 불일치가 생긴다. */
1012
- .aw-content > figure {
1013
- margin: 1em 0;
1014
- }
1015
-
1016
- /* ─── 이미지 캡션 ────────────────────────────────────────────────────
1017
- 그림에 딸린 설명. 본문보다 한 단계 작고 흐리게 — 그림을 읽고 나서 눈이
1018
- 내려오는 자리다. 그림 정렬(가운데/오른쪽) 을 따라가도록 figure 의
1019
- text-align 을 그대로 상속받는다. */
1020
- .aw-content > figure > figcaption {
1021
- margin-top: 0.5em;
1022
- font-size: 0.85em;
1023
- line-height: 1.5;
1024
- color: var(--aw-fg-muted);
1025
- outline: none;
1026
- }
1027
- /* 비어 있는 동안만 안내를 띄운다. 캡션은 캐럿이 떠나면 사라지므로, 이
1028
- 안내가 보이는 시간은 "지금 쓰면 된다" 는 순간뿐이다. */
1029
- .aw-content > figure > figcaption:empty::before {
1030
- content: var(--aw-caption-hint, '');
1031
- color: var(--aw-fg-muted);
1032
- opacity: 0.5;
1033
- pointer-events: none;
1034
- }
1035
- .aeriz-wysiwyg-viewer .aw-content > figure > figcaption:empty {
1036
- display: none;
1037
- }
1038
-
1039
- /* 좌상단 정렬 컨트롤 — 좌/중/우 3 버튼. hover 시 위에서 내려오며 fade-in. */
1040
- .aw-block-align-controls {
1041
- position: absolute;
1042
- top: 6px;
1043
- left: 6px;
1044
- display: flex;
1045
- gap: 1px;
1046
- background: rgba(0, 0, 0, 0.55);
1047
- backdrop-filter: blur(6px);
1048
- border-radius: 6px;
1049
- padding: 2px;
1050
- z-index: 11;
1051
- opacity: 0;
1052
- transform: translateY(-4px);
1053
- transition:
1054
- opacity var(--aw-dur-base) var(--aw-ease-out),
1055
- transform var(--aw-dur-base) var(--aw-ease-out);
1056
- user-select: none;
1057
- }
1058
- .aw-content > figure:hover .aw-block-align-controls,
1059
- .aw-content > div.aw-youtube-embed:hover .aw-block-align-controls,
1060
- .aw-content > figure:focus-within .aw-block-align-controls,
1061
- .aw-resizing .aw-block-align-controls {
1062
- opacity: 1;
1063
- transform: none;
1064
- }
1065
- /* 정렬 버튼과 캡션 버튼 사이 구분선 — 하는 일이 다르다는 표시. */
1066
- .aw-block-ctl-divider {
1067
- width: 1px;
1068
- align-self: stretch;
1069
- margin: 2px 2px;
1070
- background: rgba(255, 255, 255, 0.25);
1071
- }
1072
-
1073
- .aw-block-align-btn,
1074
- .aw-block-caption-btn {
1075
- width: 22px;
1076
- height: 22px;
1077
- display: inline-flex;
1078
- align-items: center;
1079
- justify-content: center;
1080
- background: transparent;
1081
- border: none;
1082
- color: #fff;
1083
- cursor: pointer;
1084
- border-radius: 4px;
1085
- padding: 0;
1086
- transition:
1087
- background-color var(--aw-dur-fast) var(--aw-ease-out),
1088
- transform var(--aw-dur-fast) var(--aw-ease-out);
1089
- }
1090
- .aw-block-align-btn:hover,
1091
- .aw-block-caption-btn:hover {
1092
- background: rgba(255, 255, 255, 0.18);
1093
- }
1094
- .aw-block-align-btn:active,
1095
- .aw-block-caption-btn:active {
1096
- transform: scale(0.9);
1097
- }
1098
- .aw-block-align-btn:focus-visible,
1099
- .aw-block-caption-btn:focus-visible {
1100
- outline: 2px solid #fff;
1101
- outline-offset: -2px;
1102
- }
1103
- .aw-block-align-btn svg,
1104
- .aw-block-caption-btn svg {
1105
- width: 14px;
1106
- height: 14px;
1107
- }
1108
- .aw-resize-handle {
1109
- position: absolute;
1110
- right: -1px;
1111
- bottom: -1px;
1112
- width: 14px;
1113
- height: 14px;
1114
- background: var(--aw-primary);
1115
- border: 2px solid var(--aw-bg);
1116
- border-radius: 4px;
1117
- cursor: nwse-resize;
1118
- z-index: 10;
1119
- user-select: none;
1120
- opacity: 0;
1121
- transform: scale(0.7);
1122
- transition:
1123
- opacity var(--aw-dur-base) var(--aw-ease-out),
1124
- transform var(--aw-dur-base) var(--aw-ease-spring),
1125
- box-shadow var(--aw-dur-base) var(--aw-ease-out);
1126
- }
1127
- /* 실제로 잡히는 면적은 보이는 것보다 넓다 — 14px 짜리 점을 정확히 겨냥해야
1128
- 하면 리사이즈가 스트레스가 된다. */
1129
- .aw-resize-handle::before {
1130
- content: '';
1131
- position: absolute;
1132
- inset: -7px;
1133
- }
1134
- .aw-content > figure:hover .aw-resize-handle,
1135
- .aw-content > div.aw-youtube-embed:hover .aw-resize-handle,
1136
- .aw-resizing .aw-resize-handle {
1137
- opacity: 1;
1138
- transform: none;
1139
- }
1140
- .aw-resize-handle:hover {
1141
- transform: scale(1.18);
1142
- box-shadow: 0 0 0 4px color-mix(in srgb, var(--aw-primary) 22%, transparent);
1143
- }
1144
- /* 크기를 조절하는 동안은 그 블록만 또렷하게 남기고 나머지 UI 는 조용해진다. */
1145
- .aw-resizing .aw-resize-handle {
1146
- transform: scale(1.18);
1147
- box-shadow: 0 0 0 5px color-mix(in srgb, var(--aw-primary) 28%, transparent);
1148
- }
1149
- .aw-resizing {
1150
- outline: 1px solid color-mix(in srgb, var(--aw-primary) 60%, transparent);
1151
- outline-offset: 2px;
1152
- }
1153
-
1154
- /* 유튜브 임베드는 iframe 안에 자체 워터마크/컨트롤이 있어 코너에 핸들을 두면
1155
- * 클릭이 충돌한다. embed 안쪽 우하단에 약간 들여서 배치 — YouTube 자체 UI
1156
- * 영역과 약간 떨어진 위치. */
1157
- .aw-content > div.aw-youtube-embed > .aw-resize-handle {
1158
- right: 0px;
1159
- bottom: -15px;
1160
- }
1161
- .aw-resizing {
1162
- user-select: none;
1163
- }
1164
-
1165
- /* 뷰어(읽기 전용) 모드에서는 편집용 컨트롤(리사이즈 핸들 / 정렬 컨트롤) 을
1166
- * 모두 숨긴다. AerizViewer.root 의 className 'aeriz-wysiwyg-viewer' 를 타겟. */
1167
- .aeriz-wysiwyg-viewer .aw-resize-handle,
1168
- .aeriz-wysiwyg-viewer .aw-block-align-controls {
1169
- display: none !important;
1170
- }
1171
-
1172
- /* ─── 모션 민감 사용자 ───────────────────────────────────────────────
1173
- 움직임은 전부 걷어내되 **상태는 그대로 보이게** 한다. transition 만 0 으로
1174
- 만들면 애니메이션으로 나타나던 요소가 영영 안 보이는 사고가 나므로,
1175
- 등장 애니메이션은 끝 상태를 직접 지정한다. */
1176
- @media (prefers-reduced-motion: reduce) {
1177
- .aeriz-wysiwyg *,
1178
- .aeriz-wysiwyg *::before,
1179
- .aeriz-wysiwyg *::after {
1180
- transition-duration: 1ms !important;
1181
- animation-duration: 1ms !important;
1182
- animation-iteration-count: 1 !important;
1183
- }
1184
- .aw-content > [data-block-focused]::before {
1185
- transform: none;
1186
- }
1187
- .aw-block-add:hover svg,
1188
- .aw-block-label-pill:hover,
1189
- .aw-block-handle:active,
1190
- .aw-block-add:active,
1191
- .aw-block-align-btn:active {
1192
- transform: none;
1193
- }
1194
- }
1195
- .aw-toolbar {
1196
- display: flex;
1197
- align-items: center;
1198
- flex-wrap: wrap;
1199
- gap: 2px;
1200
- padding: 6px 8px;
1201
- background-color: var(--aw-toolbar-bg);
1202
- border-bottom: 1px solid var(--aw-border);
1203
- color: var(--aw-toolbar-fg);
1204
- z-index: var(--aw-z-toolbar);
1205
- }
1206
-
1207
- .aw-toolbar-sticky {
1208
- position: sticky;
1209
- top: 0;
1210
- }
1211
-
1212
- .aw-toolbar-group {
1213
- display: flex;
1214
- align-items: center;
1215
- gap: 1px;
1216
- padding: 0 4px;
1217
- border-right: 1px solid var(--aw-divider);
1218
- }
1219
-
1220
- .aw-toolbar-group:last-child {
1221
- border-right: none;
1222
- }
1223
-
1224
- .aw-toolbar-group:empty {
1225
- display: none;
1226
- }
1227
-
1228
- .aw-toolbar-btn {
1229
- display: inline-flex;
1230
- align-items: center;
1231
- justify-content: center;
1232
- height: 32px;
1233
- min-width: 32px;
1234
- padding: 0 6px;
1235
- background: transparent;
1236
- border: 1px solid transparent;
1237
- border-radius: var(--aw-radius-sm);
1238
- cursor: pointer;
1239
- color: var(--aw-toolbar-fg);
1240
- position: relative;
1241
- transition:
1242
- background-color var(--aw-dur-fast) var(--aw-ease-out),
1243
- color var(--aw-dur-fast) var(--aw-ease-out),
1244
- border-color var(--aw-dur-fast) var(--aw-ease-out),
1245
- transform var(--aw-dur-fast) var(--aw-ease-out);
1246
- }
1247
-
1248
- .aw-toolbar-btn:hover:not(:disabled) {
1249
- background-color: var(--aw-btn-hover-bg);
1250
- }
1251
-
1252
- /* 누르는 순간의 반응 — 색보다 먼저 손끝에 닿는 신호. */
1253
- .aw-toolbar-btn:active:not(:disabled) {
1254
- transform: scale(0.94);
1255
- }
1256
-
1257
- .aw-toolbar-btn:focus-visible {
1258
- outline: 2px solid var(--aw-primary);
1259
- outline-offset: -1px;
1260
- }
1261
-
1262
- .aw-toolbar-btn:disabled {
1263
- opacity: 0.4;
1264
- cursor: not-allowed;
1265
- }
1266
-
1267
- .aw-toolbar-btn-active {
1268
- background-color: var(--aw-btn-active-bg);
1269
- color: var(--aw-btn-active-fg);
1270
- }
1271
-
1272
- .aw-toolbar-btn-active:hover:not(:disabled) {
1273
- background-color: var(--aw-btn-active-bg);
1274
- }
1275
-
1276
- /* 켜진 항목 아래 2px 밑줄이 가운데에서 좌우로 펼쳐진다. 배경색만 바뀌면
1277
- 어두운 테마에서 on/off 차이가 잘 안 읽힌다. */
1278
- .aw-toolbar-btn::after {
1279
- content: '';
1280
- position: absolute;
1281
- left: 6px;
1282
- right: 6px;
1283
- bottom: 3px;
1284
- height: 2px;
1285
- border-radius: 1px;
1286
- background: currentColor;
1287
- transform: scaleX(0);
1288
- opacity: 0;
1289
- transition:
1290
- transform var(--aw-dur-base) var(--aw-ease-out),
1291
- opacity var(--aw-dur-base) var(--aw-ease-out);
1292
- }
1293
- .aw-toolbar-btn-active::after {
1294
- transform: scaleX(1);
1295
- opacity: 0.8;
1296
- }
1297
-
1298
- /* 이 버튼에서 열린 드롭다운이 떠 있는 동안 — 셰브론이 뒤집혀 "다시 누르면
1299
- 닫힌다" 를 알린다. */
1300
- .aw-toolbar-btn.is-popover-open {
1301
- background-color: var(--aw-btn-active-bg);
1302
- color: var(--aw-btn-active-fg);
1303
- }
1304
- .aw-toolbar-btn .aw-toolbar-btn-chevron {
1305
- transition: transform var(--aw-dur-base) var(--aw-ease-out), opacity var(--aw-dur-fast);
1306
- }
1307
- .aw-toolbar-btn.is-popover-open .aw-toolbar-btn-chevron {
1308
- transform: rotate(180deg);
1309
- opacity: 1;
1310
- }
1311
-
1312
- .aw-toolbar-btn-icon {
1313
- display: inline-flex;
1314
- width: 18px;
1315
- height: 18px;
1316
- }
1317
-
1318
- .aw-toolbar-btn-icon svg {
1319
- width: 100%;
1320
- height: 100%;
1321
- }
1322
-
1323
- .aw-toolbar-btn-chevron {
1324
- display: inline-flex;
1325
- width: 12px;
1326
- height: 12px;
1327
- margin-left: 2px;
1328
- opacity: 0.7;
1329
- }
1330
-
1331
- .aw-toolbar-btn-chevron svg {
1332
- width: 100%;
1333
- height: 100%;
1334
- }
1335
- /*
1336
- * Dialog / popover 는 .aeriz-wysiwyg 의 자식이 아닌 document.body 에 attach
1337
- * 되므로 에디터의 CSS 변수를 직접 받지 못한다. 대신 자기 자신에
1338
- * data-theme 속성을 받아 아래의 변수 토큰을 분기한다. 호출자(에디터)는
1339
- * dialog/popover 를 열 때 자신의 theme 을 전달한다.
1340
- */
1341
-
1342
- .aw-dialog-overlay,
1343
- .aw-popover {
1344
- --d-bg: #ffffff;
1345
- --d-fg: #1a1a1a;
1346
- --d-fg-muted: #6b7280;
1347
- --d-border: #e5e7eb;
1348
- --d-border-strong: #d1d5db;
1349
- --d-input-bg: #ffffff;
1350
- --d-input-fg: #1a1a1a;
1351
- --d-footer-bg: #f9fafb;
1352
- --d-hover-bg: #f3f4f6;
1353
- --d-primary: #64c8ff;
1354
- --d-primary-hover: #38bdf8;
1355
- --d-primary-soft: rgba(100, 200, 255, 0.18);
1356
- --d-on-primary: #050810;
1357
- --d-overlay: rgba(0, 0, 0, 0.45);
1358
- --d-mono: ui-monospace, SFMono-Regular, 'JetBrains Mono', Menlo, Consolas, monospace;
1359
- /* 다이얼로그/팝오버는 body 에 붙어 에디터 토큰을 상속받지 못한다.
1360
- 같은 값을 여기에도 둔다 — 두 표면의 모션이 어긋나면 한 제품처럼 안 느껴진다.
1361
- (tokens.css 의 --aw-dur-* / --aw-ease-* 와 값이 같아야 한다.) */
1362
- --aw-dur-fast: 90ms;
1363
- --aw-dur-base: 160ms;
1364
- --aw-dur-slow: 240ms;
1365
- --aw-ease-out: cubic-bezier(0.22, 0.85, 0.3, 1);
1366
- --aw-ease-spring: cubic-bezier(0.2, 1.3, 0.4, 1);
1367
- }
1368
-
1369
- .aw-dialog-overlay[data-theme='dark'],
1370
- .aw-popover[data-theme='dark'] {
1371
- --d-bg: #0a0e14;
1372
- --d-fg: #e7eef5;
1373
- --d-fg-muted: #76849b;
1374
- --d-border: rgba(100, 200, 255, 0.16);
1375
- --d-border-strong: rgba(100, 200, 255, 0.30);
1376
- --d-input-bg: #0d1320;
1377
- --d-input-fg: #e7eef5;
1378
- --d-footer-bg: #05080d;
1379
- --d-hover-bg: rgba(100, 200, 255, 0.10);
1380
- --d-primary: #64c8ff;
1381
- --d-primary-hover: #8ad6ff;
1382
- --d-primary-soft: rgba(100, 200, 255, 0.22);
1383
- --d-on-primary: #050810;
1384
- --d-overlay: rgba(0, 0, 0, 0.65);
1385
- }
1386
-
1387
- .aw-dialog-overlay {
1388
- position: fixed;
1389
- inset: 0;
1390
- background-color: var(--d-overlay);
1391
- display: flex;
1392
- align-items: center;
1393
- justify-content: center;
1394
- z-index: var(--aw-z-dialog, 1000);
1395
- font-family: -apple-system, BlinkMacSystemFont, 'Apple SD Gothic Neo', 'Pretendard',
1396
- 'Noto Sans KR', 'Malgun Gothic', sans-serif;
1397
- animation: aw-fade-in 0.12s ease;
1398
- }
1399
-
1400
- @keyframes aw-fade-in { from { opacity: 0; } to { opacity: 1; } }
1401
-
1402
- .aw-dialog {
1403
- background: var(--d-bg);
1404
- color: var(--d-fg);
1405
- border-radius: 8px;
1406
- box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.45);
1407
- width: 480px;
1408
- max-width: 95vw;
1409
- max-height: 85vh;
1410
- display: flex;
1411
- flex-direction: column;
1412
- overflow: hidden;
1413
- animation: aw-slide-up 0.18s ease;
1414
- border: 1px solid var(--d-border);
1415
- }
1416
-
1417
- @keyframes aw-slide-up {
1418
- from { transform: translateY(8px); opacity: 0; }
1419
- to { transform: translateY(0); opacity: 1; }
1420
- }
1421
-
1422
- .aw-dialog-header {
1423
- display: flex;
1424
- align-items: center;
1425
- justify-content: space-between;
1426
- padding: 16px 20px;
1427
- border-bottom: 1px solid var(--d-border);
1428
- }
1429
-
1430
- .aw-dialog-title {
1431
- margin: 0;
1432
- font-size: 16px;
1433
- font-weight: 700;
1434
- color: var(--d-fg);
1435
- }
1436
-
1437
- .aw-dialog-close {
1438
- background: transparent;
1439
- border: none;
1440
- cursor: pointer;
1441
- width: 28px; height: 28px;
1442
- display: inline-flex; align-items: center; justify-content: center;
1443
- border-radius: 4px;
1444
- color: var(--d-fg-muted);
1445
- transition: background-color 0.12s, color 0.12s;
1446
- }
1447
- .aw-dialog-close:hover { background-color: var(--d-hover-bg); color: var(--d-fg); }
1448
- .aw-dialog-close svg { width: 18px; height: 18px; }
1449
-
1450
- .aw-dialog-body {
1451
- padding: 20px;
1452
- overflow-y: auto;
1453
- display: flex; flex-direction: column; gap: 14px;
1454
- }
1455
-
1456
- .aw-dialog-footer {
1457
- padding: 12px 20px;
1458
- border-top: 1px solid var(--d-border);
1459
- display: flex; justify-content: flex-end; gap: 8px;
1460
- background-color: var(--d-footer-bg);
1461
- }
1462
-
1463
- .aw-field { display: flex; flex-direction: column; gap: 6px; }
1464
- .aw-field-label { font-size: 13px; font-weight: 600; color: var(--d-fg); }
1465
-
1466
- .aw-input {
1467
- width: 100%;
1468
- padding: 8px 12px;
1469
- font-size: 14px;
1470
- font-family: inherit;
1471
- border: 1px solid var(--d-border-strong);
1472
- border-radius: 6px;
1473
- outline: none;
1474
- transition: border-color 0.12s, box-shadow 0.12s;
1475
- color: var(--d-input-fg);
1476
- background-color: var(--d-input-bg);
1477
- }
1478
- .aw-input:focus {
1479
- border-color: var(--d-primary);
1480
- box-shadow: 0 0 0 3px var(--d-primary-soft);
1481
- }
1482
-
1483
- .aw-textarea {
1484
- resize: vertical;
1485
- min-height: 120px;
1486
- font-family: var(--d-mono);
1487
- font-size: 12px;
1488
- line-height: 1.5;
1489
- }
1490
-
1491
- .aw-checkbox {
1492
- display: inline-flex; align-items: center; gap: 8px;
1493
- font-size: 14px;
1494
- color: var(--d-fg);
1495
- cursor: pointer; user-select: none;
1496
- }
1497
- .aw-checkbox input { width: 16px; height: 16px; accent-color: var(--d-primary); }
1498
-
1499
- .aw-btn {
1500
- padding: 8px 16px;
1501
- font-size: 14px;
1502
- font-weight: 600;
1503
- border-radius: 6px;
1504
- border: 1px solid transparent;
1505
- cursor: pointer;
1506
- font-family: inherit;
1507
- transition: background-color 0.12s, border-color 0.12s, color 0.12s;
1508
- }
1509
- .aw-btn-primary { background-color: var(--d-primary); color: var(--d-on-primary); }
1510
- .aw-btn-primary:hover { background-color: var(--d-primary-hover); }
1511
- .aw-btn-ghost {
1512
- background-color: transparent;
1513
- color: var(--d-fg);
1514
- border-color: var(--d-border-strong);
1515
- }
1516
- .aw-btn-ghost:hover { background-color: var(--d-hover-bg); }
1517
-
1518
- .aw-tabs {
1519
- display: flex; gap: 4px;
1520
- border-bottom: 1px solid var(--d-border);
1521
- margin: -4px -4px 8px;
1522
- }
1523
- .aw-tab {
1524
- padding: 8px 14px;
1525
- background: transparent;
1526
- border: none;
1527
- border-bottom: 2px solid transparent;
1528
- font-size: 14px;
1529
- font-family: inherit;
1530
- font-weight: 500;
1531
- color: var(--d-fg-muted);
1532
- cursor: pointer;
1533
- transition: color 0.12s, border-color 0.12s;
1534
- }
1535
- .aw-tab:hover { color: var(--d-fg); }
1536
- .aw-tab-active { color: var(--d-primary); border-bottom-color: var(--d-primary); }
1537
- .aw-tab-disabled,
1538
- .aw-tab[disabled] {
1539
- opacity: 0.4;
1540
- cursor: not-allowed;
1541
- text-decoration: line-through;
1542
- color: var(--d-fg-muted);
1543
- }
1544
- .aw-tab-disabled:hover,
1545
- .aw-tab[disabled]:hover { color: var(--d-fg-muted); }
1546
-
1547
- .aw-dialog-hint {
1548
- font-size: 12px;
1549
- color: var(--d-fg-muted);
1550
- margin-top: -4px;
1551
- }
1552
-
1553
- .aw-pane-notice {
1554
- padding: 24px 16px;
1555
- text-align: center;
1556
- font-size: 13px;
1557
- color: var(--d-fg-muted);
1558
- background-color: var(--d-input-bg);
1559
- border: 1px dashed var(--d-border-strong);
1560
- border-radius: 8px;
1561
- }
1562
-
1563
- .aw-pane { display: flex; flex-direction: column; gap: 12px; }
1564
-
1565
- .aw-dropzone {
1566
- display: flex; flex-direction: column;
1567
- align-items: center; justify-content: center;
1568
- gap: 10px;
1569
- padding: 32px 20px;
1570
- border: 2px dashed var(--d-border-strong);
1571
- border-radius: 8px;
1572
- cursor: pointer;
1573
- text-align: center;
1574
- color: var(--d-fg-muted);
1575
- transition: border-color 0.12s, background-color 0.12s, color 0.12s;
1576
- background-color: var(--d-input-bg);
1577
- }
1578
- .aw-dropzone:hover {
1579
- border-color: var(--d-primary);
1580
- background-color: var(--d-hover-bg);
1581
- color: var(--d-primary);
1582
- }
1583
- .aw-dropzone svg { width: 36px; height: 36px; }
1584
- .aw-dropzone-text { font-size: 13px; line-height: 1.5; }
1585
- .aw-dropzone-loading { opacity: 0.6; pointer-events: none; }
1586
-
1587
- .aw-preview {
1588
- width: 100%; height: 160px;
1589
- border-radius: 6px;
1590
- background-size: cover; background-position: center;
1591
- border: 1px solid var(--d-border);
1592
- }
1593
-
1594
- .aw-table-grid {
1595
- display: grid;
1596
- grid-template-columns: repeat(10, 22px);
1597
- grid-template-rows: repeat(10, 22px);
1598
- gap: 2px;
1599
- padding: 8px;
1600
- background-color: var(--d-footer-bg);
1601
- border-radius: 6px;
1602
- width: max-content;
1603
- margin: 0 auto;
1604
- }
1605
- .aw-table-cell {
1606
- background-color: var(--d-input-bg);
1607
- border: 1px solid var(--d-border-strong);
1608
- border-radius: 2px;
1609
- cursor: pointer;
1610
- transition: background-color 0.08s;
1611
- }
1612
- .aw-table-cell-active {
1613
- background-color: var(--d-primary-soft);
1614
- border-color: var(--d-primary);
1615
- }
1616
- .aw-table-status {
1617
- text-align: center;
1618
- font-size: 13px;
1619
- color: var(--d-fg-muted);
1620
- margin-top: 4px;
1621
- }
1622
-
1623
- /* ─── Popovers ──────────────────────────────────────────────── */
1624
-
1625
- .aw-popover {
1626
- background: var(--d-bg);
1627
- color: var(--d-fg);
1628
- border: 1px solid var(--d-border);
1629
- border-radius: 8px;
1630
- box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.35);
1631
- padding: 8px;
1632
- z-index: var(--aw-z-popover, 100);
1633
- animation: aw-fade-in 0.1s ease;
1634
- min-width: 160px;
1635
- font-family: -apple-system, BlinkMacSystemFont, 'Apple SD Gothic Neo', 'Pretendard',
1636
- 'Noto Sans KR', 'Malgun Gothic', sans-serif;
1637
- }
1638
-
1639
- .aw-color-popover {
1640
- /* 8 swatch × 22 + 7 gap × 4 + 8 padding × 2 = 220px 컨텐츠에 맞게 */
1641
- width: max-content;
1642
- min-width: 220px;
1643
- }
1644
-
1645
- .aw-color-grid {
1646
- display: grid;
1647
- grid-template-columns: repeat(8, 22px);
1648
- gap: 4px;
1649
- justify-content: start;
1650
- }
1651
-
1652
- .aw-color-swatch {
1653
- width: 22px; height: 22px;
1654
- border-radius: 4px;
1655
- border: 1px solid var(--d-border);
1656
- cursor: pointer;
1657
- padding: 0;
1658
- transition:
1659
- transform var(--aw-dur-base, 160ms) var(--aw-ease-spring, ease),
1660
- box-shadow var(--aw-dur-fast, 90ms) ease;
1661
- }
1662
- /* 색은 손톱만 한 사각형이라, 확대만으로는 어느 칸에 있는지 확신이 안 선다.
1663
- 같은 색 링을 한 겹 둘러 커서가 놓인 칸을 못 박아 준다. */
1664
- .aw-color-swatch:hover {
1665
- transform: scale(1.18);
1666
- box-shadow: 0 0 0 2px var(--d-bg), 0 0 0 3px var(--d-primary);
1667
- }
1668
- .aw-color-swatch:active { transform: scale(1.02); }
1669
- .aw-color-swatch:focus-visible {
1670
- outline: none;
1671
- box-shadow: 0 0 0 2px var(--d-bg), 0 0 0 3px var(--d-primary);
1672
- }
1673
-
1674
- .aw-color-section {
1675
- font-size: 10px;
1676
- letter-spacing: 0.08em;
1677
- text-transform: uppercase;
1678
- color: var(--d-fg-muted);
1679
- font-weight: 700;
1680
- padding: 6px 2px 4px;
1681
- }
1682
- .aw-color-section + .aw-color-grid { margin-bottom: 4px; }
1683
-
1684
- .aw-popover-action {
1685
- display: block;
1686
- width: 100%;
1687
- text-align: left;
1688
- padding: 8px 10px;
1689
- background: transparent;
1690
- border: none;
1691
- border-radius: 4px;
1692
- cursor: pointer;
1693
- font-size: 13px;
1694
- color: var(--d-fg);
1695
- font-family: inherit;
1696
- margin-top: 4px;
1697
- position: relative;
1698
- }
1699
- .aw-popover-action:hover { background-color: var(--d-hover-bg); }
1700
-
1701
- .aw-popover-item {
1702
- display: flex;
1703
- align-items: center;
1704
- gap: 8px;
1705
- width: 100%;
1706
- text-align: left;
1707
- padding: 4px 8px;
1708
- background: transparent;
1709
- border: none;
1710
- border-radius: 4px;
1711
- cursor: pointer;
1712
- color: var(--d-fg);
1713
- font-family: inherit;
1714
- font-size: 12.5px;
1715
- transition:
1716
- background-color var(--aw-dur-fast, 90ms) ease,
1717
- color var(--aw-dur-fast, 90ms) ease;
1718
- }
1719
- .aw-popover-item:hover { background-color: var(--d-hover-bg); }
1720
- .aw-popover-item:active { background-color: var(--d-primary-soft); }
1721
- .aw-popover-item:focus-visible {
1722
- outline: 2px solid var(--d-primary);
1723
- outline-offset: -2px;
1724
- }
1725
-
1726
- .aw-popover-item-glyph {
1727
- display: inline-flex;
1728
- width: 18px; height: 18px;
1729
- align-items: center; justify-content: center;
1730
- flex-shrink: 0;
1731
- border-radius: 3px;
1732
- background: var(--d-hover-bg);
1733
- color: var(--d-fg-muted);
1734
- font-family: var(--d-mono);
1735
- font-size: 10px;
1736
- font-weight: 700;
1737
- transition:
1738
- background-color var(--aw-dur-fast, 90ms) ease,
1739
- color var(--aw-dur-fast, 90ms) ease,
1740
- transform var(--aw-dur-base, 160ms) var(--aw-ease-spring, ease);
1741
- }
1742
- /* 항목을 훑을 때 기호만 살짝 커진다 — 행 전체가 움직이면 목록이 출렁이지만,
1743
- 18px 짜리 기호 하나는 커서를 따라오는 느낌만 남기고 레이아웃을 흔들지 않는다. */
1744
- .aw-popover-item:hover .aw-popover-item-glyph {
1745
- background: var(--d-primary-soft);
1746
- color: var(--d-primary);
1747
- transform: scale(1.12);
1748
- }
1749
- .aw-popover-item-glyph svg { width: 11px; height: 11px; }
1750
-
1751
- .aw-popover-item-label { flex: 1; min-width: 0; }
1752
-
1753
- .aw-popover-item-icon {
1754
- display: inline-flex;
1755
- width: auto;
1756
- padding: 8px;
1757
- align-items: center; justify-content: center;
1758
- gap: 0;
1759
- }
1760
- .aw-popover-item-icon svg { width: 18px; height: 18px; }
1761
-
1762
- .aw-popover-divider {
1763
- height: 1px;
1764
- background: var(--d-border);
1765
- margin: 4px 6px;
1766
- }
1767
-
1768
- /* 블록 유형 드롭다운 — 항목이 많아 세로로 길어지지 않도록 2 컬럼 그리드.
1769
- * 섹션 구분 divider 는 grid-column 으로 두 컬럼을 모두 가로지른다. */
1770
- .aw-block-type-popover {
1771
- display: grid;
1772
- grid-template-columns: repeat(2, minmax(150px, 1fr));
1773
- column-gap: 2px;
1774
- min-width: 320px;
1775
- }
1776
- .aw-block-type-popover .aw-popover-divider {
1777
- grid-column: 1 / -1;
1778
- }
1779
-
1780
- /* 글꼴 드롭다운 — 라벨(작게) + '가A' 미리보기(글꼴 자체) 두 영역. */
1781
- .aw-font-family-popover {
1782
- min-width: 180px;
1783
- max-height: 360px;
1784
- overflow-y: auto;
1785
- }
1786
- .aw-font-family-item {
1787
- padding: 4px 8px;
1788
- display: flex;
1789
- align-items: center;
1790
- gap: 8px;
1791
- }
1792
- .aw-font-family-name {
1793
- flex: 1 1 auto;
1794
- min-width: 0;
1795
- font-size: 11px;
1796
- line-height: 1.4;
1797
- color: var(--d-fg);
1798
- white-space: nowrap;
1799
- overflow: hidden;
1800
- text-overflow: ellipsis;
1801
- }
1802
- .aw-font-family-preview {
1803
- flex: 0 0 auto;
1804
- font-size: 13px;
1805
- line-height: 1.4;
1806
- color: var(--d-fg-muted);
1807
- letter-spacing: 0.05em;
1808
- }
1809
-
1810
- /* 표 편집 드롭다운 — 행/열 추가·삭제 텍스트 항목 */
1811
- .aw-table-menu-popover {
1812
- min-width: 160px;
1813
- }
1814
- .aw-table-menu-item {
1815
- font-size: 12px;
1816
- padding: 6px 10px;
1817
- }
1818
-
1819
- /* 글자 크기 드롭다운 — 한 줄당 px 라벨이 그 크기로 렌더되는 컴팩트 리스트 */
1820
- .aw-font-size-popover {
1821
- min-width: 80px;
1822
- max-height: 320px;
1823
- overflow-y: auto;
1824
- }
1825
- .aw-font-size-item {
1826
- padding: 2px 10px;
1827
- justify-content: space-between;
1828
- }
1829
- .aw-font-size-label {
1830
- font-size: 11px;
1831
- color: var(--d-fg);
1832
- font-family: var(--d-mono);
1833
- letter-spacing: 0.02em;
1834
- }
1835
-
1836
- .aw-popover-item-active {
1837
- background-color: color-mix(in srgb, var(--d-primary) 14%, transparent);
1838
- color: var(--d-primary);
1839
- }
1840
- .aw-popover-item-active::after {
1841
- content: '✓';
1842
- margin-left: auto;
1843
- font-size: 11px;
1844
- }
1845
-
1846
- .aw-align-popover {
1847
- display: flex;
1848
- gap: 2px;
1849
- min-width: auto;
1850
- }
1851
-
1852
- /* 표 삽입 드롭다운 — 호버에 따라 동적으로 늘어나는 셀 그리드 */
1853
- .aw-table-popover {
1854
- padding: 10px;
1855
- min-width: auto;
1856
- }
1857
- .aw-table-picker-grid {
1858
- display: grid;
1859
- gap: 2px;
1860
- margin-bottom: 6px;
1861
- }
1862
- .aw-table-picker-grid .aw-table-cell {
1863
- width: 18px;
1864
- height: 18px;
1865
- background-color: var(--d-input-bg);
1866
- border: 1px solid var(--d-border-strong);
1867
- border-radius: 2px;
1868
- cursor: pointer;
1869
- transition: background-color 0.06s, border-color 0.06s;
1870
- }
1871
- /* 선택된 칸은 살짝 안으로 눌러 담긴다 — 몇 칸이 잡혔는지 숫자를 읽지 않고도
1872
- 면적으로 먼저 보인다. */
1873
- .aw-table-picker-grid .aw-table-cell.aw-table-cell-active {
1874
- background-color: var(--d-primary-soft);
1875
- border-color: var(--d-primary);
1876
- transform: scale(0.9);
1877
- }
1878
- .aw-table-picker-grid .aw-table-cell {
1879
- transition:
1880
- background-color var(--aw-dur-fast, 90ms) ease,
1881
- border-color var(--aw-dur-fast, 90ms) ease,
1882
- transform var(--aw-dur-fast, 90ms) var(--aw-ease-out, ease);
1883
- }
1884
- .aw-table-popover .aw-table-status {
1885
- text-align: center;
1886
- font-size: 12px;
1887
- color: var(--d-fg-muted);
1888
- font-family: var(--d-mono);
1889
- margin: 0;
1890
- }
1891
-
1892
- .aw-coming-soon-popover .aw-popover-section {
1893
- font-size: 11px;
1894
- letter-spacing: 0.08em;
1895
- text-transform: uppercase;
1896
- color: var(--d-fg-muted);
1897
- padding: 4px 8px;
1898
- font-weight: 700;
1899
- }
1900
- .aw-coming-soon-popover .aw-popover-empty {
1901
- padding: 10px 8px;
1902
- font-size: 13px;
1903
- color: var(--d-fg-muted);
1904
- }
1
+ .aeriz-wysiwyg {
2
+ --aw-bg: #ffffff;
3
+ --aw-fg: #1a1a1a;
4
+ --aw-fg-muted: #6b7280;
5
+ --aw-border: #e5e7eb;
6
+ --aw-toolbar-bg: #f9fafb;
7
+ --aw-toolbar-fg: #374151;
8
+ --aw-btn-hover-bg: #e5e7eb;
9
+ --aw-btn-active-bg: #dbeafe;
10
+ --aw-btn-active-fg: #1d4ed8;
11
+ --aw-primary: #03c75a;
12
+ --aw-primary-fg: #ffffff;
13
+ --aw-primary-hover: #02b150;
14
+ --aw-divider: #e5e7eb;
15
+ --aw-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
16
+ --aw-shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
17
+ --aw-shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
18
+ --aw-radius: 6px;
19
+ --aw-radius-sm: 4px;
20
+ --aw-font: -apple-system, BlinkMacSystemFont, 'Apple SD Gothic Neo', 'Pretendard',
21
+ 'Noto Sans KR', 'Malgun Gothic', sans-serif;
22
+ --aw-font-mono: 'SF Mono', Menlo, Monaco, Consolas, 'D2Coding', monospace;
23
+
24
+ /* ─── 모션 ──────────────────────────────────────────────────────────
25
+ 블록 UI 의 전환은 전부 이 토큰들을 쓴다. 값이 한 군데 모여 있어야
26
+ "누르면 즉각, 사라질 땐 느긋하게" 같은 규칙이 화면 전체에서 일관된다.
27
+ · fast — 눌림/색 변화처럼 손끝에 바로 붙어야 하는 것
28
+ · base — 나타남/사라짐
29
+ · slow — 위치 이동처럼 눈으로 따라가는 것
30
+ · ease-out : 등장(감속). ease-spring : 살짝 튕기며 자리잡는 등장. */
31
+ --aw-dur-fast: 90ms;
32
+ --aw-dur-base: 160ms;
33
+ --aw-dur-slow: 240ms;
34
+ --aw-ease-out: cubic-bezier(0.22, 0.85, 0.3, 1);
35
+ --aw-ease-spring: cubic-bezier(0.2, 1.3, 0.4, 1);
36
+
37
+ --aw-toolbar-height: 44px;
38
+ --aw-z-toolbar: 10;
39
+ --aw-z-popover: 100;
40
+ --aw-z-dialog: 1000;
41
+ }
42
+
43
+ .aeriz-wysiwyg[data-theme='dark'] {
44
+ --aw-bg: #0a0e14;
45
+ --aw-fg: #e7eef5;
46
+ --aw-fg-muted: #6a7a8a;
47
+ --aw-border: rgba(100, 200, 255, 0.12);
48
+ --aw-toolbar-bg: #0d1320;
49
+ --aw-toolbar-fg: #cfd6dd;
50
+ --aw-btn-hover-bg: rgba(100, 200, 255, 0.06);
51
+ --aw-btn-active-bg: rgba(100, 200, 255, 0.14);
52
+ --aw-btn-active-fg: #64c8ff;
53
+ --aw-primary: #64c8ff;
54
+ --aw-primary-fg: #050810;
55
+ --aw-primary-hover: #8ad6ff;
56
+ --aw-divider: rgba(100, 200, 255, 0.12);
57
+ }
58
+ .aeriz-wysiwyg {
59
+ font-family: var(--aw-font);
60
+ background-color: var(--aw-bg);
61
+ color: var(--aw-fg);
62
+ border: 1px solid var(--aw-border);
63
+ border-radius: var(--aw-radius);
64
+ overflow: hidden;
65
+ display: flex;
66
+ flex-direction: column;
67
+ /* 부모가 명시적 높이를 주면 그 높이를 채우고 내부 컨텐츠가 스크롤되며,
68
+ 부모가 auto 면 min-height 가 floor 로 작용한다. */
69
+ height: 100%;
70
+ min-height: 320px;
71
+ box-shadow: var(--aw-shadow-sm);
72
+ }
73
+
74
+ .aeriz-wysiwyg *,
75
+ .aeriz-wysiwyg *::before,
76
+ .aeriz-wysiwyg *::after {
77
+ box-sizing: border-box;
78
+ }
79
+
80
+ .aw-content-wrap {
81
+ flex: 1;
82
+ /* flex 자식이 기본 min-content 크기 이상으로 줄어들 수 있게 함 — 이게 없으면
83
+ overflow-y:auto 가 부모를 밀어내 스크롤이 발생하지 않는다. */
84
+ min-height: 0;
85
+ overflow-y: auto;
86
+ background-color: var(--aw-bg);
87
+ position: relative; /* 드래그 핸들 / 드롭 인디케이터 절대 위치 기준 */
88
+ }
89
+
90
+ /* ─── 뷰어 모드 ─────────────────────────────────────────────────────
91
+ AerizViewer 는 컨텐츠 길이만큼 자연스럽게 늘어난다 (스크롤 / 고정 높이 X).
92
+ 에디터 기본 규칙을 viewer 클래스가 좁게 덮어쓴다. */
93
+ .aeriz-wysiwyg-viewer {
94
+ height: auto;
95
+ min-height: 0;
96
+ }
97
+ .aeriz-wysiwyg-viewer .aw-content-wrap {
98
+ flex: 0 0 auto;
99
+ overflow: visible;
100
+ min-height: 0;
101
+ }
102
+ .aeriz-wysiwyg-viewer .aw-content {
103
+ min-height: 0;
104
+ }
105
+
106
+ /* ─── 드래그 핸들 오버레이 ───────────────────────────────────────────── */
107
+
108
+ .aw-block-handles {
109
+ position: absolute;
110
+ top: 0;
111
+ left: 0;
112
+ width: 20px;
113
+ height: 100%;
114
+ pointer-events: none;
115
+ z-index: 2;
116
+ }
117
+
118
+ /* 블록별 컨트롤 그룹 — 드래그 핸들 + 단락 추가 버튼을 세로로 묶는다.
119
+ 지금 다루는 블록(포인터가 올라간 블록 / 캐럿이 있는 블록) 것만 드러난다.
120
+ 본문 어디에나 마우스만 올리면 모든 블록의 핸들이 한꺼번에 떠오르던
121
+ 예전 동작은, 글이 길어질수록 왼쪽에 점 기둥을 세워 놓는 꼴이었다. */
122
+ .aw-block-controls {
123
+ position: absolute;
124
+ left: 3px;
125
+ display: flex;
126
+ flex-direction: column;
127
+ gap: 0;
128
+ pointer-events: none;
129
+ opacity: 0;
130
+ /* 살짝 왼쪽에서 밀려 들어온다 — 나타남에 방향이 생기면 어느 블록에
131
+ 붙은 컨트롤인지가 눈으로 더 빨리 읽힌다. */
132
+ transform: translateX(-3px);
133
+ transition:
134
+ opacity var(--aw-dur-base) var(--aw-ease-out),
135
+ transform var(--aw-dur-base) var(--aw-ease-out);
136
+ }
137
+ /* 캐럿이 있는 블록 — 은은하게 상주. 포인터가 올라오면 완전히 또렷하게. */
138
+ .aw-block-controls.is-active {
139
+ opacity: 0.5;
140
+ transform: none;
141
+ }
142
+ .aw-block-controls.is-hovered,
143
+ .aw-block-controls:hover,
144
+ .aw-block-controls:focus-within {
145
+ opacity: 1;
146
+ transform: none;
147
+ }
148
+
149
+ /* 높이는 AerizEditor.positionHandle 의 HANDLE_HEIGHT 와 짝을 이룬다 —
150
+ 한쪽만 바꾸면 짧은 블록(hr) 위에서 핸들의 수직 정렬이 어긋난다. */
151
+ .aw-block-handle,
152
+ .aw-block-add {
153
+ width: 14px;
154
+ height: 18px;
155
+ display: inline-flex;
156
+ align-items: center;
157
+ justify-content: center;
158
+ background: transparent;
159
+ border: 1px solid transparent;
160
+ border-radius: 3px;
161
+ color: var(--aw-fg-muted);
162
+ pointer-events: auto;
163
+ user-select: none;
164
+ appearance: none;
165
+ padding: 0;
166
+ cursor: pointer;
167
+ transition:
168
+ color var(--aw-dur-fast) var(--aw-ease-out),
169
+ background-color var(--aw-dur-fast) var(--aw-ease-out),
170
+ border-color var(--aw-dur-fast) var(--aw-ease-out),
171
+ transform var(--aw-dur-fast) var(--aw-ease-out);
172
+ }
173
+ .aw-block-handle { cursor: grab; }
174
+
175
+ .aw-block-handle svg,
176
+ .aw-block-add svg {
177
+ width: 10px;
178
+ height: 11px;
179
+ }
180
+
181
+ .aw-block-handle:hover,
182
+ .aw-block-add:hover {
183
+ color: var(--aw-btn-active-fg, var(--aw-primary));
184
+ background-color: var(--aw-btn-hover-bg);
185
+ border-color: var(--aw-border);
186
+ }
187
+ /* 눌린 순간 살짝 눌러앉는다 — 클릭이 먹었다는 신호가 색보다 빠르다. */
188
+ .aw-block-handle:active,
189
+ .aw-block-add:active {
190
+ transform: scale(0.88);
191
+ }
192
+ /* 키보드 사용자에게도 같은 위치를 알려 준다. */
193
+ .aw-block-handle:focus-visible,
194
+ .aw-block-add:focus-visible {
195
+ outline: 2px solid var(--aw-primary);
196
+ outline-offset: 1px;
197
+ color: var(--aw-primary);
198
+ }
199
+ /* 추가 버튼의 '+' 는 hover 시 1/4 바퀴 돌아 "여기서 새로 시작한다" 는
200
+ 느낌을 준다. 회전은 8px 아이콘에서도 확실히 읽히는 몇 안 되는 변화다. */
201
+ .aw-block-add svg {
202
+ transition: transform var(--aw-dur-base) var(--aw-ease-spring);
203
+ }
204
+ .aw-block-add:hover svg {
205
+ transform: rotate(90deg);
206
+ }
207
+
208
+ .aw-block-handle.is-dragging {
209
+ cursor: grabbing;
210
+ color: var(--aw-primary);
211
+ background-color: var(--aw-btn-active-bg);
212
+ border-color: var(--aw-primary);
213
+ transform: scale(1.05);
214
+ }
215
+
216
+ /* 끌고 있는 블록 자신 — 자리만 남기고 물러난다. */
217
+ .is-block-dragging {
218
+ opacity: 0.35;
219
+ filter: saturate(0.6);
220
+ transition:
221
+ opacity var(--aw-dur-base) var(--aw-ease-out),
222
+ filter var(--aw-dur-base) var(--aw-ease-out);
223
+ }
224
+
225
+ /* ─── 드롭 위치 인디케이터 ─────────────────────────────────────────────
226
+ 선 왼쪽 끝에 점을 하나 찍는다. 얇은 선만 있으면 어느 쪽 여백에 걸린
227
+ 건지 애매한데, 점이 있으면 "여기서부터" 가 분명해진다. */
228
+
229
+ .aw-block-drop-indicator {
230
+ position: absolute;
231
+ left: 28px;
232
+ right: 28px;
233
+ height: 2px;
234
+ background: var(--aw-primary);
235
+ border-radius: 1px;
236
+ pointer-events: none;
237
+ z-index: 3;
238
+ transform: translateY(-1px);
239
+ box-shadow: 0 0 6px color-mix(in srgb, var(--aw-primary) 70%, transparent);
240
+ animation: aw-drop-in var(--aw-dur-base) var(--aw-ease-out);
241
+ }
242
+ .aw-block-drop-indicator::before {
243
+ content: '';
244
+ position: absolute;
245
+ left: -3px;
246
+ top: 50%;
247
+ width: 7px;
248
+ height: 7px;
249
+ border-radius: 50%;
250
+ background: var(--aw-primary);
251
+ transform: translateY(-50%);
252
+ }
253
+ @keyframes aw-drop-in {
254
+ from {
255
+ opacity: 0;
256
+ transform: translateY(-1px) scaleX(0.94);
257
+ }
258
+ to {
259
+ opacity: 1;
260
+ transform: translateY(-1px) scaleX(1);
261
+ }
262
+ }
263
+
264
+ /* ─── Status Bar ─────────────────────────────────────────────────────
265
+ 에디터 하단의 메타 정보 표시(활성 블록 / 블록 수 / 글자 수). 뷰어에는
266
+ 불필요하므로 .aeriz-wysiwyg-viewer 안에서는 숨긴다. */
267
+
268
+ .aw-status-bar {
269
+ display: flex;
270
+ align-items: center;
271
+ justify-content: space-between;
272
+ gap: 12px;
273
+ padding: 5px 12px;
274
+ border-top: 1px solid var(--aw-border);
275
+ background-color: var(--aw-toolbar-bg);
276
+ color: var(--aw-fg-muted);
277
+ font-family: var(--aw-font-mono);
278
+ font-size: 11px;
279
+ letter-spacing: 0.02em;
280
+ flex-shrink: 0;
281
+ }
282
+
283
+ .aw-status-left,
284
+ .aw-status-right {
285
+ display: inline-flex;
286
+ align-items: center;
287
+ gap: 12px;
288
+ min-width: 0;
289
+ }
290
+
291
+ .aw-status-item {
292
+ display: inline-flex;
293
+ align-items: center;
294
+ gap: 6px;
295
+ white-space: nowrap;
296
+ overflow: hidden;
297
+ text-overflow: ellipsis;
298
+ }
299
+
300
+ .aw-status-item::before {
301
+ content: '';
302
+ width: 5px;
303
+ height: 5px;
304
+ border-radius: 50%;
305
+ background: var(--aw-primary);
306
+ opacity: 0.55;
307
+ flex-shrink: 0;
308
+ }
309
+
310
+ /* status bar 안의 작은 아이콘 액션 버튼 (undo/redo 등) */
311
+ .aw-status-action {
312
+ appearance: none;
313
+ cursor: pointer;
314
+ background: transparent;
315
+ border: 1px solid transparent;
316
+ color: var(--aw-fg-muted);
317
+ padding: 0;
318
+ width: 22px;
319
+ height: 22px;
320
+ border-radius: 3px;
321
+ display: inline-flex;
322
+ align-items: center;
323
+ justify-content: center;
324
+ flex-shrink: 0;
325
+ transition: color 0.12s, background-color 0.12s, border-color 0.12s;
326
+ }
327
+ .aw-status-action svg {
328
+ width: 13px;
329
+ height: 13px;
330
+ }
331
+ .aw-status-action:hover {
332
+ color: var(--aw-btn-active-fg, var(--aw-primary));
333
+ background-color: var(--aw-btn-hover-bg);
334
+ border-color: var(--aw-border);
335
+ }
336
+ .aw-status-action:active {
337
+ background-color: var(--aw-btn-active-bg);
338
+ }
339
+
340
+ .aeriz-wysiwyg-viewer .aw-status-bar {
341
+ display: none;
342
+ }
343
+
344
+ /* ─── 블록 포커스 인디케이터 + 라벨 태그 ─────────────────────────────── */
345
+
346
+ /* 활성 블록 표시는 **레일(왼쪽 세로선) + 옅은 바탕** 두 겹이다.
347
+ 레일은 색이 아니라 길이로 자란다 — `box-shadow` 를 켜고 끄면 선이 툭
348
+ 나타나는데, 세로로 자라 올라오면 "이 블록이 지금 잡혔다" 가 동작으로
349
+ 읽힌다. 레일은 별도 pseudo 로 그려서 블록 자신의 배경(코드 블록 ·
350
+ 인용구) 과 겹치지 않는다. 어떤 태그에도 레이아웃 영향을 주지 않도록
351
+ position:absolute 만 쓰고 블록의 박스 크기는 건드리지 않는다. */
352
+ .aw-content > [data-block-id] {
353
+ position: relative;
354
+ transition: background-color var(--aw-dur-base) var(--aw-ease-out);
355
+ }
356
+
357
+ .aw-content > [data-block-id]::before {
358
+ content: '';
359
+ position: absolute;
360
+ left: -10px;
361
+ top: 0;
362
+ width: 3px;
363
+ height: 100%;
364
+ border-radius: 2px;
365
+ background: var(--aw-primary);
366
+ opacity: 0;
367
+ transform: scaleY(0.25);
368
+ transform-origin: center;
369
+ pointer-events: none;
370
+ transition:
371
+ opacity var(--aw-dur-base) var(--aw-ease-out),
372
+ transform var(--aw-dur-base) var(--aw-ease-spring);
373
+ }
374
+
375
+ .aw-content > [data-block-focused] {
376
+ background-color: color-mix(in srgb, var(--aw-primary) 7%, transparent);
377
+ border-radius: 3px;
378
+ }
379
+
380
+ .aw-content > [data-block-focused]::before {
381
+ opacity: 1;
382
+ transform: scaleY(1);
383
+ }
384
+
385
+ /* 포인터가 올라간 블록 — 아직 잡은 건 아니므로 레일 없이 바탕만 살짝.
386
+ 활성 블록의 강조를 덮지 않도록 :not() 으로 비켜 준다. */
387
+ .aw-content > [data-block-id]:hover:not([data-block-focused]) {
388
+ background-color: color-mix(in srgb, var(--aw-fg) 3%, transparent);
389
+ }
390
+
391
+ /* 표는 pseudo 요소가 익명 테이블 박스로 감싸여 위치가 어긋난다. 레일 대신
392
+ 테두리 강조로 같은 뜻을 전한다. */
393
+ .aw-content > table::before {
394
+ display: none;
395
+ }
396
+ .aw-content > table[data-block-focused] {
397
+ outline: 1px solid color-mix(in srgb, var(--aw-primary) 55%, transparent);
398
+ outline-offset: 3px;
399
+ }
400
+
401
+ /* 유형이 막 바뀐 블록 — 한 번만 반짝. AerizEditor 가 애니메이션이 끝나면
402
+ 속성을 지운다. */
403
+ .aw-content > [data-block-flash] {
404
+ animation: aw-block-flash 520ms var(--aw-ease-out);
405
+ }
406
+ @keyframes aw-block-flash {
407
+ 0% {
408
+ background-color: color-mix(in srgb, var(--aw-primary) 26%, transparent);
409
+ }
410
+ 100% {
411
+ background-color: transparent;
412
+ }
413
+ }
414
+
415
+ /* 캐럿이 놓인 빈 본문 블록 — 무엇을 하면 되는지 한 줄로 알려 준다.
416
+ 문서 전체가 비었을 때 뜨는 에디터 placeholder 와 겹치지 않도록, 블록이
417
+ 둘 이상일 때만(`:has(> :nth-child(2))`) 나타난다. */
418
+ .aw-content:has(> :nth-child(2))
419
+ > p[data-block-focused]:has(> br:only-child)::after {
420
+ content: var(--aw-empty-hint, '');
421
+ position: absolute;
422
+ left: 0;
423
+ top: 0;
424
+ color: var(--aw-fg-muted);
425
+ opacity: 0;
426
+ pointer-events: none;
427
+ user-select: none;
428
+ font-size: 0.95em;
429
+ animation: aw-hint-in var(--aw-dur-slow) var(--aw-ease-out) 120ms forwards;
430
+ }
431
+ @keyframes aw-hint-in {
432
+ from {
433
+ opacity: 0;
434
+ transform: translateX(-2px);
435
+ }
436
+ to {
437
+ opacity: 0.45;
438
+ transform: none;
439
+ }
440
+ }
441
+
442
+ /* 구분선은 높이가 사실상 0 이라 레일이 안 보인다 — 최소 길이를 주고
443
+ 세로 가운데에 건다 (핸들의 HANDLE_HEIGHT 정렬과 같은 규칙). */
444
+ .aw-content > hr::before {
445
+ height: 14px;
446
+ top: 50%;
447
+ margin-top: -7px;
448
+ }
449
+
450
+ /* 읽기 전용 뷰어에는 편집 상태 표시가 의미 없다. */
451
+ .aeriz-wysiwyg-viewer .aw-content > [data-block-id]::before {
452
+ display: none;
453
+ }
454
+ .aeriz-wysiwyg-viewer .aw-content > [data-block-id]:hover {
455
+ background-color: transparent;
456
+ }
457
+
458
+ /* ─── 인터랙티브 블록 라벨 pill ───────────────────────────────────────
459
+ 활성 블록 위에 떠 있는 작은 버튼. 정적 ::before pseudo 라벨을 대체하며
460
+ 클릭 시 블록 유형 드롭다운을 열어 마이크로 인터랙션으로 유형 변경. */
461
+ .aw-block-label-pill {
462
+ position: absolute;
463
+ z-index: 4;
464
+ appearance: none;
465
+ font-family: var(--aw-font-mono);
466
+ font-size: 9px;
467
+ font-weight: 700;
468
+ letter-spacing: 0.08em;
469
+ text-transform: uppercase;
470
+ color: var(--aw-primary);
471
+ background: var(--aw-bg);
472
+ padding: 2px 5px 2px 4px;
473
+ border: 1px solid color-mix(in srgb, var(--aw-primary) 45%, transparent);
474
+ border-radius: 4px;
475
+ cursor: pointer;
476
+ user-select: none;
477
+ display: inline-flex;
478
+ align-items: center;
479
+ gap: 4px;
480
+ line-height: 1;
481
+ white-space: nowrap;
482
+ transition:
483
+ transform var(--aw-dur-fast) var(--aw-ease-out),
484
+ background-color var(--aw-dur-fast) var(--aw-ease-out),
485
+ color var(--aw-dur-fast) var(--aw-ease-out),
486
+ border-color var(--aw-dur-fast) var(--aw-ease-out),
487
+ box-shadow var(--aw-dur-base) var(--aw-ease-out);
488
+ }
489
+
490
+ /* 유형 기호 — 라벨을 읽기 전에 눈이 먼저 잡는 부분이라 한 칸 더 진하게. */
491
+ .aw-block-label-pill .aw-pill-glyph {
492
+ display: inline-flex;
493
+ align-items: center;
494
+ justify-content: center;
495
+ min-width: 13px;
496
+ height: 13px;
497
+ padding: 0 2px;
498
+ border-radius: 3px;
499
+ background: color-mix(in srgb, var(--aw-primary) 16%, transparent);
500
+ font-size: 9px;
501
+ letter-spacing: 0;
502
+ transition: background-color var(--aw-dur-fast) var(--aw-ease-out);
503
+ }
504
+
505
+ .aw-block-label-pill::after {
506
+ content: '▾';
507
+ font-size: 9px;
508
+ opacity: 0.55;
509
+ transition:
510
+ opacity var(--aw-dur-fast) var(--aw-ease-out),
511
+ transform var(--aw-dur-base) var(--aw-ease-out);
512
+ }
513
+ .aw-block-label-pill:hover {
514
+ background: var(--aw-primary);
515
+ color: var(--aw-primary-fg);
516
+ border-color: var(--aw-primary);
517
+ transform: translateY(-1px);
518
+ box-shadow: 0 3px 10px color-mix(in srgb, var(--aw-primary) 32%, transparent);
519
+ }
520
+ .aw-block-label-pill:hover .aw-pill-glyph {
521
+ background: color-mix(in srgb, var(--aw-primary-fg) 22%, transparent);
522
+ }
523
+ .aw-block-label-pill:hover::after {
524
+ opacity: 1;
525
+ transform: translateY(1px);
526
+ }
527
+ .aw-block-label-pill:active {
528
+ transform: translateY(0) scale(0.97);
529
+ box-shadow: none;
530
+ }
531
+ .aw-block-label-pill:focus-visible {
532
+ outline: 2px solid var(--aw-primary);
533
+ outline-offset: 2px;
534
+ }
535
+
536
+ /* 다른 블록으로 캐럿이 옮겨갈 때만 재생된다 (AerizEditor 가 클래스를 다시
537
+ 붙인다). 같은 블록에서 타이핑하는 동안에는 미동도 없어야 한다. */
538
+ .aw-block-label-pill.is-entering {
539
+ animation: aw-pill-in var(--aw-dur-slow) var(--aw-ease-spring);
540
+ }
541
+ /* pill 에서 연 블록 유형 메뉴가 떠 있는 동안 */
542
+ .aw-block-label-pill.is-popover-open {
543
+ background: var(--aw-primary);
544
+ color: var(--aw-primary-fg);
545
+ border-color: var(--aw-primary);
546
+ }
547
+ .aw-block-label-pill.is-popover-open::after {
548
+ transform: rotate(180deg);
549
+ opacity: 1;
550
+ }
551
+ @keyframes aw-pill-in {
552
+ from {
553
+ opacity: 0;
554
+ transform: translateY(3px) scale(0.92);
555
+ }
556
+ to {
557
+ opacity: 1;
558
+ transform: translateY(0) scale(1);
559
+ }
560
+ }
561
+
562
+ .aw-content {
563
+ outline: none;
564
+ /* 위쪽 여백은 라벨 pill(약 15px) + 여유가 들어갈 만큼 둔다 — 문서 첫
565
+ 블록에서도 pill 이 글자를 덮지 않아야 한다. */
566
+ padding: 28px 28px 24px;
567
+ min-height: 240px;
568
+ font-size: 15px;
569
+ line-height: 1.7;
570
+ color: var(--aw-fg);
571
+ word-break: break-word;
572
+ }
573
+
574
+ .aw-content:empty::before,
575
+ .aw-content[data-placeholder]:has(p:only-child > br:only-child)::before {
576
+ content: attr(data-placeholder);
577
+ color: var(--aw-fg-muted);
578
+ pointer-events: none;
579
+ position: absolute;
580
+ }
581
+
582
+ .aw-content > p {
583
+ margin: 0 0 0.8em 0;
584
+ }
585
+ .aw-content > p:last-child {
586
+ margin-bottom: 0;
587
+ }
588
+
589
+ /* 정렬 클래스 — getHTML({ styles: 'class' }) 출력이 style="text-align:…" 대신
590
+ 사용한다. style 속성을 제거하는 서버 sanitizer 를 통과해도 정렬이 남는다.
591
+ 에디터 자체는 계속 인라인 style 로 정렬하므로 편집 중 동작에는 영향이 없다. */
592
+ .aw-content .aw-align-left {
593
+ text-align: left;
594
+ }
595
+ .aw-content .aw-align-center {
596
+ text-align: center;
597
+ }
598
+ .aw-content .aw-align-right {
599
+ text-align: right;
600
+ }
601
+ .aw-content .aw-align-justify {
602
+ text-align: justify;
603
+ }
604
+
605
+ .aw-content h1,
606
+ .aw-content h2,
607
+ .aw-content h3 {
608
+ font-weight: 700;
609
+ margin: 0.6em 0 0.4em;
610
+ padding-bottom: 0.25em;
611
+ border-bottom: 1px solid var(--aw-border);
612
+ }
613
+ .aw-content h1 { font-size: 1.875rem; line-height: 1.3; }
614
+ .aw-content h2 { font-size: 1.5rem; line-height: 1.35; }
615
+ .aw-content h3 { font-size: 1.25rem; line-height: 1.4; }
616
+
617
+ .aw-content blockquote {
618
+ margin: 1em 0;
619
+ padding: 0.5em 1em;
620
+ border-left: 4px solid var(--aw-primary);
621
+ background-color: color-mix(in srgb, var(--aw-primary) 8%, transparent);
622
+ color: var(--aw-fg);
623
+ font-style: italic;
624
+ }
625
+
626
+ /* ── 콜아웃 (aside.aw-callout) ───────────────────────────────────────
627
+ 종류마다 색조와 아이콘이 곧 의미다. 색만으로 구분하지 않고 아이콘을 함께
628
+ 두는 이유 — 색각 이상이 있거나 흑백으로 인쇄하면 색은 아무것도 알려주지
629
+ 못한다. 아이콘은 `::before` 라 출력에 남길 element 가 없다. */
630
+ .aw-content aside.aw-callout {
631
+ --aw-callout-tone: var(--aw-primary);
632
+ --aw-callout-icon: 'ℹ';
633
+ position: relative;
634
+ margin: 1em 0;
635
+ padding: 0.75em 1em 0.75em 2.75em;
636
+ border-left: 4px solid var(--aw-callout-tone);
637
+ border-radius: var(--aw-radius-sm);
638
+ background-color: color-mix(in srgb, var(--aw-callout-tone) 10%, transparent);
639
+ color: var(--aw-fg);
640
+ }
641
+
642
+ .aw-content aside.aw-callout::before {
643
+ content: var(--aw-callout-icon);
644
+ position: absolute;
645
+ left: 0.85em;
646
+ top: 0.7em;
647
+ width: 1.2em;
648
+ height: 1.2em;
649
+ display: flex;
650
+ align-items: center;
651
+ justify-content: center;
652
+ border-radius: 50%;
653
+ background-color: var(--aw-callout-tone);
654
+ color: #fff;
655
+ font-size: 0.8em;
656
+ font-weight: 700;
657
+ line-height: 1;
658
+ cursor: pointer;
659
+ }
660
+
661
+ .aw-content aside.aw-callout-note { --aw-callout-tone: #3b82f6; --aw-callout-icon: 'i'; }
662
+ .aw-content aside.aw-callout-tip { --aw-callout-tone: #10b981; --aw-callout-icon: '✓'; }
663
+ .aw-content aside.aw-callout-warning { --aw-callout-tone: #f59e0b; --aw-callout-icon: '!'; }
664
+ .aw-content aside.aw-callout-danger { --aw-callout-tone: #ef4444; --aw-callout-icon: '×'; }
665
+
666
+ .aw-content aside.aw-callout > :first-child { margin-top: 0; }
667
+ .aw-content aside.aw-callout > :last-child { margin-bottom: 0; }
668
+
669
+ /* ── 토글 (details/summary) ──────────────────────────────────────────
670
+ 접기/펴기는 브라우저 기본 동작이다. 여기서는 모양만 손본다 — 발행된
671
+ 문서에 이 CSS 가 없어도 내용은 그대로 읽히고 접기도 계속 동작한다. */
672
+ .aw-content details {
673
+ margin: 1em 0;
674
+ padding: 0.5em 0.75em;
675
+ border: 1px solid var(--aw-border);
676
+ border-radius: var(--aw-radius-sm);
677
+ background-color: color-mix(in srgb, var(--aw-fg) 3%, var(--aw-bg));
678
+ }
679
+
680
+ .aw-content details > summary {
681
+ /* 기본 삼각형을 지우고 ::before 로 직접 그린다 — 브라우저마다 위치와
682
+ 크기가 달라 클릭 판정(handleToggleSummaryClick) 과 어긋난다. */
683
+ list-style: none;
684
+ position: relative;
685
+ padding-left: 1.5em;
686
+ cursor: default;
687
+ font-weight: 600;
688
+ color: var(--aw-fg);
689
+ }
690
+
691
+ .aw-content details > summary::-webkit-details-marker {
692
+ display: none;
693
+ }
694
+
695
+ .aw-content details > summary::before {
696
+ content: '';
697
+ position: absolute;
698
+ /* 왼쪽 패딩(1.5em) 안에 그린다 — 이 영역을 누르면 접기/펴기다. */
699
+ left: 0.35em;
700
+ top: 50%;
701
+ width: 0;
702
+ height: 0;
703
+ border-left: 5px solid currentColor;
704
+ border-top: 4px solid transparent;
705
+ border-bottom: 4px solid transparent;
706
+ transform: translateY(-50%);
707
+ transition: transform var(--aw-dur-fast) var(--aw-ease-out);
708
+ cursor: pointer;
709
+ }
710
+
711
+ .aw-content details[open] > summary::before {
712
+ transform: translateY(-50%) rotate(90deg);
713
+ }
714
+
715
+ .aw-content details > .aw-toggle-body {
716
+ padding-top: 0.25em;
717
+ }
718
+
719
+ /* 본문 첫/마지막 단락의 바깥 여백은 토글의 padding 과 겹쳐 보인다. */
720
+ .aw-content details > .aw-toggle-body > :first-child { margin-top: 0; }
721
+ .aw-content details > .aw-toggle-body > :last-child { margin-bottom: 0; }
722
+
723
+ .aw-content pre {
724
+ position: relative;
725
+ background-color: color-mix(in srgb, var(--aw-fg) 6%, var(--aw-bg));
726
+ border: 1px solid var(--aw-border);
727
+ border-radius: var(--aw-radius-sm);
728
+ padding: 12px 16px;
729
+ font-family: var(--aw-font-mono);
730
+ font-size: 13px;
731
+ line-height: 1.6;
732
+ overflow-x: auto;
733
+ margin: 1em 0;
734
+ }
735
+
736
+ /* 코드 블록 우상단의 언어 라벨 — `<code data-language="javascript">` 의
737
+ * data-language 가 있을 때만 표시. attr() 로 자동 동기화돼 별도 JS 갱신
738
+ * 불필요. 사용자가 드롭다운으로 언어 변경 시 CSS 가 즉시 반영. */
739
+ .aw-content > pre > code[data-language]:not([data-language=""])::before {
740
+ content: attr(data-language);
741
+ position: absolute;
742
+ top: 6px;
743
+ right: 10px;
744
+ font-size: 10px;
745
+ font-weight: 600;
746
+ letter-spacing: 0.06em;
747
+ text-transform: uppercase;
748
+ color: var(--aw-fg-muted);
749
+ background: color-mix(in srgb, var(--aw-fg) 10%, transparent);
750
+ padding: 2px 8px;
751
+ border-radius: 4px;
752
+ pointer-events: none;
753
+ user-select: none;
754
+ }
755
+
756
+ .aw-content code {
757
+ font-family: var(--aw-font-mono);
758
+ font-size: 0.9em;
759
+ background-color: color-mix(in srgb, var(--aw-fg) 8%, transparent);
760
+ padding: 0.15em 0.4em;
761
+ border-radius: 3px;
762
+ }
763
+ .aw-content pre code {
764
+ background: transparent;
765
+ padding: 0;
766
+ }
767
+
768
+ /* ─── Syntax highlight 토큰 색 (자체 highlight.ts 출력) ──────────── */
769
+ .aw-content pre code .hl-keyword { color: #c678dd; font-weight: 600; }
770
+ .aw-content pre code .hl-string { color: #98c379; }
771
+ .aw-content pre code .hl-number { color: #d19a66; }
772
+ .aw-content pre code .hl-comment { color: var(--aw-fg-muted); font-style: italic; }
773
+ .aw-content pre code .hl-fn { color: #61afef; }
774
+ .aw-content pre code .hl-builtin { color: #56b6c2; }
775
+ .aw-content pre code .hl-tag { color: #e06c75; }
776
+ .aw-content pre code .hl-attr { color: #d19a66; }
777
+ .aw-content pre code .hl-prop { color: #61afef; }
778
+ .aw-content pre code .hl-punct { color: var(--aw-fg-muted); }
779
+
780
+ /* light 테마 보정 */
781
+ .aeriz-wysiwyg[data-theme='light'] .aw-content pre code .hl-keyword { color: #a626a4; }
782
+ .aeriz-wysiwyg[data-theme='light'] .aw-content pre code .hl-string { color: #50a14f; }
783
+ .aeriz-wysiwyg[data-theme='light'] .aw-content pre code .hl-number { color: #986801; }
784
+ .aeriz-wysiwyg[data-theme='light'] .aw-content pre code .hl-fn { color: #4078f2; }
785
+ .aeriz-wysiwyg[data-theme='light'] .aw-content pre code .hl-builtin { color: #0184bc; }
786
+ .aeriz-wysiwyg[data-theme='light'] .aw-content pre code .hl-tag { color: #e45649; }
787
+ .aeriz-wysiwyg[data-theme='light'] .aw-content pre code .hl-prop { color: #4078f2; }
788
+
789
+ .aw-content ul,
790
+ .aw-content ol {
791
+ margin: 0.6em 0;
792
+ padding-left: 1.6em;
793
+ }
794
+
795
+ .aw-content li {
796
+ margin: 0.2em 0;
797
+ }
798
+
799
+ /* ─── 체크리스트 ─────────────────────────────────────────────────────
800
+ 체크박스는 `<input>` 이 아니라 `<li>` 의 ::before 다. contentEditable 안의
801
+ 폼 요소는 캐럿이 들어가고 서버 sanitizer 가 지우는데, ::before 는 둘 다
802
+ 해당이 없다. 완료 여부는 `data-checked` 한 글자로 남는다. */
803
+ .aw-content ul.aw-task-list {
804
+ list-style: none;
805
+ padding-left: 1.9em;
806
+ }
807
+ .aw-content ul.aw-task-list > li {
808
+ position: relative;
809
+ }
810
+ .aw-content ul.aw-task-list > li::before {
811
+ content: '';
812
+ position: absolute;
813
+ left: -1.5em;
814
+ top: 0.28em;
815
+ width: 15px;
816
+ height: 15px;
817
+ border: 1.5px solid var(--aw-fg-muted);
818
+ border-radius: 4px;
819
+ background: transparent;
820
+ cursor: pointer;
821
+ box-sizing: border-box;
822
+ transition:
823
+ background-color var(--aw-dur-fast) var(--aw-ease-out),
824
+ border-color var(--aw-dur-fast) var(--aw-ease-out),
825
+ transform var(--aw-dur-base) var(--aw-ease-spring);
826
+ }
827
+ .aw-content ul.aw-task-list > li:hover::before {
828
+ border-color: var(--aw-primary);
829
+ transform: scale(1.1);
830
+ }
831
+ /* 체크 표시는 두 번째 pseudo 로 그린다 — 네모와 따로 움직여야 "찍히는"
832
+ 느낌이 난다. 회전한 ㄴ 자 두 획이 체크 모양이다. */
833
+ .aw-content ul.aw-task-list > li::after {
834
+ content: '';
835
+ position: absolute;
836
+ left: -1.16em;
837
+ top: 0.42em;
838
+ width: 4px;
839
+ height: 8px;
840
+ border: solid var(--aw-primary-fg);
841
+ border-width: 0 2px 2px 0;
842
+ transform: rotate(45deg) scale(0.4);
843
+ opacity: 0;
844
+ pointer-events: none;
845
+ transition:
846
+ opacity var(--aw-dur-fast) var(--aw-ease-out),
847
+ transform var(--aw-dur-base) var(--aw-ease-spring);
848
+ }
849
+ .aw-content ul.aw-task-list > li[data-checked='true']::before {
850
+ background: var(--aw-primary);
851
+ border-color: var(--aw-primary);
852
+ }
853
+ .aw-content ul.aw-task-list > li[data-checked='true']::after {
854
+ opacity: 1;
855
+ transform: rotate(45deg) scale(1);
856
+ }
857
+ /* 완료한 항목은 한 걸음 물러난다. 지우지는 않는다 — 무엇을 끝냈는지도
858
+ 목록의 일부다. */
859
+ .aw-content ul.aw-task-list > li[data-checked='true'] {
860
+ color: var(--aw-fg-muted);
861
+ text-decoration: line-through;
862
+ text-decoration-color: color-mix(in srgb, var(--aw-fg-muted) 60%, transparent);
863
+ }
864
+
865
+ .aw-content a {
866
+ color: var(--aw-primary);
867
+ text-decoration: underline;
868
+ }
869
+
870
+ .aw-content img {
871
+ max-width: 100%;
872
+ height: auto;
873
+ border-radius: var(--aw-radius-sm);
874
+ margin: 0.5em 0;
875
+ }
876
+
877
+ .aw-content hr {
878
+ border: none;
879
+ border-top: 1px solid var(--aw-border);
880
+ margin: 1.5em 0;
881
+ }
882
+
883
+ /* ─── 유튜브 임베드 (16:9 반응형 컨테이너) ──────────────────── */
884
+ .aw-content > div.aw-youtube-embed {
885
+ position: relative;
886
+ width: 100%;
887
+ aspect-ratio: 16 / 9;
888
+ margin: 1em 0;
889
+ border-radius: 8px;
890
+ /* overflow: hidden; */
891
+ background: #000;
892
+ border: 1px solid var(--aw-border);
893
+ }
894
+ .aw-content > div.aw-youtube-embed iframe {
895
+ position: absolute;
896
+ inset: 0;
897
+ width: 100%;
898
+ height: 100%;
899
+ border: 0;
900
+ display: block;
901
+ }
902
+
903
+ /* ─── 파일 첨부 (다운로드 카드) ──────────────────── */
904
+ .aw-content > a.aw-file-attachment {
905
+ display: flex;
906
+ align-items: center;
907
+ gap: 12px;
908
+ margin: 1em 0;
909
+ padding: 10px 14px;
910
+ border: 1px solid var(--aw-border);
911
+ border-radius: 8px;
912
+ background: var(--aw-bg);
913
+ color: var(--aw-fg);
914
+ text-decoration: none;
915
+ transition: background-color 0.08s, border-color 0.08s;
916
+ cursor: pointer;
917
+ user-select: none;
918
+ }
919
+ .aw-content > a.aw-file-attachment:hover {
920
+ background: color-mix(in srgb, var(--aw-fg) 5%, var(--aw-bg));
921
+ border-color: color-mix(in srgb, var(--aw-primary) 40%, var(--aw-border));
922
+ }
923
+ .aw-content > a.aw-file-attachment .aw-file-icon {
924
+ flex: 0 0 auto;
925
+ display: inline-flex;
926
+ width: 36px;
927
+ height: 36px;
928
+ align-items: center;
929
+ justify-content: center;
930
+ border-radius: 6px;
931
+ background: color-mix(in srgb, var(--aw-primary) 12%, transparent);
932
+ color: var(--aw-primary);
933
+ }
934
+ .aw-content > a.aw-file-attachment .aw-file-icon svg {
935
+ width: 20px;
936
+ height: 20px;
937
+ }
938
+ .aw-content > a.aw-file-attachment .aw-file-meta {
939
+ flex: 1 1 auto;
940
+ min-width: 0;
941
+ display: flex;
942
+ flex-direction: column;
943
+ gap: 2px;
944
+ }
945
+ .aw-content > a.aw-file-attachment .aw-file-name {
946
+ font-weight: 600;
947
+ font-size: 0.95rem;
948
+ color: var(--aw-fg);
949
+ word-break: break-all;
950
+ line-height: 1.3;
951
+ }
952
+ .aw-content > a.aw-file-attachment .aw-file-info {
953
+ font-size: 0.75rem;
954
+ color: var(--aw-fg-muted);
955
+ font-family: var(--aw-mono, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);
956
+ letter-spacing: 0.02em;
957
+ }
958
+ .aw-content > a.aw-file-attachment .aw-file-action {
959
+ flex: 0 0 auto;
960
+ display: inline-flex;
961
+ width: 32px;
962
+ height: 32px;
963
+ align-items: center;
964
+ justify-content: center;
965
+ color: var(--aw-fg-muted);
966
+ }
967
+ .aw-content > a.aw-file-attachment .aw-file-action svg {
968
+ width: 18px;
969
+ height: 18px;
970
+ }
971
+ .aw-content > a.aw-file-attachment:hover .aw-file-action {
972
+ color: var(--aw-primary);
973
+ }
974
+
975
+ /* ─── 링크 카드 (Open Graph 스타일 미리보기) ──────────────────── */
976
+ .aw-content > a.aw-link-card {
977
+ display: flex;
978
+ align-items: stretch;
979
+ gap: 14px;
980
+ text-decoration: none;
981
+ margin: 1em 0;
982
+ padding: 0;
983
+ border: 1px solid var(--aw-border);
984
+ border-radius: 8px;
985
+ background-color: color-mix(in srgb, var(--aw-primary) 5%, var(--aw-bg));
986
+ color: var(--aw-fg);
987
+ overflow: hidden;
988
+ transition: border-color 0.12s, background-color 0.12s;
989
+ }
990
+ .aw-content > a.aw-link-card:hover {
991
+ border-color: var(--aw-primary);
992
+ background-color: color-mix(in srgb, var(--aw-primary) 9%, var(--aw-bg));
993
+ }
994
+
995
+ /* 좌측 og:image 썸네일 — 있을 때만. */
996
+ .aw-content > a.aw-link-card > .aw-link-card-image {
997
+ flex: 0 0 auto;
998
+ width: 120px;
999
+ height: auto;
1000
+ object-fit: cover;
1001
+ align-self: stretch;
1002
+ background: color-mix(in srgb, var(--aw-fg) 5%, var(--aw-bg));
1003
+ }
1004
+
1005
+ /* 우측 본문 — title / desc / url 행. */
1006
+ .aw-content > a.aw-link-card > .aw-link-card-body {
1007
+ flex: 1 1 auto;
1008
+ min-width: 0;
1009
+ display: flex;
1010
+ flex-direction: column;
1011
+ gap: 4px;
1012
+ padding: 12px 14px;
1013
+ }
1014
+ .aw-content > a.aw-link-card .aw-link-card-title {
1015
+ font-weight: 600;
1016
+ font-size: 1rem;
1017
+ line-height: 1.3;
1018
+ color: var(--aw-fg);
1019
+ display: -webkit-box;
1020
+ -webkit-line-clamp: 2;
1021
+ -webkit-box-orient: vertical;
1022
+ overflow: hidden;
1023
+ }
1024
+ .aw-content > a.aw-link-card .aw-link-card-desc {
1025
+ font-size: 0.85rem;
1026
+ line-height: 1.4;
1027
+ color: var(--aw-fg-muted);
1028
+ display: -webkit-box;
1029
+ -webkit-line-clamp: 2;
1030
+ -webkit-box-orient: vertical;
1031
+ overflow: hidden;
1032
+ }
1033
+ .aw-content > a.aw-link-card .aw-link-card-url {
1034
+ display: flex;
1035
+ align-items: center;
1036
+ gap: 6px;
1037
+ margin-top: 2px;
1038
+ font-size: 0.78rem;
1039
+ color: var(--aw-primary);
1040
+ font-family: var(--aw-font-mono);
1041
+ word-break: break-all;
1042
+ }
1043
+ .aw-content > a.aw-link-card .aw-link-card-favicon {
1044
+ flex: 0 0 auto;
1045
+ width: 14px;
1046
+ height: 14px;
1047
+ border-radius: 2px;
1048
+ background: transparent;
1049
+ }
1050
+ .aw-content > a.aw-link-card .aw-link-card-url-text {
1051
+ flex: 1 1 auto;
1052
+ min-width: 0;
1053
+ overflow: hidden;
1054
+ text-overflow: ellipsis;
1055
+ white-space: nowrap;
1056
+ }
1057
+
1058
+ .aw-content table {
1059
+ border-collapse: collapse;
1060
+ width: 100%;
1061
+ margin: 1em 0;
1062
+ }
1063
+ .aw-content table td,
1064
+ .aw-content table th {
1065
+ border: 1px solid var(--aw-border);
1066
+ padding: 8px 12px;
1067
+ vertical-align: top;
1068
+ min-width: 40px;
1069
+ /* td/th 의 inline background-color 가 overflow 자식에 가려지지 않도록 */
1070
+ background-clip: padding-box;
1071
+ }
1072
+ .aw-content table th {
1073
+ background-color: color-mix(in srgb, var(--aw-fg) 5%, var(--aw-bg));
1074
+ font-weight: 600;
1075
+ }
1076
+
1077
+ .aw-content table td,
1078
+ .aw-content table th {
1079
+ transition: background-color var(--aw-dur-fast) var(--aw-ease-out);
1080
+ }
1081
+
1082
+ /* 커스텀 드래그-셀렉션으로 마킹된 표 셀 — 다중 선택 시각 강조. */
1083
+ .aw-content table td.aw-cell-selected,
1084
+ .aw-content table th.aw-cell-selected {
1085
+ outline: 2px solid var(--aw-primary);
1086
+ outline-offset: -2px;
1087
+ background-color: color-mix(in srgb, var(--aw-primary) 18%, transparent) !important;
1088
+ }
1089
+ .aeriz-wysiwyg-viewer .aw-cell-selected {
1090
+ outline: none;
1091
+ background-color: revert !important;
1092
+ }
1093
+
1094
+ /* ─── 리사이즈 핸들 + 정렬 컨트롤 (이미지·유튜브) ──────────────────── */
1095
+ .aw-content > figure,
1096
+ .aw-content > div.aw-youtube-embed {
1097
+ position: relative;
1098
+ /* 인라인 width 가 본문보다 커도 넘치지 않게 막는다. 드래그 리사이즈는
1099
+ JS 에서 본문 폭으로 상한을 두지만, 외부에서 들어온 값(HTML 가져오기,
1100
+ 예전에 더 넓은 화면에서 저장된 스냅샷)까지 막아 주는 건 이 규칙이다.
1101
+ 이게 없으면 본문이 가로로 넓어지면서 에디터 전체가 늘어난다. */
1102
+ max-width: 100%;
1103
+ }
1104
+
1105
+ /* figure 의 좌우 여백을 0 으로 명시한다.
1106
+ 브라우저 UA 기본값은 `figure { margin: 1em 40px }` 인데, getHTML() 이
1107
+ 베이크하는 portable 스타일은 `margin: 1em 0` 이다. 그대로 두면 에디터에서는
1108
+ 80px 좁게 보이고 내보낸 HTML 에서는 넓게 보이는 WYSIWYG 불일치가 생긴다. */
1109
+ .aw-content > figure {
1110
+ margin: 1em 0;
1111
+ }
1112
+
1113
+ /* ─── 이미지 캡션 ────────────────────────────────────────────────────
1114
+ 그림에 딸린 설명. 본문보다 한 단계 작고 흐리게 — 그림을 읽고 나서 눈이
1115
+ 내려오는 자리다. 그림 정렬(가운데/오른쪽) 을 따라가도록 figure 의
1116
+ text-align 을 그대로 상속받는다. */
1117
+ .aw-content > figure > figcaption {
1118
+ margin-top: 0.5em;
1119
+ font-size: 0.85em;
1120
+ line-height: 1.5;
1121
+ color: var(--aw-fg-muted);
1122
+ outline: none;
1123
+ }
1124
+ /* 비어 있는 동안만 안내를 띄운다. 캡션은 캐럿이 떠나면 사라지므로, 이
1125
+ 안내가 보이는 시간은 "지금 쓰면 된다" 는 순간뿐이다. */
1126
+ .aw-content > figure > figcaption:empty::before {
1127
+ content: var(--aw-caption-hint, '');
1128
+ color: var(--aw-fg-muted);
1129
+ opacity: 0.5;
1130
+ pointer-events: none;
1131
+ }
1132
+ .aeriz-wysiwyg-viewer .aw-content > figure > figcaption:empty {
1133
+ display: none;
1134
+ }
1135
+
1136
+ /* 좌상단 정렬 컨트롤 — 좌/중/우 3 버튼. hover 시 위에서 내려오며 fade-in. */
1137
+ .aw-block-align-controls {
1138
+ position: absolute;
1139
+ top: 6px;
1140
+ left: 6px;
1141
+ display: flex;
1142
+ gap: 1px;
1143
+ background: rgba(0, 0, 0, 0.55);
1144
+ backdrop-filter: blur(6px);
1145
+ border-radius: 6px;
1146
+ padding: 2px;
1147
+ z-index: 11;
1148
+ opacity: 0;
1149
+ transform: translateY(-4px);
1150
+ transition:
1151
+ opacity var(--aw-dur-base) var(--aw-ease-out),
1152
+ transform var(--aw-dur-base) var(--aw-ease-out);
1153
+ user-select: none;
1154
+ }
1155
+ .aw-content > figure:hover .aw-block-align-controls,
1156
+ .aw-content > div.aw-youtube-embed:hover .aw-block-align-controls,
1157
+ .aw-content > figure:focus-within .aw-block-align-controls,
1158
+ .aw-resizing .aw-block-align-controls {
1159
+ opacity: 1;
1160
+ transform: none;
1161
+ }
1162
+ /* 정렬 버튼과 캡션 버튼 사이 구분선 — 하는 일이 다르다는 표시. */
1163
+ .aw-block-ctl-divider {
1164
+ width: 1px;
1165
+ align-self: stretch;
1166
+ margin: 2px 2px;
1167
+ background: rgba(255, 255, 255, 0.25);
1168
+ }
1169
+
1170
+ .aw-block-align-btn,
1171
+ .aw-block-caption-btn {
1172
+ width: 22px;
1173
+ height: 22px;
1174
+ display: inline-flex;
1175
+ align-items: center;
1176
+ justify-content: center;
1177
+ background: transparent;
1178
+ border: none;
1179
+ color: #fff;
1180
+ cursor: pointer;
1181
+ border-radius: 4px;
1182
+ padding: 0;
1183
+ transition:
1184
+ background-color var(--aw-dur-fast) var(--aw-ease-out),
1185
+ transform var(--aw-dur-fast) var(--aw-ease-out);
1186
+ }
1187
+ .aw-block-align-btn:hover,
1188
+ .aw-block-caption-btn:hover {
1189
+ background: rgba(255, 255, 255, 0.18);
1190
+ }
1191
+ .aw-block-align-btn:active,
1192
+ .aw-block-caption-btn:active {
1193
+ transform: scale(0.9);
1194
+ }
1195
+ .aw-block-align-btn:focus-visible,
1196
+ .aw-block-caption-btn:focus-visible {
1197
+ outline: 2px solid #fff;
1198
+ outline-offset: -2px;
1199
+ }
1200
+ .aw-block-align-btn svg,
1201
+ .aw-block-caption-btn svg {
1202
+ width: 14px;
1203
+ height: 14px;
1204
+ }
1205
+ .aw-resize-handle {
1206
+ position: absolute;
1207
+ right: -1px;
1208
+ bottom: -1px;
1209
+ width: 14px;
1210
+ height: 14px;
1211
+ background: var(--aw-primary);
1212
+ border: 2px solid var(--aw-bg);
1213
+ border-radius: 4px;
1214
+ cursor: nwse-resize;
1215
+ z-index: 10;
1216
+ user-select: none;
1217
+ opacity: 0;
1218
+ transform: scale(0.7);
1219
+ transition:
1220
+ opacity var(--aw-dur-base) var(--aw-ease-out),
1221
+ transform var(--aw-dur-base) var(--aw-ease-spring),
1222
+ box-shadow var(--aw-dur-base) var(--aw-ease-out);
1223
+ }
1224
+ /* 실제로 잡히는 면적은 보이는 것보다 넓다 — 14px 짜리 점을 정확히 겨냥해야
1225
+ 하면 리사이즈가 스트레스가 된다. */
1226
+ .aw-resize-handle::before {
1227
+ content: '';
1228
+ position: absolute;
1229
+ inset: -7px;
1230
+ }
1231
+ .aw-content > figure:hover .aw-resize-handle,
1232
+ .aw-content > div.aw-youtube-embed:hover .aw-resize-handle,
1233
+ .aw-resizing .aw-resize-handle {
1234
+ opacity: 1;
1235
+ transform: none;
1236
+ }
1237
+ .aw-resize-handle:hover {
1238
+ transform: scale(1.18);
1239
+ box-shadow: 0 0 0 4px color-mix(in srgb, var(--aw-primary) 22%, transparent);
1240
+ }
1241
+ /* 크기를 조절하는 동안은 그 블록만 또렷하게 남기고 나머지 UI 는 조용해진다. */
1242
+ .aw-resizing .aw-resize-handle {
1243
+ transform: scale(1.18);
1244
+ box-shadow: 0 0 0 5px color-mix(in srgb, var(--aw-primary) 28%, transparent);
1245
+ }
1246
+ .aw-resizing {
1247
+ outline: 1px solid color-mix(in srgb, var(--aw-primary) 60%, transparent);
1248
+ outline-offset: 2px;
1249
+ }
1250
+
1251
+ /* 유튜브 임베드는 iframe 안에 자체 워터마크/컨트롤이 있어 코너에 핸들을 두면
1252
+ * 클릭이 충돌한다. embed 안쪽 우하단에 약간 들여서 배치 — YouTube 자체 UI
1253
+ * 영역과 약간 떨어진 위치. */
1254
+ .aw-content > div.aw-youtube-embed > .aw-resize-handle {
1255
+ right: 0px;
1256
+ bottom: -15px;
1257
+ }
1258
+ .aw-resizing {
1259
+ user-select: none;
1260
+ }
1261
+
1262
+ /* 뷰어(읽기 전용) 모드에서는 편집용 컨트롤(리사이즈 핸들 / 정렬 컨트롤) 을
1263
+ * 모두 숨긴다. AerizViewer.root 의 className 'aeriz-wysiwyg-viewer' 를 타겟. */
1264
+ .aeriz-wysiwyg-viewer .aw-resize-handle,
1265
+ .aeriz-wysiwyg-viewer .aw-block-align-controls {
1266
+ display: none !important;
1267
+ }
1268
+
1269
+ /* ─── 모션 민감 사용자 ───────────────────────────────────────────────
1270
+ 움직임은 전부 걷어내되 **상태는 그대로 보이게** 한다. transition 만 0 으로
1271
+ 만들면 애니메이션으로 나타나던 요소가 영영 안 보이는 사고가 나므로,
1272
+ 등장 애니메이션은 끝 상태를 직접 지정한다. */
1273
+ @media (prefers-reduced-motion: reduce) {
1274
+ .aeriz-wysiwyg *,
1275
+ .aeriz-wysiwyg *::before,
1276
+ .aeriz-wysiwyg *::after {
1277
+ transition-duration: 1ms !important;
1278
+ animation-duration: 1ms !important;
1279
+ animation-iteration-count: 1 !important;
1280
+ }
1281
+ .aw-content > [data-block-focused]::before {
1282
+ transform: none;
1283
+ }
1284
+ .aw-block-add:hover svg,
1285
+ .aw-block-label-pill:hover,
1286
+ .aw-block-handle:active,
1287
+ .aw-block-add:active,
1288
+ .aw-block-align-btn:active {
1289
+ transform: none;
1290
+ }
1291
+ }
1292
+
1293
+ /* ─── 문서 도구: 찾기 · 바꾸기 / 소스 보기 / 전체 화면 ────────────────── */
1294
+
1295
+ .aw-find-panel {
1296
+ display: flex;
1297
+ flex-direction: column;
1298
+ gap: 6px;
1299
+ padding: 8px 12px;
1300
+ border-bottom: 1px solid var(--aw-border);
1301
+ background: var(--aw-toolbar-bg);
1302
+ font-size: 13px;
1303
+ }
1304
+ .aw-find-panel[hidden] { display: none; }
1305
+ .aw-find-row { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
1306
+ .aw-find-input {
1307
+ flex: 1 1 180px;
1308
+ min-width: 0;
1309
+ height: 30px;
1310
+ padding: 0 8px;
1311
+ border: 1px solid var(--aw-border);
1312
+ border-radius: var(--aw-radius-sm);
1313
+ background: var(--aw-bg);
1314
+ color: var(--aw-fg);
1315
+ font: inherit;
1316
+ }
1317
+ .aw-find-input:focus { outline: 2px solid var(--aw-primary); outline-offset: -1px; }
1318
+ .aw-find-btn {
1319
+ height: 30px;
1320
+ padding: 0 10px;
1321
+ border: 1px solid var(--aw-border);
1322
+ border-radius: var(--aw-radius-sm);
1323
+ background: var(--aw-bg);
1324
+ color: var(--aw-fg);
1325
+ font: inherit;
1326
+ cursor: pointer;
1327
+ }
1328
+ .aw-find-btn:hover { background: var(--aw-btn-hover-bg); }
1329
+ .aw-find-btn:focus-visible { outline: 2px solid var(--aw-primary); outline-offset: 1px; }
1330
+ .aw-find-status { color: var(--aw-fg-muted); min-width: 5em; font-variant-numeric: tabular-nums; }
1331
+ .aw-find-case { display: inline-flex; align-items: center; gap: 4px; color: var(--aw-fg-muted); }
1332
+ .aw-find-close { margin-left: auto; }
1333
+
1334
+ /* 찾은 곳 표시 — CSS Custom Highlight API. 문서 DOM 을 건드리지 않는다. */
1335
+ ::highlight(aw-find) { background-color: rgba(250, 204, 21, 0.45); }
1336
+ ::highlight(aw-find-current) { background-color: rgba(249, 115, 22, 0.75); color: #000; }
1337
+
1338
+ .aw-source {
1339
+ display: block;
1340
+ box-sizing: border-box;
1341
+ width: 100%;
1342
+ border: 0;
1343
+ padding: 16px 24px;
1344
+ resize: vertical;
1345
+ background: var(--aw-bg);
1346
+ color: var(--aw-fg);
1347
+ font: 13px/1.6 var(--aw-font-mono);
1348
+ white-space: pre-wrap;
1349
+ tab-size: 2;
1350
+ }
1351
+ .aw-source:focus { outline: none; }
1352
+
1353
+ .aeriz-wysiwyg.aw-fullscreen {
1354
+ position: fixed;
1355
+ inset: 0;
1356
+ z-index: var(--aw-z-dialog);
1357
+ border-radius: 0;
1358
+ height: 100vh;
1359
+ max-height: none;
1360
+ }
1361
+ .aeriz-wysiwyg.aw-fullscreen .aw-content-wrap { flex: 1 1 auto; overflow: auto; }
1362
+
1363
+ /* 툴바 키보드 이동(Alt+F10 · 화살표) 의 위치 표시. */
1364
+ .aw-toolbar-btn:focus-visible { outline: 2px solid var(--aw-primary); outline-offset: 1px; }
1365
+ .aw-toolbar-btn:disabled { opacity: 0.35; cursor: default; }
1366
+
1367
+ @media print {
1368
+ .aeriz-wysiwyg .aw-toolbar,
1369
+ .aeriz-wysiwyg .aw-status-bar,
1370
+ .aeriz-wysiwyg .aw-find-panel { display: none !important; }
1371
+ }
1372
+ .aw-toolbar {
1373
+ display: flex;
1374
+ align-items: center;
1375
+ flex-wrap: wrap;
1376
+ gap: 2px;
1377
+ padding: 6px 8px;
1378
+ background-color: var(--aw-toolbar-bg);
1379
+ border-bottom: 1px solid var(--aw-border);
1380
+ color: var(--aw-toolbar-fg);
1381
+ z-index: var(--aw-z-toolbar);
1382
+ }
1383
+
1384
+ .aw-toolbar-sticky {
1385
+ position: sticky;
1386
+ top: 0;
1387
+ }
1388
+
1389
+ .aw-toolbar-group {
1390
+ display: flex;
1391
+ align-items: center;
1392
+ gap: 1px;
1393
+ padding: 0 4px;
1394
+ border-right: 1px solid var(--aw-divider);
1395
+ }
1396
+
1397
+ .aw-toolbar-group:last-child {
1398
+ border-right: none;
1399
+ }
1400
+
1401
+ .aw-toolbar-group:empty {
1402
+ display: none;
1403
+ }
1404
+
1405
+ .aw-toolbar-btn {
1406
+ display: inline-flex;
1407
+ align-items: center;
1408
+ justify-content: center;
1409
+ height: 32px;
1410
+ min-width: 32px;
1411
+ padding: 0 6px;
1412
+ background: transparent;
1413
+ border: 1px solid transparent;
1414
+ border-radius: var(--aw-radius-sm);
1415
+ cursor: pointer;
1416
+ color: var(--aw-toolbar-fg);
1417
+ position: relative;
1418
+ transition:
1419
+ background-color var(--aw-dur-fast) var(--aw-ease-out),
1420
+ color var(--aw-dur-fast) var(--aw-ease-out),
1421
+ border-color var(--aw-dur-fast) var(--aw-ease-out),
1422
+ transform var(--aw-dur-fast) var(--aw-ease-out);
1423
+ }
1424
+
1425
+ .aw-toolbar-btn:hover:not(:disabled) {
1426
+ background-color: var(--aw-btn-hover-bg);
1427
+ }
1428
+
1429
+ /* 누르는 순간의 반응 — 색보다 먼저 손끝에 닿는 신호. */
1430
+ .aw-toolbar-btn:active:not(:disabled) {
1431
+ transform: scale(0.94);
1432
+ }
1433
+
1434
+ .aw-toolbar-btn:focus-visible {
1435
+ outline: 2px solid var(--aw-primary);
1436
+ outline-offset: -1px;
1437
+ }
1438
+
1439
+ .aw-toolbar-btn:disabled {
1440
+ opacity: 0.4;
1441
+ cursor: not-allowed;
1442
+ }
1443
+
1444
+ .aw-toolbar-btn-active {
1445
+ background-color: var(--aw-btn-active-bg);
1446
+ color: var(--aw-btn-active-fg);
1447
+ }
1448
+
1449
+ .aw-toolbar-btn-active:hover:not(:disabled) {
1450
+ background-color: var(--aw-btn-active-bg);
1451
+ }
1452
+
1453
+ /* 켜진 항목 아래 2px 밑줄이 가운데에서 좌우로 펼쳐진다. 배경색만 바뀌면
1454
+ 어두운 테마에서 on/off 차이가 잘 안 읽힌다. */
1455
+ .aw-toolbar-btn::after {
1456
+ content: '';
1457
+ position: absolute;
1458
+ left: 6px;
1459
+ right: 6px;
1460
+ bottom: 3px;
1461
+ height: 2px;
1462
+ border-radius: 1px;
1463
+ background: currentColor;
1464
+ transform: scaleX(0);
1465
+ opacity: 0;
1466
+ transition:
1467
+ transform var(--aw-dur-base) var(--aw-ease-out),
1468
+ opacity var(--aw-dur-base) var(--aw-ease-out);
1469
+ }
1470
+ .aw-toolbar-btn-active::after {
1471
+ transform: scaleX(1);
1472
+ opacity: 0.8;
1473
+ }
1474
+
1475
+ /* 이 버튼에서 열린 드롭다운이 떠 있는 동안 — 셰브론이 뒤집혀 "다시 누르면
1476
+ 닫힌다" 를 알린다. */
1477
+ .aw-toolbar-btn.is-popover-open {
1478
+ background-color: var(--aw-btn-active-bg);
1479
+ color: var(--aw-btn-active-fg);
1480
+ }
1481
+ .aw-toolbar-btn .aw-toolbar-btn-chevron {
1482
+ transition: transform var(--aw-dur-base) var(--aw-ease-out), opacity var(--aw-dur-fast);
1483
+ }
1484
+ .aw-toolbar-btn.is-popover-open .aw-toolbar-btn-chevron {
1485
+ transform: rotate(180deg);
1486
+ opacity: 1;
1487
+ }
1488
+
1489
+ .aw-toolbar-btn-icon {
1490
+ display: inline-flex;
1491
+ width: 18px;
1492
+ height: 18px;
1493
+ }
1494
+
1495
+ .aw-toolbar-btn-icon svg {
1496
+ width: 100%;
1497
+ height: 100%;
1498
+ }
1499
+
1500
+ .aw-toolbar-btn-chevron {
1501
+ display: inline-flex;
1502
+ width: 12px;
1503
+ height: 12px;
1504
+ margin-left: 2px;
1505
+ opacity: 0.7;
1506
+ }
1507
+
1508
+ .aw-toolbar-btn-chevron svg {
1509
+ width: 100%;
1510
+ height: 100%;
1511
+ }
1512
+ /*
1513
+ * Dialog / popover 는 .aeriz-wysiwyg 의 자식이 아닌 document.body 에 attach
1514
+ * 되므로 에디터의 CSS 변수를 직접 받지 못한다. 대신 자기 자신에
1515
+ * data-theme 속성을 받아 아래의 변수 토큰을 분기한다. 호출자(에디터)는
1516
+ * dialog/popover 를 열 때 자신의 theme 을 전달한다.
1517
+ */
1518
+
1519
+ .aw-dialog-overlay,
1520
+ .aw-popover {
1521
+ --d-bg: #ffffff;
1522
+ --d-fg: #1a1a1a;
1523
+ --d-fg-muted: #6b7280;
1524
+ --d-border: #e5e7eb;
1525
+ --d-border-strong: #d1d5db;
1526
+ --d-input-bg: #ffffff;
1527
+ --d-input-fg: #1a1a1a;
1528
+ --d-footer-bg: #f9fafb;
1529
+ --d-hover-bg: #f3f4f6;
1530
+ --d-primary: #64c8ff;
1531
+ --d-primary-hover: #38bdf8;
1532
+ --d-primary-soft: rgba(100, 200, 255, 0.18);
1533
+ --d-on-primary: #050810;
1534
+ --d-overlay: rgba(0, 0, 0, 0.45);
1535
+ --d-mono: ui-monospace, SFMono-Regular, 'JetBrains Mono', Menlo, Consolas, monospace;
1536
+ /* 다이얼로그/팝오버는 body 에 붙어 에디터 토큰을 상속받지 못한다.
1537
+ 같은 값을 여기에도 둔다 — 두 표면의 모션이 어긋나면 한 제품처럼 안 느껴진다.
1538
+ (tokens.css 의 --aw-dur-* / --aw-ease-* 와 값이 같아야 한다.) */
1539
+ --aw-dur-fast: 90ms;
1540
+ --aw-dur-base: 160ms;
1541
+ --aw-dur-slow: 240ms;
1542
+ --aw-ease-out: cubic-bezier(0.22, 0.85, 0.3, 1);
1543
+ --aw-ease-spring: cubic-bezier(0.2, 1.3, 0.4, 1);
1544
+ }
1545
+
1546
+ .aw-dialog-overlay[data-theme='dark'],
1547
+ .aw-popover[data-theme='dark'] {
1548
+ --d-bg: #0a0e14;
1549
+ --d-fg: #e7eef5;
1550
+ --d-fg-muted: #76849b;
1551
+ --d-border: rgba(100, 200, 255, 0.16);
1552
+ --d-border-strong: rgba(100, 200, 255, 0.30);
1553
+ --d-input-bg: #0d1320;
1554
+ --d-input-fg: #e7eef5;
1555
+ --d-footer-bg: #05080d;
1556
+ --d-hover-bg: rgba(100, 200, 255, 0.10);
1557
+ --d-primary: #64c8ff;
1558
+ --d-primary-hover: #8ad6ff;
1559
+ --d-primary-soft: rgba(100, 200, 255, 0.22);
1560
+ --d-on-primary: #050810;
1561
+ --d-overlay: rgba(0, 0, 0, 0.65);
1562
+ }
1563
+
1564
+ .aw-dialog-overlay {
1565
+ position: fixed;
1566
+ inset: 0;
1567
+ background-color: var(--d-overlay);
1568
+ display: flex;
1569
+ align-items: center;
1570
+ justify-content: center;
1571
+ z-index: var(--aw-z-dialog, 1000);
1572
+ font-family: -apple-system, BlinkMacSystemFont, 'Apple SD Gothic Neo', 'Pretendard',
1573
+ 'Noto Sans KR', 'Malgun Gothic', sans-serif;
1574
+ animation: aw-fade-in 0.12s ease;
1575
+ }
1576
+
1577
+ @keyframes aw-fade-in { from { opacity: 0; } to { opacity: 1; } }
1578
+
1579
+ .aw-dialog {
1580
+ background: var(--d-bg);
1581
+ color: var(--d-fg);
1582
+ border-radius: 8px;
1583
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.45);
1584
+ width: 480px;
1585
+ max-width: 95vw;
1586
+ max-height: 85vh;
1587
+ display: flex;
1588
+ flex-direction: column;
1589
+ overflow: hidden;
1590
+ animation: aw-slide-up 0.18s ease;
1591
+ border: 1px solid var(--d-border);
1592
+ }
1593
+
1594
+ @keyframes aw-slide-up {
1595
+ from { transform: translateY(8px); opacity: 0; }
1596
+ to { transform: translateY(0); opacity: 1; }
1597
+ }
1598
+
1599
+ .aw-dialog-header {
1600
+ display: flex;
1601
+ align-items: center;
1602
+ justify-content: space-between;
1603
+ padding: 16px 20px;
1604
+ border-bottom: 1px solid var(--d-border);
1605
+ }
1606
+
1607
+ .aw-dialog-title {
1608
+ margin: 0;
1609
+ font-size: 16px;
1610
+ font-weight: 700;
1611
+ color: var(--d-fg);
1612
+ }
1613
+
1614
+ .aw-dialog-close {
1615
+ background: transparent;
1616
+ border: none;
1617
+ cursor: pointer;
1618
+ width: 28px; height: 28px;
1619
+ display: inline-flex; align-items: center; justify-content: center;
1620
+ border-radius: 4px;
1621
+ color: var(--d-fg-muted);
1622
+ transition: background-color 0.12s, color 0.12s;
1623
+ }
1624
+ .aw-dialog-close:hover { background-color: var(--d-hover-bg); color: var(--d-fg); }
1625
+ .aw-dialog-close svg { width: 18px; height: 18px; }
1626
+
1627
+ .aw-dialog-body {
1628
+ padding: 20px;
1629
+ overflow-y: auto;
1630
+ display: flex; flex-direction: column; gap: 14px;
1631
+ }
1632
+
1633
+ .aw-dialog-footer {
1634
+ padding: 12px 20px;
1635
+ border-top: 1px solid var(--d-border);
1636
+ display: flex; justify-content: flex-end; gap: 8px;
1637
+ background-color: var(--d-footer-bg);
1638
+ }
1639
+
1640
+ .aw-field { display: flex; flex-direction: column; gap: 6px; }
1641
+ .aw-field-label { font-size: 13px; font-weight: 600; color: var(--d-fg); }
1642
+
1643
+ .aw-input {
1644
+ width: 100%;
1645
+ padding: 8px 12px;
1646
+ font-size: 14px;
1647
+ font-family: inherit;
1648
+ border: 1px solid var(--d-border-strong);
1649
+ border-radius: 6px;
1650
+ outline: none;
1651
+ transition: border-color 0.12s, box-shadow 0.12s;
1652
+ color: var(--d-input-fg);
1653
+ background-color: var(--d-input-bg);
1654
+ }
1655
+ .aw-input:focus {
1656
+ border-color: var(--d-primary);
1657
+ box-shadow: 0 0 0 3px var(--d-primary-soft);
1658
+ }
1659
+
1660
+ .aw-textarea {
1661
+ resize: vertical;
1662
+ min-height: 120px;
1663
+ font-family: var(--d-mono);
1664
+ font-size: 12px;
1665
+ line-height: 1.5;
1666
+ }
1667
+
1668
+ .aw-checkbox {
1669
+ display: inline-flex; align-items: center; gap: 8px;
1670
+ font-size: 14px;
1671
+ color: var(--d-fg);
1672
+ cursor: pointer; user-select: none;
1673
+ }
1674
+ .aw-checkbox input { width: 16px; height: 16px; accent-color: var(--d-primary); }
1675
+
1676
+ .aw-btn {
1677
+ padding: 8px 16px;
1678
+ font-size: 14px;
1679
+ font-weight: 600;
1680
+ border-radius: 6px;
1681
+ border: 1px solid transparent;
1682
+ cursor: pointer;
1683
+ font-family: inherit;
1684
+ transition: background-color 0.12s, border-color 0.12s, color 0.12s;
1685
+ }
1686
+ .aw-btn-primary { background-color: var(--d-primary); color: var(--d-on-primary); }
1687
+ .aw-btn-primary:hover { background-color: var(--d-primary-hover); }
1688
+ .aw-btn-ghost {
1689
+ background-color: transparent;
1690
+ color: var(--d-fg);
1691
+ border-color: var(--d-border-strong);
1692
+ }
1693
+ .aw-btn-ghost:hover { background-color: var(--d-hover-bg); }
1694
+
1695
+ .aw-tabs {
1696
+ display: flex; gap: 4px;
1697
+ border-bottom: 1px solid var(--d-border);
1698
+ margin: -4px -4px 8px;
1699
+ }
1700
+ .aw-tab {
1701
+ padding: 8px 14px;
1702
+ background: transparent;
1703
+ border: none;
1704
+ border-bottom: 2px solid transparent;
1705
+ font-size: 14px;
1706
+ font-family: inherit;
1707
+ font-weight: 500;
1708
+ color: var(--d-fg-muted);
1709
+ cursor: pointer;
1710
+ transition: color 0.12s, border-color 0.12s;
1711
+ }
1712
+ .aw-tab:hover { color: var(--d-fg); }
1713
+ .aw-tab-active { color: var(--d-primary); border-bottom-color: var(--d-primary); }
1714
+ .aw-tab-disabled,
1715
+ .aw-tab[disabled] {
1716
+ opacity: 0.4;
1717
+ cursor: not-allowed;
1718
+ text-decoration: line-through;
1719
+ color: var(--d-fg-muted);
1720
+ }
1721
+ .aw-tab-disabled:hover,
1722
+ .aw-tab[disabled]:hover { color: var(--d-fg-muted); }
1723
+
1724
+ .aw-dialog-hint {
1725
+ font-size: 12px;
1726
+ color: var(--d-fg-muted);
1727
+ margin-top: -4px;
1728
+ }
1729
+
1730
+ .aw-pane-notice {
1731
+ padding: 24px 16px;
1732
+ text-align: center;
1733
+ font-size: 13px;
1734
+ color: var(--d-fg-muted);
1735
+ background-color: var(--d-input-bg);
1736
+ border: 1px dashed var(--d-border-strong);
1737
+ border-radius: 8px;
1738
+ }
1739
+
1740
+ .aw-pane { display: flex; flex-direction: column; gap: 12px; }
1741
+
1742
+ .aw-dropzone {
1743
+ display: flex; flex-direction: column;
1744
+ align-items: center; justify-content: center;
1745
+ gap: 10px;
1746
+ padding: 32px 20px;
1747
+ border: 2px dashed var(--d-border-strong);
1748
+ border-radius: 8px;
1749
+ cursor: pointer;
1750
+ text-align: center;
1751
+ color: var(--d-fg-muted);
1752
+ transition: border-color 0.12s, background-color 0.12s, color 0.12s;
1753
+ background-color: var(--d-input-bg);
1754
+ }
1755
+ .aw-dropzone:hover {
1756
+ border-color: var(--d-primary);
1757
+ background-color: var(--d-hover-bg);
1758
+ color: var(--d-primary);
1759
+ }
1760
+ .aw-dropzone svg { width: 36px; height: 36px; }
1761
+ .aw-dropzone-text { font-size: 13px; line-height: 1.5; }
1762
+ .aw-dropzone-loading { opacity: 0.6; pointer-events: none; }
1763
+
1764
+ .aw-preview {
1765
+ width: 100%; height: 160px;
1766
+ border-radius: 6px;
1767
+ background-size: cover; background-position: center;
1768
+ border: 1px solid var(--d-border);
1769
+ }
1770
+
1771
+ .aw-table-grid {
1772
+ display: grid;
1773
+ grid-template-columns: repeat(10, 22px);
1774
+ grid-template-rows: repeat(10, 22px);
1775
+ gap: 2px;
1776
+ padding: 8px;
1777
+ background-color: var(--d-footer-bg);
1778
+ border-radius: 6px;
1779
+ width: max-content;
1780
+ margin: 0 auto;
1781
+ }
1782
+ .aw-table-cell {
1783
+ background-color: var(--d-input-bg);
1784
+ border: 1px solid var(--d-border-strong);
1785
+ border-radius: 2px;
1786
+ cursor: pointer;
1787
+ transition: background-color 0.08s;
1788
+ }
1789
+ .aw-table-cell-active {
1790
+ background-color: var(--d-primary-soft);
1791
+ border-color: var(--d-primary);
1792
+ }
1793
+ .aw-table-status {
1794
+ text-align: center;
1795
+ font-size: 13px;
1796
+ color: var(--d-fg-muted);
1797
+ margin-top: 4px;
1798
+ }
1799
+
1800
+ /* ─── Popovers ──────────────────────────────────────────────── */
1801
+
1802
+ .aw-popover {
1803
+ background: var(--d-bg);
1804
+ color: var(--d-fg);
1805
+ border: 1px solid var(--d-border);
1806
+ border-radius: 8px;
1807
+ box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.35);
1808
+ padding: 8px;
1809
+ z-index: var(--aw-z-popover, 100);
1810
+ animation: aw-fade-in 0.1s ease;
1811
+ min-width: 160px;
1812
+ font-family: -apple-system, BlinkMacSystemFont, 'Apple SD Gothic Neo', 'Pretendard',
1813
+ 'Noto Sans KR', 'Malgun Gothic', sans-serif;
1814
+ }
1815
+
1816
+ .aw-color-popover {
1817
+ /* 8 swatch × 22 + 7 gap × 4 + 8 padding × 2 = 220px 컨텐츠에 맞게 */
1818
+ width: max-content;
1819
+ min-width: 220px;
1820
+ }
1821
+
1822
+ .aw-color-grid {
1823
+ display: grid;
1824
+ grid-template-columns: repeat(8, 22px);
1825
+ gap: 4px;
1826
+ justify-content: start;
1827
+ }
1828
+
1829
+ .aw-color-swatch {
1830
+ width: 22px; height: 22px;
1831
+ border-radius: 4px;
1832
+ border: 1px solid var(--d-border);
1833
+ cursor: pointer;
1834
+ padding: 0;
1835
+ transition:
1836
+ transform var(--aw-dur-base, 160ms) var(--aw-ease-spring, ease),
1837
+ box-shadow var(--aw-dur-fast, 90ms) ease;
1838
+ }
1839
+ /* 색은 손톱만 한 사각형이라, 확대만으로는 어느 칸에 있는지 확신이 안 선다.
1840
+ 같은 색 링을 한 겹 둘러 커서가 놓인 칸을 못 박아 준다. */
1841
+ .aw-color-swatch:hover {
1842
+ transform: scale(1.18);
1843
+ box-shadow: 0 0 0 2px var(--d-bg), 0 0 0 3px var(--d-primary);
1844
+ }
1845
+ .aw-color-swatch:active { transform: scale(1.02); }
1846
+ .aw-color-swatch:focus-visible {
1847
+ outline: none;
1848
+ box-shadow: 0 0 0 2px var(--d-bg), 0 0 0 3px var(--d-primary);
1849
+ }
1850
+
1851
+ .aw-color-section {
1852
+ font-size: 10px;
1853
+ letter-spacing: 0.08em;
1854
+ text-transform: uppercase;
1855
+ color: var(--d-fg-muted);
1856
+ font-weight: 700;
1857
+ padding: 6px 2px 4px;
1858
+ }
1859
+ .aw-color-section + .aw-color-grid { margin-bottom: 4px; }
1860
+
1861
+ .aw-popover-action {
1862
+ display: block;
1863
+ width: 100%;
1864
+ text-align: left;
1865
+ padding: 8px 10px;
1866
+ background: transparent;
1867
+ border: none;
1868
+ border-radius: 4px;
1869
+ cursor: pointer;
1870
+ font-size: 13px;
1871
+ color: var(--d-fg);
1872
+ font-family: inherit;
1873
+ margin-top: 4px;
1874
+ position: relative;
1875
+ }
1876
+ .aw-popover-action:hover { background-color: var(--d-hover-bg); }
1877
+
1878
+ .aw-popover-item {
1879
+ display: flex;
1880
+ align-items: center;
1881
+ gap: 8px;
1882
+ width: 100%;
1883
+ text-align: left;
1884
+ padding: 4px 8px;
1885
+ background: transparent;
1886
+ border: none;
1887
+ border-radius: 4px;
1888
+ cursor: pointer;
1889
+ color: var(--d-fg);
1890
+ font-family: inherit;
1891
+ font-size: 12.5px;
1892
+ transition:
1893
+ background-color var(--aw-dur-fast, 90ms) ease,
1894
+ color var(--aw-dur-fast, 90ms) ease;
1895
+ }
1896
+ .aw-popover-item:hover { background-color: var(--d-hover-bg); }
1897
+ .aw-popover-item:active { background-color: var(--d-primary-soft); }
1898
+ .aw-popover-item:disabled { opacity: 0.4; cursor: default; background: none; }
1899
+ .aw-popover-item:focus-visible {
1900
+ outline: 2px solid var(--d-primary);
1901
+ outline-offset: -2px;
1902
+ }
1903
+
1904
+ .aw-popover-item-glyph {
1905
+ display: inline-flex;
1906
+ width: 18px; height: 18px;
1907
+ align-items: center; justify-content: center;
1908
+ flex-shrink: 0;
1909
+ border-radius: 3px;
1910
+ background: var(--d-hover-bg);
1911
+ color: var(--d-fg-muted);
1912
+ font-family: var(--d-mono);
1913
+ font-size: 10px;
1914
+ font-weight: 700;
1915
+ transition:
1916
+ background-color var(--aw-dur-fast, 90ms) ease,
1917
+ color var(--aw-dur-fast, 90ms) ease,
1918
+ transform var(--aw-dur-base, 160ms) var(--aw-ease-spring, ease);
1919
+ }
1920
+ /* 항목을 훑을 때 기호만 살짝 커진다 — 행 전체가 움직이면 목록이 출렁이지만,
1921
+ 18px 짜리 기호 하나는 커서를 따라오는 느낌만 남기고 레이아웃을 흔들지 않는다. */
1922
+ .aw-popover-item:hover .aw-popover-item-glyph {
1923
+ background: var(--d-primary-soft);
1924
+ color: var(--d-primary);
1925
+ transform: scale(1.12);
1926
+ }
1927
+ .aw-popover-item-glyph svg { width: 11px; height: 11px; }
1928
+
1929
+ .aw-popover-item-label { flex: 1; min-width: 0; }
1930
+
1931
+ .aw-popover-item-icon {
1932
+ display: inline-flex;
1933
+ width: auto;
1934
+ padding: 8px;
1935
+ align-items: center; justify-content: center;
1936
+ gap: 0;
1937
+ }
1938
+ .aw-popover-item-icon svg { width: 18px; height: 18px; }
1939
+
1940
+ .aw-popover-divider {
1941
+ height: 1px;
1942
+ background: var(--d-border);
1943
+ margin: 4px 6px;
1944
+ }
1945
+
1946
+ /* 블록 유형 드롭다운 — 항목이 많아 세로로 길어지지 않도록 2 컬럼 그리드.
1947
+ * 섹션 구분 divider 는 grid-column 으로 두 컬럼을 모두 가로지른다. */
1948
+ .aw-block-type-popover {
1949
+ display: grid;
1950
+ grid-template-columns: repeat(2, minmax(150px, 1fr));
1951
+ column-gap: 2px;
1952
+ min-width: 320px;
1953
+ }
1954
+ .aw-block-type-popover .aw-popover-divider {
1955
+ grid-column: 1 / -1;
1956
+ }
1957
+
1958
+ /* 글꼴 드롭다운 — 라벨(작게) + '가A' 미리보기(글꼴 자체) 두 영역. */
1959
+ .aw-font-family-popover {
1960
+ min-width: 180px;
1961
+ max-height: 360px;
1962
+ overflow-y: auto;
1963
+ }
1964
+ .aw-font-family-item {
1965
+ padding: 4px 8px;
1966
+ display: flex;
1967
+ align-items: center;
1968
+ gap: 8px;
1969
+ }
1970
+ .aw-font-family-name {
1971
+ flex: 1 1 auto;
1972
+ min-width: 0;
1973
+ font-size: 11px;
1974
+ line-height: 1.4;
1975
+ color: var(--d-fg);
1976
+ white-space: nowrap;
1977
+ overflow: hidden;
1978
+ text-overflow: ellipsis;
1979
+ }
1980
+ .aw-font-family-preview {
1981
+ flex: 0 0 auto;
1982
+ font-size: 13px;
1983
+ line-height: 1.4;
1984
+ color: var(--d-fg-muted);
1985
+ letter-spacing: 0.05em;
1986
+ }
1987
+
1988
+ /* 표 편집 드롭다운 — 행/열 추가·삭제 텍스트 항목 */
1989
+ .aw-table-menu-popover {
1990
+ min-width: 160px;
1991
+ }
1992
+ .aw-table-menu-item {
1993
+ font-size: 12px;
1994
+ padding: 6px 10px;
1995
+ }
1996
+
1997
+ /* 글자 크기 드롭다운 — 한 줄당 px 라벨이 그 크기로 렌더되는 컴팩트 리스트 */
1998
+ .aw-font-size-popover {
1999
+ min-width: 80px;
2000
+ max-height: 320px;
2001
+ overflow-y: auto;
2002
+ }
2003
+ .aw-font-size-item {
2004
+ padding: 2px 10px;
2005
+ justify-content: space-between;
2006
+ }
2007
+ .aw-font-size-label {
2008
+ font-size: 11px;
2009
+ color: var(--d-fg);
2010
+ font-family: var(--d-mono);
2011
+ letter-spacing: 0.02em;
2012
+ }
2013
+
2014
+ .aw-popover-item-active {
2015
+ background-color: color-mix(in srgb, var(--d-primary) 14%, transparent);
2016
+ color: var(--d-primary);
2017
+ }
2018
+ .aw-popover-item-active::after {
2019
+ content: '✓';
2020
+ margin-left: auto;
2021
+ font-size: 11px;
2022
+ }
2023
+
2024
+ .aw-align-popover {
2025
+ display: flex;
2026
+ gap: 2px;
2027
+ min-width: auto;
2028
+ }
2029
+
2030
+ /* 표 삽입 드롭다운 — 호버에 따라 동적으로 늘어나는 셀 그리드 */
2031
+ .aw-table-popover {
2032
+ padding: 10px;
2033
+ min-width: auto;
2034
+ }
2035
+ .aw-table-picker-grid {
2036
+ display: grid;
2037
+ gap: 2px;
2038
+ margin-bottom: 6px;
2039
+ }
2040
+ .aw-table-picker-grid .aw-table-cell {
2041
+ width: 18px;
2042
+ height: 18px;
2043
+ background-color: var(--d-input-bg);
2044
+ border: 1px solid var(--d-border-strong);
2045
+ border-radius: 2px;
2046
+ cursor: pointer;
2047
+ transition: background-color 0.06s, border-color 0.06s;
2048
+ }
2049
+ /* 선택된 칸은 살짝 안으로 눌러 담긴다 — 몇 칸이 잡혔는지 숫자를 읽지 않고도
2050
+ 면적으로 먼저 보인다. */
2051
+ .aw-table-picker-grid .aw-table-cell.aw-table-cell-active {
2052
+ background-color: var(--d-primary-soft);
2053
+ border-color: var(--d-primary);
2054
+ transform: scale(0.9);
2055
+ }
2056
+ .aw-table-picker-grid .aw-table-cell {
2057
+ transition:
2058
+ background-color var(--aw-dur-fast, 90ms) ease,
2059
+ border-color var(--aw-dur-fast, 90ms) ease,
2060
+ transform var(--aw-dur-fast, 90ms) var(--aw-ease-out, ease);
2061
+ }
2062
+ .aw-table-popover .aw-table-status {
2063
+ text-align: center;
2064
+ font-size: 12px;
2065
+ color: var(--d-fg-muted);
2066
+ font-family: var(--d-mono);
2067
+ margin: 0;
2068
+ }
2069
+
2070
+ .aw-coming-soon-popover .aw-popover-section {
2071
+ font-size: 11px;
2072
+ letter-spacing: 0.08em;
2073
+ text-transform: uppercase;
2074
+ color: var(--d-fg-muted);
2075
+ padding: 4px 8px;
2076
+ font-weight: 700;
2077
+ }
2078
+ .aw-coming-soon-popover .aw-popover-empty {
2079
+ padding: 10px 8px;
2080
+ font-size: 13px;
2081
+ color: var(--d-fg-muted);
2082
+ }
2083
+
2084
+ /* ─── 특수 문자 표 ─────────────────────────────────────────────────── */
2085
+ .aw-special-char-popover { width: 296px; max-height: 360px; overflow-y: auto; }
2086
+ .aw-special-char-popover .aw-popover-section {
2087
+ font-size: 11px;
2088
+ font-weight: 700;
2089
+ color: var(--d-fg-muted);
2090
+ padding: 6px 4px 2px;
2091
+ }
2092
+ .aw-special-char-grid { display: grid; grid-template-columns: repeat(10, 1fr); gap: 2px; }
2093
+ .aw-special-char {
2094
+ height: 26px;
2095
+ border: 0;
2096
+ border-radius: 4px;
2097
+ background: transparent;
2098
+ color: var(--d-fg);
2099
+ font-size: 15px;
2100
+ line-height: 1;
2101
+ cursor: pointer;
2102
+ }
2103
+ .aw-special-char:hover { background: var(--d-hover-bg); }
2104
+ .aw-special-char:focus-visible { outline: 2px solid var(--d-primary, #03c75a); }