@aeriz/wysiwyg 1.0.2 → 1.0.4

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 (131) hide show
  1. package/README.md +634 -96
  2. package/angular/README.md +112 -0
  3. package/angular/aeriz-editor.directive.ts +243 -0
  4. package/angular/aeriz-wysiwyg.module.ts +14 -0
  5. package/angular/index.ts +5 -0
  6. package/dist/adapters/jquery.cjs +1 -0
  7. package/dist/adapters/jquery.js +91 -0
  8. package/dist/adapters/react.cjs +1 -0
  9. package/dist/adapters/react.js +66 -0
  10. package/dist/adapters/shared.cjs +1 -0
  11. package/dist/adapters/shared.js +87 -0
  12. package/dist/adapters/svelte.cjs +1 -0
  13. package/dist/adapters/svelte.js +29 -0
  14. package/dist/adapters/vue.cjs +1 -0
  15. package/dist/adapters/vue.js +110 -0
  16. package/dist/adapters/vue2.cjs +1 -0
  17. package/dist/adapters/vue2.js +117 -0
  18. package/dist/adapters/wc.cjs +1 -0
  19. package/dist/adapters/wc.js +207 -0
  20. package/dist/aeriz-wysiwyg-adapter.js +169 -0
  21. package/dist/aeriz-wysiwyg-adapter.min.js +1 -0
  22. package/dist/aeriz-wysiwyg-jquery.js +275 -0
  23. package/dist/aeriz-wysiwyg-jquery.min.js +1 -0
  24. package/dist/aeriz-wysiwyg-wc.js +401 -0
  25. package/dist/aeriz-wysiwyg-wc.min.js +1 -0
  26. package/dist/aeriz-wysiwyg.cjs +8759 -0
  27. package/dist/aeriz-wysiwyg.css +587 -163
  28. package/dist/aeriz-wysiwyg.esm.js +4249 -1381
  29. package/dist/aeriz-wysiwyg.esm.min.js +4456 -1995
  30. package/dist/aeriz-wysiwyg.js +4249 -1381
  31. package/dist/aeriz-wysiwyg.min.cjs +56 -0
  32. package/dist/aeriz-wysiwyg.min.css +1 -1
  33. package/dist/aeriz-wysiwyg.min.js +54 -21
  34. package/dist/server.cjs +34 -0
  35. package/dist/server.js +798 -0
  36. package/dist-types/adapters/jquery.d.ts +34 -0
  37. package/dist-types/adapters/jquery.d.ts.map +1 -0
  38. package/dist-types/adapters/react.d.ts +28 -0
  39. package/dist-types/adapters/react.d.ts.map +1 -0
  40. package/dist-types/adapters/shared.d.ts +83 -0
  41. package/dist-types/adapters/shared.d.ts.map +1 -0
  42. package/dist-types/adapters/svelte.d.ts +38 -0
  43. package/dist-types/adapters/svelte.d.ts.map +1 -0
  44. package/dist-types/adapters/vue.d.ts +189 -0
  45. package/dist-types/adapters/vue.d.ts.map +1 -0
  46. package/dist-types/adapters/vue2.d.ts +175 -0
  47. package/dist-types/adapters/vue2.d.ts.map +1 -0
  48. package/dist-types/adapters/wc.d.ts +96 -0
  49. package/dist-types/adapters/wc.d.ts.map +1 -0
  50. package/dist-types/cjs/adapters/jquery.d.cts +34 -0
  51. package/dist-types/cjs/adapters/react.d.cts +28 -0
  52. package/dist-types/cjs/adapters/shared.d.cts +83 -0
  53. package/dist-types/cjs/adapters/svelte.d.cts +38 -0
  54. package/dist-types/cjs/adapters/vue.d.cts +189 -0
  55. package/dist-types/cjs/adapters/vue2.d.cts +175 -0
  56. package/dist-types/cjs/adapters/wc.d.cts +96 -0
  57. package/dist-types/cjs/core/AerizEditor.d.cts +530 -0
  58. package/dist-types/cjs/core/AerizViewer.d.cts +61 -0
  59. package/dist-types/cjs/core/block-meta.d.cts +98 -0
  60. package/dist-types/cjs/core/block-policy.d.cts +43 -0
  61. package/dist-types/cjs/core/blocks.d.cts +411 -0
  62. package/dist-types/cjs/core/fonts.d.cts +60 -0
  63. package/dist-types/cjs/core/highlight.d.cts +44 -0
  64. package/dist-types/cjs/core/history.d.cts +86 -0
  65. package/dist-types/cjs/core/html-mini.d.cts +65 -0
  66. package/dist-types/cjs/core/i18n.d.cts +221 -0
  67. package/dist-types/cjs/core/import-html.d.cts +40 -0
  68. package/dist-types/cjs/core/import-markdown.d.cts +33 -0
  69. package/dist-types/cjs/core/input-rules.d.cts +46 -0
  70. package/dist-types/cjs/core/markdown-export.d.cts +30 -0
  71. package/dist-types/cjs/core/plain-text.d.cts +17 -0
  72. package/dist-types/cjs/core/render-string.d.cts +68 -0
  73. package/dist-types/cjs/core/selection.d.cts +46 -0
  74. package/dist-types/cjs/core/snapshot.d.cts +51 -0
  75. package/dist-types/cjs/core/table-ops.d.cts +30 -0
  76. package/dist-types/cjs/index.d.cts +22 -0
  77. package/dist-types/cjs/server/index.d.cts +37 -0
  78. package/dist-types/cjs/types/index.d.cts +61 -0
  79. package/dist-types/cjs/ui/Toolbar.d.cts +21 -0
  80. package/dist-types/cjs/ui/dialogs.d.cts +74 -0
  81. package/dist-types/cjs/ui/icons.d.cts +2 -0
  82. package/dist-types/cjs/ui/popovers.d.cts +70 -0
  83. package/dist-types/core/AerizEditor.d.ts +210 -17
  84. package/dist-types/core/AerizEditor.d.ts.map +1 -1
  85. package/dist-types/core/AerizViewer.d.ts +16 -4
  86. package/dist-types/core/AerizViewer.d.ts.map +1 -1
  87. package/dist-types/core/block-meta.d.ts +98 -0
  88. package/dist-types/core/block-meta.d.ts.map +1 -0
  89. package/dist-types/core/block-policy.d.ts +43 -0
  90. package/dist-types/core/block-policy.d.ts.map +1 -0
  91. package/dist-types/core/blocks.d.ts +109 -11
  92. package/dist-types/core/blocks.d.ts.map +1 -1
  93. package/dist-types/core/fonts.d.ts +11 -0
  94. package/dist-types/core/fonts.d.ts.map +1 -1
  95. package/dist-types/core/history.d.ts +86 -0
  96. package/dist-types/core/history.d.ts.map +1 -0
  97. package/dist-types/core/html-mini.d.ts +65 -0
  98. package/dist-types/core/html-mini.d.ts.map +1 -0
  99. package/dist-types/core/i18n.d.ts +12 -0
  100. package/dist-types/core/i18n.d.ts.map +1 -1
  101. package/dist-types/core/import-html.d.ts +1 -0
  102. package/dist-types/core/import-html.d.ts.map +1 -1
  103. package/dist-types/core/import-markdown.d.ts +0 -4
  104. package/dist-types/core/import-markdown.d.ts.map +1 -1
  105. package/dist-types/core/input-rules.d.ts +46 -0
  106. package/dist-types/core/input-rules.d.ts.map +1 -0
  107. package/dist-types/core/markdown-export.d.ts +30 -0
  108. package/dist-types/core/markdown-export.d.ts.map +1 -0
  109. package/dist-types/core/plain-text.d.ts +17 -0
  110. package/dist-types/core/plain-text.d.ts.map +1 -0
  111. package/dist-types/core/render-string.d.ts +68 -0
  112. package/dist-types/core/render-string.d.ts.map +1 -0
  113. package/dist-types/core/selection.d.ts +46 -0
  114. package/dist-types/core/selection.d.ts.map +1 -0
  115. package/dist-types/core/snapshot.d.ts +51 -0
  116. package/dist-types/core/snapshot.d.ts.map +1 -0
  117. package/dist-types/core/table-ops.d.ts +30 -0
  118. package/dist-types/core/table-ops.d.ts.map +1 -0
  119. package/dist-types/index.d.ts +21 -10
  120. package/dist-types/index.d.ts.map +1 -1
  121. package/dist-types/server/index.d.ts +37 -0
  122. package/dist-types/server/index.d.ts.map +1 -0
  123. package/dist-types/types/index.d.ts +21 -0
  124. package/dist-types/types/index.d.ts.map +1 -1
  125. package/dist-types/ui/Toolbar.d.ts +1 -1
  126. package/dist-types/ui/Toolbar.d.ts.map +1 -1
  127. package/dist-types/ui/dialogs.d.ts.map +1 -1
  128. package/dist-types/ui/icons.d.ts.map +1 -1
  129. package/dist-types/ui/popovers.d.ts +1 -1
  130. package/dist-types/ui/popovers.d.ts.map +1 -1
  131. package/package.json +213 -58
@@ -21,6 +21,19 @@
21
21
  'Noto Sans KR', 'Malgun Gothic', sans-serif;
22
22
  --aw-font-mono: 'SF Mono', Menlo, Monaco, Consolas, 'D2Coding', monospace;
23
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
+
24
37
  --aw-toolbar-height: 44px;
25
38
  --aw-z-toolbar: 10;
26
39
  --aw-z-popover: 100;
@@ -42,7 +55,6 @@
42
55
  --aw-primary-hover: #8ad6ff;
43
56
  --aw-divider: rgba(100, 200, 255, 0.12);
44
57
  }
45
-
46
58
  .aeriz-wysiwyg {
47
59
  font-family: var(--aw-font);
48
60
  background-color: var(--aw-bg);
@@ -78,18 +90,15 @@
78
90
  /* ─── 뷰어 모드 ─────────────────────────────────────────────────────
79
91
  AerizViewer 는 컨텐츠 길이만큼 자연스럽게 늘어난다 (스크롤 / 고정 높이 X).
80
92
  에디터 기본 규칙을 viewer 클래스가 좁게 덮어쓴다. */
81
-
82
93
  .aeriz-wysiwyg-viewer {
83
94
  height: auto;
84
95
  min-height: 0;
85
96
  }
86
-
87
97
  .aeriz-wysiwyg-viewer .aw-content-wrap {
88
98
  flex: 0 0 auto;
89
99
  overflow: visible;
90
100
  min-height: 0;
91
101
  }
92
-
93
102
  .aeriz-wysiwyg-viewer .aw-content {
94
103
  min-height: 0;
95
104
  }
@@ -100,37 +109,49 @@
100
109
  position: absolute;
101
110
  top: 0;
102
111
  left: 0;
103
- width: 24px;
112
+ width: 20px;
104
113
  height: 100%;
105
114
  pointer-events: none;
106
115
  z-index: 2;
107
116
  }
108
117
 
109
- /* 블록별 컨트롤 그룹 — 드래그 핸들 + 단락 추가 버튼을 세로로 묶는다. */
110
-
118
+ /* 블록별 컨트롤 그룹 — 드래그 핸들 + 단락 추가 버튼을 세로로 묶는다.
119
+ 지금 다루는 블록(포인터가 올라간 블록 / 캐럿이 있는 블록) 것만 드러난다.
120
+ 본문 어디에나 마우스만 올리면 모든 블록의 핸들이 한꺼번에 떠오르던
121
+ 예전 동작은, 글이 길어질수록 왼쪽에 점 기둥을 세워 놓는 꼴이었다. */
111
122
  .aw-block-controls {
112
123
  position: absolute;
113
- left: 4px;
124
+ left: 3px;
114
125
  display: flex;
115
126
  flex-direction: column;
116
127
  gap: 0;
117
128
  pointer-events: none;
118
129
  opacity: 0;
119
- transition: opacity 0.12s ease;
120
- }
121
-
122
- .aw-content-wrap:hover .aw-block-controls {
123
- opacity: 0.45;
124
- }
125
-
126
- .aw-block-controls:hover {
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 {
127
145
  opacity: 1;
146
+ transform: none;
128
147
  }
129
148
 
149
+ /* 높이는 AerizEditor.positionHandle 의 HANDLE_HEIGHT 와 짝을 이룬다 —
150
+ 한쪽만 바꾸면 짧은 블록(hr) 위에서 핸들의 수직 정렬이 어긋난다. */
130
151
  .aw-block-handle,
131
152
  .aw-block-add {
132
- width: 18px;
133
- height: 22px;
153
+ width: 14px;
154
+ height: 18px;
134
155
  display: inline-flex;
135
156
  align-items: center;
136
157
  justify-content: center;
@@ -143,15 +164,18 @@
143
164
  appearance: none;
144
165
  padding: 0;
145
166
  cursor: pointer;
146
- transition: color 0.12s, background-color 0.12s, border-color 0.12s;
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);
147
172
  }
148
-
149
173
  .aw-block-handle { cursor: grab; }
150
174
 
151
175
  .aw-block-handle svg,
152
176
  .aw-block-add svg {
153
- width: 12px;
154
- height: 14px;
177
+ width: 10px;
178
+ height: 11px;
155
179
  }
156
180
 
157
181
  .aw-block-handle:hover,
@@ -160,17 +184,47 @@
160
184
  background-color: var(--aw-btn-hover-bg);
161
185
  border-color: var(--aw-border);
162
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
+ }
163
207
 
164
208
  .aw-block-handle.is-dragging {
165
209
  cursor: grabbing;
166
210
  color: var(--aw-primary);
211
+ background-color: var(--aw-btn-active-bg);
212
+ border-color: var(--aw-primary);
213
+ transform: scale(1.05);
167
214
  }
168
215
 
216
+ /* 끌고 있는 블록 자신 — 자리만 남기고 물러난다. */
169
217
  .is-block-dragging {
170
- opacity: 0.4;
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);
171
223
  }
172
224
 
173
- /* ─── 드롭 위치 인디케이터 ───────────────────────────────────────────── */
225
+ /* ─── 드롭 위치 인디케이터 ─────────────────────────────────────────────
226
+ 선 왼쪽 끝에 점을 하나 찍는다. 얇은 선만 있으면 어느 쪽 여백에 걸린
227
+ 건지 애매한데, 점이 있으면 "여기서부터" 가 분명해진다. */
174
228
 
175
229
  .aw-block-drop-indicator {
176
230
  position: absolute;
@@ -182,7 +236,29 @@
182
236
  pointer-events: none;
183
237
  z-index: 3;
184
238
  transform: translateY(-1px);
185
- box-shadow: 0 0 6px var(--aw-primary);
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
+ }
186
262
  }
187
263
 
188
264
  /* ─── Status Bar ─────────────────────────────────────────────────────
@@ -232,7 +308,6 @@
232
308
  }
233
309
 
234
310
  /* status bar 안의 작은 아이콘 액션 버튼 (undo/redo 등) */
235
-
236
311
  .aw-status-action {
237
312
  appearance: none;
238
313
  cursor: pointer;
@@ -249,18 +324,15 @@
249
324
  flex-shrink: 0;
250
325
  transition: color 0.12s, background-color 0.12s, border-color 0.12s;
251
326
  }
252
-
253
327
  .aw-status-action svg {
254
328
  width: 13px;
255
329
  height: 13px;
256
330
  }
257
-
258
331
  .aw-status-action:hover {
259
332
  color: var(--aw-btn-active-fg, var(--aw-primary));
260
333
  background-color: var(--aw-btn-hover-bg);
261
334
  border-color: var(--aw-border);
262
335
  }
263
-
264
336
  .aw-status-action:active {
265
337
  background-color: var(--aw-btn-active-bg);
266
338
  }
@@ -271,21 +343,121 @@
271
343
 
272
344
  /* ─── 블록 포커스 인디케이터 + 라벨 태그 ─────────────────────────────── */
273
345
 
346
+ /* 활성 블록 표시는 **레일(왼쪽 세로선) + 옅은 바탕** 두 겹이다.
347
+ 레일은 색이 아니라 길이로 자란다 — `box-shadow` 를 켜고 끄면 선이 툭
348
+ 나타나는데, 세로로 자라 올라오면 "이 블록이 지금 잡혔다" 가 동작으로
349
+ 읽힌다. 레일은 별도 pseudo 로 그려서 블록 자신의 배경(코드 블록 ·
350
+ 인용구) 과 겹치지 않는다. 어떤 태그에도 레이아웃 영향을 주지 않도록
351
+ position:absolute 만 쓰고 블록의 박스 크기는 건드리지 않는다. */
274
352
  .aw-content > [data-block-id] {
275
353
  position: relative;
276
- transition: background-color 0.12s ease, box-shadow 0.12s ease;
354
+ transition: background-color var(--aw-dur-base) var(--aw-ease-out);
277
355
  }
278
356
 
279
- .aw-content > [data-block-focused] {
280
- background-color: color-mix(in srgb, var(--aw-primary) 8%, transparent);
281
- box-shadow: -3px 0 0 var(--aw-primary);
357
+ .aw-content > [data-block-id]::before {
358
+ content: '';
359
+ position: absolute;
360
+ left: -10px;
361
+ top: 0;
362
+ width: 3px;
363
+ height: 100%;
282
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;
283
456
  }
284
457
 
285
458
  /* ─── 인터랙티브 블록 라벨 pill ───────────────────────────────────────
286
459
  활성 블록 위에 떠 있는 작은 버튼. 정적 ::before pseudo 라벨을 대체하며
287
460
  클릭 시 블록 유형 드롭다운을 열어 마이크로 인터랙션으로 유형 변경. */
288
-
289
461
  .aw-block-label-pill {
290
462
  position: absolute;
291
463
  z-index: 4;
@@ -297,39 +469,101 @@
297
469
  text-transform: uppercase;
298
470
  color: var(--aw-primary);
299
471
  background: var(--aw-bg);
300
- padding: 2px 6px 2px 6px;
301
- border: 1px solid var(--aw-primary);
302
- border-radius: 3px;
472
+ padding: 2px 5px 2px 4px;
473
+ border: 1px solid color-mix(in srgb, var(--aw-primary) 45%, transparent);
474
+ border-radius: 4px;
303
475
  cursor: pointer;
304
476
  user-select: none;
305
477
  display: inline-flex;
306
478
  align-items: center;
307
479
  gap: 4px;
308
480
  line-height: 1;
309
- transition: transform 0.12s ease, background-color 0.12s, color 0.12s,
310
- box-shadow 0.12s;
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);
311
503
  }
312
504
 
313
505
  .aw-block-label-pill::after {
314
506
  content: '▾';
315
507
  font-size: 9px;
316
- opacity: 0.7;
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);
317
512
  }
318
-
319
513
  .aw-block-label-pill:hover {
320
514
  background: var(--aw-primary);
321
- color: var(--aw-bg);
515
+ color: var(--aw-primary-fg);
516
+ border-color: var(--aw-primary);
322
517
  transform: translateY(-1px);
323
- box-shadow: 0 2px 6px color-mix(in srgb, var(--aw-primary) 35%, transparent);
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);
324
526
  }
325
-
326
527
  .aw-block-label-pill:active {
327
- transform: translateY(0);
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
+ }
328
560
  }
329
561
 
330
562
  .aw-content {
331
563
  outline: none;
332
- padding: 24px 28px;
564
+ /* 위쪽 여백은 라벨 pill(약 15px) + 여유가 들어갈 만큼 둔다 — 문서 첫
565
+ 블록에서도 pill 이 글자를 덮지 않아야 한다. */
566
+ padding: 28px 28px 24px;
333
567
  min-height: 240px;
334
568
  font-size: 15px;
335
569
  line-height: 1.7;
@@ -348,11 +582,26 @@
348
582
  .aw-content > p {
349
583
  margin: 0 0 0.8em 0;
350
584
  }
351
-
352
585
  .aw-content > p:last-child {
353
586
  margin-bottom: 0;
354
587
  }
355
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
+
356
605
  .aw-content h1,
357
606
  .aw-content h2,
358
607
  .aw-content h3 {
@@ -361,11 +610,8 @@
361
610
  padding-bottom: 0.25em;
362
611
  border-bottom: 1px solid var(--aw-border);
363
612
  }
364
-
365
613
  .aw-content h1 { font-size: 1.875rem; line-height: 1.3; }
366
-
367
614
  .aw-content h2 { font-size: 1.5rem; line-height: 1.35; }
368
-
369
615
  .aw-content h3 { font-size: 1.25rem; line-height: 1.4; }
370
616
 
371
617
  .aw-content blockquote {
@@ -393,7 +639,6 @@
393
639
  /* 코드 블록 우상단의 언어 라벨 — `<code data-language="javascript">` 의
394
640
  * data-language 가 있을 때만 표시. attr() 로 자동 동기화돼 별도 JS 갱신
395
641
  * 불필요. 사용자가 드롭다운으로 언어 변경 시 CSS 가 즉시 반영. */
396
-
397
642
  .aw-content > pre > code[data-language]:not([data-language=""])::before {
398
643
  content: attr(data-language);
399
644
  position: absolute;
@@ -418,48 +663,30 @@
418
663
  padding: 0.15em 0.4em;
419
664
  border-radius: 3px;
420
665
  }
421
-
422
666
  .aw-content pre code {
423
667
  background: transparent;
424
668
  padding: 0;
425
669
  }
426
670
 
427
671
  /* ─── Syntax highlight 토큰 색 (자체 highlight.ts 출력) ──────────── */
428
-
429
672
  .aw-content pre code .hl-keyword { color: #c678dd; font-weight: 600; }
430
-
431
673
  .aw-content pre code .hl-string { color: #98c379; }
432
-
433
674
  .aw-content pre code .hl-number { color: #d19a66; }
434
-
435
675
  .aw-content pre code .hl-comment { color: var(--aw-fg-muted); font-style: italic; }
436
-
437
676
  .aw-content pre code .hl-fn { color: #61afef; }
438
-
439
677
  .aw-content pre code .hl-builtin { color: #56b6c2; }
440
-
441
678
  .aw-content pre code .hl-tag { color: #e06c75; }
442
-
443
679
  .aw-content pre code .hl-attr { color: #d19a66; }
444
-
445
680
  .aw-content pre code .hl-prop { color: #61afef; }
446
-
447
681
  .aw-content pre code .hl-punct { color: var(--aw-fg-muted); }
448
682
 
449
683
  /* light 테마 보정 */
450
-
451
684
  .aeriz-wysiwyg[data-theme='light'] .aw-content pre code .hl-keyword { color: #a626a4; }
452
-
453
685
  .aeriz-wysiwyg[data-theme='light'] .aw-content pre code .hl-string { color: #50a14f; }
454
-
455
686
  .aeriz-wysiwyg[data-theme='light'] .aw-content pre code .hl-number { color: #986801; }
456
-
457
687
  .aeriz-wysiwyg[data-theme='light'] .aw-content pre code .hl-fn { color: #4078f2; }
458
-
459
688
  .aeriz-wysiwyg[data-theme='light'] .aw-content pre code .hl-builtin { color: #0184bc; }
460
-
461
689
  .aeriz-wysiwyg[data-theme='light'] .aw-content pre code .hl-tag { color: #e45649; }
462
-
463
690
  .aeriz-wysiwyg[data-theme='light'] .aw-content pre code .hl-prop { color: #4078f2; }
464
691
 
465
692
  .aw-content ul,
@@ -472,6 +699,72 @@
472
699
  margin: 0.2em 0;
473
700
  }
474
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
+
475
768
  .aw-content a {
476
769
  color: var(--aw-primary);
477
770
  text-decoration: underline;
@@ -491,7 +784,6 @@
491
784
  }
492
785
 
493
786
  /* ─── 유튜브 임베드 (16:9 반응형 컨테이너) ──────────────────── */
494
-
495
787
  .aw-content > div.aw-youtube-embed {
496
788
  position: relative;
497
789
  width: 100%;
@@ -502,7 +794,6 @@
502
794
  background: #000;
503
795
  border: 1px solid var(--aw-border);
504
796
  }
505
-
506
797
  .aw-content > div.aw-youtube-embed iframe {
507
798
  position: absolute;
508
799
  inset: 0;
@@ -513,7 +804,6 @@
513
804
  }
514
805
 
515
806
  /* ─── 파일 첨부 (다운로드 카드) ──────────────────── */
516
-
517
807
  .aw-content > a.aw-file-attachment {
518
808
  display: flex;
519
809
  align-items: center;
@@ -529,12 +819,10 @@
529
819
  cursor: pointer;
530
820
  user-select: none;
531
821
  }
532
-
533
822
  .aw-content > a.aw-file-attachment:hover {
534
823
  background: color-mix(in srgb, var(--aw-fg) 5%, var(--aw-bg));
535
824
  border-color: color-mix(in srgb, var(--aw-primary) 40%, var(--aw-border));
536
825
  }
537
-
538
826
  .aw-content > a.aw-file-attachment .aw-file-icon {
539
827
  flex: 0 0 auto;
540
828
  display: inline-flex;
@@ -546,12 +834,10 @@
546
834
  background: color-mix(in srgb, var(--aw-primary) 12%, transparent);
547
835
  color: var(--aw-primary);
548
836
  }
549
-
550
837
  .aw-content > a.aw-file-attachment .aw-file-icon svg {
551
838
  width: 20px;
552
839
  height: 20px;
553
840
  }
554
-
555
841
  .aw-content > a.aw-file-attachment .aw-file-meta {
556
842
  flex: 1 1 auto;
557
843
  min-width: 0;
@@ -559,7 +845,6 @@
559
845
  flex-direction: column;
560
846
  gap: 2px;
561
847
  }
562
-
563
848
  .aw-content > a.aw-file-attachment .aw-file-name {
564
849
  font-weight: 600;
565
850
  font-size: 0.95rem;
@@ -567,14 +852,12 @@
567
852
  word-break: break-all;
568
853
  line-height: 1.3;
569
854
  }
570
-
571
855
  .aw-content > a.aw-file-attachment .aw-file-info {
572
856
  font-size: 0.75rem;
573
857
  color: var(--aw-fg-muted);
574
858
  font-family: var(--aw-mono, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);
575
859
  letter-spacing: 0.02em;
576
860
  }
577
-
578
861
  .aw-content > a.aw-file-attachment .aw-file-action {
579
862
  flex: 0 0 auto;
580
863
  display: inline-flex;
@@ -584,18 +867,15 @@
584
867
  justify-content: center;
585
868
  color: var(--aw-fg-muted);
586
869
  }
587
-
588
870
  .aw-content > a.aw-file-attachment .aw-file-action svg {
589
871
  width: 18px;
590
872
  height: 18px;
591
873
  }
592
-
593
874
  .aw-content > a.aw-file-attachment:hover .aw-file-action {
594
875
  color: var(--aw-primary);
595
876
  }
596
877
 
597
878
  /* ─── 링크 카드 (Open Graph 스타일 미리보기) ──────────────────── */
598
-
599
879
  .aw-content > a.aw-link-card {
600
880
  display: flex;
601
881
  align-items: stretch;
@@ -610,14 +890,12 @@
610
890
  overflow: hidden;
611
891
  transition: border-color 0.12s, background-color 0.12s;
612
892
  }
613
-
614
893
  .aw-content > a.aw-link-card:hover {
615
894
  border-color: var(--aw-primary);
616
895
  background-color: color-mix(in srgb, var(--aw-primary) 9%, var(--aw-bg));
617
896
  }
618
897
 
619
898
  /* 좌측 og:image 썸네일 — 있을 때만. */
620
-
621
899
  .aw-content > a.aw-link-card > .aw-link-card-image {
622
900
  flex: 0 0 auto;
623
901
  width: 120px;
@@ -628,7 +906,6 @@
628
906
  }
629
907
 
630
908
  /* 우측 본문 — title / desc / url 행. */
631
-
632
909
  .aw-content > a.aw-link-card > .aw-link-card-body {
633
910
  flex: 1 1 auto;
634
911
  min-width: 0;
@@ -637,7 +914,6 @@
637
914
  gap: 4px;
638
915
  padding: 12px 14px;
639
916
  }
640
-
641
917
  .aw-content > a.aw-link-card .aw-link-card-title {
642
918
  font-weight: 600;
643
919
  font-size: 1rem;
@@ -648,7 +924,6 @@
648
924
  -webkit-box-orient: vertical;
649
925
  overflow: hidden;
650
926
  }
651
-
652
927
  .aw-content > a.aw-link-card .aw-link-card-desc {
653
928
  font-size: 0.85rem;
654
929
  line-height: 1.4;
@@ -658,7 +933,6 @@
658
933
  -webkit-box-orient: vertical;
659
934
  overflow: hidden;
660
935
  }
661
-
662
936
  .aw-content > a.aw-link-card .aw-link-card-url {
663
937
  display: flex;
664
938
  align-items: center;
@@ -669,7 +943,6 @@
669
943
  font-family: var(--aw-font-mono);
670
944
  word-break: break-all;
671
945
  }
672
-
673
946
  .aw-content > a.aw-link-card .aw-link-card-favicon {
674
947
  flex: 0 0 auto;
675
948
  width: 14px;
@@ -677,7 +950,6 @@
677
950
  border-radius: 2px;
678
951
  background: transparent;
679
952
  }
680
-
681
953
  .aw-content > a.aw-link-card .aw-link-card-url-text {
682
954
  flex: 1 1 auto;
683
955
  min-width: 0;
@@ -691,7 +963,6 @@
691
963
  width: 100%;
692
964
  margin: 1em 0;
693
965
  }
694
-
695
966
  .aw-content table td,
696
967
  .aw-content table th {
697
968
  border: 1px solid var(--aw-border);
@@ -701,35 +972,71 @@
701
972
  /* td/th 의 inline background-color 가 overflow 자식에 가려지지 않도록 */
702
973
  background-clip: padding-box;
703
974
  }
704
-
705
975
  .aw-content table th {
706
976
  background-color: color-mix(in srgb, var(--aw-fg) 5%, var(--aw-bg));
707
977
  font-weight: 600;
708
978
  }
709
979
 
710
- /* 커스텀 드래그-셀렉션으로 마킹된 표 셀 — 다중 선택 시각 강조. */
980
+ .aw-content table td,
981
+ .aw-content table th {
982
+ transition: background-color var(--aw-dur-fast) var(--aw-ease-out);
983
+ }
711
984
 
985
+ /* 커스텀 드래그-셀렉션으로 마킹된 표 셀 — 다중 선택 시각 강조. */
712
986
  .aw-content table td.aw-cell-selected,
713
987
  .aw-content table th.aw-cell-selected {
714
988
  outline: 2px solid var(--aw-primary);
715
989
  outline-offset: -2px;
716
990
  background-color: color-mix(in srgb, var(--aw-primary) 18%, transparent) !important;
717
991
  }
718
-
719
992
  .aeriz-wysiwyg-viewer .aw-cell-selected {
720
993
  outline: none;
721
994
  background-color: revert !important;
722
995
  }
723
996
 
724
997
  /* ─── 리사이즈 핸들 + 정렬 컨트롤 (이미지·유튜브) ──────────────────── */
725
-
726
998
  .aw-content > figure,
727
999
  .aw-content > div.aw-youtube-embed {
728
1000
  position: relative;
1001
+ /* 인라인 width 가 본문보다 커도 넘치지 않게 막는다. 드래그 리사이즈는
1002
+ JS 에서 본문 폭으로 상한을 두지만, 외부에서 들어온 값(HTML 가져오기,
1003
+ 예전에 더 넓은 화면에서 저장된 스냅샷)까지 막아 주는 건 이 규칙이다.
1004
+ 이게 없으면 본문이 가로로 넓어지면서 에디터 전체가 늘어난다. */
1005
+ max-width: 100%;
729
1006
  }
730
1007
 
731
- /* 좌상단 정렬 컨트롤 — 좌/중/우 3 버튼. hover 시 fade-in. */
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
+ }
732
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. */
733
1040
  .aw-block-align-controls {
734
1041
  position: absolute;
735
1042
  top: 6px;
@@ -737,21 +1044,34 @@
737
1044
  display: flex;
738
1045
  gap: 1px;
739
1046
  background: rgba(0, 0, 0, 0.55);
740
- border-radius: 5px;
1047
+ backdrop-filter: blur(6px);
1048
+ border-radius: 6px;
741
1049
  padding: 2px;
742
1050
  z-index: 11;
743
1051
  opacity: 0;
744
- transition: opacity 0.12s;
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);
745
1056
  user-select: none;
746
1057
  }
747
-
748
1058
  .aw-content > figure:hover .aw-block-align-controls,
749
1059
  .aw-content > div.aw-youtube-embed:hover .aw-block-align-controls,
1060
+ .aw-content > figure:focus-within .aw-block-align-controls,
750
1061
  .aw-resizing .aw-block-align-controls {
751
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);
752
1071
  }
753
1072
 
754
- .aw-block-align-btn {
1073
+ .aw-block-align-btn,
1074
+ .aw-block-caption-btn {
755
1075
  width: 22px;
756
1076
  height: 22px;
757
1077
  display: inline-flex;
@@ -761,19 +1081,30 @@
761
1081
  border: none;
762
1082
  color: #fff;
763
1083
  cursor: pointer;
764
- border-radius: 3px;
1084
+ border-radius: 4px;
765
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);
766
1089
  }
767
-
768
- .aw-block-align-btn:hover {
1090
+ .aw-block-align-btn:hover,
1091
+ .aw-block-caption-btn:hover {
769
1092
  background: rgba(255, 255, 255, 0.18);
770
1093
  }
771
-
772
- .aw-block-align-btn svg {
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 {
773
1105
  width: 14px;
774
1106
  height: 14px;
775
1107
  }
776
-
777
1108
  .aw-resize-handle {
778
1109
  position: absolute;
779
1110
  right: -1px;
@@ -782,45 +1113,85 @@
782
1113
  height: 14px;
783
1114
  background: var(--aw-primary);
784
1115
  border: 2px solid var(--aw-bg);
785
- border-radius: 3px;
1116
+ border-radius: 4px;
786
1117
  cursor: nwse-resize;
787
1118
  z-index: 10;
788
1119
  user-select: none;
789
1120
  opacity: 0;
790
- transition: opacity 0.12s, transform 0.08s;
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;
791
1133
  }
792
-
793
1134
  .aw-content > figure:hover .aw-resize-handle,
794
1135
  .aw-content > div.aw-youtube-embed:hover .aw-resize-handle,
795
1136
  .aw-resizing .aw-resize-handle {
796
1137
  opacity: 1;
1138
+ transform: none;
797
1139
  }
798
-
799
1140
  .aw-resize-handle:hover {
800
- transform: scale(1.15);
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;
801
1152
  }
802
1153
 
803
1154
  /* 유튜브 임베드는 iframe 안에 자체 워터마크/컨트롤이 있어 코너에 핸들을 두면
804
1155
  * 클릭이 충돌한다. embed 안쪽 우하단에 약간 들여서 배치 — YouTube 자체 UI
805
1156
  * 영역과 약간 떨어진 위치. */
806
-
807
1157
  .aw-content > div.aw-youtube-embed > .aw-resize-handle {
808
1158
  right: 0px;
809
1159
  bottom: -15px;
810
1160
  }
811
-
812
1161
  .aw-resizing {
813
1162
  user-select: none;
814
1163
  }
815
1164
 
816
1165
  /* 뷰어(읽기 전용) 모드에서는 편집용 컨트롤(리사이즈 핸들 / 정렬 컨트롤) 을
817
1166
  * 모두 숨긴다. AerizViewer.root 의 className 'aeriz-wysiwyg-viewer' 를 타겟. */
818
-
819
1167
  .aeriz-wysiwyg-viewer .aw-resize-handle,
820
1168
  .aeriz-wysiwyg-viewer .aw-block-align-controls {
821
1169
  display: none !important;
822
1170
  }
823
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
+ }
824
1195
  .aw-toolbar {
825
1196
  display: flex;
826
1197
  align-items: center;
@@ -866,13 +1237,28 @@
866
1237
  border-radius: var(--aw-radius-sm);
867
1238
  cursor: pointer;
868
1239
  color: var(--aw-toolbar-fg);
869
- transition: background-color 0.12s ease, color 0.12s ease, border-color 0.12s ease;
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);
870
1246
  }
871
1247
 
872
1248
  .aw-toolbar-btn:hover:not(:disabled) {
873
1249
  background-color: var(--aw-btn-hover-bg);
874
1250
  }
875
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
+
876
1262
  .aw-toolbar-btn:disabled {
877
1263
  opacity: 0.4;
878
1264
  cursor: not-allowed;
@@ -887,6 +1273,42 @@
887
1273
  background-color: var(--aw-btn-active-bg);
888
1274
  }
889
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
+
890
1312
  .aw-toolbar-btn-icon {
891
1313
  display: inline-flex;
892
1314
  width: 18px;
@@ -910,7 +1332,6 @@
910
1332
  width: 100%;
911
1333
  height: 100%;
912
1334
  }
913
-
914
1335
  /*
915
1336
  * Dialog / popover 는 .aeriz-wysiwyg 의 자식이 아닌 document.body 에 attach
916
1337
  * 되므로 에디터의 CSS 변수를 직접 받지 못한다. 대신 자기 자신에
@@ -935,6 +1356,14 @@
935
1356
  --d-on-primary: #050810;
936
1357
  --d-overlay: rgba(0, 0, 0, 0.45);
937
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);
938
1367
  }
939
1368
 
940
1369
  .aw-dialog-overlay[data-theme='dark'],
@@ -1015,9 +1444,7 @@
1015
1444
  color: var(--d-fg-muted);
1016
1445
  transition: background-color 0.12s, color 0.12s;
1017
1446
  }
1018
-
1019
1447
  .aw-dialog-close:hover { background-color: var(--d-hover-bg); color: var(--d-fg); }
1020
-
1021
1448
  .aw-dialog-close svg { width: 18px; height: 18px; }
1022
1449
 
1023
1450
  .aw-dialog-body {
@@ -1034,7 +1461,6 @@
1034
1461
  }
1035
1462
 
1036
1463
  .aw-field { display: flex; flex-direction: column; gap: 6px; }
1037
-
1038
1464
  .aw-field-label { font-size: 13px; font-weight: 600; color: var(--d-fg); }
1039
1465
 
1040
1466
  .aw-input {
@@ -1049,7 +1475,6 @@
1049
1475
  color: var(--d-input-fg);
1050
1476
  background-color: var(--d-input-bg);
1051
1477
  }
1052
-
1053
1478
  .aw-input:focus {
1054
1479
  border-color: var(--d-primary);
1055
1480
  box-shadow: 0 0 0 3px var(--d-primary-soft);
@@ -1069,7 +1494,6 @@
1069
1494
  color: var(--d-fg);
1070
1495
  cursor: pointer; user-select: none;
1071
1496
  }
1072
-
1073
1497
  .aw-checkbox input { width: 16px; height: 16px; accent-color: var(--d-primary); }
1074
1498
 
1075
1499
  .aw-btn {
@@ -1082,17 +1506,13 @@
1082
1506
  font-family: inherit;
1083
1507
  transition: background-color 0.12s, border-color 0.12s, color 0.12s;
1084
1508
  }
1085
-
1086
1509
  .aw-btn-primary { background-color: var(--d-primary); color: var(--d-on-primary); }
1087
-
1088
1510
  .aw-btn-primary:hover { background-color: var(--d-primary-hover); }
1089
-
1090
1511
  .aw-btn-ghost {
1091
1512
  background-color: transparent;
1092
1513
  color: var(--d-fg);
1093
1514
  border-color: var(--d-border-strong);
1094
1515
  }
1095
-
1096
1516
  .aw-btn-ghost:hover { background-color: var(--d-hover-bg); }
1097
1517
 
1098
1518
  .aw-tabs {
@@ -1100,7 +1520,6 @@
1100
1520
  border-bottom: 1px solid var(--d-border);
1101
1521
  margin: -4px -4px 8px;
1102
1522
  }
1103
-
1104
1523
  .aw-tab {
1105
1524
  padding: 8px 14px;
1106
1525
  background: transparent;
@@ -1113,11 +1532,8 @@
1113
1532
  cursor: pointer;
1114
1533
  transition: color 0.12s, border-color 0.12s;
1115
1534
  }
1116
-
1117
1535
  .aw-tab:hover { color: var(--d-fg); }
1118
-
1119
1536
  .aw-tab-active { color: var(--d-primary); border-bottom-color: var(--d-primary); }
1120
-
1121
1537
  .aw-tab-disabled,
1122
1538
  .aw-tab[disabled] {
1123
1539
  opacity: 0.4;
@@ -1125,7 +1541,6 @@
1125
1541
  text-decoration: line-through;
1126
1542
  color: var(--d-fg-muted);
1127
1543
  }
1128
-
1129
1544
  .aw-tab-disabled:hover,
1130
1545
  .aw-tab[disabled]:hover { color: var(--d-fg-muted); }
1131
1546
 
@@ -1160,17 +1575,13 @@
1160
1575
  transition: border-color 0.12s, background-color 0.12s, color 0.12s;
1161
1576
  background-color: var(--d-input-bg);
1162
1577
  }
1163
-
1164
1578
  .aw-dropzone:hover {
1165
1579
  border-color: var(--d-primary);
1166
1580
  background-color: var(--d-hover-bg);
1167
1581
  color: var(--d-primary);
1168
1582
  }
1169
-
1170
1583
  .aw-dropzone svg { width: 36px; height: 36px; }
1171
-
1172
1584
  .aw-dropzone-text { font-size: 13px; line-height: 1.5; }
1173
-
1174
1585
  .aw-dropzone-loading { opacity: 0.6; pointer-events: none; }
1175
1586
 
1176
1587
  .aw-preview {
@@ -1191,7 +1602,6 @@
1191
1602
  width: max-content;
1192
1603
  margin: 0 auto;
1193
1604
  }
1194
-
1195
1605
  .aw-table-cell {
1196
1606
  background-color: var(--d-input-bg);
1197
1607
  border: 1px solid var(--d-border-strong);
@@ -1199,12 +1609,10 @@
1199
1609
  cursor: pointer;
1200
1610
  transition: background-color 0.08s;
1201
1611
  }
1202
-
1203
1612
  .aw-table-cell-active {
1204
1613
  background-color: var(--d-primary-soft);
1205
1614
  border-color: var(--d-primary);
1206
1615
  }
1207
-
1208
1616
  .aw-table-status {
1209
1617
  text-align: center;
1210
1618
  font-size: 13px;
@@ -1247,11 +1655,22 @@
1247
1655
  border: 1px solid var(--d-border);
1248
1656
  cursor: pointer;
1249
1657
  padding: 0;
1250
- transition: transform 0.08s;
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);
1251
1672
  }
1252
1673
 
1253
- .aw-color-swatch:hover { transform: scale(1.15); }
1254
-
1255
1674
  .aw-color-section {
1256
1675
  font-size: 10px;
1257
1676
  letter-spacing: 0.08em;
@@ -1260,7 +1679,6 @@
1260
1679
  font-weight: 700;
1261
1680
  padding: 6px 2px 4px;
1262
1681
  }
1263
-
1264
1682
  .aw-color-section + .aw-color-grid { margin-bottom: 4px; }
1265
1683
 
1266
1684
  .aw-popover-action {
@@ -1278,7 +1696,6 @@
1278
1696
  margin-top: 4px;
1279
1697
  position: relative;
1280
1698
  }
1281
-
1282
1699
  .aw-popover-action:hover { background-color: var(--d-hover-bg); }
1283
1700
 
1284
1701
  .aw-popover-item {
@@ -1295,9 +1712,16 @@
1295
1712
  color: var(--d-fg);
1296
1713
  font-family: inherit;
1297
1714
  font-size: 12.5px;
1715
+ transition:
1716
+ background-color var(--aw-dur-fast, 90ms) ease,
1717
+ color var(--aw-dur-fast, 90ms) ease;
1298
1718
  }
1299
-
1300
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
+ }
1301
1725
 
1302
1726
  .aw-popover-item-glyph {
1303
1727
  display: inline-flex;
@@ -1310,8 +1734,18 @@
1310
1734
  font-family: var(--d-mono);
1311
1735
  font-size: 10px;
1312
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);
1313
1748
  }
1314
-
1315
1749
  .aw-popover-item-glyph svg { width: 11px; height: 11px; }
1316
1750
 
1317
1751
  .aw-popover-item-label { flex: 1; min-width: 0; }
@@ -1323,7 +1757,6 @@
1323
1757
  align-items: center; justify-content: center;
1324
1758
  gap: 0;
1325
1759
  }
1326
-
1327
1760
  .aw-popover-item-icon svg { width: 18px; height: 18px; }
1328
1761
 
1329
1762
  .aw-popover-divider {
@@ -1334,33 +1767,28 @@
1334
1767
 
1335
1768
  /* 블록 유형 드롭다운 — 항목이 많아 세로로 길어지지 않도록 2 컬럼 그리드.
1336
1769
  * 섹션 구분 divider 는 grid-column 으로 두 컬럼을 모두 가로지른다. */
1337
-
1338
1770
  .aw-block-type-popover {
1339
1771
  display: grid;
1340
1772
  grid-template-columns: repeat(2, minmax(150px, 1fr));
1341
1773
  column-gap: 2px;
1342
1774
  min-width: 320px;
1343
1775
  }
1344
-
1345
1776
  .aw-block-type-popover .aw-popover-divider {
1346
1777
  grid-column: 1 / -1;
1347
1778
  }
1348
1779
 
1349
1780
  /* 글꼴 드롭다운 — 라벨(작게) + '가A' 미리보기(글꼴 자체) 두 영역. */
1350
-
1351
1781
  .aw-font-family-popover {
1352
1782
  min-width: 180px;
1353
1783
  max-height: 360px;
1354
1784
  overflow-y: auto;
1355
1785
  }
1356
-
1357
1786
  .aw-font-family-item {
1358
1787
  padding: 4px 8px;
1359
1788
  display: flex;
1360
1789
  align-items: center;
1361
1790
  gap: 8px;
1362
1791
  }
1363
-
1364
1792
  .aw-font-family-name {
1365
1793
  flex: 1 1 auto;
1366
1794
  min-width: 0;
@@ -1371,7 +1799,6 @@
1371
1799
  overflow: hidden;
1372
1800
  text-overflow: ellipsis;
1373
1801
  }
1374
-
1375
1802
  .aw-font-family-preview {
1376
1803
  flex: 0 0 auto;
1377
1804
  font-size: 13px;
@@ -1381,29 +1808,24 @@
1381
1808
  }
1382
1809
 
1383
1810
  /* 표 편집 드롭다운 — 행/열 추가·삭제 텍스트 항목 */
1384
-
1385
1811
  .aw-table-menu-popover {
1386
1812
  min-width: 160px;
1387
1813
  }
1388
-
1389
1814
  .aw-table-menu-item {
1390
1815
  font-size: 12px;
1391
1816
  padding: 6px 10px;
1392
1817
  }
1393
1818
 
1394
1819
  /* 글자 크기 드롭다운 — 한 줄당 px 라벨이 그 크기로 렌더되는 컴팩트 리스트 */
1395
-
1396
1820
  .aw-font-size-popover {
1397
1821
  min-width: 80px;
1398
1822
  max-height: 320px;
1399
1823
  overflow-y: auto;
1400
1824
  }
1401
-
1402
1825
  .aw-font-size-item {
1403
1826
  padding: 2px 10px;
1404
1827
  justify-content: space-between;
1405
1828
  }
1406
-
1407
1829
  .aw-font-size-label {
1408
1830
  font-size: 11px;
1409
1831
  color: var(--d-fg);
@@ -1415,7 +1837,6 @@
1415
1837
  background-color: color-mix(in srgb, var(--d-primary) 14%, transparent);
1416
1838
  color: var(--d-primary);
1417
1839
  }
1418
-
1419
1840
  .aw-popover-item-active::after {
1420
1841
  content: '✓';
1421
1842
  margin-left: auto;
@@ -1429,18 +1850,15 @@
1429
1850
  }
1430
1851
 
1431
1852
  /* 표 삽입 드롭다운 — 호버에 따라 동적으로 늘어나는 셀 그리드 */
1432
-
1433
1853
  .aw-table-popover {
1434
1854
  padding: 10px;
1435
1855
  min-width: auto;
1436
1856
  }
1437
-
1438
1857
  .aw-table-picker-grid {
1439
1858
  display: grid;
1440
1859
  gap: 2px;
1441
1860
  margin-bottom: 6px;
1442
1861
  }
1443
-
1444
1862
  .aw-table-picker-grid .aw-table-cell {
1445
1863
  width: 18px;
1446
1864
  height: 18px;
@@ -1450,12 +1868,19 @@
1450
1868
  cursor: pointer;
1451
1869
  transition: background-color 0.06s, border-color 0.06s;
1452
1870
  }
1453
-
1871
+ /* 선택된 칸은 살짝 안으로 눌러 담긴다 — 몇 칸이 잡혔는지 숫자를 읽지 않고도
1872
+ 면적으로 먼저 보인다. */
1454
1873
  .aw-table-picker-grid .aw-table-cell.aw-table-cell-active {
1455
1874
  background-color: var(--d-primary-soft);
1456
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);
1457
1883
  }
1458
-
1459
1884
  .aw-table-popover .aw-table-status {
1460
1885
  text-align: center;
1461
1886
  font-size: 12px;
@@ -1472,7 +1897,6 @@
1472
1897
  padding: 4px 8px;
1473
1898
  font-weight: 700;
1474
1899
  }
1475
-
1476
1900
  .aw-coming-soon-popover .aw-popover-empty {
1477
1901
  padding: 10px 8px;
1478
1902
  font-size: 13px;