@aeriz/wysiwyg 1.0.3 → 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 (64) hide show
  1. package/README.md +37 -13
  2. package/dist/adapters/shared.cjs +1 -1
  3. package/dist/adapters/shared.js +11 -11
  4. package/dist/aeriz-wysiwyg-adapter.js +1 -1
  5. package/dist/aeriz-wysiwyg-adapter.min.js +1 -1
  6. package/dist/aeriz-wysiwyg-jquery.js +1 -1
  7. package/dist/aeriz-wysiwyg-jquery.min.js +1 -1
  8. package/dist/aeriz-wysiwyg-wc.js +1 -1
  9. package/dist/aeriz-wysiwyg-wc.min.js +1 -1
  10. package/dist/aeriz-wysiwyg.cjs +3773 -1926
  11. package/dist/aeriz-wysiwyg.css +554 -38
  12. package/dist/aeriz-wysiwyg.esm.js +3773 -1926
  13. package/dist/aeriz-wysiwyg.esm.min.js +4271 -2658
  14. package/dist/aeriz-wysiwyg.js +3773 -1926
  15. package/dist/aeriz-wysiwyg.min.cjs +36 -34
  16. package/dist/aeriz-wysiwyg.min.css +1 -1
  17. package/dist/aeriz-wysiwyg.min.js +40 -38
  18. package/dist/server.cjs +24 -22
  19. package/dist/server.js +444 -312
  20. package/dist-types/adapters/shared.d.ts.map +1 -1
  21. package/dist-types/cjs/core/AerizEditor.d.cts +180 -7
  22. package/dist-types/cjs/core/block-meta.d.cts +66 -0
  23. package/dist-types/cjs/core/block-policy.d.cts +43 -0
  24. package/dist-types/cjs/core/blocks.d.cts +47 -4
  25. package/dist-types/cjs/core/history.d.cts +86 -0
  26. package/dist-types/cjs/core/i18n.d.cts +12 -0
  27. package/dist-types/cjs/core/import-html.d.cts +1 -0
  28. package/dist-types/cjs/core/import-markdown.d.cts +0 -4
  29. package/dist-types/cjs/core/input-rules.d.cts +46 -0
  30. package/dist-types/cjs/core/selection.d.cts +46 -0
  31. package/dist-types/cjs/core/table-ops.d.cts +30 -0
  32. package/dist-types/cjs/index.d.cts +2 -0
  33. package/dist-types/core/AerizEditor.d.ts +180 -7
  34. package/dist-types/core/AerizEditor.d.ts.map +1 -1
  35. package/dist-types/core/block-meta.d.ts +66 -0
  36. package/dist-types/core/block-meta.d.ts.map +1 -1
  37. package/dist-types/core/block-policy.d.ts +43 -0
  38. package/dist-types/core/block-policy.d.ts.map +1 -0
  39. package/dist-types/core/blocks.d.ts +47 -4
  40. package/dist-types/core/blocks.d.ts.map +1 -1
  41. package/dist-types/core/history.d.ts +86 -0
  42. package/dist-types/core/history.d.ts.map +1 -0
  43. package/dist-types/core/i18n.d.ts +12 -0
  44. package/dist-types/core/i18n.d.ts.map +1 -1
  45. package/dist-types/core/import-html.d.ts +1 -0
  46. package/dist-types/core/import-html.d.ts.map +1 -1
  47. package/dist-types/core/import-markdown.d.ts +0 -4
  48. package/dist-types/core/import-markdown.d.ts.map +1 -1
  49. package/dist-types/core/input-rules.d.ts +46 -0
  50. package/dist-types/core/input-rules.d.ts.map +1 -0
  51. package/dist-types/core/plain-text.d.ts.map +1 -1
  52. package/dist-types/core/render-string.d.ts.map +1 -1
  53. package/dist-types/core/selection.d.ts +46 -0
  54. package/dist-types/core/selection.d.ts.map +1 -0
  55. package/dist-types/core/snapshot.d.ts.map +1 -1
  56. package/dist-types/core/table-ops.d.ts +30 -0
  57. package/dist-types/core/table-ops.d.ts.map +1 -0
  58. package/dist-types/index.d.ts +2 -0
  59. package/dist-types/index.d.ts.map +1 -1
  60. package/dist-types/ui/Toolbar.d.ts.map +1 -1
  61. package/dist-types/ui/dialogs.d.ts.map +1 -1
  62. package/dist-types/ui/icons.d.ts.map +1 -1
  63. package/dist-types/ui/popovers.d.ts.map +1 -1
  64. package/package.json +1 -1
@@ -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;
@@ -102,7 +115,10 @@
102
115
  z-index: 2;
103
116
  }
104
117
 
105
- /* 블록별 컨트롤 그룹 — 드래그 핸들 + 단락 추가 버튼을 세로로 묶는다. */
118
+ /* 블록별 컨트롤 그룹 — 드래그 핸들 + 단락 추가 버튼을 세로로 묶는다.
119
+ 지금 다루는 블록(포인터가 올라간 블록 / 캐럿이 있는 블록) 것만 드러난다.
120
+ 본문 어디에나 마우스만 올리면 모든 블록의 핸들이 한꺼번에 떠오르던
121
+ 예전 동작은, 글이 길어질수록 왼쪽에 점 기둥을 세워 놓는 꼴이었다. */
106
122
  .aw-block-controls {
107
123
  position: absolute;
108
124
  left: 3px;
@@ -111,13 +127,23 @@
111
127
  gap: 0;
112
128
  pointer-events: none;
113
129
  opacity: 0;
114
- transition: opacity 0.12s ease;
115
- }
116
- .aw-content-wrap:hover .aw-block-controls {
117
- opacity: 0.45;
118
- }
119
- .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 {
120
145
  opacity: 1;
146
+ transform: none;
121
147
  }
122
148
 
123
149
  /* 높이는 AerizEditor.positionHandle 의 HANDLE_HEIGHT 와 짝을 이룬다 —
@@ -138,7 +164,11 @@
138
164
  appearance: none;
139
165
  padding: 0;
140
166
  cursor: pointer;
141
- 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);
142
172
  }
143
173
  .aw-block-handle { cursor: grab; }
144
174
 
@@ -154,17 +184,47 @@
154
184
  background-color: var(--aw-btn-hover-bg);
155
185
  border-color: var(--aw-border);
156
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
+ }
157
207
 
158
208
  .aw-block-handle.is-dragging {
159
209
  cursor: grabbing;
160
210
  color: var(--aw-primary);
211
+ background-color: var(--aw-btn-active-bg);
212
+ border-color: var(--aw-primary);
213
+ transform: scale(1.05);
161
214
  }
162
215
 
216
+ /* 끌고 있는 블록 자신 — 자리만 남기고 물러난다. */
163
217
  .is-block-dragging {
164
- 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);
165
223
  }
166
224
 
167
- /* ─── 드롭 위치 인디케이터 ───────────────────────────────────────────── */
225
+ /* ─── 드롭 위치 인디케이터 ─────────────────────────────────────────────
226
+ 선 왼쪽 끝에 점을 하나 찍는다. 얇은 선만 있으면 어느 쪽 여백에 걸린
227
+ 건지 애매한데, 점이 있으면 "여기서부터" 가 분명해진다. */
168
228
 
169
229
  .aw-block-drop-indicator {
170
230
  position: absolute;
@@ -176,7 +236,29 @@
176
236
  pointer-events: none;
177
237
  z-index: 3;
178
238
  transform: translateY(-1px);
179
- 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
+ }
180
262
  }
181
263
 
182
264
  /* ─── Status Bar ─────────────────────────────────────────────────────
@@ -261,15 +343,116 @@
261
343
 
262
344
  /* ─── 블록 포커스 인디케이터 + 라벨 태그 ─────────────────────────────── */
263
345
 
346
+ /* 활성 블록 표시는 **레일(왼쪽 세로선) + 옅은 바탕** 두 겹이다.
347
+ 레일은 색이 아니라 길이로 자란다 — `box-shadow` 를 켜고 끄면 선이 툭
348
+ 나타나는데, 세로로 자라 올라오면 "이 블록이 지금 잡혔다" 가 동작으로
349
+ 읽힌다. 레일은 별도 pseudo 로 그려서 블록 자신의 배경(코드 블록 ·
350
+ 인용구) 과 겹치지 않는다. 어떤 태그에도 레이아웃 영향을 주지 않도록
351
+ position:absolute 만 쓰고 블록의 박스 크기는 건드리지 않는다. */
264
352
  .aw-content > [data-block-id] {
265
353
  position: relative;
266
- transition: background-color 0.12s ease, box-shadow 0.12s ease;
354
+ transition: background-color var(--aw-dur-base) var(--aw-ease-out);
267
355
  }
268
356
 
269
- .aw-content > [data-block-focused] {
270
- background-color: color-mix(in srgb, var(--aw-primary) 8%, transparent);
271
- 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%;
272
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;
273
456
  }
274
457
 
275
458
  /* ─── 인터랙티브 블록 라벨 pill ───────────────────────────────────────
@@ -286,36 +469,101 @@
286
469
  text-transform: uppercase;
287
470
  color: var(--aw-primary);
288
471
  background: var(--aw-bg);
289
- padding: 2px 6px 2px 6px;
290
- border: 1px solid var(--aw-primary);
291
- 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;
292
475
  cursor: pointer;
293
476
  user-select: none;
294
477
  display: inline-flex;
295
478
  align-items: center;
296
479
  gap: 4px;
297
480
  line-height: 1;
298
- transition: transform 0.12s ease, background-color 0.12s, color 0.12s,
299
- 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);
300
503
  }
504
+
301
505
  .aw-block-label-pill::after {
302
506
  content: '▾';
303
507
  font-size: 9px;
304
- 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);
305
512
  }
306
513
  .aw-block-label-pill:hover {
307
514
  background: var(--aw-primary);
308
- color: var(--aw-bg);
515
+ color: var(--aw-primary-fg);
516
+ border-color: var(--aw-primary);
309
517
  transform: translateY(-1px);
310
- 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);
311
526
  }
312
527
  .aw-block-label-pill:active {
313
- 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
+ }
314
560
  }
315
561
 
316
562
  .aw-content {
317
563
  outline: none;
318
- padding: 24px 28px;
564
+ /* 위쪽 여백은 라벨 pill(약 15px) + 여유가 들어갈 만큼 둔다 — 문서 첫
565
+ 블록에서도 pill 이 글자를 덮지 않아야 한다. */
566
+ padding: 28px 28px 24px;
319
567
  min-height: 240px;
320
568
  font-size: 15px;
321
569
  line-height: 1.7;
@@ -451,6 +699,72 @@
451
699
  margin: 0.2em 0;
452
700
  }
453
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
+
454
768
  .aw-content a {
455
769
  color: var(--aw-primary);
456
770
  text-decoration: underline;
@@ -663,6 +977,11 @@
663
977
  font-weight: 600;
664
978
  }
665
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
+
666
985
  /* 커스텀 드래그-셀렉션으로 마킹된 표 셀 — 다중 선택 시각 강조. */
667
986
  .aw-content table td.aw-cell-selected,
668
987
  .aw-content table th.aw-cell-selected {
@@ -694,7 +1013,30 @@
694
1013
  margin: 1em 0;
695
1014
  }
696
1015
 
697
- /* 좌상단 정렬 컨트롤 — 좌/중/우 3 버튼. hover 시 fade-in. */
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. */
698
1040
  .aw-block-align-controls {
699
1041
  position: absolute;
700
1042
  top: 6px;
@@ -702,19 +1044,34 @@
702
1044
  display: flex;
703
1045
  gap: 1px;
704
1046
  background: rgba(0, 0, 0, 0.55);
705
- border-radius: 5px;
1047
+ backdrop-filter: blur(6px);
1048
+ border-radius: 6px;
706
1049
  padding: 2px;
707
1050
  z-index: 11;
708
1051
  opacity: 0;
709
- 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);
710
1056
  user-select: none;
711
1057
  }
712
1058
  .aw-content > figure:hover .aw-block-align-controls,
713
1059
  .aw-content > div.aw-youtube-embed:hover .aw-block-align-controls,
1060
+ .aw-content > figure:focus-within .aw-block-align-controls,
714
1061
  .aw-resizing .aw-block-align-controls {
715
1062
  opacity: 1;
1063
+ transform: none;
716
1064
  }
717
- .aw-block-align-btn {
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 {
718
1075
  width: 22px;
719
1076
  height: 22px;
720
1077
  display: inline-flex;
@@ -724,13 +1081,27 @@
724
1081
  border: none;
725
1082
  color: #fff;
726
1083
  cursor: pointer;
727
- border-radius: 3px;
1084
+ border-radius: 4px;
728
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);
729
1089
  }
730
- .aw-block-align-btn:hover {
1090
+ .aw-block-align-btn:hover,
1091
+ .aw-block-caption-btn:hover {
731
1092
  background: rgba(255, 255, 255, 0.18);
732
1093
  }
733
- .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 {
734
1105
  width: 14px;
735
1106
  height: 14px;
736
1107
  }
@@ -742,20 +1113,42 @@
742
1113
  height: 14px;
743
1114
  background: var(--aw-primary);
744
1115
  border: 2px solid var(--aw-bg);
745
- border-radius: 3px;
1116
+ border-radius: 4px;
746
1117
  cursor: nwse-resize;
747
1118
  z-index: 10;
748
1119
  user-select: none;
749
1120
  opacity: 0;
750
- 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;
751
1133
  }
752
1134
  .aw-content > figure:hover .aw-resize-handle,
753
1135
  .aw-content > div.aw-youtube-embed:hover .aw-resize-handle,
754
1136
  .aw-resizing .aw-resize-handle {
755
1137
  opacity: 1;
1138
+ transform: none;
756
1139
  }
757
1140
  .aw-resize-handle:hover {
758
- 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;
759
1152
  }
760
1153
 
761
1154
  /* 유튜브 임베드는 iframe 안에 자체 워터마크/컨트롤이 있어 코너에 핸들을 두면
@@ -775,6 +1168,30 @@
775
1168
  .aeriz-wysiwyg-viewer .aw-block-align-controls {
776
1169
  display: none !important;
777
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
+ }
778
1195
  .aw-toolbar {
779
1196
  display: flex;
780
1197
  align-items: center;
@@ -820,13 +1237,28 @@
820
1237
  border-radius: var(--aw-radius-sm);
821
1238
  cursor: pointer;
822
1239
  color: var(--aw-toolbar-fg);
823
- 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);
824
1246
  }
825
1247
 
826
1248
  .aw-toolbar-btn:hover:not(:disabled) {
827
1249
  background-color: var(--aw-btn-hover-bg);
828
1250
  }
829
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
+
830
1262
  .aw-toolbar-btn:disabled {
831
1263
  opacity: 0.4;
832
1264
  cursor: not-allowed;
@@ -841,6 +1273,42 @@
841
1273
  background-color: var(--aw-btn-active-bg);
842
1274
  }
843
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
+
844
1312
  .aw-toolbar-btn-icon {
845
1313
  display: inline-flex;
846
1314
  width: 18px;
@@ -888,6 +1356,14 @@
888
1356
  --d-on-primary: #050810;
889
1357
  --d-overlay: rgba(0, 0, 0, 0.45);
890
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);
891
1367
  }
892
1368
 
893
1369
  .aw-dialog-overlay[data-theme='dark'],
@@ -1179,9 +1655,21 @@
1179
1655
  border: 1px solid var(--d-border);
1180
1656
  cursor: pointer;
1181
1657
  padding: 0;
1182
- 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);
1183
1672
  }
1184
- .aw-color-swatch:hover { transform: scale(1.15); }
1185
1673
 
1186
1674
  .aw-color-section {
1187
1675
  font-size: 10px;
@@ -1224,8 +1712,16 @@
1224
1712
  color: var(--d-fg);
1225
1713
  font-family: inherit;
1226
1714
  font-size: 12.5px;
1715
+ transition:
1716
+ background-color var(--aw-dur-fast, 90ms) ease,
1717
+ color var(--aw-dur-fast, 90ms) ease;
1227
1718
  }
1228
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
+ }
1229
1725
 
1230
1726
  .aw-popover-item-glyph {
1231
1727
  display: inline-flex;
@@ -1238,6 +1734,17 @@
1238
1734
  font-family: var(--d-mono);
1239
1735
  font-size: 10px;
1240
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);
1241
1748
  }
1242
1749
  .aw-popover-item-glyph svg { width: 11px; height: 11px; }
1243
1750
 
@@ -1361,9 +1868,18 @@
1361
1868
  cursor: pointer;
1362
1869
  transition: background-color 0.06s, border-color 0.06s;
1363
1870
  }
1871
+ /* 선택된 칸은 살짝 안으로 눌러 담긴다 — 몇 칸이 잡혔는지 숫자를 읽지 않고도
1872
+ 면적으로 먼저 보인다. */
1364
1873
  .aw-table-picker-grid .aw-table-cell.aw-table-cell-active {
1365
1874
  background-color: var(--d-primary-soft);
1366
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);
1367
1883
  }
1368
1884
  .aw-table-popover .aw-table-status {
1369
1885
  text-align: center;