// ============================================================
// SETTINGS
// ============================================================
var GROW = 10.0; // how far the glow extends beyond the card
var BLUR = 20.0; // glow blur amount
var BORDER = 2.0; // gradient border thickness (the plate behind the card)
var TURN = 10.0; // seconds per full turn (shared rotation)
var BREATH = 3.6; // seconds for one half of the breathing cycle (glow only)
var OP_MIN = 0.40; // glow opacity at the trough
var OP_MAX = 0.60; // glow opacity at the peak
var PULSE = 1.03; // max scale of the breathing pulse
// Palette for the glow and the border. The transparent stops (#00...) are the
// dark gaps that make the color read as a rotating light.
List<Color> Pal() => new List<Color>
{
Color.FromHex("#000F1117"),
Color.FromHex("#000F1117"),
Color.FromHex("#000F1117"),
Color.FromHex("#000F1117"),
Color.FromHex("#EB5011"),
Color.FromHex("#F62974"),
Color.FromHex("#FC14BF"),
Color.FromHex("#6366F1"),
Color.FromHex("#9132F4"),
Color.FromHex("#0A83FF"),
Color.FromHex("#000F1117"),
Color.FromHex("#000F1117"),
Color.FromHex("#000F1117"),
Color.FromHex("#000F1117"),
};
// Reduced palette for small pieces: 3 stops, ratios reaching 1 so it covers
// the full width (a diagonal Angle would cut it off at 71%).
SkiaGradient Accent3() => new SkiaGradient
{
Type = GradientType.Linear,
StartXRatio = 0, StartYRatio = 0,
EndXRatio = 1, EndYRatio = 0,
Colors = new List<Color>
{
Color.FromHex("#EB5011"),
Color.FromHex("#FC14BF"),
Color.FromHex("#0A83FF"),
},
};
// 2 stops on a diagonal, for the 8px dots
SkiaGradient Accent2() => new SkiaGradient
{
Type = GradientType.Linear,
StartXRatio = 0, StartYRatio = 0,
EndXRatio = 1, EndYRatio = 1,
Colors = new List<Color>
{
Color.FromHex("#EB5011"),
Color.FromHex("#FC14BF"),
},
};
// Feature row: color dot + text
SkiaLayout Feature(string text) => new SkiaLayout
{
Type = LayoutType.Row, Spacing = 12,
HorizontalOptions = LayoutOptions.Fill,
Children =
{
new SkiaShape
{
Type = ShapeType.Circle, WidthRequest = 8, HeightRequest = 8,
VerticalOptions = LayoutOptions.Center,
FillGradient = Accent2(),
},
new SkiaLabel(text) { FontSize = 14, TextColor = Color.FromHex("#CBD5E1"), VerticalOptions = LayoutOptions.Center },
}
};
// ============================================================
// GLOW: blurred conic gradient. Rotates + breathes.
// ============================================================
var glow = new SkiaShape
{
Type = ShapeType.Rectangle,
CornerRadius = 28 + GROW,
HorizontalOptions = LayoutOptions.Fill,
VerticalOptions = LayoutOptions.Fill,
Margin = new Thickness(-GROW), // grows outward from the card
ExpandDirtyRegion = new Thickness(BLUR * 3 + 24), // so the blur does not get clipped
Value2 = 360, // Sweep: full 360° span
FillGradient = new SkiaGradient
{
Type = GradientType.Sweep,
TileMode = SKShaderTileMode.Repeat, // KEY: with Clamp the rotation flattens out and jumps
Colors = Pal(),
},
VisualEffects = { new BlurEffect { Amount = BLUR } },
}
// BREATHING (here only): opacity + scale, infinite ping-pong
.AnimateOpacity(OP_MIN, OP_MAX, BREATH, repeat: -1, pingPong: true, easing: Easing.SinInOut)
.AnimateScale(1.0, PULSE, BREATH, repeat: -1, pingPong: true, easing: Easing.SinInOut);
// ============================================================
// BORDER: card behind that sticks out BORDER px. Same gradient
// rotating, no breathing. Uses FillGradient and not StrokeGradient
// because the stroke caches its shader and would freeze on frame 1.
// ============================================================
var border = new SkiaShape
{
Type = ShapeType.Rectangle,
CornerRadius = 28 + BORDER,
HorizontalOptions = LayoutOptions.Fill,
VerticalOptions = LayoutOptions.Fill,
Margin = new Thickness(-BORDER),
Value2 = 360,
FillGradient = new SkiaGradient
{
Type = GradientType.Sweep,
TileMode = SKShaderTileMode.Repeat,
Colors = Pal(),
},
};
// ============================================================
// THE CARD
// ============================================================
var card = new SkiaShape
{
UseCache = SkiaCacheType.Image,
Type = ShapeType.Rectangle,
CornerRadius = 28,
WidthRequest = 300,
BackgroundColor = Color.FromHex("#0F1117"),
StrokeWidth = 0,
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Center,
Children =
{
new SkiaLayout
{
Type = LayoutType.Column,
Spacing = 18,
Padding = new Thickness(28),
HorizontalOptions = LayoutOptions.Fill,
Children =
{
new SkiaShape
{
Type = ShapeType.Rectangle,
CornerRadius = 99,
HorizontalOptions = LayoutOptions.Start,
FillGradient = Accent3(),
Children =
{
new SkiaLabel("PRO PLAN")
{
FontSize = 11, FontAttributes = FontAttributes.Bold,
TextColor = Colors.White, Margin = new Thickness(14, 6),
},
}
},
new SkiaLayout
{
Type = LayoutType.Row, Spacing = 6,
HorizontalOptions = LayoutOptions.Start,
Children =
{
new SkiaLabel("$29")
{
FontSize = 56, FontAttributes = FontAttributes.Bold,
TextColor = Colors.White,
},
new SkiaLabel("/month")
{
FontSize = 15, TextColor = Color.FromHex("#8B93A7"),
VerticalOptions = LayoutOptions.End, Margin = new Thickness(0, 0, 0, 12),
},
}
},
new SkiaShape
{
Type = ShapeType.Rectangle, HeightRequest = 1, CornerRadius = 1,
HorizontalOptions = LayoutOptions.Fill,
BackgroundColor = Color.FromHex("#232833"),
},
Feature("Unlimited GPU-drawn views"),
Feature("60 fps buttery scrolling"),
Feature("Gestures, caching & shaders"),
new SkiaShape
{
UseCache = SkiaCacheType.Image,
Type = ShapeType.Rectangle,
HeightRequest = 52,
CornerRadius = 16,
HorizontalOptions = LayoutOptions.Fill,
BackgroundColor = Colors.White,
Children =
{
new SkiaLabel("Get started")
{
FontSize = 16, FontAttributes = FontAttributes.Bold, TextColor = Colors.Black,
HorizontalOptions = LayoutOptions.Center, VerticalOptions = LayoutOptions.Center,
},
}
}
.OnTapped(async me => { await me.ScaleToAsync(0.96, 0.96, 90); await me.ScaleToAsync(1, 1, 90); }),
}
}
}
};
// ============================================================
// Layers: glow -> border -> card.
// A SINGLE animator drives the rotation of both layers, so they
// can never drift out of phase with each other.
// ============================================================
return new SkiaLayout
{
Type = LayoutType.Absolute,
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Center,
Children = { glow, border, card }
}
.Animate(TURN, (me, a, v, dt) =>
{
var deg = v * 360;
glow.Value1 = deg;
border.Value1 = deg;
}, repeat: -1);