Skip to content

Commit 12dde0b

Browse files
authored
Merge pull request #156 from jongalloway/advanced-svg-filter-themes
Add advanced SVG filter effects and glass/neumorphic/neon themes
2 parents 46cb693 + 01506c5 commit 12dde0b

14 files changed

+904
-36
lines changed

README.md

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ DiagramForge currently supports more than a dozen diagram types across Mermaid,
157157

158158
<h3>Built-in Themes</h3>
159159

160-
This theme gallery is also a representative sample rather than the full catalog. DiagramForge ships with 25 built-in themes: `default`, `zinc-light`, `zinc-dark`, `dark`, `neutral`, `forest`, `presentation`, `prism`, `angled-light`, `angled-dark`, `github-light`, `github-dark`, `nord`, `nord-light`, `dracula`, `tokyo-night`, `tokyo-night-storm`, `tokyo-night-light`, `catppuccin-latte`, `catppuccin-mocha`, `solarized-light`, `solarized-dark`, `one-dark`, `cyberpunk`, and `synthwave`. See [With a custom theme](#with-a-custom-theme), [doc/theming.md](doc/theming.md), and [doc/frontmatter.md](doc/frontmatter.md) for the full styling surface.
160+
This theme gallery is also a representative sample rather than the full catalog. DiagramForge ships with 28 built-in themes: `default`, `zinc-light`, `zinc-dark`, `dark`, `neutral`, `forest`, `presentation`, `prism`, `angled-light`, `angled-dark`, `github-light`, `github-dark`, `nord`, `nord-light`, `dracula`, `tokyo-night`, `tokyo-night-storm`, `tokyo-night-light`, `catppuccin-latte`, `catppuccin-mocha`, `solarized-light`, `solarized-dark`, `one-dark`, `cyberpunk`, `synthwave`, `glass`, `neumorphic`, and `neon`. See [With a custom theme](#with-a-custom-theme), [doc/theming.md](doc/theming.md), and [doc/frontmatter.md](doc/frontmatter.md) for the full styling surface.
161161

162162
<table cellpadding="16" width="100%">
163163
<tr>
@@ -272,6 +272,32 @@ This theme gallery is also a representative sample rather than the full catalog.
272272
<sub>Synthwave</sub>
273273
</td>
274274
</tr>
275+
<tr>
276+
<td align="center" valign="top" width="50%">
277+
<a href="https://github.com/jongalloway/DiagramForge/blob/main/tests/DiagramForge.E2ETests/Fixtures/mermaid-theme-glass.expected.svg">
278+
<img src="https://raw.githubusercontent.com/jongalloway/DiagramForge/main/tests/DiagramForge.E2ETests/Fixtures/mermaid-theme-glass.expected.svg" alt="Glass theme" width="400" />
279+
</a>
280+
<br />
281+
<sub>Glass</sub>
282+
</td>
283+
<td align="center" valign="top" width="50%">
284+
<a href="https://github.com/jongalloway/DiagramForge/blob/main/tests/DiagramForge.E2ETests/Fixtures/mermaid-theme-neumorphic.expected.svg">
285+
<img src="https://raw.githubusercontent.com/jongalloway/DiagramForge/main/tests/DiagramForge.E2ETests/Fixtures/mermaid-theme-neumorphic.expected.svg" alt="Neumorphic theme" width="400" />
286+
</a>
287+
<br />
288+
<sub>Neumorphic</sub>
289+
</td>
290+
</tr>
291+
<tr>
292+
<td align="center" valign="top" width="50%">
293+
<a href="https://github.com/jongalloway/DiagramForge/blob/main/tests/DiagramForge.E2ETests/Fixtures/mermaid-theme-neon.expected.svg">
294+
<img src="https://raw.githubusercontent.com/jongalloway/DiagramForge/main/tests/DiagramForge.E2ETests/Fixtures/mermaid-theme-neon.expected.svg" alt="Neon theme" width="400" />
295+
</a>
296+
<br />
297+
<sub>Neon</sub>
298+
</td>
299+
<td></td>
300+
</tr>
275301
</table>
276302
<!-- markdownlint-enable MD033 -->
277303

src/DiagramForge/DiagramRenderer.cs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -543,8 +543,42 @@ private static void ApplyShadowStyle(Theme theme, string shadowStyle)
543543
theme.ShadowOffsetX = 0;
544544
theme.ShadowOffsetY = 0;
545545
break;
546+
case "neumorphic":
547+
theme.ShadowStyle = "neumorphic";
548+
theme.UseNodeShadows = true;
549+
theme.ShadowOpacity = Math.Clamp(theme.ShadowOpacity <= 0 ? 0.50 : theme.ShadowOpacity, 0.30, 0.70);
550+
theme.ShadowBlur = Math.Clamp(theme.ShadowBlur <= 0 ? 4.00 : theme.ShadowBlur, 2.00, 6.00);
551+
theme.ShadowOffsetX = 0;
552+
theme.ShadowOffsetY = 0;
553+
break;
554+
case "frosted-glass":
555+
theme.ShadowStyle = "frosted-glass";
556+
theme.UseNodeShadows = true;
557+
theme.ShadowOpacity = Math.Clamp(theme.ShadowOpacity <= 0 ? 0.10 : theme.ShadowOpacity, 0.06, 0.18);
558+
theme.ShadowBlur = Math.Clamp(theme.ShadowBlur <= 0 ? 2.80 : theme.ShadowBlur, 1.50, 5.00);
559+
theme.ShadowOffsetY = theme.ShadowOffsetY == 0 ? 3.00 : theme.ShadowOffsetY;
560+
break;
561+
case "glass-glow":
562+
theme.ShadowStyle = "glass-glow";
563+
theme.UseNodeShadows = true;
564+
theme.ShadowBlur = Math.Clamp(theme.ShadowBlur <= 0 ? 2.00 : theme.ShadowBlur, 1.00, 5.00);
565+
theme.ShadowOffsetX = 0;
566+
theme.ShadowOffsetY = 0;
567+
break;
568+
case "inner-glass":
569+
theme.ShadowStyle = "inner-glass";
570+
theme.UseNodeShadows = true;
571+
theme.ShadowBlur = Math.Clamp(theme.ShadowBlur <= 0 ? 1.20 : theme.ShadowBlur, 0.50, 2.50);
572+
break;
573+
case "ambient-shadow":
574+
theme.ShadowStyle = "ambient-shadow";
575+
theme.UseNodeShadows = true;
576+
theme.ShadowOpacity = Math.Clamp(theme.ShadowOpacity <= 0 ? 0.10 : theme.ShadowOpacity, 0.04, 0.20);
577+
theme.ShadowBlur = Math.Clamp(theme.ShadowBlur <= 0 ? 3.00 : theme.ShadowBlur, 1.50, 6.00);
578+
theme.ShadowOffsetY = theme.ShadowOffsetY == 0 ? 3.00 : theme.ShadowOffsetY;
579+
break;
546580
default:
547-
throw new ArgumentException($"Unknown shadow style in frontmatter: '{shadowStyle}'. Expected none, soft, or glow.", nameof(shadowStyle));
581+
throw new ArgumentException($"Unknown shadow style in frontmatter: '{shadowStyle}'. Expected none, soft, glow, neumorphic, frosted-glass, glass-glow, inner-glass, or ambient-shadow.", nameof(shadowStyle));
548582
}
549583
}
550584

src/DiagramForge/Models/Theme.cs

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ public class Theme
3434
"one-dark",
3535
"cyberpunk",
3636
"synthwave",
37+
"glass",
38+
"neumorphic",
39+
"neon",
3740
];
3841

3942
// ── Built-in named presets ────────────────────────────────────────────────
@@ -390,6 +393,15 @@ public class Theme
390393
/// <summary>Dark synthwave theme with warm sunset gradients and analog glow.</summary>
391394
public static Theme Synthwave => CreateSynthwaveTheme();
392395

396+
/// <summary>Light frosted-glass theme with translucent sheen and specular highlights.</summary>
397+
public static Theme Glass => CreateGlassTheme();
398+
399+
/// <summary>Clean neumorphic theme with paired light and dark soft shadows for tactile depth.</summary>
400+
public static Theme Neumorphic => CreateNeumorphicTheme();
401+
402+
/// <summary>Dark neon theme with vivid glass-glow halos around nodes.</summary>
403+
public static Theme Neon => CreateNeonTheme();
404+
393405
// ── Palette lookup ────────────────────────────────────────────────────────
394406

395407
/// <summary>All built-in theme names supported by <see cref="GetByName(string?)"/>.</summary>
@@ -428,6 +440,9 @@ public class Theme
428440
"one-dark" => OneDark,
429441
"cyberpunk" => Cyberpunk,
430442
"synthwave" => Synthwave,
443+
"glass" => Glass,
444+
"neumorphic" => Neumorphic,
445+
"neon" => Neon,
431446
_ => null,
432447
};
433448

@@ -891,6 +906,117 @@ private static Theme CreateSynthwaveTheme()
891906
return theme;
892907
}
893908

909+
private static Theme CreateGlassTheme()
910+
{
911+
var theme = CreatePreset(
912+
backgroundColor: "#E2E8F0",
913+
foregroundColor: "#1A2B42",
914+
accentColor: "#2563EB",
915+
mutedColor: "#64748B",
916+
surfaceColor: "#F8FAFC",
917+
borderColor: "#94A3B8",
918+
lineColor: "#475569",
919+
nodePalette:
920+
[
921+
"#C7D8EED9", "#C7E8D5D9", "#E8D5C7D9", "#D5C7E8D9",
922+
"#C7E8E2D9", "#E8C7D2D9", "#D5DFEAD9", "#E8E2C7D9",
923+
],
924+
useGradients: true,
925+
useBorderGradients: false,
926+
gradientStrength: 0.08);
927+
928+
theme.NodeFillColor = "#C7D8EED9";
929+
theme.NodeStrokeColor = "#94A3B8";
930+
theme.GroupFillColor = "#CBD5E1B3";
931+
theme.GroupStrokeColor = "#94A3B8";
932+
theme.BorderRadius = 14;
933+
theme.StrokeWidth = 1.4;
934+
theme.ShadowStyle = "frosted-glass";
935+
theme.UseNodeShadows = true;
936+
theme.ShadowColor = "#0F172A";
937+
theme.ShadowOpacity = 0.18;
938+
theme.ShadowBlur = 3.50;
939+
theme.ShadowOffsetY = 3.00;
940+
return theme;
941+
}
942+
943+
private static Theme CreateNeumorphicTheme()
944+
{
945+
var theme = CreatePreset(
946+
backgroundColor: "#E4E9F0",
947+
foregroundColor: "#2D3748",
948+
accentColor: "#4299E1",
949+
mutedColor: "#8896A6",
950+
surfaceColor: "#E4E9F0",
951+
borderColor: "#D2D8E0",
952+
lineColor: "#8896A6",
953+
nodePalette:
954+
[
955+
"#E4E9F0", "#E4E9F0", "#E4E9F0", "#E4E9F0",
956+
"#E4E9F0", "#E4E9F0", "#E4E9F0", "#E4E9F0",
957+
],
958+
useGradients: false,
959+
useBorderGradients: false,
960+
gradientStrength: 0.0);
961+
962+
theme.NodeFillColor = "#E4E9F0";
963+
theme.NodeStrokeColor = "#D2D8E0";
964+
theme.GroupFillColor = "#E4E9F0";
965+
theme.GroupStrokeColor = "#D2D8E0";
966+
theme.BorderRadius = 16;
967+
theme.StrokeWidth = 0.8;
968+
theme.ShadowStyle = "neumorphic";
969+
theme.UseNodeShadows = true;
970+
theme.ShadowColor = "#8B98AC";
971+
theme.ShadowOpacity = 0.55;
972+
theme.ShadowBlur = 4.00;
973+
theme.ShadowOffsetX = 0;
974+
theme.ShadowOffsetY = 0;
975+
return theme;
976+
}
977+
978+
private static Theme CreateNeonTheme()
979+
{
980+
var theme = CreatePreset(
981+
backgroundColor: "#0D0D1A",
982+
foregroundColor: "#E0E0F0",
983+
accentColor: "#00FF88",
984+
mutedColor: "#7A7A9E",
985+
surfaceColor: "#141428",
986+
borderColor: "#00FF88",
987+
lineColor: "#00CCFF",
988+
nodePalette:
989+
[
990+
"#101028", "#121230", "#0E1828", "#141432",
991+
"#161636", "#0E1E30", "#121228", "#101830",
992+
],
993+
useGradients: true,
994+
useBorderGradients: true,
995+
gradientStrength: 0.14);
996+
997+
theme.NodeFillColor = "#101028";
998+
theme.NodeStrokeColor = "#00FF88";
999+
theme.GroupFillColor = "#141428E0";
1000+
theme.GroupStrokeColor = "#00CCFF";
1001+
theme.TitleTextColor = "#00FF88";
1002+
theme.EdgeColor = "#00CCFF";
1003+
theme.BorderGradientStops =
1004+
[
1005+
"#00FF88", // neon green
1006+
"#00CCFF", // electric blue
1007+
"#FF00FF", // magenta
1008+
"#FFD700", // gold
1009+
];
1010+
theme.BorderRadius = 8;
1011+
theme.StrokeWidth = 1.6;
1012+
theme.ShadowStyle = "glass-glow";
1013+
theme.UseNodeShadows = true;
1014+
theme.ShadowBlur = 4.00;
1015+
theme.ShadowOffsetX = 0;
1016+
theme.ShadowOffsetY = 0;
1017+
return theme;
1018+
}
1019+
8941020
private static List<string> CreateStrokePalette(IEnumerable<string> palette, double darkenAmount) =>
8951021
[.. palette.Select(color => ColorUtils.Darken(color, darkenAmount))];
8961022

src/DiagramForge/Rendering/SvgNodeWriter.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,8 @@ internal static void AppendNode(StringBuilder sb, Node node, Theme theme, int no
6565
&& textOnlyObj is bool isTextOnly
6666
&& isTextOnly;
6767
bool applyNodeShadow = theme.UseNodeShadows
68-
&& (string.Equals(theme.ShadowStyle, "soft", StringComparison.OrdinalIgnoreCase)
69-
|| string.Equals(theme.ShadowStyle, "glow", StringComparison.OrdinalIgnoreCase))
68+
&& !string.IsNullOrWhiteSpace(theme.ShadowStyle)
69+
&& !string.Equals(theme.ShadowStyle.Trim(), "none", StringComparison.OrdinalIgnoreCase)
7070
&& !textOnly;
7171

7272
sb.AppendLine($""" <g transform="translate({SvgRenderSupport.F(node.X)},{SvgRenderSupport.F(node.Y)})">""");

0 commit comments

Comments
 (0)