@storkspear/post-editor-react 0.1.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 (192) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +12 -0
  3. package/dist/ColorPicker.d.ts +11 -0
  4. package/dist/ColorPicker.d.ts.map +1 -0
  5. package/dist/ColorPicker.js +30 -0
  6. package/dist/ColorPicker.js.map +1 -0
  7. package/dist/DrawioModal.d.ts +9 -0
  8. package/dist/DrawioModal.d.ts.map +1 -0
  9. package/dist/DrawioModal.js +97 -0
  10. package/dist/DrawioModal.js.map +1 -0
  11. package/dist/FontMenu.d.ts +14 -0
  12. package/dist/FontMenu.d.ts.map +1 -0
  13. package/dist/FontMenu.js +14 -0
  14. package/dist/FontMenu.js.map +1 -0
  15. package/dist/Icon.d.ts +69 -0
  16. package/dist/Icon.d.ts.map +1 -0
  17. package/dist/Icon.js +47 -0
  18. package/dist/Icon.js.map +1 -0
  19. package/dist/InsertHandle.d.ts +14 -0
  20. package/dist/InsertHandle.d.ts.map +1 -0
  21. package/dist/InsertHandle.js +60 -0
  22. package/dist/InsertHandle.js.map +1 -0
  23. package/dist/PickMenu.d.ts +61 -0
  24. package/dist/PickMenu.d.ts.map +1 -0
  25. package/dist/PickMenu.js +57 -0
  26. package/dist/PickMenu.js.map +1 -0
  27. package/dist/Popover.d.ts +20 -0
  28. package/dist/Popover.d.ts.map +1 -0
  29. package/dist/Popover.js +22 -0
  30. package/dist/Popover.js.map +1 -0
  31. package/dist/PostTitle.d.ts +33 -0
  32. package/dist/PostTitle.d.ts.map +1 -0
  33. package/dist/PostTitle.js +171 -0
  34. package/dist/PostTitle.js.map +1 -0
  35. package/dist/SelectionBubble.d.ts +6 -0
  36. package/dist/SelectionBubble.d.ts.map +1 -0
  37. package/dist/SelectionBubble.js +71 -0
  38. package/dist/SelectionBubble.js.map +1 -0
  39. package/dist/TableTools.d.ts +10 -0
  40. package/dist/TableTools.d.ts.map +1 -0
  41. package/dist/TableTools.js +200 -0
  42. package/dist/TableTools.js.map +1 -0
  43. package/dist/Toolbar.d.ts +17 -0
  44. package/dist/Toolbar.d.ts.map +1 -0
  45. package/dist/Toolbar.js +333 -0
  46. package/dist/Toolbar.js.map +1 -0
  47. package/dist/UrlPrompt.d.ts +24 -0
  48. package/dist/UrlPrompt.d.ts.map +1 -0
  49. package/dist/UrlPrompt.js +53 -0
  50. package/dist/UrlPrompt.js.map +1 -0
  51. package/dist/VariantMenu.d.ts +47 -0
  52. package/dist/VariantMenu.d.ts.map +1 -0
  53. package/dist/VariantMenu.js +21 -0
  54. package/dist/VariantMenu.js.map +1 -0
  55. package/dist/WidthToggle.d.ts +19 -0
  56. package/dist/WidthToggle.d.ts.map +1 -0
  57. package/dist/WidthToggle.js +17 -0
  58. package/dist/WidthToggle.js.map +1 -0
  59. package/dist/bubbleShows.d.ts +10 -0
  60. package/dist/bubbleShows.d.ts.map +1 -0
  61. package/dist/bubbleShows.js +29 -0
  62. package/dist/bubbleShows.js.map +1 -0
  63. package/dist/content-baked.d.ts +22 -0
  64. package/dist/content-baked.d.ts.map +1 -0
  65. package/dist/content-baked.js +21 -0
  66. package/dist/content-baked.js.map +1 -0
  67. package/dist/content.d.ts +29 -0
  68. package/dist/content.d.ts.map +1 -0
  69. package/dist/content.js +35 -0
  70. package/dist/content.js.map +1 -0
  71. package/dist/extensions/DiagramView.d.ts +26 -0
  72. package/dist/extensions/DiagramView.d.ts.map +1 -0
  73. package/dist/extensions/DiagramView.js +146 -0
  74. package/dist/extensions/DiagramView.js.map +1 -0
  75. package/dist/extensions/FigureView.d.ts +3 -0
  76. package/dist/extensions/FigureView.d.ts.map +1 -0
  77. package/dist/extensions/FigureView.js +108 -0
  78. package/dist/extensions/FigureView.js.map +1 -0
  79. package/dist/index.d.ts +100 -0
  80. package/dist/index.d.ts.map +1 -0
  81. package/dist/index.js +166 -0
  82. package/dist/index.js.map +1 -0
  83. package/dist/objects/ObjectFloat.d.ts +11 -0
  84. package/dist/objects/ObjectFloat.d.ts.map +1 -0
  85. package/dist/objects/ObjectFloat.js +42 -0
  86. package/dist/objects/ObjectFloat.js.map +1 -0
  87. package/dist/objects/ObjectLayer.d.ts +10 -0
  88. package/dist/objects/ObjectLayer.d.ts.map +1 -0
  89. package/dist/objects/ObjectLayer.js +20 -0
  90. package/dist/objects/ObjectLayer.js.map +1 -0
  91. package/dist/objects/blockSize.d.ts +30 -0
  92. package/dist/objects/blockSize.d.ts.map +1 -0
  93. package/dist/objects/blockSize.js +56 -0
  94. package/dist/objects/blockSize.js.map +1 -0
  95. package/dist/objects/code.d.ts +3 -0
  96. package/dist/objects/code.d.ts.map +1 -0
  97. package/dist/objects/code.js +55 -0
  98. package/dist/objects/code.js.map +1 -0
  99. package/dist/objects/diagram.d.ts +21 -0
  100. package/dist/objects/diagram.d.ts.map +1 -0
  101. package/dist/objects/diagram.js +109 -0
  102. package/dist/objects/diagram.js.map +1 -0
  103. package/dist/objects/diagramGlyphs.d.ts +12 -0
  104. package/dist/objects/diagramGlyphs.d.ts.map +1 -0
  105. package/dist/objects/diagramGlyphs.js +49 -0
  106. package/dist/objects/diagramGlyphs.js.map +1 -0
  107. package/dist/objects/divider.d.ts +22 -0
  108. package/dist/objects/divider.d.ts.map +1 -0
  109. package/dist/objects/divider.js +60 -0
  110. package/dist/objects/divider.js.map +1 -0
  111. package/dist/objects/figure.d.ts +3 -0
  112. package/dist/objects/figure.d.ts.map +1 -0
  113. package/dist/objects/figure.js +62 -0
  114. package/dist/objects/figure.js.map +1 -0
  115. package/dist/objects/imageRow.d.ts +3 -0
  116. package/dist/objects/imageRow.d.ts.map +1 -0
  117. package/dist/objects/imageRow.js +29 -0
  118. package/dist/objects/imageRow.js.map +1 -0
  119. package/dist/objects/quote.d.ts +6 -0
  120. package/dist/objects/quote.d.ts.map +1 -0
  121. package/dist/objects/quote.js +13 -0
  122. package/dist/objects/quote.js.map +1 -0
  123. package/dist/objects/quoteGlyphs.d.ts +6 -0
  124. package/dist/objects/quoteGlyphs.d.ts.map +1 -0
  125. package/dist/objects/quoteGlyphs.js +28 -0
  126. package/dist/objects/quoteGlyphs.js.map +1 -0
  127. package/dist/objects/quoteRow.d.ts +3 -0
  128. package/dist/objects/quoteRow.d.ts.map +1 -0
  129. package/dist/objects/quoteRow.js +61 -0
  130. package/dist/objects/quoteRow.js.map +1 -0
  131. package/dist/objects/registry.d.ts +60 -0
  132. package/dist/objects/registry.d.ts.map +1 -0
  133. package/dist/objects/registry.js +45 -0
  134. package/dist/objects/registry.js.map +1 -0
  135. package/dist/objects/table.d.ts +11 -0
  136. package/dist/objects/table.d.ts.map +1 -0
  137. package/dist/objects/table.js +136 -0
  138. package/dist/objects/table.js.map +1 -0
  139. package/dist/objects/useDomRect.d.ts +14 -0
  140. package/dist/objects/useDomRect.d.ts.map +1 -0
  141. package/dist/objects/useDomRect.js +32 -0
  142. package/dist/objects/useDomRect.js.map +1 -0
  143. package/dist/objects/useEngaged.d.ts +14 -0
  144. package/dist/objects/useEngaged.d.ts.map +1 -0
  145. package/dist/objects/useEngaged.js +41 -0
  146. package/dist/objects/useEngaged.js.map +1 -0
  147. package/dist/pickItems.d.ts +36 -0
  148. package/dist/pickItems.d.ts.map +1 -0
  149. package/dist/pickItems.js +40 -0
  150. package/dist/pickItems.js.map +1 -0
  151. package/dist/recentColors.d.ts +7 -0
  152. package/dist/recentColors.d.ts.map +1 -0
  153. package/dist/recentColors.js +30 -0
  154. package/dist/recentColors.js.map +1 -0
  155. package/dist/seeds/drawio/aws.d.ts +15 -0
  156. package/dist/seeds/drawio/aws.d.ts.map +1 -0
  157. package/dist/seeds/drawio/aws.js +15 -0
  158. package/dist/seeds/drawio/aws.js.map +1 -0
  159. package/dist/seeds/drawio/azure.d.ts +14 -0
  160. package/dist/seeds/drawio/azure.d.ts.map +1 -0
  161. package/dist/seeds/drawio/azure.js +14 -0
  162. package/dist/seeds/drawio/azure.js.map +1 -0
  163. package/dist/seeds/drawio/ec2docker.d.ts +15 -0
  164. package/dist/seeds/drawio/ec2docker.d.ts.map +1 -0
  165. package/dist/seeds/drawio/ec2docker.js +15 -0
  166. package/dist/seeds/drawio/ec2docker.js.map +1 -0
  167. package/dist/seeds/drawio/userflow.d.ts +21 -0
  168. package/dist/seeds/drawio/userflow.d.ts.map +1 -0
  169. package/dist/seeds/drawio/userflow.js +21 -0
  170. package/dist/seeds/drawio/userflow.js.map +1 -0
  171. package/dist/seeds/mermaid.d.ts +19 -0
  172. package/dist/seeds/mermaid.d.ts.map +1 -0
  173. package/dist/seeds/mermaid.js +82 -0
  174. package/dist/seeds/mermaid.js.map +1 -0
  175. package/dist/useAnchoredPos.d.ts +22 -0
  176. package/dist/useAnchoredPos.d.ts.map +1 -0
  177. package/dist/useAnchoredPos.js +57 -0
  178. package/dist/useAnchoredPos.js.map +1 -0
  179. package/dist/useDismiss.d.ts +17 -0
  180. package/dist/useDismiss.d.ts.map +1 -0
  181. package/dist/useDismiss.js +36 -0
  182. package/dist/useDismiss.js.map +1 -0
  183. package/dist/useLastVariant.d.ts +9 -0
  184. package/dist/useLastVariant.d.ts.map +1 -0
  185. package/dist/useLastVariant.js +33 -0
  186. package/dist/useLastVariant.js.map +1 -0
  187. package/dist/useUploadCount.d.ts +3 -0
  188. package/dist/useUploadCount.d.ts.map +1 -0
  189. package/dist/useUploadCount.js +9 -0
  190. package/dist/useUploadCount.js.map +1 -0
  191. package/package.json +57 -0
  192. package/src/editor-ui.css +737 -0
@@ -0,0 +1,737 @@
1
+ /**
2
+ * 편집 UI 의 겉모습 — 툴바·버블·팝오버·오버레이.
3
+ *
4
+ * 본문(`prose.css`)과 갈라 둔다: 본문 CSS 는 **발행 페이지도** 쓰고,
5
+ * 이 파일은 **편집기에만** 필요하다. 공개 빌드에 편집 UI 가 한 줄도 안 들어가야 한다.
6
+ */
7
+ * { box-sizing: border-box; }
8
+ html, body, #root { height: 100%; }
9
+ body { margin: 0; background: #fff; color: #1a1a1a;
10
+ font-family: Pretendard, -apple-system, 'Apple SD Gothic Neo', 'Noto Sans KR', system-ui, sans-serif; }
11
+
12
+ .tb-group { display: flex; gap: 2px; padding-right: 0.4rem; flex: none;
13
+ border-right: 1px solid #eeece6; align-items: center; }
14
+ .tb-group:last-child { border-right: 0; }
15
+ .tb-btn { min-width: 36px; height: 36px; padding: 0 8px; flex: none;
16
+ border: 1px solid transparent; background: transparent; border-radius: 7px;
17
+ font-size: 0.85rem; color: #3a3a3a; cursor: pointer; line-height: 1; }
18
+ .tb-btn:hover:not(:disabled) { background: #f2f1ec; }
19
+ .tb-btn.is-on { background: #1a1a1a; color: #fff; }
20
+ .tb-btn:disabled { opacity: 0.3; cursor: default; }
21
+ .tb-swatch { width: 22px; height: 22px; border-radius: 50%; border: 1px solid #d8d6d0;
22
+ cursor: pointer; flex: none; margin: 0 1px; padding: 0; }
23
+ /* 고르는 칸(글꼴·문단·크기) — **상자를 두르지 않는다.**
24
+ 테두리를 치면 버튼이 아닌데 버튼처럼 보이고, 옆의 진짜 버튼들과 무게가 같아져 줄이 시끄럽다.
25
+ 네이버도 글자 + 작은 꺾쇠만 둔다. */
26
+ .tb-select {
27
+ display: inline-flex; align-items: center; flex: none;
28
+ height: 32px; border: 0; border-radius: 6px; background: transparent;
29
+ font-size: 0.86rem; color: #3a3a3a; cursor: pointer; text-align: left;
30
+ padding: 0 1.15rem 0 0.45rem; white-space: nowrap;
31
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='9' height='6' viewBox='0 0 9 6'%3E%3Cpath d='M1 1l3.5 3.5L8 1' fill='none' stroke='%238a8a8a' stroke-width='1.3' stroke-linecap='round'/%3E%3C/svg%3E");
32
+ background-repeat: no-repeat; background-position: right 0.35rem center;
33
+ }
34
+ .tb-select:hover { background-color: #f4f3ef; }
35
+ .tb-select.is-open { background-color: #eceae4; }
36
+ .tb-select:focus-visible { outline: 2px solid #2563eb; outline-offset: -2px; }
37
+ /* 아이콘만 든 고르기 칸(줄 간격) — 꺾쇠를 빼고 아이콘 버튼과 같은 크기로 선다 */
38
+ .tb-iconselect { background-image: none; padding: 0; width: 36px; justify-content: center; }
39
+
40
+ .editor-surface { outline: none; }
41
+ /**
42
+ * 글 끝의 여유 — 마지막 줄이 창 바닥에 붙지 않게 한다.
43
+ *
44
+ * 패딩이 없으면 문서 끝까지 내렸을 때 마지막 문단의 바닥이 곧 편집면의 바닥이라,
45
+ * 커서가 창 맨 아래 모서리에 붙는다. 거기서는 글자 버블도 드롭다운도 펼 자리가 없고,
46
+ * 무엇보다 **더 쓸 자리가 눈에 안 보여** 글이 끝난 것처럼 읽힌다.
47
+ * 네이버 블로그는 문서 끝까지 내려도 마지막 줄 밑에 약 245px(창 높이 1025px 기준)를
48
+ * 남겨 둔다 — 네이버 블로그도 창 높이의 약 24% 를 남긴다.
49
+ *
50
+ * **패딩은 contenteditable 안쪽에 준다.** 바깥 껍데기에 주면 그 빈 곳을 눌러도
51
+ * 아무 일이 없지만, 안쪽이면 ProseMirror 가 가장 가까운 자리 = 문서 끝으로 커서를 보낸다.
52
+ * 창이 낮을 때 화면을 반이나 먹지 않도록 vh 와 상한을 같이 건다.
53
+ */
54
+ .editor-surface .ProseMirror { outline: none; min-height: 50vh; padding-bottom: min(30vh, 260px); }
55
+ .editor-surface .ProseMirror-selectednode { outline: 2px solid #2563eb; border-radius: 4px; }
56
+ .editor-surface .selectedCell { background: #e8effb; }
57
+ /* 열 너비 손잡이 — 편집 전용. 위치 선언이 없으면 삽입돼도 크기가 0 이라 안 보인다.
58
+ 네이버처럼 끄는 동안 세로 안내선으로 읽히도록 셀 높이를 꽉 채운다. */
59
+ .editor-surface .column-resize-handle {
60
+ position: absolute; right: -2px; top: 0; bottom: 0; width: 3px;
61
+ background: #2563eb; pointer-events: none; z-index: 3;
62
+ }
63
+ /* 경계 위에 있을 때 커서를 바꿔 끌 수 있다는 걸 알린다 */
64
+ .editor-surface .ProseMirror.resize-cursor { cursor: col-resize; }
65
+
66
+ /* ── 툴바 v2 ── */
67
+ .tb-btn { display: inline-flex; align-items: center; justify-content: center; }
68
+ .tb-btn svg { display: block; }
69
+ .tb-txt { font-size: 0.76rem; letter-spacing: -0.02em; }
70
+ .tb-hint { font-size: 0.72rem; color: #9aa0aa; white-space: nowrap; padding: 0 2px; }
71
+
72
+ .tb-size { justify-content: center; }
73
+ .tb-unit { font-size: 0.72rem; color: #9a9a9a; margin: 0 6px 0 -2px; flex: none; }
74
+
75
+ .tb-swatch.is-on { box-shadow: 0 0 0 2px #fff, 0 0 0 3.5px #1a1a1a; }
76
+
77
+ /* 문맥 그룹 — 목록/표 안에 있을 때만 나타난다 */
78
+ .tb-context { background: #f5f4f0; border-radius: 8px; padding: 3px 6px 3px 8px;
79
+ border-right: 0; margin-left: 2px; animation: ctxIn .14s ease-out; }
80
+ @keyframes ctxIn { from { opacity: 0; transform: translateY(-2px); } to { opacity: 1; transform: none; } }
81
+
82
+ @media (max-width: 640px) {
83
+ .tb-btn { min-width: 40px; height: 40px; } /* 폰 터치 타깃 */
84
+ .tb-size { min-width: 50px; }
85
+ }
86
+
87
+
88
+ /* ══ 툴바 v3 — 네이버 구조 참고: 윗줄 넣기(아이콘+글자) / 아랫줄 서식(아이콘) ══ */
89
+ /* 편집 도구는 **한 덩이의 띠**여야 한다.
90
+ 소비자 레이아웃의 좌우 패딩 안에 갇히면 왼쪽에 빈 칸이 생겨 「어디까지가 도구인지」가 흐려진다.
91
+ 음수 마진으로 그 패딩을 뚫고 나가고, 뚫은 만큼 안쪽에서 되돌려 준다. */
92
+ /* 붙는 자리는 **소비자가 알려 준다.** 여기에 숫자를 박아 두면 소비자의 머리줄 높이가 바뀌는 날
93
+ 툴바가 그 밑으로 파고들어 겹친다. */
94
+ .toolbar2 {
95
+ position: sticky; top: var(--editor-sticky-top, 0px); z-index: 20; background: #fff;
96
+ margin-inline: calc(-1 * var(--editor-gutter, 0px));
97
+ border-bottom: 1px solid #dedbd4;
98
+ box-shadow: 0 1px 0 #fff, 0 2px 6px rgba(0, 0, 0, 0.035);
99
+ }
100
+ /* 도구 띠의 안쪽 여백은 **소비자 패딩과 무관하게** 작게 — 본문 글줄에 맞추면 도구가
101
+ 가운데로 밀려 띠의 왼쪽이 비어 보인다. 네이버도 화면 가장자리에 가깝게 붙여 둔다. */
102
+ .toolbar2 .tb-row { padding-inline: 0.85rem; }
103
+ /* ⚠ 한 축이 `auto` 면 다른 축의 `visible` 은 **`auto` 로 계산된다**(CSS Overflow 3).
104
+ 즉 이 줄은 세로로도 잘리는 상자다 — 그래서 메뉴·팝오버가 전부 포털로 빠져 있다.
105
+ `visible` 이라고 적어 두면 읽는 사람이 안 잘린다고 믿는다. 계산되는 값을 그대로 적는다. */
106
+ .tb-row { display: flex; align-items: center; gap: 2px; overflow: auto hidden;
107
+ -webkit-overflow-scrolling: touch; scrollbar-width: thin; }
108
+ .tb-row-insert { padding-block: 0.5rem 0.4rem; border-bottom: 1px solid #f0eee9; gap: 4px; }
109
+ .tb-row-format { padding-block: 0.32rem; }
110
+ /* 편집기 밖에 손이 가 있는 동안 — 누를 수 있게 두면 글이 엉뚱한 데 걸리고 화면이 튄다.
111
+ 줄 **전체**가 클릭을 안 받아야 한다. 자식만 막으면 줄의 빈 자리를 누른 것이
112
+ 「편집 UI 를 눌렀다」로 세어져 잠금이 풀린다(그래서 두 번째 클릭에서 다시 튄다). */
113
+ .tb-row-format.is-idle { opacity: 0.42; pointer-events: none; }
114
+ .tb-row-format.is-idle .tb-sheet-btn { pointer-events: auto; }
115
+
116
+ /* 넣기 타일 */
117
+ /* 위아래가 빡빡하면 아이콘과 글자가 서로 붙어 버튼이 눌린 것처럼 보인다.
118
+ 숨 쉴 자리를 준다 — 넣기 줄은 이 편집기에서 제일 먼저 눈이 가는 줄이다. */
119
+ .tb-tile { display: flex; flex-direction: column; align-items: center; gap: 4px; flex: none;
120
+ min-width: 58px; padding: 9px 10px 8px; border: 1px solid transparent; border-radius: 10px;
121
+ background: transparent; color: #3a3a3a; cursor: pointer; }
122
+ .tb-tile:hover { background: #f4f3ef; }
123
+ .tb-tile.is-on { background: #1a1a1a; color: #fff; }
124
+ .tb-tile-ico { display: flex; }
125
+ .tb-tile-label { font-size: 0.7rem; letter-spacing: -0.03em; display: inline-flex; align-items: center; gap: 2px; white-space: nowrap; }
126
+ .tb-caret { width: 0; height: 0; border-left: 3.5px solid transparent; border-right: 3.5px solid transparent;
127
+ border-top: 4px solid currentColor; display: inline-block; opacity: .55; }
128
+
129
+ /* 구분자 */
130
+ .tb-div { width: 1px; height: 20px; background: #e6e4de; flex: none; margin: 0 6px; }
131
+
132
+ /* ▾ 메뉴 */
133
+ .tb-anchor { position: relative; flex: none; display: flex; }
134
+ .tb-caret-btn { display: inline-flex; align-items: center; gap: 2px; padding: 0 6px; }
135
+ .tb-menu { position: absolute; top: calc(100% + 5px); left: 0; z-index: 60; min-width: 190px;
136
+ background: #fff; border: 1px solid #e0ded8; border-radius: 10px; padding: 5px;
137
+ box-shadow: 0 8px 26px rgba(0,0,0,.13); }
138
+ .tb-menu button { display: flex; align-items: center; gap: 9px; width: 100%; text-align: left;
139
+ border: 0; background: transparent; border-radius: 7px; padding: 7px 9px; cursor: pointer; color: #2a2a2a;
140
+ min-height: 40px; } /* 폰 터치 타깃 — 34px 이면 손가락으로 못 누른다 */
141
+ .tb-menu button:hover { background: #f4f3ef; }
142
+ .tb-menu button.on { background: #1a1a1a; color: #fff; }
143
+ .tb-menu button span { display: flex; flex-direction: column; line-height: 1.3; }
144
+ .tb-menu button b { font-size: 0.83rem; font-weight: 600; }
145
+ .tb-menu button i { font-size: 0.72rem; opacity: .62; font-style: normal; }
146
+
147
+ .tb-block { min-width: 66px; }
148
+ .tb-size { min-width: 46px; padding-left: 0.3rem; justify-content: center; }
149
+ .tb-lang { min-width: 82px; }
150
+
151
+ @media (max-width: 640px) {
152
+ .tb-tile { min-width: 52px; padding: 7px 7px 6px; }
153
+ .tb-btn { min-width: 40px; height: 40px; }
154
+ }
155
+
156
+ /* ── 글 제목 — 편집 전용 ──
157
+ 모양은 전부 core 의 prose.css 가 갖는다(발행 페이지와 같은 파일).
158
+ 여기 있는 것은 **편집 중에만 있는 것**뿐이다. */
159
+ .post-head.is-editing { margin-top: 6.2rem; margin-bottom: 6rem; }
160
+ /* 배너 모드에서는 위 여백을 안 준다 — prose.css 의 규칙과 같은 뜻이다 */
161
+ .post-head.is-editing[data-banner='1'] { margin-top: 0; }
162
+ /* 밑줄은 header 가 갖는다(prose.css) — 포커스 표시도 그 선을 진하게 한다.
163
+ 제목이 사진 위에 없으므로 배너 유무를 가르지 않는다. */
164
+ .post-head.is-editing:focus-within { border-bottom-color: #1a1a1a; }
165
+ /* 안내 글씨는 **안내처럼** — 제목과 같은 굵기·검정이면 이미 쓴 제목으로 읽힌다.
166
+ contenteditable 이라 ::placeholder 가 없다. 빈 상태에서만 ::before 로 그린다. */
167
+ .post-head.is-editing .post-title:empty::before {
168
+ content: attr(data-placeholder); color: #c7c4bd; letter-spacing: -0.02em;
169
+ }
170
+
171
+ /* 제목 도구 — 전부 absolute 라 제목의 기하를 건드리지 않는다 */
172
+ .ph-tools, .ph-actions, .ph-badge, .ph-hint { position: absolute; z-index: 2; }
173
+ /* 배너 넣기 버튼은 제목 **위쪽 빈 자리**에 둔다 — 제목 첫 줄과 나란히 두면
174
+ 글자에 붙어 보이고, 제목을 길게 쓰면 겹친다.
175
+ 위 여백이 6.2rem 이라 -40px 은 그 안에 넉넉히 들어간다. */
176
+ .ph-tools { top: -40px; right: 0; display: flex; gap: 4px; }
177
+ .ph-tools button, .ph-actions button, .ph-badge {
178
+ display: inline-flex; align-items: center; gap: 4px; cursor: pointer;
179
+ border: 1px solid #e0ded8; background: #fff; border-radius: 7px;
180
+ min-height: 30px; padding: 0 9px; font-size: 0.76rem; color: #3a3a3a;
181
+ }
182
+ .ph-tools button:hover { background: #f4f3ef; }
183
+ .ph-tools button:disabled { opacity: .45; cursor: default; }
184
+ /* 배너 위 버튼은 사진 위에 뜬다 — 흰 배경으로는 사진이 밝을 때 묻힌다 */
185
+ .post-head[data-banner='1'] .ph-actions button,
186
+ .post-head[data-banner='1'] .ph-badge {
187
+ border-color: rgba(255,255,255,.34); background: rgba(0,0,0,.44); color: #fff;
188
+ -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px);
189
+ }
190
+ .post-head[data-banner='1'] .ph-actions button:hover { background: rgba(0,0,0,.62); }
191
+ .ph-badge { top: 10px; left: 10px; }
192
+ .ph-badge[aria-pressed='false'] { opacity: .62; }
193
+ .ph-actions { top: 10px; right: 10px; display: flex; gap: 6px; }
194
+ .ph-actions .is-go { background: #1f9d55 !important; border-color: #1f9d55 !important; color: #fff !important; }
195
+ /* 안내문은 **사진 가운데**에 뜬다 — header 기준이면 제목까지 포함한 가운데라
196
+ 사진 아래쪽 여백에 걸린다. `.post-cover` 가 position:relative 다. */
197
+ .ph-hint {
198
+ left: 0; right: 0; top: 50%; transform: translateY(-50%);
199
+ margin: 0; text-align: center; color: #fff; font-size: 0.95rem;
200
+ text-shadow: 0 1px 3px rgba(0,0,0,.5); pointer-events: none;
201
+ }
202
+ .post-head.is-moving .post-cover { touch-action: none; cursor: grab; }
203
+ .post-head.is-moving .post-cover:active { cursor: grabbing; }
204
+
205
+ /* ── 색 팔레트 ── */
206
+ .cp { position: absolute; top: calc(100% + 6px); left: 0; z-index: 70;
207
+ background: #fff; border: 1px solid #e0ded8; border-radius: 12px; padding: 10px;
208
+ box-shadow: 0 10px 30px rgba(0,0,0,.14); }
209
+ .cp-head { font-size: 0.72rem; color: #8a8a8a; margin: 0 2px 7px; letter-spacing: -0.02em; }
210
+ .cp-grid { display: flex; gap: 4px; align-items: flex-start; }
211
+ .cp-col { display: flex; flex-direction: column; gap: 4px; }
212
+ .cp-cell { width: 19px; height: 19px; border-radius: 4px; border: 1px solid rgba(0,0,0,.10);
213
+ cursor: pointer; padding: 0; flex: none; }
214
+ .cp-cell:hover { transform: scale(1.18); border-color: rgba(0,0,0,.28); }
215
+ .cp-cell.is-on { box-shadow: 0 0 0 2px #fff, 0 0 0 3.5px #1a1a1a; }
216
+ .cp-none { background: #fff; display: flex; align-items: center; justify-content: center;
217
+ align-self: flex-start; }
218
+ .cp-none svg { width: 15px; height: 15px; stroke: #e03131; stroke-width: 1.8; }
219
+
220
+ /* ── 선택 시 뜨는 버블 툴바 ── */
221
+ /* 인용 안에서는 인용 모양 일곱 개가 앞에 붙어 줄이 길어진다 — 좁은 화면에서는 옆으로 굴린다.
222
+ (여기에 overflow 를 걸 수 있게 된 건 팝오버들이 전부 포털로 빠져나간 뒤다.
223
+ 안 그랬으면 한 축만 auto 여도 나머지 축이 같이 잘려 드롭다운이 통째로 사라진다.) */
224
+ .bub { display: flex; align-items: center; gap: 2px; background: #fff;
225
+ border: 1px solid #e0ded8; border-radius: 10px; padding: 4px 5px;
226
+ box-shadow: 0 6px 22px rgba(0,0,0,.15);
227
+ max-width: min(92vw, 780px); overflow-x: auto; scrollbar-width: thin; }
228
+ .bub > * { flex: none; }
229
+ .bub button { display: inline-flex; align-items: center; justify-content: center; gap: 3px;
230
+ min-width: 30px; height: 30px; padding: 0 6px; border: 0; background: transparent;
231
+ border-radius: 7px; color: #3a3a3a; cursor: pointer; }
232
+ .bub button:hover { background: #f4f3ef; }
233
+ .bub button.on { background: #1a1a1a; color: #fff; }
234
+ .bub-div { width: 1px; height: 18px; background: #e6e4de; margin: 0 3px; flex: none; }
235
+ .bub-size { min-width: 44px; }
236
+ .bub-anchor { position: relative; display: flex; }
237
+ .bub-letter { font-size: 0.82rem; font-weight: 700; line-height: 1; }
238
+ .bub-swatch { width: 12px; height: 12px; border-radius: 3px; border: 1px solid rgba(0,0,0,.14); }
239
+ .cp-sub { font-size: 0.7rem; color: #9a9a9a; margin: 2px 2px 5px; }
240
+ .cp-recent { display: flex; gap: 4px; flex-wrap: wrap; margin-bottom: 9px;
241
+ padding-bottom: 9px; border-bottom: 1px solid #efedE8; max-width: 240px; }
242
+
243
+ /* ══ 표 조작 오버레이 — 네이버 구조 참고, 색은 초록 대신 차분한 남색 계열 ══ */
244
+ .tt-layer { position: fixed; inset: 0; pointer-events: none; z-index: 40; }
245
+ .tt-layer > *, .tt-layer button { pointer-events: auto; }
246
+
247
+ .tt-corner { position: fixed; width: 12px; height: 12px; padding: 0; cursor: pointer;
248
+ background: #e8e6e0; border: 1px solid #d8d6d0; border-radius: 3px 0 0 0; }
249
+ .tt-corner:hover { background: var(--tt-accent, #4c6ef5); border-color: var(--tt-accent, #4c6ef5); }
250
+
251
+ .tt-band { position: fixed; padding: 0; cursor: pointer; border: 1px solid #e2e0da;
252
+ background: #eeece6; border-radius: 2px; }
253
+ .tt-band:hover { background: #4c6ef5; border-color: #4c6ef5; }
254
+
255
+ .tt-plus { position: fixed; width: 18px; height: 18px; padding: 0; cursor: pointer;
256
+ display: flex; align-items: center; justify-content: center;
257
+ background: #fff; color: #8a8a8a; border: 1px solid #d8d6d0; border-radius: 50%;
258
+ opacity: 0; transition: opacity .12s, background .12s, color .12s; }
259
+ .tt-layer:hover .tt-plus { opacity: 1; }
260
+ .tt-plus:hover { background: #4c6ef5; color: #fff; border-color: #4c6ef5; opacity: 1; }
261
+
262
+ .tt-trash { position: fixed; width: 38px; height: 34px; padding: 0; cursor: pointer;
263
+ display: flex; align-items: center; justify-content: center;
264
+ background: #fff; color: #c92a2a; border: 1px solid #e0ded8; border-radius: 9px;
265
+ box-shadow: 0 4px 16px rgba(0,0,0,.14); }
266
+ .tt-trash:hover { background: #fff5f5; border-color: #ffc9c9; }
267
+
268
+ .tt-anchor { position: fixed; }
269
+ .tt-more { width: 26px; height: 26px; padding: 0; cursor: pointer; margin-left: 5px;
270
+ background: #fff; border: 1px solid #e0ded8; border-radius: 7px; color: #5b5b5b;
271
+ box-shadow: 0 3px 12px rgba(0,0,0,.12); line-height: 1; }
272
+ .tt-more:hover { background: #f4f3ef; }
273
+ .tt-menu { position: absolute; top: 30px; left: 0; min-width: 152px; z-index: 80;
274
+ background: #fff; border: 1px solid #e0ded8; border-radius: 10px; padding: 5px;
275
+ box-shadow: 0 10px 28px rgba(0,0,0,.15); display: flex; flex-direction: column; }
276
+ .tt-menu button { display: flex; align-items: center; gap: 8px; width: 100%; text-align: left;
277
+ border: 0; background: transparent; border-radius: 7px; padding: 7px 9px; cursor: pointer;
278
+ color: #2a2a2a; font-size: 0.83rem; }
279
+ .tt-menu button:hover { background: #f4f3ef; }
280
+ .tt-menu button.danger { color: #c92a2a; }
281
+ .tt-menu button.danger:hover { background: #fff5f5; }
282
+ .tt-menu-div { height: 1px; background: #efedE8; margin: 4px 2px; }
283
+
284
+ /* 선택된 셀 — 네이버 초록 대신 차분한 남색 틴트 */
285
+ .editor-surface .selectedCell { position: relative; background: rgba(76,110,245,.10); }
286
+ .editor-surface .selectedCell::after { content: ''; position: absolute; inset: 0;
287
+ border: 1px solid rgba(76,110,245,.35); pointer-events: none; }
288
+
289
+ /* 표 너비 슬라이더 */
290
+ .tt-width { position: fixed; display: flex; align-items: center; gap: 8px;
291
+ background: #fff; border: 1px solid #e0ded8; border-radius: 9px; padding: 5px 11px;
292
+ box-shadow: 0 4px 16px rgba(0,0,0,.11); font-size: 0.76rem; color: #5b5b5b; }
293
+ .tt-width input[type='range'] { width: 104px; accent-color: #4c6ef5; }
294
+ .tt-width b { color: #1a1a1a; font-variant-numeric: tabular-nums; min-width: 2.2em; text-align: right; }
295
+ .tt-width i { font-style: normal; color: #9a9a9a; }
296
+ .prose table { table-layout: fixed; }
297
+
298
+ .tt-width-div { width: 1px; height: 18px; background: #e6e4de; margin: 0 2px; }
299
+ .tt-bg-anchor { position: relative; display: flex; }
300
+ .tt-bg { width: 28px; height: 26px; padding: 0; display: flex; align-items: center; justify-content: center;
301
+ border: 0; background: transparent; border-radius: 7px; cursor: pointer; color: #3a3a3a; }
302
+ .tt-bg:hover { background: #f2f1ec; }
303
+
304
+ /* 표 폭은 감싸개가 갖는다 — 라이브러리가 table 인라인 width 를 지우므로 !important 로 고정 */
305
+ .editor-surface .tableWrapper > table,
306
+ .prose .tableWrapper > table { min-width: 0 !important; width: 100% !important; }
307
+
308
+ /* 아랫줄 안의 인라인 폭 슬라이더 (표) */
309
+ .tb-width-inline { display: inline-flex; align-items: center; gap: 6px; flex: none;
310
+ font-size: 0.76rem; color: #5b5b5b; white-space: nowrap; }
311
+ .tb-width-inline input[type='range'] { width: 92px; accent-color: #4c6ef5; }
312
+ .tb-width-inline b { color: #1a1a1a; font-variant-numeric: tabular-nums; min-width: 2.1em; text-align: right; }
313
+ .tb-width-inline i { font-style: normal; color: #9a9a9a; }
314
+ .tb-phone-only { display: none; }
315
+ @media (max-width: 640px) { .tb-phone-only { display: inline-flex; } }
316
+
317
+ /* ── 변형 고르기 메뉴 (구분선·인용구·표 프리셋·목록 모양 공용) ── */
318
+ .vm { position: absolute; top: calc(100% + 6px); left: 0; z-index: 70;
319
+ background: #fff; border: 1px solid #e0ded8; border-radius: 11px; padding: 6px;
320
+ box-shadow: 0 10px 30px rgba(0,0,0,.15); display: grid; gap: 4px; }
321
+ .vm-col-1 { grid-template-columns: 1fr; min-width: 214px; }
322
+ /* 폭을 안 주면 격자 칸이 미리보기의 max-content 로 벌어져 팝오버가 화면을 가로지른다. 열 수마다 폭을 못 박는다. */
323
+ .vm-col-3 { grid-template-columns: repeat(3, 1fr); width: 264px; }
324
+ .vm-col-4 { grid-template-columns: repeat(4, 1fr); width: 340px; }
325
+
326
+ .vm-item { display: flex; align-items: center; gap: 10px; text-align: left;
327
+ border: 1px solid transparent; background: transparent; border-radius: 8px;
328
+ padding: 6px 8px; cursor: pointer; color: #2a2a2a; min-height: 40px; }
329
+ .vm-col-4 .vm-item, .vm-col-3 .vm-item { flex-direction: column; gap: 3px; padding: 7px 4px; text-align: center; }
330
+ .vm-item:hover { background: #f4f3ef; }
331
+ .vm-item.is-on { border-color: #1a1a1a; background: #f7f6f2; }
332
+
333
+ /* 미리보기 — 본문과 같은 CSS 가 걸리도록 .prose 를 쓰되, 지면 여백만 지운다 */
334
+ .vm-preview { display: block; flex: none; pointer-events: none;
335
+ background: transparent; overflow: hidden; }
336
+ .vm-preview > * { margin: 0 !important; max-width: none !important; }
337
+ /* 프레임 두 종은 갈고리 자리(2.4em 안팎)를 padding 으로 비워 둔다 —
338
+ 66px 짜리 미리보기에서는 그 여백이 글자를 세로로 쪼갠다. 타일에서만 좁힌다. */
339
+ .vm-preview blockquote[data-variant='frame'],
340
+ .vm-preview blockquote[data-variant='framebold'] { padding: 0.9em !important; }
341
+ .vm-col-1 .vm-preview { width: 66px; }
342
+ /* 높이를 34px 로 **고정**하면 3줄짜리 표 미리보기가 위아래로 잘려 무슨 스타일인지 안 보인다. 구분선처럼 한 줄짜리는 그대로 34px 이고, 표는 필요한 만큼 늘어난다. */
343
+ .vm-col-4 .vm-preview, .vm-col-3 .vm-preview { width: 100%; min-height: 34px;
344
+ display: flex; align-items: center; justify-content: center; }
345
+ /* 격자형 미리보기의 자식은 칸 폭을 채워야 한다 —
346
+ 그러지 않으면 width:100% 짜리 모양(긴 선·굵은 선)이 기준을 못 잡아 0px 이 된다 */
347
+ .vm-col-4 .vm-preview > *, .vm-col-3 .vm-preview > * { width: 100%; }
348
+ /* 미리보기 칸 안에서는 본문용 비율 크기를 칸 폭에 맞춰 줄인다.
349
+ (본문에서 clamp(...) 로 커지게 해 둔 값이 작은 칸을 뚫고 나간다) */
350
+ .vm-preview hr::after { max-width: 100% !important; }
351
+ .vm-preview hr[data-kind='wave']::after,
352
+ .vm-preview hr[data-kind='diamond']::after,
353
+ .vm-preview hr[data-kind='slash']::after,
354
+ .vm-preview hr[data-kind='dots']::after,
355
+ .vm-preview hr[data-kind='short']::after { width: 88% !important; }
356
+ /* 점 세 개는 폭이 60px 로 **고정**이라 줄이면 안 된다 — 줄이면 점이 두 개로 보인다 */
357
+ .vm-preview hr[data-kind='dots3']::after { width: 60px !important; }
358
+
359
+ /* ── 메뉴 바닥의 축 줄 (구분선 두께·색) ── */
360
+ .vm-axis { grid-column: 1 / -1; display: flex; align-items: center; gap: 4px;
361
+ padding: 5px 6px; border-top: 1px solid #eceae4; }
362
+ /* 이름은 **줄바꿈되면 안 된다** — 「모서리」가 26px 폭에서 두 줄로 쪼개졌다 */
363
+ .vm-axis-name { font-size: 0.72rem; color: #8a8a8a; min-width: 34px; flex: none; white-space: nowrap; }
364
+ .vm-chip { border: 1px solid #e0ded8; background: #fff; border-radius: 6px;
365
+ padding: 3px 8px; font-size: 0.74rem; color: #3a3a3a; cursor: pointer; }
366
+ .vm-chip:hover:not(:disabled) { background: #f4f3ef; }
367
+ .vm-chip.is-on { background: #1a1a1a; border-color: #1a1a1a; color: #fff; }
368
+ .vm-chip:disabled { opacity: .38; cursor: default; }
369
+ /* 색 견본 — 값이 곧 그림이라 글씨를 안 쓴다 */
370
+ .vm-dot { border: 1px solid transparent; background: none; border-radius: 50%;
371
+ padding: 2px; cursor: pointer; line-height: 0; }
372
+ .vm-dot > i { display: block; width: 15px; height: 15px; border-radius: 50%;
373
+ box-shadow: inset 0 0 0 1px rgba(0,0,0,.10); }
374
+ .vm-dot.is-on { border-color: #1a1a1a; }
375
+
376
+ /* 구역 제목 — 격자 한 줄을 통째로 쓴다. 첫 줄이면 위 여백을 뺀다(판이 뜨는 자리가 밀린다) */
377
+ .vm-head { grid-column: 1 / -1; margin: 8px 4px 2px; display: flex; align-items: baseline; gap: 6px; }
378
+ .vm-head:first-child { margin-top: 2px; }
379
+ .vm-head b { font-size: 0.74rem; font-weight: 600; color: #1a1a1a; }
380
+ .vm-head i { font-size: 0.7rem; font-style: normal; color: #9a958c; }
381
+ .vm-head + .vm-item { margin-top: 0; }
382
+
383
+ .vm-text { display: flex; flex-direction: column; line-height: 1.3; min-width: 0; }
384
+ .vm-text b { font-size: 0.83rem; font-weight: 600; }
385
+ .vm-text i { font-size: 0.72rem; opacity: .62; font-style: normal; }
386
+ .vm-col-4 .vm-text b, .vm-col-3 .vm-text b { font-size: 0.72rem; font-weight: 500; }
387
+
388
+ @media (max-width: 640px) {
389
+ /* 폰에서는 바텀시트로 */
390
+ .vm { position: fixed; inset: auto 0 0 0; top: auto; border-radius: 14px 14px 0 0;
391
+ max-height: 62vh; overflow-y: auto; padding: 10px 10px calc(10px + env(safe-area-inset-bottom)); }
392
+ .vm-col-4 { width: auto; }
393
+ }
394
+
395
+ /* ── 본체 + ▾ 로 갈린 타일 (네이버 구조) ── */
396
+ .tb-split { display: inline-flex; align-items: stretch; flex: none; border-radius: 10px;
397
+ border: 1px solid #e6e4de; background: #fff; overflow: hidden; }
398
+ .tb-split:hover { border-color: #d8d6d0; }
399
+ .tb-split .tb-split-main { border-radius: 0; padding-right: 4px; min-width: 50px; border: 0; }
400
+ .tb-split-caret { display: flex; align-items: center; justify-content: center; width: 24px;
401
+ border: 0; border-left: 1px solid #eeece6; border-radius: 0;
402
+ background: #fbfaf8; color: #5b5b5b; cursor: pointer; padding: 0; }
403
+ .tb-split-caret:hover { background: #efedE8; color: #1a1a1a; }
404
+ .tb-split-caret .tb-caret { border-top-width: 5px; border-left-width: 4px; border-right-width: 4px; opacity: .8; }
405
+ .tb-split.is-open { border-color: #1a1a1a; }
406
+ .tb-split.is-open .tb-split-caret { background: #1a1a1a; color: #fff; border-left-color: #1a1a1a; }
407
+ .tb-split.is-on .tb-split-main { background: #1a1a1a; color: #fff; }
408
+ .tb-split.is-on .tb-split-caret { border-left-color: rgba(255,255,255,.25); }
409
+ @media (max-width: 640px) { .tb-split-caret { width: 22px; } }
410
+
411
+ /* 아랫줄 안의 입력칸 (코드 블록 제목·강조 줄) */
412
+ .tb-input { height: 30px; border: 1px solid #e0ded8; border-radius: 7px; background: #fff;
413
+ font-size: 0.76rem; padding: 0 8px; color: #2a2a2a; font-family: inherit; flex: none; }
414
+ .tb-input:focus { outline: none; border-color: #1a1a1a; }
415
+ .tb-input.is-bad { border-color: #e8a0a0; background: #fff8f8; }
416
+ .tb-code-title { width: 150px; }
417
+ .tb-lang { width: 108px; }
418
+ .tb-code-note { width: 260px; }
419
+ .tb-code-note:disabled { background: #f7f6f3; color: #b0aca2; }
420
+ @media (max-width: 640px) {
421
+ .tb-input { height: 40px; }
422
+ .tb-code-title { width: 120px; }
423
+ .tb-code-note { width: 150px; }
424
+ }
425
+
426
+ /* 선택된 객체 위에 뜨는 바 */
427
+ .obj-float { position: fixed; z-index: 45; display: flex; align-items: center; gap: 2px;
428
+ background: #fff; border: 1px solid #e0ded8; border-radius: 10px; padding: 4px 5px;
429
+ box-shadow: 0 6px 22px rgba(0,0,0,.15); white-space: nowrap; }
430
+ .obj-float .tb-btn { min-width: 30px; height: 30px; }
431
+ .obj-float .tb-div { height: 18px; margin: 0 3px; }
432
+ @media (pointer: coarse) { .obj-float .tb-btn { min-width: 40px; height: 40px; } }
433
+
434
+ /* 좌측 여백의 삽입 핸들 */
435
+ .ins-anchor { position: fixed; z-index: 35; }
436
+ .ins-btn { width: 28px; height: 28px; display: flex; align-items: center; justify-content: center;
437
+ border: 1px solid #e0ded8; background: #fff; border-radius: 8px; color: #8a8a8a; cursor: pointer;
438
+ opacity: .45; transition: opacity .12s, color .12s, border-color .12s; }
439
+ .ins-anchor:hover .ins-btn, .ins-btn:focus-visible { opacity: 1; color: #1a1a1a; border-color: #c9c6bd; }
440
+ .ins-menu { position: absolute; top: 32px; left: 0; min-width: 190px; }
441
+
442
+
443
+ /* ══ 폰: 윗줄을 접는다 ══
444
+ 실측 근거 — 헤더 45 + 윗줄 46 + 아랫줄 50 = 141px 고정.
445
+ 667px 화면에서 키보드(약 260px)가 뜨면 본문이 260px 밖에 안 남아 글을 못 쓴다.
446
+ 윗줄을 시트로 접으면 툴바가 50px 대로 내려간다. */
447
+ @media (max-width: 640px) {
448
+ .toolbar2 .tb-row-insert {
449
+ position: fixed; inset: auto 0 0 0; z-index: 80;
450
+ display: none; flex-wrap: wrap; gap: 6px; justify-content: flex-start;
451
+ background: #fff; border-top: 1px solid #e6e4de; border-bottom: 0;
452
+ border-radius: 14px 14px 0 0; box-shadow: 0 -8px 28px rgba(0,0,0,.14);
453
+ padding: 12px 10px calc(12px + env(safe-area-inset-bottom));
454
+ max-height: 55vh; overflow-y: auto;
455
+ }
456
+ .toolbar2.sheet-open .tb-row-insert { display: flex; }
457
+ .toolbar2 .tb-row-insert > * { flex: 0 0 calc(25% - 5px); }
458
+ .toolbar2 .tb-tile { width: 100%; min-height: 62px; }
459
+
460
+ }
461
+
462
+ /* 시트를 여는 버튼 — 기본은 숨김, 폰에서만 보인다.
463
+ (기본 규칙을 미디어쿼리 뒤에 두면 특이도가 같아 나중 것이 이겨 폰에서도 숨는다) */
464
+ .tb-sheet-btn { display: none; align-items: center; justify-content: center;
465
+ min-width: 40px; height: 40px; flex: none; margin-right: 4px;
466
+ border: 1px solid #e0ded8; background: #fff; border-radius: 8px; color: #1a1a1a; cursor: pointer; }
467
+ .tb-sheet-backdrop { display: none; }
468
+ @media (max-width: 640px) {
469
+ .tb-sheet-btn { display: inline-flex; }
470
+ .tb-sheet-backdrop { display: block; position: fixed; inset: 0; z-index: 79; background: rgba(0,0,0,.18); }
471
+ }
472
+
473
+ /* 타일 라벨 밑에 현재 고른 모양 이름을 작게 — 22px 아이콘으로는 모양이 안 읽힌다.
474
+ (메뉴 안에서는 크게 보이므로 거기서는 진짜 렌더 미리보기를 그대로 쓴다) */
475
+ .tb-tile-sub { display: block; font-size: 0.6rem; color: #9a9a9a; letter-spacing: -0.04em;
476
+ max-width: 56px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
477
+ .tb-tile.is-on .tb-tile-sub, .tb-split.is-on .tb-tile-sub { color: rgba(255,255,255,.7); }
478
+
479
+ /* 드롭다운을 fixed 로 띄울 때는 CSS 의 absolute 좌표를 쓰지 않는다.
480
+ (툴바 줄의 overflow 가 세로까지 잘라내 absolute 로는 화면에 안 나왔다) */
481
+ .vm.is-fixed { position: fixed; top: auto; left: auto; }
482
+
483
+ /* 사진 업로드 진행 — 화면 오른쪽 아래에 잠깐 뜬다 */
484
+ .up-chip {
485
+ position: fixed; right: 18px; bottom: 60px; z-index: 60;
486
+ background: #1c1d21; color: #f2f2f2; border-radius: 999px;
487
+ padding: 8px 16px; font-size: 0.8rem; letter-spacing: -0.01em;
488
+ box-shadow: 0 6px 20px rgba(0, 0, 0, .22);
489
+ }
490
+
491
+ /* 글자색 버튼 — 글자 버블의 것과 같은 모양 */
492
+ .tb-color { gap: 3px; }
493
+ .tb-color-swatch { width: 11px; height: 11px; border-radius: 3px; border: 1px solid rgba(0,0,0,.18); }
494
+ .tb-color-letter { font-size: 0.82rem; font-weight: 700; line-height: 1; }
495
+
496
+ /* 행 높이 손잡이 — 행 경계 위에 얇게 깔린다. 평소엔 안 보이고 올려놓으면 파란 선이 뜬다 */
497
+ .tt-rowsize {
498
+ position: fixed; height: 7px; cursor: row-resize; z-index: 41;
499
+ touch-action: none;
500
+ }
501
+ .tt-rowsize::after {
502
+ content: ''; position: absolute; left: 0; right: 0; top: 2px; height: 3px;
503
+ background: #4c6ef5; opacity: 0; transition: opacity .12s;
504
+ }
505
+ .tt-rowsize:hover::after { opacity: 1; }
506
+
507
+ /* 변형 메뉴 아래 덧줄 (표 테두리의 「선 색」 등) */
508
+ .vm-footer { grid-column: 1 / -1; border-top: 1px solid #eceae4; margin-top: 4px; padding-top: 4px; position: relative; }
509
+ .vm-footer > button {
510
+ display: flex; align-items: center; gap: 6px; width: 100%;
511
+ border: 0; background: none; cursor: pointer; padding: 7px 9px; border-radius: 7px;
512
+ font-size: 0.78rem; color: #3a3a3a;
513
+ }
514
+ .vm-footer > button:hover { background: #f2f1ec; }
515
+ /* 목록 모양 덧줄 — 세 모양을 나란히. 네이버엔 없는 우리 기능이라 본 목록 아래에 붙인다. */
516
+ .vm-markers { display: flex; gap: 4px; }
517
+ .vm-markers > button { flex: 1; justify-content: center; padding: 4px; border: 1px solid #eceae4; }
518
+ .vm-markers > button.is-on { border-color: #1a1a1a; background: #f7f6f2; }
519
+ .vm-markers .vm-preview { width: auto; }
520
+
521
+ /* 목록 메뉴 — 아이콘 셋을 세로로 (네이버 블로그와 같은 모양) */
522
+ .tb-listmenu { position: absolute; left: 0; top: calc(100% + 6px); z-index: 60;
523
+ background: #fff; border: 1px solid #e6e4de; border-radius: 10px; padding: 5px;
524
+ box-shadow: 0 10px 30px rgba(0,0,0,0.1); display: flex; flex-direction: column; gap: 2px; width: 46px; }
525
+ .tb-listmenu.is-fixed { top: auto; }
526
+ .tb-listmenu > button { display: flex; align-items: center; justify-content: center;
527
+ width: 36px; height: 34px; border: 0; background: none; cursor: pointer; border-radius: 7px; color: #3a3a3a; }
528
+ .tb-listmenu > button:hover { background: #f4f3ef; }
529
+ .tb-listmenu > button.is-on { background: #1a1a1a; color: #fff; }
530
+ /* 모양 덧줄 — 우리 기능. 아이콘 셋을 밀어내지 않게 아래에 얇게 */
531
+ .tb-listmarkers { border-top: 1px solid #eceae4; margin-top: 3px; padding-top: 3px;
532
+ display: flex; flex-direction: column; gap: 2px; }
533
+ .tb-listmarkers > button { display: flex; align-items: center; justify-content: center;
534
+ width: 36px; height: 28px; border: 1px solid transparent; background: none; cursor: pointer;
535
+ border-radius: 6px; font-size: 0.72rem; color: #5b5b5b; line-height: 1; }
536
+ .tb-listmarkers > button:hover { background: #f4f3ef; }
537
+ .tb-listmarkers > button.is-on { border-color: #1a1a1a; background: #f7f6f2; color: #1a1a1a; }
538
+
539
+ /* 글꼴 — 고른 이름을 그 글꼴로 보여 준다 */
540
+ .bub-font { display: inline-flex; align-items: center; gap: 4px; white-space: nowrap;
541
+ max-width: 110px; overflow: hidden; font-size: 0.82rem; }
542
+ .tb-font { min-width: 96px; }
543
+ /* 그리는 방식 칸 — 「글로 쓰기」와 「캔버스로 그리기」는 길이가 달라
544
+ 고를 때마다 오른쪽 도구가 통째로 밀린다. 글꼴 칸과 같은 처방을 쓴다. */
545
+ .tb-dgmode { min-width: 122px; }
546
+
547
+ /* ── 고르는 메뉴 (글꼴·문단·크기·줄간격·언어 공용) ──
548
+ `<select>` 를 안 쓰므로 OS 메뉴가 아니라 우리 CSS 가 그린다.
549
+ 목록이 길어도(크기 16줄·언어 27줄) 화면을 넘지 않게 스스로 스크롤한다. */
550
+ .pm { position: absolute; top: calc(100% + 6px); left: 0; z-index: 70;
551
+ background: #fff; border: 1px solid #e0ded8; border-radius: 10px; padding: 5px;
552
+ box-shadow: 0 10px 30px rgba(0,0,0,.14);
553
+ max-height: min(58vh, 430px); overflow-y: auto; overscroll-behavior: contain; }
554
+ .pm-item { display: block; width: 100%; text-align: left; border: 0; background: none;
555
+ cursor: pointer; padding: 9px 10px; border-radius: 7px; font-size: 0.92rem; color: #1a1a1a;
556
+ white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
557
+ .pm-center .pm-item { text-align: center; }
558
+ /* 글꼴 고르기 — 이름이 곧 견본이라 본문보다 크게 보여 준다 */
559
+ .pm-fonts { width: 200px !important; }
560
+ .pm-fonts .pm-item { font-size: 1.18rem; line-height: 1.5; padding: 10px 12px; }
561
+ .pm-item:hover { background: #f4f3ef; }
562
+ .pm-item.is-on { background: #f7f6f2; font-weight: 600; }
563
+ .pm-item.is-on::after { content: '✓'; float: right; color: #1a1a1a; margin-left: 8px; }
564
+ .pm-center .pm-item.is-on::after { float: none; margin-left: 5px; }
565
+
566
+ /* 글자 버블 안의 고르기 칸은 버블 높이(30px)에 맞춘다 */
567
+ /* `.bub button` 이 padding 을 덮어써 꺾쇠가 글자 위에 겹친다 — 여기서 되돌린다 */
568
+ .bub .tb-select { height: 28px; font-size: 0.8rem; padding: 0 1.05rem 0 0.4rem; border-radius: 7px; }
569
+
570
+ /* ── 주소 입력 팝오버 (링크·비디오) ── */
571
+ .up { position: absolute; top: calc(100% + 6px); left: 0; z-index: 70;
572
+ background: #fff; border: 1px solid #e0ded8; border-radius: 12px; padding: 12px;
573
+ box-shadow: 0 10px 30px rgba(0,0,0,.14); }
574
+ .up-head { font-size: 0.74rem; color: #8a8a8a; margin: 0 2px 7px; letter-spacing: -0.02em; }
575
+ .up-input { width: 100%; height: 36px; padding: 0 10px; border-radius: 8px;
576
+ border: 1px solid #d9d6cf; font-size: 0.88rem; color: #1a1a1a; background: #fff; }
577
+ .up-input:focus { outline: none; border-color: #2563eb; box-shadow: 0 0 0 3px rgba(37,99,235,.14); }
578
+ .up-input.is-bad { border-color: #d64545; box-shadow: 0 0 0 3px rgba(214,69,69,.13); }
579
+ .up-hint { margin: 7px 2px 0; font-size: 0.73rem; color: #9a9a9a; line-height: 1.45; }
580
+ .up-hint.is-bad { color: #c53030; }
581
+ .up-foot { display: flex; justify-content: flex-end; gap: 6px; margin-top: 10px; }
582
+ .up-foot button { height: 32px; padding: 0 14px; border-radius: 8px; cursor: pointer;
583
+ font-size: 0.84rem; border: 1px solid transparent; }
584
+ .up-cancel { background: #f4f3ef; color: #4a4a4a; }
585
+ .up-cancel:hover { background: #eceae4; }
586
+ .up-ok { background: #1a1a1a; color: #fff; }
587
+ .up-ok:hover { background: #333; }
588
+
589
+ /* 글자 배경색 버튼 — 「가」 뒤에 색이 깔린다 */
590
+ .tb-hl-letter, .bub-hl-letter {
591
+ padding: 1px 4px; border-radius: 3px; box-shadow: inset 0 0 0 1px rgba(0,0,0,.10);
592
+ }
593
+
594
+ /* 문맥줄 버튼에 아이콘과 한글 라벨을 같이 — 아이콘만으로는 무슨 기능인지 안 읽힌다 */
595
+ .tb-row-format .tb-btn { gap: 4px; }
596
+
597
+ /* 인용 떠 있는 바 — 모양 일곱 개를 바로 누른다 */
598
+ .tb-qv { min-width: 32px; height: 32px; padding: 0 4px; color: #5b5b5b; }
599
+ .tb-qv.is-on { background: #1a1a1a; color: #fff; }
600
+
601
+ /* 잡힌 블록 — PickedBlock 데코레이션이 붙인다.
602
+ 편집기 밖을 누르면 사라진다(:focus-within). 떠 있는 바도 같은 조건으로 숨는다. */
603
+ .ProseMirror:focus-within [data-picked] {
604
+ outline: 2px solid #2563eb; outline-offset: 8px; border-radius: 3px;
605
+ }
606
+ .ProseMirror:focus-within details[data-code][data-picked] { outline-offset: 4px; }
607
+
608
+ /* 넣기 줄 오른쪽 끝으로 미는 칸 */
609
+ .tb-right { margin-left: auto; display: flex; align-items: center; padding-right: 2px; }
610
+
611
+ /* ── 본문 폭 고르개 ── */
612
+ /* 직각 — 관리자의 어휘가 직각 검정 사각형이라 눌린 쪽(is-on)이 그 도형과 같아야 한다 */
613
+ .wt { display: inline-flex; gap: 2px; padding: 2px; border-radius: 0; background: #f2f1ec; }
614
+ .wt button {
615
+ border: 0; background: none; border-radius: 0; cursor: pointer;
616
+ padding: 5px 11px; font-size: 0.78rem; color: #5b5b5b; letter-spacing: -0.02em;
617
+ }
618
+ .wt button:hover { color: #1a1a1a; }
619
+ .wt button.is-on { background: #1a1a1a; color: #fff; }
620
+
621
+ /* ── 다이어그램 ─────────────────────────────────────────────────────────
622
+ 그림 자체는 prose.css 가 그린다(편집·발행이 같은 것을 봐야 한다).
623
+ 여기는 **편집할 때만 있는 것** — 껍데기, 원본 칸, 오류줄, 그리기 모달. */
624
+
625
+ /**
626
+ * 껍데기 — **잡았을 때만** 생긴다.
627
+ *
628
+ * 안 잡았을 때는 발행본과 글자 하나까지 같아야 하므로 아무것도 안 씌운다.
629
+ * 잡으면 미리보기와 원본 칸이 테두리 하나로 묶인 한 덩어리가 된다 — 소스코드 블록의
630
+ * `details[data-code] > .cb-bar + pre` 와 같은 어휘다.
631
+ *
632
+ * 이게 없던 동안의 화면: 빈 상자는 `width: fit-content` 라 글자만큼 쪼그라들어 가운데
633
+ * 떠 있고, 그 밑의 `textarea` 만 폭을 다 썼다. 한 블록 안에 폭 모델이 둘이었다.
634
+ */
635
+ .editor-surface .dg-shell {
636
+ border: 1px solid #e6e4de; border-radius: 10px; overflow: hidden; background: #fff;
637
+ }
638
+ /* 껍데기가 테두리를 맡으므로 안쪽 상자는 테두리를 내려놓고 폭을 다 쓴다 */
639
+ .editor-surface .dg-shell > .dg-body {
640
+ width: auto; max-width: none; margin: 0; border: 0; border-radius: 0;
641
+ min-height: 132px; display: grid; place-items: center;
642
+ }
643
+ .editor-surface .dg-shell > .dg-body > .dg-svg { max-width: 100%; }
644
+
645
+ /**
646
+ * 잡은 표시 — **껍데기에** 건다.
647
+ *
648
+ * ⚠ 기준은 `ProseMirror-selectednode` 다. 내 `selected` prop 만 보고 `data-selected` 를
649
+ * 달았더니 아무것도 안 나왔다 — 캡션을 품은 노드는 클릭이 캡션의 글자 선택으로 떨어져
650
+ * 그 prop 이 안 켜진다. 그리고 그 클래스는 **저절로 안 붙는다**: 붙게 만드는 쪽은
651
+ * `DiagramView` 의 mousedown 이다. 두 곳이 맞물려야 이 규칙이 산다.
652
+ */
653
+ .editor-surface .react-renderer.ProseMirror-selectednode .dg-shell {
654
+ outline: 2px solid #2563eb; outline-offset: 2px;
655
+ }
656
+ /* 껍데기가 없을 때(안 잡힘)는 옛 자리로 — 그림 상자에 건다 */
657
+ .editor-surface .react-renderer.ProseMirror-selectednode > .dg > .dg-body {
658
+ outline: 2px solid #2563eb; outline-offset: 3px;
659
+ }
660
+
661
+ /**
662
+ * 빈 상태 — **기하에 영향을 주지 않는다.**
663
+ * 이 레포의 다른 빈 블록 안내는 전부 `position: absolute` 인 `::before` 다(prose.css).
664
+ * 여기만 실제 요소로 `padding: 2rem 0` 를 밀어 넣었더니 `fit-content` 상자를 부풀렸다.
665
+ */
666
+ .dg-empty {
667
+ position: absolute; inset: 0; display: grid; place-items: center;
668
+ padding: 0 1rem; text-align: center;
669
+ color: #8a9099; font-size: .88rem; pointer-events: none;
670
+ }
671
+
672
+ /* 굽는 중 — 오른쪽 위 작은 점. 300ms 쉬고 굽기 때문에 자주 안 보인다 */
673
+ .dg-busy {
674
+ position: absolute; right: 10px; top: 10px; width: 6px; height: 6px;
675
+ border-radius: 50%; background: #1a1a1a; opacity: .5;
676
+ }
677
+ @media (prefers-reduced-motion: no-preference) {
678
+ .dg-busy { animation: dgPulse 1s ease-in-out infinite; }
679
+ }
680
+ @keyframes dgPulse { 0%, 100% { opacity: .5 } 50% { opacity: .12 } }
681
+
682
+ /* 원본 칸 — 껍데기 아래쪽 절반. 위 경계선 하나로 미리보기와 갈린다 */
683
+ .dg-panel { border-top: 1px solid #ece9e2; background: #fbfbfa; }
684
+ .dg-bar {
685
+ display: flex; align-items: center; gap: 8px;
686
+ padding: .42rem .7rem; color: #8a867e; font-size: .74rem;
687
+ }
688
+ .dg-bar b { font-weight: 600; color: #4a463f; }
689
+ /* 종류 칩 — mermaidKind() 가 첫 줄로 짚어 준다 */
690
+ .dg-kind {
691
+ padding: 1px 7px; border: 1px solid #e0ddd6; border-radius: 999px;
692
+ background: #fff; font-size: .7rem; color: #6f6b63;
693
+ }
694
+ .dg-bar .dg-spacer { margin-left: auto; }
695
+ .dg-bar .tb-btn { height: 28px; min-width: 28px; }
696
+
697
+ .dg-input {
698
+ display: block; box-sizing: border-box; width: 100%; resize: none; overflow: hidden;
699
+ min-height: 4.4em; padding: 0 .8rem .7rem;
700
+ border: 0; outline: 0; background: transparent; color: #1a1a1a;
701
+ font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
702
+ font-size: .82rem; line-height: 1.6; tab-size: 2;
703
+ }
704
+ .dg-input::placeholder { color: #b4afa5; }
705
+
706
+ /**
707
+ * 오류 — **한글 한 문장이 먼저**, 원문은 근거로 작게.
708
+ * 원문만 보여 줬더니 첫 글자를 치는 순간 빨간 영어가 떴다
709
+ * (`No diagram type detected matching given configuration for text: $`).
710
+ * 그렇다고 버리지는 않는다 — 파스 오류의 줄·열이 제일 쓸모 있다.
711
+ */
712
+ .dg-err {
713
+ margin: 0; padding: .5rem .8rem .6rem;
714
+ border-top: 1px solid #f0e2e0; background: #fdf7f6;
715
+ color: #a3231b; font-size: .78rem; line-height: 1.5;
716
+ }
717
+ .dg-err code {
718
+ display: block; margin-top: .25rem;
719
+ font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
720
+ font-size: .72rem; color: #9a7a76; word-break: break-all;
721
+ }
722
+
723
+ /* draw.io — 화면을 통째로 덮는다. 그리는 동안은 글이 아니라 도면이 일이다 */
724
+ .dio { position: fixed; inset: 0; z-index: 200; display: flex; flex-direction: column; background: #fff; }
725
+ .dio-bar {
726
+ display: flex; align-items: center; gap: .75rem; flex: none;
727
+ height: 44px; padding: 0 .6rem 0 1rem; background: #000; color: #fff;
728
+ }
729
+ .dio-t { font-weight: 700; font-size: .9rem; letter-spacing: -.02em; }
730
+ .dio-hint { margin-right: auto; font-size: .8rem; color: rgba(255,255,255,.6); }
731
+ .dio-x {
732
+ flex: none; width: 32px; height: 32px; border: 0; border-radius: 0;
733
+ background: transparent; color: rgba(255,255,255,.72); cursor: pointer; font-size: 1rem;
734
+ }
735
+ .dio-x:hover { background: rgba(255,255,255,.14); color: #fff; }
736
+ .dio-wait { position: absolute; inset: 44px 0 0; display: grid; place-items: center; margin: 0; color: #6b7280; }
737
+ .dio-frame { flex: 1 1 auto; width: 100%; border: 0; }