diff --git a/wp-content/themes/core/assets/js/editor/block-animations.js b/wp-content/themes/core/assets/js/editor/block-animations.js
new file mode 100644
index 00000000..c8dddd1a
--- /dev/null
+++ b/wp-content/themes/core/assets/js/editor/block-animations.js
@@ -0,0 +1,550 @@
+/**
+ * @module block-animation
+ *
+ * @description handles setting up animation settings for blocks
+ *
+ * theme.json settings:
+ * "animationType": [
+ * { "label": "None", "value": "none" },
+ * { "label": "Fade In", "value": "fade-in" }
+ * ],
+ * "animationDirection": {
+ * "fade-in": [
+ * { "label": "Top", "value": "top" },
+ * { "label": "Bottom", "value": "bottom" }
+ * ]
+ * },
+ * "animationSpeeds": [
+ * { "label": "200ms", "value": "0.2s" },
+ * { "label": "800ms", "value": "0.8s" }
+ * ],
+ * "animationDelays": [
+ * { "label": "0", "value": "0s" },
+ * { "label": "200ms", "value": "0.2s" },
+ * { "label": "800ms", "value": "0.8s" }
+ * ],
+ * "animationEasings": [
+ * { "label": "Ease In", "value": "ease-in" },
+ * { "label": "Ease Out", "value": "ease-out" }
+ * ],
+ * "animationPosition": [
+ * { "label": "25%", "value": "25" },
+ * { "label": "50%", "value": "50" },
+ * ],
+ * "animationIncludes": [
+ * "core/group",
+ * "core/heading"
+ * ],
+ * "animationExcludes": [
+ * "core/group",
+ * "core/heading"
+ * ],
+ */
+
+import { InspectorControls } from '@wordpress/block-editor';
+import { PanelBody, SelectControl, ToggleControl } from '@wordpress/components';
+import { createHigherOrderComponent } from '@wordpress/compose';
+import { Fragment } from '@wordpress/element';
+import { addFilter } from '@wordpress/hooks';
+import { __ } from '@wordpress/i18n';
+import themeJson from '../../../theme.json';
+
+const state = {};
+
+/**
+ * @function applyAnimationProps
+ *
+ * @description updates props on the block with new animation settings
+ *
+ * @param {Object} props
+ * @param {Object} block
+ * @param {Object} attributes
+ *
+ * @return {Object} updated props object
+ */
+const applyAnimationProps = ( props, block, attributes ) => {
+ // return default props if block isn't in the includes array
+ if (
+ state.includes.length > 0 &&
+ ! state.includes.includes( block.name )
+ ) {
+ return props;
+ }
+
+ // return default props if block is in the excludes array
+ if ( state.excludes.length > 0 && state.excludes.includes( block.name ) ) {
+ return props;
+ }
+
+ const {
+ animationType,
+ animationDirection,
+ animationDuration,
+ animationDelay,
+ animationMobileDisableDelay,
+ animationEasing,
+ animationTrigger,
+ animationPosition,
+ } = attributes;
+
+ if ( animationType === undefined || animationType === 'none' ) {
+ return props;
+ }
+
+ if ( props.className === undefined ) {
+ props.className = '';
+ }
+
+ props.className = `${ props.className } is-animated-on-scroll-${ animationPosition } tribe-animation-type-${ animationType } tribe-animation-direction-${ animationDirection }`;
+
+ if ( animationDuration !== undefined && animationDuration ) {
+ props.style = {
+ ...props.style,
+ '--tribe-animation-speed': animationDuration,
+ };
+ }
+
+ if ( animationDelay !== undefined && animationDelay ) {
+ props.style = {
+ ...props.style,
+ '--tribe-animation-delay': animationDelay,
+ };
+ }
+
+ if (
+ animationMobileDisableDelay !== undefined &&
+ animationMobileDisableDelay
+ ) {
+ props.className = `${ props.className } tribe-animation-mobile-disable-delay`;
+ }
+
+ if ( animationEasing !== undefined && animationEasing ) {
+ props.style = {
+ ...props.style,
+ '--tribe-animation-easing': animationEasing,
+ };
+ }
+
+ if ( animationTrigger !== undefined && animationTrigger ) {
+ props.className = `${ props.className } tribe-animate-multiple`;
+ }
+
+ return props;
+};
+
+/**
+ * @function animationControls
+ *
+ * @description creates component that overrides the edit functionality of the block with new animation controls
+ */
+const animationControls = createHigherOrderComponent( ( BlockEdit ) => {
+ return ( props ) => {
+ const { attributes, setAttributes, isSelected, name } = props;
+
+ // return default Edit function if block isn't in the includes array
+ if ( state.includes.length > 0 && ! state.includes.includes( name ) ) {
+ return ;
+ }
+
+ // return default Edit function if block is in the excludes array
+ if ( state.excludes.length > 0 && state.excludes.includes( name ) ) {
+ return ;
+ }
+
+ const {
+ animationType,
+ animationDirection,
+ animationDuration,
+ animationDelay,
+ animationMobileDisableDelay,
+ animationEasing,
+ animationTrigger,
+ animationPosition,
+ } = attributes;
+
+ let blockClass =
+ attributes.className !== undefined ? attributes.className : '';
+ const blockStyles = { ...props.style };
+
+ if ( animationType !== undefined && animationType !== 'none' ) {
+ // set block class for animation direction & animation position, if it's not set to the default
+ blockClass = `${ blockClass } is-animated-on-scroll-${ animationPosition } tribe-animation-type-${ animationType } tribe-animation-direction-${ animationDirection }`;
+
+ // set block styles for animation duration
+ if ( animationDuration !== undefined && animationDuration ) {
+ blockStyles[ '--tribe-animation-speed' ] = animationDuration;
+ }
+
+ // set block styles for animation delay
+ if ( animationDelay !== undefined && animationDelay ) {
+ blockStyles[ '--tribe-animation-delay' ] = animationDelay;
+ }
+
+ // set block class for disabling animation delays on mobile
+ if (
+ animationMobileDisableDelay !== undefined &&
+ animationMobileDisableDelay
+ ) {
+ blockClass = `${ blockClass } tribe-animation-mobile-disable-delay`;
+ }
+
+ // set block styles for animation easing
+ if ( animationEasing !== undefined && animationEasing ) {
+ blockStyles[ '--tribe-animation-easing' ] = animationEasing;
+ }
+
+ // set block class for triggering animation multiple times
+ if ( animationTrigger !== undefined && animationTrigger ) {
+ blockClass = `${ blockClass } tribe-animate-multiple`;
+ }
+ }
+
+ const blockProps = {
+ ...props,
+ attributes: {
+ ...attributes,
+ className: blockClass,
+ },
+ style: blockStyles,
+ };
+
+ return (
+
+
+ { isSelected && (
+
+
+ {
+ setAttributes( {
+ animationType: newValue,
+ } );
+ } }
+ options={ state.type }
+ />
+ { animationType === undefined ||
+ ( animationType !== 'none' && (
+ <>
+ {
+ setAttributes( {
+ animationDirection:
+ newValue,
+ } );
+ } }
+ options={
+ state.direction[ animationType ]
+ }
+ />
+
+ setAttributes( {
+ animationDuration: newValue,
+ } )
+ }
+ options={ state.duration }
+ />
+
+ setAttributes( {
+ animationDelay: newValue,
+ } )
+ }
+ options={ state.delay }
+ />
+
+ setAttributes( {
+ animationMobileDisableDelay:
+ newValue,
+ } )
+ }
+ />
+
+ setAttributes( {
+ animationEasing: newValue,
+ } )
+ }
+ options={ state.easing }
+ />
+
+ setAttributes( {
+ animationTrigger: newValue,
+ } )
+ }
+ />
+
+ setAttributes( {
+ animationPosition: newValue,
+ } )
+ }
+ options={ state.position }
+ />
+ >
+ ) ) }
+
+
+ ) }
+
+ );
+ };
+}, 'animationControls' );
+
+/**
+ * @function addAnimationAttributes
+ *
+ * @description add new attributes to blocks for animation settings
+ *
+ * @param {Object} settings
+ * @param {string} name
+ *
+ * @return {Object} returns updates settings object
+ */
+const addAnimationAttributes = ( settings, name ) => {
+ // return default settings if block isn't in the includes array
+ if ( state.includes.length > 0 && ! state.includes.includes( name ) ) {
+ return settings;
+ }
+
+ // return default settings if block is in the excludes array
+ if ( state.excludes.length > 0 && state.excludes.includes( name ) ) {
+ return settings;
+ }
+
+ if ( settings?.attributes !== undefined ) {
+ settings.attributes = {
+ ...settings.attributes,
+ animationType: {
+ type: 'string',
+ default: 'none',
+ },
+ animationDirection: {
+ type: 'string',
+ default: 'bottom',
+ },
+ animationDuration: {
+ type: 'string',
+ default: '0.6s',
+ },
+ animationDelay: {
+ type: 'string',
+ default: '0s',
+ },
+ animationMobileDisableDelay: {
+ type: 'boolean',
+ default: false,
+ },
+ animationEasing: {
+ type: 'string',
+ default: 'cubic-bezier(0.390, 0.575, 0.565, 1.000)',
+ },
+ animationTrigger: {
+ type: 'boolean',
+ default: false,
+ },
+ animationPosition: {
+ type: 'string',
+ default: '25',
+ },
+ };
+ }
+
+ return settings;
+};
+
+/**
+ * @function registerFilters
+ *
+ * @description register block filters for adding animation controls
+ */
+const registerFilters = () => {
+ addFilter(
+ 'blocks.registerBlockType',
+ 'tribe/add-animation-options',
+ addAnimationAttributes
+ );
+
+ addFilter(
+ 'editor.BlockEdit',
+ 'tribe/animation-advanced-control',
+ animationControls
+ );
+
+ addFilter(
+ 'blocks.getSaveContent.extraProps',
+ 'tribe/apply-animation-classes',
+ applyAnimationProps
+ );
+};
+
+/**
+ * @function initializeSettings
+ *
+ * @description pull settings from theme.json or add default settings
+ *
+ * @todo work with design to handle defining these defaults
+ * @todo do we need to handle translations if pulling from theme.json?
+ */
+const initializeSettings = () => {
+ state.type = themeJson?.settings?.animationType ?? [
+ { label: __( 'None', 'tribe' ), value: 'none' },
+ { label: __( 'Fade In', 'tribe' ), value: 'fade-in' },
+ ];
+ // direction is an object with keys for each animation type
+ state.direction = themeJson?.settings?.animationDirection ?? {
+ 'fade-in': [
+ { label: __( 'Bottom', 'tribe' ), value: 'bottom' },
+ { label: __( 'Right', 'tribe' ), value: 'right' },
+ { label: __( 'Top Right', 'tribe' ), value: 'top-right' },
+ { label: __( 'Bottom Right', 'tribe' ), value: 'bottom-right' },
+ { label: __( 'Left', 'tribe' ), value: 'left' },
+ { label: __( 'Top Left', 'tribe' ), value: 'top-left' },
+ { label: __( 'Bottom Left', 'tribe' ), value: 'bottom-left' },
+ { label: __( 'Forward', 'tribe' ), value: 'forward' },
+ { label: __( 'Back', 'tribe' ), value: 'back' },
+ { label: __( 'Top', 'tribe' ), value: 'top' },
+ { label: __( 'Simple', 'tribe' ), value: 'simple' },
+ ],
+ };
+ state.duration = themeJson?.settings?.animationDuration ?? [
+ { label: __( '300ms', 'tribe' ), value: '0.3s' },
+ { label: __( '600ms', 'tribe' ), value: '0.6s' },
+ { label: __( '900ms', 'tribe' ), value: '0.9s' },
+ { label: __( '1200ms', 'tribe' ), value: '1.2s' },
+ { label: __( '1600ms', 'tribe' ), value: '1.6s' },
+ ];
+ state.delay = themeJson?.settings?.animationDelay ?? [
+ { label: __( '0', 'tribe' ), value: '0s' },
+ { label: __( '300ms', 'tribe' ), value: '0.3s' },
+ { label: __( '600ms', 'tribe' ), value: '0.6s' },
+ { label: __( '900ms', 'tribe' ), value: '0.9s' },
+ { label: __( '1200ms', 'tribe' ), value: '1.2s' },
+ { label: __( '1600ms', 'tribe' ), value: '1.6s' },
+ { label: __( '2000ms', 'tribe' ), value: '2s' },
+ ];
+ state.easing = themeJson?.settings?.animationEasing ?? [
+ {
+ label: __( 'Ease Out Sine', 'tribe' ),
+ value: 'cubic-bezier(0.390, 0.575, 0.565, 1.000)',
+ },
+ {
+ label: __( 'Ease In Sine', 'tribe' ),
+ value: 'cubic-bezier(0.470, 0.000, 0.745, 0.715)',
+ },
+ {
+ label: __( 'Ease In Out Sine', 'tribe' ),
+ value: 'cubic-bezier(0.445, 0.050, 0.550, 0.950)',
+ },
+ {
+ label: __( 'Ease Out Quad', 'tribe' ),
+ value: 'cubic-bezier(0.250, 0.460, 0.450, 0.940)',
+ },
+ {
+ label: __( 'Ease In Quad', 'tribe' ),
+ value: 'cubic-bezier(0.550, 0.085, 0.680, 0.530)',
+ },
+ {
+ label: __( 'Ease In Out Quad', 'tribe' ),
+ value: 'cubic-bezier(0.455, 0.030, 0.515, 0.955)',
+ },
+ ];
+ state.position = themeJson?.settings?.animationPosition ?? [
+ { label: __( '25%', 'tribe' ), value: '25' },
+ { label: __( '50%', 'tribe' ), value: '50' },
+ { label: __( '75%', 'tribe' ), value: '75' },
+ { label: __( '100%', 'tribe' ), value: '100' },
+ ];
+ state.includes = themeJson.settings.animationIncludes ?? [];
+ state.excludes = themeJson.settings.animationExcludes ?? [];
+};
+
+/**
+ * @function init
+ *
+ * @description initializes this modules functions
+ */
+const init = () => {
+ // initialize settings
+ initializeSettings();
+
+ // handle registering block filters
+ registerFilters();
+};
+
+export default init;
diff --git a/wp-content/themes/core/assets/js/editor/ready.js b/wp-content/themes/core/assets/js/editor/ready.js
index 72776bba..eb9fb98e 100644
--- a/wp-content/themes/core/assets/js/editor/ready.js
+++ b/wp-content/themes/core/assets/js/editor/ready.js
@@ -4,13 +4,16 @@
* @description The core dispatcher for the dom ready event javascript.
*/
+import blockAnimations from './block-animations';
+
/**
* @function init
* @description The core dispatcher for init across the codebase.
*/
const init = () => {
- // intentionally left blank for now
+ // load animations
+ blockAnimations();
console.info(
'Editor: Initialized all javascript that targeted document ready.'
diff --git a/wp-content/themes/core/assets/js/theme/animate-on-scroll.js b/wp-content/themes/core/assets/js/theme/animate-on-scroll.js
new file mode 100644
index 00000000..662648d1
--- /dev/null
+++ b/wp-content/themes/core/assets/js/theme/animate-on-scroll.js
@@ -0,0 +1,107 @@
+/**
+ * @module
+ * @exports init
+ * @description functions for handling elements that should change on scroll
+ */
+
+const el = {};
+
+/**
+ * @function handleIntersection
+ *
+ * @description Callback function for when an element comes into view
+ *
+ * @param {*} entries
+ */
+const handleIntersection = ( entries ) => {
+ entries.forEach( ( entry ) => {
+ if ( entry.isIntersecting ) {
+ entry.target.classList.remove( 'is-exiting-view' );
+ entry.target.classList.add( 'is-scrolled-into-view' );
+ entry.target.classList.add( 'is-scrolled-into-view-first-time' );
+ } else {
+ entry.target.classList.remove( 'is-scrolled-into-view' );
+ entry.target.classList.add( 'is-exiting-view' );
+ }
+ } );
+};
+
+/**
+ * @function attachObservers
+ *
+ * @description attach intersection observers to elements
+ */
+const attachObservers = () => {
+ if ( el.aosElements.length ) {
+ const observer = new window.IntersectionObserver( handleIntersection, {
+ threshold: 0.25,
+ } );
+
+ el.aosElements.forEach( ( element ) => observer.observe( element ) );
+ }
+
+ if ( el.aos50Elements.length ) {
+ const observer = new window.IntersectionObserver( handleIntersection, {
+ threshold: 0.5,
+ } );
+
+ el.aos50Elements.forEach( ( element ) => observer.observe( element ) );
+ }
+
+ if ( el.aos75Elements.length ) {
+ const observer = new window.IntersectionObserver( handleIntersection, {
+ threshold: 0.75,
+ } );
+
+ el.aos75Elements.forEach( ( element ) => observer.observe( element ) );
+ }
+
+ if ( el.aosFullElements.length ) {
+ const observer = new window.IntersectionObserver( handleIntersection, {
+ threshold: 1,
+ } );
+
+ el.aosFullElements.forEach( ( element ) =>
+ observer.observe( element )
+ );
+ }
+};
+
+/**
+ * @function cacheElements
+ *
+ * @description Cache elements for this module
+ */
+const cacheElements = () => {
+ /**
+ * Note that the below selectors would need to change if the values of
+ * the animationPosition values change in theme.json (or the
+ * block-animations.js file).
+ */
+
+ // grabs elements that should animate when the element is 25% in view
+ el.aosElements = document.querySelectorAll( '.is-animated-on-scroll-25' );
+
+ // grabs elements that should animate when 50% of the element is in view
+ el.aos50Elements = document.querySelectorAll( '.is-animated-on-scroll-50' );
+
+ // grabs elements that should animate when 75% of the element is in view
+ el.aos75Elements = document.querySelectorAll( '.is-animated-on-scroll-75' );
+
+ // grabs elements that should animate when the entire element is in view
+ el.aosFullElements = document.querySelectorAll(
+ '.is-animated-on-scroll-100'
+ );
+};
+
+/**
+ * @function init
+ *
+ * @description Kick off this module's functionality
+ */
+const init = () => {
+ cacheElements();
+ attachObservers();
+};
+
+export default init;
diff --git a/wp-content/themes/core/assets/js/theme/ready.js b/wp-content/themes/core/assets/js/theme/ready.js
index 8bc21896..dd2ad60a 100644
--- a/wp-content/themes/core/assets/js/theme/ready.js
+++ b/wp-content/themes/core/assets/js/theme/ready.js
@@ -10,6 +10,8 @@ import { debounce } from 'utils/tools.js';
import resize from 'common/resize.js';
import viewportDims from 'common/viewport-dims.js';
+import animateOnScroll from './animate-on-scroll';
+
/**
* @function bindEvents
* @description Bind global event listeners here,
@@ -33,6 +35,9 @@ const init = () => {
bindEvents();
+ // run animations
+ animateOnScroll();
+
console.info(
'Theme: Initialized all javascript that targeted document ready.'
);
diff --git a/wp-content/themes/core/assets/pcss/global/animation.pcss b/wp-content/themes/core/assets/pcss/global/animation.pcss
new file mode 100644
index 00000000..d55c65e6
--- /dev/null
+++ b/wp-content/themes/core/assets/pcss/global/animation.pcss
@@ -0,0 +1,212 @@
+/* -------------------------------------------------------------------------
+ *
+ * Global: Animations
+ * Works with our block-animations code in core/assets/js/admin along with
+ * our FE IntersectionObserver in core/assets/js/utils/animate-on-scroll.js
+ * to create animations with settings on individual blocks
+ *
+ * ------------------------------------------------------------------------- */
+
+/* Setup default animation variables so they can be overridden per block */
+:root {
+ --tribe-animation-delay: 0s;
+ --tribe-animation-speed: 0.6s;
+ --tribe-animation-easing: cubic-bezier(0.39, 0.575, 0.565, 1);
+ --tribe-animation-offset: 50px;
+}
+
+/* -------------------------------------------------------------------------
+ * Animated Element / Block
+ *
+ * We set opacity to 0 here because all of our animations involve fades
+ * Also set the default transition for the block, if the "Animation should
+ * trigger every time the element is in the viewport" setting is checked,
+ * this transition will handle in & out animations.
+ * ------------------------------------------------------------------------- */
+
+.tribe-animation-type-fade-in {
+
+ /* Only run animations if the users has no preference on reduced motion */
+ @media (prefers-reduced-motion: no-preference) {
+ opacity: 0;
+ transition: opacity var(--tribe-animation-speed) var(--tribe-animation-delay) var(--tribe-animation-easing);
+
+ /* turn off delay for mobile if setting is set */
+ @media (--mq-wp-mobile-max) {
+
+ &.tribe-animation-mobile-disable-delay {
+ transition-delay: 0s !important;
+ }
+ }
+
+ /* -------------------------------------------------------------------------
+ * Fade In (Simple)
+ * Don't run "first time" animation if multiple is set
+ * ------------------------------------------------------------------------- */
+
+ &.tribe-animation-direction-simple.is-scrolled-into-view,
+ &:not(.tribe-animate-multiple).tribe-animation-direction-simple.is-scrolled-into-view-first-time {
+ opacity: 1;
+ }
+
+ /* -------------------------------------------------------------------------
+ * Fade In Up (Bottom)
+ * Don't run "first time" animation if multiple is set
+ * ------------------------------------------------------------------------- */
+
+ &.tribe-animation-direction-bottom {
+ transition-property: all;
+ transform: translateY(var(--tribe-animation-offset));
+
+ &.is-scrolled-into-view,
+ &:not(.tribe-animate-multiple).is-scrolled-into-view-first-time {
+ opacity: 1;
+ transform: translateY(0);
+ }
+ }
+
+ /* -------------------------------------------------------------------------
+ * Fade In (from the) Bottom Left (Bottom Left)
+ * Don't run "first time" animation if multiple is set
+ * ------------------------------------------------------------------------- */
+
+ &.tribe-animation-direction-bottom-left {
+ transition-property: all;
+ transform: translate(calc(var(--tribe-animation-offset) * -1), var(--tribe-animation-offset));
+
+ &.is-scrolled-into-view,
+ &:not(.tribe-animate-multiple).is-scrolled-into-view-first-time {
+ opacity: 1;
+ transform: translate(0, 0);
+ }
+ }
+
+ /* -------------------------------------------------------------------------
+ * Fade In (from the) Bottom Right (Bottom Right)
+ * Don't run "first time" animation if multiple is set
+ * ------------------------------------------------------------------------- */
+
+ &.tribe-animation-direction-bottom-right {
+ transition-property: all;
+ transform: translate(var(--tribe-animation-offset), var(--tribe-animation-offset));
+
+ &.is-scrolled-into-view,
+ &:not(.tribe-animate-multiple).is-scrolled-into-view-first-time {
+ opacity: 1;
+ transform: translate(0, 0);
+ }
+ }
+
+ /* -------------------------------------------------------------------------
+ * Fade In Down (Top)
+ * Don't run "first time" animation if multiple is set
+ * ------------------------------------------------------------------------- */
+
+ &.tribe-animation-direction-top {
+ transition-property: all;
+ transform: translateY(calc(var(--tribe-animation-offset) * -1));
+
+ &.is-scrolled-into-view,
+ &:not(.tribe-animate-multiple).is-scrolled-into-view-first-time {
+ opacity: 1;
+ transform: translateY(0);
+ }
+ }
+
+ /* -------------------------------------------------------------------------
+ * Fade In (from the) Top Right (Top Right)
+ * Don't run "first time" animation if multiple is set
+ * ------------------------------------------------------------------------- */
+
+ &.tribe-animation-direction-top-right {
+ transition-property: all;
+ transform: translate(var(--tribe-animation-offset), calc(var(--tribe-animation-offset) * -1));
+
+ &.is-scrolled-into-view,
+ &:not(.tribe-animate-multiple).is-scrolled-into-view-first-time {
+ opacity: 1;
+ transform: translate(0, 0);
+ }
+ }
+
+ /* -------------------------------------------------------------------------
+ * Fade In (from the) Top Left (Top Left)
+ * Don't run "first time" animation if multiple is set
+ * ------------------------------------------------------------------------- */
+
+ &.tribe-animation-direction-top-left {
+ transition-property: all;
+ transform: translate(calc(var(--tribe-animation-offset) * -1), calc(var(--tribe-animation-offset) * -1));
+
+ &.is-scrolled-into-view,
+ &:not(.tribe-animate-multiple).is-scrolled-into-view-first-time {
+ opacity: 1;
+ transform: translate(0, 0);
+ }
+ }
+
+ /* -------------------------------------------------------------------------
+ * Fade In (from the) Right (Left)
+ * Don't run "first time" animation if multiple is set
+ * ------------------------------------------------------------------------- */
+
+ &.tribe-animation-direction-left {
+ transition-property: all;
+ transform: translateX(calc(var(--tribe-animation-offset) * -1));
+
+ &.is-scrolled-into-view,
+ &:not(.tribe-animate-multiple).is-scrolled-into-view-first-time {
+ opacity: 1;
+ transform: translateX(0);
+ }
+ }
+
+ /* -------------------------------------------------------------------------
+ * Fade In (from the) Left (Right)
+ * Don't run "first time" animation if multiple is set
+ * ------------------------------------------------------------------------- */
+
+ &.tribe-animation-direction-right {
+ transition-property: all;
+ transform: translateX(var(--tribe-animation-offset));
+
+ &.is-scrolled-into-view,
+ &:not(.tribe-animate-multiple).is-scrolled-into-view-first-time {
+ opacity: 1;
+ transform: translateX(0);
+ }
+ }
+
+ /* -------------------------------------------------------------------------
+ * Fade In Forward
+ * Don't run "first time" animation if multiple is set
+ * ------------------------------------------------------------------------- */
+
+ &.tribe-animation-direction-forward {
+ transition-property: all;
+ transform: scale(0.85);
+
+ &.is-scrolled-into-view,
+ &:not(.tribe-animate-multiple).is-scrolled-into-view-first-time {
+ opacity: 1;
+ transform: scale(1);
+ }
+ }
+
+ /* -------------------------------------------------------------------------
+ * Fade In Backward
+ * Don't run "first time" animation if multiple is set
+ * ------------------------------------------------------------------------- */
+
+ &.tribe-animation-direction-back {
+ transition-property: all;
+ transform: scale(1.15);
+
+ &.is-scrolled-into-view,
+ &:not(.tribe-animate-multiple).is-scrolled-into-view-first-time {
+ opacity: 1;
+ transform: scale(1);
+ }
+ }
+ }
+}
diff --git a/wp-content/themes/core/assets/pcss/theme.pcss b/wp-content/themes/core/assets/pcss/theme.pcss
index b9875d82..95230e9a 100644
--- a/wp-content/themes/core/assets/pcss/theme.pcss
+++ b/wp-content/themes/core/assets/pcss/theme.pcss
@@ -11,6 +11,7 @@
/* Global Theme Styles */
@import "global/reset.pcss";
@import "typography/anchors.pcss";
+@import "global/animation.pcss";
/* Patterns */
@import "cards/post.pcss";