diff --git a/apps/web/src/TopologyCanvas.tsx b/apps/web/src/TopologyCanvas.tsx index 2d024e7..13cef28 100644 --- a/apps/web/src/TopologyCanvas.tsx +++ b/apps/web/src/TopologyCanvas.tsx @@ -5,8 +5,6 @@ import { fillAccentWash, paintAccentRail, paintBadgeChip, - paintGlassGlow, - paintGlassHighlight, paintNodeShadow, } from "./canvas-chrome"; import { @@ -606,24 +604,11 @@ export function TopologyCanvas() { ctx.shadowColor = flashNodeColor; ctx.shadowBlur = 14 / viewport.zoom; } else if (cardView) { - // Accent bloom pass (low alpha), then glass body with drop shadow - ctx.save(); - paintGlassGlow(ctx, fill, viewport.zoom, isSel); - ctx.globalAlpha = (inGhost ? 0.7 : 1) * (isSel ? 0.28 : 0.16); - ctx.fillStyle = fill; - drawNodeShape( - ctx, - "card", - x, - y, - boxW, - boxH, - dens ? 14 : 16, - ); - ctx.fill(); - ctx.restore(); - if (inGhost) ctx.globalAlpha = 0.7; - paintNodeShadow(ctx, viewport.zoom, { selected: isSel }); + // Solid body + deep drop shadow (skip floaty accent bloom) + paintNodeShadow(ctx, viewport.zoom, { + selected: isSel, + weight: "heavy", + }); } else { paintNodeShadow(ctx, viewport.zoom, { selected: isSel }); } @@ -640,22 +625,12 @@ export function TopologyCanvas() { ctx.fill(); clearShadow(ctx); ctx.globalAlpha = inGhost ? 0.7 : 1; + // Soft accent tint only — no header band / dividers fillAccentWash( ctx, fill, - cardView ? 0.06 : n.stylePreset === "title" ? 0.22 : 0.14, + cardView ? 0.08 : n.stylePreset === "title" ? 0.22 : 0.14, ); - if (cardView) { - paintGlassHighlight( - ctx, - x, - y, - boxW, - boxH, - cornerR, - theme.glassHighlight, - ); - } if ( !cardView && n.stylePreset !== "decision" && @@ -679,7 +654,7 @@ export function TopologyCanvas() { ? fill : theme.glassBorder; ctx.lineWidth = - (isDrop || isFlash || isSel || isHover ? 1.6 : 1) / viewport.zoom; + (isDrop || isFlash || isSel || isHover ? 1.8 : 1.25) / viewport.zoom; } else { ctx.strokeStyle = isForbidden ? theme.warn @@ -723,9 +698,7 @@ export function TopologyCanvas() { () => setImageTick((t) => t + 1), ); } else { - // Match glass header so overlay mount doesn't flash empty - ctx.fillStyle = theme.glassHeader; - ctx.fillRect(x, y, boxW, 40); + // Title-only placeholder under HTML overlay (same flat surface) ctx.beginPath(); ctx.arc(x + 16, y + 20, 4, 0, Math.PI * 2); ctx.fillStyle = fill; diff --git a/apps/web/src/canvas-chrome.ts b/apps/web/src/canvas-chrome.ts index c138040..21aee75 100644 --- a/apps/web/src/canvas-chrome.ts +++ b/apps/web/src/canvas-chrome.ts @@ -14,14 +14,25 @@ export function canvasFont( export function paintNodeShadow( ctx: CanvasRenderingContext2D, zoom: number, - opts?: { selected?: boolean }, + opts?: { selected?: boolean; weight?: "default" | "heavy" }, ) { + const heavy = opts?.weight === "heavy"; + const z = Math.max(0.5, zoom); + if (heavy) { + ctx.shadowColor = opts?.selected + ? "rgba(0, 0, 0, 0.62)" + : "rgba(0, 0, 0, 0.5)"; + ctx.shadowBlur = (opts?.selected ? 28 : 22) / z; + ctx.shadowOffsetX = 0; + ctx.shadowOffsetY = 7 / z; + return; + } ctx.shadowColor = opts?.selected ? "rgba(0, 0, 0, 0.45)" : "rgba(0, 0, 0, 0.28)"; - ctx.shadowBlur = (opts?.selected ? 18 : 12) / Math.max(0.5, zoom); + ctx.shadowBlur = (opts?.selected ? 18 : 12) / z; ctx.shadowOffsetX = 0; - ctx.shadowOffsetY = 3 / Math.max(0.5, zoom); + ctx.shadowOffsetY = 3 / z; } export function clearShadow(ctx: CanvasRenderingContext2D) { @@ -122,7 +133,7 @@ export function paintGlassGlow( ctx.shadowOffsetY = 0; } -/** Inner top highlight strip for frosted-glass feel. */ +/** Soft top sheen — keep short so it does not read as a header divider. */ export function paintGlassHighlight( ctx: CanvasRenderingContext2D, x: number, @@ -135,17 +146,19 @@ export function paintGlassHighlight( ctx.save(); ctx.beginPath(); const rr = Math.min(radius, h / 2, w / 2); + const band = Math.min(12, h * 0.2); ctx.moveTo(x + rr, y); ctx.lineTo(x + w - rr, y); ctx.quadraticCurveTo(x + w, y, x + w, y + rr); - ctx.lineTo(x + w, y + Math.min(h * 0.45, 28)); - ctx.lineTo(x, y + Math.min(h * 0.45, 28)); + ctx.lineTo(x + w, y + band); + ctx.lineTo(x, y + band); ctx.lineTo(x, y + rr); ctx.quadraticCurveTo(x, y, x + rr, y); ctx.closePath(); - const g = ctx.createLinearGradient(x, y, x, y + 28); + const g = ctx.createLinearGradient(x, y, x, y + band); g.addColorStop(0, highlight); g.addColorStop(1, "rgba(255,255,255,0)"); + ctx.globalAlpha = 0.55; ctx.fillStyle = g; ctx.fill(); ctx.restore(); diff --git a/apps/web/src/node-props/draw-sheet.ts b/apps/web/src/node-props/draw-sheet.ts index c75828e..e8c1e70 100644 --- a/apps/web/src/node-props/draw-sheet.ts +++ b/apps/web/src/node-props/draw-sheet.ts @@ -45,19 +45,12 @@ export function drawPropSheet( ) { const { w, h } = estimateCardSize(node, dens); const radius = dens ? 14 : 16; - const z = Math.max(0.5, zoom); ctx.save(); ctx.beginPath(); roundRectPath(ctx, x, y, w, h, radius); ctx.clip(); - // Soft top accent hairline (identity without thick colored frame) - ctx.fillStyle = accent; - ctx.globalAlpha = 0.85; - ctx.fillRect(x, y, w, 2.5); - ctx.globalAlpha = 1; - // Low zoom: title only if (zoom < 0.35) { ctx.fillStyle = theme.labelPrimary; @@ -78,11 +71,7 @@ export function drawPropSheet( return; } - // Glass header band - ctx.fillStyle = theme.glassHeader; - ctx.fillRect(x, y, w, CARD_HEADER_H); - - // Accent dot with soft halo + // Title row (same surface as body — no striped header band) ctx.beginPath(); ctx.arc(x + 16, y + CARD_HEADER_H / 2, 7, 0, Math.PI * 2); ctx.fillStyle = accent; @@ -114,15 +103,7 @@ export function drawPropSheet( }); } - // Hairline under header - ctx.strokeStyle = theme.glassBorder; - ctx.lineWidth = 1 / z; - ctx.beginPath(); - ctx.moveTo(x + CARD_PAD_X, y + CARD_HEADER_H); - ctx.lineTo(x + w - CARD_PAD_X, y + CARD_HEADER_H); - ctx.stroke(); - - let cy = y + CARD_HEADER_H + CARD_PAD_Y; + let cy = y + CARD_HEADER_H + Math.max(4, CARD_PAD_Y - 4); const contentW = w - CARD_PAD_X * 2; // Hero image @@ -179,21 +160,12 @@ export function drawPropSheet( cy += lines.length * lh + CARD_GAP; } - // Description as readable key-value list (never raw JSON) + // Description as one compact key-value block (no row dividers) const chips = descriptionChips(node.description); if (chips.length) { + const rowGap = dens ? 4 : 5; for (let i = 0; i < chips.length; i++) { const chip = chips[i]!; - if (i > 0) { - ctx.strokeStyle = theme.glassBorder; - ctx.globalAlpha = 0.55; - ctx.lineWidth = 1 / z; - ctx.beginPath(); - ctx.moveTo(x + CARD_PAD_X, cy - 2); - ctx.lineTo(x + w - CARD_PAD_X, cy - 2); - ctx.stroke(); - ctx.globalAlpha = 1; - } ctx.fillStyle = theme.labelSecondary; ctx.font = canvasFont(10, 500); ctx.textBaseline = "middle"; @@ -207,7 +179,7 @@ export function drawPropSheet( const vw = contentW - labelW - 10; const vLines = wrapTextLines(ctx, chip.value, vw, 1); ctx.fillText(vLines[0] ?? chip.value, vx, cy + CARD_CHIP_H / 2); - cy += CARD_CHIP_H + 6; + cy += CARD_CHIP_H + rowGap; } cy += CARD_GAP - 4; } diff --git a/apps/web/src/node-props/node-shell.css b/apps/web/src/node-props/node-shell.css index 3929322..cbd995a 100644 --- a/apps/web/src/node-props/node-shell.css +++ b/apps/web/src/node-props/node-shell.css @@ -2,10 +2,10 @@ .ns-editor { --ns-bg: color-mix( in srgb, - var(--dsw-alias-bg-layer-1, #1b1e28) 72%, - transparent + #2d3444 88%, + var(--dsw-alias-bg-layer-1, #1b1e28) ); - --ns-border: rgba(255, 255, 255, 0.14); + --ns-border: rgba(255, 255, 255, 0.2); --ns-label: var(--dsw-alias-label-secondary, #a5adb8); --ns-text: var(--dsw-alias-label-primary, #f4f5f7); --ns-brand: var(--dsw-alias-brand-primary, #5e6ad2); @@ -19,11 +19,11 @@ border: 1px solid var(--ns-border); border-radius: 16px; overflow: visible; - backdrop-filter: blur(16px) saturate(1.2); - -webkit-backdrop-filter: blur(16px) saturate(1.2); + backdrop-filter: blur(18px) saturate(1.15); + -webkit-backdrop-filter: blur(18px) saturate(1.15); box-shadow: - 0 1px 0 rgba(255, 255, 255, 0.1) inset, - 0 12px 40px rgba(0, 0, 0, 0.42); + 0 10px 28px rgba(0, 0, 0, 0.45), + 0 22px 56px rgba(0, 0, 0, 0.38); color: var(--ns-text); font-family: var(--ns-font); font-size: 12.5px; @@ -47,13 +47,9 @@ display: flex; align-items: center; gap: 10px; - padding: 12px 14px; - background: linear-gradient( - 180deg, - rgba(255, 255, 255, 0.08) 0%, - rgba(255, 255, 255, 0.02) 100% - ); - border-bottom: 1px solid var(--ns-border); + padding: 12px 14px 8px; + background: transparent; + border-bottom: none; border-radius: 16px 16px 0 0; cursor: grab; user-select: none; @@ -140,10 +136,10 @@ .ns-kv { margin: 4px 2px 6px; - padding: 8px 8px 10px; - border-radius: 12px; - background: rgba(255, 255, 255, 0.03); - border: 1px solid rgba(255, 255, 255, 0.08); + padding: 4px 2px 6px; + border-radius: 0; + background: transparent; + border: none; } .ns-kv__title { @@ -160,7 +156,7 @@ padding: 0; display: flex; flex-direction: column; - gap: 0; + gap: 4px; } .ns-kv__row { @@ -168,8 +164,8 @@ grid-template-columns: minmax(64px, 0.34fr) 1fr auto; gap: 6px; align-items: center; - padding: 6px 2px; - border-top: 1px solid rgba(255, 255, 255, 0.06); + padding: 2px 0; + border-top: none; } .ns-kv__row:first-child { diff --git a/apps/web/src/system-theme.ts b/apps/web/src/system-theme.ts index d67033d..cb11dd7 100644 --- a/apps/web/src/system-theme.ts +++ b/apps/web/src/system-theme.ts @@ -26,10 +26,11 @@ const FALLBACK_DARK: SystemTheme = { bubble: "rgba(37,42,58,0.9)", warn: "#f59e5b", error: "#ef4444", - glassFill: "rgba(28, 32, 44, 0.72)", - glassBorder: "rgba(255, 255, 255, 0.14)", - glassHighlight: "rgba(255, 255, 255, 0.1)", - glassHeader: "rgba(255, 255, 255, 0.045)", + // Elevated card surface — clearly above canvas, not near-black + glassFill: "rgba(45, 52, 68, 0.96)", + glassBorder: "rgba(255, 255, 255, 0.22)", + glassHighlight: "rgba(255, 255, 255, 0.06)", + glassHeader: "rgba(255, 255, 255, 0.05)", }; const FALLBACK_LIGHT: SystemTheme = { @@ -42,9 +43,9 @@ const FALLBACK_LIGHT: SystemTheme = { bubble: "rgba(255,255,255,0.92)", warn: "#d97706", error: "#dc2626", - glassFill: "rgba(255, 255, 255, 0.78)", - glassBorder: "rgba(0, 0, 0, 0.1)", - glassHighlight: "rgba(255, 255, 255, 0.65)", + glassFill: "rgba(255, 255, 255, 0.96)", + glassBorder: "rgba(0, 0, 0, 0.12)", + glassHighlight: "rgba(255, 255, 255, 0.4)", glassHeader: "rgba(0, 0, 0, 0.03)", };