diff --git a/projects/packages/jetpack-mu-wpcom/.eslintignore b/projects/packages/jetpack-mu-wpcom/.eslintignore
index 6a0ddf86aeb10..3ccc072cf2684 100644
--- a/projects/packages/jetpack-mu-wpcom/.eslintignore
+++ b/projects/packages/jetpack-mu-wpcom/.eslintignore
@@ -1 +1,2 @@
src/features/newspack-blocks/synced-newspack-blocks
+src/features/holiday-snow/snowstorm.js
diff --git a/projects/packages/jetpack-mu-wpcom/changelog/add-holiday-snow b/projects/packages/jetpack-mu-wpcom/changelog/add-holiday-snow
new file mode 100644
index 0000000000000..59dee1913ffb6
--- /dev/null
+++ b/projects/packages/jetpack-mu-wpcom/changelog/add-holiday-snow
@@ -0,0 +1,4 @@
+Significance: minor
+Type: added
+
+WordPress.com Features: add Holiday Snow functionality.
diff --git a/projects/packages/jetpack-mu-wpcom/src/class-jetpack-mu-wpcom.php b/projects/packages/jetpack-mu-wpcom/src/class-jetpack-mu-wpcom.php
index 05261495c7668..a607b7e67d910 100644
--- a/projects/packages/jetpack-mu-wpcom/src/class-jetpack-mu-wpcom.php
+++ b/projects/packages/jetpack-mu-wpcom/src/class-jetpack-mu-wpcom.php
@@ -103,6 +103,7 @@ public static function load_features() {
require_once __DIR__ . '/features/first-posts-stream/first-posts-stream-helpers.php';
require_once __DIR__ . '/features/font-smoothing-antialiased/font-smoothing-antialiased.php';
require_once __DIR__ . '/features/google-analytics/google-analytics.php';
+ require_once __DIR__ . '/features/holiday-snow/class-holiday-snow.php';
require_once __DIR__ . '/features/import-customizations/import-customizations.php';
require_once __DIR__ . '/features/marketplace-products-updater/class-marketplace-products-updater.php';
require_once __DIR__ . '/features/media/heif-support.php';
@@ -118,6 +119,8 @@ public static function load_features() {
\Automattic\Jetpack\Classic_Theme_Helper\Main::init();
\Automattic\Jetpack\Classic_Theme_Helper\Featured_Content::setup();
+ \Automattic\Jetpack\Jetpack_Mu_Wpcom\Holiday_Snow::init();
+
// Gets autoloaded from the Scheduled_Updates package.
if ( class_exists( 'Automattic\Jetpack\Scheduled_Updates' ) ) {
Scheduled_Updates::init();
diff --git a/projects/packages/jetpack-mu-wpcom/src/features/holiday-snow/class-holiday-snow.php b/projects/packages/jetpack-mu-wpcom/src/features/holiday-snow/class-holiday-snow.php
new file mode 100644
index 0000000000000..cd53e73d43957
--- /dev/null
+++ b/projects/packages/jetpack-mu-wpcom/src/features/holiday-snow/class-holiday-snow.php
@@ -0,0 +1,216 @@
+= $first_snow_day || $today < $last_snow_day ) {
+ $is_snow_season = true;
+ }
+
+ /**
+ * Filter to check if it is the snow season.
+ * It allows to change the start and end dates of the season,
+ * for regions where the holiday season may be different.
+ *
+ * @since $$next-version$$
+ *
+ * @param bool $is_holiday_snow_season Is it the snow season?
+ */
+ return apply_filters( 'jetpack_is_holiday_snow_season', $is_snow_season );
+ }
+
+ /**
+ * Check if the snow is enabled.
+ *
+ * @return bool
+ */
+ public static function is_snow_enabled() {
+ return (bool) get_option( self::HOLIDAY_SNOW_OPTION_NAME );
+ }
+
+ /**
+ * Register the hooks.
+ *
+ * @return void
+ */
+ public static function init() {
+ if ( ! self::is_snow_season() ) {
+ return;
+ }
+
+ add_filter( 'site_settings_endpoint_get', array( __CLASS__, 'add_option_api' ) );
+ add_filter( 'rest_api_update_site_settings', array( __CLASS__, 'update_option_api' ), 10, 2 );
+ add_action( 'admin_init', array( __CLASS__, 'register_settings' ) );
+ add_action( 'update_option_' . self::HOLIDAY_SNOW_OPTION_NAME, array( __CLASS__, 'holiday_snow_option_updated' ) );
+
+ if ( self::is_snow_enabled() ) {
+ add_action( 'wp_enqueue_scripts', array( __CLASS__, 'holiday_snow_script' ) );
+ }
+ }
+
+ /**
+ * Enqueue the snowstorm script on the frontend.
+ *
+ * @return void
+ */
+ public static function holiday_snow_script() {
+ if (
+ /**
+ * Allow short-circuiting the snow, even when enabled on the site in settings.
+ *
+ * @since $$next-version$$
+ *
+ * @param bool true Whether to show the snow.
+ */
+ ! apply_filters( 'jetpack_holiday_chance_of_snow', true )
+ ) {
+ return;
+ }
+
+ /**
+ * Fires when the snow is falling.
+ *
+ * @since $$next-version$$
+ */
+ do_action( 'jetpack_stats_extra', 'holiday_snow', 'snowing' );
+
+ /**
+ * Filter the URL of the snowstorm script.
+ *
+ * @since $$next-version$$
+ *
+ * @param string $snowstorm_url URL of the snowstorm script.
+ */
+ $snowstorm_url = apply_filters(
+ 'jetpack_holiday_snow_js_url',
+ plugins_url( 'snowstorm.js', __FILE__ )
+ );
+
+ wp_enqueue_script(
+ 'snowstorm',
+ $snowstorm_url,
+ array(),
+ \Automattic\Jetpack\Jetpack_Mu_Wpcom::PACKAGE_VERSION,
+ true
+ );
+ }
+
+ /**
+ * Add the option to the v1 API site settings endpoint.
+ *
+ * @param array $settings A single site's settings.
+ * @return array
+ */
+ public static function add_option_api( $settings ) {
+ $settings[ self::HOLIDAY_SNOW_OPTION_NAME ] = self::is_snow_enabled();
+
+ return $settings;
+ }
+
+ /**
+ * Update settings via public-api.wordpress.com.
+ *
+ * @param array $input Associative array of site settings to be updated.
+ * Cast and filtered based on documentation.
+ * @param array $unfiltered_input Associative array of site settings to be updated.
+ * Neither cast nor filtered. Contains raw input.
+ * @return array
+ */
+ public static function update_option_api( $input, $unfiltered_input ) {
+ if ( isset( $unfiltered_input[ self::HOLIDAY_SNOW_OPTION_NAME ] ) ) {
+ $input[ self::HOLIDAY_SNOW_OPTION_NAME ] = (bool) $unfiltered_input[ self::HOLIDAY_SNOW_OPTION_NAME ];
+ }
+
+ return $input;
+ }
+
+ /**
+ * Registers the settings section and fields.
+ *
+ * @return void
+ */
+ public static function register_settings() {
+ register_setting(
+ 'general',
+ self::HOLIDAY_SNOW_OPTION_NAME,
+ array(
+ 'type' => 'boolean',
+ 'description' => esc_attr__( 'Show falling snow on my site', 'jetpack-mu-wpcom' ),
+ 'show_in_rest' => true,
+ 'default' => false,
+ 'sanitize_callback' => function ( $value ) {
+ return (bool) $value;
+ },
+ )
+ );
+
+ add_settings_field(
+ self::HOLIDAY_SNOW_OPTION_NAME,
+ esc_attr__( 'Snow', 'jetpack-mu-wpcom' ),
+ array( __CLASS__, 'option_field_display' ),
+ 'general',
+ 'default',
+ array(
+ 'label_for' => self::HOLIDAY_SNOW_OPTION_NAME,
+ )
+ );
+ }
+
+ /**
+ * Renders the Snow settings markup.
+ *
+ * @return void
+ */
+ public static function option_field_display() {
+ printf(
+ '',
+ esc_attr( self::HOLIDAY_SNOW_OPTION_NAME ),
+ checked( self::is_snow_enabled(), true, false ),
+ wp_kses(
+ __( 'Show falling snow on my site until January 4th.', 'jetpack-mu-wpcom' ),
+ array(
+ 'sup' => array(),
+ )
+ )
+ );
+ }
+
+ /**
+ * Fires whenever the holiday snow option is updated.
+ * Used to gather stats about modified options.
+ *
+ * @return void
+ */
+ public static function holiday_snow_option_updated() {
+ /** This action is already documented in modules/widgets/gravatar-profile.php */
+ do_action( 'jetpack_stats_extra', 'holiday_snow', 'toggle' );
+ }
+}
diff --git a/projects/packages/jetpack-mu-wpcom/src/features/holiday-snow/snowstorm.js b/projects/packages/jetpack-mu-wpcom/src/features/holiday-snow/snowstorm.js
new file mode 100644
index 0000000000000..1559e4e4df738
--- /dev/null
+++ b/projects/packages/jetpack-mu-wpcom/src/features/holiday-snow/snowstorm.js
@@ -0,0 +1,29 @@
+/** @license
+
+ DHTML Snowstorm! JavaScript-based snow for web pages
+ Making it snow on the internets since 2003. You're welcome.
+ -----------------------------------------------------------
+ Version 1.44.20131215 (Previous rev: 1.44.20131208)
+ Copyright (c) 2007, Scott Schiller. All rights reserved.
+ Code provided under the BSD License
+ http://schillmania.com/projects/snowstorm/license.txt
+*/
+var snowStorm=function(g,f){function k(a,d){isNaN(d)&&(d=0);return Math.random()*a+d}function x(){g.setTimeout(function(){a.start(!0)},20);a.events.remove(m?f:g,"mousemove",x)}function y(){(!a.excludeMobile||!D)&&x();a.events.remove(g,"load",y)}this.excludeMobile=this.autoStart=!0;this.flakesMax=128;this.flakesMaxActive=64;this.animationInterval=33;this.useGPU=!0;this.className=null;this.excludeMobile=!0;this.flakeBottom=null;this.followMouse=!0;this.snowColor="#fff";this.snowCharacter="•";this.snowStick=
+!0;this.targetElement=null;this.useMeltEffect=!0;this.usePixelPosition=this.usePositionFixed=this.useTwinkleEffect=!1;this.freezeOnBlur=!0;this.flakeRightOffset=this.flakeLeftOffset=0;this.flakeHeight=this.flakeWidth=8;this.vMaxX=5;this.vMaxY=4;this.zIndex=0;var a=this,q,m=navigator.userAgent.match(/msie/i),E=navigator.userAgent.match(/msie 6/i),D=navigator.userAgent.match(/mobile|opera m(ob|in)/i),r=m&&"BackCompat"===f.compatMode||E,h=null,n=null,l=null,p=null,s=null,z=null,A=null,v=1,t=!1,w=!1,
+u;a:{try{f.createElement("div").style.opacity="0.5"}catch(F){u=!1;break a}u=!0}var B=!1,C=f.createDocumentFragment();q=function(){function c(b){g.setTimeout(b,1E3/(a.animationInterval||20))}function d(a){return void 0!==h.style[a]?a:null}var e,b=g.requestAnimationFrame||g.webkitRequestAnimationFrame||g.mozRequestAnimationFrame||g.oRequestAnimationFrame||g.msRequestAnimationFrame||c;e=b?function(){return b.apply(g,arguments)}:null;var h;h=f.createElement("div");e={transform:{ie:d("-ms-transform"),
+moz:d("MozTransform"),opera:d("OTransform"),webkit:d("webkitTransform"),w3:d("transform"),prop:null},getAnimationFrame:e};e.transform.prop=e.transform.w3||e.transform.moz||e.transform.webkit||e.transform.ie||e.transform.opera;h=null;return e}();this.timer=null;this.flakes=[];this.active=this.disabled=!1;this.meltFrameCount=20;this.meltFrames=[];this.setXY=function(c,d,e){if(!c)return!1;a.usePixelPosition||w?(c.style.left=d-a.flakeWidth+"px",c.style.top=e-a.flakeHeight+"px"):r?(c.style.right=100-100*
+(d/h)+"%",c.style.top=Math.min(e,s-a.flakeHeight)+"px"):a.flakeBottom?(c.style.right=100-100*(d/h)+"%",c.style.top=Math.min(e,s-a.flakeHeight)+"px"):(c.style.right=100-100*(d/h)+"%",c.style.bottom=100-100*(e/l)+"%")};this.events=function(){function a(c){c=b.call(c);var d=c.length;e?(c[1]="on"+c[1],3b.vX?b.vX=0.2:0>b.vX&&-0.2b.vY&&(b.vY=0.2)};this.move=function(){var c=b.vX*v;b.x+=c;b.y+=b.vY*b.vAmp;b.x>=h||h-b.xc&&b.x-a.flakeLeftOffset<-a.flakeWidth&&(b.x=h-a.flakeWidth-1);b.refresh();l+p-b.y+a.flakeHeightb.type&&!b.melting&&0.998b.twinkleFrame?0.97a.flakesMaxActive)a.flakes[a.flakes.length-1].active=-1;a.targetElement.appendChild(C)};this.timerInit=
+function(){a.timer=!0;a.snow()};this.init=function(){var c;for(c=0;c 'pineapple',
'carousel_display_exif' => 'pineapple',
'carousel_display_comments' => 'pineapple',
+ 'jetpack_holiday_snow_enabled' => false,
'jetpack_portfolio' => 'pineapple',
'jetpack_portfolio_posts_per_page' => 'pineapple',
'jetpack_testimonial' => 'pineapple',