Compare commits
2 Commits
454d2a2f40
...
d12e9873f0
| Author | SHA1 | Date | |
|---|---|---|---|
| d12e9873f0 | |||
| 7224a423fa |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -19,3 +19,6 @@ temp
|
||||
|
||||
# Claude Code
|
||||
.claude/
|
||||
LazyBear.MCP/LazyBear.MCP.csproj
|
||||
|
||||
/artifacts
|
||||
@@ -18,6 +18,9 @@
|
||||
<PackageReference Include="RazorConsole.Core" Version="0.5.0" />
|
||||
<PackageReference Include="RestSharp" Version="112.0.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<EmbeddedResource Include="logo.svg">
|
||||
<LogicalView>LazyBear.MCP</LogicalView>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -9,37 +9,31 @@
|
||||
@implements IDisposable
|
||||
|
||||
<Rows Expand="true">
|
||||
<Panel Title="@($"LazyBear MCP [{Localization.Label}]")"
|
||||
TitleColor="@UiPalette.Accent"
|
||||
BorderColor="@UiPalette.Frame"
|
||||
<Columns Expand="true">
|
||||
<Markup Content="@GetHeaderProjectSegment()"
|
||||
Foreground="@UiPalette.Text"
|
||||
Background="@UiPalette.HeaderBrandBackground"
|
||||
Decoration="@Spectre.Console.Decoration.Bold" />
|
||||
@foreach (var segment in BuildHeaderSegments())
|
||||
{
|
||||
<Markup Content="@segment.Content"
|
||||
Foreground="@segment.Foreground"
|
||||
Background="@segment.Background"
|
||||
Decoration="@segment.Decoration" />
|
||||
}
|
||||
</Columns>
|
||||
|
||||
<Panel BorderColor="@UiPalette.Frame"
|
||||
Expand="true"
|
||||
Height="@GetPanelHeight()"
|
||||
Padding="@(new Spectre.Console.Padding(1, 0, 1, 0))">
|
||||
<Rows Expand="true">
|
||||
<Markup Content="@Localization.Current.HintBar" Foreground="@UiPalette.TextMuted" />
|
||||
<Markup Content=" " />
|
||||
|
||||
<Columns>
|
||||
@foreach (var tab in _tabs)
|
||||
{
|
||||
var isActive = _activeTab == tab;
|
||||
<Markup Content="@($" {GetTabLabel(tab)} ")"
|
||||
Foreground="@(isActive ? UiPalette.SelectionForeground : UiPalette.Text)"
|
||||
Background="@(isActive ? UiPalette.Accent : UiPalette.SurfaceMuted)"
|
||||
Decoration="@(isActive ? Spectre.Console.Decoration.Bold : Spectre.Console.Decoration.None)" />
|
||||
<Markup Content=" " />
|
||||
}
|
||||
</Columns>
|
||||
|
||||
<Markup Content=" " />
|
||||
|
||||
@if (_activeTab == Tab.Overview)
|
||||
{
|
||||
<OverviewTab Rows="@GetOverviewRows()"
|
||||
SelectedIndex="@_overviewSelection"
|
||||
SelectedIndexChanged="@OnOverviewSelectionChanged"
|
||||
ViewportRows="@GetOverviewViewportRows()"
|
||||
Endpoint="@_mcpEndpoint"
|
||||
Loc="@Localization.Current" />
|
||||
}
|
||||
else if (_activeTab == Tab.Logs)
|
||||
@@ -62,9 +56,38 @@
|
||||
}
|
||||
</Rows>
|
||||
</Panel>
|
||||
<Markup Content="@BuildStatusBar(UiMetrics.ConsoleWidth)"
|
||||
Foreground="@UiPalette.Text"
|
||||
Background="@UiPalette.SurfaceMuted" />
|
||||
|
||||
<ModalWindow IsOpened="@_isHelpOpen">
|
||||
<Panel Title="@Localization.Current.HelpModalTitle"
|
||||
TitleColor="@UiPalette.Accent"
|
||||
BorderColor="@UiPalette.AccentSoft"
|
||||
Width="@GetHelpModalWidth()"
|
||||
Padding="@(new Spectre.Console.Padding(1, 0, 1, 0))">
|
||||
<Rows>
|
||||
<Markup Content="@Localization.Current.HintBar"
|
||||
Foreground="@UiPalette.Text" />
|
||||
<Markup Content=" " />
|
||||
<Markup Content="@GetHelpLine(Tab.Overview, Localization.Current.OverviewHint)"
|
||||
Foreground="@UiPalette.TextMuted" />
|
||||
<Markup Content="@GetHelpLine(Tab.Logs, Localization.Current.LogsHint)"
|
||||
Foreground="@UiPalette.TextMuted" />
|
||||
<Markup Content="@GetHelpLine(Tab.Settings, Localization.Current.SettingsHint)"
|
||||
Foreground="@UiPalette.TextMuted" />
|
||||
<Markup Content=" " />
|
||||
<Markup Content="@Localization.Current.HelpCloseHint"
|
||||
Foreground="@UiPalette.AccentSoft"
|
||||
Decoration="@Spectre.Console.Decoration.Bold" />
|
||||
</Rows>
|
||||
</Panel>
|
||||
</ModalWindow>
|
||||
</Rows>
|
||||
|
||||
@code {
|
||||
private const char Nbsp = '\u00A0';
|
||||
|
||||
private enum Tab
|
||||
{
|
||||
Overview,
|
||||
@@ -72,6 +95,12 @@
|
||||
Settings
|
||||
}
|
||||
|
||||
private readonly record struct HeaderSegment(
|
||||
string Content,
|
||||
Spectre.Console.Color Foreground,
|
||||
Spectre.Console.Color Background,
|
||||
Spectre.Console.Decoration Decoration);
|
||||
|
||||
private static readonly Tab[] _tabs = [Tab.Overview, Tab.Logs, Tab.Settings];
|
||||
private static readonly string[] _logFilters = ["All", "Info", "Warn", "Error"];
|
||||
private readonly HashSet<string> _expandedModules = new(StringComparer.Ordinal);
|
||||
@@ -82,14 +111,16 @@
|
||||
private int _logSelection;
|
||||
private int _settingsSelection;
|
||||
private bool _logsStickToBottom = true;
|
||||
private bool _isHelpOpen;
|
||||
|
||||
private static readonly string _mcpEndpoint =
|
||||
Environment.GetEnvironmentVariable("ASPNETCORE_URLS") ?? "http://localhost:5000";
|
||||
|
||||
private static int GetPanelHeight() => Math.Max(Console.WindowHeight - 2, 10);
|
||||
private static int GetOverviewViewportRows() => Math.Max(Console.WindowHeight - 11, 3);
|
||||
private static int GetLogsViewportRows() => Math.Max(Console.WindowHeight - 16, 5);
|
||||
private static int GetSettingsViewportRows() => Math.Max(Console.WindowHeight - 13, 5);
|
||||
private static int GetPanelHeight() => Math.Max(UiMetrics.ConsoleHeight - 4, 10);
|
||||
private static int GetOverviewViewportRows() => Math.Max(GetPanelHeight() - 6, 3);
|
||||
private static int GetLogsViewportRows() => Math.Max(GetPanelHeight() - 10, 5);
|
||||
private static int GetSettingsViewportRows() => Math.Max(GetPanelHeight() - 7, 5);
|
||||
private static int GetHelpModalWidth() => Math.Max(Math.Min(UiMetrics.ConsoleWidth - 6, 84), 36);
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
@@ -115,6 +146,7 @@
|
||||
ConsoleKey.PageUp => "PageUp",
|
||||
ConsoleKey.PageDown => "PageDown",
|
||||
ConsoleKey.Tab => "Tab",
|
||||
ConsoleKey.Escape => "Escape",
|
||||
_ => key.KeyChar == '\0' ? string.Empty : key.KeyChar.ToString()
|
||||
};
|
||||
|
||||
@@ -145,6 +177,22 @@
|
||||
|
||||
private void HandleKeyDown(KeyboardEventArgs args)
|
||||
{
|
||||
if (args.Key is "h" or "H")
|
||||
{
|
||||
_isHelpOpen = !_isHelpOpen;
|
||||
return;
|
||||
}
|
||||
|
||||
if (_isHelpOpen)
|
||||
{
|
||||
if (args.Key == "Escape")
|
||||
{
|
||||
_isHelpOpen = false;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.Equals(args.Key, "Tab", StringComparison.Ordinal))
|
||||
{
|
||||
ChangeTab(args.ShiftKey ? -1 : 1);
|
||||
@@ -559,6 +607,127 @@
|
||||
_ => Localization.Current.TabSettings
|
||||
};
|
||||
|
||||
private HeaderSegment[] BuildHeaderSegments()
|
||||
{
|
||||
var width = Math.Max(UiMetrics.ConsoleWidth, 1);
|
||||
var projectSegmentWidth = GetHeaderProjectSegment().Length;
|
||||
var availableTabsWidth = Math.Max(width - projectSegmentWidth, 0);
|
||||
if (availableTabsWidth <= 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
var labels = _tabs.Select(GetTabLabel).ToArray();
|
||||
var labelWidths = labels.Select(label => label.Length).ToArray();
|
||||
var availableLabelWidth = availableTabsWidth - (_tabs.Length * 2);
|
||||
if (availableLabelWidth < _tabs.Length)
|
||||
{
|
||||
return
|
||||
[
|
||||
new HeaderSegment(
|
||||
FillBar(availableTabsWidth),
|
||||
UiPalette.Text,
|
||||
UiPalette.HeaderTabsBackground,
|
||||
Spectre.Console.Decoration.None)
|
||||
];
|
||||
}
|
||||
|
||||
while (labelWidths.Sum() > availableLabelWidth)
|
||||
{
|
||||
var largestIndex = Array.IndexOf(labelWidths, labelWidths.Max());
|
||||
if (largestIndex < 0 || labelWidths[largestIndex] <= 1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
labelWidths[largestIndex]--;
|
||||
}
|
||||
|
||||
var segments = new List<HeaderSegment>(_tabs.Length + 1);
|
||||
var usedWidth = 0;
|
||||
|
||||
for (var i = 0; i < _tabs.Length; i++)
|
||||
{
|
||||
var tab = _tabs[i];
|
||||
var label = FitInline(labels[i], labelWidths[i]);
|
||||
var content = WrapBarSegment(label);
|
||||
|
||||
segments.Add(new HeaderSegment(
|
||||
content,
|
||||
tab == _activeTab ? UiPalette.SelectionForeground : UiPalette.Text,
|
||||
tab == _activeTab ? UiPalette.SelectionBackground : UiPalette.HeaderTabsBackground,
|
||||
tab == _activeTab ? Spectre.Console.Decoration.Bold : Spectre.Console.Decoration.None));
|
||||
|
||||
usedWidth += content.Length;
|
||||
}
|
||||
|
||||
var fillerWidth = Math.Max(availableTabsWidth - usedWidth, 0);
|
||||
if (fillerWidth > 0)
|
||||
{
|
||||
segments.Add(new HeaderSegment(
|
||||
FillBar(fillerWidth),
|
||||
UiPalette.Text,
|
||||
UiPalette.HeaderTabsBackground,
|
||||
Spectre.Console.Decoration.None));
|
||||
}
|
||||
|
||||
return [.. segments];
|
||||
}
|
||||
|
||||
private string GetHeaderProjectSegment() => WrapBarSegment(Localization.Current.ProjectTitle);
|
||||
|
||||
private string GetHelpLine(Tab tab, string hint) => $"{GetTabLabel(tab)}: {hint}";
|
||||
|
||||
private string GetStatusBarLeftText() => $"{Localization.Label} | {Localization.Current.HelpStatusHint}";
|
||||
|
||||
private string GetStatusBarRightText() => $"{Localization.Current.DashboardEndpointLabel}: {_mcpEndpoint}";
|
||||
|
||||
private string BuildStatusBar(int width)
|
||||
{
|
||||
var left = GetStatusBarLeftText();
|
||||
var right = GetStatusBarRightText();
|
||||
width = Math.Max(width, 1);
|
||||
|
||||
if (right.Length >= width)
|
||||
{
|
||||
return right[^width..];
|
||||
}
|
||||
|
||||
var leftWidth = Math.Max(width - right.Length - 1, 0);
|
||||
var fittedLeft = leftWidth == 0 ? string.Empty : PadRightVisible(FitInline(left, leftWidth), leftWidth);
|
||||
|
||||
return leftWidth == 0
|
||||
? PadLeftVisible(right, width)
|
||||
: $"{fittedLeft} {right}";
|
||||
}
|
||||
|
||||
private static string FillBar(int width) =>
|
||||
width <= 0 ? string.Empty : new string(Nbsp, width);
|
||||
|
||||
private static string WrapBarSegment(string text) => $"{Nbsp}{text}{Nbsp}";
|
||||
|
||||
private static string PadRightVisible(string text, int width)
|
||||
{
|
||||
if (width <= 0) return string.Empty;
|
||||
if (text.Length >= width) return text[..width];
|
||||
return text + FillBar(width - text.Length);
|
||||
}
|
||||
|
||||
private static string PadLeftVisible(string text, int width)
|
||||
{
|
||||
if (width <= 0) return string.Empty;
|
||||
if (text.Length >= width) return text[^width..];
|
||||
return FillBar(width - text.Length) + text;
|
||||
}
|
||||
|
||||
private static string FitInline(string text, int width)
|
||||
{
|
||||
if (width <= 0) return string.Empty;
|
||||
if (text.Length <= width) return text;
|
||||
if (width <= 3) return text[..width];
|
||||
return text[..(width - 3)] + "...";
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Registry.StateChanged -= OnRegistryChanged;
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
<Rows>
|
||||
<Markup Content="@Loc.LogsTitle" Foreground="@UiPalette.Text" Decoration="@Spectre.Console.Decoration.Bold" />
|
||||
<Markup Content="@Loc.LogsHint" Foreground="@UiPalette.TextMuted" />
|
||||
<Markup Content=" " />
|
||||
|
||||
<Columns>
|
||||
@@ -87,7 +86,7 @@
|
||||
? selected.Message
|
||||
: $"{selected.Message} | {selected.Exception}";
|
||||
|
||||
return Fit(details, Math.Max(Console.WindowWidth - 12, 32));
|
||||
return Fit(details, Math.Max(UiMetrics.ConsoleWidth - 12, 32));
|
||||
}
|
||||
|
||||
private string FormatEntry(int index)
|
||||
@@ -104,7 +103,7 @@
|
||||
};
|
||||
|
||||
var text = $"{entry.Timestamp:HH:mm:ss} {level,-3} {entry.ShortCategory,-18} {entry.Message}";
|
||||
return Fit(text, Math.Max(Console.WindowWidth - 12, 32));
|
||||
return Fit(text, Math.Max(UiMetrics.ConsoleWidth - 12, 32));
|
||||
}
|
||||
|
||||
private static string Fit(string text, int width)
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<Rows>
|
||||
<Markup Content="@Loc.OverviewTitle" Foreground="@UiPalette.Text" Decoration="@Spectre.Console.Decoration.Bold" />
|
||||
<Markup Content="@Loc.OverviewHint" Foreground="@UiPalette.TextMuted" />
|
||||
<Markup Content=" " />
|
||||
<Markup Content="@($"{Loc.DashboardEndpointLabel}: {Endpoint}")" Foreground="@UiPalette.Accent" />
|
||||
<Markup Content=" " />
|
||||
|
||||
@if (Rows.Count == 0)
|
||||
@@ -32,7 +29,6 @@
|
||||
[Parameter] public int SelectedIndex { get; set; }
|
||||
[Parameter] public EventCallback<int> SelectedIndexChanged { get; set; }
|
||||
[Parameter] public int ViewportRows { get; set; } = 3;
|
||||
[Parameter] public string Endpoint { get; set; } = "";
|
||||
[Parameter] public TuiResources Loc { get; set; } = TuiResources.En;
|
||||
|
||||
private int[] GetOptions() => Enumerable.Range(0, Rows.Count).ToArray();
|
||||
@@ -56,7 +52,7 @@
|
||||
var row = Rows[index];
|
||||
var status = row.IsModuleEnabled ? $"[{Loc.StateOn}] " : $"[{Loc.StateOff}]";
|
||||
var text = $"{row.ModuleName,-12} {status} {row.ConfiguredTools,2}/{row.TotalTools,-2} {row.Description}";
|
||||
return Fit(text, Math.Max(Console.WindowWidth - 12, 32));
|
||||
return Fit(text, Math.Max(UiMetrics.ConsoleWidth - 12, 32));
|
||||
}
|
||||
|
||||
private static string Fit(string text, int width)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<Rows>
|
||||
<Markup Content="@Loc.SettingsTitle" Foreground="@UiPalette.Text" Decoration="@Spectre.Console.Decoration.Bold" />
|
||||
<Markup Content="@Loc.SettingsHint" Foreground="@UiPalette.TextMuted" />
|
||||
<Markup Content=" " />
|
||||
|
||||
@if (Entries.Count == 0)
|
||||
@@ -67,7 +66,7 @@
|
||||
text = $"{indent}{checkbox} {entry.Label}{disabledSuffix}";
|
||||
}
|
||||
|
||||
return Fit(text, Math.Max(Console.WindowWidth - 12, 32));
|
||||
return Fit(text, Math.Max(UiMetrics.ConsoleWidth - 12, 32));
|
||||
}
|
||||
|
||||
private static string Fit(string text, int width)
|
||||
|
||||
@@ -7,10 +7,15 @@ namespace LazyBear.MCP.TUI.Localization;
|
||||
public sealed record TuiResources
|
||||
{
|
||||
// ── Подсказка и вкладки ──────────────────────────────────────────────────
|
||||
public string ProjectTitle { get; init; } = "";
|
||||
public string HintBar { get; init; } = "";
|
||||
public string TabOverview { get; init; } = "";
|
||||
public string TabLogs { get; init; } = "";
|
||||
public string TabSettings { get; init; } = "";
|
||||
public string HelpButtonLabel { get; init; } = "";
|
||||
public string HelpStatusHint { get; init; } = "";
|
||||
public string HelpModalTitle { get; init; } = "";
|
||||
public string HelpCloseHint { get; init; } = "";
|
||||
|
||||
// ── Dashboard ────────────────────────────────────────────────────────────
|
||||
public string OverviewTitle { get; init; } = "";
|
||||
@@ -44,10 +49,15 @@ public sealed record TuiResources
|
||||
|
||||
public static readonly TuiResources En = new()
|
||||
{
|
||||
HintBar = "Tab: tabs | Arrows: navigate | Space: toggle | Enter: open | L: language | Q: quit",
|
||||
ProjectTitle = "LazyBear MCP",
|
||||
HintBar = "Tab/Shift+Tab: switch tabs | H: help | L: language | Q: quit",
|
||||
TabOverview = "Dashboard",
|
||||
TabLogs = "Logs",
|
||||
TabSettings = "Settings",
|
||||
HelpButtonLabel = "Help",
|
||||
HelpStatusHint = "H: help",
|
||||
HelpModalTitle = "Keyboard Shortcuts",
|
||||
HelpCloseHint = "H / Esc: close",
|
||||
|
||||
OverviewTitle = "Dashboard",
|
||||
OverviewHint = "Up/Down: select module. Enter: open settings.",
|
||||
@@ -77,10 +87,15 @@ public sealed record TuiResources
|
||||
|
||||
public static readonly TuiResources Ru = new()
|
||||
{
|
||||
HintBar = "Tab: вкладки | Стрелки: навигация | Space: вкл/выкл | Enter: открыть | L: язык | Q: выход",
|
||||
ProjectTitle = "LazyBear MCP",
|
||||
HintBar = "Tab/Shift+Tab: вкладки | H: справка | L: язык | Q: выход",
|
||||
TabOverview = "Dashboard",
|
||||
TabLogs = "Логи",
|
||||
TabSettings = "Настройки",
|
||||
HelpButtonLabel = "Справка",
|
||||
HelpStatusHint = "H: справка",
|
||||
HelpModalTitle = "Горячие клавиши",
|
||||
HelpCloseHint = "H / Esc: закрыть",
|
||||
|
||||
OverviewTitle = "Dashboard",
|
||||
OverviewHint = "Вверх/вниз: выбор модуля. Enter: открыть настройки.",
|
||||
|
||||
43
LazyBear.MCP/TUI/UiMetrics.cs
Normal file
43
LazyBear.MCP/TUI/UiMetrics.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using Spectre.Console;
|
||||
|
||||
namespace LazyBear.MCP.TUI;
|
||||
|
||||
internal static class UiMetrics
|
||||
{
|
||||
public static int ConsoleWidth => Math.Max(ReadConsoleSize(ReadConsoleWidth, () => AnsiConsole.Profile.Width, 80), 1);
|
||||
public static int ConsoleHeight => Math.Max(ReadConsoleSize(ReadConsoleHeight, () => AnsiConsole.Profile.Height, 24), 1);
|
||||
|
||||
private static int ReadConsoleSize(Func<int> consoleReader, Func<int> profileReader, int fallback)
|
||||
{
|
||||
try
|
||||
{
|
||||
var consoleValue = consoleReader();
|
||||
if (consoleValue > 0)
|
||||
{
|
||||
return consoleValue;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Игнорируем и пробуем fallback через профиль Spectre.
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var profileValue = profileReader();
|
||||
if (profileValue > 0)
|
||||
{
|
||||
return profileValue;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Игнорируем и используем значение по умолчанию.
|
||||
}
|
||||
|
||||
return fallback;
|
||||
}
|
||||
|
||||
private static int ReadConsoleWidth() => Console.WindowWidth;
|
||||
private static int ReadConsoleHeight() => Console.WindowHeight;
|
||||
}
|
||||
@@ -8,6 +8,8 @@ internal static class UiPalette
|
||||
public static readonly Color Surface = new(12, 21, 34);
|
||||
public static readonly Color SurfaceAlt = new(18, 29, 44);
|
||||
public static readonly Color SurfaceMuted = new(28, 40, 56);
|
||||
public static readonly Color HeaderBrandBackground = new(9, 31, 47);
|
||||
public static readonly Color HeaderTabsBackground = SurfaceMuted;
|
||||
public static readonly Color Accent = Color.Cyan1;
|
||||
public static readonly Color AccentSoft = Color.DeepSkyBlue1;
|
||||
public static readonly Color Text = Color.Grey93;
|
||||
|
||||
77
LazyBear.MCP/logo.svg
Normal file
77
LazyBear.MCP/logo.svg
Normal file
@@ -0,0 +1,77 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" width="1024" height="1024">
|
||||
<defs>
|
||||
<linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0%" stop-color="#050816"/>
|
||||
<stop offset="100%" stop-color="#0E0A22"/>
|
||||
</linearGradient>
|
||||
|
||||
<linearGradient id="cyan" x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0%" stop-color="#F2FFFF"/>
|
||||
<stop offset="50%" stop-color="#3EE8FF"/>
|
||||
<stop offset="100%" stop-color="#0A8FE5"/>
|
||||
</linearGradient>
|
||||
|
||||
<linearGradient id="visor" x1="0" y1="0" x2="1" y2="0">
|
||||
<stop offset="0%" stop-color="#C7FFFF"/>
|
||||
<stop offset="100%" stop-color="#9B6CFF"/>
|
||||
</linearGradient>
|
||||
|
||||
<filter id="glow" x="-40%" y="-40%" width="180%" height="180%">
|
||||
<feGaussianBlur stdDeviation="8" result="b"/>
|
||||
<feMerge>
|
||||
<feMergeNode in="b"/>
|
||||
<feMergeNode in="SourceGraphic"/>
|
||||
</feMerge>
|
||||
</filter>
|
||||
</defs>
|
||||
|
||||
<!-- background -->
|
||||
<rect width="1024" height="1024" rx="220" fill="url(#bg)"/>
|
||||
|
||||
<!-- BIGGER BEAR (scaled + centered) -->
|
||||
<g transform="translate(512,560) scale(1.25) translate(-512,-560)">
|
||||
|
||||
<!-- ears -->
|
||||
<circle cx="360" cy="300" r="90" fill="#0A101B" stroke="#4EE7FF" stroke-width="14"/>
|
||||
<circle cx="664" cy="300" r="90" fill="#0A101B" stroke="#4EE7FF" stroke-width="14"/>
|
||||
|
||||
<!-- head -->
|
||||
<path d="M260 380
|
||||
C260 240 380 160 512 160
|
||||
C644 160 764 240 764 380
|
||||
C764 560 650 760 512 840
|
||||
C374 760 260 560 260 380Z"
|
||||
fill="#0A101B" stroke="#54E8FF" stroke-width="16"/>
|
||||
|
||||
<!-- face -->
|
||||
<path d="M340 430
|
||||
C360 330 430 290 512 290
|
||||
C594 290 664 330 684 430
|
||||
C694 500 660 610 590 670
|
||||
C550 700 474 700 434 670
|
||||
C364 610 330 500 340 430Z"
|
||||
fill="url(#cyan)"/>
|
||||
|
||||
<!-- visor -->
|
||||
<g filter="url(#glow)">
|
||||
<path d="M360 370
|
||||
C400 330 624 330 664 370
|
||||
L664 450
|
||||
C620 480 404 480 360 450Z"
|
||||
fill="#0B1528" stroke="url(#visor)" stroke-width="12"/>
|
||||
</g>
|
||||
|
||||
<!-- nose -->
|
||||
<ellipse cx="512" cy="500" rx="40" ry="26" fill="#0E121A"/>
|
||||
|
||||
<!-- simplified mouth for small sizes -->
|
||||
<path d="M440 580
|
||||
C470 610 554 610 584 580"
|
||||
fill="none" stroke="#FFFFFF" stroke-width="10" stroke-linecap="round"/>
|
||||
|
||||
<!-- teeth (simplified) -->
|
||||
<path d="M470 585 L490 615 L510 585 L530 615 L550 585"
|
||||
fill="none" stroke="#EFFFFF" stroke-width="8" stroke-linecap="round"/>
|
||||
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
Reference in New Issue
Block a user