diff --git a/docs/docs/lottie-blazor.md b/docs/docs/lottie-blazor.md new file mode 100644 index 0000000000..60ba726ea7 --- /dev/null +++ b/docs/docs/lottie-blazor.md @@ -0,0 +1,232 @@ +# Blazor Lottie Animations + +Play Lottie animations in Blazor WebAssembly apps with [`SKLottieView`](xref:SkiaSharp.Extended.UI.Blazor.Controls.SKLottieView). This component wraps the shared [`SKLottiePlayer`](lottie-player.md) engine with a Blazor-friendly API — just point it at a JSON URL and it handles loading, frame updates, and rendering. + +For building custom animated canvases beyond Lottie, see [`SKAnimatedCanvasView`](#custom-animations-with-skanimatedcanvasview) below. + +## Quick Start + +### 1. Add the NuGet package + +```bash +dotnet add package SkiaSharp.Extended.UI.Blazor +``` + +### 2. Add the namespace + +In your `_Imports.razor`: + +```cshtml-razor +@using SkiaSharp.Extended.UI.Blazor.Controls +``` + +### 3. Add the component + +```cshtml-razor + +``` + +That's it — the animation loads from the URL and starts playing automatically. + +## Playback Controls + +### Repeat modes + +```cshtml-razor +@* Loop forever, restarting each time *@ + + +@* Ping-pong 3 times *@ + + +@* Play once *@ + +``` + +### Speed and direction + +```cshtml-razor + + +@* Negative speed plays in reverse *@ + +``` + +### Pause and resume + +```cshtml-razor + + + + +@code { + private bool isPlaying = true; +} +``` + +### Restart programmatically + +Use an `@ref` to call `Restart()`: + +```cshtml-razor + + + + +@code { + private SKLottieView? lottieView; +} +``` + +## Reading Playback State + +Access read-only state through the component reference: + +```cshtml-razor + + +@if (lottieView?.HasAnimation == true) +{ +

+ Duration: @lottieView.Duration.TotalSeconds.ToString("0.00")s | + Progress: @lottieView.Progress.TotalSeconds.ToString("0.00")s | + @(lottieView.IsComplete ? "Complete ✓" : "Playing") +

+} + +@code { + private SKLottieView? lottieView; +} +``` + +> **Tip:** Bind `AnimationUpdated` to `StateHasChanged` so the UI refreshes with each frame. Without this, the displayed progress won't update live. + +## Events + +| Event | Type | Description | +| :---- | :--- | :---------- | +| `AnimationLoaded` | `EventCallback` | The animation JSON was loaded and parsed successfully | +| `AnimationCompleted` | `EventCallback` | All repeats finished (never fires for infinite loops) | +| `AnimationFailed` | `EventCallback` | Loading or parsing failed | +| `AnimationUpdated` | `EventCallback` | Fires after each frame update | + +```cshtml-razor + + +@code { + private void OnLoaded() => Console.WriteLine("Loaded!"); + private void OnFailed(Exception? ex) => Console.WriteLine($"Failed: {ex?.Message}"); + private void OnCompleted() => Console.WriteLine("Done!"); +} +``` + +## Parameters Reference + +| Parameter | Type | Default | Description | +| :-------- | :--- | :------ | :---------- | +| `Source` | `string?` | `null` | URL of the Lottie JSON file to load | +| `RepeatMode` | `SKLottieRepeatMode` | `Restart` | `Restart` or `Reverse` (ping-pong) | +| `RepeatCount` | `int` | `-1` | Additional plays after the first (`0` = once, `-1` = infinite) | +| `AnimationSpeed` | `double` | `1.0` | Speed multiplier (negative = reverse) | +| `IsAnimationEnabled` | `bool` | `true` | Play/pause the animation loop | + +Additional HTML attributes (like `style` and `class`) are forwarded to the underlying canvas element. + +## Read-Only State (via `@ref`) + +| Property | Type | Description | +| :------- | :--- | :---------- | +| `IsLoading` | `bool` | Whether the animation is currently being fetched | +| `HasAnimation` | `bool` | Whether an animation is loaded and ready | +| `Duration` | `TimeSpan` | Total animation duration | +| `Progress` | `TimeSpan` | Current playback position | +| `IsComplete` | `bool` | Whether all repeats have finished | + +## Sizing + +The component renders an HTML `` element. Set the size using standard CSS: + +```cshtml-razor +@* Fixed size *@ + + +@* Fill parent container *@ +
+ +
+``` + +## Custom Animations with SKAnimatedCanvasView + +For animations beyond Lottie, [`SKAnimatedCanvasView`](xref:SkiaSharp.Extended.UI.Blazor.Controls.SKAnimatedCanvasView) provides a frame loop with a SkiaSharp canvas. It runs at ~60 fps and calls your update and paint callbacks each frame. + +```cshtml-razor +@using SkiaSharp +@using SkiaSharp.Extended.UI.Blazor.Controls + + + +@code { + private float angle = 0; + + private void HandleUpdate(TimeSpan delta) + { + angle += (float)(delta.TotalSeconds * 90); // 90° per second + } + + private void HandlePaint(SKPaintSurfaceEventArgs e) + { + var canvas = e.Surface.Canvas; + canvas.Clear(SKColors.White); + canvas.RotateDegrees(angle, e.Info.Width / 2f, e.Info.Height / 2f); + + using var paint = new SKPaint { Color = SKColors.CornflowerBlue }; + canvas.DrawRect( + e.Info.Width / 2f - 50, e.Info.Height / 2f - 50, + 100, 100, paint); + } +} +``` + +### SKAnimatedCanvasView Parameters + +| Parameter | Type | Default | Description | +| :-------- | :--- | :------ | :---------- | +| `IsAnimationEnabled` | `bool` | `true` | Start/stop the frame loop | +| `OnUpdate` | `EventCallback` | — | Called each frame with delta time | +| `OnPaintSurface` | `EventCallback` | — | Called to render each frame | + +> **Important:** `OnPaintSurface` handlers must be synchronous. The canvas is only valid during the paint callback. + +## Learn More + +- [Lottie Player](lottie-player.md) — The shared playback engine behind both MAUI and Blazor views +- [MAUI Lottie Animations](lottie-maui.md) — Use Lottie in .NET MAUI apps +- [Lottie by Airbnb](https://airbnb.design/lottie/) — Official project page +- [LottieFiles](https://lottiefiles.com/) — Free and premium animations +- [API Reference](xref:SkiaSharp.Extended.UI.Blazor.Controls.SKLottieView) — Full method documentation diff --git a/docs/docs/lottie.md b/docs/docs/lottie-maui.md similarity index 77% rename from docs/docs/lottie.md rename to docs/docs/lottie-maui.md index aa4c4a2072..ced1f7cd7d 100644 --- a/docs/docs/lottie.md +++ b/docs/docs/lottie-maui.md @@ -4,6 +4,8 @@ Lottie brings designer-created animations to your .NET MAUI apps. Instead of man ![Lottie animation preview][lottie-preview] +> **Other platforms:** This page covers .NET MAUI. For Blazor WebAssembly, see [Blazor Lottie Animations](lottie-blazor.md). For the shared playback engine, see [Lottie Player](lottie-player.md). + ## What is Lottie? [Lottie](https://airbnb.design/lottie/) is an animation format created by Airbnb. Animations are designed in Adobe After Effects, exported as JSON using the [Bodymovin](https://github.com/airbnb/lottie-web) plugin, and rendered natively on mobile and web. The name honors Lotte Reiniger, a pioneer of silhouette animation. @@ -61,6 +63,18 @@ lottieView.Source = new SKStreamLottieImageSource { Stream = myStream }; ``` +> **Under the hood:** The MAUI `SKLottieView` translates `RepeatCount` and `RepeatMode` into [`SKLottieRepeat`](xref:SkiaSharp.Extended.SKLottieRepeat) values on the shared [`SKLottiePlayer`](lottie-player.md). See the [Lottie Player](lottie-player.md) docs for details on repeat semantics. + +### Speed and direction + +```xml + + + + + +``` + ### Control playback programmatically ```csharp @@ -70,7 +84,7 @@ lottieView.IsAnimationEnabled = false; // Resume lottieView.IsAnimationEnabled = true; -// Jump to specific progress +// Jump to specific progress (also works while paused for scrubbing) lottieView.Progress = TimeSpan.FromSeconds(1.5); // Check if complete @@ -109,11 +123,12 @@ lottieView.AnimationCompleted += (s, e) => | :------- | :--- | :---------- | | `Source` | [`SKLottieImageSource`](xref:SkiaSharp.Extended.UI.Controls.SKLottieImageSource) | The Lottie JSON file to play | | `Duration` | `TimeSpan` | Total duration of the animation (read-only) | -| `Progress` | `TimeSpan` | Current playback position | +| `Progress` | `TimeSpan` | Current playback position (two-way bindable) | | `RepeatCount` | `int` | Times to repeat (0 = once, -1 = forever) | | `RepeatMode` | [`SKLottieRepeatMode`](xref:SkiaSharp.Extended.UI.Controls.SKLottieRepeatMode) | `Restart` or `Reverse` (ping-pong) | +| `AnimationSpeed` | `double` | Speed multiplier (negative = reverse). Default: `1.0` | | `IsAnimationEnabled` | `bool` | Play/pause the animation | -| `IsComplete` | `bool` | Whether playback has finished | +| `IsComplete` | `bool` | Whether playback has finished (read-only) | ## Where to Find Animations @@ -138,10 +153,12 @@ You can customize the rendering surface by overriding the control template: ``` -The `PART_DrawingSurface` name is required—it can be either `SKCanvasView` (software) or `SKGLView` (GPU). +The `PART_DrawingSurface` name is required — it can be either `SKCanvasView` (software) or `SKGLView` (GPU). ## Learn More +- [Lottie Player](lottie-player.md) — The shared playback engine behind MAUI and Blazor views +- [Blazor Lottie Animations](lottie-blazor.md) — Use Lottie in Blazor WebAssembly - [Lottie by Airbnb](https://airbnb.design/lottie/) — Official project page - [Lottie Documentation](https://airbnb.io/lottie/) — Format specification and guides - [LottieFiles](https://lottiefiles.com/) — Animation marketplace diff --git a/docs/docs/lottie-player.md b/docs/docs/lottie-player.md new file mode 100644 index 0000000000..e53dfb4ea4 --- /dev/null +++ b/docs/docs/lottie-player.md @@ -0,0 +1,162 @@ +# Lottie Player + +[`SKLottiePlayer`](xref:SkiaSharp.Extended.SKLottiePlayer) is the platform-agnostic engine that drives Lottie animation playback. It manages timing, repeat logic, and rendering — you just feed it frames. Both the [MAUI `SKLottieView`](lottie-maui.md) and the [Blazor `SKLottieView`](lottie-blazor.md) are built on top of this player. + +## When to Use the Player Directly + +Most apps should use the higher-level view components for [MAUI](lottie-maui.md) or [Blazor](lottie-blazor.md). Use `SKLottiePlayer` directly when you need: + +- A custom rendering host (WPF, Avalonia, console, tests) +- Full control over the frame loop +- Headless animation processing (e.g., exporting frames to images) + +## Quick Start + +```csharp +using SkiaSharp; +using SkiaSharp.Extended; +using SkiaSharp.Skottie; + +// 1. Create the player +var player = new SKLottiePlayer +{ + Repeat = SKLottieRepeat.Restart(), // loop forever + AnimationSpeed = 1.0 +}; + +// 2. Load a Lottie animation +var json = File.ReadAllText("animation.json"); +var animation = Animation.Parse(json); +player.SetAnimation(animation); + +// 3. On each frame tick, advance and render +player.Update(deltaTime); // deltaTime = time since last frame +player.Render(canvas, destRect); // draw current frame +``` + +## The Frame Loop + +`SKLottiePlayer` follows a simple two-step pattern on every frame: + +1. **`Update(deltaTime)`** — Advances the playback position by the elapsed time, applying speed, direction, and repeat logic. +2. **`Render(canvas, rect)`** — Draws the current frame onto the canvas within the given rectangle. + +```csharp +// Typical game/render loop integration +var lastTick = DateTime.UtcNow; + +void OnFrame() +{ + var now = DateTime.UtcNow; + var delta = now - lastTick; + lastTick = now; + + player.Update(delta); + canvas.Clear(SKColors.Transparent); + player.Render(canvas, SKRect.Create(0, 0, width, height)); +} +``` + +## Playback Speed + +The `AnimationSpeed` property multiplies the time delta on every frame: + +| Speed | Effect | +| :---: | :----- | +| `1.0` | Normal speed | +| `2.0` | Double speed | +| `0.5` | Half speed | +| `-1.0` | Play in reverse | +| `-2.0` | Double speed, reversed | + +```csharp +player.AnimationSpeed = -1.5; // 1.5× speed, playing backward +``` + +## Repeat Modes + +The [`SKLottieRepeat`](xref:SkiaSharp.Extended.SKLottieRepeat) struct controls what happens when the animation reaches a boundary: + +```csharp +// Play once, then stop +player.Repeat = SKLottieRepeat.Never; + +// Loop forever, restarting from the beginning each time +player.Repeat = SKLottieRepeat.Restart(); + +// Loop 3 additional times (4 total plays), restarting each time +player.Repeat = SKLottieRepeat.Restart(3); + +// Ping-pong forever (forward → backward → forward → …) +player.Repeat = SKLottieRepeat.Reverse(); + +// Ping-pong 2 additional times (3 total plays) +player.Repeat = SKLottieRepeat.Reverse(2); +``` + +You can change the repeat mode mid-playback. The player preserves the current direction until the animation hits its next boundary, preventing abrupt mid-animation direction changes. + +## Seeking + +Use `Seek` to jump to an absolute position (clamped to `[0, Duration]`): + +```csharp +// Jump to the 1-second mark +player.Seek(TimeSpan.FromSeconds(1.0)); + +// Jump to the beginning +player.Seek(TimeSpan.Zero); +``` + +> **Note:** `Seek` does not increment the internal repeat counter. Use `Update` for frame-by-frame playback with repeat/completion logic. + +## Events + +| Event | When It Fires | +| :---- | :------------ | +| `AnimationCompleted` | All repeats finished (never fires for infinite loops) | +| `AnimationUpdated` | After every `Seek` call, including those triggered by `Update` | + +```csharp +player.AnimationCompleted += (s, e) => +{ + Console.WriteLine("Animation finished!"); +}; + +player.AnimationUpdated += (s, e) => +{ + Console.WriteLine($"Progress: {player.Progress.TotalSeconds:0.00}s"); +}; +``` + +## Properties Reference + +| Property | Type | Description | +| :------- | :--- | :---------- | +| `Duration` | `TimeSpan` | Total animation duration (read-only, set when animation loads) | +| `Progress` | `TimeSpan` | Current playback position (read-only, advances via `Update`) | +| `IsComplete` | `bool` | Whether all repeats have finished | +| `HasAnimation` | `bool` | Whether an animation is currently loaded | +| `Repeat` | [`SKLottieRepeat`](xref:SkiaSharp.Extended.SKLottieRepeat) | How the animation repeats | +| `AnimationSpeed` | `double` | Playback speed multiplier (negative = reverse) | + +## Loading and Resetting + +```csharp +// Load a new animation (resets progress, completion, and repeat counters) +var animation = Animation.Parse(json); +player.SetAnimation(animation); + +// Clear the animation +player.SetAnimation(null); +``` + +The caller owns the `Skottie.Animation` instance and is responsible for disposing it when no longer needed. + +## Learn More + +- [MAUI Lottie Animations](lottie-maui.md) — Use Lottie in .NET MAUI with `SKLottieView` +- [Blazor Lottie Animations](lottie-blazor.md) — Use Lottie in Blazor WebAssembly +- [Lottie by Airbnb](https://airbnb.design/lottie/) — Official project page +- [LottieFiles](https://lottiefiles.com/) — Free and premium animations +- [API Reference](xref:SkiaSharp.Extended.SKLottiePlayer) — Full method documentation diff --git a/docs/docs/toc.yml b/docs/docs/toc.yml index 6fb3c47b97..655abca785 100644 --- a/docs/docs/toc.yml +++ b/docs/docs/toc.yml @@ -8,6 +8,8 @@ items: href: blurhash.md - name: Geometry Helpers href: geometry.md +- name: Lottie Player + href: lottie-player.md - name: Path Interpolation href: path-interpolation.md - name: Pixel Comparer @@ -17,7 +19,11 @@ items: - name: Confetti Effects href: confetti.md - name: Lottie Animations - href: lottie.md + href: lottie-maui.md + +- name: SkiaSharp.Extended.UI.Blazor +- name: Lottie Animations + href: lottie-blazor.md - name: Resources - name: Migration Guides diff --git a/docs/index.md b/docs/index.md index b5835c3fe6..d77a186351 100644 --- a/docs/index.md +++ b/docs/index.md @@ -62,7 +62,7 @@ Compare two images pixel by pixel, quantify differences, and generate visual dif Ready-to-use .NET MAUI controls for rich visual effects. -### [Lottie Animations](docs/lottie.md) +### [Lottie Animations](docs/lottie-maui.md) Play designer-created After Effects animations exported as Lottie JSON files. diff --git a/samples/SkiaSharpDemo.Blazor/Layout/NavMenu.razor b/samples/SkiaSharpDemo.Blazor/Layout/NavMenu.razor index cca6db98fe..711c7a26c5 100644 --- a/samples/SkiaSharpDemo.Blazor/Layout/NavMenu.razor +++ b/samples/SkiaSharpDemo.Blazor/Layout/NavMenu.razor @@ -32,11 +32,20 @@ BlurHash + + + + + diff --git a/samples/SkiaSharpDemo.Blazor/Layout/NavMenu.razor.css b/samples/SkiaSharpDemo.Blazor/Layout/NavMenu.razor.css index 95cc1cd77b..fbdbe6c40e 100644 --- a/samples/SkiaSharpDemo.Blazor/Layout/NavMenu.razor.css +++ b/samples/SkiaSharpDemo.Blazor/Layout/NavMenu.razor.css @@ -41,6 +41,10 @@ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' viewBox='0 0 16 16'%3E%3Cpath d='M16 8s-3-5.5-8-5.5S0 8 0 8s3 5.5 8 5.5S16 8 16 8zM1.173 8a13.133 13.133 0 0 1 1.66-2.043C4.12 4.668 5.88 3.5 8 3.5c2.12 0 3.879 1.168 5.168 2.457A13.133 13.133 0 0 1 14.828 8c-.058.087-.122.183-.195.288-.335.48-.83 1.12-1.465 1.755C11.879 11.332 10.119 12.5 8 12.5c-2.12 0-3.879-1.168-5.168-2.457A13.134 13.134 0 0 1 1.172 8z'/%3E%3Cpath d='M8 5.5a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5zM4.5 8a3.5 3.5 0 1 1 7 0 3.5 3.5 0 0 1-7 0z'/%3E%3C/svg%3E"); } +.bi-play-circle-nav-menu { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' viewBox='0 0 16 16'%3E%3Cpath d='M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z'/%3E%3Cpath d='M6.271 5.055a.5.5 0 0 1 .52.038l3.5 2.5a.5.5 0 0 1 0 .814l-3.5 2.5A.5.5 0 0 1 6 10.5v-5a.5.5 0 0 1 .271-.445z'/%3E%3C/svg%3E"); +} + .nav-group-header { font-size: 0.7rem; font-weight: 600; diff --git a/samples/SkiaSharpDemo.Blazor/Pages/Home.razor b/samples/SkiaSharpDemo.Blazor/Pages/Home.razor index 6744556e63..93f121d0b0 100644 --- a/samples/SkiaSharpDemo.Blazor/Pages/Home.razor +++ b/samples/SkiaSharpDemo.Blazor/Pages/Home.razor @@ -56,3 +56,18 @@ + +

CONTROLS

+ +
+
+
+
Lottie
+

+ Play Lottie animations using the SKLottieView Blazor + component, powered by the shared SKLottiePlayer engine. +

+ Open Demo +
+
+
diff --git a/samples/SkiaSharpDemo.Blazor/Pages/Lottie.razor b/samples/SkiaSharpDemo.Blazor/Pages/Lottie.razor new file mode 100644 index 0000000000..7d10f50bca --- /dev/null +++ b/samples/SkiaSharpDemo.Blazor/Pages/Lottie.razor @@ -0,0 +1,84 @@ +@page "/lottie" + +Lottie + +

Lottie Animation

+ +

+ Play Lottie animations using the SKLottieView Blazor component. + The same SKLottiePlayer logic is shared between this Blazor + component and the .NET MAUI SKLottieView. +

+ +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+ +
+
+ @if (lottieView?.IsLoading == true) + { +
+
+ Loading animation… +
+ } + else if (lottieView?.HasAnimation != true) + { + Failed to load animation. + } + else + { + + Duration: @lottieView.Duration.TotalSeconds.ToString("0.00")s  |  + Progress: @lottieView.Progress.TotalSeconds.ToString("0.00")s  |  + @(lottieView.IsComplete ? "Complete ✓" : "Playing") + + } +
+
+ +
+
+
+ +
+
+
+ +@code { + private SKLottieView? lottieView; + private SKLottieRepeatMode repeatMode = SKLottieRepeatMode.Restart; + private double animationSpeed = 1.0; + private int repeatCount = -1; +} diff --git a/samples/SkiaSharpDemo.Blazor/Pages/Lottie.razor.css b/samples/SkiaSharpDemo.Blazor/Pages/Lottie.razor.css new file mode 100644 index 0000000000..267f7cd3ed --- /dev/null +++ b/samples/SkiaSharpDemo.Blazor/Pages/Lottie.razor.css @@ -0,0 +1,11 @@ +.speed-slider { + width: 120px; +} + +.lottie-wrapper { + width: 400px; + height: 400px; + background: #f8f9fa; + border: 1px solid #dee2e6; + border-radius: 4px; +} diff --git a/samples/SkiaSharpDemo.Blazor/SkiaSharpDemo.Blazor.csproj b/samples/SkiaSharpDemo.Blazor/SkiaSharpDemo.Blazor.csproj index e22a8ae99e..b3f499746c 100644 --- a/samples/SkiaSharpDemo.Blazor/SkiaSharpDemo.Blazor.csproj +++ b/samples/SkiaSharpDemo.Blazor/SkiaSharpDemo.Blazor.csproj @@ -29,10 +29,12 @@ + + diff --git a/samples/SkiaSharpDemo.Blazor/_Imports.razor b/samples/SkiaSharpDemo.Blazor/_Imports.razor index 10f52e8c0e..0ae1ffada0 100644 --- a/samples/SkiaSharpDemo.Blazor/_Imports.razor +++ b/samples/SkiaSharpDemo.Blazor/_Imports.razor @@ -9,5 +9,6 @@ @using SkiaSharp @using SkiaSharp.Extended @using SkiaSharp.Views.Blazor +@using SkiaSharp.Extended.UI.Blazor.Controls @using SkiaSharpDemo.Blazor @using SkiaSharpDemo.Blazor.Layout diff --git a/samples/SkiaSharpDemo.Blazor/wwwroot/animations/trophy.json b/samples/SkiaSharpDemo.Blazor/wwwroot/animations/trophy.json new file mode 100644 index 0000000000..a4f54d8c74 --- /dev/null +++ b/samples/SkiaSharpDemo.Blazor/wwwroot/animations/trophy.json @@ -0,0 +1 @@ +{"v":"5.8.1","fr":30,"ip":0,"op":71,"w":500,"h":500,"nm":"Trophy","ddd":0,"assets":[{"id":"comp_0","nm":"Pre-comp 3","fr":30,"layers":[{"ddd":0,"ind":1,"ty":0,"nm":"Pre-comp 2","refId":"comp_1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[391.176,345.588,0],"ix":2,"l":2},"a":{"a":0,"k":[50,48.5,0],"ix":1,"l":2},"s":{"a":0,"k":[30,30,100],"ix":6,"l":2}},"ao":0,"w":100,"h":97,"ip":2,"op":17,"st":2,"bm":0},{"ddd":0,"ind":2,"ty":0,"nm":"Pre-comp 2","refId":"comp_1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[344.118,294.118,0],"ix":2,"l":2},"a":{"a":0,"k":[50,48.5,0],"ix":1,"l":2},"s":{"a":0,"k":[50,50,100],"ix":6,"l":2}},"ao":0,"w":100,"h":97,"ip":1,"op":16,"st":1,"bm":0},{"ddd":0,"ind":3,"ty":0,"nm":"Pre-comp 2","refId":"comp_1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[151.471,317.647,0],"ix":2,"l":2},"a":{"a":0,"k":[50,48.5,0],"ix":1,"l":2},"s":{"a":0,"k":[30,30,100],"ix":6,"l":2}},"ao":0,"w":100,"h":97,"ip":7,"op":22,"st":7,"bm":0},{"ddd":0,"ind":4,"ty":0,"nm":"Pre-comp 2","refId":"comp_1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[104.412,266.176,0],"ix":2,"l":2},"a":{"a":0,"k":[50,48.5,0],"ix":1,"l":2},"s":{"a":0,"k":[50,50,100],"ix":6,"l":2}},"ao":0,"w":100,"h":97,"ip":6,"op":21,"st":6,"bm":0},{"ddd":0,"ind":5,"ty":0,"nm":"Pre-comp 2","refId":"comp_1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[342.647,145.588,0],"ix":2,"l":2},"a":{"a":0,"k":[50,48.5,0],"ix":1,"l":2},"s":{"a":0,"k":[30,30,100],"ix":6,"l":2}},"ao":0,"w":100,"h":97,"ip":4,"op":19,"st":4,"bm":0},{"ddd":0,"ind":6,"ty":0,"nm":"Pre-comp 2","refId":"comp_1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[295.588,94.118,0],"ix":2,"l":2},"a":{"a":0,"k":[50,48.5,0],"ix":1,"l":2},"s":{"a":0,"k":[50,50,100],"ix":6,"l":2}},"ao":0,"w":100,"h":97,"ip":3,"op":18,"st":3,"bm":0},{"ddd":0,"ind":7,"ty":0,"nm":"Pre-comp 2","refId":"comp_1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[133.824,122.059,0],"ix":2,"l":2},"a":{"a":0,"k":[50,48.5,0],"ix":1,"l":2},"s":{"a":0,"k":[30,30,100],"ix":6,"l":2}},"ao":0,"w":100,"h":97,"ip":1,"op":16,"st":1,"bm":0},{"ddd":0,"ind":8,"ty":0,"nm":"Pre-comp 2","refId":"comp_1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[179.412,82.353,0],"ix":2,"l":2},"a":{"a":0,"k":[50,48.5,0],"ix":1,"l":2},"s":{"a":0,"k":[50,50,100],"ix":6,"l":2}},"ao":0,"w":100,"h":97,"ip":0,"op":15,"st":0,"bm":0}]},{"id":"comp_1","nm":"Pre-comp 2","fr":30,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 12","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[50.5,47,0],"ix":2,"l":2},"a":{"a":0,"k":[-142.5,-154,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-142.5,-154],[-101.5,-154]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.705882352941,0.247058838489,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":4,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":4,"s":[0]},{"t":14,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[0]},{"t":10,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":15,"st":-11,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 11","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":180,"ix":10},"p":{"a":0,"k":[50.5,47,0],"ix":2,"l":2},"a":{"a":0,"k":[-142.5,-154,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-142.5,-154],[-101.5,-154]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.705882352941,0.247058838489,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":4,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":4,"s":[0]},{"t":14,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[0]},{"t":10,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":15,"st":-11,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Shape Layer 10","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":90,"ix":10},"p":{"a":0,"k":[50.5,47,0],"ix":2,"l":2},"a":{"a":0,"k":[-142.5,-154,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-142.5,-154],[-101.5,-154]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.705882352941,0.247058838489,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":4,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":4,"s":[0]},{"t":14,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[0]},{"t":10,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":15,"st":-11,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Shape Layer 9","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[50.5,47,0],"ix":2,"l":2},"a":{"a":0,"k":[-142.5,-154,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-142.5,-154],[-101.5,-154]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.705882352941,0.247058838489,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":4,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":4,"s":[0]},{"t":14,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[0]},{"t":10,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":15,"st":-11,"bm":0}]},{"id":"comp_2","nm":"Pre-comp 1","fr":30,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 10","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[250,250,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[0,0],[178,0]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.705882352941,0.247058838489,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":4,"s":[60]},{"t":14,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[60]},{"t":10,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 11","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":30,"ix":10},"p":{"a":0,"k":[250,250,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[0,0],[178,0]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.705882352941,0.247058838489,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":4,"s":[60]},{"t":14,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[60]},{"t":10,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Shape Layer 12","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":60,"ix":10},"p":{"a":0,"k":[250,250,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[0,0],[178,0]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.705882352941,0.247058838489,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":4,"s":[60]},{"t":14,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[60]},{"t":10,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Shape Layer 13","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":90,"ix":10},"p":{"a":0,"k":[250,250,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[0,0],[178,0]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.705882352941,0.247058838489,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":4,"s":[60]},{"t":14,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[60]},{"t":10,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Shape Layer 14","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":120,"ix":10},"p":{"a":0,"k":[250,250,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[0,0],[178,0]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.705882352941,0.247058838489,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":4,"s":[60]},{"t":14,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[60]},{"t":10,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"Shape Layer 15","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":150,"ix":10},"p":{"a":0,"k":[250,250,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[0,0],[178,0]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.705882352941,0.247058838489,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":4,"s":[60]},{"t":14,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[60]},{"t":10,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"Shape Layer 16","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":180,"ix":10},"p":{"a":0,"k":[250,250,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[0,0],[178,0]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.705882352941,0.247058838489,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":4,"s":[60]},{"t":14,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[60]},{"t":10,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Shape Layer 17","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":210,"ix":10},"p":{"a":0,"k":[250,250,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[0,0],[178,0]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.705882352941,0.247058838489,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":4,"s":[60]},{"t":14,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[60]},{"t":10,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"Shape Layer 18","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":240,"ix":10},"p":{"a":0,"k":[250,250,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[0,0],[178,0]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.705882352941,0.247058838489,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":4,"s":[60]},{"t":14,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[60]},{"t":10,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"Shape Layer 19","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":270,"ix":10},"p":{"a":0,"k":[250,250,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[0,0],[178,0]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.705882352941,0.247058838489,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":4,"s":[60]},{"t":14,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[60]},{"t":10,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"Shape Layer 21","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":300,"ix":10},"p":{"a":0,"k":[250,250,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[0,0],[178,0]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.705882352941,0.247058838489,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":3,"s":[60]},{"t":13,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[60]},{"t":10,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"Shape Layer 20","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":330,"ix":10},"p":{"a":0,"k":[250,250,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[0,0],[178,0]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.705882352941,0.247058838489,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":3,"s":[60]},{"t":13,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[60]},{"t":10,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":300,"st":0,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":0,"nm":"Pre-comp 3","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[250,250,0],"ix":2,"l":2},"a":{"a":0,"k":[250,250,0],"ix":1,"l":2},"s":{"a":0,"k":[-100,100,100],"ix":6,"l":2}},"ao":0,"w":500,"h":500,"ip":39,"op":61,"st":39,"bm":0},{"ddd":0,"ind":2,"ty":0,"nm":"Pre-comp 3","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[250,250,0],"ix":2,"l":2},"a":{"a":0,"k":[250,250,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"w":500,"h":500,"ip":24,"op":46,"st":24,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Cup 3","parent":14,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.371,-98.838,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.8,"y":1},"o":{"x":0.333,"y":0},"t":10,"s":[{"i":[[-11.815,0],[0,0],[1.176,-11.756],[0,0],[5.492,54.916],[0,0]],"o":[[0,0],[11.815,0],[0,0],[-5.492,54.916],[0,0],[-1.176,-11.756]],"v":[[-49.8,-128.285],[49.3,-128.285],[70.626,-106.958],[62.096,-21.652],[-62.596,-21.652],[-71.126,-106.958]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.2,"y":0},"t":14,"s":[{"i":[[0,6.785],[0,0],[0,-11.667],[0,0],[0,55.777],[0,0]],"o":[[0,0],[0,8.035],[0,0],[0,54.652],[0,0],[0,-12.042]],"v":[[-0.25,-128.285],[-0.25,-128.285],[-0.25,-106.958],[-0.25,-21.652],[-0.25,-21.652],[-0.25,-106.958]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":18,"s":[{"i":[[-11.815,0],[0,0],[1.176,-11.756],[0,0],[5.492,54.916],[0,0]],"o":[[0,0],[11.815,0],[0,0],[-5.492,54.916],[0,0],[-1.176,-11.756]],"v":[[-49.8,-128.285],[49.3,-128.285],[70.626,-106.958],[62.096,-21.652],[-62.596,-21.652],[-71.126,-106.958]],"c":true}]},{"i":{"x":1,"y":1},"o":{"x":0.333,"y":0},"t":24,"s":[{"i":[[-11.815,0],[0,0],[1.176,-11.756],[0,0],[5.492,54.916],[0,0]],"o":[[0,0],[11.815,0],[0,0],[-5.492,54.916],[0,0],[-1.176,-11.756]],"v":[[-49.8,-128.285],[49.3,-128.285],[70.626,-106.958],[62.096,-21.652],[-62.596,-21.652],[-71.126,-106.958]],"c":true}]},{"i":{"x":0.223,"y":1},"o":{"x":0.2,"y":0},"t":31,"s":[{"i":[[0,6.785],[0,0],[0,-11.667],[0,0],[0,55.777],[0,0]],"o":[[0,0],[0,8.035],[0,0],[0,54.652],[0,0],[0,-12.042]],"v":[[-0.25,-128.285],[-0.25,-128.285],[-0.25,-106.958],[-0.25,-21.652],[-0.25,-21.652],[-0.25,-106.958]],"c":true}]},{"t":50,"s":[{"i":[[-11.815,0],[0,0],[1.176,-11.756],[0,0],[5.492,54.916],[0,0]],"o":[[0,0],[11.815,0],[0,0],[-5.492,54.916],[0,0],[-1.176,-11.756]],"v":[[-49.8,-128.285],[49.3,-128.285],[70.626,-106.958],[62.096,-21.652],[-62.596,-21.652],[-71.126,-106.958]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.705882370472,0.247058823705,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Cup","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":31,"op":310,"st":10,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Shape Layer 7","tt":2,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[250,250,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"t":24,"s":[{"i":[[3.191,-0.395],[2.304,-0.927],[2.095,-1.709],[1.788,-1.877],[0.908,-1.912],[-1.334,-6.312],[-2.779,-4.188],[-3.401,-3.602],[-3.548,-3.297],[-2.312,-2.352],[-2.506,-2.506],[-2.476,-2.535],[-0.232,-1.997],[0.723,-0.831],[0.267,-1.304],[-2.88,-0.857],[1.3,9.712],[4.203,4.76],[9.453,16.328],[-0.295,3.28],[-3.343,1.249],[-4.023,-0.951],[-1.8,-0.768],[-8.286,2.069],[-0.398,3.182],[3.129,3.445],[1.614,1.176],[1.189,0.657],[2.306,0.956],[2.086,0.582]],"o":[[-4.689,0.581],[-2.304,0.927],[-1.938,1.582],[-1.788,1.877],[-3.116,6.566],[1.334,6.312],[2.849,4.294],[3.401,3.602],[2.244,2.084],[2.312,2.352],[2.864,2.864],[2.476,2.535],[0.16,1.372],[-0.723,0.831],[-1.339,6.557],[13.183,3.921],[-1.018,-7.607],[-12.335,-13.97],[-1.509,-2.606],[0.413,-4.602],[3.955,-1.477],[2.275,0.538],[8.878,3.789],[3.458,-0.863],[0.467,-3.729],[-1.703,-1.875],[-1.654,-1.205],[-1.861,-1.028],[-2.371,-0.983],[-7.691,-2.147]],"v":[[-93,-111],[-102.945,-108.846],[-109,-105],[-114.773,-99.748],[-119,-94],[-120.921,-74.216],[-114,-58],[-104.525,-46.252],[-94,-36],[-87.197,-29.316],[-80,-22],[-71.526,-13.849],[-67,-7],[-68.18,-3.949],[-70,-1],[-64,12],[-47,-11],[-59,-30],[-99,-72],[-102,-81],[-94,-91],[-82,-91],[-75,-89],[-55,-79],[-48,-87],[-53,-98],[-58,-101],[-62,-105],[-69,-107],[-75,-110]],"c":true}],"h":1},{"t":25,"s":[{"i":[[6.363,-1.468],[2.979,-2.095],[1.84,-2.639],[0.408,-1.067],[0.408,-1.35],[0.465,-0.387],[0.082,-0.263],[-3.965,-7.542],[-4.029,-4.555],[-0.766,-0.479],[-0.438,-0.523],[-0.104,-0.568],[-0.27,-0.353],[-0.859,-0.529],[-0.842,-0.709],[-4.878,-5.799],[-0.092,-0.71],[0.419,-2.677],[-6.464,-0.238],[-1.53,2.112],[6.189,7.171],[5.82,5.82],[5.515,7.127],[-5.296,5.528],[-9.204,-2.345],[-3.834,-2.514],[-5.231,0.751],[-0.822,3.258],[5.25,2.566],[1.551,0.608]],"o":[[-4.243,0.979],[-2.98,2.096],[-0.962,1.38],[-0.408,1.067],[-0.057,0.189],[-0.465,0.387],[-2.889,9.276],[3.965,7.542],[0.497,0.561],[0.766,0.479],[0.313,0.374],[0.104,0.568],[1.053,1.378],[1.068,0.657],[6.494,5.469],[1.271,1.511],[0.356,2.738],[-1.191,7.598],[4.588,0.169],[8.605,-11.877],[-4.677,-5.419],[-6.151,-6.151],[-4.119,-5.322],[4.622,-4.825],[3.701,0.943],[4.525,2.967],[3.142,-0.451],[2.691,-10.661],[-1.826,-0.892],[-7.754,-3.037]],"v":[[-95,-111],[-105.802,-106.245],[-113,-99],[-114.915,-95.478],[-116,-92],[-116.981,-91.056],[-118,-90],[-114.689,-64.459],[-101,-46],[-98.956,-44.471],[-97,-43],[-96.468,-41.485],[-96,-40],[-92,-37],[-90,-35],[-71,-17],[-65,-8],[-68,-1],[-59,12],[-49,6],[-55,-29],[-72,-45],[-91,-66],[-97,-87],[-77,-91],[-66,-85],[-54,-79],[-46,-86],[-62,-106],[-67,-109]],"c":true}],"h":1},{"t":26,"s":[{"i":[[1.111,-0.113],[2.585,-1.009],[1.674,-1.559],[0.573,-0.084],[0.356,-0.336],[0.475,-0.926],[0.564,-0.79],[0.36,-0.281],[0.285,-0.493],[0.871,-2.718],[0.063,-2.618],[-3.733,-5.588],[-2.015,-2.521],[-0.872,-1.07],[-1.274,-1.411],[-7.648,-7.648],[-0.543,-5.007],[0.55,-2.542],[-2.362,-2.153],[-1.562,7.645],[2.913,4.566],[2.86,3.478],[9.12,11.123],[-1.11,7.282],[-2.157,0.726],[-4.835,-3.467],[-4.962,0.362],[-0.24,6.416],[7.573,3.176],[2.407,0.544]],"o":[[-3.987,0.405],[-2.585,1.009],[-0.352,0.328],[-0.573,0.084],[-0.536,0.507],[-0.475,0.926],[-0.273,0.382],[-0.36,0.281],[-1.452,2.508],[-0.871,2.718],[-0.253,10.508],[1.754,2.625],[0.961,1.203],[1.009,1.238],[6.895,7.635],[5.268,5.268],[0.253,2.337],[-0.918,4.241],[8.175,7.452],[1.806,-8.842],[-2.806,-4.398],[-8.695,-10.574],[-5.23,-6.378],[0.863,-5.666],[7.845,-2.641],[3.765,2.699],[4.671,-0.341],[0.327,-8.737],[-3.3,-1.384],[-5.338,-1.207]],"v":[[-85,-112],[-94.735,-109.865],[-101,-106],[-102.498,-105.506],[-104,-105],[-105.479,-102.712],[-107,-100],[-107.991,-99.083],[-109,-98],[-112.542,-90.083],[-114,-82],[-106,-58],[-100,-51],[-98,-47],[-94,-44],[-75,-23],[-63,-8],[-65,-1],[-63,9],[-43,-1],[-48,-23],[-58,-35],[-84,-62],[-94,-83],[-86,-92],[-64,-86],[-52,-79],[-43,-89],[-63,-108],[-72,-112]],"c":true}],"h":1},{"t":27,"s":[{"i":[[2.317,-0.535],[3.86,-4.04],[1.242,-4.613],[-2.12,-5.938],[-2.373,-3.37],[-0.492,-0.639],[-0.459,-0.632],[-0.8,-1.421],[-0.923,-1.114],[-0.951,-0.655],[-0.472,-0.507],[-0.081,-0.566],[-0.34,-0.374],[-1.019,-0.973],[-0.936,-1.02],[-0.997,-1.18],[-0.954,-1.127],[-0.458,-4.534],[0.591,-1.92],[-7.875,-0.144],[-0.943,9.517],[4.79,6.294],[7.791,10.593],[-2.16,8.125],[-1.994,0.531],[-4.781,-3.242],[-0.608,-0.61],[-0.949,-0.628],[-0.752,8.522],[12.133,3.114]],"o":[[-5.317,1.227],[-3.86,4.04],[-1.851,6.879],[2.12,5.938],[0.571,0.811],[0.492,0.639],[0.875,1.203],[0.8,1.421],[0.723,0.873],[0.951,0.655],[0.337,0.361],[0.081,0.566],[0.99,1.09],[1.018,0.973],[1.058,1.152],[0.997,1.18],[3.481,4.111],[0.271,2.68],[-2.255,7.329],[7.212,0.132],[1.112,-11.222],[-8.19,-10.762],[-4.476,-6.085],[0.814,-3.063],[5.149,-1.372],[0.641,0.434],[1.172,1.175],[7.025,4.649],[0.85,-9.635],[-5.404,-1.387]],"v":[[-82,-111],[-96.057,-102.54],[-104,-89],[-102.668,-69.368],[-95,-55],[-93.416,-52.866],[-92,-51],[-89.536,-46.934],[-87,-43],[-84.312,-40.725],[-82,-39],[-81.502,-37.509],[-81,-36],[-77.959,-32.947],[-75,-30],[-71.923,-26.481],[-69,-23],[-58,-8],[-60,-2],[-51,12],[-38,-5],[-48,-30],[-76,-62],[-84,-85],[-77,-92],[-60,-87],[-57,-85],[-55,-81],[-38,-88],[-65,-111]],"c":true}],"h":1},{"t":28,"s":[{"i":[[9.692,-1.405],[1.755,-0.653],[1.212,-0.989],[0.612,-0.799],[1.064,-1.588],[0.956,-1.834],[0.829,-2.651],[-1.471,-5.599],[-2.124,-4.18],[-2.534,-3.99],[-2.617,-3.613],[-1.064,-1.14],[-0.856,-1.216],[-1.667,-2.958],[-0.212,-2.155],[0.684,-2.633],[-1.441,-2.449],[-1.846,-1.035],[-3.412,0.49],[-1.764,4.555],[0.994,6.213],[1.271,2.573],[1.579,2.614],[5.214,6.995],[2.346,5.732],[-9.454,1.216],[-1.712,-1.097],[-10.861,3.613],[-0.352,2.926],[4.889,4.64]],"o":[[-2.767,0.401],[-1.755,0.653],[-1.489,1.215],[-0.612,0.799],[-1.348,2.013],[-0.956,1.834],[-2.008,6.423],[1.471,5.599],[2.249,4.426],[2.534,3.99],[0.888,1.226],[1.064,1.14],[2.12,3.012],[1.667,2.958],[0.297,3.021],[-0.684,2.633],[0.206,0.35],[1.846,1.035],[3.939,-0.566],[1.764,-4.555],[-0.335,-2.095],[-1.271,-2.573],[-3.799,-6.286],[-5.214,-6.995],[-3.248,-7.936],[1.752,-0.225],[5.75,3.685],[2.926,-0.973],[0.526,-4.371],[-6.865,-6.516]],"v":[[-63,-111],[-69.666,-109.441],[-74,-107],[-76.82,-104.28],[-79,-101],[-82.389,-95.478],[-85,-89],[-85.099,-70.817],[-79,-56],[-71.776,-43.39],[-64,-32],[-60.976,-28.493],[-58,-25],[-52.068,-15.857],[-49,-8],[-50.358,0.429],[-50,8],[-46.904,10.63],[-39,12],[-30.301,3.736],[-29,-13],[-31.567,-20.11],[-36,-28],[-50.589,-48.416],[-63,-68],[-59,-92],[-53,-89],[-34,-79],[-28,-87],[-36,-101]],"c":true}],"h":1},{"t":29,"s":[{"i":[[-2.683,7.317],[-0.869,-1.25],[-0.8,-0.658],[-1.031,-0.204],[-1.563,0.111],[-1.735,1.264],[-0.45,1.828],[3.728,4.807],[2.323,0.76],[3.865,-2.557],[1.585,-3.719],[-0.715,-8.537],[-2.363,-6.29],[-1.877,-4.04],[-1.844,-4.454],[-1.186,-3.102],[-0.134,-2.887],[0.496,-1.449],[0.053,-1.394],[-1.552,-2.05],[-2.93,-0.213],[-1.523,0.346],[-0.883,0.689],[-0.327,1.358],[-0.442,1.593],[1.215,6.113],[1.85,4.387],[0.684,1.501],[0.533,1.264],[2.41,7.866]],"o":[[1.239,1.98],[0.869,1.25],[0.8,0.658],[1.031,0.204],[1.61,-0.114],[1.735,-1.264],[1.234,-5.011],[-3.728,-4.807],[-5.956,-1.948],[-3.865,2.557],[-3.224,7.564],[0.715,8.537],[1.648,4.387],[1.877,4.04],[1.12,2.706],[1.186,3.102],[0.038,0.811],[-0.496,1.449],[-0.136,3.585],[1.552,2.05],[1.025,0.075],[1.523,-0.346],[1.249,-0.974],[0.327,-1.358],[1.71,-6.16],[-1.215,-6.113],[-0.73,-1.733],[-0.684,-1.501],[-3.044,-7.221],[-2.41,-7.866]],"v":[[-33,-87],[-29.914,-82.19],[-27.486,-79.364],[-24.816,-78.105],[-21,-78],[-15.63,-80.215],[-12,-85],[-17.332,-100.689],[-28,-110],[-42.778,-108.25],[-51,-98],[-54.191,-73.044],[-49,-50],[-43.647,-37.55],[-38,-25],[-34.26,-16.136],[-32,-7],[-32.932,-3.437],[-34,1],[-31.799,9.529],[-25,13],[-20.893,12.572],[-17,11],[-14.895,7.464],[-14,3],[-13.83,-15.829],[-19,-32],[-21.148,-36.851],[-23,-41],[-32.295,-63.928]],"c":true}],"h":1},{"t":30,"s":[{"i":[[3.333,-0.976],[1.133,-1.074],[0.545,-1.572],[0.264,-2.087],[0.291,-2.619],[-0.238,-6.751],[-0.732,-6.624],[-0.753,-6.186],[-0.301,-5.438],[0.011,-2.415],[-0.042,-2.238],[-0.282,-1.704],[-0.709,-0.814],[-1.027,-0.436],[-1.371,-0.226],[-1.409,0.198],[-1.139,0.835],[0.122,7.953],[0.958,8.662],[0.318,3.74],[0.375,3.844],[0.571,4.011],[-0.7,2.69],[-0.723,0.668],[-0.748,0.937],[-0.514,0.926],[-0.22,1.386],[0.663,2.516],[0.717,1.6],[2.195,1.193]],"o":[[-2.029,0.594],[-1.133,1.074],[-0.545,1.572],[-0.264,2.087],[-0.729,6.568],[0.238,6.751],[0.732,6.624],[0.753,6.186],[0.124,2.236],[-0.011,2.415],[0.042,2.238],[0.282,1.704],[0.377,0.433],[1.027,0.436],[1.371,0.226],[1.409,-0.198],[3.447,-2.528],[-0.122,-7.953],[-0.353,-3.197],[-0.318,-3.74],[-0.416,-4.266],[-0.571,-4.011],[0.361,-1.39],[0.723,-0.668],[0.726,-0.91],[0.514,-0.926],[0.37,-2.338],[-0.663,-2.516],[-1.885,-4.206],[-2.195,-1.193]],"v":[[-11,-109],[-15.667,-106.502],[-18.108,-102.537],[-19.245,-97.054],[-20,-90],[-20.619,-69.944],[-19.046,-49.805],[-16.7,-30.513],[-15,-13],[-14.877,-5.936],[-14.877,1.132],[-14.438,7.133],[-13,11],[-10.818,12.357],[-7.145,13.403],[-2.899,13.497],[1,12],[5.304,-4.9],[3,-31],[2.016,-41.514],[1,-53],[-0.837,-65.682],[-1,-76],[0.711,-78.84],[3,-81],[4.88,-83.643],[6,-87],[5.316,-94.554],[3,-101],[-2.914,-108.886]],"c":true}],"h":1},{"t":31,"s":[{"i":[[3.441,-0.647],[1.283,-0.953],[0.833,-1.422],[0.508,-1.758],[0.31,-1.96],[-0.087,-1.457],[-0.408,-1.039],[-0.617,-0.938],[-0.714,-1.153],[-0.141,-1.047],[0.131,-1.095],[0.206,-1.125],[0.086,-1.137],[0.415,-3.836],[0.451,-3.58],[0.436,-3.579],[0.369,-3.834],[0.163,-4.005],[-0.565,-3.399],[-1.82,-1.944],[-3.604,0.359],[-1.357,1.89],[-0.311,2.89],[0.076,3.305],[-0.195,3.135],[-0.864,6.178],[-0.881,7.07],[-0.327,7.107],[0.798,6.288],[2.455,2.453]],"o":[[-1.859,0.35],[-1.283,0.953],[-0.833,1.422],[-0.508,1.758],[-0.346,2.191],[0.087,1.457],[0.408,1.039],[0.617,0.938],[0.608,0.981],[0.141,1.047],[-0.131,1.095],[-0.206,1.125],[-0.328,4.346],[-0.415,3.836],[-0.451,3.58],[-0.436,3.579],[-0.363,3.764],[-0.163,4.005],[0.565,3.399],[1.82,1.944],[3.06,-0.305],[1.357,-1.89],[0.311,-2.89],[-0.076,-3.305],[0.275,-4.432],[0.864,-6.178],[0.881,-7.07],[0.327,-7.107],[-0.741,-5.836],[-2.455,-2.453]],"v":[[11,-109],[6.318,-107.013],[3.176,-103.416],[1.196,-98.612],[0,-93],[-0.361,-87.608],[0.409,-83.943],[1.975,-81.057],[4,-78],[5.074,-74.953],[5.041,-71.735],[4.487,-68.399],[4,-65],[2.874,-52.791],[1.563,-41.731],[0.22,-31.056],[-1,-20],[-1.921,-8.134],[-1.45,3.184],[1.995,11.41],[10,14],[16.461,10.561],[18.798,3.245],[18.986,-6.194],[19,-16],[20.851,-32.129],[23.61,-52.216],[25.564,-73.694],[25,-94],[20.025,-106.362]],"c":true}],"h":1},{"t":32,"s":[{"i":[[10.012,-0.857],[0.663,-0.066],[0.696,-0.133],[0.685,-0.25],[0.629,-0.419],[1.161,-1.785],[0.997,-2.24],[0.588,-2.086],[-0.068,-1.321],[-1.507,-1.219],[-0.877,-0.316],[-1.658,-0.093],[-0.11,-0.173],[0.768,-3.552],[0.803,-3.414],[1.144,-4.168],[1.192,-4.1],[1.234,-4.969],[-0.056,-4.277],[-2.293,-3.72],[-5.767,1.698],[-1.257,0.847],[-0.419,0.742],[0.306,2.867],[-0.16,2.647],[-0.757,2.689],[-0.84,3.026],[-1.59,5.162],[-1.1,4.811],[1.605,11.555]],"o":[[-0.585,0.05],[-0.663,0.066],[-0.696,0.133],[-0.685,0.25],[-1.079,0.718],[-1.161,1.785],[-0.997,2.24],[-0.588,2.086],[0.093,1.806],[1.507,1.219],[1.258,0.453],[1.658,0.093],[0.876,1.379],[-0.768,3.552],[-1.23,5.23],[-1.144,4.168],[-1.119,3.847],[-1.234,4.969],[0.062,4.817],[2.293,3.72],[-0.445,0.131],[1.257,-0.847],[1.063,-1.884],[-0.306,-2.867],[0.134,-2.219],[0.757,-2.689],[1.538,-5.541],[1.59,-5.162],[2.433,-10.639],[-1.605,-11.555]],"v":[[27,-109],[25.116,-108.839],[23.066,-108.553],[20.982,-107.991],[19,-107],[15.579,-103.093],[12.28,-96.903],[9.841,-90.262],[9,-85],[11.912,-80.383],[16,-78],[20.861,-77.29],[24,-77],[23.76,-69.026],[21,-58],[17.471,-44.153],[14,-32],[10.119,-18.323],[8,-4],[11.222,9.887],[23,14],[24.852,12.655],[28,10],[28.678,2.572],[28,-6],[29.471,-13.395],[32,-22],[36.828,-38.047],[41,-53],[43.334,-89.622]],"c":true}],"h":1},{"t":33,"s":[{"i":[[-1.692,-0.766],[1.471,-6.219],[2.553,-5.943],[1.166,-2.647],[1.155,-2.664],[1.283,-2.686],[0.627,-2.395],[-0.895,-5.6],[-2.8,-1.331],[-2.452,0.856],[-0.736,1.221],[0.438,2.954],[-0.248,2.689],[-1.182,2.599],[-0.96,2.214],[-0.473,1.359],[-0.575,1.331],[-0.379,0.478],[-0.227,0.489],[-0.098,0.91],[-0.285,0.66],[-1.26,2.459],[-0.88,2.66],[-0.699,3.206],[-0.248,3.282],[3.214,7.139],[7.793,-0.124],[1.212,-0.836],[-16.354,-1.393],[-1.724,3.235]],"o":[[1.434,6.183],[-1.471,6.218],[-1.18,2.747],[-1.166,2.647],[-1.24,2.86],[-1.283,2.686],[-1.379,5.271],[0.895,5.6],[3.456,1.643],[2.452,-0.856],[1.506,-2.497],[-0.438,-2.954],[0.312,-3.39],[1.182,-2.599],[0.535,-1.233],[0.473,-1.359],[0.258,-0.599],[0.379,-0.478],[0.325,-0.701],[0.098,-0.91],[1.079,-2.504],[1.26,-2.459],[0.958,-2.895],[0.698,-3.206],[0.647,-8.57],[-3.214,-7.139],[-2.471,0.04],[-7.887,5.438],[7.4,0.63],[0.183,-0.343]],"v":[[38,-85],[37.49,-66.32],[31,-48],[27.481,-39.938],[24,-32],[20.04,-23.651],[17,-16],[16.366,1.455],[22,13],[31.04,13.648],[36,10],[36.944,1.644],[36,-7],[38.514,-15.882],[42,-23],[43.47,-26.927],[45,-31],[46.023,-32.582],[47,-34],[47.53,-36.53],[48,-39],[51.649,-46.383],[55,-54],[57.532,-63.209],[59,-73],[55.33,-98.02],[39,-110],[28,-106],[26,-77],[36,-83]],"c":true}],"h":1},{"t":34,"s":[{"i":[[14.095,-1.272],[0.937,-0.322],[0.989,-0.507],[0.677,-0.062],[0.614,-0.406],[1.107,-1.421],[0.922,-1.224],[0.408,-0.14],[0.201,-0.216],[0.756,-2.55],[-0.329,-1.4],[-2.06,-1.454],[-3.378,0.441],[-2.23,3.005],[-1.843,-1.444],[-0.405,-1.932],[-0.025,-1.995],[0.392,-1.914],[0.456,-1.6],[2.132,-3.599],[2.472,-4.571],[0.616,-1.544],[0.771,-1.406],[0.656,-1.147],[0.679,-1.473],[-14.013,-1.683],[-0.906,4.713],[-0.286,3.517],[-3.246,5.959],[-1.098,19.932]],"o":[[-2.264,0.204],[-0.937,0.322],[-0.678,0.347],[-0.677,0.062],[-1.651,1.092],[-1.107,1.421],[-0.169,0.224],[-0.407,0.14],[-1.305,1.401],[-0.756,2.55],[0.382,1.624],[2.06,1.454],[3.235,-0.423],[2.23,-3.005],[0.353,0.276],[0.405,1.932],[0.022,1.745],[-0.392,1.914],[-2.002,7.031],[-2.132,3.6],[-0.767,1.416],[-0.616,1.544],[-0.631,1.151],[-0.757,1.324],[-5.008,10.858],[6.28,0.754],[0.78,-4.06],[0.405,-4.988],[8.39,-15.404],[1.187,-21.552]],"v":[[47,-109],[42.544,-108.226],[40,-107],[37.952,-106.544],[36,-106],[31.953,-102.099],[29,-98],[28.025,-97.494],[27,-97],[23.774,-90.499],[23,-84],[26.753,-78.951],[35,-77],[43.044,-83.9],[49,-88],[50.246,-84.288],[51,-78],[50.358,-72.392],[49,-67],[42.853,-52.155],[36,-41],[34.003,-36.492],[32,-32],[29,-29],[27,-24],[32,14],[44,6],[42,-6],[49,-23],[71,-75]],"c":true}],"h":1},{"t":35,"s":[{"i":[[8.043,-0.886],[1.416,-0.578],[2.278,-1.352],[1.236,-0.627],[0.831,-0.763],[0.311,-0.547],[0.347,-0.438],[0.627,-2.982],[-3.135,-2.767],[-3.115,2.252],[-2.844,2.136],[-1.146,-0.796],[-0.7,-3.858],[2.241,-5.103],[2.163,-3.381],[0.336,-0.745],[0.319,-0.495],[4.168,-7.075],[0.392,-6.585],[-1.84,-4.18],[-4.893,0],[-1.309,1.377],[-0.485,1.491],[0.519,2.006],[-0.269,2.498],[-1.51,2.797],[-1.745,2.835],[-3.084,4.604],[-2.081,10.716],[5.726,6.344]],"o":[[-3.056,0.337],[-1.416,0.578],[-1.225,0.727],[-1.236,0.627],[-0.408,0.374],[-0.311,0.547],[-2.626,3.307],[-0.627,2.981],[4.694,4.144],[3.115,-2.252],[2.716,-2.041],[1.146,0.796],[1.126,6.206],[-2.241,5.103],[-0.337,0.526],[-0.336,0.745],[-3.936,6.116],[-4.168,7.075],[-0.255,4.279],[1.841,4.18],[3.279,0],[1.309,-1.377],[0.806,-2.478],[-0.519,-2.006],[0.213,-1.978],[1.511,-2.797],[3.564,-5.791],[5.859,-8.748],[2.529,-13.025],[-5.149,-5.706]],"v":[[54,-109],[47.916,-107.762],[43,-105],[39.204,-103.026],[36,-101],[34.955,-99.548],[34,-98],[28.68,-88.595],[32,-80],[43.387,-78.79],[52,-87],[57.513,-88.924],[60,-82],[57.466,-64.882],[50,-52],[48.986,-49.976],[48,-48],[34.842,-27.852],[27,-7],[29.139,6.709],[39,14],[45.596,11.618],[48,7],[47.903,0.515],[47,-6],[49.851,-13.357],[55,-22],[67,-40],[80,-70],[72,-102]],"c":true}],"h":1},{"t":36,"s":[{"i":[[6.95,-0.74],[2.905,-1.098],[1.338,-1.282],[2.561,-3.354],[-1.377,-4.54],[-1.524,-1.099],[-2.451,-0.109],[-1.063,0.784],[-0.925,0.989],[-2.672,1.205],[-0.427,0.231],[-1.115,-0.377],[-0.221,-2.138],[2.655,-4.292],[0.852,-1.278],[0.353,-0.47],[0.554,-0.76],[0.376,-0.501],[0.552,-0.759],[1.638,-2.261],[2.119,-8.747],[-11.571,0.349],[0.102,-0.212],[-0.6,5.533],[-3.865,5.614],[-3.36,5.017],[-2.959,7.022],[-0.68,2.933],[8.858,3.993],[0.432,0.229]],"o":[[-3.041,0.324],[-2.905,1.098],[-2.132,2.042],[-2.561,3.354],[0.454,1.496],[1.524,1.099],[2.821,0.126],[1.064,-0.784],[2.484,-2.655],[0.357,-0.161],[1.608,-0.869],[1.776,0.601],[0.807,7.818],[-0.965,1.561],[-0.298,0.447],[-0.636,0.848],[-0.344,0.472],[-0.635,0.846],[-1.758,2.419],[-5.704,7.876],[-2.125,8.771],[5.64,-0.17],[2.416,-5.024],[0.401,-3.692],[4.493,-6.525],[4.477,-6.685],[1.175,-2.788],[3.523,-15.198],[-0.36,-0.162],[-3.543,-1.875]],"v":[[60,-109],[50.723,-106.719],[44,-103],[35.868,-94.873],[33,-83],[36.002,-78.96],[42,-77],[47.422,-78.164],[50,-81],[56,-86],[57,-88],[64,-90],[69,-80],[60,-57],[58,-52],[56,-51],[55,-48],[53,-47],[52,-44],[46,-37],[32,-11],[44,14],[53,9],[51,-6],[60,-21],[73,-39],[84,-59],[88,-68],[76,-105],[75,-107]],"c":true}],"h":1},{"t":37,"s":[{"i":[[6.233,-0.744],[3.673,-1.795],[2.536,-2.251],[1.463,-2.357],[-0.21,-2.367],[-1.895,-1.574],[-2.184,-0.066],[-3.996,3.091],[-3.467,-1.045],[-0.259,-4.087],[3.554,-5.557],[3.616,-4.531],[1.087,-12.139],[-9.903,1.954],[-0.682,3.684],[-0.71,4.488],[-0.964,1.734],[-0.939,1.446],[-0.345,0.461],[-0.554,0.76],[-0.376,0.501],[-0.596,0.775],[-1.455,2.193],[-0.623,0.94],[-0.623,0.94],[-0.506,0.737],[-0.962,1.445],[-0.981,1.538],[0.282,9.538],[7.628,4.07]],"o":[[-4.773,0.57],[-3.673,1.795],[-1.358,1.206],[-1.463,2.357],[0.234,2.637],[1.896,1.574],[6.258,0.189],[4.147,-3.208],[2.255,0.68],[0.397,6.272],[-3.899,6.097],[-9.421,11.805],[-1.145,12.792],[3.806,-0.751],[0.803,-4.341],[0.086,-0.546],[0.913,-1.643],[0.285,-0.438],[0.636,-0.848],[0.344,-0.472],[0.653,-0.87],[2.166,-2.819],[1.103,-1.662],[1.103,-1.662],[0.595,-0.897],[0.809,-1.177],[0.873,-1.312],[4.282,-6.717],[-0.374,-12.63],[-4.306,-2.297]],"v":[[65,-109],[52.323,-105.261],[43,-99],[38.324,-93.371],[36,-86],[39.537,-79.572],[46,-77],[57,-84],[70,-90],[76,-79],[68,-59],[56,-43],[35,-8],[50,14],[57,6],[55,-7],[58,-11],[60,-16],[62,-17],[63,-20],[65,-21],[66,-24],[74,-33],[77,-37],[80,-41],[82,-43],[84,-48],[87,-52],[96,-79],[81,-107]],"c":true}],"h":1},{"t":38,"s":[{"i":[[1.647,-0.211],[3.205,-1.278],[3.453,-2.54],[2.196,-2.853],[-1.45,-4.627],[-1.536,-1.121],[-2.416,-0.112],[-2.608,2.496],[-2.576,1.201],[-2.905,-1.487],[-0.312,-4.948],[1.855,-3.591],[1.77,-2.606],[2.135,-2.77],[2.131,-2.486],[4.115,-5.865],[0,-7.076],[-9.381,0.969],[0.945,4.244],[-0.331,2.895],[-2.557,3.213],[-1.725,2.071],[-2.393,3.051],[-0.908,1.137],[-1.532,2.219],[-0.672,1.075],[-1.198,3.937],[3.55,7.75],[6.098,3.388],[1.056,0.329]],"o":[[-2.727,0.349],[-3.205,1.278],[-2.665,1.96],[-2.196,2.853],[0.439,1.403],[1.536,1.121],[3.993,0.185],[2.608,-2.496],[4.069,-1.897],[2.905,1.487],[0.191,3.028],[-1.855,3.591],[-2.331,3.432],[-2.135,2.77],[-5.541,6.463],[-4.115,5.865],[0,10.245],[8.784,-0.907],[-0.459,-2.063],[0.63,-5.51],[1.77,-2.224],[3.326,-3.993],[0.909,-1.159],[1.718,-2.153],[0.596,-0.863],[2.908,-4.652],[3.053,-10.033],[-3.703,-8.083],[-0.968,-0.538],[-4.187,-1.303]],"v":[[69,-109],[60.044,-106.643],[50,-101],[41.914,-94.001],[40,-83],[43.017,-79.032],[49,-77],[58.562,-81.461],[66,-88],[76.817,-88.634],[82,-79],[78.971,-68.684],[73,-59],[66.35,-49.791],[60,-42],[44.844,-23.959],[38,-5],[52,14],[60,1],[58,-6],[68,-20],[73,-27],[82,-37],[84,-41],[90,-47],[92,-51],[100,-66],[98,-93],[86,-106],[82,-109]],"c":true}],"h":1},{"t":39,"s":[{"i":[[6.89,-0.815],[3.45,-1.257],[3.629,-2.669],[2.123,-2.867],[-1.331,-4.445],[-1.321,-1.204],[-2.45,-0.294],[-2.437,1.703],[-1.607,-13.824],[3.698,-5.29],[0.365,-0.487],[0.611,-0.78],[0.417,-0.448],[0.686,-0.803],[2.043,-2.273],[2.527,-10.638],[-10.055,1.039],[-0.351,0.734],[-0.86,5.47],[-1.778,2.471],[-3.623,4.033],[-1.799,1.999],[-1.727,2.054],[-0.83,1.103],[-0.403,0.433],[-0.679,0.936],[-0.711,1.062],[0.53,11.664],[4.486,4.612],[1.94,1.026]],"o":[[-2.573,0.304],[-3.45,1.257],[-2.84,2.088],[-2.123,2.867],[0.267,0.889],[1.321,1.204],[6.346,0.762],[8.352,-5.838],[0.651,5.598],[-0.322,0.461],[-0.659,0.878],[-0.361,0.46],[-0.697,0.749],[-2.476,2.9],[-10.39,11.564],[-3.517,14.807],[4.645,-0.48],[2.721,-5.69],[0.214,-1.358],[3.702,-5.144],[2.269,-2.525],[1.844,-2.05],[0.988,-1.175],[0.337,-0.448],[0.745,-0.8],[0.829,-1.142],[4.953,-7.395],[-0.454,-9.995],[-2.485,-2.555],[-5.035,-2.663]],"v":[[73,-109],[63.792,-106.773],[53,-101],[44.872,-93.767],[43,-83],[45.362,-79.554],[51,-77],[64,-84],[87,-80],[79,-60],[77,-59],[76,-56],[74,-55],[73,-52],[66,-45],[42,-12],[55,14],[63,9],[61,-7],[66,-14],[77,-27],[83,-34],[89,-40],[91,-44],[93,-45],[94,-48],[97,-51],[107,-80],[97,-101],[91,-107]],"c":true}],"h":1},{"t":40,"s":[{"i":[[4.56,-0.479],[2.024,-0.262],[1.95,-0.748],[0.607,-0.565],[0.799,-0.444],[1.906,-1.337],[1.585,-2.101],[0.779,-1.504],[-0.362,-2.331],[-4.248,-0.316],[-3.926,2.486],[-1.573,-12.837],[4.023,-5.229],[6.173,-6.924],[2.693,-4.652],[0.019,-5.058],[-8.53,1.272],[-0.963,3.04],[0.206,2.491],[-0.396,3.016],[-1.573,2.228],[-3.55,3.963],[-1.701,1.835],[-3.405,4.389],[-0.712,0.971],[-1.534,2.568],[-1.537,4.72],[-0.22,2.206],[6.333,4.201],[0.665,0.404]],"o":[[-1.954,0.206],[-2.024,0.262],[-0.772,0.296],[-0.607,0.565],[-2.79,1.549],[-1.906,1.337],[-1.247,1.653],[-0.779,1.504],[0.802,5.157],[6.652,0.495],[9.988,-6.325],[0.794,6.479],[-5.783,7.516],[-4.146,4.65],[-2.608,4.505],[-0.039,10.342],[5.007,-0.747],[0.615,-1.941],[-0.203,-2.447],[0.19,-1.452],[3.567,-5.052],[2.286,-2.552],[3.581,-3.864],[0.608,-0.783],[2.013,-2.745],[2.329,-3.899],[0.731,-2.245],[1.388,-13.905],[-0.823,-0.546],[-5.657,-3.436]],"v":[[77,-109],[70.996,-108.407],[65,-107],[63.02,-105.611],[61,-104],[54.097,-99.914],[49,-95],[45.793,-90.508],[45,-85],[54,-77],[67,-85],[91,-80],[82,-59],[60,-35],[48,-20],[43,-5],[58,14],[65,7],[66,2],[63,-6],[68,-13],[79,-26],[85,-33],[97,-45],[99,-49],[104,-56],[109,-67],[111,-74],[99,-104],[97,-106]],"c":true}],"h":1},{"t":41,"s":[{"i":[[5.891,-0.619],[7.054,-4.41],[-0.705,-7.194],[-5.031,-0.092],[-2.824,1.828],[-0.657,0.394],[-2.013,0.636],[-2.125,-0.552],[-0.418,-5.349],[2.56,-3.348],[0.67,-0.82],[2.544,-2.78],[1.253,-1.437],[1.207,-1.257],[0,-14.229],[-8.933,0.442],[-0.698,1.155],[0.579,3.606],[-0.339,2.536],[-0.446,0.479],[-0.686,0.877],[-0.919,0.987],[-0.682,0.766],[-5.785,6.618],[-0.808,0.985],[-0.432,0.464],[-0.687,0.872],[-1.654,2.521],[-1.364,6.519],[8.91,4.888]],"o":[[-6.357,0.668],[-5.62,3.514],[0.498,5.084],[5.931,0.109],[0.694,-0.449],[1.764,-1.058],[2.338,-0.739],[2.344,0.608],[0.506,6.467],[-0.612,0.801],[-2.183,2.67],[-1.231,1.345],[-1.246,1.429],[-12.272,12.774],[0,8.527],[2.795,-0.138],[0.685,-1.133],[-0.352,-2.197],[0.226,-1.696],[0.726,-0.779],[1.372,-1.753],[0.681,-0.732],[7.305,-8.207],[0.724,-0.829],[0.388,-0.473],[0.724,-0.777],[1.893,-2.404],[3.54,-5.395],[3.454,-16.502],[-6.286,-3.448]],"v":[[80,-109],[59,-102],[47,-86],[57,-77],[68,-84],[70,-85],[77,-89],[87,-90],[95,-79],[86,-61],[85,-58],[77,-51],[74,-46],[70,-42],[45,-5],[59,14],[67,10],[68,1],[65,-6],[69,-11],[70,-14],[75,-18],[76,-21],[96,-40],[98,-44],[100,-45],[101,-48],[107,-55],[114,-71],[99,-106]],"c":true}],"h":1},{"t":42,"s":[{"i":[[5.581,-0.632],[7.165,-4.053],[-1.142,-7.166],[-1.553,-1.381],[-2.421,-0.18],[-1.8,1.565],[-1.521,0.984],[-3.539,1.312],[-2.811,-0.562],[-1.738,-1.872],[-0.263,-3.155],[2.61,-4.713],[9.48,-10.878],[1.233,-1.504],[1.8,-3.22],[0.368,-5.272],[-1.843,-2.869],[-6.806,1.534],[-0.796,2.513],[0.246,2.439],[-0.489,3.636],[-0.368,0.49],[-0.662,0.792],[-3.438,3.791],[-5.613,8.462],[-1.561,2.724],[-1.17,6.131],[7.022,5.403],[0.481,0.217],[0.411,0.238]],"o":[[-5.48,0.621],[-7.166,4.053],[0.366,2.296],[1.553,1.381],[3.279,0.244],[1.8,-1.565],[2.031,-1.314],[3.54,-1.312],[1.785,0.357],[1.738,1.872],[0.371,4.452],[-8.341,15.06],[-1.324,1.52],[-2.438,2.974],[-1.597,2.857],[-0.327,4.68],[1.338,2.084],[3.671,-0.828],[0.639,-2.018],[-0.224,-2.216],[0.192,-1.43],[0.677,-0.903],[3.676,-4.395],[7.576,-8.354],[1.656,-2.497],[2.724,-4.753],[2.594,-13.588],[-0.41,-0.315],[-0.349,-0.157],[-5.679,-3.284]],"v":[[82,-109],[60.534,-101.908],[49,-85],[51.959,-79.413],[58,-77],[65.318,-79.579],[70,-84],[78.915,-88.407],[89,-90],[94.642,-86.599],[98,-79],[94,-68],[61,-32],[58,-27],[51,-19],[47,-7],[50,8],[63,14],[69,7],[70,2],[67,-6],[71,-11],[72,-14],[85,-27],[108,-52],[112,-59],[117,-72],[106,-104],[104,-104],[103,-106]],"c":true}],"h":1},{"t":43,"s":[{"i":[[4.927,-0.558],[3.542,-1.123],[3.644,-2.146],[2.59,-2.62],[-0.351,-3.759],[-1.728,-1.527],[-1.838,-0.203],[-1.693,1.27],[-1.334,1.242],[-1.264,0.661],[-0.923,0.407],[-1.021,0.334],[-1.016,0.265],[-2.77,-1.549],[-1.102,-4.061],[2.077,-3.671],[2.572,-3.066],[2.696,-2.589],[1.977,-1.977],[2.091,-10.911],[-10.779,1.307],[-1.057,1.787],[-1.051,5.198],[-2.459,2.941],[-0.93,1.141],[-3.209,2.975],[-2.158,2.158],[-2.093,2.308],[-2.789,7.123],[11.877,6.689]],"o":[[-3.189,0.361],[-3.542,1.123],[-2.991,1.761],[-2.59,2.62],[0.251,2.69],[1.728,1.527],[2.895,0.32],[1.693,-1.27],[1.02,-0.95],[1.264,-0.661],[0.9,-0.398],[1.021,-0.334],[4.816,-1.257],[2.77,1.549],[0.882,3.25],[-2.077,3.671],[-2.239,2.668],[-2.696,2.589],[-9.81,9.81],[-2.071,10.81],[1.08,-0.131],[3.745,-6.328],[0.488,-2.412],[0.944,-1.129],[2.981,-3.657],[2.545,-2.359],[2.037,-2.037],[5.832,-6.43],[6.585,-16.816],[-5.88,-3.312]],"v":[[84,-109],[73.841,-106.839],[63,-102],[53.993,-95.498],[50,-86],[53.309,-79.635],[59,-77],[65.671,-78.829],[70,-83],[73.573,-85.407],[77,-87],[79.913,-88.099],[83,-89],[94.285,-88.489],[100,-80],[97.591,-69.362],[90,-59],[82.303,-50.981],[75,-44],[49,-10],[63,14],[70,10],[69,-7],[75,-14],[77,-18],[87,-28],[95,-34],[101,-41],[118,-64],[105,-106]],"c":true}],"h":1},{"t":44,"s":[{"i":[[5.115,-0.566],[0.943,-0.154],[1.191,-0.267],[0.914,-0.327],[1.32,-0.463],[1.941,-0.604],[1.595,-1.01],[0.14,-0.424],[0.226,-0.169],[0.793,-0.671],[0.484,-0.704],[-9.232,-0.443],[-2.197,1.257],[-5.522,-1.014],[-0.547,-6.508],[2.724,-4.107],[3.049,-2.882],[3.51,-5.292],[1.328,-2.294],[0.363,-1.894],[-1.376,-2.868],[-5.947,0.179],[-0.63,1.987],[-1.123,6.386],[-1.55,1.785],[-9.427,10.506],[-1.063,1.345],[-1.763,2.75],[0.099,8.822],[7.131,4.016]],"o":[[-1.038,0.115],[-0.943,0.154],[-1.026,0.231],[-0.914,0.327],[-1.639,0.575],[-1.941,0.604],[-0.215,0.136],[-0.14,0.424],[-0.907,0.68],[-1.156,0.978],[-5.414,7.879],[4.964,0.238],[4.374,-2.503],[3.374,0.62],[0.679,8.082],[-3.097,4.669],[-5.604,5.298],[-2.105,3.173],[-1.447,2.499],[-1.606,8.373],[2.312,4.819],[6.404,-0.193],[2.077,-6.55],[0.135,-0.768],[10.333,-11.897],[0.89,-0.991],[2.309,-2.921],[3.774,-5.887],[-0.135,-11.968],[-6.041,-3.402]],"v":[[86,-109],[83.114,-108.614],[80,-108],[77.22,-107.174],[74,-106],[68.467,-104.326],[63,-102],[62.508,-101.025],[62,-100],[59,-99],[55,-95],[61,-77],[75,-85],[93,-90],[103,-79],[91,-58],[78,-44],[61,-28],[54,-19],[50,-11],[52,6],[63,14],[72,7],[70,-7],[75,-14],[107,-45],[109,-49],[115,-56],[123,-79],[107,-106]],"c":true}],"h":1},{"t":45,"s":[{"i":[[0.261,-0.024],[8.017,-4.31],[-2.441,-8.274],[-1.328,-1.18],[-2.397,-0.288],[-2.528,1.81],[-1.946,1.114],[-3.131,1.036],[-3.078,-0.516],[-1.994,-1.612],[-0.453,-3.289],[2.299,-3.506],[2.393,-2.681],[2.722,-2.635],[2.206,-2.206],[2.769,-2.663],[2.362,-2.786],[2.18,-3.757],[0.046,-4.341],[-2.666,-3.677],[-4.385,0.463],[1.056,5.63],[-0.76,4],[-2.669,2.987],[-6.78,6.435],[-3.89,11.92],[3.755,5.161],[2.881,1.299],[0.428,0.231],[7.572,0.345]],"o":[[-5.371,0.485],[-8.018,4.31],[0.293,0.992],[1.328,1.18],[2.942,0.353],[2.528,-1.81],[2.397,-1.371],[3.131,-1.036],[1.571,0.263],[1.994,1.612],[0.59,4.296],[-2.299,3.506],[-2.904,3.254],[-2.722,2.635],[-2.564,2.564],[-2.769,2.663],[-2.232,2.632],[-2.181,3.757],[-0.05,4.755],[2.666,3.677],[7.589,-0.801],[-0.441,-2.352],[0.115,-0.608],[6.683,-7.478],[10.619,-10.079],[3.595,-11.016],[-2.432,-3.343],[-0.358,-0.161],[-4.269,-2.305],[-0.985,-0.045]],"v":[[88,-109],[64.641,-101.842],[53,-83],[55.422,-79.472],[61,-77],[69.247,-79.9],[76,-85],[84.489,-88.916],[94,-90],[99.839,-87.269],[104,-80],[100.738,-68.289],[93,-59],[84.476,-50.214],[77,-43],[68.849,-35.167],[61,-27],[53.861,-17.282],[50,-5],[54.174,8.414],[65,14],[73,1],[71,-7],[77,-15],[99,-36],[123,-69],[118,-97],[110,-104],[109,-106],[90,-110]],"c":true}],"h":1},{"t":46,"s":[{"i":[[0.261,-0.024],[3.609,-1.108],[3.998,-2.245],[-0.756,-8.148],[-4.586,-0.172],[-3.519,2.276],[-3.334,0.953],[-4.742,-2.039],[-0.147,-6.015],[1.516,-2.355],[5.212,-4.928],[2.644,-2.52],[2.302,-2.807],[1.252,-1.683],[0.027,-7.326],[-9.201,1.116],[1.156,7.206],[-0.386,2.527],[-2.006,2.278],[-4.659,4.126],[-1.031,0.939],[-4.081,4.977],[-0.432,0.464],[-0.685,0.891],[-1.167,2.216],[0.124,6.681],[1.678,2.899],[2.982,2.713],[0.823,0.444],[6.03,0.275]],"o":[[-3.566,0.322],[-3.609,1.108],[-6.202,3.483],[0.531,5.729],[7.273,0.272],[2.833,-1.832],[3.97,-1.135],[1.682,0.723],[0.086,3.512],[-5.448,8.46],[-2.964,2.802],[-3.241,3.089],[-1.422,1.734],[-3.514,4.726],[-0.035,9.359],[6.251,-0.758],[-0.396,-2.471],[0.249,-1.627],[4.982,-5.658],[1.278,-1.132],[5.173,-4.711],[0.388,-0.473],[0.73,-0.785],[2.117,-2.754],[2.635,-5.004],[-0.117,-6.315],[-2.072,-3.579],[-2.029,-1.847],[-4.47,-2.413],[-0.985,-0.045]],"v":[[89,-109],[78.324,-106.942],[67,-102],[53,-86],[63,-77],[73,-83],[84,-88],[99,-89],[106,-78],[101,-67],[78,-42],[69,-35],[62,-26],[58,-22],[51,-5],[66,14],[74,1],[71,-6],[77,-13],[93,-29],[97,-32],[112,-49],[114,-50],[115,-53],[121,-60],[126,-79],[121,-94],[115,-101],[110,-106],[91,-110]],"c":true}],"h":1},{"t":47,"s":[{"i":[[0.256,-0.022],[2.254,-0.585],[3.206,-1.332],[1.791,-0.633],[1.243,-0.803],[0.459,-0.344],[0.796,-0.69],[0.578,-2.536],[-5.412,-0.37],[-5.389,2.961],[-5.612,-7.141],[4.373,-4.9],[5.997,-4.909],[3.55,-3.74],[1.212,-6.251],[-3.531,-3.523],[-4.555,1.079],[-0.436,0.887],[-0.723,4.885],[-1.207,1.819],[-1.929,1.28],[-0.83,0.751],[-7.632,9.27],[-2.211,5.316],[-0.595,2.568],[2.654,4.923],[0.466,0.486],[3.364,1.516],[0.428,0.231],[8.55,0.39]],"o":[[-3.78,0.33],[-2.254,0.585],[-1.595,0.663],[-1.791,0.633],[-0.437,0.283],[-0.915,0.687],[-2.477,2.147],[-1.81,7.935],[5.485,0.375],[9.229,-5.071],[7.221,9.187],[-7.227,8.097],[-4.679,3.831],[-5.316,5.6],[-1.874,9.669],[2.82,2.814],[1.501,-0.355],[3.521,-7.157],[0.248,-1.678],[1.85,-2.789],[1.033,-0.685],[9.235,-8.355],[4.474,-5.434],[0.69,-1.658],[2.05,-8.853],[-0.787,-1.461],[-2.878,-3.004],[-0.358,-0.161],[-4.426,-2.389],[-0.981,-0.045]],"v":[[90,-109],[81.569,-107.752],[74,-105],[68.736,-103.105],[64,-101],[63,-99],[60,-98],[54,-90],[63,-77],[78,-85],[104,-85],[96,-60],[77,-41],[65,-29],[52,-11],[57,10],[68,14],[74,9],[72,-6],[79,-15],[86,-22],[88,-24],[112,-48],[124,-64],[126,-71],[123,-92],[121,-96],[112,-104],[111,-106],[92,-110]],"c":true}],"h":1},{"t":48,"s":[{"i":[[5.707,-0.548],[3.607,-1.102],[4.005,-2.249],[2.819,-2.546],[-0.378,-4.074],[-1.662,-1.648],[-2.151,-0.238],[-1.707,1.271],[-1.321,1.23],[-4.305,1.431],[-2.995,-0.388],[-2.217,-1.571],[-0.439,-4.495],[2.592,-3.463],[2.102,-2.328],[3.975,-3.638],[4.112,-3.924],[2.697,-3.058],[1.5,-3.18],[0.57,-2.285],[-0.267,-2.771],[-10.363,1.257],[1.29,7.424],[-0.455,2.952],[-2.67,2.951],[-4.807,4.372],[-3.133,3.133],[-1.813,14.684],[3.516,5.047],[5.309,2.786]],"o":[[-3.566,0.342],[-3.607,1.102],[-3.101,1.742],[-2.819,2.546],[0.201,2.169],[1.662,1.648],[2.852,0.316],[1.707,-1.271],[1.786,-1.663],[4.305,-1.431],[1.695,0.22],[2.216,1.571],[0.471,4.82],[-2.592,3.463],[-4.989,5.524],[-3.975,3.638],[-2.714,2.59],[-2.697,3.058],[-0.985,2.088],[-0.571,2.285],[0.413,4.288],[6.161,-0.747],[-0.406,-2.339],[0.384,-2.489],[5.442,-6.015],[2.741,-2.493],[10.566,-10.566],[1.415,-11.462],[-3.734,-5.361],[-5.857,-3.073]],"v":[[90,-109],[79.329,-106.93],[68,-102],[58.391,-95.749],[54,-86],[57.038,-80.051],[63,-77],[69.648,-78.841],[74,-83],[84.094,-88.039],[96,-90],[102.442,-87.706],[107,-79],[102.93,-66.631],[95,-58],[81.841,-44.8],[70,-34],[61.59,-25.442],[55,-16],[52.561,-9.512],[52,-2],[67,14],[75,1],[72,-6],[79,-14],[94,-29],[102,-37],[127,-73],[121,-96],[111,-106]],"c":true}],"h":1},{"t":49,"s":[{"i":[[3.726,-0.269],[3.866,-1.088],[4.144,-2.403],[2.823,-2.612],[-0.276,-3.822],[-1.586,-1.63],[-2.499,-0.282],[-1.548,1.293],[-1.59,1.192],[-0.564,0.12],[-0.354,0.225],[-4.842,0.875],[-3.172,-2.701],[-0.118,-3.707],[1.76,-2.908],[6.757,-6.211],[3.75,-10.485],[-0.208,-3.713],[-1.091,-1.383],[2.067,12.881],[-0.361,2.289],[-1.78,2.037],[-2.428,2.305],[-7.457,6.16],[-3.098,4.195],[3.88,12.637],[0.571,0.761],[0.508,0.74],[2.927,1.694],[0.556,0.251]],"o":[[-3.391,0.245],[-3.866,1.088],[-2.984,1.73],[-2.823,2.612],[0.159,2.197],[1.586,1.63],[3.218,0.363],[1.548,-1.293],[0.392,-0.294],[0.564,-0.12],[3.46,-2.202],[4.842,-0.875],[2.825,2.405],[0.108,3.373],[-5.399,8.92],[-11.244,10.335],[-2.003,5.601],[0.162,2.889],[6.774,8.581],[-0.434,-2.706],[0.207,-1.314],[2.331,-2.667],[8.916,-8.463],[4.571,-3.776],[5.652,-7.653],[-0.646,-2.104],[-0.616,-0.821],[-3.524,-5.128],[-0.784,-0.454],[-4.8,-2.162]],"v":[[91,-109],[80.065,-107.118],[68,-102],[58.555,-95.569],[54,-86],[56.744,-80.063],[63,-77],[69.721,-78.833],[74,-83],[75.529,-83.551],[77,-84],[90.216,-89.177],[103,-87],[108,-78],[104,-68],[82,-45],[54,-13],[52,-3],[56,9],[75,1],[72,-6],[78,-13],[85,-20],[107,-41],[118,-54],[127,-87],[123,-93],[122,-96],[112,-105],[110,-107]],"c":true}],"h":1},{"t":50,"s":[{"i":[[10.014,-0.756],[0.334,0.01],[0.332,-0.013],[7.34,-4.098],[-0.047,-5.375],[-1.561,-1.849],[-2.735,-0.328],[-1.553,1.304],[-1.559,1.17],[-0.564,0.12],[-0.354,0.225],[-4.846,0.868],[-3.148,-2.679],[-0.605,-0.782],[-0.453,-0.912],[2.39,-3.712],[5.022,-4.916],[1.428,-1.092],[0.716,-0.65],[4.753,-5.254],[0.052,-8.184],[-9.639,1.902],[0.967,6.027],[-0.365,2.287],[-0.108,0.144],[-0.519,0.745],[-3.935,3.656],[-4.823,4.433],[-3.771,12.303],[9.393,5.781]],"o":[[-0.331,0.025],[-0.334,-0.01],[-5.69,0.232],[-7.339,4.098],[0.02,2.276],[1.561,1.849],[3.227,0.388],[1.553,-1.304],[0.392,-0.294],[0.564,-0.12],[3.47,-2.209],[4.846,-0.868],[1.127,0.96],[0.605,0.782],[2.799,5.636],[-4.487,6.967],[-2.004,1.962],[-0.743,0.568],[-5.103,4.635],[-4.541,5.021],[-0.059,9.36],[6.182,-1.22],[-0.434,-2.705],[0.005,-0.029],[0.621,-0.828],[3.433,-4.925],[6.122,-5.688],[10.354,-9.517],[4.825,-15.744],[-6.583,-4.051]],"v":[[91,-109],[90.001,-108.991],[89,-109],[67.197,-101.857],[54,-87],[56.464,-80.539],[63,-77],[69.751,-78.832],[74,-83],[75.529,-83.551],[77,-84],[90.242,-89.166],[103,-87],[105.506,-84.464],[107,-82],[103,-67],[88,-50],[82,-44],[79,-43],[63,-27],[52,-5],[68,14],[75,1],[72,-6],[74,-7],[75,-10],[86,-21],[101,-35],[126,-68],[114,-104]],"c":true}],"h":1},{"t":51,"s":[{"i":[[10.014,-0.756],[0.334,0.01],[0.332,-0.013],[7.385,-4.171],[-0.434,-6.076],[-1.652,-1.692],[-2.25,-0.27],[-3.046,2.238],[-3.59,1.436],[-3.744,0.083],[-2.191,-1.865],[-0.881,-1.501],[-0.057,-1.79],[1.061,-1.93],[0.82,-1.273],[2.519,-2.714],[2.61,-2.555],[1.071,-1.118],[0.714,-0.546],[0.716,-0.65],[4.921,-5.441],[0,-8.425],[-9.948,1.963],[0.967,6.027],[-0.361,2.289],[-1.893,2.135],[-2.46,2.285],[-4.891,4.496],[-3.77,12.301],[9.393,5.781]],"o":[[-0.331,0.025],[-0.334,-0.01],[-5.894,0.24],[-7.385,4.171],[0.141,1.96],[1.652,1.692],[3.227,0.388],[3.046,-2.238],[3.834,-1.534],[3.744,-0.083],[1.418,1.208],[0.881,1.501],[0.064,2.005],[-1.061,1.93],[-2.312,3.591],[-2.519,2.714],[-1.002,0.981],[-1.071,1.118],[-0.743,0.568],[-5.167,4.692],[-4.504,4.98],[0,9.142],[6.182,-1.22],[-0.434,-2.706],[0.105,-0.668],[2.217,-2.501],[6.163,-5.726],[10.351,-9.515],[4.825,-15.744],[-6.583,-4.051]],"v":[[91,-109],[90.001,-108.991],[89,-109],[66.754,-101.877],[54,-86],[56.918,-80.233],[63,-77],[72.227,-80.632],[82,-87],[93.732,-89.55],[103,-87],[106.521,-82.937],[108,-78],[106.163,-71.951],[103,-67],[95.723,-57.723],[88,-50],[84.784,-46.674],[82,-44],[79,-43],[63,-27],[52,-5],[68,14],[75,1],[72,-6],[78,-14],[86,-21],[101,-35],[126,-68],[114,-104]],"c":true}],"h":1},{"t":52,"s":[{"i":[[12.38,-0.935],[0.334,0.01],[0.332,-0.013],[7.395,-4.231],[-0.401,-5.84],[-1.589,-1.631],[-2.495,-0.281],[-1.556,1.296],[-1.569,1.177],[-0.708,0.451],[-6.344,-5.401],[-0.118,-3.707],[1.553,-2.412],[2.109,-2.261],[3.479,-3.032],[1.015,-0.925],[4.884,-5.399],[0.457,-0.597],[0.675,-0.881],[0.965,-1.73],[-4.653,-6.739],[-6.717,1.325],[0.967,6.027],[-0.361,2.289],[-1.893,2.135],[-2.46,2.285],[-4.813,4.442],[-3.763,12.278],[3.547,5.784],[3.366,3.062]],"o":[[-0.331,0.025],[-0.334,-0.01],[-5.822,0.237],[-7.395,4.231],[0.151,2.196],[1.589,1.631],[3.209,0.362],[1.556,-1.296],[0.784,-0.588],[6.92,-4.404],[2.825,2.405],[0.127,3.97],[-1.794,2.785],[-3.802,4.076],[-1.461,1.273],[-5.345,4.868],[-0.563,0.622],[-0.636,0.832],[-1.303,1.701],[-4.265,7.65],[2.215,3.208],[6.182,-1.22],[-0.434,-2.706],[0.105,-0.668],[2.217,-2.501],[6.121,-5.687],[10.358,-9.559],[2.752,-8.978],[-2.654,-4.329],[-7.422,-6.753]],"v":[[91,-109],[90.001,-108.991],[89,-109],[66.832,-101.702],[54,-86],[56.742,-80.064],[63,-77],[69.73,-78.846],[74,-83],[77,-84],[103,-87],[108,-78],[103,-67],[96,-58],[84,-46],[79,-43],[63,-27],[61,-25],[60,-22],[56,-18],[56,8],[68,14],[75,1],[72,-6],[78,-14],[86,-21],[101,-35],[126,-68],[124,-93],[117,-101]],"c":true}],"h":1},{"t":53,"s":[{"i":[[12.304,-0.929],[0.334,0.01],[0.332,-0.013],[7.404,-4.223],[-0.424,-5.87],[-1.586,-1.63],[-2.499,-0.282],[-1.556,1.296],[-1.569,1.177],[-0.564,0.12],[-0.354,0.225],[-4.842,0.875],[-3.172,-2.701],[-0.882,-1.486],[-0.059,-1.853],[1.066,-1.956],[0.799,-1.242],[3.42,-3.683],[2.926,-2.55],[1.015,-0.925],[4.644,-5.08],[-8.281,-11.995],[-6.717,1.325],[0.967,6.027],[-0.361,2.289],[-1.837,2.073],[-2.369,2.222],[-4.859,4.485],[13.718,22.37],[3.366,3.062]],"o":[[-0.331,0.025],[-0.334,-0.01],[-5.806,0.237],[-7.404,4.223],[0.159,2.197],[1.586,1.63],[3.209,0.362],[1.556,-1.296],[0.392,-0.294],[0.564,-0.12],[3.46,-2.202],[4.842,-0.875],[1.412,1.203],[0.882,1.486],[0.061,1.934],[-1.066,1.956],[-2.394,3.718],[-3.42,3.683],[-1.461,1.273],[-5.408,4.924],[-7.65,8.367],[2.215,3.208],[6.182,-1.22],[-0.434,-2.706],[0.097,-0.617],[2.18,-2.459],[6.391,-5.993],[13.758,-12.697],[-2.654,-4.329],[-7.482,-6.808]],"v":[[91,-109],[90.001,-108.991],[89,-109],[66.828,-101.724],[54,-86],[56.744,-80.063],[63,-77],[69.73,-78.846],[74,-83],[75.529,-83.551],[77,-84],[90.216,-89.177],[103,-87],[106.515,-82.988],[108,-78],[106.146,-71.981],[103,-67],[93.899,-55.624],[84,-46],[79,-43],[63,-27],[56,8],[68,14],[75,1],[72,-6],[78,-14],[86,-21],[101,-35],[124,-93],[117,-101]],"c":true}],"h":1},{"t":54,"s":[{"i":[[12.304,-0.929],[0.334,0.01],[0.332,-0.013],[7.404,-4.223],[-0.424,-5.87],[-1.586,-1.63],[-2.499,-0.282],[-1.556,1.296],[-1.569,1.177],[-0.564,0.12],[-0.354,0.225],[-4.842,0.875],[-3.172,-2.701],[-0.882,-1.486],[-0.059,-1.853],[1.066,-1.956],[0.799,-1.242],[3.42,-3.683],[2.926,-2.55],[1.015,-0.925],[4.644,-5.08],[-8.281,-11.995],[-6.717,1.325],[0.967,6.027],[-0.361,2.289],[-1.837,2.073],[-2.369,2.222],[-4.843,4.499],[13.699,22.339],[3.366,3.062]],"o":[[-0.331,0.025],[-0.334,-0.01],[-5.806,0.237],[-7.404,4.223],[0.159,2.197],[1.586,1.63],[3.209,0.362],[1.556,-1.296],[0.392,-0.294],[0.564,-0.12],[3.46,-2.202],[4.842,-0.875],[1.412,1.203],[0.882,1.486],[0.061,1.934],[-1.066,1.956],[-2.394,3.718],[-3.42,3.683],[-1.461,1.273],[-5.408,4.924],[-7.65,8.367],[2.215,3.208],[6.182,-1.22],[-0.434,-2.706],[0.097,-0.617],[2.18,-2.459],[6.39,-5.993],[13.777,-12.8],[-2.654,-4.329],[-7.482,-6.808]],"v":[[91,-109],[90.001,-108.991],[89,-109],[66.828,-101.724],[54,-86],[56.744,-80.063],[63,-77],[69.73,-78.846],[74,-83],[75.529,-83.551],[77,-84],[90.216,-89.177],[103,-87],[106.515,-82.988],[108,-78],[106.146,-71.981],[103,-67],[93.899,-55.624],[84,-46],[79,-43],[63,-27],[56,8],[68,14],[75,1],[72,-6],[78,-14],[86,-21],[101,-35],[124,-93],[117,-101]],"c":true}],"h":1},{"t":55,"s":[{"i":[[12.3,-0.929],[0.334,0.01],[0.332,-0.013],[7.404,-4.223],[-0.424,-5.87],[-1.586,-1.63],[-2.499,-0.282],[-1.556,1.296],[-1.569,1.177],[-0.564,0.12],[-0.354,0.225],[-6.344,-5.401],[-0.118,-3.707],[1.809,-2.989],[6.743,-6.198],[3.75,-10.485],[-0.208,-3.713],[-1.091,-1.383],[2.067,12.881],[-0.361,2.289],[-1.831,2.095],[-2.428,2.305],[-3.88,3.668],[-1.006,0.754],[-0.796,0.79],[-2.045,2.458],[-2.43,5.656],[-0.59,2.441],[2.858,4.937],[2.61,2.375]],"o":[[-0.331,0.025],[-0.334,-0.01],[-5.806,0.237],[-7.404,4.223],[0.159,2.197],[1.586,1.63],[3.209,0.362],[1.556,-1.296],[0.392,-0.294],[0.564,-0.12],[6.92,-4.404],[2.825,2.405],[0.107,3.347],[-5.399,8.921],[-11.244,10.335],[-2.003,5.601],[0.162,2.889],[6.774,8.581],[-0.434,-2.706],[0.213,-1.349],[2.331,-2.667],[5.725,-5.434],[1.796,-1.698],[0.952,-0.714],[2.279,-2.261],[4.543,-5.459],[0.729,-1.698],[2.288,-9.461],[-1.972,-3.406],[-7.468,-6.795]],"v":[[91,-109],[90.001,-108.991],[89,-109],[66.828,-101.724],[54,-86],[56.744,-80.063],[63,-77],[69.73,-78.846],[74,-83],[75.529,-83.551],[77,-84],[103,-87],[108,-78],[104,-68],[82,-45],[54,-13],[52,-3],[56,9],[75,1],[72,-6],[78,-13],[85,-20],[99,-34],[104,-39],[107,-40],[113,-48],[125,-64],[127,-71],[123,-94],[117,-101]],"c":true}],"h":1},{"t":56,"s":[{"i":[[12.291,-0.928],[0.334,0.01],[0.332,-0.013],[7.404,-4.223],[-0.424,-5.87],[-1.585,-1.63],[-2.5,-0.282],[-1.556,1.296],[-1.569,1.177],[-0.564,0.12],[-0.354,0.225],[-4.842,0.875],[-3.172,-2.701],[-0.118,-3.707],[1.809,-2.989],[6.743,-6.198],[3.75,-10.485],[-0.208,-3.713],[-1.091,-1.383],[2.067,12.881],[-0.361,2.289],[-1.78,2.037],[-2.428,2.305],[-5.434,5.059],[-0.68,0.664],[-2.148,2.482],[-1.904,2.579],[0.202,10.903],[1.709,2.951],[2.701,2.457]],"o":[[-0.331,0.025],[-0.334,-0.01],[-5.806,0.237],[-7.404,4.223],[0.159,2.197],[1.585,1.63],[3.209,0.362],[1.556,-1.296],[0.392,-0.294],[0.564,-0.12],[3.46,-2.202],[4.842,-0.875],[2.825,2.405],[0.107,3.347],[-5.399,8.921],[-11.244,10.335],[-2.003,5.601],[0.162,2.889],[6.774,8.581],[-0.434,-2.706],[0.207,-1.314],[2.331,-2.667],[7.595,-7.21],[0.69,-0.642],[2.343,-2.289],[2.151,-2.486],[4.975,-6.737],[-0.118,-6.386],[-1.971,-3.404],[-7.448,-6.777]],"v":[[91,-109],[90.001,-108.991],[89,-109],[66.828,-101.724],[54,-86],[56.744,-80.063],[63,-77],[69.73,-78.846],[74,-83],[75.529,-83.551],[77,-84],[90.216,-89.177],[103,-87],[108,-78],[104,-68],[82,-45],[54,-13],[52,-3],[56,9],[75,1],[72,-6],[78,-13],[85,-20],[103,-38],[106,-39],[112,-47],[118,-54],[128,-79],[123,-94],[117,-101]],"c":true}],"h":1},{"t":57,"s":[{"i":[[12.291,-0.928],[0.334,0.01],[0.332,-0.013],[7.406,-4.223],[-0.432,-5.871],[-1.584,-1.63],[-2.502,-0.282],[-1.556,1.296],[-1.569,1.177],[-0.564,0.12],[-0.354,0.225],[-4.842,0.875],[-3.172,-2.701],[-0.882,-1.486],[-0.059,-1.853],[0.787,-1.708],[0.904,-1.495],[6.663,-6.125],[3.75,-10.485],[-0.208,-3.713],[-1.091,-1.383],[2.067,12.881],[-0.361,2.289],[-1.78,2.037],[-2.428,2.305],[-7.457,6.16],[-0.666,0.747],[0.293,15.788],[1.709,2.951],[2.701,2.457]],"o":[[-0.331,0.025],[-0.334,-0.01],[-5.806,0.237],[-7.406,4.223],[0.162,2.198],[1.584,1.63],[3.209,0.362],[1.556,-1.296],[0.392,-0.294],[0.564,-0.12],[3.46,-2.202],[4.842,-0.875],[1.412,1.203],[0.882,1.486],[0.053,1.673],[-0.787,1.708],[-5.368,8.869],[-11.244,10.335],[-2.003,5.601],[0.162,2.889],[6.774,8.581],[-0.434,-2.706],[0.207,-1.314],[2.331,-2.667],[8.916,-8.463],[0.788,-0.651],[8.101,-9.083],[-0.118,-6.386],[-1.971,-3.404],[-7.448,-6.777]],"v":[[91,-109],[90.001,-108.991],[89,-109],[66.821,-101.725],[54,-86],[56.745,-80.063],[63,-77],[69.73,-78.846],[74,-83],[75.529,-83.551],[77,-84],[90.216,-89.177],[103,-87],[106.515,-82.988],[108,-78],[106.719,-72.866],[104,-68],[82,-45],[54,-13],[52,-3],[56,9],[75,1],[72,-6],[78,-13],[85,-20],[107,-41],[109,-43],[128,-79],[123,-94],[117,-101]],"c":true}],"h":1},{"t":58,"s":[{"i":[[12.291,-0.928],[0.334,0.01],[0.332,-0.013],[7.406,-4.223],[-0.432,-5.871],[-1.584,-1.63],[-2.502,-0.282],[-1.556,1.296],[-1.569,1.177],[-0.564,0.12],[-0.354,0.225],[-4.842,0.875],[-3.172,-2.701],[-0.882,-1.486],[-0.059,-1.853],[0.787,-1.708],[0.904,-1.495],[6.663,-6.125],[3.75,-10.485],[-0.208,-3.713],[-1.091,-1.383],[2.067,12.881],[-0.361,2.289],[-1.78,2.037],[-2.428,2.305],[-7.457,6.16],[-0.666,0.747],[0.293,15.788],[1.709,2.951],[2.701,2.457]],"o":[[-0.331,0.025],[-0.334,-0.01],[-5.806,0.237],[-7.406,4.223],[0.162,2.198],[1.584,1.63],[3.209,0.362],[1.556,-1.296],[0.392,-0.294],[0.564,-0.12],[3.46,-2.202],[4.842,-0.875],[1.412,1.203],[0.882,1.486],[0.053,1.673],[-0.787,1.708],[-5.368,8.869],[-11.244,10.335],[-2.003,5.601],[0.162,2.889],[6.774,8.581],[-0.434,-2.706],[0.207,-1.314],[2.331,-2.667],[8.916,-8.463],[0.788,-0.651],[8.101,-9.083],[-0.118,-6.386],[-1.971,-3.404],[-7.448,-6.777]],"v":[[91,-109],[90.001,-108.991],[89,-109],[66.821,-101.725],[54,-86],[56.745,-80.063],[63,-77],[69.73,-78.846],[74,-83],[75.529,-83.551],[77,-84],[90.216,-89.177],[103,-87],[106.515,-82.988],[108,-78],[106.719,-72.866],[104,-68],[82,-45],[54,-13],[52,-3],[56,9],[75,1],[72,-6],[78,-13],[85,-20],[107,-41],[109,-43],[128,-79],[123,-94],[117,-101]],"c":true}],"h":1},{"t":59,"s":[{"i":[[0.233,-0.018],[0.334,0.01],[0.332,-0.013],[7.34,-4.074],[-0.087,-5.471],[-1.568,-1.799],[-2.689,-0.303],[-1.556,1.296],[-1.569,1.177],[-0.564,0.12],[-0.354,0.225],[-4.842,0.875],[-3.172,-2.701],[-0.118,-3.707],[1.665,-2.75],[6.591,-6.058],[3.695,-10.333],[-0.208,-3.713],[-1.136,-1.439],[2.07,12.899],[-0.361,2.289],[-2.204,2.522],[-2.383,2.262],[-7.197,5.946],[-3.098,4.195],[0.194,10.463],[2.607,2.721],[3.203,1.444],[0.428,0.231],[6.327,0.288]],"o":[[-0.331,0.025],[-0.334,-0.01],[-5.729,0.234],[-7.339,4.074],[0.04,2.501],[1.568,1.799],[3.209,0.362],[1.556,-1.296],[0.392,-0.294],[0.564,-0.12],[3.46,-2.202],[4.842,-0.875],[2.825,2.405],[0.102,3.205],[-5.468,9.033],[-11.226,10.319],[-2.003,5.601],[0.162,2.892],[6.756,8.557],[-0.434,-2.706],[0.214,-1.36],[2.256,-2.581],[8.329,-7.906],[4.571,-3.776],[4.943,-6.693],[-0.111,-5.958],[-2.958,-3.088],[-0.358,-0.161],[-4.365,-2.357],[-0.963,-0.044]],"v":[[91,-109],[90.001,-108.991],[89,-109],[67.138,-101.928],[54,-87],[56.513,-80.352],[63,-77],[69.73,-78.846],[74,-83],[75.529,-83.551],[77,-84],[90.216,-89.177],[103,-87],[108,-78],[104,-68],[82,-45],[54,-13],[52,-3],[56,9],[75,1],[72,-6],[78,-13],[85,-20],[107,-41],[118,-54],[128,-79],[122,-96],[113,-104],[112,-106],[93,-110]],"c":true}],"h":1},{"t":60,"s":[{"i":[[12.291,-0.928],[0.334,0.01],[0.332,-0.013],[7.34,-4.074],[-0.087,-5.471],[-1.568,-1.799],[-2.689,-0.303],[-1.556,1.296],[-1.569,1.177],[-0.564,0.12],[-0.354,0.225],[-4.842,0.875],[-3.172,-2.701],[-0.882,-1.486],[-0.059,-1.853],[0.805,-1.756],[0.833,-1.375],[6.591,-6.058],[3.695,-10.333],[-0.208,-3.713],[-1.136,-1.439],[2.07,12.899],[-0.361,2.289],[-1.78,2.037],[-2.428,2.305],[-7.457,6.16],[-0.666,0.747],[0.293,15.788],[1.709,2.951],[2.701,2.457]],"o":[[-0.331,0.025],[-0.334,-0.01],[-5.729,0.234],[-7.339,4.074],[0.04,2.501],[1.568,1.799],[3.209,0.362],[1.556,-1.296],[0.392,-0.294],[0.564,-0.12],[3.46,-2.202],[4.842,-0.875],[1.412,1.203],[0.882,1.486],[0.051,1.602],[-0.805,1.756],[-5.468,9.033],[-11.226,10.319],[-2.003,5.601],[0.162,2.892],[6.756,8.557],[-0.434,-2.706],[0.207,-1.314],[2.331,-2.667],[8.916,-8.463],[0.788,-0.651],[8.101,-9.083],[-0.118,-6.386],[-1.971,-3.404],[-7.448,-6.777]],"v":[[91,-109],[90.001,-108.991],[89,-109],[67.138,-101.928],[54,-87],[56.513,-80.352],[63,-77],[69.73,-78.846],[74,-83],[75.529,-83.551],[77,-84],[90.216,-89.177],[103,-87],[106.515,-82.988],[108,-78],[106.663,-72.829],[104,-68],[82,-45],[54,-13],[52,-3],[56,9],[75,1],[72,-6],[78,-13],[85,-20],[107,-41],[109,-43],[128,-79],[123,-94],[117,-101]],"c":true}],"h":1}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.525490196078,0.270588235294,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":31,"op":300,"st":0,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"Shape Layer 4","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.016,54.049,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":10,"s":[{"i":[[0,0],[11.928,-26.533],[-4,-20],[1.5,-2]],"o":[[-4.5,-7],[-12.25,27.25],[0.88,4.401],[-1.5,2]],"v":[[-64.5,-87],[-116.25,-85.75],[-62.5,-7],[-65.5,4]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":18,"s":[{"i":[[0,0],[-11.928,-26.533],[4,-20],[-1.5,-2]],"o":[[4.5,-7],[12.25,27.25],[-0.88,4.401],[1.5,2]],"v":[[64.42,-87],[116.17,-85.75],[62.42,-7],[65.42,4]],"c":false}]},{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":24,"s":[{"i":[[0,0],[11.928,-26.533],[-4,-20],[1.5,-2]],"o":[[-4.5,-7],[-12.25,27.25],[0.88,4.401],[-1.5,2]],"v":[[-64.5,-87],[-116.25,-85.75],[-62.5,-7],[-65.5,4]],"c":false}]},{"t":50,"s":[{"i":[[0,0],[-11.928,-26.533],[4,-20],[-1.5,-2]],"o":[[4.5,-7],[12.25,27.25],[-0.88,4.401],[1.5,2]],"v":[[64.42,-87],[116.17,-85.75],[62.42,-7],[65.42,4]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.525490196078,0.270588235294,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":20,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[43.313,-47.836],"ix":2},"a":{"a":0,"k":[43.313,-47.836],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":24,"op":31,"st":10,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"Shape Layer 1","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.016,54.049,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":10,"s":[{"i":[[0,0],[11.928,-26.533],[-4,-20],[1.5,-2]],"o":[[-4.5,-7],[-12.25,27.25],[0.88,4.401],[-1.5,2]],"v":[[-64.5,-87],[-116.25,-85.75],[-62.5,-7],[-65.5,4]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":18,"s":[{"i":[[0,0],[-11.928,-26.533],[4,-20],[-1.5,-2]],"o":[[4.5,-7],[12.25,27.25],[-0.88,4.401],[1.5,2]],"v":[[64.42,-87],[116.17,-85.75],[62.42,-7],[65.42,4]],"c":false}]},{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":24,"s":[{"i":[[0,0],[11.928,-26.533],[-4,-20],[1.5,-2]],"o":[[-4.5,-7],[-12.25,27.25],[0.88,4.401],[-1.5,2]],"v":[[-64.5,-87],[-116.25,-85.75],[-62.5,-7],[-65.5,4]],"c":false}]},{"t":50,"s":[{"i":[[0,0],[-11.928,-26.533],[4,-20],[-1.5,-2]],"o":[[4.5,-7],[12.25,27.25],[-0.88,4.401],[1.5,2]],"v":[[64.42,-87],[116.17,-85.75],[62.42,-7],[65.42,4]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.525490196078,0.270588235294,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":20,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[43.313,-47.836],"ix":2},"a":{"a":0,"k":[43.313,-47.836],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":18,"st":10,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"Shape Layer 5","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.016,54.049,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[-100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":10,"s":[{"i":[[0,0],[11.928,-26.533],[-4,-20],[1.5,-2]],"o":[[-4.5,-7],[-12.25,27.25],[0.88,4.401],[-1.5,2]],"v":[[-64.5,-87],[-116.25,-85.75],[-62.5,-7],[-65.5,4]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":18,"s":[{"i":[[0,0],[-11.928,-26.533],[4,-20],[-1.5,-2]],"o":[[4.5,-7],[12.25,27.25],[-0.88,4.401],[1.5,2]],"v":[[64.42,-87],[116.17,-85.75],[62.42,-7],[65.42,4]],"c":false}]},{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":24,"s":[{"i":[[0,0],[11.928,-26.533],[-4,-20],[1.5,-2]],"o":[[-4.5,-7],[-12.25,27.25],[0.88,4.401],[-1.5,2]],"v":[[-64.5,-87],[-116.25,-85.75],[-62.5,-7],[-65.5,4]],"c":false}]},{"t":50,"s":[{"i":[[0,0],[-11.928,-26.533],[4,-20],[-1.5,-2]],"o":[[4.5,-7],[12.25,27.25],[-0.88,4.401],[1.5,2]],"v":[[64.42,-87],[116.17,-85.75],[62.42,-7],[65.42,4]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.525490196078,0.270588235294,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":20,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[-78.173,-47.836],"ix":2},"a":{"a":0,"k":[-78.173,-47.836],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":18,"op":24,"st":10,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"Cup 2","parent":15,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-11.815,0],[0,0],[1.176,-11.756],[0,0],[5.492,54.916],[0,0]],"o":[[0,0],[11.815,0],[0,0],[-5.492,54.916],[0,0],[-1.176,-11.756]],"v":[[-49.55,-73.91],[49.55,-73.91],[70.876,-52.583],[62.346,32.723],[-62.346,32.723],[-70.876,-52.583]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.705882370472,0.247058823705,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Cup","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":310,"st":10,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"Star 4 :M","parent":15,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":10,"s":[-225,-6.953,0],"to":[75,0,0],"ti":[-75,0,0]},{"t":50,"s":[225,-6.953,0]}],"ix":2,"l":2},"a":{"a":0,"k":[24.984,188.998,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[5.278,-3.874],[6.547,-0.032],[5.316,3.822],[2.054,6.217],[-1.993,6.237],[-5.278,3.874],[-6.547,0.032],[-5.316,-3.822],[-2.054,-6.217],[1.993,-6.237]],"o":[[-5.278,3.874],[-6.547,0.032],[-5.316,-3.822],[-2.054,-6.217],[1.993,-6.237],[5.278,-3.874],[6.547,-0.033],[5.316,3.822],[2.054,6.217],[-1.993,6.237]],"v":[[19.304,28.834],[0.146,23.68],[-18.962,29.022],[-19.98,9.209],[-30.965,-7.313],[-12.436,-14.404],[-0.118,-29.957],[12.352,-14.526],[30.95,-7.617],[20.128,9.011]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Star","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[249.984,188.998],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Star","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[-200.016,188.998],"ix":2},"a":{"a":0,"k":[249.984,188.998],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Star 4","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[5.278,-3.874],[6.547,-0.032],[5.316,3.822],[2.054,6.217],[-1.993,6.237],[-5.278,3.874],[-6.547,0.032],[-5.316,-3.822],[-2.054,-6.217],[1.993,-6.237]],"o":[[-5.278,3.874],[-6.547,0.032],[-5.316,-3.822],[-2.054,-6.217],[1.993,-6.237],[5.278,-3.874],[6.547,-0.033],[5.316,3.822],[2.054,6.217],[-1.993,6.237]],"v":[[19.304,28.834],[0.146,23.68],[-18.962,29.022],[-19.98,9.209],[-30.965,-7.313],[-12.436,-14.404],[-0.118,-29.957],[12.352,-14.526],[30.95,-7.617],[20.128,9.011]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Star","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[249.984,188.998],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Star","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[-50.016,188.998],"ix":2},"a":{"a":0,"k":[249.984,188.998],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Star 3","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[5.278,-3.874],[6.547,-0.032],[5.316,3.822],[2.054,6.217],[-1.993,6.237],[-5.278,3.874],[-6.547,0.032],[-5.316,-3.822],[-2.054,-6.217],[1.993,-6.237]],"o":[[-5.278,3.874],[-6.547,0.032],[-5.316,-3.822],[-2.054,-6.217],[1.993,-6.237],[5.278,-3.874],[6.547,-0.033],[5.316,3.822],[2.054,6.217],[-1.993,6.237]],"v":[[19.304,28.834],[0.146,23.68],[-18.962,29.022],[-19.98,9.209],[-30.965,-7.313],[-12.436,-14.404],[-0.118,-29.957],[12.352,-14.526],[30.95,-7.617],[20.128,9.011]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Star","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[249.984,188.998],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Star","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[99.984,188.998],"ix":2},"a":{"a":0,"k":[249.984,188.998],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Star 2","np":1,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[5.278,-3.874],[6.547,-0.032],[5.316,3.822],[2.054,6.217],[-1.993,6.237],[-5.278,3.874],[-6.547,0.032],[-5.316,-3.822],[-2.054,-6.217],[1.993,-6.237]],"o":[[-5.278,3.874],[-6.547,0.032],[-5.316,-3.822],[-2.054,-6.217],[1.993,-6.237],[5.278,-3.874],[6.547,-0.033],[5.316,3.822],[2.054,6.217],[-1.993,6.237]],"v":[[19.304,28.834],[0.146,23.68],[-18.962,29.022],[-19.98,9.209],[-30.965,-7.313],[-12.436,-14.404],[-0.118,-29.957],[12.352,-14.526],[30.95,-7.617],[20.128,9.011]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Star","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[249.984,188.998],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Star","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[249.984,188.998],"ix":2},"a":{"a":0,"k":[249.984,188.998],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Star","np":1,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":310,"st":10,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"Black Stand 2","parent":14,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-24.605,0],[0,0],[18.303,0]],"o":[[-18.303,0],[0,0],[24.605,0],[0,0]],"v":[[-42.653,-29.114],[-53.962,29.114],[53.962,29.114],[42.653,-29.114]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.349019616842,0.345098048449,0.43137255311,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Black Stand","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":310,"st":10,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":"White Stand 4 :M","parent":14,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":10,"s":[-225,-1.544,0],"to":[75,0,0],"ti":[-75,0,0]},{"t":50,"s":[225,-1.544,0]}],"ix":2,"l":2},"a":{"a":0,"k":[24.984,347.302,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.323,0],[0,0],[-1.582,-4.024],[0,0],[4.323,0],[0,0],[-1.582,4.024],[0,0]],"o":[[0,0],[4.323,0],[0,0],[1.582,4.024],[0,0],[-4.323,0],[0,0],[1.582,-4.024]],"v":[[-25.949,-12.268],[25.998,-12.268],[33.803,-4.464],[37.313,4.464],[31.758,12.268],[-32.174,12.268],[-37.263,4.464],[-33.753,-4.464]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"White Stand","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[249.984,347.302],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"White Stand","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[-200.016,347.302],"ix":2},"a":{"a":0,"k":[249.984,347.302],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"White Stand 4","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.323,0],[0,0],[-1.582,-4.024],[0,0],[4.323,0],[0,0],[-1.582,4.024],[0,0]],"o":[[0,0],[4.323,0],[0,0],[1.582,4.024],[0,0],[-4.323,0],[0,0],[1.582,-4.024]],"v":[[-25.949,-12.268],[25.998,-12.268],[33.803,-4.464],[37.313,4.464],[31.758,12.268],[-32.174,12.268],[-37.263,4.464],[-33.753,-4.464]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"White Stand","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[249.984,347.302],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"White Stand","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[-50.016,347.302],"ix":2},"a":{"a":0,"k":[249.984,347.302],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"White Stand 3","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.323,0],[0,0],[-1.582,-4.024],[0,0],[4.323,0],[0,0],[-1.582,4.024],[0,0]],"o":[[0,0],[4.323,0],[0,0],[1.582,4.024],[0,0],[-4.323,0],[0,0],[1.582,-4.024]],"v":[[-25.949,-12.268],[25.998,-12.268],[33.803,-4.464],[37.313,4.464],[31.758,12.268],[-32.174,12.268],[-37.263,4.464],[-33.753,-4.464]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"White Stand","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[249.984,347.302],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"White Stand","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[99.984,347.302],"ix":2},"a":{"a":0,"k":[249.984,347.302],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"White Stand 2","np":1,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.323,0],[0,0],[-1.582,-4.024],[0,0],[4.323,0],[0,0],[-1.582,4.024],[0,0]],"o":[[0,0],[4.323,0],[0,0],[1.582,4.024],[0,0],[-4.323,0],[0,0],[1.582,-4.024]],"v":[[-25.949,-12.268],[25.998,-12.268],[33.803,-4.464],[37.313,4.464],[31.758,12.268],[-32.174,12.268],[-37.263,4.464],[-33.753,-4.464]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"White Stand","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[249.984,347.302],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"White Stand","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[249.984,347.302],"ix":2},"a":{"a":0,"k":[249.984,347.302],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"White Stand","np":1,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":310,"st":10,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":"Black Stand","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"k":[{"s":[90],"t":2,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[88.052],"t":3,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[83.09],"t":4,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[75.985],"t":5,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[67.277],"t":6,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[57.336],"t":7,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[46.447],"t":8,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[34.86],"t":9,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[10.836],"t":11,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0],"t":12,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-6.514],"t":13,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-10.253],"t":14,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-11.772],"t":15,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-11.657],"t":16,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-10.457],"t":17,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-8.646],"t":18,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-6.599],"t":19,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-4.592],"t":20,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-2.804],"t":21,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-1.336],"t":22,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-0.223],"t":23,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.544],"t":24,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.006],"t":25,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.219],"t":26,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.245],"t":27,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[1.142],"t":28,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.963],"t":29,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.75],"t":30,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.535],"t":31,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.34],"t":32,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.176],"t":33,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.049],"t":34,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-0.04],"t":35,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-0.097],"t":36,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-0.125],"t":37,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-0.132],"t":38,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-0.124],"t":39,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-0.107],"t":40,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-0.085],"t":41,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-0.062],"t":42,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-0.041],"t":43,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-0.023],"t":44,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-0.008],"t":45,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.002],"t":46,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.009],"t":47,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.013],"t":48,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.014],"t":49,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.013],"t":50,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.012],"t":51,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.01],"t":52,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.007],"t":53,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.005],"t":54,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.003],"t":55,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.001],"t":56,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0],"t":57,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-0.001],"t":58,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-0.001],"t":59,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-0.001],"t":60,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-0.001],"t":61,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-0.001],"t":62,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-0.001],"t":63,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-0.001],"t":65,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0],"t":66,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0],"t":67,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0],"t":68,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0],"t":69,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"p":{"k":[{"s":[138.235,254.547,0],"t":0,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[143.584,250.368,0],"t":1,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[157.812,240.556,0],"t":2,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[179.791,229.215,0],"t":3,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[209.087,221.759,0],"t":4,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[243.189,225.873,0],"t":5,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[274.404,246.799,0],"t":6,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[294.84,281.274,0],"t":7,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[299.502,322.507,0],"t":8,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[282.589,360.014,0],"t":9,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[249.984,377.959,0],"t":10,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[228.111,384.013,0],"t":11,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[215.555,387.488,0],"t":12,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[210.454,388.9,0],"t":13,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[210.841,388.792,0],"t":14,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[214.869,387.678,0],"t":15,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[220.951,385.994,0],"t":16,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[227.823,384.092,0],"t":17,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[234.564,382.227,0],"t":18,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[240.567,380.565,0],"t":19,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[245.498,379.201,0],"t":20,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[249.235,378.166,0],"t":21,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[251.813,377.453,0],"t":22,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[253.364,377.023,0],"t":23,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[254.079,376.826,0],"t":24,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[254.164,376.802,0],"t":25,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[253.818,376.898,0],"t":26,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[253.217,377.064,0],"t":27,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[252.503,377.262,0],"t":28,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[251.782,377.461,0],"t":29,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[251.126,377.643,0],"t":30,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[250.576,377.795,0],"t":31,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[250.15,377.913,0],"t":32,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[249.849,377.996,0],"t":33,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[249.66,378.049,0],"t":34,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[249.909,377.98,0],"t":42,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}],"l":2},"a":{"a":0,"k":[0,29.114,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":0,"s":[0,0,100]},{"t":10,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"ef":[{"ty":5,"nm":"Elastic Controller","np":5,"mn":"Pseudo/MDS Elastic Controller","ix":1,"en":1,"ef":[{"ty":0,"nm":"Amplitude","mn":"Pseudo/MDS Elastic Controller-0001","ix":1,"v":{"a":0,"k":20,"ix":1}},{"ty":0,"nm":"Frequency","mn":"Pseudo/MDS Elastic Controller-0002","ix":2,"v":{"a":0,"k":40,"ix":2}},{"ty":0,"nm":"Decay","mn":"Pseudo/MDS Elastic Controller-0003","ix":3,"v":{"a":0,"k":60,"ix":3}}]},{"ty":5,"nm":"Elastic Controller 2","np":5,"mn":"Pseudo/MDS Elastic Controller","ix":2,"en":1,"ef":[{"ty":0,"nm":"Amplitude","mn":"Pseudo/MDS Elastic Controller-0001","ix":1,"v":{"a":0,"k":20,"ix":1}},{"ty":0,"nm":"Frequency","mn":"Pseudo/MDS Elastic Controller-0002","ix":2,"v":{"a":0,"k":40,"ix":2}},{"ty":0,"nm":"Decay","mn":"Pseudo/MDS Elastic Controller-0003","ix":3,"v":{"a":0,"k":60,"ix":3}}]}],"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-24.605,0],[0,0],[18.303,0]],"o":[[-18.303,0],[0,0],[24.605,0],[0,0]],"v":[[-42.653,-29.114],[-53.962,29.114],[53.962,29.114],[42.653,-29.114]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.349019616842,0.345098048449,0.43137255311,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Black Stand","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":310,"st":10,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":"Cup","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,-152.895,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-11.815,0],[0,0],[1.176,-11.756],[0,0],[5.492,54.916],[0,0]],"o":[[0,0],[11.815,0],[0,0],[-5.492,54.916],[0,0],[-1.176,-11.756]],"v":[[-49.55,-73.91],[49.55,-73.91],[70.876,-52.583],[62.346,32.723],[-62.346,32.723],[-70.876,-52.583]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.705882370472,0.247058823705,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Cup","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":310,"st":10,"bm":0},{"ddd":0,"ind":16,"ty":4,"nm":"Stand","parent":14,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,-56.636,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[19.235,36.65],[0,0],[-15.853,-38.082],[0,0]],"o":[[0,0],[-20.405,35.342],[0,0],[17.561,-38.659]],"v":[[-33.841,-56.55],[33.841,-56.55],[25.31,56.55],[-25.31,56.55]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.525490224361,0.270588248968,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Stand","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":310,"st":10,"bm":0},{"ddd":0,"ind":17,"ty":4,"nm":"Shape Layer 3","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.016,54.049,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":10,"s":[{"i":[[0,0],[11.928,-26.533],[-4,-20],[1.5,-2]],"o":[[-4.5,-7],[-12.25,27.25],[0.88,4.401],[-1.5,2]],"v":[[-64.5,-87],[-116.25,-85.75],[-62.5,-7],[-65.5,4]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":18,"s":[{"i":[[0,0],[-11.928,-26.533],[4,-20],[-1.5,-2]],"o":[[4.5,-7],[12.25,27.25],[-0.88,4.401],[1.5,2]],"v":[[64.42,-87],[116.17,-85.75],[62.42,-7],[65.42,4]],"c":false}]},{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":24,"s":[{"i":[[0,0],[11.928,-26.533],[-4,-20],[1.5,-2]],"o":[[-4.5,-7],[-12.25,27.25],[0.88,4.401],[-1.5,2]],"v":[[-64.5,-87],[-116.25,-85.75],[-62.5,-7],[-65.5,4]],"c":false}]},{"t":50,"s":[{"i":[[0,0],[-11.928,-26.533],[4,-20],[-1.5,-2]],"o":[[4.5,-7],[12.25,27.25],[-0.88,4.401],[1.5,2]],"v":[[64.42,-87],[116.17,-85.75],[62.42,-7],[65.42,4]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.525490196078,0.270588235294,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":20,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[43.313,-47.836],"ix":2},"a":{"a":0,"k":[43.313,-47.836],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":18,"op":24,"st":10,"bm":0},{"ddd":0,"ind":18,"ty":4,"nm":"Shape Layer 6","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.016,54.049,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[-100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":10,"s":[{"i":[[0,0],[11.928,-26.533],[-4,-20],[1.5,-2]],"o":[[-4.5,-7],[-12.25,27.25],[0.88,4.401],[-1.5,2]],"v":[[-64.5,-87],[-116.25,-85.75],[-62.5,-7],[-65.5,4]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":18,"s":[{"i":[[0,0],[-11.928,-26.533],[4,-20],[-1.5,-2]],"o":[[4.5,-7],[12.25,27.25],[-0.88,4.401],[1.5,2]],"v":[[64.42,-87],[116.17,-85.75],[62.42,-7],[65.42,4]],"c":false}]},{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":24,"s":[{"i":[[0,0],[11.928,-26.533],[-4,-20],[1.5,-2]],"o":[[-4.5,-7],[-12.25,27.25],[0.88,4.401],[-1.5,2]],"v":[[-64.5,-87],[-116.25,-85.75],[-62.5,-7],[-65.5,4]],"c":false}]},{"t":50,"s":[{"i":[[0,0],[-11.928,-26.533],[4,-20],[-1.5,-2]],"o":[[4.5,-7],[12.25,27.25],[-0.88,4.401],[1.5,2]],"v":[[64.42,-87],[116.17,-85.75],[62.42,-7],[65.42,4]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.525490196078,0.270588235294,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":20,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[-78.173,-47.836],"ix":2},"a":{"a":0,"k":[-78.173,-47.836],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":24,"op":310,"st":10,"bm":0},{"ddd":0,"ind":19,"ty":4,"nm":"Shape Layer 2","parent":15,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0.016,54.049,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[-100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":10,"s":[{"i":[[0,0],[11.928,-26.533],[-4,-20],[1.5,-2]],"o":[[-4.5,-7],[-12.25,27.25],[0.88,4.401],[-1.5,2]],"v":[[-64.5,-87],[-116.25,-85.75],[-62.5,-7],[-65.5,4]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":18,"s":[{"i":[[0,0],[-11.928,-26.533],[4,-20],[-1.5,-2]],"o":[[4.5,-7],[12.25,27.25],[-0.88,4.401],[1.5,2]],"v":[[64.42,-87],[116.17,-85.75],[62.42,-7],[65.42,4]],"c":false}]},{"i":{"x":0,"y":1},"o":{"x":0.333,"y":0},"t":24,"s":[{"i":[[0,0],[11.928,-26.533],[-4,-20],[1.5,-2]],"o":[[-4.5,-7],[-12.25,27.25],[0.88,4.401],[-1.5,2]],"v":[[-64.5,-87],[-116.25,-85.75],[-62.5,-7],[-65.5,4]],"c":false}]},{"t":50,"s":[{"i":[[0,0],[-11.928,-26.533],[4,-20],[-1.5,-2]],"o":[[4.5,-7],[12.25,27.25],[-0.88,4.401],[1.5,2]],"v":[[64.42,-87],[116.17,-85.75],[62.42,-7],[65.42,4]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,0.525490196078,0.270588235294,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":20,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[-78.173,-47.836],"ix":2},"a":{"a":0,"k":[-78.173,-47.836],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":18,"st":10,"bm":0},{"ddd":0,"ind":21,"ty":0,"nm":"Pre-comp 1","refId":"comp_2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":60,"ix":10},"p":{"a":0,"k":[250,250,0],"ix":2,"l":2},"a":{"a":0,"k":[250,250,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"w":500,"h":500,"ip":16,"op":316,"st":16,"bm":0},{"ddd":0,"ind":22,"ty":0,"nm":"Pre-comp 1","refId":"comp_2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":45,"ix":10},"p":{"a":0,"k":[250,250,0],"ix":2,"l":2},"a":{"a":0,"k":[250,250,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"w":500,"h":500,"ip":11,"op":311,"st":11,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/source/SkiaSharp.Extended.UI.Blazor/Controls/SKAnimatedCanvasView.razor b/source/SkiaSharp.Extended.UI.Blazor/Controls/SKAnimatedCanvasView.razor new file mode 100644 index 0000000000..ead6c31f18 --- /dev/null +++ b/source/SkiaSharp.Extended.UI.Blazor/Controls/SKAnimatedCanvasView.razor @@ -0,0 +1,5 @@ +@namespace SkiaSharp.Extended.UI.Blazor.Controls + + diff --git a/source/SkiaSharp.Extended.UI.Blazor/Controls/SKAnimatedCanvasView.razor.cs b/source/SkiaSharp.Extended.UI.Blazor/Controls/SKAnimatedCanvasView.razor.cs new file mode 100644 index 0000000000..779863b50c --- /dev/null +++ b/source/SkiaSharp.Extended.UI.Blazor/Controls/SKAnimatedCanvasView.razor.cs @@ -0,0 +1,152 @@ +using Microsoft.AspNetCore.Components; +using SkiaSharp.Views.Blazor; + +namespace SkiaSharp.Extended.UI.Blazor.Controls; + +/// +/// A Blazor component that wraps and drives a +/// frame-update loop using a , mirroring the +/// MAUI SKAnimatedSurfaceView pattern for Blazor applications. +/// +/// +/// +/// Subclass this component and override to update +/// your animation state on each frame, and subscribe to +/// to render your content. +/// +/// +/// The animation loop runs at approximately 60 fps while +/// is . Setting it to +/// stops the loop; setting it back to +/// restarts it. +/// +/// +public partial class SKAnimatedCanvasView : ComponentBase, IAsyncDisposable +{ + private bool _isAnimationEnabled = true; + private CancellationTokenSource? _cts; + private Task? _loopTask; + private SKCanvasView? _canvasView; + + /// + /// Gets or sets whether the animation loop is running. + /// Defaults to . + /// + [Parameter] + public bool IsAnimationEnabled { get; set; } = true; + + /// + /// Callback invoked on each frame tick with the elapsed time since the + /// previous frame. Use this to update animation state from a parent component. + /// + [Parameter] + public Action? OnUpdate { get; set; } + + /// + /// Callback invoked each time the canvas needs to be redrawn. + /// Subscribe here to render your content onto the . + /// + [Parameter] + public Action? OnPaintSurface { get; set; } + + /// + /// Additional HTML attributes to be applied to the underlying + /// element (e.g., style, class). + /// + [Parameter(CaptureUnmatchedValues = true)] + public IDictionary? AdditionalAttributes { get; set; } + + /// + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender && IsAnimationEnabled) + await StartLoopAsync(); + } + + /// + protected override async Task OnParametersSetAsync() + { + if (_isAnimationEnabled == IsAnimationEnabled) + return; + + _isAnimationEnabled = IsAnimationEnabled; + if (_isAnimationEnabled) + await StartLoopAsync(); + else + await StopLoopAsync(); + } + + /// + /// Called once per frame before the canvas is invalidated. Override this + /// method in a subclass to update animation state. + /// + /// Time elapsed since the previous frame. + /// A representing the asynchronous update work. + protected virtual Task UpdateAsync(TimeSpan deltaTime) + { + OnUpdate?.Invoke(deltaTime); + return Task.CompletedTask; + } + + private void HandlePaintSurface(SKPaintSurfaceEventArgs e) + { + OnPaintSurface?.Invoke(e); + } + + private async Task StartLoopAsync() + { + await StopLoopAsync(); + _cts = new CancellationTokenSource(); + _loopTask = RunLoopAsync(_cts.Token); + } + + private async Task StopLoopAsync() + { + _cts?.Cancel(); + _cts?.Dispose(); + _cts = null; + var loopTask = _loopTask; + _loopTask = null; + if (loopTask is not null) + { + try { await loopTask; } + catch (Exception) { } + } + } + + private async Task RunLoopAsync(CancellationToken ct) + { + using var timer = new PeriodicTimer(TimeSpan.FromMilliseconds(1000.0 / 60)); + var lastTick = DateTime.UtcNow; + + try + { + while (await timer.WaitForNextTickAsync(ct)) + { + var now = DateTime.UtcNow; + var delta = now - lastTick; + lastTick = now; + + await InvokeAsync(async () => + { + await UpdateAsync(delta); + _canvasView?.Invalidate(); + StateHasChanged(); + }); + } + } + catch (OperationCanceledException) + { + // Normal shutdown — ignore. + } + } + + /// Forces the canvas to repaint on the next frame. + public void Invalidate() => _canvasView?.Invalidate(); + + /// + public async ValueTask DisposeAsync() + { + await StopLoopAsync(); + } +} diff --git a/source/SkiaSharp.Extended.UI.Blazor/Controls/SKLottieRepeatMode.cs b/source/SkiaSharp.Extended.UI.Blazor/Controls/SKLottieRepeatMode.cs new file mode 100644 index 0000000000..395e3b7108 --- /dev/null +++ b/source/SkiaSharp.Extended.UI.Blazor/Controls/SKLottieRepeatMode.cs @@ -0,0 +1,13 @@ +namespace SkiaSharp.Extended.UI.Blazor.Controls; + +/// +/// Specifies how a Lottie animation repeats in the Blazor . +/// +public enum SKLottieRepeatMode +{ + /// Restart from the beginning after each cycle. + Restart, + + /// Alternate between forward and backward (ping-pong) on each cycle. + Reverse +} diff --git a/source/SkiaSharp.Extended.UI.Blazor/Controls/SKLottieView.razor b/source/SkiaSharp.Extended.UI.Blazor/Controls/SKLottieView.razor new file mode 100644 index 0000000000..5df1cf26b1 --- /dev/null +++ b/source/SkiaSharp.Extended.UI.Blazor/Controls/SKLottieView.razor @@ -0,0 +1,6 @@ +@namespace SkiaSharp.Extended.UI.Blazor.Controls + + diff --git a/source/SkiaSharp.Extended.UI.Blazor/Controls/SKLottieView.razor.cs b/source/SkiaSharp.Extended.UI.Blazor/Controls/SKLottieView.razor.cs new file mode 100644 index 0000000000..f9b8b1e413 --- /dev/null +++ b/source/SkiaSharp.Extended.UI.Blazor/Controls/SKLottieView.razor.cs @@ -0,0 +1,213 @@ +using Microsoft.AspNetCore.Components; +using SkiaSharp.Skottie; +using SkiaSharp.Views.Blazor; + +namespace SkiaSharp.Extended.UI.Blazor.Controls; + +/// +/// A Blazor component that plays Lottie animations, mirroring the MAUI +/// SKLottieView API. Wraps with +/// an to handle loading, playback, and rendering. +/// +/// +/// +/// Point at a Lottie JSON URL, configure repeat and speed, +/// and the component handles the rest—loading, frame updates, and rendering. +/// +/// +/// Access read-only state (, , etc.) +/// via an @ref to the component instance. +/// +/// +public partial class SKLottieView : ComponentBase, IAsyncDisposable +{ + private readonly SKLottiePlayer _player = new(); + private CancellationTokenSource? _loadCts; + private Animation? _loadedAnimation; + private bool _isLoading; + private string? _currentSource; + + [Inject] + private HttpClient Http { get; set; } = default!; + + /// URL of the Lottie JSON file to load. + [Parameter] + public string? Source { get; set; } + + /// How the animation repeats. Defaults to . + [Parameter] + public SKLottieRepeatMode RepeatMode { get; set; } = SKLottieRepeatMode.Restart; + + /// + /// Number of additional plays after the first. + /// Use -1 for infinite, 0 for no repeat. Defaults to -1. + /// + [Parameter] + public int RepeatCount { get; set; } = 0; + + /// Playback speed multiplier. Negative values play in reverse. Defaults to 1.0. + [Parameter] + public double AnimationSpeed { get; set; } = 1.0; + + /// Whether the animation loop is running. Defaults to . + [Parameter] + public bool IsAnimationEnabled { get; set; } = true; + + /// Fires when the animation is successfully loaded. + [Parameter] + public EventCallback AnimationLoaded { get; set; } + + /// Fires when all repeats complete. + [Parameter] + public EventCallback AnimationCompleted { get; set; } + + /// Fires when loading fails. + [Parameter] + public EventCallback AnimationFailed { get; set; } + + /// Fires after each frame update, allowing the host to refresh displayed state. + [Parameter] + public EventCallback AnimationUpdated { get; set; } + + /// Additional HTML attributes forwarded to the underlying canvas element. + [Parameter(CaptureUnmatchedValues = true)] + public IDictionary? AdditionalAttributes { get; set; } + + /// Gets whether the animation is currently loading. + public bool IsLoading => _isLoading; + + /// Gets whether an animation is loaded and ready to play. + public bool HasAnimation => _player.HasAnimation; + + /// Gets the total animation duration. + public TimeSpan Duration => _player.Duration; + + /// Gets the current playback position. + public TimeSpan Progress => _player.Progress; + + /// Gets whether the animation has completed all repeats. + public bool IsComplete => _player.IsComplete; + + /// Restarts the animation from the beginning with current settings. + public void Restart() + { + ApplySettings(); + _player.SetAnimation(_loadedAnimation); + } + + /// + protected override async Task OnParametersSetAsync() + { + ApplySettings(); + + if (Source != _currentSource) + { + _currentSource = Source; + await LoadAnimationAsync(); + } + } + + private void ApplySettings() + { + _player.Repeat = RepeatCount == 0 + ? SKLottieRepeat.Never + : RepeatMode == SKLottieRepeatMode.Reverse + ? SKLottieRepeat.Reverse(RepeatCount) + : SKLottieRepeat.Restart(RepeatCount); + _player.AnimationSpeed = AnimationSpeed; + } + + private async Task LoadAnimationAsync() + { + // Cancel any in-flight load before starting a new one + _loadCts?.Cancel(); + _loadCts?.Dispose(); + _loadCts = new CancellationTokenSource(); + var ct = _loadCts.Token; + + if (string.IsNullOrEmpty(Source)) + { + _loadedAnimation?.Dispose(); + _loadedAnimation = null; + _player.SetAnimation(null); + return; + } + + _isLoading = true; + StateHasChanged(); + + Exception? exception = null; + try + { + var json = await Http.GetStringAsync(Source, ct); + + if (ct.IsCancellationRequested) + return; + + _player.SetAnimation(null); + _loadedAnimation?.Dispose(); + _loadedAnimation = Animation.Parse(json); + _player.SetAnimation(_loadedAnimation); + } + catch (OperationCanceledException) when (ct.IsCancellationRequested) + { + return; + } + catch (Exception ex) + { + if (ct.IsCancellationRequested) + return; + + exception = ex; + _loadedAnimation?.Dispose(); + _loadedAnimation = null; + _player.SetAnimation(null); + } + finally + { + _isLoading = false; + } + + if (_player.HasAnimation) + await AnimationLoaded.InvokeAsync(); + else + { + exception ??= new InvalidOperationException("The Lottie animation source could not be parsed."); + await AnimationFailed.InvokeAsync(exception); + } + + StateHasChanged(); + } + + private void HandleUpdate(TimeSpan delta) + { + var wasComplete = _player.IsComplete; + _player.Update(delta); + + if (_player.IsComplete && !wasComplete) + _ = AnimationCompleted.InvokeAsync(); + + if (AnimationUpdated.HasDelegate) + _ = AnimationUpdated.InvokeAsync(); + } + + private void HandlePaintSurface(SKPaintSurfaceEventArgs e) + { + var canvas = e.Surface.Canvas; + canvas.Clear(SKColors.Transparent); + + if (_player.HasAnimation) + _player.Render(canvas, SKRect.Create(0, 0, e.Info.Width, e.Info.Height)); + } + + /// + public ValueTask DisposeAsync() + { + _loadCts?.Cancel(); + _loadCts?.Dispose(); + _player.SetAnimation(null); + _loadedAnimation?.Dispose(); + _loadedAnimation = null; + return ValueTask.CompletedTask; + } +} diff --git a/source/SkiaSharp.Extended.UI.Blazor/SkiaSharp.Extended.UI.Blazor.csproj b/source/SkiaSharp.Extended.UI.Blazor/SkiaSharp.Extended.UI.Blazor.csproj new file mode 100644 index 0000000000..f7d061d4ad --- /dev/null +++ b/source/SkiaSharp.Extended.UI.Blazor/SkiaSharp.Extended.UI.Blazor.csproj @@ -0,0 +1,27 @@ + + + + net8.0;net9.0;net10.0 + SkiaSharp.Extended.UI.Blazor + SkiaSharp.Extended.UI.Blazor + enable + enable + true + + + + SkiaSharp.Extended.UI.Blazor + Additional SkiaSharp controls for Blazor + This package adds additional SkiaSharp controls and utilities for Blazor applications. + + + + + + + + + + + + diff --git a/source/SkiaSharp.Extended.UI.Maui/Controls/Lottie/SKLottieAnimation.shared.cs b/source/SkiaSharp.Extended.UI.Maui/Controls/Lottie/SKLottieAnimation.shared.cs index 325ae3a2d8..e8953a9032 100644 --- a/source/SkiaSharp.Extended.UI.Maui/Controls/Lottie/SKLottieAnimation.shared.cs +++ b/source/SkiaSharp.Extended.UI.Maui/Controls/Lottie/SKLottieAnimation.shared.cs @@ -1,33 +1,17 @@ -namespace SkiaSharp.Extended.UI.Controls; +namespace SkiaSharp.Extended.UI.Controls; -/// -/// Wraps a loaded Skottie animation instance. -/// public class SKLottieAnimation { - /// - /// Initializes a new instance of the class with no animation loaded. - /// - public SKLottieAnimation() - { - } +public SKLottieAnimation() +{ +} - /// - /// Initializes a new instance of the class with the specified animation. - /// - /// The Skottie animation instance, or . - public SKLottieAnimation(Skottie.Animation? animation) - { - Animation = animation; - } +public SKLottieAnimation(Skottie.Animation? animation) +{ +Animation = animation; +} - /// - /// Gets the underlying Skottie animation instance, or if not loaded. - /// - public Skottie.Animation? Animation { get; } +public Skottie.Animation? Animation { get; } - /// - /// Gets a value indicating whether the animation is loaded. - /// - public bool IsLoaded => Animation is not null; +public bool IsLoaded => Animation is not null; } diff --git a/source/SkiaSharp.Extended.UI.Maui/Controls/Lottie/SKLottieAnimationFailedEventArgs.shared.cs b/source/SkiaSharp.Extended.UI.Maui/Controls/Lottie/SKLottieAnimationFailedEventArgs.shared.cs index eab0a988e7..10cc40c154 100644 --- a/source/SkiaSharp.Extended.UI.Maui/Controls/Lottie/SKLottieAnimationFailedEventArgs.shared.cs +++ b/source/SkiaSharp.Extended.UI.Maui/Controls/Lottie/SKLottieAnimationFailedEventArgs.shared.cs @@ -1,28 +1,15 @@ -namespace SkiaSharp.Extended.UI.Controls; +namespace SkiaSharp.Extended.UI.Controls; -/// -/// Event arguments for when a Lottie animation fails to load. -/// public class SKLottieAnimationFailedEventArgs : EventArgs { - /// - /// Initializes a new instance of the class. - /// - public SKLottieAnimationFailedEventArgs() - { - } +public SKLottieAnimationFailedEventArgs() +{ +} - /// - /// Initializes a new instance of the class with the specified exception. - /// - /// The exception that caused the failure, or . - public SKLottieAnimationFailedEventArgs(Exception? exception) - { - Exception = exception; - } +public SKLottieAnimationFailedEventArgs(Exception? exception) +{ +Exception = exception; +} - /// - /// Gets the exception that caused the failure, or . - /// - public Exception? Exception { get; } +public Exception? Exception { get; } } diff --git a/source/SkiaSharp.Extended.UI.Maui/Controls/Lottie/SKLottieRepeatMode.shared.cs b/source/SkiaSharp.Extended.UI.Maui/Controls/Lottie/SKLottieRepeatMode.shared.cs index 0107f8b7c9..46848c3e22 100644 --- a/source/SkiaSharp.Extended.UI.Maui/Controls/Lottie/SKLottieRepeatMode.shared.cs +++ b/source/SkiaSharp.Extended.UI.Maui/Controls/Lottie/SKLottieRepeatMode.shared.cs @@ -1,16 +1,7 @@ -namespace SkiaSharp.Extended.UI.Controls; +namespace SkiaSharp.Extended.UI.Controls; -/// -/// Specifies how a Lottie animation repeats. -/// public enum SKLottieRepeatMode { - /// - /// The animation restarts from the beginning when repeating. - /// - Restart, - /// - /// The animation reverses direction when repeating (ping-pong). - /// - Reverse +Restart, +Reverse } diff --git a/source/SkiaSharp.Extended.UI.Maui/Controls/Lottie/SKLottieView.shared.cs b/source/SkiaSharp.Extended.UI.Maui/Controls/Lottie/SKLottieView.shared.cs index 756cdf3c3d..1764c57a29 100644 --- a/source/SkiaSharp.Extended.UI.Maui/Controls/Lottie/SKLottieView.shared.cs +++ b/source/SkiaSharp.Extended.UI.Maui/Controls/Lottie/SKLottieView.shared.cs @@ -1,4 +1,4 @@ -namespace SkiaSharp.Extended.UI.Controls; +namespace SkiaSharp.Extended.UI.Controls; /// /// A view that plays Lottie animations using the Skottie library. @@ -20,8 +20,7 @@ public class SKLottieView : SKAnimatedSurfaceView typeof(TimeSpan), typeof(SKLottieView), TimeSpan.Zero, - defaultBindingMode: BindingMode.OneWayToSource, - propertyChanged: OnProgressDurationPropertyChanged); + defaultBindingMode: BindingMode.OneWayToSource); /// /// Identifies the bindable property. @@ -37,7 +36,7 @@ public class SKLottieView : SKAnimatedSurfaceView typeof(SKLottieView), TimeSpan.Zero, BindingMode.TwoWay, - propertyChanged: OnProgressDurationPropertyChanged); + propertyChanged: OnProgressPropertyChanged); private static readonly BindablePropertyKey IsCompletePropertyKey = BindableProperty.CreateReadOnly( nameof(IsComplete), @@ -58,7 +57,8 @@ public class SKLottieView : SKAnimatedSurfaceView nameof(RepeatCount), typeof(int), typeof(SKLottieView), - 0); + 0, + propertyChanged: OnRepeatPropertyChanged); /// /// Identifies the bindable property. @@ -67,7 +67,8 @@ public class SKLottieView : SKAnimatedSurfaceView nameof(RepeatMode), typeof(SKLottieRepeatMode), typeof(SKLottieView), - SKLottieRepeatMode.Restart); + SKLottieRepeatMode.Restart, + propertyChanged: OnRepeatPropertyChanged); /// /// Identifies the bindable property. @@ -76,13 +77,11 @@ public class SKLottieView : SKAnimatedSurfaceView nameof(AnimationSpeed), typeof(double), typeof(SKLottieView), - 1.0); + 1.0, + propertyChanged: OnAnimationSpeedPropertyChanged); - Skottie.Animation? animation; - bool isInForwardPhase = true; - int repeatsCompleted = 0; - CancellationTokenSource? loadCancellation; - bool isResetting; + private readonly SKLottiePlayer player = new(); + private CancellationTokenSource? loadCancellation; /// /// Initializes a new instance of the class. @@ -93,6 +92,16 @@ public SKLottieView() IsAnimationEnabled = true; + // Initialize player from default property values (propertyChanged callbacks don't + // fire for the initial default, so we push the defaults explicitly here). + player.Repeat = RepeatCount == 0 + ? SKLottieRepeat.Never + : SKLottieRepeat.Restart(RepeatCount); + player.AnimationSpeed = AnimationSpeed; + + player.AnimationUpdated += OnPlayerAnimationUpdated; + player.AnimationCompleted += OnPlayerAnimationCompleted; + #if DEBUG AnimationCompleted += (s, e) => DebugUtils.LogEvent(nameof(AnimationCompleted)); AnimationFailed += (s, e) => DebugUtils.LogEvent(nameof(AnimationFailed)); @@ -184,138 +193,28 @@ public double AnimationSpeed /// protected override void Update(TimeSpan deltaTime) { - if (animation is null) - return; - - // Apply animation speed with overflow protection - // Handle NaN and Infinity explicitly, and use safe bounds for long cast - var scaledTicks = deltaTime.Ticks * AnimationSpeed; - const long SafeMax = long.MaxValue - 1; // Avoid overflow when casting from double - const long SafeMin = long.MinValue + 2; // Avoid overflow when negating TimeSpan - if (!double.IsFinite(scaledTicks)) - scaledTicks = double.IsNaN(scaledTicks) || scaledTicks < 0 ? SafeMin : SafeMax; - else if (scaledTicks > SafeMax) - scaledTicks = SafeMax; - else if (scaledTicks < SafeMin) - scaledTicks = SafeMin; - deltaTime = TimeSpan.FromTicks((long)scaledTicks); - - // Apply phase direction (for RepeatMode.Reverse ping-pong) - if (!isInForwardPhase) - deltaTime = -deltaTime; - - var newProgress = Progress + deltaTime; - if (newProgress > Duration) - newProgress = Duration; - if (newProgress < TimeSpan.Zero) - newProgress = TimeSpan.Zero; - - Progress = newProgress; + player.Update(deltaTime); } /// protected override void OnPaintSurface(SKCanvas canvas, SKSize size) { - if (animation is null) - return; - - animation.Render(canvas, SKRect.Create(SKPoint.Empty, size)); - -#if DEBUG - WriteDebugStatus($"Repeats: {repeatsCompleted}/{RepeatCount}"); - WriteDebugStatus($"Forward: {isInForwardPhase} ({RepeatMode})"); -#endif + player.Render(canvas, SKRect.Create(SKPoint.Empty, size)); } - private void UpdateProgress(TimeSpan progress) + private void OnPlayerAnimationUpdated(object? sender, EventArgs e) { - if (animation is null) - { - IsComplete = true; - return; - } - - animation.SeekFrameTime(progress.TotalSeconds); - - // Skip completion/repeat logic during Reset to avoid spurious events - if (isResetting) - return; - - var repeatMode = RepeatMode; - var duration = Duration; - - // Determine effective movement direction - // Negative AnimationSpeed inverts the movement relative to the phase - var movingForward = AnimationSpeed >= 0 ? isInForwardPhase : !isInForwardPhase; - - // Have we reached a boundary based on our movement direction? - var atStart = !movingForward && progress <= TimeSpan.Zero; - var atEnd = movingForward && progress >= duration; - - // A run is "finished" based on RepeatMode: - // - Restart: finished when reaching the destination (end for forward, start for backward) - // - Reverse: finished when completing full cycle (forward + back to start, or backward + back to end) - // With positive speed: start -> end -> start (finish at start) - // With negative speed: end -> start -> end (finish at end) - var reverseFinishPoint = AnimationSpeed >= 0 ? atStart : atEnd; - var isFinishedRun = repeatMode == SKLottieRepeatMode.Restart - ? (movingForward ? atEnd : atStart) - : reverseFinishPoint; - - // For Reverse mode: flip direction when hitting a boundary (but not the finish boundary) - // With positive speed: flip at end (start going back toward start) - // With negative speed: flip at start (start going back toward end) - var needsFlip = repeatMode == SKLottieRepeatMode.Reverse && - (AnimationSpeed >= 0 ? atEnd : atStart) && !isFinishedRun; - - if (needsFlip) - { - // we need to reverse to finish the run - isInForwardPhase = !isInForwardPhase; - - IsComplete = false; - } - else - { - // make sure repeats are positive to make things easier - var totalRepeatCount = RepeatCount; - if (totalRepeatCount < 0) - totalRepeatCount = int.MaxValue; - - // infinite - var infinite = totalRepeatCount == int.MaxValue; - if (infinite) - repeatsCompleted = 0; - - // if we are at the end and we are repeating, then repeat - if (isFinishedRun && repeatsCompleted < totalRepeatCount) - { - if (!infinite) - repeatsCompleted++; - - isFinishedRun = false; - - if (repeatMode == SKLottieRepeatMode.Restart) - { - // Restart at the beginning of the movement direction: - // - Positive speed: restart at 0, move toward Duration - // - Negative speed: restart at Duration, move toward 0 - Progress = AnimationSpeed >= 0 ? TimeSpan.Zero : Duration; - } - else if (repeatMode == SKLottieRepeatMode.Reverse) - isInForwardPhase = !isInForwardPhase; - } - - IsComplete = - isFinishedRun && - repeatsCompleted >= totalRepeatCount; - - if (IsComplete) - AnimationCompleted?.Invoke(this, EventArgs.Empty); - } + Duration = player.Duration; + Progress = player.Progress; + IsComplete = player.IsComplete; + } - if (!IsAnimationEnabled) - Invalidate(); + private void OnPlayerAnimationCompleted(object? sender, EventArgs e) + { + Duration = player.Duration; + Progress = player.Progress; + IsComplete = player.IsComplete; + AnimationCompleted?.Invoke(this, EventArgs.Empty); } private async Task LoadAnimationAsync(SKLottieImageSource? imageSource) @@ -328,22 +227,21 @@ private async Task LoadAnimationAsync(SKLottieImageSource? imageSource) if (imageSource is null || imageSource.IsEmpty) { - animation = null; - Reset(); + player.SetAnimation(null); } else { Exception? exception; + SKLottieAnimation? loadResult = null; try { - var loadResult = await Task.Run(() => imageSource.LoadAnimationAsync(cancellationToken), cancellationToken); + loadResult = await Task.Run(() => imageSource.LoadAnimationAsync(cancellationToken), cancellationToken); // Check if cancelled before applying result if (cancellationToken.IsCancellationRequested) return; exception = null; - animation = loadResult.Animation; } catch (OperationCanceledException) { @@ -353,39 +251,22 @@ private async Task LoadAnimationAsync(SKLottieImageSource? imageSource) catch (Exception ex) { exception = ex; - animation = null; + loadResult = null; } - Reset(); + player.SetAnimation(loadResult?.Animation); - if (animation is null) - AnimationFailed?.Invoke(this, new SKLottieAnimationFailedEventArgs(exception)); + if (!player.HasAnimation && loadResult is not null) + exception ??= new InvalidOperationException("The Lottie animation source could not be parsed."); + + if (player.HasAnimation) + AnimationLoaded?.Invoke(this, SKLottieAnimationLoadedEventArgs.Create(loadResult!.Animation!)); else - AnimationLoaded?.Invoke(this, SKLottieAnimationLoadedEventArgs.Create(animation)); + AnimationFailed?.Invoke(this, new SKLottieAnimationFailedEventArgs(exception)); } if (!IsAnimationEnabled) Invalidate(); - - void Reset() - { - isResetting = true; - try - { - isInForwardPhase = true; - repeatsCompleted = 0; - - // Initialize Progress based on AnimationSpeed: - // - Positive/zero speed: start at 0, move toward Duration - // - Negative speed: start at Duration, move toward 0 - Duration = animation?.Duration ?? TimeSpan.Zero; - Progress = AnimationSpeed < 0 ? Duration : TimeSpan.Zero; - } - finally - { - isResetting = false; - } - } } private static async void OnSourcePropertyChanged(BindableObject bindable, object? oldValue, object? newValue) @@ -406,11 +287,45 @@ private async void OnSourceChanged(object? sender, EventArgs e) await LoadAnimationAsync(sender as SKLottieImageSource); } - private static void OnProgressDurationPropertyChanged(BindableObject bindable, object? oldValue, object? newValue) + private static void OnProgressPropertyChanged(BindableObject bindable, object? oldValue, object? newValue) { if (bindable is not SKLottieView lv) return; - lv.UpdateProgress(lv.Progress); + var newProgress = (TimeSpan)newValue!; + + // Skip if the player already has this value (e.g. set via AnimationUpdated event). + if (lv.player.Progress == newProgress) + return; + + // User-driven change (e.g. scrubbing): propagate to player. + // AnimationUpdated fires from Seek(), which syncs Duration, Progress, + // and IsComplete back to the view via OnPlayerAnimationUpdated. + lv.player.Seek(newProgress); + + // Trigger repaint if animation is disabled (e.g. user is scrubbing a paused animation). + if (!lv.IsAnimationEnabled) + lv.Invalidate(); + } + + private static void OnRepeatPropertyChanged(BindableObject bindable, object? oldValue, object? newValue) + { + if (bindable is not SKLottieView lv) + return; + + lv.player.Repeat = lv.RepeatCount == 0 + ? SKLottieRepeat.Never + : lv.RepeatMode == SKLottieRepeatMode.Reverse + ? SKLottieRepeat.Reverse(lv.RepeatCount) + : SKLottieRepeat.Restart(lv.RepeatCount); } + + private static void OnAnimationSpeedPropertyChanged(BindableObject bindable, object? oldValue, object? newValue) + { + if (bindable is not SKLottieView lv) + return; + + lv.player.AnimationSpeed = (double)newValue!; + } + } diff --git a/source/SkiaSharp.Extended.UI.Maui/SkiaSharp.Extended.UI.Maui.csproj b/source/SkiaSharp.Extended.UI.Maui/SkiaSharp.Extended.UI.Maui.csproj index 1c0365a82a..cdc7157d00 100644 --- a/source/SkiaSharp.Extended.UI.Maui/SkiaSharp.Extended.UI.Maui.csproj +++ b/source/SkiaSharp.Extended.UI.Maui/SkiaSharp.Extended.UI.Maui.csproj @@ -10,6 +10,7 @@ SkiaSharp.Extended.UI SkiaSharp.Extended.UI false + true @@ -44,6 +45,7 @@ + diff --git a/source/SkiaSharp.Extended/Lottie/SKLottiePlayer.cs b/source/SkiaSharp.Extended/Lottie/SKLottiePlayer.cs new file mode 100644 index 0000000000..cf4877d1aa --- /dev/null +++ b/source/SkiaSharp.Extended/Lottie/SKLottiePlayer.cs @@ -0,0 +1,271 @@ +using System; + +namespace SkiaSharp.Extended; + +/// +/// A platform-agnostic Skottie (Lottie) animation player that manages playback state +/// and rendering. Can be used directly from any .NET host including .NET MAUI, Blazor, +/// console apps, or custom renderers. +/// +/// +/// +/// Typical usage: +/// +/// Create a player and set and . +/// Call with a loaded . +/// On each frame tick, call with the elapsed time. +/// Call inside your paint/draw callback. +/// +/// +/// +/// The player is not thread-safe; all calls should occur on the same thread (typically the UI thread). +/// +/// +public class SKLottiePlayer +{ + private Skottie.Animation? animation; + private bool isInForwardPhase = true; + private int repeatsCompleted = 0; + + /// Gets the total duration of the loaded animation. + public TimeSpan Duration { get; private set; } = TimeSpan.Zero; + + /// Gets the current playback position. + public TimeSpan Progress { get; private set; } + + /// Gets whether the animation has completed all repeats. + public bool IsComplete { get; private set; } = false; + + private SKLottieRepeat repeat = SKLottieRepeat.Never; + + /// Gets or sets how the animation repeats. Defaults to . + /// + /// Changing this property resets the repeat counter and completion state but preserves the + /// current playback direction. The direction phase is only reset when the animation actually + /// hits a boundary and restarts, preventing abrupt mid-animation direction changes. + /// + public SKLottieRepeat Repeat + { + get => repeat; + set + { + if (repeat != value) + { + repeat = value; + repeatsCompleted = 0; + IsComplete = false; + } + } + } + + /// + /// Gets or sets the playback speed multiplier. + /// 1.0 = normal speed, 2.0 = double speed, 0.5 = half speed, negative = reverse. + /// + public double AnimationSpeed { get; set; } = 1.0; + + /// Gets whether an animation is currently loaded. + public bool HasAnimation => animation is not null; + + /// Fires when the animation completes all repeats. + public event EventHandler? AnimationCompleted; + + /// + /// Fires after each call, notifying subscribers of + /// updated state (Progress, Duration, IsComplete). + /// + public event EventHandler? AnimationUpdated; + + /// + /// Sets the animation to play. Pass to clear the current animation. + /// Resets playback state (Progress, IsComplete, repeat counters). + /// + /// + /// The to play, or to clear. + /// The player does not take ownership of the animation; the caller is responsible for disposing it. + /// + /// + /// Calling this method always resets to + /// (or when is negative) and clears + /// . It also raises . + /// + public void SetAnimation(Skottie.Animation? newAnimation) + { + animation = newAnimation; + Reset(); + } + + /// + /// Seeks the animation to the specified position and raises . + /// Completion and repeat logic is applied as part of the seek. + /// + /// The absolute playback position to seek to. + /// + /// Unlike , Seek sets an absolute position rather than advancing + /// by a delta. The position is clamped to [, ] + /// and repeat/completion state is evaluated immediately. + /// Setting to a boundary via Seek does not increment + /// the internal repeat counter; use for frame-by-frame playback. + /// + public void Seek(TimeSpan position) + { + if (position < TimeSpan.Zero) position = TimeSpan.Zero; + if (position > Duration) position = Duration; + Progress = position; + UpdateProgress(Progress); + AnimationUpdated?.Invoke(this, EventArgs.Empty); + } + + /// + /// Advances the animation by the given time delta, applying and . + /// Call this on each frame tick. + /// + /// + /// The time elapsed since the last call. A positive value advances forward; a negative value + /// moves the position backward (subject to clamping at the boundaries). + /// + /// + /// + /// The effective delta is scaled by before being applied: + /// a speed of 2.0 doubles the rate, 0.5 halves it, and -1.0 plays in reverse. + /// + /// + /// When is , the internal direction + /// is flipped automatically when the animation reaches a boundary, producing a ping-pong effect. + /// + /// + /// Has no effect when no animation is loaded ( is ). + /// + /// + public void Update(TimeSpan deltaTime) + { + if (animation is null) + return; + + // Apply animation speed with overflow protection + var scaledTicks = deltaTime.Ticks * AnimationSpeed; + const long SafeMax = long.MaxValue - 1; + const long SafeMin = long.MinValue + 2; + if (double.IsNaN(scaledTicks) || double.IsInfinity(scaledTicks)) + scaledTicks = double.IsNaN(scaledTicks) || scaledTicks < 0 ? SafeMin : SafeMax; + else if (scaledTicks > SafeMax) + scaledTicks = SafeMax; + else if (scaledTicks < SafeMin) + scaledTicks = SafeMin; + deltaTime = TimeSpan.FromTicks((long)scaledTicks); + + // Apply phase direction (for Reverse ping-pong) + if (!isInForwardPhase) + deltaTime = -deltaTime; + + var newProgress = Progress + deltaTime; + if (newProgress > Duration) + newProgress = Duration; + if (newProgress < TimeSpan.Zero) + newProgress = TimeSpan.Zero; + + Seek(newProgress); + } + + /// Renders the current animation frame to the given canvas within the specified rectangle. + /// The to draw onto. + /// The destination rectangle within the canvas. + /// + /// Has no effect when no animation is loaded ( is ). + /// Call this inside your paint/draw callback after has been called for the current frame. + /// + public void Render(SKCanvas canvas, SKRect rect) + { + animation?.Render(canvas, rect); + } + + private void UpdateProgress(TimeSpan progress) + { + if (animation is null) + return; + + animation.SeekFrameTime(progress.TotalSeconds); + + var repeat = Repeat; + var duration = Duration; + + // Determine effective movement direction + var movingForward = AnimationSpeed >= 0 ? isInForwardPhase : !isInForwardPhase; + + // Have we reached a boundary based on our movement direction? + var atStart = !movingForward && progress <= TimeSpan.Zero; + var atEnd = movingForward && progress >= duration; + + // A run is "finished" based on repeat kind. + // For Reverse, the finish point is the start of the return trip (atStart for +speed, atEnd for -speed). + // For Never and Restart, the finish point is simply the end of the movement direction. + var reverseFinishPoint = AnimationSpeed >= 0 ? atStart : atEnd; + var isFinishedRun = repeat.IsReverseRepeating + ? reverseFinishPoint + : (movingForward ? atEnd : atStart); + + // For Reverse mode: flip direction when hitting a boundary (but not the finish boundary) + var needsFlip = repeat.IsReverseRepeating && + (AnimationSpeed >= 0 ? atEnd : atStart) && !isFinishedRun; + + if (needsFlip) + { + isInForwardPhase = !isInForwardPhase; + IsComplete = false; + } + else + { + var totalRepeatCount = repeat.Count; + if (totalRepeatCount < 0) + totalRepeatCount = int.MaxValue; + + var infinite = totalRepeatCount == int.MaxValue; + if (infinite) + repeatsCompleted = 0; + + if (isFinishedRun && repeatsCompleted < totalRepeatCount) + { + if (!infinite) + repeatsCompleted++; + + isFinishedRun = false; + + if (repeat.IsRestartRepeating) + { + // Reset phase and position to the natural start for the current speed. + // Resetting phase here (at the boundary) rather than in the Repeat setter + // prevents abrupt mid-animation direction changes when the user switches modes. + isInForwardPhase = true; + // Reset position directly without going through Seek(), to avoid + // firing AnimationUpdated twice (once here, once in the outer Seek). + Progress = AnimationSpeed >= 0 ? TimeSpan.Zero : Duration; + animation.SeekFrameTime(Progress.TotalSeconds); + } + else if (repeat.IsReverseRepeating) + isInForwardPhase = !isInForwardPhase; + } + + var prevIsComplete = IsComplete; + IsComplete = + isFinishedRun && + repeatsCompleted >= totalRepeatCount; + + if (IsComplete && !prevIsComplete) + AnimationCompleted?.Invoke(this, EventArgs.Empty); + } + } + + private void Reset() + { + isInForwardPhase = true; + repeatsCompleted = 0; + IsComplete = false; + + Duration = animation?.Duration ?? TimeSpan.Zero; + + // Directly set the initial position without triggering completion logic. + Progress = AnimationSpeed < 0 ? Duration : TimeSpan.Zero; + animation?.SeekFrameTime(Progress.TotalSeconds); + AnimationUpdated?.Invoke(this, EventArgs.Empty); + } +} diff --git a/source/SkiaSharp.Extended/Lottie/SKLottieRepeat.cs b/source/SkiaSharp.Extended/Lottie/SKLottieRepeat.cs new file mode 100644 index 0000000000..92342582fb --- /dev/null +++ b/source/SkiaSharp.Extended/Lottie/SKLottieRepeat.cs @@ -0,0 +1,67 @@ +using System; + +namespace SkiaSharp.Extended; + +/// +/// Describes how a Lottie animation repeats. Use the static factory members +/// , , and to +/// create instances. +/// +public readonly struct SKLottieRepeat : IEquatable +{ + private enum RepeatKind { Never, Restart, Reverse } + + private readonly RepeatKind kind; + private readonly int count; + + private SKLottieRepeat(RepeatKind kind, int count) + { + this.kind = kind; + this.count = count; + } + + /// The animation plays once without repeating. + public static SKLottieRepeat Never => new(RepeatKind.Never, 0); + + /// + /// The animation repeats by restarting from the beginning. + /// + /// Number of additional plays after the first. Use -1 for infinite. + public static SKLottieRepeat Restart(int count = -1) => new(RepeatKind.Restart, count); + + /// + /// The animation repeats by reversing direction (ping-pong). + /// + /// Number of additional plays after the first. Use -1 for infinite. + public static SKLottieRepeat Reverse(int count = -1) => new(RepeatKind.Reverse, count); + + /// Gets whether the animation repeats at all. + public bool IsRepeating => kind != RepeatKind.Never; + + /// Gets whether the animation repeats by restarting from the beginning. + public bool IsRestartRepeating => kind == RepeatKind.Restart; + + /// Gets whether the animation repeats by reversing direction (ping-pong). + public bool IsReverseRepeating => kind == RepeatKind.Reverse; + + /// + /// Gets the number of additional plays after the first. -1 means infinite. + /// Returns 0 for . + /// + public int Count => count; + + /// + public bool Equals(SKLottieRepeat other) => kind == other.kind && count == other.count; + + /// + public override bool Equals(object? obj) => obj is SKLottieRepeat other && Equals(other); + + /// + public override int GetHashCode() => (int)kind * 397 ^ count; + + /// + public static bool operator ==(SKLottieRepeat left, SKLottieRepeat right) => left.Equals(right); + + /// + public static bool operator !=(SKLottieRepeat left, SKLottieRepeat right) => !left.Equals(right); +} diff --git a/source/SkiaSharp.Extended/SkiaSharp.Extended.csproj b/source/SkiaSharp.Extended/SkiaSharp.Extended.csproj index 5e76557727..b5e804645e 100644 --- a/source/SkiaSharp.Extended/SkiaSharp.Extended.csproj +++ b/source/SkiaSharp.Extended/SkiaSharp.Extended.csproj @@ -4,6 +4,7 @@ netstandard2.0;net9.0;net10.0 SkiaSharp.Extended SkiaSharp.Extended + true @@ -15,6 +16,7 @@ + \ No newline at end of file diff --git a/tests/SkiaSharp.Extended.Tests/Lottie/SKLottiePlayerTest.cs b/tests/SkiaSharp.Extended.Tests/Lottie/SKLottiePlayerTest.cs new file mode 100644 index 0000000000..de5cc69e62 --- /dev/null +++ b/tests/SkiaSharp.Extended.Tests/Lottie/SKLottiePlayerTest.cs @@ -0,0 +1,683 @@ +using System; +using SkiaSharp.Skottie; +using Xunit; + +namespace SkiaSharp.Extended.Tests; + +public class SKLottiePlayerTest +{ + // Minimal valid Lottie JSON: 60 frames at 60 fps → 1-second duration. + private static readonly string MinimalLottieJson = + """{"v":"5.7.4","fr":60,"ip":0,"op":60,"w":100,"h":100,"nm":"test","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"layer","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[50,50,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[],"ip":0,"op":60,"st":0,"bm":0}]}"""; + + private static Animation CreateAnimation() => + Animation.Parse(MinimalLottieJson) + ?? throw new InvalidOperationException("Failed to parse test animation."); + + // ── Initial state ──────────────────────────────────────────────────────── + + [Fact] + public void InitialState_IsEmpty() + { + var player = new SKLottiePlayer(); + + Assert.Equal(TimeSpan.Zero, player.Progress); + Assert.Equal(TimeSpan.Zero, player.Duration); + Assert.False(player.IsComplete); + Assert.False(player.HasAnimation); + } + + // ── SetAnimation ───────────────────────────────────────────────────────── + + [Fact] + public void SetAnimation_Null_ClearsState() + { + using var anim = CreateAnimation(); + var player = new SKLottiePlayer(); + player.SetAnimation(anim); + player.SetAnimation(null); + + Assert.Equal(TimeSpan.Zero, player.Duration); + Assert.Equal(TimeSpan.Zero, player.Progress); + Assert.False(player.HasAnimation); + } + + [Fact] + public void SetAnimation_SetsHasAnimation() + { + using var anim = CreateAnimation(); + var player = new SKLottiePlayer(); + player.SetAnimation(anim); + + Assert.True(player.HasAnimation); + } + + [Fact] + public void SetAnimation_SetsDuration() + { + using var anim = CreateAnimation(); + var player = new SKLottiePlayer(); + player.SetAnimation(anim); + + Assert.Equal(TimeSpan.FromSeconds(1), player.Duration); + } + + [Fact] + public void SetAnimation_ResetsProgress() + { + using var anim = CreateAnimation(); + var player = new SKLottiePlayer(); + player.SetAnimation(anim); + player.Seek(TimeSpan.FromSeconds(0.5)); + + player.SetAnimation(anim); + + Assert.Equal(TimeSpan.Zero, player.Progress); + Assert.False(player.IsComplete); + } + + [Fact] + public void SetAnimation_RaisesAnimationUpdated() + { + using var anim = CreateAnimation(); + var player = new SKLottiePlayer(); + var raised = 0; + player.AnimationUpdated += (_, _) => raised++; + + player.SetAnimation(anim); + + Assert.Equal(1, raised); + } + + // ── Seek ───────────────────────────────────────────────────────────────── + + [Fact] + public void Seek_UpdatesProgress() + { + using var anim = CreateAnimation(); + var player = new SKLottiePlayer(); + player.SetAnimation(anim); + + player.Seek(TimeSpan.FromSeconds(0.5)); + + Assert.Equal(TimeSpan.FromSeconds(0.5), player.Progress); + } + + [Fact] + public void Seek_RaisesAnimationUpdated() + { + using var anim = CreateAnimation(); + var player = new SKLottiePlayer(); + player.SetAnimation(anim); + var raised = 0; + player.AnimationUpdated += (_, _) => raised++; + + player.Seek(TimeSpan.FromSeconds(0.5)); + + Assert.Equal(1, raised); + } + + [Fact] + public void Seek_WithNegativePosition_ClampsToZero() + { + using var anim = CreateAnimation(); + var player = new SKLottiePlayer(); + player.SetAnimation(anim); + + player.Seek(TimeSpan.FromSeconds(-5)); + + Assert.Equal(TimeSpan.Zero, player.Progress); + } + + [Fact] + public void Seek_WithPositionBeyondDuration_ClampsToDuration() + { + using var anim = CreateAnimation(); + var player = new SKLottiePlayer(); + player.SetAnimation(anim); + + player.Seek(player.Duration + TimeSpan.FromSeconds(10)); + + Assert.Equal(player.Duration, player.Progress); + } + + // ── Update / playback ──────────────────────────────────────────────────── + + [Fact] + public void Update_AdvancesProgress() + { + using var anim = CreateAnimation(); + var player = new SKLottiePlayer(); + player.SetAnimation(anim); + + player.Update(TimeSpan.FromSeconds(0.5)); + + Assert.Equal(TimeSpan.FromSeconds(0.5), player.Progress); + Assert.False(player.IsComplete); + } + + [Fact] + public void Update_DoesNothing_WhenNoAnimation() + { + var player = new SKLottiePlayer(); + player.Update(TimeSpan.FromSeconds(1)); + + Assert.Equal(TimeSpan.Zero, player.Progress); + } + + [Fact] + public void Update_CapsProgressAtDuration_WithRepeatNever() + { + using var anim = CreateAnimation(); + var player = new SKLottiePlayer(); + player.SetAnimation(anim); + player.Repeat = SKLottieRepeat.Never; + + player.Update(TimeSpan.FromSeconds(10)); + + Assert.Equal(player.Duration, player.Progress); + } + + // ── Repeat.Never ───────────────────────────────────────────────────────── + + [Fact] + public void RepeatNever_CompletesAfterOnePlay() + { + using var anim = CreateAnimation(); + var player = new SKLottiePlayer(); + player.SetAnimation(anim); + player.Repeat = SKLottieRepeat.Never; + var completed = 0; + player.AnimationCompleted += (_, _) => completed++; + + player.Update(TimeSpan.FromSeconds(10)); + + Assert.True(player.IsComplete); + Assert.Equal(1, completed); + } + + [Fact] + public void RepeatNever_DoesNotFireCompletedTwice() + { + using var anim = CreateAnimation(); + var player = new SKLottiePlayer(); + player.SetAnimation(anim); + player.Repeat = SKLottieRepeat.Never; + var completed = 0; + player.AnimationCompleted += (_, _) => completed++; + + player.Update(TimeSpan.FromSeconds(10)); + player.Update(TimeSpan.FromSeconds(10)); + + Assert.Equal(1, completed); + } + + // ── Repeat.Restart ─────────────────────────────────────────────────────── + + [Fact] + public void RepeatRestart_InfiniteLoop_NeverCompletes() + { + using var anim = CreateAnimation(); + var player = new SKLottiePlayer(); + player.SetAnimation(anim); + player.Repeat = SKLottieRepeat.Restart(); + var completed = 0; + player.AnimationCompleted += (_, _) => completed++; + + // Advance well past end multiple times + for (var i = 0; i < 5; i++) + player.Update(TimeSpan.FromSeconds(10)); + + Assert.False(player.IsComplete); + Assert.Equal(0, completed); + } + + [Fact] + public void RepeatRestart_FiniteCount_CompletesAfterNPlus1Plays() + { + using var anim = CreateAnimation(); + var player = new SKLottiePlayer(); + player.SetAnimation(anim); + player.Repeat = SKLottieRepeat.Restart(count: 2); // 3 plays total + var completed = 0; + player.AnimationCompleted += (_, _) => completed++; + + // Play 1 — not done + player.Update(TimeSpan.FromSeconds(10)); + Assert.False(player.IsComplete); + + // Play 2 — not done + player.Update(TimeSpan.FromSeconds(10)); + Assert.False(player.IsComplete); + + // Play 3 — done + player.Update(TimeSpan.FromSeconds(10)); + Assert.True(player.IsComplete); + Assert.Equal(1, completed); + } + + [Fact] + public void RepeatRestart_ResetsProgressToZero() + { + using var anim = CreateAnimation(); + var player = new SKLottiePlayer(); + player.SetAnimation(anim); + player.Repeat = SKLottieRepeat.Restart(count: 1); + + // Seek exactly to the end to trigger restart + player.Seek(player.Duration); + + Assert.Equal(TimeSpan.Zero, player.Progress); + Assert.False(player.IsComplete); + } + + // ── Repeat.Reverse (ping-pong) ─────────────────────────────────────────── + + [Fact] + public void RepeatReverse_InfiniteLoop_NeverCompletes() + { + using var anim = CreateAnimation(); + var player = new SKLottiePlayer(); + player.SetAnimation(anim); + player.Repeat = SKLottieRepeat.Reverse(); + var completed = 0; + player.AnimationCompleted += (_, _) => completed++; + + for (var i = 0; i < 10; i++) + player.Update(TimeSpan.FromSeconds(10)); + + Assert.False(player.IsComplete); + Assert.Equal(0, completed); + } + + [Fact] + public void RepeatReverse_BouncesDirectionAtEnd() + { + using var anim = CreateAnimation(); + var player = new SKLottiePlayer(); + player.SetAnimation(anim); + player.Repeat = SKLottieRepeat.Reverse(); + + // Play forward to end + player.Update(player.Duration + TimeSpan.FromTicks(1)); + var progressAtBounce = player.Progress; + + // After bounce, next update should move backward + player.Update(TimeSpan.FromSeconds(0.3)); + + Assert.True(player.Progress < progressAtBounce, + $"Expected progress to decrease after bounce, but got {player.Progress} >= {progressAtBounce}"); + } + + // ── AnimationSpeed ──────────────────────────────────────────────────────── + + [Fact] + public void NegativeSpeed_StartsAtDuration() + { + using var anim = CreateAnimation(); + var player = new SKLottiePlayer(); + player.AnimationSpeed = -1.0; + player.SetAnimation(anim); + + Assert.Equal(player.Duration, player.Progress); + } + + [Fact] + public void NegativeSpeed_MovesProgressBackward() + { + using var anim = CreateAnimation(); + var player = new SKLottiePlayer(); + player.AnimationSpeed = -1.0; + player.Repeat = SKLottieRepeat.Never; + player.SetAnimation(anim); + + player.Update(TimeSpan.FromSeconds(0.5)); + + Assert.Equal(player.Duration - TimeSpan.FromSeconds(0.5), player.Progress); + Assert.False(player.IsComplete); + } + + [Fact] + public void NegativeSpeed_CompletesAtZero() + { + using var anim = CreateAnimation(); + var player = new SKLottiePlayer(); + player.AnimationSpeed = -1.0; + player.Repeat = SKLottieRepeat.Never; + player.SetAnimation(anim); + var completed = 0; + player.AnimationCompleted += (_, _) => completed++; + + player.Update(TimeSpan.FromSeconds(10)); + + Assert.True(player.IsComplete); + Assert.Equal(1, completed); + } + + // ── AnimationUpdated event ──────────────────────────────────────────────── + + [Fact] + public void AnimationUpdated_FiredOnUpdate() + { + using var anim = CreateAnimation(); + var player = new SKLottiePlayer(); + player.SetAnimation(anim); + var raised = 0; + player.AnimationUpdated += (_, _) => raised++; + + player.Update(TimeSpan.FromSeconds(0.1)); + + Assert.True(raised >= 1); + } + + [Fact] + public void RepeatRestart_AnimationUpdated_FiresExactlyOncePerUpdate() + { + // Regression: Seek() called inside UpdateProgress for restart fired the + // event once internally, then the outer Seek() fired it again — 2x per cycle. + using var anim = CreateAnimation(); + var player = new SKLottiePlayer(); + player.SetAnimation(anim); + player.Repeat = SKLottieRepeat.Restart(2); + + // Advance to well past the first cycle boundary so a restart is triggered. + var raised = 0; + player.AnimationUpdated += (_, _) => raised++; + + player.Update(TimeSpan.FromSeconds(1.5)); + + Assert.Equal(1, raised); + } + + [Fact] + public void Seek_BeforeSetAnimation_DoesNotSetIsComplete() + { + // Regression: UpdateProgress with null animation was setting IsComplete=true. + var player = new SKLottiePlayer(); + + player.Seek(TimeSpan.FromSeconds(1)); + + Assert.False(player.IsComplete); + } + + // ── Negative deltaTime ──────────────────────────────────────────────────── + + [Fact] + public void Update_NegativeDelta_ClampsToZero() + { + using var anim = CreateAnimation(); + var player = new SKLottiePlayer(); + player.SetAnimation(anim); + + player.Update(TimeSpan.FromSeconds(-1)); + + Assert.Equal(TimeSpan.Zero, player.Progress); + Assert.False(player.IsComplete); + } + + [Fact] + public void Update_NegativeDeltaAfterPositive_MovesBack() + { + using var anim = CreateAnimation(); + var player = new SKLottiePlayer(); + player.SetAnimation(anim); + player.Update(TimeSpan.FromSeconds(0.5)); + + player.Update(TimeSpan.FromSeconds(-0.3)); + + Assert.Equal(TimeSpan.FromSeconds(0.2), player.Progress); + Assert.False(player.IsComplete); + } + + // ── AnimationSpeed variants ─────────────────────────────────────────────── + + [Fact] + public void AnimationSpeed_Double_AdvancesAtDoubleRate() + { + using var anim = CreateAnimation(); + var player = new SKLottiePlayer(); + player.AnimationSpeed = 2.0; + player.SetAnimation(anim); + + player.Update(TimeSpan.FromSeconds(0.3)); + + Assert.Equal(TimeSpan.FromSeconds(0.6), player.Progress); + } + + [Fact] + public void AnimationSpeed_Half_AdvancesAtHalfRate() + { + using var anim = CreateAnimation(); + var player = new SKLottiePlayer(); + player.AnimationSpeed = 0.5; + player.SetAnimation(anim); + + player.Update(TimeSpan.FromSeconds(0.5)); + + Assert.Equal(TimeSpan.FromSeconds(0.25), player.Progress); + } + + [Fact] + public void AnimationSpeed_Zero_DoesNotAdvance() + { + using var anim = CreateAnimation(); + var player = new SKLottiePlayer(); + player.AnimationSpeed = 0; + player.SetAnimation(anim); + + player.Update(TimeSpan.FromSeconds(1)); + + Assert.Equal(TimeSpan.Zero, player.Progress); + Assert.False(player.IsComplete); + } + + [Fact] + public void AnimationSpeed_CanBeChangedDynamically() + { + using var anim = CreateAnimation(); + var player = new SKLottiePlayer(); + player.AnimationSpeed = 1.0; + player.SetAnimation(anim); + + player.Update(TimeSpan.FromSeconds(0.3)); + player.AnimationSpeed = 2.0; + player.Update(TimeSpan.FromSeconds(0.2)); + + // 0.3 + (0.2 × 2.0) = 0.7 + Assert.Equal(TimeSpan.FromSeconds(0.7), player.Progress); + } + + [Fact] + public void AnimationSpeed_NegativeMidPlayback_MovesBackward() + { + using var anim = CreateAnimation(); + var player = new SKLottiePlayer(); + player.AnimationSpeed = 1.0; + player.Repeat = SKLottieRepeat.Never; + player.SetAnimation(anim); + + player.Update(TimeSpan.FromSeconds(0.5)); + var progressBefore = player.Progress; + + player.AnimationSpeed = -1.0; + player.Update(TimeSpan.FromSeconds(0.2)); + + Assert.True(player.Progress < progressBefore); + } + + // ── Repeat.Reverse finite (count 0) ────────────────────────────────────── + + [Fact] + public void RepeatReverse_FiniteCount_ZeroCount_CompletesAfterOneCycle() + { + using var anim = CreateAnimation(); + var player = new SKLottiePlayer(); + player.SetAnimation(anim); + player.Repeat = SKLottieRepeat.Reverse(count: 0); + var completed = 0; + player.AnimationCompleted += (_, _) => completed++; + + // Forward to end — triggers direction flip, not completion + player.Update(TimeSpan.FromSeconds(10)); + Assert.False(player.IsComplete); + + // Backward to start — completes + player.Update(TimeSpan.FromSeconds(10)); + Assert.True(player.IsComplete); + Assert.Equal(1, completed); + } + + // ── Repeat mode change resets phase ────────────────────────────────────── + + [Fact] + public void SwitchingFromReverseToRestart_WithNegativeSpeed_DoesNotStick() + { + // Regression: switching from Reverse to Restart while isInForwardPhase=false (set during + // ping-pong) caused Update() to drive progress the wrong way and Restart to reset to the + // same boundary, freezing the animation. + using var anim = CreateAnimation(); + var player = new SKLottiePlayer(); + player.AnimationSpeed = -1.0; + player.Repeat = SKLottieRepeat.Reverse(); + player.SetAnimation(anim); // starts at Duration (negative speed) + + // Advance past the first boundary flip so isInForwardPhase becomes false internally. + // With -1 speed and Reverse, progress moves from Duration toward Zero, flips at Zero. + player.Update(TimeSpan.FromSeconds(10)); // reaches Zero → flip + player.Update(TimeSpan.FromSeconds(0.1)); // now moving away from Zero + + // Switch to Restart — this must reset isInForwardPhase to true. + player.Repeat = SKLottieRepeat.Restart(); + + var progressBefore = player.Progress; + player.Update(TimeSpan.FromSeconds(0.2)); + player.Update(TimeSpan.FromSeconds(0.2)); + + // Progress must have moved (animation not frozen at a boundary). + Assert.NotEqual(progressBefore, player.Progress); + Assert.False(player.IsComplete); + } + + [Fact] + public void SwitchingFromReverseToRestart_StillResetsPhaseAtBoundary() + { + // Phase reset now happens at the boundary (restart), not on property change. + // After switching to Restart mid-animation, the animation continues in its current + // direction until it hits a boundary, then restarts cleanly from the natural start. + using var anim = CreateAnimation(); + var player = new SKLottiePlayer(); + player.AnimationSpeed = -1.0; + player.Repeat = SKLottieRepeat.Reverse(); + player.SetAnimation(anim); // starts at Duration + + // Get into backward phase (isInForwardPhase=false: animation moving toward Duration) + player.Update(TimeSpan.FromSeconds(10)); // flip at Zero → isInForwardPhase=false + player.Update(TimeSpan.FromSeconds(0.3)); // now at ~0.3, moving toward Duration + + // Switch to Restart — direction NOT changed yet (preserved until boundary) + player.Repeat = SKLottieRepeat.Restart(); + + // Short update — still moving toward Duration (isInForwardPhase still false) + var progressAfterSwitch = player.Progress; + player.Update(TimeSpan.FromSeconds(0.1)); + Assert.True(player.Progress > progressAfterSwitch, "Should still move toward Duration before hitting boundary"); + + // Large update — hits Duration boundary, Restart resets phase + position to Duration (negative speed) + player.Update(TimeSpan.FromSeconds(10)); + + // After restart: position at Duration, movingForward=false → moves toward Zero + var progressAfterRestart = player.Progress; + player.Update(TimeSpan.FromSeconds(0.2)); + Assert.True(player.Progress < progressAfterRestart, "After restart, should move toward Zero (negative speed)"); + } + + [Fact] + public void SwitchingReverseToRestart_NegativeSpeed_NoAbruptDirectionChange() + { + // Regression: switching from Reverse to Restart while isInForwardPhase=false + // must NOT abruptly flip direction mid-animation. Progress should continue increasing + // until the boundary is reached. + using var anim = CreateAnimation(); + var player = new SKLottiePlayer(); + player.AnimationSpeed = -1.0; + player.Repeat = SKLottieRepeat.Reverse(); + player.SetAnimation(anim); // starts at Duration + + // Advance past first flip: isInForwardPhase=false, progress moving toward Duration + player.Update(TimeSpan.FromSeconds(10)); // flip at Zero + player.Update(TimeSpan.FromSeconds(0.5)); // at ~0.5, moving toward Duration + + var progressBefore = player.Progress; + + // Switch to Restart -- must NOT cause abrupt direction change + player.Repeat = SKLottieRepeat.Restart(); + + // Progress should still increase (continuing toward Duration) + player.Update(TimeSpan.FromSeconds(0.1)); + Assert.True(player.Progress > progressBefore, "Direction must not flip abruptly when switching Reverse→Restart mid-animation"); + } + + [Fact] + public void ChangingReverseCount_PreservesPlaybackDirection() + { + // Regression: changing Repeat from Reverse(∞) to Reverse(2) mid-playback while in the + // backward phase (isInForwardPhase=false, moving toward Duration with negative speed) + // must NOT reset isInForwardPhase — the animation should continue moving forward. + using var anim = CreateAnimation(); + var player = new SKLottiePlayer(); + player.AnimationSpeed = -1.0; + player.Repeat = SKLottieRepeat.Reverse(); + player.SetAnimation(anim); // starts at Duration + + // Advance to flip: isInForwardPhase becomes false (moving toward Duration with -1 speed) + player.Update(TimeSpan.FromSeconds(10)); // flip at Zero + player.Update(TimeSpan.FromSeconds(0.2)); // at ~0.2, moving toward Duration + + var progressBeforeSwitch = player.Progress; + + // Switch repeat count — must preserve direction + player.Repeat = SKLottieRepeat.Reverse(2); + + player.Update(TimeSpan.FromSeconds(0.1)); + + // Direction preserved: progress should have increased (still moving toward Duration) + Assert.True(player.Progress > progressBeforeSwitch); + } + + [Fact] + public void ChangingRepeatMode_ResetsCompletionState() + { + using var anim = CreateAnimation(); + var player = new SKLottiePlayer(); + player.SetAnimation(anim); + player.Repeat = SKLottieRepeat.Never; + + // Advance to completion + player.Update(TimeSpan.FromSeconds(10)); + Assert.True(player.IsComplete); + + // Switching Repeat mode must clear IsComplete + player.Repeat = SKLottieRepeat.Restart(); + Assert.False(player.IsComplete); + } + + // ── Negative speed + Restart infinite ──────────────────────────────────── + + [Fact] + public void NegativeSpeed_RepeatRestart_Infinite_NeverCompletes() + { + using var anim = CreateAnimation(); + var player = new SKLottiePlayer(); + player.AnimationSpeed = -1.0; + player.Repeat = SKLottieRepeat.Restart(); + player.SetAnimation(anim); + var completed = 0; + player.AnimationCompleted += (_, _) => completed++; + + for (var i = 0; i < 5; i++) + player.Update(TimeSpan.FromSeconds(10)); + + Assert.False(player.IsComplete); + Assert.Equal(0, completed); + } +} diff --git a/tests/SkiaSharp.Extended.Tests/SkiaSharp.Extended.Tests.csproj b/tests/SkiaSharp.Extended.Tests/SkiaSharp.Extended.Tests.csproj index 76c95dbdf4..4c6613d323 100644 --- a/tests/SkiaSharp.Extended.Tests/SkiaSharp.Extended.Tests.csproj +++ b/tests/SkiaSharp.Extended.Tests/SkiaSharp.Extended.Tests.csproj @@ -9,6 +9,7 @@ + diff --git a/tests/SkiaSharp.Extended.UI.Blazor.Tests/Controls/SKAnimatedCanvasViewTest.cs b/tests/SkiaSharp.Extended.UI.Blazor.Tests/Controls/SKAnimatedCanvasViewTest.cs new file mode 100644 index 0000000000..a230a59362 --- /dev/null +++ b/tests/SkiaSharp.Extended.UI.Blazor.Tests/Controls/SKAnimatedCanvasViewTest.cs @@ -0,0 +1,66 @@ +using Bunit; +using Microsoft.Extensions.DependencyInjection; +using SkiaSharp.Extended.UI.Blazor.Controls; + +namespace SkiaSharp.Extended.UI.Blazor.Tests.Controls; + +public class SKAnimatedCanvasViewTest +{ + /// + /// Verifies that has IsAnimationEnabled + /// defaulting to and that it exposes the + /// OnPaintSurface callback parameter. + /// + [Fact] + public void DefaultParameters_IsAnimationEnabled_IsTrue() + { + // Arrange – use a test-double subclass so we never need the real JS canvas. + var component = new SKAnimatedCanvasViewAccessor(); + + // Assert defaults are sensible before rendering. + Assert.True(component.IsAnimationEnabled); + Assert.Equal(default, component.OnPaintSurface); + Assert.Null(component.AdditionalAttributes); + } + + /// + /// Verifies that toggling IsAnimationEnabled from true → false → true + /// does not throw and leaves the flag in the expected state. + /// + [Fact] + public void ToggleIsAnimationEnabled_DoesNotThrow() + { + var component = new SKAnimatedCanvasViewAccessor(); + + // Start enabled (default) + Assert.True(component.IsAnimationEnabled); + + // Disable + var ex1 = Record.Exception(() => component.IsAnimationEnabled = false); + Assert.Null(ex1); + Assert.False(component.IsAnimationEnabled); + + // Re-enable + var ex2 = Record.Exception(() => component.IsAnimationEnabled = true); + Assert.Null(ex2); + Assert.True(component.IsAnimationEnabled); + } + + /// + /// A minimal subclass that exposes protected members for unit-testing + /// without rendering into a real Blazor/WASM environment. + /// + private sealed class SKAnimatedCanvasViewAccessor : SKAnimatedCanvasView + { + // Expose the loop-start / stop calls as no-ops by overriding UpdateAsync + // so the base class can be exercised without a running event loop. + protected override Task UpdateAsync(TimeSpan deltaTime) => Task.CompletedTask; + + // Shadow the auto-property so the setter logic can be exercised. + public new bool IsAnimationEnabled + { + get => base.IsAnimationEnabled; + set => base.IsAnimationEnabled = value; + } + } +} diff --git a/tests/SkiaSharp.Extended.UI.Blazor.Tests/SkiaSharp.Extended.UI.Blazor.Tests.csproj b/tests/SkiaSharp.Extended.UI.Blazor.Tests/SkiaSharp.Extended.UI.Blazor.Tests.csproj new file mode 100644 index 0000000000..e904c75db5 --- /dev/null +++ b/tests/SkiaSharp.Extended.UI.Blazor.Tests/SkiaSharp.Extended.UI.Blazor.Tests.csproj @@ -0,0 +1,26 @@ + + + + net10.0 + enable + enable + + + + + + + + + + + + + + + + + + + + diff --git a/tests/SkiaSharp.Extended.UI.Maui.Tests/Controls/Lottie/SKLottieViewTest.cs b/tests/SkiaSharp.Extended.UI.Maui.Tests/Controls/Lottie/SKLottieViewTest.cs index 5e7b8bc343..3255d70daf 100644 --- a/tests/SkiaSharp.Extended.UI.Maui.Tests/Controls/Lottie/SKLottieViewTest.cs +++ b/tests/SkiaSharp.Extended.UI.Maui.Tests/Controls/Lottie/SKLottieViewTest.cs @@ -40,183 +40,6 @@ public async Task EnsureNewAnimationResetsProgress() Assert.False(lottie.IsComplete); } - [Fact] - public async Task UpdatesMoveProgress() - { - // create - var source = new SKFileLottieImageSource { File = TrophyJson }; - var lottie = new WaitingLottieView { Source = source }; - var animationCompleted = 0; - lottie.AnimationCompleted += (s, e) => animationCompleted++; - await lottie.LoadedTask; - - // update - lottie.CallUpdate(TimeSpan.FromSeconds(1)); - - // test - Assert.Equal(TimeSpan.FromSeconds(1), lottie.Progress); - Assert.False(lottie.IsComplete); - Assert.Equal(0, animationCompleted); - } - - [Fact] - public async Task MultipleUpdatesMoveProgressUntilDurationMax() - { - // create - var source = new SKFileLottieImageSource { File = TrophyJson }; - var lottie = new WaitingLottieView { Source = source }; - var animationCompleted = 0; - lottie.AnimationCompleted += (s, e) => animationCompleted++; - await lottie.LoadedTask; - - // update & test - lottie.CallUpdate(TimeSpan.FromSeconds(1)); - Assert.Equal(TimeSpan.FromSeconds(1), lottie.Progress); - Assert.False(lottie.IsComplete); - Assert.Equal(0, animationCompleted); - - // update & test - lottie.CallUpdate(TimeSpan.FromSeconds(1)); - Assert.Equal(TimeSpan.FromSeconds(2), lottie.Progress); - Assert.False(lottie.IsComplete); - Assert.Equal(0, animationCompleted); - - // update & test - lottie.CallUpdate(TimeSpan.FromSeconds(1)); - Assert.Equal(TimeSpan.FromSeconds(2.3666665), lottie.Progress); - Assert.True(lottie.IsComplete); - Assert.Equal(1, animationCompleted); - } - - [Fact] - public async Task UpdatesLargerThanDurationHasMax() - { - // create - var source = new SKFileLottieImageSource { File = TrophyJson }; - var lottie = new WaitingLottieView { Source = source }; - var animationCompleted = 0; - lottie.AnimationCompleted += (s, e) => animationCompleted++; - await lottie.LoadedTask; - - // update - lottie.CallUpdate(TimeSpan.FromSeconds(5)); - - // test - Assert.Equal(TimeSpan.FromSeconds(2.3666665), lottie.Progress); - Assert.True(lottie.IsComplete); - Assert.Equal(1, animationCompleted); - } - - [Fact] - public async Task NegativeUpdatesDoesNothing() - { - // create - var source = new SKFileLottieImageSource { File = TrophyJson }; - var lottie = new WaitingLottieView { Source = source }; - var animationCompleted = 0; - lottie.AnimationCompleted += (s, e) => animationCompleted++; - await lottie.LoadedTask; - - // update - lottie.CallUpdate(TimeSpan.FromSeconds(-1)); - - // test - Assert.Equal(TimeSpan.Zero, lottie.Progress); - Assert.False(lottie.IsComplete); - Assert.Equal(0, animationCompleted); - } - - [Fact] - public async Task NegativeUpdatesAfterPositiveGoesBack() - { - // create - var source = new SKFileLottieImageSource { File = TrophyJson }; - var lottie = new WaitingLottieView { Source = source }; - var animationCompleted = 0; - lottie.AnimationCompleted += (s, e) => animationCompleted++; - await lottie.LoadedTask; - - // update - lottie.CallUpdate(TimeSpan.FromSeconds(1)); - lottie.CallUpdate(TimeSpan.FromSeconds(1)); - lottie.CallUpdate(TimeSpan.FromSeconds(-1)); - - // test - Assert.Equal(TimeSpan.FromSeconds(1), lottie.Progress); - Assert.False(lottie.IsComplete); - Assert.Equal(0, animationCompleted); - } - - [Theory] - [InlineData(SKLottieRepeatMode.Restart, 1, 1)] - [InlineData(SKLottieRepeatMode.Restart, 2, 2)] - [InlineData(SKLottieRepeatMode.Restart, 3, 0)] - [InlineData(SKLottieRepeatMode.Restart, 4, 1)] - [InlineData(SKLottieRepeatMode.Restart, 5, 2)] - [InlineData(SKLottieRepeatMode.Restart, 6, 0)] - [InlineData(SKLottieRepeatMode.Restart, 7, 1)] - [InlineData(SKLottieRepeatMode.Reverse, 1, 1)] - [InlineData(SKLottieRepeatMode.Reverse, 2, 2)] - [InlineData(SKLottieRepeatMode.Reverse, 3, 2.3666665)] - [InlineData(SKLottieRepeatMode.Reverse, 4, 1.3666665)] - [InlineData(SKLottieRepeatMode.Reverse, 5, 0.3666665)] - [InlineData(SKLottieRepeatMode.Reverse, 6, 0)] - [InlineData(SKLottieRepeatMode.Reverse, 7, 1)] - public async Task ReachingTheEndAndThenMoreWithRepeat(SKLottieRepeatMode repeatMode, int steps, double progress) - { - // create - var source = new SKFileLottieImageSource { File = TrophyJson }; - var lottie = new WaitingLottieView { Source = source, RepeatMode = repeatMode, RepeatCount = -1 }; - var animationCompleted = 0; - lottie.AnimationCompleted += (s, e) => animationCompleted++; - await lottie.LoadedTask; - - // update - for (var i = 0; i < steps; i++) - lottie.CallUpdate(TimeSpan.FromSeconds(1)); - - // test - Assert.Equal(TimeSpan.FromSeconds(progress), lottie.Progress); - Assert.False(lottie.IsComplete); - Assert.Equal(0, animationCompleted); - } - - [Theory] - [InlineData(SKLottieRepeatMode.Restart, 1, 1, false)] - [InlineData(SKLottieRepeatMode.Restart, 2, 2, false)] - [InlineData(SKLottieRepeatMode.Restart, 3, 2.3666665, true)] - [InlineData(SKLottieRepeatMode.Restart, 4, 2.3666665, true)] - [InlineData(SKLottieRepeatMode.Restart, 5, 2.3666665, true)] - [InlineData(SKLottieRepeatMode.Reverse, 1, 1, false)] - [InlineData(SKLottieRepeatMode.Reverse, 2, 2, false)] - [InlineData(SKLottieRepeatMode.Reverse, 3, 2.3666665, false)] - [InlineData(SKLottieRepeatMode.Reverse, 4, 1.3666665, false)] - [InlineData(SKLottieRepeatMode.Reverse, 5, 0.3666665, false)] - [InlineData(SKLottieRepeatMode.Reverse, 6, 0, true)] - [InlineData(SKLottieRepeatMode.Reverse, 7, 0, true)] - [InlineData(SKLottieRepeatMode.Reverse, 8, 0, true)] - public async Task ReachingTheEndAndThenMoreWithRepeatModeButZeroCount(SKLottieRepeatMode repeatMode, int steps, double progress, bool isComplete) - { - // create - var source = new SKFileLottieImageSource { File = TrophyJson }; - var lottie = new WaitingLottieView { Source = source, RepeatMode = repeatMode, RepeatCount = 0 }; - var animationCompleted = 0; - lottie.AnimationCompleted += (s, e) => animationCompleted++; - await lottie.LoadedTask; - - // update - for (var i = 0; i < steps; i++) - lottie.CallUpdate(TimeSpan.FromSeconds(1)); - - // test - Assert.Equal(TimeSpan.FromSeconds(progress), lottie.Progress); - Assert.Equal(isComplete, lottie.IsComplete); - if (isComplete) - Assert.Equal(1, animationCompleted); - else - Assert.Equal(0, animationCompleted); - } - [Fact] public async Task DefaultAnimationSpeedIsOne() { @@ -229,373 +52,6 @@ public async Task DefaultAnimationSpeedIsOne() Assert.Equal(1.0, lottie.AnimationSpeed); } - [Fact] - public async Task AnimationSpeedDoubleMakesItTwiceAsFast() - { - // create - var source = new SKFileLottieImageSource { File = TrophyJson }; - var lottie = new WaitingLottieView { Source = source, AnimationSpeed = 2.0 }; - await lottie.LoadedTask; - - // update with 1 second, but should progress 2 seconds - lottie.CallUpdate(TimeSpan.FromSeconds(1)); - - // test - Assert.Equal(TimeSpan.FromSeconds(2), lottie.Progress); - Assert.False(lottie.IsComplete); - } - - [Fact] - public async Task AnimationSpeedHalfMakesItTwiceAsSlow() - { - // create - var source = new SKFileLottieImageSource { File = TrophyJson }; - var lottie = new WaitingLottieView { Source = source, AnimationSpeed = 0.5 }; - await lottie.LoadedTask; - - // update with 1 second, but should progress 0.5 seconds - lottie.CallUpdate(TimeSpan.FromSeconds(1)); - - // test - Assert.Equal(TimeSpan.FromSeconds(0.5), lottie.Progress); - Assert.False(lottie.IsComplete); - } - - [Fact] - public async Task AnimationSpeedZeroStopsAnimation() - { - // create - var source = new SKFileLottieImageSource { File = TrophyJson }; - var lottie = new WaitingLottieView { Source = source, AnimationSpeed = 0 }; - await lottie.LoadedTask; - - // update with 1 second, but should not progress - lottie.CallUpdate(TimeSpan.FromSeconds(1)); - - // test - Assert.Equal(TimeSpan.Zero, lottie.Progress); - Assert.False(lottie.IsComplete); - } - - [Fact] - public async Task AnimationSpeedCanBeChangedDynamically() - { - // create - var source = new SKFileLottieImageSource { File = TrophyJson }; - var lottie = new WaitingLottieView { Source = source, AnimationSpeed = 1.0 }; - await lottie.LoadedTask; - - // update with normal speed (0.5 seconds) - lottie.CallUpdate(TimeSpan.FromSeconds(0.5)); - Assert.Equal(TimeSpan.FromSeconds(0.5), lottie.Progress); - - // change speed to 2x - lottie.AnimationSpeed = 2.0; - lottie.CallUpdate(TimeSpan.FromSeconds(0.5)); - - // test - should now be at 1.5 seconds (0.5 + 1.0 at 2x speed) - Assert.Equal(TimeSpan.FromSeconds(1.5), lottie.Progress); - Assert.False(lottie.IsComplete); - } - - [Fact] - public async Task NegativeAnimationSpeedReversesPlayback() - { - // create - start at Progress = Duration to play backwards - var source = new SKFileLottieImageSource { File = TrophyJson }; - var lottie = new WaitingLottieView { Source = source, AnimationSpeed = -1.0 }; - await lottie.LoadedTask; - - // set progress to end of animation - lottie.Progress = lottie.Duration; - var startProgress = lottie.Progress; - - // update with 1 second - should go backwards - lottie.CallUpdate(TimeSpan.FromSeconds(1)); - - // test - progress should have decreased - Assert.Equal(startProgress - TimeSpan.FromSeconds(1), lottie.Progress); - } - - [Fact] - public async Task NegativeAnimationSpeedStartsAtDuration() - { - // With negative speed, animation should start at Duration and play backwards - var source = new SKFileLottieImageSource { File = TrophyJson }; - var lottie = new WaitingLottieView { Source = source, AnimationSpeed = -1.0 }; - await lottie.LoadedTask; - - // progress starts at Duration (not 0) for negative speed - Assert.Equal(lottie.Duration, lottie.Progress); - - // update with 1 second - should go backwards - lottie.CallUpdate(TimeSpan.FromSeconds(1)); - - // test - progress should have decreased by 1 second - Assert.Equal(lottie.Duration - TimeSpan.FromSeconds(1), lottie.Progress); - } - - [Fact] - public async Task AnimationSpeedWorksWithRepeatModeReverse() - { - // create with RepeatMode.Reverse and 2x speed - var source = new SKFileLottieImageSource { File = TrophyJson }; - var lottie = new WaitingLottieView - { - Source = source, - AnimationSpeed = 2.0, - RepeatMode = SKLottieRepeatMode.Reverse, - RepeatCount = 1 - }; - await lottie.LoadedTask; - - var duration = lottie.Duration; - - // update to slightly past the end to ensure we hit the boundary and reverse - lottie.CallUpdate(TimeSpan.FromTicks((duration.Ticks / 2) + 1)); - - // should be at the end (clamped) - Assert.Equal(duration, lottie.Progress); - - // update again - should now be playing in reverse at 2x speed (0.5 seconds of real time = 1 second at 2x) - lottie.CallUpdate(TimeSpan.FromSeconds(0.5)); - - // test - progress should have decreased by 1 second (0.5s * 2x speed in reverse) - Assert.Equal(duration - TimeSpan.FromSeconds(1), lottie.Progress); - } - - [Fact] - public async Task NegativeAnimationSpeedCompletesAndFiresEvent() - { - // create with negative speed and start at end - var source = new SKFileLottieImageSource { File = TrophyJson }; - var lottie = new WaitingLottieView - { - Source = source, - AnimationSpeed = -1.0, - RepeatCount = 0 // no repeats, should complete - }; - await lottie.LoadedTask; - - var duration = lottie.Duration; - var completedFired = false; - lottie.AnimationCompleted += (s, e) => completedFired = true; - - // set progress to end - lottie.Progress = duration; - - // update enough to reach the start - lottie.CallUpdate(duration + TimeSpan.FromSeconds(1)); - - // test - should be at start and completed - Assert.Equal(TimeSpan.Zero, lottie.Progress); - Assert.True(lottie.IsComplete); - Assert.True(completedFired); - } - - [Fact] - public async Task NegativeAnimationSpeedWithRepeatModeRestartLoops() - { - // create with negative speed and RepeatMode.Restart with infinite repeats - var source = new SKFileLottieImageSource { File = TrophyJson }; - var lottie = new WaitingLottieView - { - Source = source, - AnimationSpeed = -1.0, - RepeatMode = SKLottieRepeatMode.Restart, - RepeatCount = -1 // infinite repeats - }; - await lottie.LoadedTask; - - var duration = lottie.Duration; - - // set progress to end (where negative speed animations start) - lottie.Progress = duration; - Assert.False(lottie.IsComplete); - - // Run multiple times - should never complete with infinite repeats - for (int i = 0; i < 5; i++) - { - lottie.CallUpdate(duration); - Assert.False(lottie.IsComplete); - } - } - - // =========================================== - // Edge Case Tests (from multi-model review) - // =========================================== - - [Fact] - public async Task AnimationCompletedEventFiresOnlyOnce() - { - // Verify AnimationCompleted doesn't spam every frame - var source = new SKFileLottieImageSource { File = TrophyJson }; - var lottie = new WaitingLottieView { Source = source, RepeatCount = 0 }; - await lottie.LoadedTask; - - var completedCount = 0; - lottie.AnimationCompleted += (s, e) => completedCount++; - - // Play to completion - lottie.CallUpdate(lottie.Duration + TimeSpan.FromSeconds(1)); - Assert.True(lottie.IsComplete); - Assert.Equal(1, completedCount); - - // Call update several more times - event should NOT fire again - for (int i = 0; i < 5; i++) - { - lottie.CallUpdate(TimeSpan.FromSeconds(0.1)); - } - Assert.Equal(1, completedCount); - } - - [Fact] - public async Task ManualProgressSetDoesNotIncrementRepeatCount() - { - // Manually setting Progress to a boundary should not trigger repeat logic - var source = new SKFileLottieImageSource { File = TrophyJson }; - var lottie = new WaitingLottieView - { - Source = source, - RepeatCount = 5, - RepeatMode = SKLottieRepeatMode.Restart - }; - await lottie.LoadedTask; - - var duration = lottie.Duration; - - // Manually scrub to end multiple times - lottie.Progress = duration; - lottie.Progress = TimeSpan.Zero; - lottie.Progress = duration; - lottie.Progress = TimeSpan.Zero; - - // Now play normally - should still have all 5 repeats available - // (repeatsCompleted should still be 0) - lottie.CallUpdate(duration); - Assert.False(lottie.IsComplete); - } - - [Fact] - public async Task SwitchingRepeatModeFromReverseToRestartMidAnimation() - { - // Verify animation doesn't get stuck when switching modes - var source = new SKFileLottieImageSource { File = TrophyJson }; - var lottie = new WaitingLottieView - { - Source = source, - RepeatCount = -1, // infinite - RepeatMode = SKLottieRepeatMode.Reverse - }; - await lottie.LoadedTask; - - var duration = lottie.Duration; - - // Play to end and start reversing - lottie.CallUpdate(duration); - Assert.Equal(duration, lottie.Progress); - - // Play partway back - lottie.CallUpdate(TimeSpan.FromSeconds(1)); - var midProgress = lottie.Progress; - Assert.True(midProgress < duration); - - // Switch to Restart mode - lottie.RepeatMode = SKLottieRepeatMode.Restart; - - // Continue playing - should still be able to move - lottie.CallUpdate(TimeSpan.FromSeconds(0.5)); - - // Progress should have changed (not stuck) - Assert.NotEqual(midProgress, lottie.Progress); - } - - [Fact] - public async Task ProgressOutOfBoundsIsAccepted() - { - // CURRENT BEHAVIOR: Out-of-bounds Progress values are accepted without clamping - // This test documents the current behavior - a future fix should add clamping - var source = new SKFileLottieImageSource { File = TrophyJson }; - var lottie = new WaitingLottieView { Source = source }; - await lottie.LoadedTask; - - var duration = lottie.Duration; - - // Set negative progress - currently accepted (should ideally be clamped) - lottie.Progress = TimeSpan.FromSeconds(-100); - // Verify the animation doesn't crash - Assert.NotNull(lottie); - - // Set progress beyond duration - currently accepted - lottie.Progress = duration + TimeSpan.FromSeconds(100); - // Verify the animation doesn't crash - Assert.NotNull(lottie); - } - - [Fact] - public async Task ChangingAnimationSpeedToNegativeMidPlayback() - { - // Verify animation continues correctly when speed changes sign - var source = new SKFileLottieImageSource { File = TrophyJson }; - var lottie = new WaitingLottieView - { - Source = source, - AnimationSpeed = 1.0, - RepeatCount = -1 // infinite - }; - await lottie.LoadedTask; - - // Play forward partway - lottie.CallUpdate(TimeSpan.FromSeconds(1)); - var progress1 = lottie.Progress; - Assert.Equal(TimeSpan.FromSeconds(1), progress1); - - // Change to negative speed - lottie.AnimationSpeed = -1.0; - - // Continue - should now play backward - lottie.CallUpdate(TimeSpan.FromSeconds(0.5)); - var progress2 = lottie.Progress; - Assert.True(progress2 < progress1); - } - - [Fact] - public async Task ZeroAnimationSpeedPausesAnimation() - { - // Verify zero speed pauses without side effects - var source = new SKFileLottieImageSource { File = TrophyJson }; - var lottie = new WaitingLottieView { Source = source, AnimationSpeed = 0 }; - await lottie.LoadedTask; - - var initialProgress = lottie.Progress; - - // Multiple updates should not change progress - for (int i = 0; i < 10; i++) - { - lottie.CallUpdate(TimeSpan.FromSeconds(1)); - } - - Assert.Equal(initialProgress, lottie.Progress); - Assert.False(lottie.IsComplete); - } - - [Fact] - public async Task NegativeSpeedAnimationStartsAtDurationOnLoad() - { - // Verify Fix 1: Negative speed animation initializes Progress to Duration - var source = new SKFileLottieImageSource { File = TrophyJson }; - var lottie = new WaitingLottieView { Source = source, AnimationSpeed = -1.0 }; - await lottie.LoadedTask; - - // Should start at Duration, not Zero - Assert.Equal(lottie.Duration, lottie.Progress); - Assert.False(lottie.IsComplete); - - // Should be able to play backwards without manual intervention - lottie.CallUpdate(TimeSpan.FromSeconds(1)); - Assert.True(lottie.Progress < lottie.Duration); - } - [Fact] public async Task SourceChangeCancellsPreviousLoad() { @@ -603,7 +59,7 @@ public async Task SourceChangeCancellsPreviousLoad() var source1 = new SKFileLottieImageSource { File = TrophyJson }; var source2 = new SKFileLottieImageSource { File = LoloJson }; var lottie = new WaitingLottieView { Source = source1 }; - + // Immediately change source before first load completes lottie.ResetTask(); lottie.Source = source2; @@ -614,3 +70,4 @@ public async Task SourceChangeCancellsPreviousLoad() Assert.NotEqual(TimeSpan.Zero, lottie.Duration); } } +