Repository: tailwindlabs/tailwindcss
Pull request #2068 (2020-07-24 16:11:15)

Add animation support
This PR introduces a new `animation` core plugin that generates a set of `animate-{name}` classes and the correponding `@keyframes` declarations.

Live demo:

https://tailwind-animation-playground.vercel.app/

It is configured through both the `animation` and `keyframes` sections of your `tailwind.config.js` theme:

```js
// tailwind.config.js
module.exports = {
  // ...
  theme: {
    animation: {
      none: 'none',
      spin: 'spin 1s linear infinite',
      ping: 'ping 1s cubic-bezier(0, 0, 0.2, 1) infinite',
      pulse: 'pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite',
      bounce: 'bounce 1s infinite',
    },
    keyframes: {
      spin: {
        from: { transform: 'rotate(0deg)' },
        to: { transform: 'rotate(360deg)' },
      },
      ping: {
        '0%': { transform: 'scale(1)', opacity: '1' },
        '75%, 100%': { transform: 'scale(2)', opacity: '0' },
      },
      pulse: {
        '0%, 100%': { opacity: '1' },
        '50%': { opacity: '.5' },
      },
      bounce: {
        '0%, 100%': {
          transform: 'translateY(-25%)',
          animationTimingFunction: 'cubic-bezier(0.8,0,1,1)',
        },
        '50%': {
          transform: 'translateY(0)',
          animationTimingFunction: 'cubic-bezier(0,0,0.2,1)',
        },
      },
    },
    // ...
  },
  variants: {
    animation: ['responsive'],
    // ...
  }
  // ...
}
```

Animation utilities only include `responsive` variants by default.

## Design rationale

### Targeting the `animation` shorthand property

Originally I had planned to implement this feature similar to our transition utilities, where we'd have separate utilities for animation name, duration, timing function, repeat, etc.

I decided against this ultimately because in my explorations I noticed that almost all animations are very specific, and all of the elements of an animation are inherently tightly coupled, making _composing_ animations through several utilities something that is just not that useful in the real world.

Instead I chose to implement this feature by targeting the `animation` shorthand property directly, so every detail of an animation is specified together in one place.

This has the added benefit of avoiding any naming collision with the transition utilities.

If someone wanted to create variations of a single animation with the parameters changed, they can just create multiple animations, like `animate-spin`, `animate-spin-fast`, and `animate-spin-slow`.

### Generating utilities and keyframes from a single plugin

You'll notice that the configuration for the `animation` property lives in an `animation` section of the theme, and the `@keyframes` definitions live in a `keyframes` section, but there is only one core plugin being added (`animation`).

I considered creating two plugins (`animation` and `keyframes`) but ultimately this felt pedantic. They are always going to be used together and it doesn't add any benefit to control them separately. If anything it would lead to confusion when users disable the `animation` plugin and are surprised when the `@keyframes` are still generated. It seemed more aligned with people's expectations to me that they be linked, so I've chosen to generate both from a single plugin, even though it sets a precedent as the first core plugin to read from more than one theme key.

I also considered trying to group the `animation` and `keyframes` definitions together using some fancy data structure, but the results also felt unintuitive, and led to other questions that made things feel even more confusing.

Example of what I considered but rejected:

```js
animation: {
  spin: [
    '3s linear infinite',
    {
      from: { transform: 'rotate(0deg)' },
      to: { transform: 'rotate(360deg)' },
    },
  ],
},
```

Notice how the keyframes can't be named here? Does that mean you leave the name off the animation too, like I have in that example, and have Tailwind add it automatically? Or do you manually add it yourself, knowing it has to match the key name? Too much weird magic, no good.

### Included animations

I've chosen to bake in four default animations:

- `animate-spin`, which is a simple infinite spin, for loading spinners and stuff
- `animate-ping`, which is a scale/fade animation that looks like a little radar blip
- `animate-pulse`, which is a subtle opacity pulse, useful for skeleton screens
- `animate-bounce`, which is a playful bounce, useful for arrows and stuff

You can preview them here:

https://tailwind-animation-playground.vercel.app/

They are all infinite looping animations, and were the only ideas I could come up with that felt general purpose enough to include by default. Even then, I expect all of these baked in animations are only going to be useful in very specific situations, so I'm choosing to think of them much more like **example animations** than a strongly recommended universal animation system or something.

The nature of animations is that they tend to be very specific to a given design, so I think it is inevitable that people will be adding their own to support the specific designs they are working on. Adding animations as a feature to Tailwind at least gives them an idiomatic approach for introducing animations, and makes their custom animations feel like they "belong" in the system better than if they were to just throw them in their custom CSS file.


Patch:
diff --git a/src/corePlugins.js b/src/corePlugins.js
index f1211ba3..41e2f79a 100644
--- a/src/corePlugins.js
+++ b/src/corePlugins.js
@@ -97,6 +97,7 @@ import backgroundOpacity from './plugins/backgroundOpacity'
 import borderOpacity from './plugins/borderOpacity'
 import textOpacity from './plugins/textOpacity'
 import placeholderOpacity from './plugins/placeholderOpacity'
+import animation from './plugins/animation'
 
 import configurePlugins from './util/configurePlugins'
 
@@ -201,5 +202,6 @@ export default function({ corePlugins: corePluginConfig }) {
     transitionTimingFunction,
     transitionDuration,
     transitionDelay,
+    animation,
   })
 }
diff --git a/src/plugins/animation.js b/src/plugins/animation.js
new file mode 100644
index 00000000..50ad186d
--- /dev/null
+++ b/src/plugins/animation.js
@@ -0,0 +1,26 @@
+import _ from 'lodash'
+
+export default function() {
+  return function({ addBase, addUtilities, e, theme, variants }) {
+    const keyframesConfig = theme('keyframes')
+    const keyframesStyles = _.fromPairs(
+      _.toPairs(keyframesConfig).map(([name, keyframes]) => {
+        return [`@keyframes ${name}`, keyframes]
+      })
+    )
+    addBase(keyframesStyles)
+
+    const animationConfig = theme('animation')
+    const utilities = _.fromPairs(
+      _.toPairs(animationConfig).map(([suffix, animation]) => {
+        return [
+          `.${e(`animate-${suffix}`)}`,
+          {
+            animation,
+          },
+        ]
+      })
+    )
+    addUtilities(utilities, variants('animation'))
+  }
+}
diff --git a/stubs/defaultConfig.stub.js b/stubs/defaultConfig.stub.js
index bab7b16d..f75f95f3 100644
--- a/stubs/defaultConfig.stub.js
+++ b/stubs/defaultConfig.stub.js
@@ -622,6 +622,37 @@ module.exports = {
       '700': '700ms',
       '1000': '1000ms',
     },
+    animation: {
+      none: 'none',
+      spin: 'spin 1s linear infinite',
+      ping: 'ping 1s cubic-bezier(0, 0, 0.2, 1) infinite',
+      pulse: 'pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite',
+      bounce: 'bounce 1s infinite',
+    },
+    keyframes: {
+      spin: {
+        from: { transform: 'rotate(0deg)' },
+        to: { transform: 'rotate(360deg)' },
+      },
+      ping: {
+        '0%': { transform: 'scale(1)', opacity: '1' },
+        '75%, 100%': { transform: 'scale(2)', opacity: '0' },
+      },
+      pulse: {
+        '0%, 100%': { opacity: '1' },
+        '50%': { opacity: '.5' },
+      },
+      bounce: {
+        '0%, 100%': {
+          transform: 'translateY(-25%)',
+          animationTimingFunction: 'cubic-bezier(0.8,0,1,1)',
+        },
+        '50%': {
+          transform: 'translateY(0)',
+          animationTimingFunction: 'cubic-bezier(0,0,0.2,1)',
+        },
+      },
+    },
   },
   variants: {
     accessibility: ['responsive', 'focus'],
@@ -722,6 +753,7 @@ module.exports = {
     transitionTimingFunction: ['responsive'],
     transitionDuration: ['responsive'],
     transitionDelay: ['responsive'],
+    animation: ['responsive'],
   },
   corePlugins: {},
   plugins: [],


Test patch:
diff --git a/__tests__/fixtures/tailwind-output-ie11.css b/__tests__/fixtures/tailwind-output-ie11.css
index 760a22cf..7e06186a 100644
--- a/__tests__/fixtures/tailwind-output-ie11.css
+++ b/__tests__/fixtures/tailwind-output-ie11.css
@@ -577,6 +577,50 @@ video {
   height: auto;
 }
 
+@keyframes spin {
+  from {
+    transform: rotate(0deg);
+  }
+
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@keyframes ping {
+  0% {
+    transform: scale(1);
+    opacity: 1;
+  }
+
+  75%, 100% {
+    transform: scale(2);
+    opacity: 0;
+  }
+}
+
+@keyframes pulse {
+  0%, 100% {
+    opacity: 1;
+  }
+
+  50% {
+    opacity: .5;
+  }
+}
+
+@keyframes bounce {
+  0%, 100% {
+    transform: translateY(-25%);
+    animation-timing-function: cubic-bezier(0.8,0,1,1);
+  }
+
+  50% {
+    transform: translateY(0);
+    animation-timing-function: cubic-bezier(0,0,0.2,1);
+  }
+}
+
 .container {
   width: 100%;
 }
@@ -10950,6 +10994,26 @@ video {
   transition-delay: 1000ms;
 }
 
+.animate-none {
+  animation: none;
+}
+
+.animate-spin {
+  animation: spin 1s linear infinite;
+}
+
+.animate-ping {
+  animation: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite;
+}
+
+.animate-pulse {
+  animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
+}
+
+.animate-bounce {
+  animation: bounce 1s infinite;
+}
+
 .example {
   font-weight: 700;
   color: #f56565;
@@ -21329,6 +21393,26 @@ video {
     transition-delay: 1000ms;
   }
 
+  .sm\:animate-none {
+    animation: none;
+  }
+
+  .sm\:animate-spin {
+    animation: spin 1s linear infinite;
+  }
+
+  .sm\:animate-ping {
+    animation: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite;
+  }
+
+  .sm\:animate-pulse {
+    animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
+  }
+
+  .sm\:animate-bounce {
+    animation: bounce 1s infinite;
+  }
+
   .sm\:example {
     font-weight: 700;
     color: #f56565;
@@ -31709,6 +31793,26 @@ video {
     transition-delay: 1000ms;
   }
 
+  .md\:animate-none {
+    animation: none;
+  }
+
+  .md\:animate-spin {
+    animation: spin 1s linear infinite;
+  }
+
+  .md\:animate-ping {
+    animation: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite;
+  }
+
+  .md\:animate-pulse {
+    animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
+  }
+
+  .md\:animate-bounce {
+    animation: bounce 1s infinite;
+  }
+
   .md\:example {
     font-weight: 700;
     color: #f56565;
@@ -42089,6 +42193,26 @@ video {
     transition-delay: 1000ms;
   }
 
+  .lg\:animate-none {
+    animation: none;
+  }
+
+  .lg\:animate-spin {
+    animation: spin 1s linear infinite;
+  }
+
+  .lg\:animate-ping {
+    animation: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite;
+  }
+
+  .lg\:animate-pulse {
+    animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
+  }
+
+  .lg\:animate-bounce {
+    animation: bounce 1s infinite;
+  }
+
   .lg\:example {
     font-weight: 700;
     color: #f56565;
@@ -52469,6 +52593,26 @@ video {
     transition-delay: 1000ms;
   }
 
+  .xl\:animate-none {
+    animation: none;
+  }
+
+  .xl\:animate-spin {
+    animation: spin 1s linear infinite;
+  }
+
+  .xl\:animate-ping {
+    animation: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite;
+  }
+
+  .xl\:animate-pulse {
+    animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
+  }
+
+  .xl\:animate-bounce {
+    animation: bounce 1s infinite;
+  }
+
   .xl\:example {
     font-weight: 700;
     color: #f56565;
diff --git a/__tests__/fixtures/tailwind-output-important.css b/__tests__/fixtures/tailwind-output-important.css
index 0949bc69..c8c61c63 100644
--- a/__tests__/fixtures/tailwind-output-important.css
+++ b/__tests__/fixtures/tailwind-output-important.css
@@ -577,6 +577,50 @@ video {
   height: auto;
 }
 
+@keyframes spin {
+  from {
+    transform: rotate(0deg);
+  }
+
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@keyframes ping {
+  0% {
+    transform: scale(1);
+    opacity: 1;
+  }
+
+  75%, 100% {
+    transform: scale(2);
+    opacity: 0;
+  }
+}
+
+@keyframes pulse {
+  0%, 100% {
+    opacity: 1;
+  }
+
+  50% {
+    opacity: .5;
+  }
+}
+
+@keyframes bounce {
+  0%, 100% {
+    transform: translateY(-25%);
+    animation-timing-function: cubic-bezier(0.8,0,1,1);
+  }
+
+  50% {
+    transform: translateY(0);
+    animation-timing-function: cubic-bezier(0,0,0.2,1);
+  }
+}
+
 .container {
   width: 100%;
 }
@@ -14336,6 +14380,26 @@ video {
   transition-delay: 1000ms !important;
 }
 
+.animate-none {
+  animation: none !important;
+}
+
+.animate-spin {
+  animation: spin 1s linear infinite !important;
+}
+
+.animate-ping {
+  animation: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite !important;
+}
+
+.animate-pulse {
+  animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite !important;
+}
+
+.animate-bounce {
+  animation: bounce 1s infinite !important;
+}
+
 .example {
   font-weight: 700;
   color: #f56565;
@@ -28101,6 +28165,26 @@ video {
     transition-delay: 1000ms !important;
   }
 
+  .sm\:animate-none {
+    animation: none !important;
+  }
+
+  .sm\:animate-spin {
+    animation: spin 1s linear infinite !important;
+  }
+
+  .sm\:animate-ping {
+    animation: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite !important;
+  }
+
+  .sm\:animate-pulse {
+    animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite !important;
+  }
+
+  .sm\:animate-bounce {
+    animation: bounce 1s infinite !important;
+  }
+
   .sm\:example {
     font-weight: 700;
     color: #f56565;
@@ -41867,6 +41951,26 @@ video {
     transition-delay: 1000ms !important;
   }
 
+  .md\:animate-none {
+    animation: none !important;
+  }
+
+  .md\:animate-spin {
+    animation: spin 1s linear infinite !important;
+  }
+
+  .md\:animate-ping {
+    animation: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite !important;
+  }
+
+  .md\:animate-pulse {
+    animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite !important;
+  }
+
+  .md\:animate-bounce {
+    animation: bounce 1s infinite !important;
+  }
+
   .md\:example {
     font-weight: 700;
     color: #f56565;
@@ -55633,6 +55737,26 @@ video {
     transition-delay: 1000ms !important;
   }
 
+  .lg\:animate-none {
+    animation: none !important;
+  }
+
+  .lg\:animate-spin {
+    animation: spin 1s linear infinite !important;
+  }
+
+  .lg\:animate-ping {
+    animation: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite !important;
+  }
+
+  .lg\:animate-pulse {
+    animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite !important;
+  }
+
+  .lg\:animate-bounce {
+    animation: bounce 1s infinite !important;
+  }
+
   .lg\:example {
     font-weight: 700;
     color: #f56565;
@@ -69399,6 +69523,26 @@ video {
     transition-delay: 1000ms !important;
   }
 
+  .xl\:animate-none {
+    animation: none !important;
+  }
+
+  .xl\:animate-spin {
+    animation: spin 1s linear infinite !important;
+  }
+
+  .xl\:animate-ping {
+    animation: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite !important;
+  }
+
+  .xl\:animate-pulse {
+    animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite !important;
+  }
+
+  .xl\:animate-bounce {
+    animation: bounce 1s infinite !important;
+  }
+
   .xl\:example {
     font-weight: 700;
     color: #f56565;
diff --git a/__tests__/fixtures/tailwind-output-no-color-opacity.css b/__tests__/fixtures/tailwind-output-no-color-opacity.css
index cc84a1b6..14ab13e5 100644
--- a/__tests__/fixtures/tailwind-output-no-color-opacity.css
+++ b/__tests__/fixtures/tailwind-output-no-color-opacity.css
@@ -577,6 +577,50 @@ video {
   height: auto;
 }
 
+@keyframes spin {
+  from {
+    transform: rotate(0deg);
+  }
+
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@keyframes ping {
+  0% {
+    transform: scale(1);
+    opacity: 1;
+  }
+
+  75%, 100% {
+    transform: scale(2);
+    opacity: 0;
+  }
+}
+
+@keyframes pulse {
+  0%, 100% {
+    opacity: 1;
+  }
+
+  50% {
+    opacity: .5;
+  }
+}
+
+@keyframes bounce {
+  0%, 100% {
+    transform: translateY(-25%);
+    animation-timing-function: cubic-bezier(0.8,0,1,1);
+  }
+
+  50% {
+    transform: translateY(0);
+    animation-timing-function: cubic-bezier(0,0,0.2,1);
+  }
+}
+
 .container {
   width: 100%;
 }
@@ -11888,6 +11932,26 @@ video {
   transition-delay: 1000ms;
 }
 
+.animate-none {
+  animation: none;
+}
+
+.animate-spin {
+  animation: spin 1s linear infinite;
+}
+
+.animate-ping {
+  animation: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite;
+}
+
+.animate-pulse {
+  animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
+}
+
+.animate-bounce {
+  animation: bounce 1s infinite;
+}
+
 .example {
   font-weight: 700;
   color: #f56565;
@@ -23205,6 +23269,26 @@ video {
     transition-delay: 1000ms;
   }
 
+  .sm\:animate-none {
+    animation: none;
+  }
+
+  .sm\:animate-spin {
+    animation: spin 1s linear infinite;
+  }
+
+  .sm\:animate-ping {
+    animation: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite;
+  }
+
+  .sm\:animate-pulse {
+    animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
+  }
+
+  .sm\:animate-bounce {
+    animation: bounce 1s infinite;
+  }
+
   .sm\:example {
     font-weight: 700;
     color: #f56565;
@@ -34523,6 +34607,26 @@ video {
     transition-delay: 1000ms;
   }
 
+  .md\:animate-none {
+    animation: none;
+  }
+
+  .md\:animate-spin {
+    animation: spin 1s linear infinite;
+  }
+
+  .md\:animate-ping {
+    animation: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite;
+  }
+
+  .md\:animate-pulse {
+    animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
+  }
+
+  .md\:animate-bounce {
+    animation: bounce 1s infinite;
+  }
+
   .md\:example {
     font-weight: 700;
     color: #f56565;
@@ -45841,6 +45945,26 @@ video {
     transition-delay: 1000ms;
   }
 
+  .lg\:animate-none {
+    animation: none;
+  }
+
+  .lg\:animate-spin {
+    animation: spin 1s linear infinite;
+  }
+
+  .lg\:animate-ping {
+    animation: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite;
+  }
+
+  .lg\:animate-pulse {
+    animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
+  }
+
+  .lg\:animate-bounce {
+    animation: bounce 1s infinite;
+  }
+
   .lg\:example {
     font-weight: 700;
     color: #f56565;
@@ -57159,6 +57283,26 @@ video {
     transition-delay: 1000ms;
   }
 
+  .xl\:animate-none {
+    animation: none;
+  }
+
+  .xl\:animate-spin {
+    animation: spin 1s linear infinite;
+  }
+
+  .xl\:animate-ping {
+    animation: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite;
+  }
+
+  .xl\:animate-pulse {
+    animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
+  }
+
+  .xl\:animate-bounce {
+    animation: bounce 1s infinite;
+  }
+
   .xl\:example {
     font-weight: 700;
     color: #f56565;
diff --git a/__tests__/fixtures/tailwind-output.css b/__tests__/fixtures/tailwind-output.css
index 97596e0b..064db904 100644
--- a/__tests__/fixtures/tailwind-output.css
+++ b/__tests__/fixtures/tailwind-output.css
@@ -577,6 +577,50 @@ video {
   height: auto;
 }
 
+@keyframes spin {
+  from {
+    transform: rotate(0deg);
+  }
+
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@keyframes ping {
+  0% {
+    transform: scale(1);
+    opacity: 1;
+  }
+
+  75%, 100% {
+    transform: scale(2);
+    opacity: 0;
+  }
+}
+
+@keyframes pulse {
+  0%, 100% {
+    opacity: 1;
+  }
+
+  50% {
+    opacity: .5;
+  }
+}
+
+@keyframes bounce {
+  0%, 100% {
+    transform: translateY(-25%);
+    animation-timing-function: cubic-bezier(0.8,0,1,1);
+  }
+
+  50% {
+    transform: translateY(0);
+    animation-timing-function: cubic-bezier(0,0,0.2,1);
+  }
+}
+
 .container {
   width: 100%;
 }
@@ -14336,6 +14380,26 @@ video {
   transition-delay: 1000ms;
 }
 
+.animate-none {
+  animation: none;
+}
+
+.animate-spin {
+  animation: spin 1s linear infinite;
+}
+
+.animate-ping {
+  animation: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite;
+}
+
+.animate-pulse {
+  animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
+}
+
+.animate-bounce {
+  animation: bounce 1s infinite;
+}
+
 .example {
   font-weight: 700;
   color: #f56565;
@@ -28101,6 +28165,26 @@ video {
     transition-delay: 1000ms;
   }
 
+  .sm\:animate-none {
+    animation: none;
+  }
+
+  .sm\:animate-spin {
+    animation: spin 1s linear infinite;
+  }
+
+  .sm\:animate-ping {
+    animation: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite;
+  }
+
+  .sm\:animate-pulse {
+    animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
+  }
+
+  .sm\:animate-bounce {
+    animation: bounce 1s infinite;
+  }
+
   .sm\:example {
     font-weight: 700;
     color: #f56565;
@@ -41867,6 +41951,26 @@ video {
     transition-delay: 1000ms;
   }
 
+  .md\:animate-none {
+    animation: none;
+  }
+
+  .md\:animate-spin {
+    animation: spin 1s linear infinite;
+  }
+
+  .md\:animate-ping {
+    animation: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite;
+  }
+
+  .md\:animate-pulse {
+    animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
+  }
+
+  .md\:animate-bounce {
+    animation: bounce 1s infinite;
+  }
+
   .md\:example {
     font-weight: 700;
     color: #f56565;
@@ -55633,6 +55737,26 @@ video {
     transition-delay: 1000ms;
   }
 
+  .lg\:animate-none {
+    animation: none;
+  }
+
+  .lg\:animate-spin {
+    animation: spin 1s linear infinite;
+  }
+
+  .lg\:animate-ping {
+    animation: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite;
+  }
+
+  .lg\:animate-pulse {
+    animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
+  }
+
+  .lg\:animate-bounce {
+    animation: bounce 1s infinite;
+  }
+
   .lg\:example {
     font-weight: 700;
     color: #f56565;
@@ -69399,6 +69523,26 @@ video {
     transition-delay: 1000ms;
   }
 
+  .xl\:animate-none {
+    animation: none;
+  }
+
+  .xl\:animate-spin {
+    animation: spin 1s linear infinite;
+  }
+
+  .xl\:animate-ping {
+    animation: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite;
+  }
+
+  .xl\:animate-pulse {
+    animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
+  }
+
+  .xl\:animate-bounce {
+    animation: bounce 1s infinite;
+  }
+
   .xl\:example {
     font-weight: 700;
     color: #f56565;