@transitrix/diagrams 1.8.3 → 1.8.6
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.
- package/dist/__tests__/edge-path.test.js +8 -0
- package/dist/capability-map/render-capability-tree.js +2 -2
- package/dist/edge-path.js +10 -1
- package/dist/theme/__tests__/tokens.test.d.ts +1 -0
- package/dist/theme/__tests__/tokens.test.js +159 -0
- package/dist/theme/index.d.ts +1 -1
- package/dist/theme/index.js +1 -1
- package/dist/theme/themes.js +4 -1
- package/dist/theme/tokens.d.ts +107 -45
- package/dist/theme/tokens.js +82 -32
- package/dist/webview/render-activities.d.ts +1 -1
- package/dist/webview/render-activities.js +3 -3
- package/dist/webview/render-fgca.js +1 -4
- package/dist/webview/render-process.d.ts +1 -1
- package/dist/webview/render-process.js +20 -20
- package/package.json +1 -1
- package/src/__tests__/edge-path.test.ts +9 -0
- package/src/capability-map/render-capability-tree.ts +2 -2
- package/src/edge-path.ts +15 -1
- package/src/theme/__tests__/tokens.test.ts +185 -0
- package/src/theme/index.ts +1 -1
- package/src/theme/themes.ts +4 -0
- package/src/theme/tokens.ts +83 -32
- package/src/webview/render-activities.ts +3 -3
- package/src/webview/render-fgca.ts +1 -4
- package/src/webview/render-process.ts +20 -20
|
@@ -7,7 +7,7 @@ import { type NodeSizePreset } from '../node-size-presets.js';
|
|
|
7
7
|
* network subset. Embedded alongside the shared theme CSS so the SVG is
|
|
8
8
|
* self-contained for the JCEF host.
|
|
9
9
|
*/
|
|
10
|
-
export declare const ACTIVITIES_NETWORK_CSS = "\n .act-node { fill: var(--ts-layer-activity, #d4edda); stroke: var(--ts-node-stroke, #
|
|
10
|
+
export declare const ACTIVITIES_NETWORK_CSS = "\n .act-node { fill: var(--ts-layer-activity, #d4edda); stroke: var(--ts-node-stroke, #004d67); stroke-width: 1; }\n .critical-node { fill: var(--ts-brand-orange-tint, #ffeee5); stroke: var(--ts-brand-orange, #ff4d00); stroke-width: 1; }\n .milestone-node { fill: #ecfeff; stroke: var(--ts-text-muted, #64748b); stroke-dasharray: 4 2; }\n .critical-edge { stroke: var(--ts-brand-orange, #ff4d00); stroke-width: 1.5; }\n .arrow-fill-critical { fill: var(--ts-brand-orange, #ff4d00); }\n";
|
|
11
11
|
/** Arrowhead marker defs shared by both hosts for the network view. */
|
|
12
12
|
export declare const ACTIVITIES_NETWORK_DEFS = "<defs>\n <marker id=\"arrow\" markerWidth=\"8\" markerHeight=\"8\" refX=\"8\" refY=\"3\" orient=\"auto\">\n <path d=\"M0,0 L0,6 L8,3 z\" class=\"arrow-fill\"/>\n </marker>\n <marker id=\"arrow-crit\" markerWidth=\"8\" markerHeight=\"8\" refX=\"8\" refY=\"3\" orient=\"auto\">\n <path d=\"M0,0 L0,6 L8,3 z\" class=\"arrow-fill-critical\"/>\n </marker>\n</defs>";
|
|
13
13
|
/**
|
|
@@ -31,10 +31,10 @@ const N_PAD = 24;
|
|
|
31
31
|
* self-contained for the JCEF host.
|
|
32
32
|
*/
|
|
33
33
|
export const ACTIVITIES_NETWORK_CSS = `
|
|
34
|
-
.act-node { fill: var(--ts-layer-activity, #d4edda); stroke: var(--ts-node-stroke, #
|
|
35
|
-
.critical-node { fill: #
|
|
34
|
+
.act-node { fill: var(--ts-layer-activity, #d4edda); stroke: var(--ts-node-stroke, #004d67); stroke-width: 1; }
|
|
35
|
+
.critical-node { fill: var(--ts-brand-orange-tint, #ffeee5); stroke: var(--ts-brand-orange, #ff4d00); stroke-width: 1; }
|
|
36
36
|
.milestone-node { fill: #ecfeff; stroke: var(--ts-text-muted, #64748b); stroke-dasharray: 4 2; }
|
|
37
|
-
.critical-edge { stroke: var(--ts-brand-orange, #ff4d00); stroke-width:
|
|
37
|
+
.critical-edge { stroke: var(--ts-brand-orange, #ff4d00); stroke-width: 1.5; }
|
|
38
38
|
.arrow-fill-critical { fill: var(--ts-brand-orange, #ff4d00); }
|
|
39
39
|
`;
|
|
40
40
|
/** Arrowhead marker defs shared by both hosts for the network view. */
|
|
@@ -38,10 +38,7 @@ export function renderFgcaBody(columns, nodes, edges, curvature, entryCurvature,
|
|
|
38
38
|
const nodeHeight = bodyOptions.nodeHeight ?? FGCA_NODE_H;
|
|
39
39
|
const headerTruncate = Math.max(8, Math.floor((nodeWidth - 16) / 7));
|
|
40
40
|
const headerSvg = columns
|
|
41
|
-
.map(({ col, x }) => [
|
|
42
|
-
`<rect class="diagram-node layer-${col}" x="${x}" y="${PAD}" width="${nodeWidth}" height="${HEADER_H}" rx="6"/>`,
|
|
43
|
-
`<text class="text-header" x="${x + nodeWidth / 2}" y="${PAD + HEADER_H / 2}" text-anchor="middle" dominant-baseline="central">${escXml(truncateLine(COL_LABELS[col], headerTruncate))}</text>`,
|
|
44
|
-
].join('\n'))
|
|
41
|
+
.map(({ col, x }) => `<text class="text-header" x="${x + nodeWidth / 2}" y="${PAD + HEADER_H / 2}" text-anchor="middle" dominant-baseline="central">${escXml(truncateLine(COL_LABELS[col], headerTruncate))}</text>`)
|
|
45
42
|
.join('\n');
|
|
46
43
|
const edgeSvg = edges
|
|
47
44
|
.map((e) => `<path d="${horizontalCubicEdgePath(e.sx, e.sy, e.tx, e.ty, curvature, entryCurvature)}" class="diagram-edge" marker-end="url(#arrow)"/>`)
|
|
@@ -70,7 +70,7 @@ export interface RenderProcessOptions {
|
|
|
70
70
|
/** Extra vertical space (px) reserved above the pool for a title block. */
|
|
71
71
|
topInset?: number;
|
|
72
72
|
}
|
|
73
|
-
export declare const BPMN_PROCESS_CSS = "\n.bpmn-pool { fill: var(--ts-bg-surface,#f8fafc); stroke: var(--ts-node-stroke,#
|
|
73
|
+
export declare const BPMN_PROCESS_CSS = "\n.bpmn-pool { fill: var(--ts-bg-surface,#f8fafc); stroke: var(--ts-node-stroke,#004d67); stroke-width: 1.5; }\n.bpmn-pool-name { fill: var(--ts-bg-elevated,#f1f5f9); stroke: var(--ts-node-stroke,#004d67); stroke-width: 0.75; }\n.bpmn-lane { fill: var(--ts-bg,#ffffff); stroke: var(--ts-node-stroke,#004d67); stroke-width: 0.75; }\n.bpmn-lane-header { fill: var(--ts-bg-elevated,#f1f5f9); stroke: var(--ts-node-stroke,#004d67); stroke-width: 0.75; }\n.bpmn-pool-label { fill: var(--ts-text-primary,#0d2b35); font-size: 11px; font-weight: 700; }\n.bpmn-lane-label { fill: var(--ts-text-primary,#0d2b35); font-size: 11px; font-weight: 600; }\n.bpmn-task { fill: var(--ts-bg,#ffffff); stroke: var(--ts-node-stroke,#004d67); stroke-width: 1.5; }\n.bpmn-task-name { fill: var(--ts-text-primary,#0d2b35); font-size: 11px; text-anchor: middle; }\n.bpmn-event { fill: var(--ts-bg,#ffffff); stroke: var(--ts-node-stroke,#004d67); }\n.bpmn-event-start { stroke-width: 1.5; }\n.bpmn-event-end { stroke-width: 4; }\n.bpmn-event-label { fill: var(--ts-text-secondary,#516970); font-size: 10px; text-anchor: middle; }\n.bpmn-gateway { fill: var(--ts-bg,#ffffff); stroke: var(--ts-node-stroke,#004d67); stroke-width: 1.5; }\n.bpmn-gateway-marker { stroke: var(--ts-node-stroke,#004d67); stroke-width: 2.5; stroke-linecap: round; fill: none; }\n.bpmn-gateway-label { fill: var(--ts-text-secondary,#516970); font-size: 10px; text-anchor: middle; }\n.bpmn-seq-flow { fill: none; stroke: var(--ts-edge-stroke,#004d67); stroke-width: 1.5; }\n.bpmn-default-mark { stroke: var(--ts-edge-stroke,#004d67); stroke-width: 1.5; stroke-linecap: round; }\n.bpmn-assoc { fill: none; stroke: var(--ts-edge-stroke,#004d67); stroke-width: 1; stroke-dasharray: 4 2; }\n.bpmn-data-obj { fill: var(--ts-bg,#ffffff); stroke: var(--ts-node-stroke,#004d67); stroke-width: 1; }\n.bpmn-data-obj-label { fill: var(--ts-text-secondary,#516970); font-size: 10px; text-anchor: middle; }\n.bpmn-event-intermediate { fill: none; stroke: var(--ts-node-stroke,#004d67); stroke-width: 1; }\n.bpmn-event-icon { fill: none; stroke: var(--ts-node-stroke,#004d67); stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }\n";
|
|
74
74
|
/**
|
|
75
75
|
* Canonical body emitter, shared by every host (VS Code, JCEF, static export).
|
|
76
76
|
*
|
|
@@ -35,28 +35,28 @@ const LABEL_LINE_H = 13;
|
|
|
35
35
|
// BPMN-specific CSS — no font-style:italic per CLAUDE.md design rule
|
|
36
36
|
// ---------------------------------------------------------------------------
|
|
37
37
|
export const BPMN_PROCESS_CSS = `
|
|
38
|
-
.bpmn-pool { fill: var(--ts-bg-surface,#f8fafc); stroke: var(--ts-node-stroke,#
|
|
39
|
-
.bpmn-pool-name { fill: var(--ts-bg-elevated,#f1f5f9); stroke: var(--ts-node-stroke,#
|
|
40
|
-
.bpmn-lane { fill: var(--ts-bg,#ffffff); stroke: var(--ts-node-stroke,#
|
|
41
|
-
.bpmn-lane-header { fill: var(--ts-bg-elevated,#f1f5f9); stroke: var(--ts-node-stroke,#
|
|
42
|
-
.bpmn-pool-label { fill: var(--ts-text-primary,#
|
|
43
|
-
.bpmn-lane-label { fill: var(--ts-text-primary,#
|
|
44
|
-
.bpmn-task { fill: var(--ts-bg,#ffffff); stroke: var(--ts-node-stroke,#
|
|
45
|
-
.bpmn-task-name { fill: var(--ts-text-primary,#
|
|
46
|
-
.bpmn-event { fill: var(--ts-bg,#ffffff); stroke: var(--ts-node-stroke,#
|
|
38
|
+
.bpmn-pool { fill: var(--ts-bg-surface,#f8fafc); stroke: var(--ts-node-stroke,#004d67); stroke-width: 1.5; }
|
|
39
|
+
.bpmn-pool-name { fill: var(--ts-bg-elevated,#f1f5f9); stroke: var(--ts-node-stroke,#004d67); stroke-width: 0.75; }
|
|
40
|
+
.bpmn-lane { fill: var(--ts-bg,#ffffff); stroke: var(--ts-node-stroke,#004d67); stroke-width: 0.75; }
|
|
41
|
+
.bpmn-lane-header { fill: var(--ts-bg-elevated,#f1f5f9); stroke: var(--ts-node-stroke,#004d67); stroke-width: 0.75; }
|
|
42
|
+
.bpmn-pool-label { fill: var(--ts-text-primary,#0d2b35); font-size: 11px; font-weight: 700; }
|
|
43
|
+
.bpmn-lane-label { fill: var(--ts-text-primary,#0d2b35); font-size: 11px; font-weight: 600; }
|
|
44
|
+
.bpmn-task { fill: var(--ts-bg,#ffffff); stroke: var(--ts-node-stroke,#004d67); stroke-width: 1.5; }
|
|
45
|
+
.bpmn-task-name { fill: var(--ts-text-primary,#0d2b35); font-size: 11px; text-anchor: middle; }
|
|
46
|
+
.bpmn-event { fill: var(--ts-bg,#ffffff); stroke: var(--ts-node-stroke,#004d67); }
|
|
47
47
|
.bpmn-event-start { stroke-width: 1.5; }
|
|
48
48
|
.bpmn-event-end { stroke-width: 4; }
|
|
49
|
-
.bpmn-event-label { fill: var(--ts-text-secondary,#
|
|
50
|
-
.bpmn-gateway { fill: var(--ts-bg,#ffffff); stroke: var(--ts-node-stroke,#
|
|
51
|
-
.bpmn-gateway-marker { stroke: var(--ts-node-stroke,#
|
|
52
|
-
.bpmn-gateway-label { fill: var(--ts-text-secondary,#
|
|
53
|
-
.bpmn-seq-flow { fill: none; stroke: var(--ts-edge-stroke,#
|
|
54
|
-
.bpmn-default-mark { stroke: var(--ts-edge-stroke,#
|
|
55
|
-
.bpmn-assoc { fill: none; stroke: var(--ts-edge-stroke,#
|
|
56
|
-
.bpmn-data-obj { fill: var(--ts-bg,#ffffff); stroke: var(--ts-node-stroke,#
|
|
57
|
-
.bpmn-data-obj-label { fill: var(--ts-text-secondary,#
|
|
58
|
-
.bpmn-event-intermediate { fill: none; stroke: var(--ts-node-stroke,#
|
|
59
|
-
.bpmn-event-icon { fill: none; stroke: var(--ts-node-stroke,#
|
|
49
|
+
.bpmn-event-label { fill: var(--ts-text-secondary,#516970); font-size: 10px; text-anchor: middle; }
|
|
50
|
+
.bpmn-gateway { fill: var(--ts-bg,#ffffff); stroke: var(--ts-node-stroke,#004d67); stroke-width: 1.5; }
|
|
51
|
+
.bpmn-gateway-marker { stroke: var(--ts-node-stroke,#004d67); stroke-width: 2.5; stroke-linecap: round; fill: none; }
|
|
52
|
+
.bpmn-gateway-label { fill: var(--ts-text-secondary,#516970); font-size: 10px; text-anchor: middle; }
|
|
53
|
+
.bpmn-seq-flow { fill: none; stroke: var(--ts-edge-stroke,#004d67); stroke-width: 1.5; }
|
|
54
|
+
.bpmn-default-mark { stroke: var(--ts-edge-stroke,#004d67); stroke-width: 1.5; stroke-linecap: round; }
|
|
55
|
+
.bpmn-assoc { fill: none; stroke: var(--ts-edge-stroke,#004d67); stroke-width: 1; stroke-dasharray: 4 2; }
|
|
56
|
+
.bpmn-data-obj { fill: var(--ts-bg,#ffffff); stroke: var(--ts-node-stroke,#004d67); stroke-width: 1; }
|
|
57
|
+
.bpmn-data-obj-label { fill: var(--ts-text-secondary,#516970); font-size: 10px; text-anchor: middle; }
|
|
58
|
+
.bpmn-event-intermediate { fill: none; stroke: var(--ts-node-stroke,#004d67); stroke-width: 1; }
|
|
59
|
+
.bpmn-event-icon { fill: none; stroke: var(--ts-node-stroke,#004d67); stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
|
|
60
60
|
`;
|
|
61
61
|
// ---------------------------------------------------------------------------
|
|
62
62
|
// Internal helpers
|
package/package.json
CHANGED
|
@@ -55,4 +55,13 @@ describe('horizontalCubicEdgePath', () => {
|
|
|
55
55
|
horizontalCubicEdgePath(0, 0, 40, 0, 0.5, 0.5),
|
|
56
56
|
);
|
|
57
57
|
});
|
|
58
|
+
|
|
59
|
+
it('caps the handle for a tall edge with a narrow column gap, instead of overshooting the target', () => {
|
|
60
|
+
// A network-view edge spanning several rows (|dy|=500) with a typical
|
|
61
|
+
// adjacent-column gap (dx=80): uncapped, |dy|*0.8=400 would push the exit
|
|
62
|
+
// control point 320px past the target's x, producing an exaggerated
|
|
63
|
+
// S-bow. Capped at 160, the control points stay close to the endpoints.
|
|
64
|
+
const d = horizontalCubicEdgePath(0, 0, 80, 500, 1);
|
|
65
|
+
expect(d).toBe('M0,0 C160,0 -80,500 80,500');
|
|
66
|
+
});
|
|
58
67
|
});
|
|
@@ -82,7 +82,7 @@ export function renderCapabilityTreeSvg(
|
|
|
82
82
|
: '';
|
|
83
83
|
|
|
84
84
|
return `<g${nameTitle ? ` title="${nameTitle}"` : ''}>
|
|
85
|
-
<rect class="tree-level-${band}" x="${x}" y="${y}" width="${n.width}" height="${n.height}" rx="11" stroke="var(--ts-node-stroke,#
|
|
85
|
+
<rect class="tree-level-${band}" x="${x}" y="${y}" width="${n.width}" height="${n.height}" rx="11" stroke="var(--ts-node-stroke,#004d67)" stroke-width="3"/>
|
|
86
86
|
<rect class="tree-maturity-${mat}" x="${badgeX}" y="${badgeY}" width="${badgeW}" height="${badgeH}" rx="4"/>
|
|
87
87
|
<text class="text-pill" x="${badgeX + badgeW / 2}" y="${badgeY + badgeH / 2}" text-anchor="middle" fill="white">L${mat}</text>
|
|
88
88
|
<text class="text-primary" x="${textX}" y="${nameY}" dominant-baseline="central">${escXml(nameText)}</text>
|
|
@@ -102,7 +102,7 @@ export function renderCapabilityTreeSvg(
|
|
|
102
102
|
const bx = legendX;
|
|
103
103
|
legendX += bw;
|
|
104
104
|
if (count === 0) return '';
|
|
105
|
-
return `<rect class="tree-level-${i}" x="${bx}" y="${legendY}" width="${bw}" height="24" rx="4" stroke="var(--ts-node-stroke,#
|
|
105
|
+
return `<rect class="tree-level-${i}" x="${bx}" y="${legendY}" width="${bw}" height="24" rx="4" stroke="var(--ts-node-stroke,#004d67)" stroke-width="1"/>
|
|
106
106
|
<text class="text-id" x="${bx + bw / 2}" y="${legendY + 12}" text-anchor="middle" dominant-baseline="central">${escXml(BAND_LABELS[i])}</text>`;
|
|
107
107
|
}).filter(Boolean).join('\n');
|
|
108
108
|
|
package/src/edge-path.ts
CHANGED
|
@@ -17,6 +17,16 @@ export const EDGE_MIN_HANDLE = 64;
|
|
|
17
17
|
const DX_FACTOR = 0.5;
|
|
18
18
|
const DY_FACTOR = 0.8;
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Ceiling on each span factor's contribution. Uncapped, a tall edge with a
|
|
22
|
+
* narrow column gap (large dy, small dx — the common case in a many-row
|
|
23
|
+
* network view) grows a handle far past the endpoints' actual x-distance;
|
|
24
|
+
* the two control points then overshoot each other, producing an
|
|
25
|
+
* exaggerated S-bow instead of a gentle curve. Capping keeps typical spans
|
|
26
|
+
* (within the existing historical range) untouched while reining in outliers.
|
|
27
|
+
*/
|
|
28
|
+
const MAX_HANDLE = 160;
|
|
29
|
+
|
|
20
30
|
/** Multiplier applied to the base handle length. 1 = historical appearance. */
|
|
21
31
|
export const DEFAULT_EDGE_CURVATURE = 1;
|
|
22
32
|
|
|
@@ -43,7 +53,11 @@ export function horizontalCubicEdgePath(
|
|
|
43
53
|
): string {
|
|
44
54
|
const dx = tx - sx;
|
|
45
55
|
const dy = ty - sy;
|
|
46
|
-
const baseHandle = Math.max(
|
|
56
|
+
const baseHandle = Math.max(
|
|
57
|
+
EDGE_MIN_HANDLE,
|
|
58
|
+
Math.min(Math.abs(dx) * DX_FACTOR, MAX_HANDLE),
|
|
59
|
+
Math.min(Math.abs(dy) * DY_FACTOR, MAX_HANDLE),
|
|
60
|
+
);
|
|
47
61
|
const exitHandle = baseHandle * curvature;
|
|
48
62
|
const entryHandle = baseHandle * (entryCurvature ?? curvature);
|
|
49
63
|
return `M${sx},${sy} C${sx + exitHandle},${sy} ${tx - entryHandle},${ty} ${tx},${ty}`;
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Brand default-theme regression tests (hub issue #548).
|
|
3
|
+
*
|
|
4
|
+
* Locks in the three-role brand mapping from `brand/transitrix_brand.md`:
|
|
5
|
+
* - Petrol = structure (node/edge stroke) and hierarchy depth (fill tint).
|
|
6
|
+
* - A depth ramp never switches hue — only lightness/saturation move.
|
|
7
|
+
* - Text stays WCAG-AA readable against every fill it can be drawn on.
|
|
8
|
+
* `hc` (VS Code high-contrast) is intentionally excluded — it's an
|
|
9
|
+
* accessibility override, not one of the three brand-contract themes.
|
|
10
|
+
*/
|
|
11
|
+
import { describe, expect, it } from 'vitest';
|
|
12
|
+
import {
|
|
13
|
+
BRAND,
|
|
14
|
+
BRAND_EMPHASIS,
|
|
15
|
+
LAYER_COLORS,
|
|
16
|
+
LEVEL_COLORS,
|
|
17
|
+
MATURITY_COLORS,
|
|
18
|
+
TREE_MATURITY_COLORS,
|
|
19
|
+
STRUCTURAL,
|
|
20
|
+
TREE_LEVEL_COLORS,
|
|
21
|
+
} from '../tokens.js';
|
|
22
|
+
|
|
23
|
+
function hexToRgb(hex: string): [number, number, number] {
|
|
24
|
+
const n = hex.replace('#', '');
|
|
25
|
+
return [0, 2, 4].map((i) => parseInt(n.slice(i, i + 2), 16)) as [number, number, number];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function relativeLuminance([r, g, b]: [number, number, number]): number {
|
|
29
|
+
const f = (v: number): number => {
|
|
30
|
+
const c = v / 255;
|
|
31
|
+
return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
32
|
+
};
|
|
33
|
+
const [R, G, B] = [f(r), f(g), f(b)];
|
|
34
|
+
return 0.2126 * R + 0.7152 * G + 0.0722 * B;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** WCAG 2.x contrast ratio between two hex colors (1..21). */
|
|
38
|
+
function contrast(a: string, b: string): number {
|
|
39
|
+
const la = relativeLuminance(hexToRgb(a));
|
|
40
|
+
const lb = relativeLuminance(hexToRgb(b));
|
|
41
|
+
const [hi, lo] = la > lb ? [la, lb] : [lb, la];
|
|
42
|
+
return (hi + 0.05) / (lo + 0.05);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Hue in degrees (0-360) from a hex color; undefined (grey) hues return -1. */
|
|
46
|
+
function hue(hex: string): number {
|
|
47
|
+
const [r, g, b] = hexToRgb(hex).map((v) => v / 255);
|
|
48
|
+
const max = Math.max(r, g, b), min = Math.min(r, g, b);
|
|
49
|
+
const delta = max - min;
|
|
50
|
+
if (delta < 0.001) return -1; // achromatic
|
|
51
|
+
let h: number;
|
|
52
|
+
if (max === r) h = ((g - b) / delta) % 6;
|
|
53
|
+
else if (max === g) h = (b - r) / delta + 2;
|
|
54
|
+
else h = (r - g) / delta + 4;
|
|
55
|
+
h *= 60;
|
|
56
|
+
return h < 0 ? h + 360 : h;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const PETROL_HUE = hue(BRAND.petrol);
|
|
60
|
+
const HUE_TOLERANCE = 6; // degrees — rounding slop from hand-tuned hex values
|
|
61
|
+
|
|
62
|
+
function expectSameHueFamily(hexes: string[], label: string): void {
|
|
63
|
+
for (const hex of hexes) {
|
|
64
|
+
const h = hue(hex);
|
|
65
|
+
const diff = Math.min(Math.abs(h - PETROL_HUE), 360 - Math.abs(h - PETROL_HUE));
|
|
66
|
+
expect(diff, `${label} ${hex} should stay in the petrol hue family (got hue ${h.toFixed(1)}, petrol is ${PETROL_HUE.toFixed(1)})`).toBeLessThanOrEqual(HUE_TOLERANCE);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Minimum AA text-contrast ratios this design targets. Primary/secondary text
|
|
71
|
+
// is small (10-12px) so we hold the full 4.5:1 normal-text bar for the name
|
|
72
|
+
// label, and the calibrated ~4.0 bar the pre-brand palette already shipped
|
|
73
|
+
// for the muted secondary/id label (see brand-ramp calibration in PR #548).
|
|
74
|
+
const MIN_PRIMARY_CONTRAST = 4.5;
|
|
75
|
+
const MIN_SECONDARY_CONTRAST = 4.0;
|
|
76
|
+
|
|
77
|
+
describe('brand default theme (#548) — structure is petrol', () => {
|
|
78
|
+
it('nodeStroke and edgeStroke are brand petrol in the light theme', () => {
|
|
79
|
+
expect(STRUCTURAL.nodeStroke.light).toBe(BRAND.petrol);
|
|
80
|
+
expect(STRUCTURAL.edgeStroke.light).toBe(BRAND.petrol);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('nodeStroke/edgeStroke stay in the petrol hue family in the dark theme', () => {
|
|
84
|
+
expectSameHueFamily([STRUCTURAL.nodeStroke.dark, STRUCTURAL.edgeStroke.dark], 'dark stroke');
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
describe('brand default theme (#548) — tint depth never switches hue', () => {
|
|
89
|
+
it('LEVEL_COLORS light ramp stays in the petrol hue family', () => {
|
|
90
|
+
expectSameHueFamily([...LEVEL_COLORS.light], 'LEVEL light');
|
|
91
|
+
});
|
|
92
|
+
it('LEVEL_COLORS dark ramp stays in the petrol hue family', () => {
|
|
93
|
+
expectSameHueFamily([...LEVEL_COLORS.dark], 'LEVEL dark');
|
|
94
|
+
});
|
|
95
|
+
it('LAYER_COLORS (DGCA/DGA columns) stay in the petrol hue family, both themes', () => {
|
|
96
|
+
const keys = ['driver', 'factor', 'goal', 'change', 'activity'] as const;
|
|
97
|
+
expectSameHueFamily(keys.map((k) => LAYER_COLORS[k].light), 'LAYER light');
|
|
98
|
+
expectSameHueFamily(keys.map((k) => LAYER_COLORS[k].dark), 'LAYER dark');
|
|
99
|
+
});
|
|
100
|
+
it('TREE_LEVEL_COLORS (capability map bands) stay in the petrol hue family, both themes', () => {
|
|
101
|
+
const keys = ['band0', 'band1', 'band2'] as const;
|
|
102
|
+
expectSameHueFamily(keys.map((k) => TREE_LEVEL_COLORS[k].light), 'BAND light');
|
|
103
|
+
expectSameHueFamily(keys.map((k) => TREE_LEVEL_COLORS[k].dark), 'BAND dark');
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe('brand default theme (#548) — WCAG-AA text-on-fill contrast', () => {
|
|
108
|
+
function checkFills(label: string, fills: string[], textPrimary: string, textSecondary: string): void {
|
|
109
|
+
for (const fill of fills) {
|
|
110
|
+
expect(contrast(fill, textPrimary), `${label} ${fill} vs text-primary`).toBeGreaterThanOrEqual(MIN_PRIMARY_CONTRAST);
|
|
111
|
+
expect(contrast(fill, textSecondary), `${label} ${fill} vs text-secondary`).toBeGreaterThanOrEqual(MIN_SECONDARY_CONTRAST);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
it('LEVEL_COLORS — every slot, both themes', () => {
|
|
116
|
+
checkFills('LEVEL light', [...LEVEL_COLORS.light], STRUCTURAL.textPrimary.light, STRUCTURAL.textSecondary.light);
|
|
117
|
+
checkFills('LEVEL dark', [...LEVEL_COLORS.dark], STRUCTURAL.textPrimary.dark, STRUCTURAL.textSecondary.dark);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('LAYER_COLORS — every column, both themes', () => {
|
|
121
|
+
const keys = ['driver', 'factor', 'goal', 'change', 'activity'] as const;
|
|
122
|
+
checkFills('LAYER light', keys.map((k) => LAYER_COLORS[k].light), STRUCTURAL.textPrimary.light, STRUCTURAL.textSecondary.light);
|
|
123
|
+
checkFills('LAYER dark', keys.map((k) => LAYER_COLORS[k].dark), STRUCTURAL.textPrimary.dark, STRUCTURAL.textSecondary.dark);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('TREE_LEVEL_COLORS — every band, both themes', () => {
|
|
127
|
+
const keys = ['band0', 'band1', 'band2'] as const;
|
|
128
|
+
checkFills('BAND light', keys.map((k) => TREE_LEVEL_COLORS[k].light), STRUCTURAL.textPrimary.light, STRUCTURAL.textSecondary.light);
|
|
129
|
+
checkFills('BAND dark', keys.map((k) => TREE_LEVEL_COLORS[k].dark), STRUCTURAL.textPrimary.dark, STRUCTURAL.textSecondary.dark);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('textPrimary/textSecondary stay readable against the shell background', () => {
|
|
133
|
+
expect(contrast(STRUCTURAL.textPrimary.light, '#ffffff')).toBeGreaterThanOrEqual(MIN_PRIMARY_CONTRAST);
|
|
134
|
+
expect(contrast(STRUCTURAL.textPrimary.dark, '#0a1628')).toBeGreaterThanOrEqual(MIN_PRIMARY_CONTRAST);
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
describe('brand default theme (#548) — amber/orange emphasis stays distinct from functional colors', () => {
|
|
139
|
+
it('does not reuse brand amber/orange for functional status', () => {
|
|
140
|
+
// FUNCTIONAL warning/error must remain visually distinct from the brand
|
|
141
|
+
// accents so a red error is never confused with brand orange, per
|
|
142
|
+
// brand/transitrix_brand.md "Don't reuse brand orange as semantic error".
|
|
143
|
+
expect(BRAND_EMPHASIS.orange.light).toBe(BRAND.orange);
|
|
144
|
+
expect(BRAND_EMPHASIS.amber.light).toBe(BRAND.amber);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('critical-path emphasis tint pairs with the orange stroke, both themes', () => {
|
|
148
|
+
expect(contrast(BRAND_EMPHASIS.orange.light, BRAND_EMPHASIS.orangeTint.light)).toBeGreaterThan(1.5);
|
|
149
|
+
expect(contrast(BRAND_EMPHASIS.orange.dark, BRAND_EMPHASIS.orangeTint.dark)).toBeGreaterThan(1.5);
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
describe('maturity Likert ramp — one harmonious scale, white-label contrast', () => {
|
|
154
|
+
// .maturity-pill / tree-maturity badges hardcode white label text
|
|
155
|
+
// (capability-map-preview.ts, render-capability-tree.ts) regardless of
|
|
156
|
+
// theme, so every step needs AA (light/dark) or AAA (hc, since VS Code
|
|
157
|
+
// high-contrast mode exists specifically to demand stronger contrast)
|
|
158
|
+
// against white — not against the page's own text-primary/secondary.
|
|
159
|
+
const MIN_WHITE_CONTRAST_AA = 4.5;
|
|
160
|
+
const MIN_WHITE_CONTRAST_AAA = 7;
|
|
161
|
+
|
|
162
|
+
it('MATURITY_COLORS — every step reads white text at AA, light + dark', () => {
|
|
163
|
+
for (const hex of MATURITY_COLORS.light) {
|
|
164
|
+
expect(contrast(hex, '#ffffff'), `MATURITY light ${hex} vs white`).toBeGreaterThanOrEqual(MIN_WHITE_CONTRAST_AA);
|
|
165
|
+
}
|
|
166
|
+
for (const hex of MATURITY_COLORS.dark) {
|
|
167
|
+
expect(contrast(hex, '#ffffff'), `MATURITY dark ${hex} vs white`).toBeGreaterThanOrEqual(MIN_WHITE_CONTRAST_AA);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('MATURITY_COLORS hc — every step reads white text at AAA', () => {
|
|
172
|
+
for (const hex of MATURITY_COLORS.hc) {
|
|
173
|
+
expect(contrast(hex, '#ffffff'), `MATURITY hc ${hex} vs white`).toBeGreaterThanOrEqual(MIN_WHITE_CONTRAST_AAA);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('TREE_MATURITY_COLORS is the same harmonious ramp as MATURITY_COLORS', () => {
|
|
178
|
+
// Previously a divergent DSM-matching pink/yellow/blue set (plus a grey
|
|
179
|
+
// L1, an odd "no signal" hue for the worst rating) — "maturity" now
|
|
180
|
+
// means the same colors everywhere it's shown.
|
|
181
|
+
expect(TREE_MATURITY_COLORS.light).toEqual(MATURITY_COLORS.light);
|
|
182
|
+
expect(TREE_MATURITY_COLORS.dark).toEqual(MATURITY_COLORS.dark);
|
|
183
|
+
expect(TREE_MATURITY_COLORS.hc).toEqual(MATURITY_COLORS.hc);
|
|
184
|
+
});
|
|
185
|
+
});
|
package/src/theme/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export type { AdaptiveTokens } from './tokens.js';
|
|
2
|
-
export { BRAND, FUNCTIONAL, LAYER_COLORS, LEVEL_COLORS, MATURITY_COLORS, TREE_LEVEL_COLORS, TREE_MATURITY_COLORS, STRUCTURAL, TYPOGRAPHY, CSS_VAR, getBaseResetCss } from './tokens.js';
|
|
2
|
+
export { BRAND, BRAND_EMPHASIS, FUNCTIONAL, LAYER_COLORS, LEVEL_COLORS, MATURITY_COLORS, TREE_LEVEL_COLORS, TREE_MATURITY_COLORS, STRUCTURAL, TYPOGRAPHY, CSS_VAR, getBaseResetCss } from './tokens.js';
|
|
3
3
|
export type { ThemeId } from './themes.js';
|
|
4
4
|
export { generateWebviewCss, generateSvgEmbedCss } from './themes.js';
|
package/src/theme/themes.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
TREE_LEVEL_COLORS,
|
|
6
6
|
TREE_MATURITY_COLORS,
|
|
7
7
|
STRUCTURAL,
|
|
8
|
+
BRAND_EMPHASIS,
|
|
8
9
|
TYPOGRAPHY,
|
|
9
10
|
CSS_VAR,
|
|
10
11
|
getBaseResetCss,
|
|
@@ -106,6 +107,9 @@ function diagramVars(variant: 'light' | 'dark' | 'hc'): string {
|
|
|
106
107
|
`${CSS_VAR.maturity3}:${mat[2]}`,
|
|
107
108
|
`${CSS_VAR.maturity4}:${mat[3]}`,
|
|
108
109
|
`${CSS_VAR.maturity5}:${mat[4]}`,
|
|
110
|
+
`${CSS_VAR.brandAmber}:${BRAND_EMPHASIS.amber[variant]}`,
|
|
111
|
+
`${CSS_VAR.brandOrange}:${BRAND_EMPHASIS.orange[variant]}`,
|
|
112
|
+
`${CSS_VAR.brandOrangeTint}:${BRAND_EMPHASIS.orangeTint[variant]}`,
|
|
109
113
|
].join(';') + ';' + levelVars + treeVars;
|
|
110
114
|
}
|
|
111
115
|
|
package/src/theme/tokens.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** Transitrix brand palette. */
|
|
1
|
+
/** Transitrix brand palette (`brand/transitrix_brand.md` — canonical). */
|
|
2
2
|
export const BRAND = {
|
|
3
3
|
petrol: '#004d67',
|
|
4
4
|
amber: '#ffaf00',
|
|
@@ -13,61 +13,109 @@ export const FUNCTIONAL = {
|
|
|
13
13
|
info: '#1d6fa8',
|
|
14
14
|
} as const;
|
|
15
15
|
|
|
16
|
-
/**
|
|
16
|
+
/**
|
|
17
|
+
* Amber/orange "author emphasis" accents, theme-adjusted for contrast against
|
|
18
|
+
* each theme's background (dark theme gets the brand hues lightened per
|
|
19
|
+
* `brand/transitrix_brand.md` §Themes: "brand colours slightly desaturated
|
|
20
|
+
* for contrast on dark surfaces"). `orangeTint` is the light emphasis-fill
|
|
21
|
+
* paired with an `orange` stroke (e.g. Activities critical-path nodes).
|
|
22
|
+
* `hc` intentionally falls back to the plain BRAND hex — VS Code
|
|
23
|
+
* high-contrast mode prioritises accessibility overrides over brand fidelity.
|
|
24
|
+
*/
|
|
25
|
+
export const BRAND_EMPHASIS = {
|
|
26
|
+
amber: { light: BRAND.amber, dark: '#ffc83d', hc: BRAND.amber },
|
|
27
|
+
orange: { light: BRAND.orange, dark: '#ff7542', hc: BRAND.orange },
|
|
28
|
+
orangeTint: { light: '#ffeee5', dark: '#432114', hc: '#ffeee5' },
|
|
29
|
+
} as const;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Fill colors for FGCA/DGCA notation columns — a petrol tint per stage
|
|
33
|
+
* (Driver → Goal → Change → Activity), deepening left to right like the
|
|
34
|
+
* Goals-tree level ladder below. Petrol tint = structural position, never a
|
|
35
|
+
* hue switch, per the brand's "structure via petrol" role mapping.
|
|
36
|
+
*/
|
|
17
37
|
export const LAYER_COLORS = {
|
|
18
|
-
driver: { light: '#
|
|
19
|
-
factor: { light: '#
|
|
20
|
-
goal: { light: '#
|
|
21
|
-
change: { light: '#
|
|
22
|
-
activity: { light: '#
|
|
38
|
+
driver: { light: '#eaf3f6', dark: '#17262c', hc: '#f59e0b' },
|
|
39
|
+
factor: { light: '#eaf3f6', dark: '#17262c', hc: '#f59e0b' },
|
|
40
|
+
goal: { light: '#d9ecf2', dark: '#192f37', hc: '#818cf8' },
|
|
41
|
+
change: { light: '#c7e5ef', dark: '#1a3842', hc: '#60a5fa' },
|
|
42
|
+
activity: { light: '#b4e0ee', dark: '#1a414e', hc: '#4ade80' },
|
|
23
43
|
} as const;
|
|
24
44
|
|
|
25
|
-
/**
|
|
45
|
+
/**
|
|
46
|
+
* Level fill colors for Goals tree nodes — 8 slots, cycled by level index.
|
|
47
|
+
* A single-hue petrol tint ramp (level-0 lightest/root → level-7 deepest);
|
|
48
|
+
* hue never switches with depth. Saturation/lightness tuned so text-primary
|
|
49
|
+
* and text-secondary keep at least their pre-brand contrast ratio against
|
|
50
|
+
* every slot — see `theme/__tests__/tokens.test.ts`.
|
|
51
|
+
*/
|
|
26
52
|
export const LEVEL_COLORS = {
|
|
27
|
-
light: ['#
|
|
28
|
-
dark: ['#
|
|
53
|
+
light: ['#eef4f7', '#e6f1f4', '#dfedf1', '#d7e9ef', '#cee6ed', '#c6e2ec', '#bfe0eb', '#b7deeb'],
|
|
54
|
+
dark: ['#152328', '#16292f', '#172e36', '#18333d', '#183944', '#183f4c', '#184554', '#174b5c'],
|
|
29
55
|
hc: ['#60a5fa', '#818cf8', '#4ade80', '#f59e0b', '#f472b6', '#38bdf8', '#a78bfa', '#facc15'],
|
|
30
56
|
} as const;
|
|
31
57
|
|
|
32
|
-
/**
|
|
58
|
+
/**
|
|
59
|
+
* Structural diagram colors (nodes, edges, text). nodeStroke/edgeStroke are
|
|
60
|
+
* brand petrol — "structure via petrol" per the brand's role mapping.
|
|
61
|
+
* textPrimary is a dark petrol ink (light theme) / near-white petrol-tinted
|
|
62
|
+
* (dark theme); textSecondary stays a muted grey with a faint petrol lean.
|
|
63
|
+
* `hc` is untouched — VS Code high-contrast accessibility overrides win over
|
|
64
|
+
* brand fidelity there.
|
|
65
|
+
*/
|
|
33
66
|
export const STRUCTURAL = {
|
|
34
|
-
nodeStroke: { light:
|
|
35
|
-
edgeStroke: { light:
|
|
36
|
-
textPrimary: { light: '#
|
|
37
|
-
textSecondary: { light: '#
|
|
38
|
-
headerText: { light: '#
|
|
67
|
+
nodeStroke: { light: BRAND.petrol, dark: '#53a9c6', hc: '#e2e8f0' },
|
|
68
|
+
edgeStroke: { light: BRAND.petrol, dark: '#53a9c6', hc: '#e2e8f0' },
|
|
69
|
+
textPrimary: { light: '#0d2b35', dark: '#edf5f8', hc: '#ffffff' },
|
|
70
|
+
textSecondary: { light: '#516970', dark: '#adc8d1', hc: '#cbd5e1' },
|
|
71
|
+
headerText: { light: '#0d2b35', dark: '#edf5f8', hc: '#ffffff' },
|
|
39
72
|
} as const;
|
|
40
73
|
|
|
41
74
|
/**
|
|
42
|
-
* Maturity scale fill colors — Likert 1..5, danger→success.
|
|
75
|
+
* Maturity scale fill colors — Likert 1..5, danger→success. A single
|
|
76
|
+
* harmonious ramp: consistent saturation curve, hue stepping smoothly
|
|
77
|
+
* red→orange→gold→yellow-green→green (never jumping registers the way the
|
|
78
|
+
* previous per-theme picks did), tuned so the badge's hardcoded white label
|
|
79
|
+
* text (`.maturity-pill`, capability-map-preview.ts) stays ≥4.5:1 AA at
|
|
80
|
+
* every step. Same ramp for light/dark — these are small solid badges, not
|
|
81
|
+
* background tints, so theme brightness doesn't change the contrast need.
|
|
82
|
+
* `hc` pushes the same hues darker/more saturated for AAA (≥7:1), since VS
|
|
83
|
+
* Code high-contrast mode exists specifically to demand stronger contrast.
|
|
43
84
|
* Used by Capability Map and any future maturity-based notation.
|
|
44
85
|
*/
|
|
45
86
|
export const MATURITY_COLORS = {
|
|
46
|
-
light: ['#
|
|
47
|
-
dark: ['#
|
|
48
|
-
hc: ['#
|
|
87
|
+
light: ['#bf2518', '#b05911', '#8d7011', '#407d21', '#237b48'],
|
|
88
|
+
dark: ['#bf2518', '#b05911', '#8d7011', '#407d21', '#237b48'],
|
|
89
|
+
hc: ['#a0190d', '#864109', '#5a4607', '#316317', '#155630'],
|
|
49
90
|
} as const;
|
|
50
91
|
|
|
51
92
|
/**
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
93
|
+
* Capability tree node fill colors, grouped by depth band — a 3-step petrol
|
|
94
|
+
* tint (shallow → deep), same "petrol tint = structural depth" rule as
|
|
95
|
+
* LEVEL_COLORS above. Previously a DSM-matching pink/yellow/blue scheme;
|
|
96
|
+
* brand/transitrix_brand.md ("Components") anticipates DSM adopting the same
|
|
97
|
+
* shared color tokens, so Studio moving first is convergence, not drift.
|
|
98
|
+
* band0: depth 0–2 (shallowest)
|
|
99
|
+
* band1: depth 3–4
|
|
100
|
+
* band2: depth 5+ (deepest)
|
|
56
101
|
*/
|
|
57
102
|
export const TREE_LEVEL_COLORS = {
|
|
58
|
-
band0: { light: '#
|
|
59
|
-
band1: { light: '#
|
|
60
|
-
band2: { light: '#
|
|
103
|
+
band0: { light: '#e6f1f4', dark: '#152b32', hc: '#ff9999' },
|
|
104
|
+
band1: { light: '#d0e9f1', dark: '#153b47', hc: '#ffff88' },
|
|
105
|
+
band2: { light: '#b5e3f2', dark: '#114c5f', hc: '#66ccff' },
|
|
61
106
|
} as const;
|
|
62
107
|
|
|
63
108
|
/**
|
|
64
|
-
*
|
|
65
|
-
*
|
|
109
|
+
* Maturity badge fills for the capability tree (L1–L5). Same ramp as
|
|
110
|
+
* MATURITY_COLORS — previously a separate DSM-matching pink/yellow/blue set
|
|
111
|
+
* that didn't even share L1's meaning (grey = lowest maturity, an odd
|
|
112
|
+
* "no signal" hue for the worst rating) and had white-label contrast as low
|
|
113
|
+
* as 1.07:1 in `hc`. One harmonious Likert ramp for "maturity" everywhere.
|
|
66
114
|
*/
|
|
67
115
|
export const TREE_MATURITY_COLORS = {
|
|
68
|
-
light:
|
|
69
|
-
dark:
|
|
70
|
-
hc:
|
|
116
|
+
light: MATURITY_COLORS.light,
|
|
117
|
+
dark: MATURITY_COLORS.dark,
|
|
118
|
+
hc: MATURITY_COLORS.hc,
|
|
71
119
|
} as const;
|
|
72
120
|
|
|
73
121
|
/**
|
|
@@ -158,6 +206,9 @@ export const CSS_VAR = {
|
|
|
158
206
|
treeMaturity3: '--ts-tree-maturity-3',
|
|
159
207
|
treeMaturity4: '--ts-tree-maturity-4',
|
|
160
208
|
treeMaturity5: '--ts-tree-maturity-5',
|
|
209
|
+
brandAmber: '--ts-brand-amber',
|
|
210
|
+
brandOrange: '--ts-brand-orange',
|
|
211
|
+
brandOrangeTint: '--ts-brand-orange-tint',
|
|
161
212
|
} as const;
|
|
162
213
|
|
|
163
214
|
export function getBaseResetCss(): string {
|
|
@@ -38,10 +38,10 @@ const N_PAD = 24;
|
|
|
38
38
|
* self-contained for the JCEF host.
|
|
39
39
|
*/
|
|
40
40
|
export const ACTIVITIES_NETWORK_CSS = `
|
|
41
|
-
.act-node { fill: var(--ts-layer-activity, #d4edda); stroke: var(--ts-node-stroke, #
|
|
42
|
-
.critical-node { fill: #
|
|
41
|
+
.act-node { fill: var(--ts-layer-activity, #d4edda); stroke: var(--ts-node-stroke, #004d67); stroke-width: 1; }
|
|
42
|
+
.critical-node { fill: var(--ts-brand-orange-tint, #ffeee5); stroke: var(--ts-brand-orange, #ff4d00); stroke-width: 1; }
|
|
43
43
|
.milestone-node { fill: #ecfeff; stroke: var(--ts-text-muted, #64748b); stroke-dasharray: 4 2; }
|
|
44
|
-
.critical-edge { stroke: var(--ts-brand-orange, #ff4d00); stroke-width:
|
|
44
|
+
.critical-edge { stroke: var(--ts-brand-orange, #ff4d00); stroke-width: 1.5; }
|
|
45
45
|
.arrow-fill-critical { fill: var(--ts-brand-orange, #ff4d00); }
|
|
46
46
|
`;
|
|
47
47
|
|
|
@@ -64,10 +64,7 @@ export function renderFgcaBody(
|
|
|
64
64
|
|
|
65
65
|
const headerSvg = columns
|
|
66
66
|
.map(({ col, x }) =>
|
|
67
|
-
[
|
|
68
|
-
`<rect class="diagram-node layer-${col}" x="${x}" y="${PAD}" width="${nodeWidth}" height="${HEADER_H}" rx="6"/>`,
|
|
69
|
-
`<text class="text-header" x="${x + nodeWidth / 2}" y="${PAD + HEADER_H / 2}" text-anchor="middle" dominant-baseline="central">${escXml(truncateLine(COL_LABELS[col], headerTruncate))}</text>`,
|
|
70
|
-
].join('\n'),
|
|
67
|
+
`<text class="text-header" x="${x + nodeWidth / 2}" y="${PAD + HEADER_H / 2}" text-anchor="middle" dominant-baseline="central">${escXml(truncateLine(COL_LABELS[col], headerTruncate))}</text>`,
|
|
71
68
|
)
|
|
72
69
|
.join('\n');
|
|
73
70
|
|