PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 1.1.10
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v1.1.10
1.1.10 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 All 34 releases
desktop-mode / assets / css / content-graph.css

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

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