mirror of
https://github.com/hansjone/mind-map.git
synced 2026-10-09 04:30:46 +08:00
fix: make glass cards a solid elevated surface without strip dividers
Remove header bands and inner hairlines that read as stacked strips; restore a lighter opaque fill and heavier shadow so cards stay readable on the dark canvas. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
74f0d8e78a
commit
4bffec1d81
5 changed files with 59 additions and 104 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)",
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue