PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / trunk
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin vtrunk
1.1.9 1.1.8 1.1.7 1.1.6 1.1.5 1.1.4 1.1.3 1.1.2 1.1.1 1.1.0 1.0.1 1.0.0 0.9.8 0.9.7 0.9.6 0.9.4 0.9.5 0.9.3 0.9.2 0.9.1 0.9.0 0.8.9 0.8.8 0.8.7 0.8.6 All 33 releases
desktop-mode / assets / css / content-graph.css

content-graph.css in OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin trunk, at assets/css/content-graph.css

1,139 lines 24.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * OpenStation — Content Graph window styles.
3 *
4 * Scoped to `.desktop-mode-content-graph`. Reads `os-variables`
5 * tokens so the chrome adapts to the active theme.
6 *
7 * @since 0.8.2
8 */
9
10 .desktop-mode-content-graph {
11 display: flex;
12 flex-direction: column;
13 width: 100%;
14 height: 100%;
15 background: var( --os-ui-surface, #f7f8fb );
16 color: var( --os-ui-fg, #1a1f2b );
17 font-family: var(
18 --os-ui-font,
19 -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
20 'Helvetica Neue', Arial, sans-serif
21 );
22 }
23
24 /* ---------- Toolbar ------------------------------------------------- */
25
26 .os-content-graph__toolbar {
27 display: flex;
28 align-items: center;
29 gap: 12px;
30 padding: 10px 14px;
31 border-bottom: 1px solid var( --os-ui-border, #e2e6ec );
32 background: var( --os-ui-surface-elevated, #fff );
33 flex-wrap: wrap;
34 }
35
36 .os-content-graph__filters {
37 display: flex;
38 align-items: center;
39 gap: 6px;
40 flex-wrap: wrap;
41 }
42
43 .os-content-graph__chip {
44 display: inline-flex;
45 align-items: center;
46 gap: 6px;
47 padding: 5px 10px;
48 border: 1px solid var( --os-ui-border, #d8dde4 );
49 border-radius: 999px;
50 background: transparent;
51 color: var( --os-ui-fg-muted, #5a6473 );
52 font-size: 12px;
53 cursor: pointer;
54 transition: background 0.12s ease, border-color 0.12s ease,
55 color 0.12s ease;
56 }
57
58 .os-content-graph__chip:hover {
59 background: var( --os-ui-hover, rgba( 79, 139, 243, 0.08 ) );
60 color: var( --os-ui-fg, #1a1f2b );
61 }
62
63 .os-content-graph__chip.is-active {
64 border-color: var( --os-ui-accent, #2c6be5 );
65 background: var( --os-ui-accent-soft, rgba( 44, 107, 229, 0.12 ) );
66 color: var( --os-ui-accent-strong, #1f4ea8 );
67 }
68
69 .os-content-graph__chip .dashicons {
70 font-size: 14px;
71 width: 14px;
72 height: 14px;
73 line-height: 14px;
74 }
75
76 .os-content-graph__chip-count {
77 font-variant-numeric: tabular-nums;
78 font-size: 11px;
79 background: var( --os-ui-surface, #f1f3f7 );
80 padding: 1px 6px;
81 border-radius: 999px;
82 }
83
84 /*
85 * The count pill on an ACTIVE chip is a bright fill, and the chip's
86 * own text colour is `--os-ui-accent-strong` — a light tint, by design,
87 * because the chip body around it is a dark accent wash. The two meet
88 * on this pill and cancel out (Nebula on 70% white is ~1.7:1).
89 *
90 * So the pill names its own text, exactly the way `--os-ui-ribbon-fg`
91 * and `--os-ui-step-chip-fg` do for the other bright fills in the kit.
92 * Unset it inherits the chip's colour, which is what a light theme
93 * wants — there the chip text is already dark.
94 */
95 .os-content-graph__chip.is-active .os-content-graph__chip-count {
96 background: rgba( 255, 255, 255, 0.7 );
97 color: var( --os-ui-cg-chip-count-fg-active, inherit );
98 }
99
100 .os-content-graph__search {
101 position: relative;
102 flex: 1;
103 min-width: 220px;
104 max-width: 320px;
105 }
106
107 .os-content-graph__search-input {
108 width: 100%;
109 padding: 6px 10px;
110 border: 1px solid var( --os-ui-border, #d8dde4 );
111 border-radius: 6px;
112 background: var( --os-ui-surface, #fff );
113 color: inherit;
114 font-size: 13px;
115 }
116
117 .os-content-graph__search-input:focus {
118 outline: none;
119 border-color: var( --os-ui-accent, #2c6be5 );
120 box-shadow: 0 0 0 3px var( --os-ui-accent-soft, rgba( 44, 107, 229, 0.18 ) );
121 }
122
123 .os-content-graph__search-results {
124 position: absolute;
125 top: calc( 100% + 4px );
126 left: 0;
127 right: 0;
128 margin: 0;
129 padding: 4px;
130 list-style: none;
131 background: var( --os-ui-surface-elevated, #fff );
132 border: 1px solid var( --os-ui-border, #d8dde4 );
133 border-radius: 8px;
134 box-shadow: 0 6px 18px rgba( 17, 24, 39, 0.08 );
135 z-index: 10;
136 max-height: 300px;
137 overflow-y: auto;
138 }
139
140 .os-content-graph__search-result {
141 display: flex;
142 align-items: center;
143 justify-content: space-between;
144 gap: 8px;
145 width: 100%;
146 padding: 6px 10px;
147 background: transparent;
148 border: 0;
149 border-radius: 6px;
150 color: inherit;
151 cursor: pointer;
152 text-align: left;
153 font-size: 13px;
154 }
155
156 .os-content-graph__search-result:hover {
157 background: var( --os-ui-hover, rgba( 79, 139, 243, 0.08 ) );
158 }
159
160 .os-content-graph__search-title {
161 flex: 1;
162 overflow: hidden;
163 text-overflow: ellipsis;
164 white-space: nowrap;
165 }
166
167 .os-content-graph__search-type {
168 font-size: 11px;
169 color: var( --os-ui-fg-muted, #6b7280 );
170 text-transform: uppercase;
171 letter-spacing: 0.05em;
172 }
173
174 .os-content-graph__actions {
175 display: flex;
176 align-items: center;
177 gap: 8px;
178 margin-left: auto;
179 }
180
181 .os-content-graph__group-by {
182 /* The os-select grows to its own intrinsic size; cap it so the
183 action row stays compact on narrow windows. */
184 max-width: 180px;
185 }
186
187 .os-content-graph__btn {
188 display: inline-flex;
189 align-items: center;
190 gap: 6px;
191 padding: 5px 10px;
192 border: 1px solid var( --os-ui-border, #d8dde4 );
193 border-radius: 6px;
194 background: var( --os-ui-surface-elevated, #fff );
195 color: inherit;
196 font-size: 12px;
197 cursor: pointer;
198 }
199
200 .os-content-graph__btn:hover {
201 background: var( --os-ui-hover, rgba( 79, 139, 243, 0.08 ) );
202 border-color: var( --os-ui-accent, #2c6be5 );
203 }
204
205 .os-content-graph__btn .dashicons {
206 font-size: 14px;
207 width: 14px;
208 height: 14px;
209 line-height: 14px;
210 }
211
212 .os-content-graph__toolbar-status {
213 margin-left: 8px;
214 font-size: 11px;
215 color: var( --os-ui-fg-muted, #6b7280 );
216 font-variant-numeric: tabular-nums;
217 }
218
219 /* ---------- Cluster labels (DOM overlay) -------------------------- */
220
221 .os-content-graph__group-labels {
222 position: absolute;
223 inset: 0;
224 pointer-events: none;
225 /* Sit above the canvas but below any toolbar / side panel. */
226 z-index: 2;
227 overflow: hidden;
228 }
229
230 .os-content-graph__group-label {
231 position: absolute;
232 top: 0;
233 left: 0;
234 padding: 5px 12px;
235 border-radius: 999px;
236 background: var( --os-ui-cg-cluster-color, #2c6be5 );
237 color: var( --os-ui-fg-on-accent, #fff );
238 font-size: 14px;
239 font-weight: 700;
240 font-family: var(
241 --os-ui-font,
242 -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
243 'Helvetica Neue', Arial, sans-serif
244 );
245 white-space: nowrap;
246 box-shadow: 0 0 0 1.5px rgba( 255, 255, 255, 0.85 ),
247 0 1px 3px rgba( 0, 0, 0, 0.18 );
248 /* Translucent so nodes/edges behind the label remain visible. */
249 opacity: 0.85;
250 /* Smooth the per-frame `style.opacity` and transform updates so
251 the cluster labels glide instead of stutter during the
252 grouping tween. */
253 transition: opacity 120ms ease-out;
254 will-change: transform, opacity;
255 }
256
257 /* ---------- Body layout (stage + side panel) ----------------------- */
258
259 .os-content-graph__body {
260 display: flex;
261 flex: 1;
262 min-height: 0;
263 }
264
265 /* ---------- Stage --------------------------------------------------- */
266
267 .os-content-graph__stage {
268 position: relative;
269 flex: 1;
270 min-width: 0;
271 min-height: 0;
272 overflow: hidden;
273 /*
274 * Three layers, painted bottom-up:
275 * 1. The theme surface fills the canvas (so PixiJS, which uses
276 * `backgroundAlpha: 0`, lets it bleed through).
277 * 2. Two soft theme-tinted radial vignettes give the canvas a
278 * sense of depth without competing with the graph itself.
279 * 3. A 24x24 dot grid at low alpha gives a subtle "graph paper"
280 * texture, mirroring the categories-mindmap reference.
281 */
282 background:
283 radial-gradient(
284 circle,
285 rgba( 0, 0, 0, 0.06 ) 1px,
286 transparent 1.5px
287 ) 0 0 / 24px 24px,
288 radial-gradient(
289 ellipse at 28% 22%,
290 rgba( 79, 139, 243, 0.08 ),
291 transparent 60%
292 ),
293 radial-gradient(
294 ellipse at 78% 80%,
295 rgba( 176, 102, 215, 0.06 ),
296 transparent 58%
297 ),
298 var( --os-ui-surface, #f7f8fb );
299 }
300
301 .os-content-graph__canvas {
302 display: block;
303 width: 100%;
304 height: 100%;
305 }
306
307 /*
308 * Full-canvas loading wash. Resting state is invisible: the bundle
309 * arms it on every graph fetch and adds `--visible` only once the
310 * fetch has run long enough to be worth interrupting the board for
311 * (see `src/content-graph/loading-overlay.ts`). A class rather than
312 * `[hidden]` so the wash fades instead of popping.
313 */
314 .os-content-graph__loading {
315 position: absolute;
316 inset: 0;
317 display: flex;
318 align-items: center;
319 justify-content: center;
320 /*
321 * A translucent sheet of the stage's own surface, so it dims a
322 * dark board as well as a light one. The literal is the pre-brand
323 * light surface, kept as the floor if the token is missing.
324 */
325 background: color-mix(
326 in srgb,
327 var( --os-ui-surface, #f7f8fb ) 72%,
328 transparent
329 );
330 pointer-events: none;
331 z-index: 3;
332 opacity: 0;
333 visibility: hidden;
334 transition: opacity 0.2s ease, visibility 0s linear 0.2s;
335 }
336
337 .os-content-graph__loading--visible {
338 opacity: 1;
339 visibility: visible;
340 transition: opacity 0.2s ease;
341 }
342
343 .os-content-graph__loading[ hidden ] {
344 display: none;
345 }
346
347 @media ( prefers-reduced-motion: reduce ) {
348 .os-content-graph__loading {
349 transition: none;
350 }
351 }
352
353 /* ---------- Board notices (empty board, no threads yet) ----------- */
354
355 /*
356 * <os-empty-state> owns the icon + copy; this is placement only. It
357 * covers the (empty) canvas and lets pointer events through so the
358 * stage still pans and the dot grid still reads as a board.
359 */
360 .os-content-graph__empty {
361 position: absolute;
362 inset: 0;
363 z-index: 2;
364 pointer-events: none;
365 }
366
367 /*
368 * "No threads yet" note. Top-left, out of the way of a small board
369 * centred in the stage and of the side panel on the right. Real DOM
370 * text over the canvas, so it is translatable and follows the theme
371 * tokens like the rest of the chrome; pointer-events are off so a
372 * pan that starts on it still pans the board. The bundle hides it
373 * while a grouping is active, when the cluster labels need the space.
374 */
375 .os-content-graph__hint {
376 position: absolute;
377 top: 12px;
378 left: 12px;
379 z-index: 2;
380 max-width: 320px;
381 padding: 10px 14px 12px;
382 border: 1px solid var( --os-ui-border, #e2e6ec );
383 border-radius: 8px;
384 background: var( --os-ui-surface-elevated, #fff );
385 color: var( --os-ui-fg-muted, #5a6473 );
386 box-shadow: 0 1px 3px rgba( 0, 0, 0, 0.08 );
387 font-size: 12px;
388 line-height: 1.45;
389 pointer-events: none;
390 }
391
392 .os-content-graph__hint-title {
393 display: block;
394 margin-bottom: 3px;
395 color: var( --os-ui-fg, #1a1f2b );
396 font-size: 12.5px;
397 font-weight: 600;
398 }
399
400 .os-content-graph__hint-body {
401 margin: 0;
402 }
403
404 /* ---------- Right side panel: breadcrumb --------------------------- */
405
406 .os-content-graph__panel-breadcrumb {
407 display: flex;
408 align-items: center;
409 padding: 8px 14px 0;
410 min-height: 32px;
411 }
412
413 .os-content-graph__panel-breadcrumb[ hidden ] {
414 display: none;
415 }
416
417 .os-content-graph__panel-breadcrumb-back {
418 display: inline-flex;
419 align-items: center;
420 gap: 4px;
421 background: transparent;
422 border: 0;
423 padding: 4px 8px 4px 4px;
424 border-radius: 6px;
425 cursor: pointer;
426 color: var( --os-ui-fg-muted, #6b7280 );
427 font-size: 12px;
428 font-weight: 500;
429 max-width: 100%;
430 min-width: 0;
431 text-align: left;
432 }
433
434 .os-content-graph__panel-breadcrumb-back:hover {
435 background: var( --os-ui-hover, rgba( 0, 0, 0, 0.05 ) );
436 color: var( --os-ui-fg, #1a1f2b );
437 }
438
439 .os-content-graph__panel-breadcrumb-arrow {
440 font-size: 16px;
441 width: 16px;
442 height: 16px;
443 line-height: 16px;
444 flex-shrink: 0;
445 }
446
447 .os-content-graph__panel-breadcrumb-label {
448 overflow: hidden;
449 text-overflow: ellipsis;
450 white-space: nowrap;
451 min-width: 0;
452 }
453
454 /* ---------- Right side panel --------------------------------------- */
455
456 .os-content-graph__panel {
457 width: 320px;
458 flex-shrink: 0;
459 border-left: 1px solid var( --os-ui-border, #e2e6ec );
460 background: var( --os-ui-surface-elevated, #fff );
461 display: flex;
462 flex-direction: column;
463 overflow: hidden;
464 /*
465 * Subtle slide-in / slide-out when the panel opens or closes —
466 * the outer container collapses its width while the inner frame
467 * eases off to the right + fades, so the canvas reclaims the
468 * space smoothly instead of the panel popping out of layout.
469 */
470 transition:
471 width 220ms cubic-bezier( 0.32, 0.72, 0, 1 ),
472 border-left-width 220ms cubic-bezier( 0.32, 0.72, 0, 1 ),
473 opacity 180ms ease-out;
474 will-change: width, opacity;
475 }
476
477 .os-content-graph__panel[ hidden ],
478 .os-content-graph__panel--closed {
479 width: 0;
480 opacity: 0;
481 pointer-events: none;
482 border-left-width: 0;
483 }
484
485 .os-content-graph__panel[ hidden ] {
486 /* Initial PHP-rendered state — no transition on the very first
487 * paint, otherwise the panel briefly slides in on page load. */
488 transition: none;
489 }
490
491 .os-content-graph__panel-frame {
492 /*
493 * Without these rules the body's `flex: 1; overflow-y: auto`
494 * has nothing to constrain it — the body just grows to fit its
495 * dossier content and the action buttons get clipped at the
496 * bottom of the panel. Make the frame a flex column itself so
497 * the body is forced to share the panel's height with the
498 * breadcrumb and head, then scrolls the overflow.
499 */
500 display: flex;
501 flex-direction: column;
502 flex: 1 1 0;
503 min-height: 0;
504 width: 320px;
505 overflow: hidden;
506 /* Slides + fades alongside the parent's width collapse so the
507 * panel content visibly leaves to the right when closing. */
508 transition:
509 transform 220ms cubic-bezier( 0.32, 0.72, 0, 1 ),
510 opacity 180ms ease-out;
511 }
512
513 .os-content-graph__panel[ hidden ] .os-content-graph__panel-frame,
514 .os-content-graph__panel--closed .os-content-graph__panel-frame {
515 transform: translateX( 16% );
516 opacity: 0;
517 }
518
519 @media ( prefers-reduced-motion: reduce ) {
520 .os-content-graph__panel,
521 .os-content-graph__panel-frame {
522 transition: none;
523 }
524 }
525
526 .os-content-graph__panel-head {
527 display: flex;
528 align-items: flex-start;
529 gap: 8px;
530 padding: 14px 16px 10px;
531 border-bottom: 1px solid var( --os-ui-border, #eef0f4 );
532 }
533
534 .os-content-graph__panel-title-wrap {
535 flex: 1;
536 min-width: 0;
537 }
538
539 .os-content-graph__panel-title {
540 margin: 0;
541 font-size: 14px;
542 font-weight: 600;
543 line-height: 1.35;
544 overflow-wrap: anywhere;
545 }
546
547 .os-content-graph__panel-meta {
548 margin: 4px 0 0;
549 font-size: 11px;
550 color: var( --os-ui-fg-muted, #6b7280 );
551 text-transform: uppercase;
552 letter-spacing: 0.06em;
553 }
554
555 .os-content-graph__panel-close {
556 background: transparent;
557 border: 0;
558 color: var( --os-ui-fg-muted, #6b7280 );
559 cursor: pointer;
560 padding: 2px;
561 border-radius: 4px;
562 flex-shrink: 0;
563 }
564
565 .os-content-graph__panel-close:hover {
566 background: var( --os-ui-hover, rgba( 0, 0, 0, 0.05 ) );
567 color: inherit;
568 }
569
570 .os-content-graph__panel-body {
571 flex: 1;
572 min-height: 0;
573 /*
574 * The body is now a clipped stage that holds one or two
575 * absolutely-positioned `__panel-page` children. During an iOS-
576 * style navigation, the new page slides in over the old; both
577 * are clipped here so the off-stage portion isn't visible.
578 */
579 position: relative;
580 overflow: hidden;
581 padding: 0;
582 }
583
584 .os-content-graph__panel-page {
585 position: absolute;
586 inset: 0;
587 overflow-y: auto;
588 padding: 14px 16px 18px;
589 display: flex;
590 flex-direction: column;
591 gap: 14px;
592 background: var( --os-ui-surface-elevated, #fff );
593 /*
594 * iOS navigation curve — quick out, slow in. 240ms matches
595 * UIKit's default push/pop duration closely enough for the
596 * reviewer-flagged "abrupt" feeling to ease.
597 */
598 transition:
599 transform 240ms cubic-bezier( 0.32, 0.72, 0, 1 ),
600 opacity 200ms ease-out;
601 will-change: transform, opacity;
602 }
603
604 /*
605 * Forward push: new page enters from the right at full distance, the
606 * outgoing page parallax-slides a fraction of the way to the left.
607 * Back pop: mirrored — new page enters from the left at parallax
608 * distance, current page slides off the right at full distance.
609 */
610 .os-content-graph__page-from-right {
611 transform: translateX( 100% );
612 }
613 .os-content-graph__page-from-left {
614 transform: translateX( -28% );
615 opacity: 0.55;
616 }
617 .os-content-graph__page-to-right {
618 transform: translateX( 100% );
619 /* Keep the outgoing page above the incoming parallax shadow on
620 * back so the slide reads as the user "removing" it. */
621 z-index: 2;
622 }
623 .os-content-graph__page-to-left {
624 transform: translateX( -28% );
625 opacity: 0.55;
626 }
627
628 @media ( prefers-reduced-motion: reduce ) {
629 .os-content-graph__panel-page {
630 transition: none;
631 }
632 }
633
634 .os-content-graph__panel-section-label {
635 display: block;
636 font-size: 10px;
637 font-weight: 600;
638 color: var( --os-ui-fg-muted, #6b7280 );
639 text-transform: uppercase;
640 letter-spacing: 0.06em;
641 margin-bottom: 4px;
642 }
643
644 .os-content-graph__panel-author {
645 display: flex;
646 flex-direction: column;
647 gap: 4px;
648 }
649
650 .os-content-graph__panel-author[ hidden ] {
651 display: none;
652 }
653
654 .os-content-graph__panel-author-row {
655 display: flex;
656 align-items: center;
657 gap: 8px;
658 }
659
660 .os-content-graph__panel-avatar {
661 width: 32px;
662 height: 32px;
663 border-radius: 50%;
664 flex-shrink: 0;
665 }
666
667 .os-content-graph__panel-author-name {
668 font-size: 13px;
669 font-weight: 500;
670 }
671
672 .os-content-graph__panel-dates {
673 display: flex;
674 flex-direction: column;
675 gap: 8px;
676 }
677
678 .os-content-graph__panel-dates[ hidden ] {
679 display: none;
680 }
681
682 .os-content-graph__panel-date-row {
683 display: flex;
684 flex-direction: column;
685 gap: 2px;
686 }
687
688 .os-content-graph__panel-date-value {
689 font-size: 12px;
690 color: var( --os-ui-fg, #1a1f2b );
691 font-variant-numeric: tabular-nums;
692 }
693
694 .os-content-graph__panel-stats {
695 margin: 0;
696 padding: 0;
697 list-style: none;
698 display: grid;
699 grid-template-columns: repeat( auto-fill, minmax( 80px, 1fr ) );
700 gap: 4px;
701 background: var( --os-ui-surface, #f7f8fb );
702 border: 1px solid var( --os-ui-border, #eef0f4 );
703 border-radius: 8px;
704 padding: 8px;
705 }
706
707 .os-content-graph__panel-stats[ hidden ] {
708 display: none;
709 }
710
711 .os-content-graph__panel-stat {
712 display: flex;
713 flex-direction: column;
714 align-items: center;
715 gap: 1px;
716 }
717
718 .os-content-graph__panel-stat-num {
719 font-size: 16px;
720 font-weight: 600;
721 font-variant-numeric: tabular-nums;
722 color: var( --os-ui-fg, #1a1f2b );
723 }
724
725 .os-content-graph__panel-stat-label {
726 font-size: 10px;
727 color: var( --os-ui-fg-muted, #6b7280 );
728 text-transform: uppercase;
729 letter-spacing: 0.04em;
730 }
731
732 .os-content-graph__panel-actions {
733 display: flex;
734 flex-direction: column;
735 gap: 6px;
736 }
737
738 .os-content-graph__panel-actions[ hidden ] {
739 display: none;
740 }
741
742 .os-content-graph__panel-actions .os-content-graph__btn {
743 width: 100%;
744 justify-content: center;
745 }
746
747 .os-content-graph__btn--primary {
748 border-color: var( --os-ui-accent, #2c6be5 );
749 background: var( --os-ui-accent, #2c6be5 );
750 color: var( --os-ui-fg-on-accent, #fff );
751 }
752
753 .os-content-graph__btn--primary:hover {
754 background: var( --os-ui-accent-strong, #1f4ea8 );
755 border-color: var( --os-ui-accent-strong, #1f4ea8 );
756 }
757
758 .os-content-graph__panel-loading {
759 display: flex;
760 justify-content: center;
761 padding: 24px 0;
762 }
763
764 .os-content-graph__panel-loading[ hidden ] {
765 display: none;
766 }
767
768 .os-content-graph__panel-empty {
769 margin: 0;
770 font-size: 12px;
771 color: var( --os-ui-fg-muted, #6b7280 );
772 font-style: italic;
773 }
774
775 .os-content-graph__panel-empty[ hidden ] {
776 display: none;
777 }
778
779 /* ---------- Contextual detail views (user / term / comment) -------- */
780
781 .os-content-graph__panel-detail-section {
782 display: flex;
783 flex-direction: column;
784 gap: 4px;
785 }
786
787 .os-content-graph__panel-detail-head {
788 display: flex;
789 align-items: center;
790 gap: 12px;
791 }
792
793 .os-content-graph__panel-detail-avatar--lg {
794 width: 56px;
795 height: 56px;
796 border-radius: 50%;
797 flex-shrink: 0;
798 }
799
800 .os-content-graph__panel-detail-handle {
801 display: flex;
802 flex-direction: column;
803 gap: 2px;
804 min-width: 0;
805 }
806
807 .os-content-graph__panel-detail-handle strong {
808 font-size: 14px;
809 font-weight: 600;
810 overflow-wrap: anywhere;
811 }
812
813 .os-content-graph__panel-detail-handle span {
814 font-size: 11px;
815 color: var( --os-ui-fg-muted, #6b7280 );
816 overflow: hidden;
817 text-overflow: ellipsis;
818 white-space: nowrap;
819 }
820
821 .os-content-graph__panel-detail-prose {
822 margin: 0;
823 font-size: 13px;
824 line-height: 1.5;
825 color: var( --os-ui-fg, #1a1f2b );
826 }
827
828 .os-content-graph__panel-detail-link {
829 font-size: 12px;
830 color: var( --os-ui-accent, #2c6be5 );
831 text-decoration: none;
832 overflow: hidden;
833 text-overflow: ellipsis;
834 white-space: nowrap;
835 display: inline-block;
836 max-width: 100%;
837 }
838
839 .os-content-graph__panel-detail-link:hover {
840 text-decoration: underline;
841 }
842
843 .os-content-graph__panel-detail-html {
844 font-size: 13px;
845 line-height: 1.5;
846 color: var( --os-ui-fg, #1a1f2b );
847 background: var( --os-ui-surface, #f7f8fb );
848 border: 1px solid var( --os-ui-border, #eef0f4 );
849 border-radius: 6px;
850 padding: 10px 12px;
851 overflow-wrap: anywhere;
852 }
853
854 .os-content-graph__panel-detail-html p:first-child {
855 margin-top: 0;
856 }
857
858 .os-content-graph__panel-detail-html p:last-child {
859 margin-bottom: 0;
860 }
861
862 .os-content-graph__panel-detail-quote {
863 margin: 0;
864 padding: 10px 12px;
865 border-left: 3px solid var( --os-ui-border, #d8dde4 );
866 background: var( --os-ui-surface, #f7f8fb );
867 border-radius: 0 6px 6px 0;
868 display: flex;
869 flex-direction: column;
870 gap: 4px;
871 }
872
873 .os-content-graph__panel-detail-quote strong {
874 font-size: 12px;
875 font-weight: 600;
876 }
877
878 .os-content-graph__panel-detail-quote span {
879 font-size: 12px;
880 color: var( --os-ui-fg-muted, #4b5563 );
881 }
882
883 .os-content-graph__panel-detail-thumb {
884 display: flex;
885 justify-content: center;
886 background: var( --os-ui-surface, #f7f8fb );
887 border: 1px solid var( --os-ui-border, #eef0f4 );
888 border-radius: 8px;
889 padding: 8px;
890 max-height: 240px;
891 overflow: hidden;
892 }
893
894 .os-content-graph__panel-detail-thumb img {
895 max-width: 100%;
896 max-height: 220px;
897 object-fit: contain;
898 border-radius: 4px;
899 }
900
901 .os-content-graph__panel-badges {
902 display: flex;
903 flex-wrap: wrap;
904 gap: 4px;
905 }
906
907 .os-content-graph__panel-badge {
908 display: inline-flex;
909 align-items: center;
910 padding: 2px 8px;
911 font-size: 11px;
912 font-weight: 500;
913 border-radius: 999px;
914 background: var( --os-ui-surface, #f1f3f7 );
915 color: var( --os-ui-fg-muted, #4b5563 );
916 border: 1px solid var( --os-ui-border, #e2e6ec );
917 }
918
919 .os-content-graph__panel-badge--blue {
920 background: rgba( 44, 107, 229, 0.12 );
921 color: var( --os-ui-accent-strong, #1f4ea8 );
922 border-color: rgba( 44, 107, 229, 0.35 );
923 }
924
925 .os-content-graph__panel-badge--green {
926 background: rgba( 44, 169, 122, 0.14 );
927 color: #1f7754;
928 border-color: rgba( 44, 169, 122, 0.35 );
929 }
930
931 .os-content-graph__panel-badge--amber {
932 background: rgba( 232, 137, 58, 0.16 );
933 color: #8a5012;
934 border-color: rgba( 232, 137, 58, 0.35 );
935 }
936
937 .os-content-graph__panel-inline-meta {
938 display: flex;
939 flex-direction: column;
940 gap: 6px;
941 }
942
943 .os-content-graph__panel-inline-meta-row {
944 display: flex;
945 align-items: baseline;
946 gap: 8px;
947 }
948
949 .os-content-graph__panel-inline-meta-row .os-content-graph__panel-section-label {
950 flex: 0 0 auto;
951 margin-bottom: 0;
952 }
953
954 .os-content-graph__panel-chips {
955 display: flex;
956 flex-wrap: wrap;
957 gap: 4px;
958 margin: 0;
959 padding: 0;
960 list-style: none;
961 }
962
963 .os-content-graph__panel-chip {
964 display: inline-flex;
965 align-items: center;
966 gap: 6px;
967 padding: 3px 9px;
968 font-size: 12px;
969 border-radius: 999px;
970 background: var( --os-ui-surface, #f1f3f7 );
971 border: 1px solid var( --os-ui-border, #e2e6ec );
972 }
973
974 .os-content-graph__panel-chip-count {
975 font-variant-numeric: tabular-nums;
976 font-size: 11px;
977 color: var( --os-ui-fg-muted, #6b7280 );
978 }
979
980 .os-content-graph__panel-author-list {
981 display: flex;
982 flex-direction: column;
983 gap: 6px;
984 margin: 0;
985 padding: 0;
986 list-style: none;
987 }
988
989 .os-content-graph__panel-author-list-row {
990 display: flex;
991 align-items: center;
992 gap: 8px;
993 padding: 4px 0;
994 }
995
996 .os-content-graph__panel-author-list-row .os-content-graph__panel-avatar {
997 width: 24px;
998 height: 24px;
999 border-radius: 50%;
1000 flex-shrink: 0;
1001 }
1002
1003 .os-content-graph__panel-author-list-row .os-content-graph__panel-author-name {
1004 flex: 1 1 auto;
1005 font-size: 13px;
1006 min-width: 0;
1007 overflow: hidden;
1008 text-overflow: ellipsis;
1009 white-space: nowrap;
1010 }
1011
1012 .os-content-graph__panel-milestones {
1013 display: flex;
1014 flex-direction: column;
1015 gap: 6px;
1016 }
1017
1018 .os-content-graph__panel-milestone-row {
1019 display: flex;
1020 justify-content: space-between;
1021 gap: 8px;
1022 }
1023
1024 .os-content-graph__panel-milestone-key {
1025 font-size: 12px;
1026 color: var( --os-ui-fg-muted, #6b7280 );
1027 }
1028
1029 .os-content-graph__panel-replies {
1030 display: flex;
1031 flex-direction: column;
1032 gap: 6px;
1033 margin: 0;
1034 padding: 0;
1035 list-style: none;
1036 }
1037
1038 .os-content-graph__panel-reply {
1039 background: var( --os-ui-surface, #f7f8fb );
1040 border: 1px solid var( --os-ui-border, #eef0f4 );
1041 border-radius: 6px;
1042 padding: 8px 10px;
1043 }
1044
1045 .os-content-graph__panel-reply header {
1046 display: flex;
1047 align-items: baseline;
1048 justify-content: space-between;
1049 gap: 8px;
1050 margin-bottom: 4px;
1051 }
1052
1053 .os-content-graph__panel-reply header strong {
1054 font-size: 12px;
1055 }
1056
1057 .os-content-graph__panel-reply header span {
1058 font-size: 11px;
1059 color: var( --os-ui-fg-muted, #6b7280 );
1060 }
1061
1062 .os-content-graph__panel-reply p {
1063 margin: 0;
1064 font-size: 12px;
1065 line-height: 1.45;
1066 color: var( --os-ui-fg-muted, #4b5563 );
1067 }
1068
1069 .os-content-graph__panel-detail-loading {
1070 display: flex;
1071 align-items: center;
1072 gap: 8px;
1073 padding: 8px 0;
1074 font-size: 12px;
1075 color: var( --os-ui-fg-muted, #6b7280 );
1076 }
1077
1078 .os-content-graph__panel-stat-row {
1079 display: flex;
1080 flex-direction: column;
1081 gap: 2px;
1082 }
1083
1084 @media ( max-width: 760px ) {
1085 .os-content-graph__panel {
1086 position: absolute;
1087 inset: auto 0 0 0;
1088 width: 100%;
1089 max-height: 60%;
1090 border-left: 0;
1091 border-top: 1px solid var( --os-ui-border, #e2e6ec );
1092 box-shadow: 0 -8px 24px rgba( 17, 24, 39, 0.08 );
1093 z-index: 4;
1094 }
1095 }
1096
1097 /* ---------- Satellite tooltip -------------------------------------- */
1098
1099 .os-content-graph__tooltip {
1100 position: absolute;
1101 z-index: 5;
1102 display: flex;
1103 flex-direction: column;
1104 gap: 2px;
1105 max-width: 240px;
1106 padding: 6px 10px;
1107 background: var(
1108 --os-tooltip-bg,
1109 var( --os-ui-surface-elevated, #1a1f2b )
1110 );
1111 color: var(
1112 --os-tooltip-fg,
1113 var( --os-ui-fg-on-accent, #fff )
1114 );
1115 border-radius: 6px;
1116 box-shadow: 0 4px 14px rgba( 17, 24, 39, 0.18 );
1117 font-size: 12px;
1118 pointer-events: none;
1119 }
1120
1121 .os-content-graph__tooltip[ hidden ] {
1122 display: none;
1123 }
1124
1125 .os-content-graph__tooltip strong {
1126 font-weight: 600;
1127 overflow: hidden;
1128 text-overflow: ellipsis;
1129 white-space: nowrap;
1130 }
1131
1132 .os-content-graph__tooltip span {
1133 font-size: 11px;
1134 opacity: 0.85;
1135 overflow: hidden;
1136 text-overflow: ellipsis;
1137 white-space: nowrap;
1138 }
1139