{"id":23964581,"url":"https://github.com/joelumbley/animation-cs","last_synced_at":"2026-05-16T04:05:49.843Z","repository":{"id":270734031,"uuid":"911304700","full_name":"JoeLumbley/Animation-CS","owner":"JoeLumbley","description":"Learn to how to animate a rectangle on a windows form.","archived":false,"fork":false,"pushed_at":"2025-01-22T17:27:41.000Z","size":329,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-01-22T18:29:15.251Z","etag":null,"topics":["animation","bufferedgraphics","csharp","deltatime","double","draw","drawstring","fillrectangle","frame-independent","k-12-education","moving-objects","paint","rectangle","rectangledouble","time-based-animation","windows-forms","windows-forms-csharp"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/JoeLumbley.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2025-01-02T17:49:21.000Z","updated_at":"2025-01-22T17:27:44.000Z","dependencies_parsed_at":"2025-01-02T18:39:47.432Z","dependency_job_id":"5b431bf5-ef9d-4178-ba38-8fa83236e98d","html_url":"https://github.com/JoeLumbley/Animation-CS","commit_stats":null,"previous_names":["joelumbley/animation-cs"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JoeLumbley%2FAnimation-CS","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JoeLumbley%2FAnimation-CS/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JoeLumbley%2FAnimation-CS/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JoeLumbley%2FAnimation-CS/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/JoeLumbley","download_url":"https://codeload.github.com/JoeLumbley/Animation-CS/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":240491707,"owners_count":19809977,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["animation","bufferedgraphics","csharp","deltatime","double","draw","drawstring","fillrectangle","frame-independent","k-12-education","moving-objects","paint","rectangle","rectangledouble","time-based-animation","windows-forms","windows-forms-csharp"],"created_at":"2025-01-06T21:49:35.781Z","updated_at":"2026-05-16T04:05:44.802Z","avatar_url":"https://github.com/JoeLumbley.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Animation C#\nThis project showcases the fundamentals of creating smooth animations. The application features a moving rectangle that glides across the screen, illustrating key concepts such as frame independence and real-time rendering.\n\n\n\n![014](https://github.com/user-attachments/assets/3926a58b-0338-476d-934d-0e327cafddd3)\n\n\n\nIn this app, you'll learn how to manage timing, handle graphics rendering, and implement basic animation principles. Whether you're a beginner looking to understand the basics of animation or an experienced developer seeking a refresher, this project provides a hands-on approach to mastering animation techniques.\n\n---\n\n# Code Walkthrough\n\nWelcome to the Animation C# project! In this lesson, we will explore the code step by step, breaking down each part to understand how it works. This project demonstrates the fundamentals of creating smooth animations in a Windows Forms application using C#. By the end of this walkthrough, you will have a solid understanding of the code and the principles behind animation.\n\nAnimation is the art of creating the illusion of motion by displaying a series of static images in quick succession. In our app, we use animation to make it appear as though our rectangle is moving towards the right. To ensure that our animation runs smoothly on all devices, we have designed it to be frame-independent. This means that our animation is not affected by changes in the frame rate, ensuring a consistent and seamless experience for all users.\n\n [Index](#index)\n \n---\n\n## Namespaces\n\n\n\n### Using Directives\n```csharp\nusing System.Diagnostics;\n```\n- This line imports the `System.Diagnostics` namespace, which provides classes for debugging and tracing. It allows us to print debug messages to the console.\n\n### Namespace Declaration\n```csharp\nnamespace Animation_CS\n{\n```\n- Here, we define a namespace called `Animation_CS`. Namespaces are used to organize code and avoid naming conflicts with other parts of the program.\n\n### Class Definition\n```csharp\npublic partial class Form1 : Form\n{\n```\n- We define a class named `Form1` that inherits from `Form`. This means that `Form1` is a type of window in our application. The `partial` keyword allows us to define the class across multiple files if needed.\n\n### Buffered Graphics Context\n```csharp\nprivate BufferedGraphicsContext Context = new();\nprivate BufferedGraphics? Buffer;\n```\n- `Context` is an instance of `BufferedGraphicsContext`, which manages the buffering for smoother graphics rendering. \n- `Buffer` is a nullable variable that will hold our graphics buffer used to draw graphics off-screen before displaying them.\n\n### Minimum Buffer Size\n```csharp\nprivate readonly Size MinimumMaxBufferSize = new(1280, 720);\n```\n- This line defines a constant size for the minimum maximum buffer size, setting it to 1280x720 pixels. This ensures our graphics can render properly on smaller screens.\n\n### Background Color\n```csharp\nprivate Color BackgroundColor = Color.Black;\n```\n- This variable defines the background color of the form, set to black.\n\n### RectangleDouble Structure\n```csharp\npublic struct RectangleDouble\n{\n    public double X, Y, Width, Height, Velocity;\n    public Brush Brush;\n```\n- We define a structure called `RectangleDouble` to represent a rectangle with double-precision coordinates and dimensions. \n- It includes properties for the rectangle's position (`X`, `Y`), size (`Width`, `Height`), movement speed (`Velocity`), and its color (`Brush`).\n\n#### Constructor\n```csharp\npublic RectangleDouble(double x, double y, double width, double height,\n                       double velocity, Brush brush)\n{\n    X = x;\n    Y = y;\n    Width = width;\n    Height = height;\n    Velocity = velocity;\n    Brush = brush;\n}\n```\n- This constructor initializes the rectangle's position, size, velocity, and color when a new instance of `RectangleDouble` is created.\n\n#### GetNearest Methods\n```csharp\npublic readonly int GetNearestX() { return RoundToNearest(X); }\npublic readonly int GetNearestY() { return RoundToNearest(Y); }\npublic readonly int GetNearestWidth() { return RoundToNearest(Width); }\npublic readonly int GetNearestHeight() { return RoundToNearest(Height); }\n```\n- These methods round the rectangle's attributes to the nearest integer values for drawing. This is important because pixel coordinates must be whole numbers.\n\n#### RoundToNearest Method\n```csharp\nprivate readonly int RoundToNearest(double value)\n{\n    return (int)Math.Round(value);\n}\n```\n- This generic method rounds a given double value to the nearest integer.\n\n#### MoveRight Method\n```csharp\npublic void MoveRight(TimeSpan deltaTime)\n{\n    X += Velocity * deltaTime.TotalSeconds;\n}\n```\n- This method updates the rectangle's position based on its velocity and the time elapsed since the last frame (`deltaTime`). The formula used is:\n  - **Displacement = Velocity x Delta Time** (Δs = V * Δt)\n\n#### Wraparound Method\n```csharp\npublic void Wraparound(Rectangle clientRectangle)\n{\n    if (X \u003e clientRectangle.Right)\n    {\n        X = clientRectangle.Left - Width;\n    }\n}\n```\n- This method checks if the rectangle has exited the right side of the client area. If it has, it repositions the rectangle to the left side of the screen, creating a wraparound effect.\n\n#### MoveRightAndWraparound Method\n```csharp\npublic void MoveRightAndWraparound(Rectangle clientRectangle, TimeSpan deltaTime)\n{\n    MoveRight(deltaTime);\n    Wraparound(clientRectangle);\n}\n```\n- This method combines moving the rectangle to the right and checking for wraparound in one call.\n\n#### CenterVertically Method\n```csharp\npublic void CenterVertically(Rectangle clientRectangle)\n{\n    Y = clientRectangle.Height / 2 - Height / 2;\n}\n```\n- This method centers the rectangle vertically in the client area of the form.\n\n### Rectangle Instance\n```csharp\nprivate RectangleDouble Rectangle = new(0.0f, 0.0f, 256.0f, 256.0f, 32.0f,\n                                        new SolidBrush(Color.Chartreuse));\n```\n- Here, we create an instance of `RectangleDouble`, starting at position (0, 0) with a width and height of 256 pixels, a velocity of 32 pixels per second, and a color of chartreuse.\n\n### DeltaTimeStructure\n```csharp\nprivate struct DeltaTimeStructure\n{\n    public DateTime CurrentFrame;\n    public DateTime LastFrame;\n    public TimeSpan ElapsedTime;\n```\n- This structure keeps track of the time between frames, which is crucial for smooth animations. It includes:\n  - `CurrentFrame`: The time of the current frame.\n  - `LastFrame`: The time of the last frame.\n  - `ElapsedTime`: The time difference between the two frames.\n\n#### Update Method\n```csharp\npublic void Update()\n{\n    CurrentFrame = DateTime.Now;\n    ElapsedTime = CurrentFrame - LastFrame;\n    LastFrame = CurrentFrame;\n}\n```\n- This method updates the current frame time, calculates the elapsed time since the last frame, and updates the last frame's time for the next iteration.\n\n### DisplayStructure\n```csharp\nprivate struct DisplayStructure\n{\n    public Point Location;\n    public string Text;\n    public Font Font;\n    public Brush Brush;\n```\n- This structure is used to display text (like FPS) on the screen. It includes:\n  - `Location`: The position where the text will be displayed.\n  - `Text`: The actual text to display.\n  - `Font`: The font used for the text.\n  - `Brush`: The color of the text.\n\n#### MoveToPosition Method\n```csharp\npublic void MoveToPosition(Rectangle clientRectangle)\n{\n    Location = new Point(Location.X, clientRectangle.Bottom - 75);\n}\n```\n- This method places the FPS display at the bottom of the client area.\n\n### FrameCounterStructure\n```csharp\nprivate struct FrameCounterStructure\n{\n    public int FrameCount;\n    public DateTime StartTime;\n    public TimeSpan TimeElapsed;\n    public String FPS;\n```\n- This structure tracks the number of frames rendered and the elapsed time, helping us calculate the FPS (frames per second).\n\n#### Update Method\n```csharp\npublic void Update()\n{\n    TimeElapsed = DateTime.Now.Subtract(StartTime);\n    if (TimeElapsed.TotalSeconds \u003c 1)\n    {\n        FrameCount += 1;\n    }\n    else\n    {\n        FPS = $\"{FrameCount} FPS\";\n        FrameCount = 0;\n        StartTime = DateTime.Now;\n    }\n}\n```\n- This method updates the frame counter, calculating the FPS and updating the display text accordingly.\n\n### Form Load Event\n```csharp\nprivate void Form1_Load(object sender, EventArgs e)\n{\n    InitializeApp();\n    Debug.Print($\"Running...{DateTime.Now}\");\n}\n```\n- This method is called when the form loads. It initializes the application and prints a debug message to the console.\n\n### Form Resize Event\n```csharp\nprivate void Form1_Resize(object sender, EventArgs e)\n{\n    if (WindowState != FormWindowState.Minimized)\n    {\n        FpsDisplay.MoveToPosition(ClientRectangle);\n        Rectangle.CenterVertically(ClientRectangle);\n        DisposeBuffer();\n        Timer1.Enabled = true;\n    }\n    else\n    {\n        Timer1.Enabled = false;\n    }\n}\n```\n- This method is triggered when the form is resized. It adjusts the FPS display and rectangle size, and disposes of the buffer if the window is not minimized.\n\n### Timer Tick Event\n```csharp\nprivate void Timer1_Tick(object sender, EventArgs e)\n{\n    UpdateFrame();\n    Invalidate(); // Calls OnPaint\n}\n```\n- This event is triggered at regular intervals (every 10 ms). It updates the frame and calls the `OnPaint` method to redraw the form.\n\n### OnPaint Method\n```csharp\nprotected override void OnPaint(PaintEventArgs e)\n{\n    AllocateBuffer();\n    DrawFrame();\n    Buffer?.Render(e.Graphics);\n    FrameCounter.Update();\n    EraseFrame();\n    base.OnPaint(e);\n}\n```\n- This method is called whenever the form needs to be redrawn. It allocates the buffer, draws the current frame, renders it on the form, and updates the frame counter.\n\n### OnPaintBackground Method\n```csharp\nprotected override void OnPaintBackground(PaintEventArgs e)\n{\n    // Intentionally left blank. Do not remove.\n}\n```\n- This method intentionally does nothing to prevent flickering during the redraw process.\n\n### Update Frame Method\n```csharp\nprivate void UpdateFrame()\n{\n    DeltaTime.Update();\n    Rectangle.MoveRightAndWraparound(ClientRectangle, DeltaTime.ElapsedTime);\n    FpsDisplay.Text = FrameCounter.FPS.ToString();\n}\n```\n- This method updates the time since the last frame and moves the rectangle.\n\n### Allocate Buffer Method\n```csharp\nprivate void AllocateBuffer()\n{\n    if (Buffer == null)\n    {\n        Buffer = Context.Allocate(CreateGraphics(), ClientRectangle);\n        Buffer.Graphics.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceOver;\n        Buffer.Graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;\n        EraseFrame();\n    }\n}\n```\n- This method allocates the buffer for drawing graphics, ensuring that the graphics are rendered smoothly.\n\n### Draw Frame Method\n```csharp\nprivate void DrawFrame()\n{\n    Buffer?.Graphics.FillRectangle(Rectangle.Brush,\n                                   Rectangle.GetNearestX(), \n                                   Rectangle.GetNearestY(), \n                                   Rectangle.GetNearestWidth(), \n                                   Rectangle.GetNearestHeight());\n    \n    Buffer?.Graphics.DrawString(FpsDisplay.Text,\n                                FpsDisplay.Font,\n                                FpsDisplay.Brush,\n                                FpsDisplay.Location);\n}\n```\n- This method clears the buffer, draws the rectangle, and displays the current FPS.\n\n### Erase Frame Method\n```csharp\nprivate void EraseFrame()\n{\n    Buffer?.Graphics.Clear(BackgroundColor);\n}\n```\n- This method clears the buffer to prepare for the next frame.\n\n### Dispose Buffer Method\n```csharp\nprivate void DisposeBuffer()\n{\n    if (Buffer != null)\n    {\n        Buffer.Dispose();\n        Buffer = null; // Set to null to avoid using a disposed object\n    }\n}\n```\n- This method disposes of the graphics buffer to free up resources.\n\n### Initialize Application Method\n```csharp\nprivate void InitializeApp()\n{\n    InitializeForm();\n    Timer1.Interval = 10;\n    Timer1.Start();\n}\n```\n- This method initializes the form and starts the timer for regular updates.\n\n### Initialize Form Method\n```csharp\nprivate void InitializeForm()\n{\n    CenterToScreen();\n    SetStyle(ControlStyles.UserPaint, true);\n    SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint, true);\n    Text = \"Animation C# - Code with Joe\";\n    WindowState = FormWindowState.Maximized;\n}\n```\n- This method sets up the form's appearance and behavior, centering it on the screen and maximizing the window.\n\n### Constructor\n```csharp\npublic Form1()\n{\n    InitializeComponent();\n    Context = BufferedGraphicsManager.Current;\n    if (Screen.PrimaryScreen != null)\n    {\n        Context.MaximumBuffer = Screen.PrimaryScreen.WorkingArea.Size;\n    }\n    else\n    {\n        Context.MaximumBuffer = MinimumMaxBufferSize;\n        Debug.Print($\"Primary screen not detected.\");\n    }\n    Buffer = Context.Allocate(CreateGraphics(), ClientRectangle);\n    Buffer.Graphics.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceOver;\n    Buffer.Graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;\n}\n```\n- The constructor initializes the form and sets up the graphics context and buffer.\n\n\nCongratulations! You’ve just completed a detailed walkthrough of the Animation C# project. We explored each part of the code, understanding how it works together to create a smooth animation of a rectangle moving across the screen. This project serves as a solid foundation for learning more about animation techniques and graphics programming in C#. Feel free to experiment with different values and see how they affect the animation! Happy coding!\n\n\n\n\n\n\n---\n\n\n# Exercises\n\nHere are some exercises to enhance your understanding of the Animation C# project by modifying various parameters:\n\n1. **Change the Rectangle's Color**\n   - **Task**: Modify the `RectangleBrush` variable to change the color of the rectangle.\n   - **Instructions**:\n     ```csharp\n     // Change Color.Chartreuse to Color.Red\n     private readonly Brush RectangleBrush = new SolidBrush(Color.Red);\n     ```\n\n2. **Change the Rectangle's Size**\n   - **Task**: Adjust the dimensions of the rectangle by modifying the `Rectangle` instance.\n   - **Instructions**:\n     ```csharp\n     private RectangleDouble Rectangle = new(0, 0, 128, 128); // Change width and height to 128\n     ```\n\n3. **Change the Rectangle's Velocity**\n   - **Task**: Modify the `Velocity` variable to change how fast the rectangle moves across the screen.\n   - **Instructions**:\n     ```csharp\n     private readonly double Velocity = 100.0; // Change from 64.0 to 100.0 for faster movement\n     ```\n\n4. **Add a Random Color Change on Wraparound**\n   - **Task**: Implement functionality to change the rectangle's color randomly on Wraparound.\n   - **Instructions**:\n     ```csharp\n     \n        private void MoveRectangle()\n        {\n            // Move the rectangle to the right.\n            Rectangle.X += Velocity * DeltaTime.ElapsedTime.TotalSeconds;\n            // Displacement = Velocity x Delta Time ( Δs = V * Δt )\n\n            // Wraparound\n            // When the rectangle exits the right side of the client area.\n            if (Rectangle.X \u003e ClientRectangle.Right)\n            {\n                // The rectangle reappears on the left side the client area.\n                Rectangle.X = ClientRectangle.Left - Rectangle.Width;\n     \n                // Change color randomly\n                Random rand = new Random();\n                RectangleBrush = new SolidBrush(Color.FromArgb(rand.Next(256), rand.Next(256), rand.Next(256)));\n     \n            }\n\n        }\n\n     ```\n\n5. **Implement Rectangle Resizing on Key Press**\n   - **Task**: Allow the user to resize the rectangle using keyboard input (e.g., increase size with the Up arrow and decrease with the Down arrow).\n   - **Instructions**:\n     ```csharp\n     protected override bool ProcessCmdKey(ref Message msg, Keys keyData)\n     {\n         if (keyData == Keys.Up)\n         {\n             Rectangle.Width += 10;\n             Rectangle.Height += 10;\n         }\n         else if (keyData == Keys.Down)\n         {\n             Rectangle.Width -= 10;\n             Rectangle.Height -= 10;\n         }\n         return base.ProcessCmdKey(ref msg, keyData);\n     }\n     ```\n\n\nThese exercises will help you explore the flexibility of the Animation C# project and deepen your understanding of graphics programming in C#. Feel free to experiment with different values and see how they affect the animation!\n\n\n----\n\n# More on DeltaTime\n\n### What is DeltaTime?\n\n**DeltaTime** refers to the time difference between the current frame and the last frame in a game or animation loop. It is crucial for creating smooth and consistent animations, especially in real-time applications where frame rates can vary.\n\n### Importance of DeltaTime\n\n1. **Frame Rate Independence**: \n   - Without DeltaTime, animations would run differently on machines with varying frame rates. By using DeltaTime, animations can be adjusted to maintain a consistent speed regardless of how many frames are rendered per second.\n\n2. **Smooth Motion**: \n   - DeltaTime allows for smoother motion by ensuring that movement calculations are based on the actual time elapsed rather than relying solely on frame counts.\n\n3. **Timing Events**: \n   - It helps in timing events accurately, ensuring that actions occur at the right moment relative to real-world time.\n\n### How DeltaTime Works\n\n1. **Capture Time**:\n   - At the beginning of each frame, capture the current time (e.g., using `DateTime.Now` in C#).\n\n2. **Calculate DeltaTime**:\n   - Subtract the last captured time from the current time to get the elapsed time for that frame.\n\n   ```csharp\n   TimeSpan elapsedTime = currentFrameTime - lastFrameTime;\n   ```\n\n3. **Update Last Frame Time**:\n   - Store the current time as the last frame time for the next iteration.\n\n   ```csharp\n   lastFrameTime = currentFrameTime;\n   ```\n\n4. **Use DeltaTime in Calculations**:\n   - Use the calculated DeltaTime to adjust movement speeds, animations, and other time-dependent calculations.\n\n   ```csharp\n   position += velocity * elapsedTime.TotalSeconds;\n   ```\n\n### Example Code\n\nHere’s a simple example of how DeltaTime might be implemented in a game loop:\n\n```csharp\nprivate DateTime lastFrameTime;\nprivate double velocity = 100.0; // pixels per second\nprivate double position = 0.0;\n\nprivate void GameLoop()\n{\n    DateTime currentFrameTime = DateTime.Now;\n    TimeSpan elapsedTime = currentFrameTime - lastFrameTime;\n    lastFrameTime = currentFrameTime;\n\n    // Update position based on velocity and elapsed time\n    position += velocity * elapsedTime.TotalSeconds;\n\n    // Render the new position\n    Render(position);\n}\n```\n\n### Best Practices\n\n1. **Consistent Units**: \n   - Always ensure that DeltaTime is in a consistent unit (usually seconds) to avoid scaling issues.\n\n2. **Cap DeltaTime**: \n   - Optionally cap DeltaTime to prevent large jumps in time due to frame drops, which can lead to erratic behavior.\n\n   ```csharp\n   if (elapsedTime.TotalSeconds \u003e maxDeltaTime)\n       elapsedTime = TimeSpan.FromSeconds(maxDeltaTime);\n   ```\n\n3. **Use Fixed Time Steps for Physics**: \n   - For physics calculations, consider using a fixed timestep to maintain stability.\n\n\nDeltaTime is a fundamental concept in game development and animation that allows for smooth, frame-rate-independent motion. By accurately calculating and utilizing DeltaTime, developers can create more responsive and visually appealing applications. Understanding and implementing DeltaTime correctly is essential for any developer working in real-time graphics and animation.\n\n\n\n---\n\n# Related Projects\n\nThis project serves as a direct port of the original Animation project created in VB.NET, which you can also explore for a different perspective on the same concepts. For more information and to access the complete code, visit the [Animation Repository](https://github.com/JoeLumbley/Animation) and the [Animation C# Repository](https://github.com/JoeLumbley/Animation-CS). Happy coding!\n\n\n\n\n\n\n![015](https://github.com/user-attachments/assets/73243b32-0f41-40a5-b7f8-7a8236474d88)\n\n\n\n---\n\n\n\n\n\n# License Information\n\n```plaintext\nMIT License\nCopyright (c) 2025 Joseph W. Lumbley\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction...\n```\n\n\n\n\n\n\n\n\n\n\n\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjoelumbley%2Fanimation-cs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjoelumbley%2Fanimation-cs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjoelumbley%2Fanimation-cs/lists"}