From 17ec5b8f2277dc90808d487f4e5feb67d67a9604 Mon Sep 17 00:00:00 2001 From: Bernhard Jackiewicz Date: Sun, 4 Oct 2026 14:39:17 +0200 Subject: [PATCH] fix(ui): paint the Galaxy overlays opaque so no scene label shows through Since K29 the scene labels lie below the Galaxy overlays, but the overlays let a little of the scene through: Selection details at .95, the path panel at .94, the hierarchy key and the loading pill at .9, the menus at .98, fit view at 82 %, the agents instrument at 88 and 92 %. The path target's name and its ring still showed through Selection details. Every overlay over the scene now keeps its colour without transparency. Signed-off-by: Bernhard Jackiewicz --- graph-ui/src/galaxy/GalaxyPanel.tsx | 6 +- graph-ui/src/galaxy/graph-exploration.css | 19 ++--- graph-ui/src/galaxy/overlay-opacity.test.ts | 77 +++++++++++++++++++++ graph-ui/src/styles/terminal.css | 17 ++--- 4 files changed, 99 insertions(+), 20 deletions(-) create mode 100644 graph-ui/src/galaxy/overlay-opacity.test.ts diff --git a/graph-ui/src/galaxy/GalaxyPanel.tsx b/graph-ui/src/galaxy/GalaxyPanel.tsx index b30100a02..63985d3c2 100644 --- a/graph-ui/src/galaxy/GalaxyPanel.tsx +++ b/graph-ui/src/galaxy/GalaxyPanel.tsx @@ -2943,9 +2943,9 @@ export default function GalaxyPanel(props: GalaxyPanelProps): JSX.Element { * Das Instrument liegt IM Kasten der Szene und nicht darunter: * es erklaert, was auf dem Graphen zu sehen ist, und ein Kasten * daneben waere eine zweite Flaeche, die man zwischen Bild und - * Text hin und her lesen muesste. Es ist halbtransparent und - * faengt seine eigenen Klicks ab; der Rest der Flaeche bleibt - * die Szene. + * Text hin und her lesen muesste. Es ist deckend (Review zu + * K29) und faengt seine eigenen Klicks ab; der Rest der Flaeche + * bleibt die Szene. */} {liveOn && agentsView !== undefined && props.agents !== undefined && ( input { box-sizing:border-box; width:100%; min-height:2rem; padding:.4rem .65rem; border:1px solid #29473a; border-radius:6px; background:rgba(7,17,12,.65); color:var(--atlas-fg,#cbdbd0); font:inherit; } .atlas-galaxy-search-container > input:focus { outline:1px solid #6d9a84; border-color:#6d9a84; } -.atlas-galaxy-search-results { position:absolute; top:calc(100% + .4rem); left:0; z-index:35; width:min(34rem, calc(100vw - 2rem)); padding:.5rem; border:1px solid #355246; border-radius:8px; background:rgba(13,25,19,.98); box-shadow:0 8px 24px #0005; } +.atlas-galaxy-search-results { position:absolute; top:calc(100% + .4rem); left:0; z-index:35; width:min(34rem, calc(100vw - 2rem)); padding:.5rem; border:1px solid #355246; border-radius:8px; background:rgb(13,25,19); box-shadow:0 8px 24px #0005; } .atlas-graph-exploration label { display:flex; align-items:center; gap:.35rem; color:var(--muted, #a0b6aa); } .atlas-graph-exploration select { padding:.2rem .35rem; background:#102019; color:#d4ded8; border:1px solid #29473a; border-radius:5px; } .atlas-graph-coverage-filter { cursor:pointer; white-space:nowrap; } @@ -60,11 +60,11 @@ /* Der Deckel im Scope: dieselbe Bauform wie die Kantenarten (Review-Befund G7). */ .atlas-graph-limits { position:relative; } .atlas-graph-limits > summary { padding:.25rem .5rem; border:1px solid #29473a; border-radius:6px; cursor:pointer; color:#b5c9bc; white-space:nowrap; } -.atlas-graph-limits-menu { position:absolute; top:calc(100% + .35rem); right:0; z-index:30; display:flex; flex-direction:column; align-items:flex-end; gap:.45rem; padding:.6rem; border:1px solid #355246; border-radius:8px; background:rgba(13,25,19,.98); box-shadow:0 8px 24px #0005; } +.atlas-graph-limits-menu { position:absolute; top:calc(100% + .35rem); right:0; z-index:30; display:flex; flex-direction:column; align-items:flex-end; gap:.45rem; padding:.6rem; border:1px solid #355246; border-radius:8px; background:rgb(13,25,19); box-shadow:0 8px 24px #0005; } .atlas-graph-scope-warning { width:100%; color:#c6aa82; } .atlas-trace-edge-filter { position:relative; } .atlas-trace-edge-filter > summary { padding:.25rem .5rem; border:1px solid #29473a; border-radius:6px; cursor:pointer; color:#b5c9bc; } -.atlas-trace-edge-menu { position:absolute; top:calc(100% + .35rem); left:0; z-index:30; width:min(20rem, calc(100vw - 3rem)); max-height:22rem; overflow:auto; padding:.65rem; border:1px solid #355246; border-radius:8px; background:rgba(13,25,19,.98); box-shadow:0 8px 24px #0005; } +.atlas-trace-edge-menu { position:absolute; top:calc(100% + .35rem); left:0; z-index:30; width:min(20rem, calc(100vw - 3rem)); max-height:22rem; overflow:auto; padding:.65rem; border:1px solid #355246; border-radius:8px; background:rgb(13,25,19); box-shadow:0 8px 24px #0005; } .atlas-trace-edge-actions { display:flex; gap:.4rem; margin-bottom:.4rem; } .atlas-trace-edge-menu small { display:block; color:#95a89e; margin-bottom:.4rem; } .atlas-trace-edge-row { display:flex; justify-content:space-between; align-items:center; gap:.5rem; padding:.2rem 0; } @@ -78,7 +78,8 @@ .atlas-galaxy-node-picker button span { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-size:.75rem; } .atlas-galaxy-node-picker .atlas-graph-result-kind { min-width:3rem; color:#91b6a3; } -.galaxy-selection-evidence.atlas-galaxy-selection-details { top:auto; bottom:12px; right:12px; width:auto; height:auto; max-width:calc(100% - 24px); max-height:55%; padding:0; border-radius:7px; background:rgba(13, 25, 19, .95); } +/* Review zu K29: jede Flaeche ueber der Szene ist deckend; durch .95 schienen der Name des Pfadziels und sein Ring hindurch. */ +.galaxy-selection-evidence.atlas-galaxy-selection-details { top:auto; bottom:12px; right:12px; width:auto; height:auto; max-width:calc(100% - 24px); max-height:55%; padding:0; border-radius:7px; background:rgb(13, 25, 19); } .galaxy-selection-evidence.atlas-galaxy-selection-details > summary { padding:.5rem .75rem; color:#b5c9bc; font-size:.8rem; cursor:pointer; } .galaxy-selection-evidence.atlas-galaxy-selection-details[open] { width:min(420px, calc(100% - 24px)); max-height:min(24rem, 55%); } .atlas-galaxy-selection-details-body { padding:0 .75rem .75rem; } @@ -86,7 +87,7 @@ .galaxy-selection-evidence.atlas-galaxy-selection-details[open] { max-height:38%; } } -.atlas-graph-render-progress { position:absolute; z-index:30; top:12px; left:50%; transform:translateX(-50%); display:flex; align-items:center; gap:.5rem; max-width:calc(100% - 24px); box-sizing:border-box; padding:.45rem .75rem; border:1px solid #6c88764d; border-radius:999px; background:rgba(14,25,20,.9); color:#b9cdc1; box-shadow:0 3px 12px #0003; pointer-events:none; font:12px/1.4 var(--atlas-ui-font,system-ui,sans-serif); } +.atlas-graph-render-progress { position:absolute; z-index:30; top:12px; left:50%; transform:translateX(-50%); display:flex; align-items:center; gap:.5rem; max-width:calc(100% - 24px); box-sizing:border-box; padding:.45rem .75rem; border:1px solid #6c88764d; border-radius:999px; background:rgb(14,25,20); color:#b9cdc1; box-shadow:0 3px 12px #0003; pointer-events:none; font:12px/1.4 var(--atlas-ui-font,system-ui,sans-serif); } .atlas-graph-render-progress > span:last-child { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; } .atlas-graph-render-spinner { width:12px; height:12px; box-sizing:border-box; flex:none; border:1.5px solid #99b7a52e; border-top-color:#a8c4b3; border-radius:50%; animation:atlas-graph-render-spin .8s linear infinite; } @keyframes atlas-graph-render-spin { to { transform:rotate(360deg); } } @@ -95,14 +96,14 @@ /* Der Pfad: Auswahl in der Werkzeugleiste, Schrittliste in der Szene, Beschriftung und Ring an den Kanten. */ .atlas-graph-path-picker { position:relative; } .atlas-graph-path-picker > summary { padding:.25rem .5rem; border:1px solid #29473a; border-radius:6px; cursor:pointer; color:#b5c9bc; white-space:nowrap; } -.atlas-graph-path-menu { position:absolute; top:calc(100% + .35rem); left:0; z-index:30; width:min(22rem, calc(100vw - 3rem)); padding:.55rem; border:1px solid #355246; border-radius:8px; background:rgba(13,25,19,.98); box-shadow:0 8px 24px #0005; } +.atlas-graph-path-menu { position:absolute; top:calc(100% + .35rem); left:0; z-index:30; width:min(22rem, calc(100vw - 3rem)); padding:.55rem; border:1px solid #355246; border-radius:8px; background:rgb(13,25,19); box-shadow:0 8px 24px #0005; } .atlas-graph-path-menu input { box-sizing:border-box; width:100%; padding:.35rem .5rem; border:1px solid #29473a; border-radius:5px; background:rgba(7,17,12,.65); color:var(--atlas-fg,#cbdbd0); font:inherit; } .atlas-graph-path-menu ul { max-height:18rem; overflow:auto; list-style:none; padding:0; margin:.45rem 0 0; } .atlas-graph-path-menu button { width:100%; display:flex; gap:.5rem; align-items:baseline; text-align:left; border:0; background:transparent; } .atlas-graph-path-menu button span { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-size:.72rem; color:#95a89e; } .atlas-graph-path-menu small { display:block; color:#95a89e; margin-top:.4rem; } .atlas-graph-exploration button[aria-pressed=true] { border-color:#80ac96; background:rgba(68, 112, 86, .3); } -.atlas-galaxy-path-panel { position:absolute; z-index:25; top:12px; left:12px; display:flex; flex-direction:column; gap:.35rem; width:min(26rem, calc(100% - 24px)); max-height:min(22rem, 55%); box-sizing:border-box; padding:.55rem .65rem; border:1px solid #355246; border-radius:8px; background:rgba(13, 25, 19, .94); color:#cbdbd0; font-size:.78rem; } +.atlas-galaxy-path-panel { position:absolute; z-index:25; top:12px; left:12px; display:flex; flex-direction:column; gap:.35rem; width:min(26rem, calc(100% - 24px)); max-height:min(22rem, 55%); box-sizing:border-box; padding:.55rem .65rem; border:1px solid #355246; border-radius:8px; background:rgb(13, 25, 19); color:#cbdbd0; font-size:.78rem; } .atlas-galaxy-path-panel header, .atlas-galaxy-path-panel footer { display:flex; align-items:center; justify-content:space-between; gap:.5rem; } .atlas-galaxy-path-panel p { margin:0; color:#c6aa82; } .atlas-galaxy-path-panel ol { flex:1 1 auto; min-height:0; overflow:auto; margin:0; padding:0; list-style:none; } @@ -129,14 +130,14 @@ /* Zweites Review zu K5: die Ueberschrift des Bandes unter den Spalten und die Notiz, wenn Namen fehlen. */ .atlas-hierarchy-band-label { display:flex; flex-direction:column; align-items:center; gap:1px; padding:2px 8px; border:1px dashed #6f8f80; border-radius:5px; background:rgba(13, 15, 18, .86); color:#d7e6dd; white-space:nowrap; font:600 11px/1.35 var(--atlas-ui-font, system-ui, sans-serif); } .atlas-hierarchy-band-label small { color:#a9bdb2; font-weight:400; font-size:10px; } -.atlas-hierarchy-key { position:absolute; z-index:20; left:12px; bottom:12px; max-width:min(34rem, calc(100% - 24px)); margin:0; padding:.35rem .6rem; border:1px solid #355246; border-radius:6px; background:rgba(13, 25, 19, .9); color:#cbdbd0; font:12px/1.4 var(--atlas-ui-font, system-ui, sans-serif); pointer-events:none; } +.atlas-hierarchy-key { position:absolute; z-index:20; left:12px; bottom:12px; max-width:min(34rem, calc(100% - 24px)); margin:0; padding:.35rem .6rem; border:1px solid #355246; border-radius:6px; background:rgb(13, 25, 19); color:#cbdbd0; font:12px/1.4 var(--atlas-ui-font, system-ui, sans-serif); pointer-events:none; } /* Zurueck, Vor und die letzten Wurzeln (Handtest K2): klein, damit die Leiste einzeilig bleibt (K3). */ .atlas-graph-history { display:flex; align-items:center; gap:.25rem; flex:none; } .atlas-graph-history > button { min-width:1.9rem; padding:.25rem .45rem; } .atlas-graph-recent { position:relative; } .atlas-graph-recent > summary { padding:.25rem .5rem; border:1px solid #29473a; border-radius:6px; cursor:pointer; color:#b5c9bc; white-space:nowrap; } -.atlas-graph-recent-menu { position:absolute; top:calc(100% + .35rem); left:0; z-index:30; width:min(24rem, calc(100vw - 3rem)); max-height:18rem; overflow:auto; margin:0; padding:.45rem; list-style:none; border:1px solid #355246; border-radius:8px; background:rgba(13,25,19,.98); box-shadow:0 8px 24px #0005; } +.atlas-graph-recent-menu { position:absolute; top:calc(100% + .35rem); left:0; z-index:30; width:min(24rem, calc(100vw - 3rem)); max-height:18rem; overflow:auto; margin:0; padding:.45rem; list-style:none; border:1px solid #355246; border-radius:8px; background:rgb(13,25,19); box-shadow:0 8px 24px #0005; } .atlas-graph-recent-menu button { width:100%; display:flex; flex-direction:column; align-items:flex-start; gap:.1rem; text-align:left; border:0; background:transparent; } .atlas-graph-recent-menu button span { max-width:100%; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-size:.72rem; color:#95a89e; } .atlas-graph-recent-menu button[aria-current=true] { opacity:.6; } diff --git a/graph-ui/src/galaxy/overlay-opacity.test.ts b/graph-ui/src/galaxy/overlay-opacity.test.ts new file mode 100644 index 000000000..963f88210 --- /dev/null +++ b/graph-ui/src/galaxy/overlay-opacity.test.ts @@ -0,0 +1,77 @@ +/* + * Handtest 2026-10-04, Review zu K29: die Namen der Szene liegen seit G1 unter + * den Bedienflaechen der Galaxie, aber die Flaechen waren leicht durchsichtig + * (.9 bis .98, die Schaltflaeche "fit view" 82 %). Der Name des Pfadziels und + * sein Ring schienen darum durch "Selection details" hindurch. Hier steht, dass + * jede Flaeche, die ueber der Szene liegt, deckend gemalt wird. + */ +import { readFileSync } from 'node:fs'; +import { describe, expect, it } from 'vitest'; + +const read = (path: string) => readFileSync(new URL(path, import.meta.url), 'utf8').replace(/\/\*[\s\S]*?\*\//g, ''); +const SHEETS = [read('./graph-exploration.css'), read('../styles/terminal.css'), read('../styles/workspace.css')]; + +/** The background of the last rule, across the sheets, whose selector list names `selector` exactly. */ +function background(selector: string): string | undefined { + let found: string | undefined; + for (const css of SHEETS) { + for (const match of css.matchAll(/([^{}]+)\{([^}]*)\}/g)) { + if (!match[1]!.split(',').map(item => item.trim().replace(/\s+/g, ' ')).includes(selector)) continue; + for (const part of match[2]!.split(';')) { + const [name, ...value] = part.split(':'); + if (name && ['background', 'background-color'].includes(name.trim())) found = value.join(':').trim(); + } + } + } + return found; +} + +/** True when the colour hides everything behind it: no alpha below one. Tokens of this project are plain hex colours. */ +function opaque(value: string): boolean { + if (/^var\(--atlas-(bg|panel|panel-raised)\)$/.test(value)) return true; + if (/^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(value)) return true; + const rgb = /^rgba?\(([^)]*)\)$/.exec(value); + if (!rgb) return false; + const parts = rgb[1]!.split(/[\s,/]+/).filter(Boolean); + if (parts.length === 3) return true; + const alpha = parts[3]!; + return alpha.endsWith('%') ? Number.parseFloat(alpha) >= 100 : Number.parseFloat(alpha) >= 1; +} + +const OVERLAYS = { + 'Selection details': '.galaxy-selection-evidence.atlas-galaxy-selection-details', + 'path panel': '.atlas-galaxy-path-panel', + 'hierarchy key': '.atlas-hierarchy-key', + 'fit view': '.atlas-galaxy-fit', + 'render progress': '.atlas-graph-render-progress', + 'search results': '.atlas-galaxy-search-results', + 'Path to menu': '.atlas-graph-path-menu', + 'Edge types menu': '.atlas-trace-edge-menu', + 'more and Limits menu': '.atlas-graph-limits-menu', + 'Recent menu': '.atlas-graph-recent-menu', + 'node picker': '.atlas-galaxy-node-picker', + 'agents instrument': '.atlas-agents', + 'agents follow line': '.atlas-agents-followline', + 'agents timeline': '.atlas-agents-timeline', + 'placeholder': '.atlas-galaxy-placeholder', + 'hover card': '.atlas-galaxy-card', + 'coverage hover card': '.atlas-coverage-tooltip', +}; + +describe('K29: nothing of the scene shows through a Galaxy overlay', () => { + for (const [name, selector] of Object.entries(OVERLAYS)) { + it(`${name} is painted opaque`, () => { + const value = background(selector); + expect(value, `${selector} has no background`).toBeDefined(); + expect(opaque(value!), `${selector}: ${value}`).toBe(true); + }); + } + + it('reads alpha in every notation the sheets use', () => { + expect(opaque('rgba(13, 25, 19, .95)')).toBe(false); + expect(opaque('rgb(10 14 13 / 82%)')).toBe(false); + expect(opaque('rgb(13, 25, 19)')).toBe(true); + expect(opaque('#0d1913')).toBe(true); + expect(opaque('#0d191380')).toBe(false); + }); +}); diff --git a/graph-ui/src/styles/terminal.css b/graph-ui/src/styles/terminal.css index 4d7546eeb..6777c9387 100644 --- a/graph-ui/src/styles/terminal.css +++ b/graph-ui/src/styles/terminal.css @@ -2369,8 +2369,8 @@ button.atlas-galaxy-legend-swatch:hover { /* * Der Weg zurueck zur eingepassten Ansicht (W10b, AC5). * - * Oben rechts in der Szene, halbtransparent, in derselben Schrift wie alles - * hier. Er liegt bewusst IM Bild und nicht im Kopf: die Kopfzeile ist bei 440 + * Oben rechts in der Szene, deckend (Review zu K29: durch 82 % schien ein + * Name der Szene hindurch), in derselben Schrift wie alles hier. Er liegt bewusst IM Bild und nicht im Kopf: die Kopfzeile ist bei 440 * Pixeln Breite voll (siehe .atlas-galaxy-head-row), und ein Wort mehr darin * haette den Zuklapper abgeschnitten. Die andere Ecke gehoert dem Instrument * der Agentenebene, also stehen sich die beiden nie im Weg. @@ -2382,7 +2382,7 @@ button.atlas-galaxy-legend-swatch:hover { z-index: 3; padding: 1px 6px; border: 1px solid var(--atlas-line); - background: rgb(10 14 13 / 82%); + background: rgb(10 14 13); color: var(--atlas-dim); font: inherit; font-size: 11px; @@ -5881,8 +5881,9 @@ button.atlas-settings-model-pick:focus-visible { /* * Das Instrument unten rechts. * - * Halbtransparent ueber dem Graphen, in derselben Schrift und mit denselben - * duennen Raendern wie der Rest der Oberflaeche. Kein Glassmorphism: keine + * Deckend ueber dem Graphen (Review zu K29: durch eine halbtransparente + * Flaeche schienen die Namen der Szene hindurch), in derselben Schrift und mit + * denselben duennen Raendern wie der Rest der Oberflaeche. Kein Glassmorphism: keine * Weichzeichnung, kein Verlauf, kein runder Kasten. Was hier "fancy" ist, kommt * aus dem Licht der Koerper daneben und nicht aus der Flaeche. */ @@ -5897,7 +5898,7 @@ button.atlas-settings-model-pick:focus-visible { max-width: calc(100% - 16px); max-height: calc(100% - 16px); border: 1px solid var(--atlas-line); - background: rgb(10 14 13 / 88%); + background: rgb(10 14 13); color: var(--atlas-fg); font-family: var(--atlas-font); font-size: 11px; @@ -6342,7 +6343,7 @@ button.atlas-settings-model-pick:focus-visible { margin: 0 0 2px; padding: 2px 8px; border: 1px solid var(--atlas-line); - background: rgb(10 14 13 / 88%); + background: rgb(10 14 13); color: var(--atlas-agent-color, var(--atlas-fg)); font-size: 11px; white-space: nowrap; @@ -6366,7 +6367,7 @@ button.atlas-settings-model-pick:focus-visible { gap: 2px; padding: 3px 8px 4px; border-top: 1px solid var(--atlas-line); - background: rgb(10 14 13 / 92%); + background: rgb(10 14 13); font-family: var(--atlas-font); font-size: 10px; }