@commonpub/layer 0.4.6 → 0.4.7

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.
@@ -6,18 +6,26 @@
6
6
  <span class="cpub-logo-name">CommonPub</span>
7
7
  </span>
8
8
 
9
- <!-- Agora logo: Town Square mark + Fraunces wordmark -->
9
+ <!-- Agora logo: Textured Town Square mark + Fraunces wordmark -->
10
10
  <span class="cpub-logo-agora">
11
- <svg class="cpub-logo-mark" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" width="22" height="22" aria-hidden="true">
12
- <rect x="18" y="18" width="72" height="72" fill="none" stroke="currentColor" stroke-width="7" stroke-linejoin="round"/>
13
- <rect x="110" y="18" width="72" height="72" fill="none" stroke="currentColor" stroke-width="7" stroke-linejoin="round"/>
14
- <rect x="18" y="110" width="72" height="72" fill="none" stroke="currentColor" stroke-width="7" stroke-linejoin="round"/>
15
- <rect x="110" y="110" width="72" height="72" fill="none" stroke="currentColor" stroke-width="7" stroke-linejoin="round"/>
16
- <rect x="88" y="88" width="24" height="24" fill="var(--accent, #3d8b5e)"/>
17
- <line x1="34" y1="54" x2="72" y2="54" stroke="var(--accent, #3d8b5e)" stroke-width="3.5" stroke-linecap="round" opacity="0.7"/>
18
- <line x1="128" y1="46" x2="166" y2="46" stroke="var(--accent, #3d8b5e)" stroke-width="3.5" stroke-linecap="round" opacity="0.7"/>
19
- <line x1="34" y1="148" x2="68" y2="148" stroke="var(--accent, #3d8b5e)" stroke-width="3.5" stroke-linecap="round" opacity="0.7"/>
20
- <line x1="128" y1="142" x2="162" y2="142" stroke="var(--accent, #3d8b5e)" stroke-width="3.5" stroke-linecap="round" opacity="0.7"/>
11
+ <svg class="cpub-logo-mark" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" width="24" height="24" aria-hidden="true">
12
+ <defs>
13
+ <filter id="cpub-tex" x="-5%" y="-5%" width="110%" height="110%">
14
+ <feTurbulence type="fractalNoise" baseFrequency="0.04" numOctaves="4" result="noise"/>
15
+ <feDisplacementMap in="SourceGraphic" in2="noise" scale="2"/>
16
+ </filter>
17
+ </defs>
18
+ <g filter="url(#cpub-tex)">
19
+ <rect x="18" y="18" width="72" height="72" fill="none" stroke="currentColor" stroke-width="7" stroke-linejoin="round"/>
20
+ <rect x="110" y="18" width="72" height="72" fill="none" stroke="currentColor" stroke-width="7" stroke-linejoin="round"/>
21
+ <rect x="18" y="110" width="72" height="72" fill="none" stroke="currentColor" stroke-width="7" stroke-linejoin="round"/>
22
+ <rect x="110" y="110" width="72" height="72" fill="none" stroke="currentColor" stroke-width="7" stroke-linejoin="round"/>
23
+ <rect x="88" y="88" width="24" height="24" fill="var(--accent, #3d8b5e)"/>
24
+ <line x1="34" y1="54" x2="72" y2="54" stroke="var(--accent, #3d8b5e)" stroke-width="3.5" stroke-linecap="round" opacity="0.7"/>
25
+ <line x1="128" y1="46" x2="166" y2="46" stroke="var(--accent, #3d8b5e)" stroke-width="3.5" stroke-linecap="round" opacity="0.7"/>
26
+ <line x1="34" y1="148" x2="68" y2="148" stroke="var(--accent, #3d8b5e)" stroke-width="3.5" stroke-linecap="round" opacity="0.7"/>
27
+ <line x1="128" y1="142" x2="162" y2="142" stroke="var(--accent, #3d8b5e)" stroke-width="3.5" stroke-linecap="round" opacity="0.7"/>
28
+ </g>
21
29
  </svg>
22
30
  <span class="cpub-logo-wordmark">Common<span class="cpub-logo-pub">Pub</span></span>
23
31
  </span>
@@ -43,7 +51,6 @@
43
51
  .cpub-logo-bracket { color: var(--accent); font-size: 15px; }
44
52
  .cpub-logo-name { margin-left: 2px; }
45
53
 
46
- /* Agora logo hidden by default — shown via agora.css */
47
54
  .cpub-logo-agora {
48
55
  display: none;
49
56
  align-items: center;
@@ -184,9 +184,9 @@ function onDragEnd(event: DragEvent): void {
184
184
  min-height: 20px;
185
185
  }
186
186
 
187
- /* Touch devices: always show controls since there's no hover */
187
+ /* Touch devices: only show controls on selected block since there's no hover */
188
188
  @media (hover: none) {
189
- .cpub-block-handle { opacity: 1; }
190
- .cpub-block-controls { opacity: 1; }
189
+ .cpub-block-wrap--selected .cpub-block-handle { opacity: 1; }
190
+ .cpub-block-wrap--selected .cpub-block-controls { opacity: 1; }
191
191
  }
192
192
  </style>
@@ -0,0 +1,559 @@
1
+ <script setup lang="ts">
2
+ export interface PageTreeItem {
3
+ id: string;
4
+ title: string;
5
+ slug: string;
6
+ parentId: string | null;
7
+ sortOrder: number;
8
+ }
9
+
10
+ export interface PageTreeNode extends PageTreeItem {
11
+ children: PageTreeNode[];
12
+ depth: number;
13
+ }
14
+
15
+ const props = defineProps<{
16
+ pages: PageTreeItem[];
17
+ selectedPageId: string | null;
18
+ }>();
19
+
20
+ const emit = defineEmits<{
21
+ select: [pageId: string];
22
+ create: [parentId: string | null, title: string];
23
+ rename: [pageId: string, title: string];
24
+ delete: [pageId: string];
25
+ reorder: [pageIds: string[]];
26
+ reparent: [pageId: string, newParentId: string | null];
27
+ }>();
28
+
29
+ // Build nested tree from flat pages
30
+ const tree = computed<PageTreeNode[]>(() => {
31
+ return buildTree(props.pages, null, 0);
32
+ });
33
+
34
+ function buildTree(pages: PageTreeItem[], parentId: string | null, depth: number): PageTreeNode[] {
35
+ return pages
36
+ .filter(p => p.parentId === parentId)
37
+ .sort((a, b) => a.sortOrder - b.sortOrder)
38
+ .map(p => ({
39
+ ...p,
40
+ depth,
41
+ children: buildTree(pages, p.id, depth + 1),
42
+ }));
43
+ }
44
+
45
+ // Expand/collapse state
46
+ const expandedIds = ref<Set<string>>(new Set());
47
+
48
+ // Auto-expand parents of selected page
49
+ watch(() => props.selectedPageId, (selectedId) => {
50
+ if (!selectedId) return;
51
+ const ancestors = getAncestors(selectedId);
52
+ for (const id of ancestors) {
53
+ expandedIds.value.add(id);
54
+ }
55
+ }, { immediate: true });
56
+
57
+ function getAncestors(pageId: string): string[] {
58
+ const result: string[] = [];
59
+ let current = props.pages.find(p => p.id === pageId);
60
+ while (current?.parentId) {
61
+ result.push(current.parentId);
62
+ current = props.pages.find(p => p.id === current!.parentId);
63
+ }
64
+ return result;
65
+ }
66
+
67
+ function toggleExpanded(id: string): void {
68
+ const s = new Set(expandedIds.value);
69
+ if (s.has(id)) s.delete(id);
70
+ else s.add(id);
71
+ expandedIds.value = s;
72
+ }
73
+
74
+ function isExpanded(id: string): boolean {
75
+ return expandedIds.value.has(id);
76
+ }
77
+
78
+ // Inline add page
79
+ const addingUnder = ref<string | null | 'root'>(null);
80
+ const newPageTitle = ref('');
81
+
82
+ function startAdd(parentId: string | null): void {
83
+ addingUnder.value = parentId ?? 'root';
84
+ newPageTitle.value = '';
85
+ nextTick(() => {
86
+ const input = document.querySelector('.cpub-tree-add-input') as HTMLInputElement | null;
87
+ input?.focus();
88
+ });
89
+ }
90
+
91
+ function confirmAdd(): void {
92
+ if (!newPageTitle.value.trim()) {
93
+ addingUnder.value = null;
94
+ return;
95
+ }
96
+ const parentId = addingUnder.value === 'root' ? null : addingUnder.value;
97
+ emit('create', parentId, newPageTitle.value.trim());
98
+ addingUnder.value = null;
99
+ newPageTitle.value = '';
100
+ }
101
+
102
+ function cancelAdd(): void {
103
+ addingUnder.value = null;
104
+ newPageTitle.value = '';
105
+ }
106
+
107
+ // Compute depth for the inline add row (child depth = parent depth + 1)
108
+ const addingDepth = computed(() => {
109
+ if (addingUnder.value === null || addingUnder.value === 'root') return 0;
110
+ // Find the parent node's depth in the flat list
111
+ const parentNode = flatNodes.value.find(n => n.id === addingUnder.value);
112
+ return parentNode ? parentNode.depth + 1 : 1;
113
+ });
114
+
115
+ // Context menu
116
+ const contextMenu = ref<{ pageId: string; x: number; y: number } | null>(null);
117
+
118
+ function showContext(pageId: string, event: MouseEvent): void {
119
+ event.preventDefault();
120
+ contextMenu.value = { pageId, x: event.clientX, y: event.clientY };
121
+ }
122
+
123
+ function closeContext(): void {
124
+ contextMenu.value = null;
125
+ }
126
+
127
+ function contextRename(): void {
128
+ if (!contextMenu.value) return;
129
+ const page = props.pages.find(p => p.id === contextMenu.value!.pageId);
130
+ if (!page) return;
131
+ const newTitle = prompt('Rename page:', page.title);
132
+ if (newTitle && newTitle.trim()) {
133
+ emit('rename', page.id, newTitle.trim());
134
+ }
135
+ closeContext();
136
+ }
137
+
138
+ function contextDelete(): void {
139
+ if (!contextMenu.value) return;
140
+ const page = props.pages.find(p => p.id === contextMenu.value!.pageId);
141
+ if (!page) return;
142
+ if (confirm(`Delete "${page.title}"? This cannot be undone.`)) {
143
+ emit('delete', page.id);
144
+ }
145
+ closeContext();
146
+ }
147
+
148
+ function contextAddChild(): void {
149
+ if (!contextMenu.value) return;
150
+ const parentId = contextMenu.value.pageId;
151
+ expandedIds.value.add(parentId);
152
+ closeContext();
153
+ startAdd(parentId);
154
+ }
155
+
156
+ // Drag and drop
157
+ const draggedId = ref<string | null>(null);
158
+ const dropTargetId = ref<string | null>(null);
159
+ const dropPosition = ref<'before' | 'inside' | 'after' | null>(null);
160
+
161
+ function onDragStart(pageId: string, event: DragEvent): void {
162
+ draggedId.value = pageId;
163
+ if (event.dataTransfer) {
164
+ event.dataTransfer.effectAllowed = 'move';
165
+ event.dataTransfer.setData('text/plain', pageId);
166
+ }
167
+ }
168
+
169
+ function onDragOver(targetId: string, event: DragEvent): void {
170
+ if (draggedId.value === targetId) return;
171
+ event.preventDefault();
172
+ if (event.dataTransfer) event.dataTransfer.dropEffect = 'move';
173
+
174
+ const rect = (event.currentTarget as HTMLElement).getBoundingClientRect();
175
+ const y = event.clientY - rect.top;
176
+ const h = rect.height;
177
+
178
+ dropTargetId.value = targetId;
179
+ if (y < h * 0.25) dropPosition.value = 'before';
180
+ else if (y > h * 0.75) dropPosition.value = 'after';
181
+ else dropPosition.value = 'inside';
182
+ }
183
+
184
+ function onDragLeave(): void {
185
+ dropTargetId.value = null;
186
+ dropPosition.value = null;
187
+ }
188
+
189
+ function onDrop(targetId: string): void {
190
+ if (!draggedId.value || draggedId.value === targetId) return;
191
+
192
+ const target = props.pages.find(p => p.id === targetId);
193
+ if (!target) return;
194
+
195
+ if (dropPosition.value === 'inside') {
196
+ // Reparent: make dragged page a child of target
197
+ emit('reparent', draggedId.value, targetId);
198
+ expandedIds.value.add(targetId);
199
+ } else {
200
+ // Before/after: move dragged page to target's parent and set sibling order
201
+ const newParentId = target.parentId;
202
+ const dragId = draggedId.value;
203
+
204
+ // Compute the new sibling order INCLUDING the dragged page
205
+ const siblings = props.pages
206
+ .filter(p => p.parentId === newParentId && p.id !== dragId)
207
+ .sort((a, b) => a.sortOrder - b.sortOrder);
208
+
209
+ const targetIdx = siblings.findIndex(p => p.id === targetId);
210
+ const insertIdx = dropPosition.value === 'before' ? targetIdx : targetIdx + 1;
211
+ const ordered = [...siblings];
212
+ const draggedPage = props.pages.find(p => p.id === dragId);
213
+ if (draggedPage) {
214
+ ordered.splice(insertIdx, 0, draggedPage);
215
+ }
216
+
217
+ // Emit a single 'move' event so parent can handle atomically
218
+ emit('reparent', dragId, newParentId);
219
+ emit('reorder', ordered.map(p => p.id));
220
+ }
221
+
222
+ draggedId.value = null;
223
+ dropTargetId.value = null;
224
+ dropPosition.value = null;
225
+ }
226
+
227
+ function onDragEnd(): void {
228
+ draggedId.value = null;
229
+ dropTargetId.value = null;
230
+ dropPosition.value = null;
231
+ }
232
+
233
+ // Flatten tree for rendering
234
+ function flattenForRender(nodes: PageTreeNode[]): PageTreeNode[] {
235
+ const result: PageTreeNode[] = [];
236
+ for (const node of nodes) {
237
+ result.push(node);
238
+ if (node.children.length > 0 && isExpanded(node.id)) {
239
+ result.push(...flattenForRender(node.children));
240
+ }
241
+ }
242
+ return result;
243
+ }
244
+
245
+ const flatNodes = computed(() => flattenForRender(tree.value));
246
+
247
+ // Close context menu on click outside
248
+ onMounted(() => {
249
+ document.addEventListener('click', closeContext);
250
+ });
251
+ onUnmounted(() => {
252
+ document.removeEventListener('click', closeContext);
253
+ });
254
+ </script>
255
+
256
+ <template>
257
+ <div class="cpub-page-tree" role="tree" aria-label="Pages">
258
+ <div
259
+ v-for="node in flatNodes"
260
+ :key="node.id"
261
+ class="cpub-tree-item"
262
+ :class="{
263
+ 'cpub-tree-item-selected': selectedPageId === node.id,
264
+ 'cpub-tree-item-dragging': draggedId === node.id,
265
+ 'cpub-tree-item-drop-before': dropTargetId === node.id && dropPosition === 'before',
266
+ 'cpub-tree-item-drop-inside': dropTargetId === node.id && dropPosition === 'inside',
267
+ 'cpub-tree-item-drop-after': dropTargetId === node.id && dropPosition === 'after',
268
+ }"
269
+ :style="{ paddingLeft: `${12 + node.depth * 16}px` }"
270
+ role="treeitem"
271
+ :aria-selected="selectedPageId === node.id"
272
+ :aria-expanded="node.children.length > 0 ? isExpanded(node.id) : undefined"
273
+ draggable="true"
274
+ @click="emit('select', node.id)"
275
+ @contextmenu="showContext(node.id, $event)"
276
+ @dragstart="onDragStart(node.id, $event)"
277
+ @dragover="onDragOver(node.id, $event)"
278
+ @dragleave="onDragLeave"
279
+ @drop.prevent="onDrop(node.id)"
280
+ @dragend="onDragEnd"
281
+ >
282
+ <!-- Expand/collapse toggle -->
283
+ <button
284
+ v-if="node.children.length > 0"
285
+ class="cpub-tree-toggle"
286
+ :aria-label="isExpanded(node.id) ? 'Collapse' : 'Expand'"
287
+ @click.stop="toggleExpanded(node.id)"
288
+ >
289
+ <i class="fa-solid" :class="isExpanded(node.id) ? 'fa-chevron-down' : 'fa-chevron-right'" />
290
+ </button>
291
+ <span v-else class="cpub-tree-toggle-spacer" />
292
+
293
+ <i class="fa-solid fa-file-lines cpub-tree-icon" />
294
+ <span class="cpub-tree-title">{{ node.title || 'Untitled' }}</span>
295
+
296
+ <!-- Kebab menu -->
297
+ <button class="cpub-tree-kebab" aria-label="Page actions" @click.stop="showContext(node.id, $event)">
298
+ <i class="fa-solid fa-ellipsis-vertical" />
299
+ </button>
300
+ </div>
301
+
302
+ <!-- Inline add at root or under parent -->
303
+ <div
304
+ v-if="addingUnder !== null"
305
+ class="cpub-tree-add-row"
306
+ :style="{ paddingLeft: `${12 + addingDepth * 16}px` }"
307
+ >
308
+ <i class="fa-solid fa-file-circle-plus cpub-tree-icon cpub-tree-icon-add" />
309
+ <input
310
+ v-model="newPageTitle"
311
+ class="cpub-tree-add-input"
312
+ placeholder="Page title..."
313
+ @keydown.enter="confirmAdd"
314
+ @keydown.escape="cancelAdd"
315
+ @blur="confirmAdd"
316
+ />
317
+ </div>
318
+
319
+ <!-- Add page button -->
320
+ <button class="cpub-tree-add-btn" @click="startAdd(null)">
321
+ <i class="fa-solid fa-plus" /> Add Page
322
+ </button>
323
+
324
+ <!-- Context menu -->
325
+ <Teleport to="body">
326
+ <div
327
+ v-if="contextMenu"
328
+ class="cpub-tree-context"
329
+ :style="{ left: contextMenu.x + 'px', top: contextMenu.y + 'px' }"
330
+ @click.stop
331
+ >
332
+ <button class="cpub-tree-context-item" @click="contextRename">
333
+ <i class="fa-solid fa-pen" /> Rename
334
+ </button>
335
+ <button class="cpub-tree-context-item" @click="contextAddChild">
336
+ <i class="fa-solid fa-folder-plus" /> Add Child
337
+ </button>
338
+ <button class="cpub-tree-context-item cpub-tree-context-danger" @click="contextDelete">
339
+ <i class="fa-solid fa-trash" /> Delete
340
+ </button>
341
+ </div>
342
+ </Teleport>
343
+ </div>
344
+ </template>
345
+
346
+ <style scoped>
347
+ .cpub-page-tree {
348
+ display: flex;
349
+ flex-direction: column;
350
+ padding: 4px 0;
351
+ user-select: none;
352
+ }
353
+
354
+ .cpub-tree-item {
355
+ display: flex;
356
+ align-items: center;
357
+ gap: 6px;
358
+ padding: 6px 8px 6px 12px;
359
+ cursor: pointer;
360
+ transition: background 0.1s;
361
+ position: relative;
362
+ }
363
+
364
+ .cpub-tree-item:hover {
365
+ background: var(--surface2);
366
+ }
367
+
368
+ .cpub-tree-item-selected {
369
+ background: var(--accent-bg);
370
+ border-left: 2px solid var(--accent);
371
+ }
372
+
373
+ .cpub-tree-item-dragging {
374
+ opacity: 0.4;
375
+ }
376
+
377
+ .cpub-tree-item-drop-before::before {
378
+ content: '';
379
+ position: absolute;
380
+ top: 0;
381
+ left: 8px;
382
+ right: 8px;
383
+ height: 2px;
384
+ background: var(--accent);
385
+ }
386
+
387
+ .cpub-tree-item-drop-inside {
388
+ background: var(--accent-bg);
389
+ outline: 2px solid var(--accent);
390
+ outline-offset: -2px;
391
+ }
392
+
393
+ .cpub-tree-item-drop-after::after {
394
+ content: '';
395
+ position: absolute;
396
+ bottom: 0;
397
+ left: 8px;
398
+ right: 8px;
399
+ height: 2px;
400
+ background: var(--accent);
401
+ }
402
+
403
+ .cpub-tree-toggle {
404
+ width: 16px;
405
+ height: 16px;
406
+ display: flex;
407
+ align-items: center;
408
+ justify-content: center;
409
+ background: none;
410
+ border: none;
411
+ color: var(--text-faint);
412
+ cursor: pointer;
413
+ font-size: 8px;
414
+ padding: 0;
415
+ flex-shrink: 0;
416
+ }
417
+
418
+ .cpub-tree-toggle:hover {
419
+ color: var(--text);
420
+ }
421
+
422
+ .cpub-tree-toggle-spacer {
423
+ width: 16px;
424
+ flex-shrink: 0;
425
+ }
426
+
427
+ .cpub-tree-icon {
428
+ font-size: 11px;
429
+ color: var(--text-faint);
430
+ flex-shrink: 0;
431
+ }
432
+
433
+ .cpub-tree-icon-add {
434
+ color: var(--accent);
435
+ }
436
+
437
+ .cpub-tree-title {
438
+ font-size: 12px;
439
+ font-weight: 500;
440
+ color: var(--text);
441
+ white-space: nowrap;
442
+ overflow: hidden;
443
+ text-overflow: ellipsis;
444
+ flex: 1;
445
+ min-width: 0;
446
+ }
447
+
448
+ .cpub-tree-item-selected .cpub-tree-title {
449
+ color: var(--accent);
450
+ }
451
+
452
+ .cpub-tree-kebab {
453
+ width: 20px;
454
+ height: 20px;
455
+ display: flex;
456
+ align-items: center;
457
+ justify-content: center;
458
+ background: none;
459
+ border: none;
460
+ color: var(--text-faint);
461
+ cursor: pointer;
462
+ font-size: 10px;
463
+ padding: 0;
464
+ flex-shrink: 0;
465
+ opacity: 0;
466
+ transition: opacity 0.1s;
467
+ }
468
+
469
+ .cpub-tree-item:hover .cpub-tree-kebab {
470
+ opacity: 1;
471
+ }
472
+
473
+ .cpub-tree-kebab:hover {
474
+ color: var(--text);
475
+ }
476
+
477
+ .cpub-tree-add-row {
478
+ display: flex;
479
+ align-items: center;
480
+ gap: 6px;
481
+ padding: 4px 8px;
482
+ }
483
+
484
+ .cpub-tree-add-input {
485
+ flex: 1;
486
+ padding: 3px 6px;
487
+ background: var(--surface);
488
+ border: var(--border-width-default) solid var(--accent);
489
+ color: var(--text);
490
+ font-size: 12px;
491
+ outline: none;
492
+ }
493
+
494
+ .cpub-tree-add-btn {
495
+ display: flex;
496
+ align-items: center;
497
+ justify-content: center;
498
+ gap: 6px;
499
+ width: 100%;
500
+ padding: 8px;
501
+ margin-top: 4px;
502
+ background: none;
503
+ border: 2px dashed var(--border2);
504
+ color: var(--text-faint);
505
+ font-family: var(--font-mono);
506
+ font-size: 10px;
507
+ text-transform: uppercase;
508
+ letter-spacing: 0.06em;
509
+ cursor: pointer;
510
+ }
511
+
512
+ .cpub-tree-add-btn:hover {
513
+ border-color: var(--accent);
514
+ color: var(--accent);
515
+ }
516
+
517
+ /* Context menu */
518
+ .cpub-tree-context {
519
+ position: fixed;
520
+ z-index: 1000;
521
+ background: var(--surface);
522
+ border: var(--border-width-default) solid var(--border);
523
+ box-shadow: var(--shadow-md);
524
+ min-width: 140px;
525
+ padding: 4px 0;
526
+ }
527
+
528
+ .cpub-tree-context-item {
529
+ display: flex;
530
+ align-items: center;
531
+ gap: 8px;
532
+ width: 100%;
533
+ padding: 6px 12px;
534
+ background: none;
535
+ border: none;
536
+ color: var(--text);
537
+ font-size: 12px;
538
+ cursor: pointer;
539
+ text-align: left;
540
+ }
541
+
542
+ .cpub-tree-context-item:hover {
543
+ background: var(--surface2);
544
+ }
545
+
546
+ .cpub-tree-context-item i {
547
+ width: 12px;
548
+ font-size: 10px;
549
+ color: var(--text-faint);
550
+ }
551
+
552
+ .cpub-tree-context-danger {
553
+ color: var(--red);
554
+ }
555
+
556
+ .cpub-tree-context-danger i {
557
+ color: var(--red);
558
+ }
559
+ </style>