{"id":361892,"date":"2026-09-05T13:28:48","date_gmt":"2026-09-05T13:28:48","guid":{"rendered":"https:\/\/wordpress.org\/plugins\/devdiggers-pwa\/"},"modified":"2026-10-23T21:12:35","modified_gmt":"2026-10-23T21:12:35","slug":"devdiggers-pwa","status":"publish","type":"plugin","link":"https:\/\/ceb.wordpress.org\/plugins\/devdiggers-pwa\/","author":20777317,"comment_status":"closed","ping_status":"closed","template":"","meta":{"version":"1.1.1","stable_tag":"1.1.1","tested":"7.1.3","requires":"6.2","requires_php":"7.4","requires_plugins":null,"header_name":"DevDiggers PWA","header_author":"DevDiggers","header_description":"Turn your WordPress or WooCommerce site into an installable app with offline browsing and web push notifications.","assets_banners_color":"fefefe","last_updated":"2026-10-23 21:12:35","external_support_url":"","external_repository_url":"","donate_link":"","header_plugin_uri":"https:\/\/devdiggers.com\/product\/progressive-web-app-for-wordpress\/","header_author_uri":"https:\/\/devdiggers.com\/","rating":5,"author_block_rating":0,"active_installs":10,"downloads":491,"num_ratings":1,"support_threads":0,"support_threads_resolved":0,"author_block_count":0,"sections":["description","installation","faq","changelog"],"tags":{"1.0.0":{"tag":"1.0.0","author":"devdiggers","date":"2026-09-05 13:28:23","revision":3682483},"1.1.0":{"tag":"1.1.0","author":"devdiggers","date":"2026-09-29 15:13:51","revision":3719328},"1.1.1":{"tag":"1.1.1","author":"devdiggers","date":"2026-10-23 21:12:35","revision":3762463}},"upgrade_notice":{"1.1.1":"<p>Fixes a fatal error when saving a push notification. Recommended for all users.<\/p>","1.1.0":"<p>Adds faster admin navigation without page reloads, a refreshed list-screen layout and minor bug fixes. Recommended for all users.<\/p>","1.0.0":"<p>First release. After activating, run the Setup Wizard so the manifest and service worker are generated.<\/p>"},"ratings":{"1":0,"2":0,"3":0,"4":0,"5":1},"assets_icons":{"icon-128x128.gif":{"filename":"icon-128x128.gif","revision":3682482,"resolution":"128x128","location":"assets","locale":"","width":128,"height":128},"icon-256x256.gif":{"filename":"icon-256x256.gif","revision":3682482,"resolution":"256x256","location":"assets","locale":"","width":256,"height":256}},"assets_banners":{"banner-1544x500-rtl.png":{"filename":"banner-1544x500-rtl.png","revision":3682482,"resolution":"1544x500","location":"assets","locale":"","width":1544,"height":500},"banner-1544x500.png":{"filename":"banner-1544x500.png","revision":3682482,"resolution":"1544x500","location":"assets","locale":"","width":1544,"height":500},"banner-772x250-rtl.png":{"filename":"banner-772x250-rtl.png","revision":3682482,"resolution":"772x250","location":"assets","locale":"","width":772,"height":250},"banner-772x250.png":{"filename":"banner-772x250.png","revision":3682482,"resolution":"772x250","location":"assets","locale":"","width":772,"height":250}},"assets_blueprints":{"blueprint.json":{"filename":"blueprint.json","revision":3762464,"resolution":false,"location":"assets","locale":"","contents":"{\"$schema\":\"https:\\\/\\\/playground.wordpress.net\\\/blueprint-schema.json\",\"meta\":{\"title\":\"DevDiggers PWA demo\",\"description\":\"A home goods store turned into an installable app, with subscribers, installs and push campaigns on the dashboard.\",\"author\":\"devdiggers\",\"categories\":[\"PWA\",\"WooCommerce\"]},\"landingPage\":\"\\\/wp-admin\\\/admin.php?page=ddwppwa-dashboard\",\"preferredVersions\":{\"php\":\"8.2\",\"wp\":\"latest\"},\"features\":{\"networking\":true},\"steps\":[{\"step\":\"installPlugin\",\"pluginData\":{\"resource\":\"wordpress.org\\\/plugins\",\"slug\":\"woocommerce\"},\"options\":{\"activate\":true}},{\"step\":\"installPlugin\",\"pluginData\":{\"resource\":\"wordpress.org\\\/plugins\",\"slug\":\"devdiggers-pwa\"},\"options\":{\"activate\":true}},{\"step\":\"installTheme\",\"themeData\":{\"resource\":\"wordpress.org\\\/themes\",\"slug\":\"kadence\"},\"options\":{\"activate\":true}},{\"step\":\"setSiteOptions\",\"options\":{\"woocommerce_onboarding_profile\":{\"skipped\":true},\"woocommerce_task_list_hidden\":\"yes\",\"woocommerce_task_list_complete\":\"yes\",\"woocommerce_show_marketplace_suggestions\":\"no\",\"woocommerce_allow_tracking\":\"no\",\"woocommerce_coming_soon\":\"no\",\"ddfw_setup_wizard_completed_devdiggers-pwa\":true}},{\"step\":\"runPHP\",\"code\":\"<?php\\nrequire '\\\/wordpress\\\/wp-load.php';\\nrequire_once ABSPATH . 'wp-admin\\\/includes\\\/image.php';\\nwp_set_current_user( 1 ); \\\/\\\/ Admin context, so global styles are saved unfiltered.\\n\\ndelete_transient( '_wc_activation_redirect' );\\ndelete_transient( 'ddfw_activation_redirect_devdiggers-pwa' );\\n\\n\\\/\\\/ Portraits for the demo people (loaded in the visitor's browser).\\n$faces = [\\n\\t'admin@localhost.com'          => 'https:\\\/\\\/randomuser.me\\\/api\\\/portraits\\\/women\\\/68.jpg',\\n\\t'amara.okafor@example.com'     => 'https:\\\/\\\/randomuser.me\\\/api\\\/portraits\\\/women\\\/44.jpg',\\n\\t'liam.carter@example.com'      => 'https:\\\/\\\/randomuser.me\\\/api\\\/portraits\\\/men\\\/32.jpg',\\n\\t'sofia.romero@example.com'     => 'https:\\\/\\\/randomuser.me\\\/api\\\/portraits\\\/women\\\/65.jpg',\\n\\t'kenji.watanabe@example.com'   => 'https:\\\/\\\/randomuser.me\\\/api\\\/portraits\\\/men\\\/75.jpg',\\n\\t'priya.sharma@example.com'     => 'https:\\\/\\\/randomuser.me\\\/api\\\/portraits\\\/women\\\/90.jpg',\\n\\t'noah.mueller@example.com'     => 'https:\\\/\\\/randomuser.me\\\/api\\\/portraits\\\/men\\\/46.jpg',\\n\\t'chloe.dubois@example.com'     => 'https:\\\/\\\/randomuser.me\\\/api\\\/portraits\\\/women\\\/24.jpg',\\n\\t'marcus.bell@example.com'      => 'https:\\\/\\\/randomuser.me\\\/api\\\/portraits\\\/men\\\/86.jpg',\\n\\t'aisha.rahman@example.com'     => 'https:\\\/\\\/randomuser.me\\\/api\\\/portraits\\\/women\\\/12.jpg',\\n\\t'diego.santos@example.com'     => 'https:\\\/\\\/randomuser.me\\\/api\\\/portraits\\\/men\\\/22.jpg',\\n];\\nwp_mkdir_p( WPMU_PLUGIN_DIR );\\nfile_put_contents(\\n\\tWPMU_PLUGIN_DIR . '\\\/demo-avatars.php',\\n\\t\\\"<?php\\\\n\\\/\\\/ Live Preview only: show a portrait for each demo person.\\\\nadd_filter( 'pre_get_avatar_data', function ( \\\\$args, \\\\$id ) {\\\\n\\\\t\\\\$faces = \\\" . var_export( $faces, true ) . \\\";\\\\n\\\\t\\\\$email = '';\\\\n\\\\tif ( is_numeric( \\\\$id ) ) { \\\\$u = get_userdata( (int) \\\\$id ); \\\\$email = \\\\$u ? \\\\$u->user_email : ''; }\\\\n\\\\telseif ( is_string( \\\\$id ) ) { \\\\$email = \\\\$id; }\\\\n\\\\telseif ( \\\\$id instanceof WP_User ) { \\\\$email = \\\\$id->user_email; }\\\\n\\\\telseif ( \\\\$id instanceof WP_Comment ) { \\\\$email = \\\\$id->comment_author_email; }\\\\n\\\\t\\\\$email = strtolower( \\\\$email );\\\\n\\\\tif ( isset( \\\\$faces[ \\\\$email ] ) ) { \\\\$args['url'] = \\\\$faces[ \\\\$email ]; \\\\$args['found_avatar'] = true; }\\\\n\\\\treturn \\\\$args;\\\\n}, 10, 2 );\\\\n\\\/\\\/ Live Preview only: hide the guest-order email verification prompt.\\\\nadd_filter( 'woocommerce_customer_email_verification_should_show_prompt', '__return_false' );\\\\n\\\"\\n);\\n\\nupdate_option( 'blogname', 'Lumen & Leaf' );\\nupdate_option( 'blogdescription', 'Modern home goods, now in your pocket' );\\nupdate_option( 'woocommerce_currency', 'USD' );\\nupdate_option( 'woocommerce_default_country', 'US:CA' );\\nwp_update_user( [ 'ID' => 1, 'first_name' => 'Maya', 'last_name' => 'Lin', 'display_name' => 'Maya Lin' ] );\\n\\n\\\/\\\/ Products with real photos that ship inside WooCommerce.\\n$img_dir = WP_PLUGIN_DIR . '\\\/woocommerce\\\/assets\\\/images\\\/pattern-placeholders\\\/';\\n$catalog = [\\n\\t[ 'Sage Ceramic Vase', 48, 'sun-glass-vase-green-ceramic-shelf.jpg', 'Wheel-thrown stoneware vase in a soft sage glaze.' ],\\n\\t[ 'Ivory Pattern Table Lamp', 129, 'white-vase-decoration-pattern-ceramic-lamp.jpg', 'Hand-carved ceramic base with a linen shade.' ],\\n\\t[ 'Oak Bistro Stool', 185, 'Image-table-wood-chair-stool-interior-restaurant.jpg', 'Solid white oak with a hand-rubbed oil finish.' ],\\n\\t[ 'Blown Glass Carafe Set', 64, 'drinkware-liquid-tableware-dishware-bottle-fluid.jpg', 'A one litre carafe with two matching tumblers.' ],\\n\\t[ 'Green Glass Pantry Jars', 42, 'green-glass-jars-on-stairs.jpg', 'Set of three recycled glass jars with cork lids.' ],\\n\\t[ 'Eucalyptus Bud Vase', 36, 'plant-white-leaf-flower-vase-green.jpg', 'Matte white porcelain, styled with dried eucalyptus.' ],\\n\\t[ 'Window Reading Chair', 420, 'table-wood-house-chair-floor-window.jpg', 'Ash frame with a woven paper cord seat.' ],\\n\\t[ 'Platinum Mesh Watch', 240, 'watch-hand-brand-jewellery-strap-platinum.jpg', 'Minimal dial on a brushed platinum mesh strap.' ],\\n\\t[ 'Walnut Record Player', 349, 'music-needle-turntable-black-and-white-white-photography.jpg', 'Belt drive turntable in a walnut plinth.' ],\\n];\\n$cat_of   = [ 'Sage Ceramic Vase' => 'Ceramics', 'Ivory Pattern Table Lamp' => 'Lighting', 'Oak Bistro Stool' => 'Furniture', 'Blown Glass Carafe Set' => 'Glassware', 'Green Glass Pantry Jars' => 'Glassware', 'Eucalyptus Bud Vase' => 'Ceramics', 'Window Reading Chair' => 'Furniture', 'Platinum Mesh Watch' => 'Accessories', 'Walnut Record Player' => 'Accessories' ];\\n$products = [];\\n$photos   = [];\\nforeach ( $catalog as $p ) {\\n\\t$upload = wp_upload_bits( $p[2], null, file_get_contents( $img_dir . $p[2] ) );\\n\\t$att_id = wp_insert_attachment( [ 'post_mime_type' => 'image\\\/jpeg', 'post_title' => $p[0], 'post_status' => 'inherit' ], $upload['file'] );\\n\\twp_update_attachment_metadata( $att_id, wp_generate_attachment_metadata( $att_id, $upload['file'] ) );\\n\\t$prod = new WC_Product_Simple();\\n\\t$prod->set_name( $p[0] );\\n\\t$prod->set_regular_price( $p[1] );\\n\\t$prod->set_short_description( $p[3] );\\n\\t$prod->set_description( $p[3] );\\n\\t$prod->set_image_id( $att_id );\\n\\t$prod->set_status( 'publish' );\\n\\t$term = term_exists( $cat_of[ $p[0] ], 'product_cat' ) ?: wp_insert_term( $cat_of[ $p[0] ], 'product_cat' );\\n\\t$prod->set_category_ids( [ (int) $term['term_id'] ] );\\n\\t$products[]           = $prod->save();\\n\\t$photos[ $p[0] ]      = wp_get_attachment_url( $att_id );\\n}\\n\\n\\\/\\\/ App icons drawn with GD: a leaf on a rounded green tile.\\nfunction ddwppwa_demo_icon( $size ) {\\n\\t$s   = 4 * $size; \\\/\\\/ Supersample, then scale down for smooth edges.\\n\\t$im  = imagecreatetruecolor( $s, $s );\\n\\timagesavealpha( $im, true );\\n\\timagefill( $im, 0, 0, imagecolorallocatealpha( $im, 0, 0, 0, 127 ) );\\n\\t$bg  = imagecolorallocate( $im, 31, 111, 92 );\\n\\t$r   = (int) ( $s * 0.22 );\\n\\timagefilledrectangle( $im, $r, 0, $s - $r, $s, $bg );\\n\\timagefilledrectangle( $im, 0, $r, $s, $s - $r, $bg );\\n\\tforeach ( [ [ $r, $r ], [ $s - $r, $r ], [ $r, $s - $r ], [ $s - $r, $s - $r ] ] as $c ) {\\n\\t\\timagefilledellipse( $im, $c[0], $c[1], 2 * $r, 2 * $r, $bg );\\n\\t}\\n\\t$leaf = imagecolorallocate( $im, 246, 241, 228 );\\n\\t$pts  = [];\\n\\t$rot  = deg2rad( -40 );\\n\\tfor ( $a = 0; $a < 360; $a += 5 ) { \\\/\\\/ Lens-shaped leaf, tilted.\\n\\t\\t$t     = deg2rad( $a );\\n\\t\\t$x     = 0.30 * cos( $t );\\n\\t\\t$y     = 0.15 * sin( $t ) * abs( sin( $t ) ) ** 0.2;\\n\\t\\t$pts[] = (int) ( $s * ( 0.5 + $x * cos( $rot ) - $y * sin( $rot ) ) );\\n\\t\\t$pts[] = (int) ( $s * ( 0.5 + $x * sin( $rot ) + $y * cos( $rot ) ) );\\n\\t}\\n\\timagefilledpolygon( $im, $pts, $leaf );\\n\\timagesetthickness( $im, max( 2, (int) ( $s * 0.02 ) ) );\\n\\timageline( $im, (int) ( $s * 0.27 ), (int) ( $s * 0.69 ), (int) ( $s * 0.70 ), (int) ( $s * 0.33 ), $bg );\\n\\t$out = imagecreatetruecolor( $size, $size );\\n\\timagesavealpha( $out, true );\\n\\timagefill( $out, 0, 0, imagecolorallocatealpha( $out, 0, 0, 0, 127 ) );\\n\\timagecopyresampled( $out, $im, 0, 0, 0, 0, $size, $size, $s, $s );\\n\\tob_start();\\n\\timagepng( $out );\\n\\treturn ob_get_clean();\\n}\\nforeach ( [ 48, 96, 144, 196, 512 ] as $size ) {\\n\\t$upload = wp_upload_bits( \\\"lumen-leaf-icon-{$size}.png\\\", null, ddwppwa_demo_icon( $size ) );\\n\\t$att_id = wp_insert_attachment( [ 'post_mime_type' => 'image\\\/png', 'post_title' => \\\"App icon {$size}\\\", 'post_status' => 'inherit' ], $upload['file'] );\\n\\twp_update_attachment_metadata( $att_id, wp_generate_attachment_metadata( $att_id, $upload['file'] ) );\\n\\tupdate_option( '_ddwppwa_app_icon' . $size, $att_id );\\n\\tif ( 512 === $size ) {\\n\\t\\tupdate_option( '_ddwppwa_app_icon_maskable', $att_id );\\n\\t\\tupdate_option( 'site_icon', $att_id );\\n\\t}\\n}\\n\\n\\\/\\\/ Offline page.\\n$offline = wp_insert_post( [ 'post_type' => 'page', 'post_status' => 'publish', 'post_title' => 'You are offline', 'post_content' => '<!-- wp:paragraph --><p>No connection right now. Pages you have already visited still work, and your cart is saved. We will pick up where you left off as soon as you are back online.<\\\/p><!-- \\\/wp:paragraph -->' ] );\\n\\n\\\/\\\/ Free plugin settings: app, install banner, push prompt, offline, automations.\\n$settings = [\\n\\t'_ddwppwa_app_name'                 => 'Lumen & Leaf',\\n\\t'_ddwppwa_app_short_name'           => 'Lumen',\\n\\t'_ddwppwa_app_description'          => 'Shop modern home goods, track orders and get restock alerts.',\\n\\t'_ddwppwa_app_theme_color'          => '#1f6f5c',\\n\\t'_ddwppwa_app_bg_color'             => '#f6f1e4',\\n\\t'_ddwppwa_app_display'              => 'standalone',\\n\\t'_ddwppwa_app_orientation'          => 'portrait',\\n\\t'_ddwppwa_app_categories'           => 'shopping, lifestyle',\\n\\t'_ddwppwa_ios_status_bar_style'     => 'black-translucent',\\n\\t'_ddwppwa_utm_enabled'              => 'yes',\\n\\t'_ddwppwa_utm_campaign'             => 'home-screen',\\n\\t'_ddwppwa_install_banner_enabled'   => 'yes',\\n\\t'_ddwppwa_install_banner_title'     => 'Get the Lumen & Leaf app',\\n\\t'_ddwppwa_install_banner_message'   => 'Faster browsing, offline wishlists and early access to drops.',\\n\\t'_ddwppwa_install_banner_button'    => 'Install app',\\n\\t'_ddwppwa_install_banner_bg'        => '#f6f1e4',\\n\\t'_ddwppwa_install_banner_text'      => '#16302a',\\n\\t'_ddwppwa_install_banner_btn_bg'    => '#1f6f5c',\\n\\t'_ddwppwa_install_banner_btn_text'  => '#ffffff',\\n\\t'_ddwppwa_notification_popup'       => 'yes',\\n\\t'_ddwppwa_popup_message'            => 'Be first to know about new drops',\\n\\t'_ddwppwa_popup_description'        => 'Restocks, order updates and members-only offers. No spam, unsubscribe anytime.',\\n\\t'_ddwppwa_popup_allow_label'        => 'Notify me',\\n\\t'_ddwppwa_popup_bg'                 => '#ffffff',\\n\\t'_ddwppwa_popup_text'               => '#16302a',\\n\\t'_ddwppwa_popup_accent'             => '#1f6f5c',\\n\\t'_ddwppwa_popup_btn_text'           => '#ffffff',\\n\\t'_ddwppwa_prompt_delay'             => 8,\\n\\t'_ddwppwa_prompt_pageviews'         => 2,\\n\\t'_ddwppwa_offline_page'             => $offline,\\n\\t'_ddwppwa_cache_max_age'            => 14,\\n\\t'_ddwppwa_auto_post_enabled'        => 'yes',\\n\\t'_ddwppwa_auto_post_title_tpl'      => 'From the journal: {title}',\\n\\t'_ddwppwa_welcome_enabled'          => 'yes',\\n\\t'_ddwppwa_welcome_title'            => 'Welcome to Lumen & Leaf',\\n\\t'_ddwppwa_welcome_body'             => 'You are in! Enjoy 10% off your first order with code HELLOAPP.',\\n\\t'_ddwppwa_welcome_url'              => home_url( '\\\/shop\\\/' ),\\n];\\nforeach ( $settings as $k => $v ) {\\n\\tupdate_option( $k, $v );\\n}\\nforeach ( [ 'pwa', 'offline', 'push', 'analytics' ] as $m ) {\\n\\tupdate_option( '_ddwppwa_module_' . $m, 'yes' );\\n}\\n\\n\\\/\\\/ Customers.\\n$people = [\\n\\t[ 'amara', 'Amara', 'Okafor', 'amara.okafor@example.com', 'Seattle', 'WA', '98101' ],\\n\\t[ 'liam', 'Liam', 'Carter', 'liam.carter@example.com', 'Denver', 'CO', '80203' ],\\n\\t[ 'sofia', 'Sofia', 'Romero', 'sofia.romero@example.com', 'San Diego', 'CA', '92101' ],\\n\\t[ 'kenji', 'Kenji', 'Watanabe', 'kenji.watanabe@example.com', 'San Francisco', 'CA', '94104' ],\\n\\t[ 'priya', 'Priya', 'Sharma', 'priya.sharma@example.com', 'Austin', 'TX', '78701' ],\\n\\t[ 'noah', 'Noah', 'Mueller', 'noah.mueller@example.com', 'Chicago', 'IL', '60601' ],\\n\\t[ 'chloe', 'Chloe', 'Dubois', 'chloe.dubois@example.com', 'Boston', 'MA', '02108' ],\\n\\t[ 'marcus', 'Marcus', 'Bell', 'marcus.bell@example.com', 'Miami', 'FL', '33139' ],\\n\\t[ 'aisha', 'Aisha', 'Rahman', 'aisha.rahman@example.com', 'New York', 'NY', '10001' ],\\n\\t[ 'diego', 'Diego', 'Santos', 'diego.santos@example.com', 'Phoenix', 'AZ', '85004' ],\\n];\\n$users = [];\\nforeach ( $people as $p ) {\\n\\t$users[] = wp_insert_user( [ 'user_login' => $p[0], 'user_pass' => 'password', 'user_email' => $p[3], 'first_name' => $p[1], 'last_name' => $p[2], 'display_name' => $p[1] . ' ' . $p[2], 'role' => 'customer' ] );\\n}\\n\\n\\\/\\\/ Orders over the past seven weeks.\\nmt_srand( 7 );\\nfor ( $i = 0; $i < 32; $i++ ) {\\n\\t$k     = $i % count( $people );\\n\\t$p     = $people[ $k ];\\n\\t$addr  = [ 'first_name' => $p[1], 'last_name' => $p[2], 'email' => $p[3], 'address_1' => ( 10 + $i ) . ' Market Street', 'city' => $p[4], 'state' => $p[5], 'postcode' => $p[6], 'country' => 'US' ];\\n\\t$order = wc_create_order( [ 'customer_id' => $users[ $k ] ] );\\n\\t$order->add_product( wc_get_product( $products[ mt_rand( 0, count( $products ) - 1 ) ] ), mt_rand( 1, 2 ) );\\n\\tif ( 0 === $i % 3 ) {\\n\\t\\t$order->add_product( wc_get_product( $products[ mt_rand( 0, count( $products ) - 1 ) ] ), 1 );\\n\\t}\\n\\t$order->set_address( $addr, 'billing' );\\n\\t$order->set_address( $addr, 'shipping' );\\n\\t$order->set_payment_method_title( 'Credit card' );\\n\\t$order->set_created_via( 0 === $i % 2 ? 'pwa' : 'checkout' );\\n\\t$order->set_date_created( time() - (int) ( ( 48 - $i * 1.5 ) * DAY_IN_SECONDS ) - mt_rand( 0, 20 ) * HOUR_IN_SECONDS );\\n\\t$order->calculate_totals();\\n\\t$order->set_status( $i > 28 ? 'processing' : 'completed' );\\n\\t$order->save();\\n}\\n\\n\\\/\\\/ Devices: subscribers, installs, browsers and device types.\\nglobal $wpdb;\\n$now      = time();\\n$browsers = [ 'Chrome', 'Chrome', 'Chrome', 'Safari', 'Safari', 'Edge', 'Samsung', 'Firefox' ];\\n$devices  = [ 'Chrome' => [ [ 'Android', 'phone' ], [ 'Windows', 'desktop' ], [ 'macOS', 'desktop' ], [ 'Android', 'tablet' ] ], 'Safari' => [ [ 'iOS', 'phone' ], [ 'iOS', 'tablet' ], [ 'macOS', 'desktop' ] ], 'Edge' => [ [ 'Windows', 'desktop' ] ], 'Samsung' => [ [ 'Android', 'phone' ] ], 'Firefox' => [ [ 'Windows', 'desktop' ], [ 'Linux', 'desktop' ] ] ];\\nfor ( $i = 0; $i < 84; $i++ ) {\\n\\t$b         = $browsers[ mt_rand( 0, count( $browsers ) - 1 ) ];\\n\\t$d         = $devices[ $b ][ mt_rand( 0, count( $devices[ $b ] ) - 1 ) ];\\n\\t$sub       = $now - (int) ( pow( mt_rand( 0, 1000 ) \\\/ 1000, 1.4 ) * 45 * DAY_IN_SECONDS );\\n\\t$installed = 'desktop' !== $d[1] ? mt_rand( 0, 100 ) < 62 : mt_rand( 0, 100 ) < 25;\\n\\t$token     = 'demo' . wp_generate_password( 140, false );\\n\\t$wpdb->insert( $wpdb->prefix . 'ddwppwa_subscribers', [\\n\\t\\t'client_id'     => wp_generate_uuid4(),\\n\\t\\t'token'         => $token,\\n\\t\\t'token_hash'    => md5( $token ),\\n\\t\\t'user_id'       => $i < 20 ? $users[ $i % count( $users ) ] : 0,\\n\\t\\t'status'        => 0 === $i % 17 ? 'stale' : 'subscribed',\\n\\t\\t'platform'      => $d[0],\\n\\t\\t'browser'       => $b,\\n\\t\\t'device_type'   => $d[1],\\n\\t\\t'installed'     => $installed ? 1 : 0,\\n\\t\\t'welcomed'      => 1,\\n\\t\\t'subscribed_at' => gmdate( 'Y-m-d H:i:s', $sub ),\\n\\t\\t'installed_at'  => $installed ? gmdate( 'Y-m-d H:i:s', $sub + mt_rand( 0, 3 * DAY_IN_SECONDS ) ) : null,\\n\\t\\t'last_seen'     => gmdate( 'Y-m-d H:i:s', min( $now - 300, $sub + mt_rand( 0, 30 ) * DAY_IN_SECONDS ) ),\\n\\t] );\\n}\\n\\n\\\/\\\/ Daily analytics counters (keyed by each day's midnight timestamp) for the charts.\\n$installs = [];\\n$subs     = [];\\nfor ( $d = 59; $d >= 0; $d-- ) {\\n\\t$day              = strtotime( gmdate( 'Y-m-d', $now - $d * DAY_IN_SECONDS ) . ' 00:00:00' );\\n\\t$trend            = ( 60 - $d ) \\\/ 60;\\n\\t$weekend          = in_array( gmdate( 'N', $day ), [ '6', '7' ], true ) ? 1.4 : 1;\\n\\t$installs[ $day ] = (int) round( ( 2 + 6 * $trend ) * $weekend + mt_rand( 0, 3 ) );\\n\\t$subs[ $day ]     = (int) round( ( 4 + 9 * $trend ) * $weekend + mt_rand( 0, 4 ) );\\n}\\nupdate_option( '_ddwppwa_pwa_installation_analytics', $installs, false );\\nupdate_option( '_ddwppwa_subscribers_analytics', $subs, false );\\n\\n\\\/\\\/ Notification campaigns and their delivery log.\\n$campaigns = [\\n\\t[ 'Autumn drop is live', 'Hand-thrown vases and linen lamps, fresh from the studio.', 'Sage Ceramic Vase', '\\\/shop\\\/', 'enabled', 2 ],\\n\\t[ 'Back in stock: Oak Bistro Stool', 'The most wished-for stool is back. Only 40 made this batch.', 'Oak Bistro Stool', '\\\/shop\\\/', 'enabled', 6 ],\\n\\t[ 'Weekend only: 15% off glassware', 'Carafes, tumblers and pantry jars, until Sunday midnight.', 'Blown Glass Carafe Set', '\\\/shop\\\/', 'enabled', 11 ],\\n\\t[ 'Spin something new', 'Meet the Walnut Record Player. Warm sound, belt drive, built-in preamp.', 'Walnut Record Player', '\\\/shop\\\/', 'enabled', 19 ],\\n\\t[ 'Your reading nook, sorted', 'The Window Reading Chair now ships in five days.', 'Window Reading Chair', '\\\/shop\\\/', 'enabled', 27 ],\\n\\t[ 'Holiday gift guide (draft)', 'Thoughtful gifts under $50, wrapped and ready.', 'Eucalyptus Bud Vase', '\\\/shop\\\/', 'disabled', 0 ],\\n];\\n$audience = 76;\\nforeach ( $campaigns as $c ) {\\n\\t$sent = $c[5] ? $now - $c[5] * DAY_IN_SECONDS - 3 * HOUR_IN_SECONDS : 0;\\n\\t$wpdb->insert( $wpdb->prefix . 'ddwppwa_notifications', [\\n\\t\\t'title'       => $c[0],\\n\\t\\t'description' => $c[1],\\n\\t\\t'image'       => $photos[ $c[2] ],\\n\\t\\t'url'         => home_url( $c[3] ),\\n\\t\\t'status'      => $c[4],\\n\\t\\t'segment'     => 'all',\\n\\t\\t'badge'       => '',\\n\\t\\t'actions'     => '',\\n\\t\\t'created_at'  => gmdate( 'Y-m-d H:i:s', $sent ? $sent - HOUR_IN_SECONDS : $now - DAY_IN_SECONDS ),\\n\\t\\t'sent_at'     => $sent ? gmdate( 'Y-m-d H:i:s', $sent ) : null,\\n\\t] );\\n\\tif ( $sent ) {\\n\\t\\t$wpdb->insert( $wpdb->prefix . 'ddwppwa_notification_log', [ 'notification_id' => $wpdb->insert_id, 'type' => 'manual', 'title' => $c[0], 'body' => $c[1], 'url' => home_url( $c[3] ), 'target' => 'all', 'status' => 'sent', 'message_id' => 'projects\\\/lumen-leaf\\\/messages\\\/' . mt_rand( 1e9, 9e9 ), 'recipients' => $audience - $c[5], 'created_at' => gmdate( 'Y-m-d H:i:s', $sent ) ] );\\n\\t}\\n}\\n$journal = [ 'How to style a bookshelf in five minutes', 'Caring for oiled oak furniture', 'Our favourite autumn stems', 'Inside the ceramics studio' ];\\nforeach ( $journal as $j => $title ) {\\n\\t$when = $now - ( 4 + $j * 9 ) * DAY_IN_SECONDS;\\n\\twp_insert_post( [ 'post_status' => 'publish', 'post_title' => $title, 'post_content' => '<!-- wp:paragraph --><p>Notes and ideas from the Lumen &amp; Leaf studio.<\\\/p><!-- \\\/wp:paragraph -->', 'post_date' => gmdate( 'Y-m-d H:i:s', $when ) ] );\\n\\t$wpdb->insert( $wpdb->prefix . 'ddwppwa_notification_log', [ 'type' => 'auto_post', 'title' => 'From the journal: ' . $title, 'body' => 'New on the blog. Tap to read.', 'url' => home_url( '\\\/' ), 'target' => 'all', 'status' => 'sent', 'message_id' => 'projects\\\/lumen-leaf\\\/messages\\\/' . mt_rand( 1e9, 9e9 ), 'recipients' => 70 - $j * 6, 'created_at' => gmdate( 'Y-m-d H:i:s', $when + 120 ) ] );\\n}\\nfor ( $i = 0; $i < 26; $i++ ) {\\n\\t$when = $now - (int) ( $i * 1.7 * DAY_IN_SECONDS ) - mt_rand( 0, 20 ) * HOUR_IN_SECONDS;\\n\\t$wpdb->insert( $wpdb->prefix . 'ddwppwa_notification_log', [ 'type' => 'welcome', 'title' => 'Welcome to Lumen & Leaf', 'body' => 'You are in! Enjoy 10% off your first order with code HELLOAPP.', 'url' => home_url( '\\\/shop\\\/' ), 'target' => 'device', 'status' => 13 === $i ? 'failed' : 'sent', 'message_id' => 13 === $i ? '' : 'projects\\\/lumen-leaf\\\/messages\\\/' . mt_rand( 1e9, 9e9 ), 'recipients' => 1, 'error' => 13 === $i ? 'Requested entity was not found.' : null, 'created_at' => gmdate( 'Y-m-d H:i:s', $when ) ] );\\n}\\n$wpdb->insert( $wpdb->prefix . 'ddwppwa_notification_log', [ 'type' => 'test', 'title' => 'Test notification', 'body' => 'If you can read this, push is working.', 'url' => home_url( '\\\/' ), 'target' => 'device', 'status' => 'sent', 'message_id' => 'projects\\\/lumen-leaf\\\/messages\\\/' . mt_rand( 1e9, 9e9 ), 'recipients' => 1, 'created_at' => gmdate( 'Y-m-d H:i:s', $now - 30 * DAY_IN_SECONDS ) ] );\\n\\n\\\/\\\/ ---- DevDiggers Live Preview demo kit (Apple design language on Kadence) ----\\n\\\/**\\n * Inserted by build.py where a seed says \\\/\\\/ @kit. Call, after products and users exist:\\n *   ddwc_demo_kit_style( [ 'accent' => '#0071e3', 'css' => '' ] )  Kadence + global CSS.\\n *   ddwc_demo_kit_home( [...] )                                     Home page, set as front page.\\n *   ddwc_demo_kit_menu( [ 'Shop' => url, ... ] )                    Primary menu.\\n *   ddwc_demo_kit_switcher( $views_php )                            \\\"View as\\\" switcher mu-plugin.\\n *   ddwc_demo_kit_icon( 'chat' )                                    SVG line icon (names in the list below).\\n * Widget classes for the hero showcase: dk-widget, dk-w-head, dk-w-app, dk-w-label, dk-w-title,\\n * dk-w-big, dk-w-row, dk-w-pill, dk-w-avatar, dk-w-bubble, dk-w-note.\\n *\\\/\\n\\nfunction ddwc_demo_kit_icon( $name, $size = 24 ) {\\n\\t$paths = [\\n\\t\\t'admin'    => '<rect x=\\\"3\\\" y=\\\"3\\\" width=\\\"7\\\" height=\\\"9\\\" rx=\\\"1.5\\\"\\\/><rect x=\\\"14\\\" y=\\\"3\\\" width=\\\"7\\\" height=\\\"5\\\" rx=\\\"1.5\\\"\\\/><rect x=\\\"14\\\" y=\\\"12\\\" width=\\\"7\\\" height=\\\"9\\\" rx=\\\"1.5\\\"\\\/><rect x=\\\"3\\\" y=\\\"16\\\" width=\\\"7\\\" height=\\\"5\\\" rx=\\\"1.5\\\"\\\/>',\\n\\t\\t'customer' => '<circle cx=\\\"12\\\" cy=\\\"8\\\" r=\\\"4\\\"\\\/><path d=\\\"M4 21c0-4 3.6-7 8-7s8 3 8 7\\\"\\\/>',\\n\\t\\t'store'    => '<path d=\\\"M3 9l1.5-5h15L21 9\\\"\\\/><path d=\\\"M3 9h18v2a3 3 0 0 1-6 0 3 3 0 0 1-6 0 3 3 0 0 1-6 0z\\\"\\\/><path d=\\\"M5 13v8h14v-8\\\"\\\/><path d=\\\"M10 21v-5h4v5\\\"\\\/>',\\n\\t\\t'product'  => '<path d=\\\"M20.6 13.4 13.4 20.6a2 2 0 0 1-2.8 0L3 13V3h10l7.6 7.6a2 2 0 0 1 0 2.8z\\\"\\\/><circle cx=\\\"7.5\\\" cy=\\\"7.5\\\" r=\\\"1.5\\\"\\\/>',\\n\\t\\t'cart'     => '<circle cx=\\\"9\\\" cy=\\\"20\\\" r=\\\"1.5\\\"\\\/><circle cx=\\\"18\\\" cy=\\\"20\\\" r=\\\"1.5\\\"\\\/><path d=\\\"M2 3h3l2.7 12.4a2 2 0 0 0 2 1.6h8.6a2 2 0 0 0 2-1.5L22 7H6\\\"\\\/>',\\n\\t\\t'chat'     => '<path d=\\\"M21 12a8 8 0 0 1-11.6 7.1L4 20l1-4.6A8 8 0 1 1 21 12z\\\"\\\/>',\\n\\t\\t'wallet'   => '<path d=\\\"M3 7a2 2 0 0 1 2-2h13v4\\\"\\\/><rect x=\\\"3\\\" y=\\\"7\\\" width=\\\"18\\\" height=\\\"13\\\" rx=\\\"2\\\"\\\/><circle cx=\\\"16.5\\\" cy=\\\"13.5\\\" r=\\\"1.3\\\"\\\/>',\\n\\t\\t'star'     => '<path d=\\\"m12 3 2.8 5.7 6.2.9-4.5 4.4 1.1 6.2L12 17.3 6.4 20.2l1.1-6.2L3 9.6l6.2-.9z\\\"\\\/>',\\n\\t\\t'register' => '<rect x=\\\"3\\\" y=\\\"4\\\" width=\\\"18\\\" height=\\\"12\\\" rx=\\\"2\\\"\\\/><path d=\\\"M7 20h10M12 16v4M7 8h4M7 11h2\\\"\\\/>',\\n\\t\\t'link'     => '<path d=\\\"M10 14a4 4 0 0 0 5.7 0l3-3a4 4 0 0 0-5.7-5.7l-1 1\\\"\\\/><path d=\\\"M14 10a4 4 0 0 0-5.7 0l-3 3a4 4 0 0 0 5.7 5.7l1-1\\\"\\\/>',\\n\\t\\t'cube'     => '<path d=\\\"M12 2 21 7v10l-9 5-9-5V7z\\\"\\\/><path d=\\\"m3 7 9 5 9-5M12 12v10\\\"\\\/>',\\n\\t\\t'ar'       => '<path d=\\\"M3 7V4h3M21 7V4h-3M3 17v3h3M21 17v3h-3\\\"\\\/><rect x=\\\"8\\\" y=\\\"8\\\" width=\\\"8\\\" height=\\\"8\\\" rx=\\\"1.5\\\"\\\/>',\\n\\t\\t'rx'       => '<path d=\\\"M9 3h6v4H9z\\\"\\\/><path d=\\\"M7 5H5v16h14V5h-2\\\"\\\/><path d=\\\"M9 12h6M9 16h4\\\"\\\/>',\\n\\t\\t'phone'    => '<rect x=\\\"6\\\" y=\\\"2\\\" width=\\\"12\\\" height=\\\"20\\\" rx=\\\"3\\\"\\\/><path d=\\\"M11 18h2\\\"\\\/>',\\n\\t\\t'orders'   => '<path d=\\\"M4 7 12 3l8 4v10l-8 4-8-4z\\\"\\\/><path d=\\\"m4 7 8 4 8-4M12 11v10\\\"\\\/>',\\n\\t\\t'calendar' => '<rect x=\\\"3\\\" y=\\\"5\\\" width=\\\"18\\\" height=\\\"16\\\" rx=\\\"2\\\"\\\/><path d=\\\"M3 10h18M8 3v4M16 3v4\\\"\\\/>',\\n\\t\\t'clock'    => '<circle cx=\\\"12\\\" cy=\\\"13\\\" r=\\\"8\\\"\\\/><path d=\\\"M12 9v4l2.5 2.5M9 2h6\\\"\\\/>',\\n\\t\\t'mail'     => '<rect x=\\\"3\\\" y=\\\"5\\\" width=\\\"18\\\" height=\\\"14\\\" rx=\\\"2\\\"\\\/><path d=\\\"m3 7 9 6 9-6\\\"\\\/>',\\n\\t\\t'chart'    => '<path d=\\\"M3 3v18h18\\\"\\\/><path d=\\\"m7 15 4-4 3 3 5-6\\\"\\\/>',\\n\\t\\t'card'     => '<rect x=\\\"2\\\" y=\\\"5\\\" width=\\\"20\\\" height=\\\"14\\\" rx=\\\"2\\\"\\\/><path d=\\\"M2 10h20M6 15h4\\\"\\\/>',\\n\\t\\t'scan'     => '<path d=\\\"M3 7V5a2 2 0 0 1 2-2h2M17 3h2a2 2 0 0 1 2 2v2M21 17v2a2 2 0 0 1-2 2h-2M7 21H5a2 2 0 0 1-2-2v-2M7 8v8M11 8v8M14 8v8M17 8v8\\\"\\\/>',\\n\\t\\t'receipt'  => '<path d=\\\"M5 3h14v18l-3-2-2 2-2-2-2 2-2-2-3 2z\\\"\\\/><path d=\\\"M9 8h6M9 12h6\\\"\\\/>',\\n\\t\\t'heart'    => '<path d=\\\"M12 21s-7-4.4-7-10a4 4 0 0 1 7-2.6A4 4 0 0 1 19 11c0 5.6-7 10-7 10z\\\"\\\/>',\\n\\t\\t'upload'   => '<path d=\\\"M12 16V4M7 9l5-5 5 5\\\"\\\/><path d=\\\"M4 16v3a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-3\\\"\\\/>',\\n\\t\\t'lock'     => '<rect x=\\\"4\\\" y=\\\"11\\\" width=\\\"16\\\" height=\\\"10\\\" rx=\\\"2\\\"\\\/><path d=\\\"M8 11V7a4 4 0 0 1 8 0v4\\\"\\\/>',\\n\\t\\t'shield'   => '<path d=\\\"M12 3 4 6v6c0 4.5 3.4 8 8 9 4.6-1 8-4.5 8-9V6z\\\"\\\/><path d=\\\"m8.5 12 2.5 2.5 4.5-5\\\"\\\/>',\\n\\t\\t'wifi'     => '<path d=\\\"M2 8.8a15 15 0 0 1 20 0M5 12.5a10 10 0 0 1 14 0M8.5 16a5 5 0 0 1 7 0\\\"\\\/><circle cx=\\\"12\\\" cy=\\\"19.5\\\" r=\\\"1\\\"\\\/>',\\n\\t\\t'bell'     => '<path d=\\\"M6 8a6 6 0 0 1 12 0c0 7 3 8 3 8H3s3-1 3-8\\\"\\\/><path d=\\\"M10.3 21a1.9 1.9 0 0 0 3.4 0\\\"\\\/>',\\n\\t\\t'gift'     => '<rect x=\\\"3\\\" y=\\\"8\\\" width=\\\"18\\\" height=\\\"13\\\" rx=\\\"2\\\"\\\/><path d=\\\"M3 12h18M12 8v13M12 8C10 5 7 5 7 7s3 1 5 1c2 0 5 1 5-1s-3-2-5 1\\\"\\\/>',\\n\\t\\t'bolt'     => '<path d=\\\"M13 2 4 14h7l-1 8 9-12h-7z\\\"\\\/>',\\n\\t\\t'ask'      => '<circle cx=\\\"12\\\" cy=\\\"12\\\" r=\\\"9\\\"\\\/><path d=\\\"M9.5 9.5a2.5 2.5 0 1 1 3.5 2.3c-.7.3-1 .9-1 1.7M12 17h.01\\\"\\\/>',\\n\\t\\t'check'    => '<path d=\\\"m5 12 5 5 9-10\\\"\\\/>',\\n\\t\\t'pay'      => '<rect x=\\\"3\\\" y=\\\"6\\\" width=\\\"18\\\" height=\\\"13\\\" rx=\\\"2\\\"\\\/><path d=\\\"M3 10h18M7 15h3\\\"\\\/>',\\n\\t];\\n\\treturn '<svg viewBox=\\\"0 0 24 24\\\" width=\\\"' . (int) $size . '\\\" height=\\\"' . (int) $size . '\\\" fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-width=\\\"1.7\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\" aria-hidden=\\\"true\\\">' . ( $paths[ $name ] ?? $paths['store'] ) . '<\\\/svg>';\\n}\\n\\n\\\/** Product photo URL (for hero_img and tile img). *\\\/\\nfunction ddwc_demo_kit_img( $product_id, $size = 'large' ) {\\n\\treturn wp_get_attachment_image_url( get_post_thumbnail_id( $product_id ), $size );\\n}\\n\\n\\\/**\\n * Brand the store: Apple's neutral system (SF Pro via the system font stack, #1d1d1f \\\/ #6e6e73 \\\/ #f5f5f7)\\n * with one accent colour per brand. Global CSS goes in an mu-plugin so the seed may still use Additional CSS.\\n *\\n * @param array $a accent (hex), css (extra CSS appended after the kit CSS).\\n *\\\/\\nfunction ddwc_demo_kit_style( $a ) {\\n\\t$a      = array_merge( [ 'accent' => '#0071e3', 'css' => '' ], $a );\\n\\t$accent = $a['accent'];\\n\\t$rgb    = sscanf( $accent, '#%02x%02x%02x' );\\n\\t$dark   = vsprintf( '#%02x%02x%02x', array_map( function ( $c ) { return (int) round( $c * 0.84 ); }, $rgb ) );\\n\\t$tint   = vsprintf( 'rgba(%d,%d,%d,.1)', $rgb );\\n\\t$stack  = '-apple-system,BlinkMacSystemFont,\\\"SF Pro Text\\\",\\\"SF Pro Display\\\",\\\"Helvetica Neue\\\",Helvetica,Arial,sans-serif';\\n\\n\\t$colors  = [ $accent, $dark, '#1d1d1f', '#1d1d1f', '#6e6e73', '#86868b', '#d2d2d7', '#f5f5f7', '#ffffff', '#ffffff', '#248a3d', '#0071e3', '#d70015', '#bf4800', '#b25e09' ];\\n\\t$palette = [];\\n\\tforeach ( $colors as $i => $color ) {\\n\\t\\t$palette[] = [ 'color' => $color, 'slug' => 'palette' . ( $i + 1 ), 'name' => 'Palette Color ' . ( $i + 1 ) ];\\n\\t}\\n\\tupdate_option( 'kadence_global_palette', wp_json_encode( [ 'palette' => $palette, 'second-palette' => $palette, 'third-palette' => $palette, 'active' => 'palette' ] ) );\\n\\n\\tset_theme_mod( 'base_font', [ 'family' => $stack, 'google' => false, 'weight' => '400', 'variant' => 'regular', 'size' => [ 'desktop' => 17 ], 'lineHeight' => [ 'desktop' => 1.47 ], 'color' => 'palette4' ] );\\n\\tset_theme_mod( 'heading_font', [ 'family' => $stack, 'google' => false, 'weight' => '600', 'variant' => [ '600' ] ] );\\n\\tforeach ( [ 'h1_font' => [ 48, 1.08 ], 'h2_font' => [ 40, 1.1 ], 'h3_font' => [ 28, 1.14 ], 'h4_font' => [ 21, 1.2 ], 'h5_font' => [ 17, 1.3 ], 'h6_font' => [ 14, 1.4 ] ] as $mod => $size ) {\\n\\t\\tset_theme_mod( $mod, [ 'size' => [ 'desktop' => $size[0] ], 'lineHeight' => [ 'desktop' => $size[1] ], 'family' => 'inherit', 'google' => false, 'weight' => '600', 'variant' => '600', 'color' => 'palette3' ] );\\n\\t}\\n\\tset_theme_mod( 'load_fonts_local', false );\\n\\n\\tset_theme_mod( 'site_background', [ 'desktop' => [ 'color' => 'palette9' ] ] );\\n\\tset_theme_mod( 'content_background', [ 'desktop' => [ 'color' => 'palette9' ] ] );\\n\\tset_theme_mod( 'page_content_style', 'unboxed' );\\n\\tset_theme_mod( 'product_archive_content_style', 'unboxed' );\\n\\tset_theme_mod( 'product_content_style', 'unboxed' );\\n\\tset_theme_mod( 'product_archive_columns', '4' );\\n\\tset_theme_mod( 'buttons_border_radius', [ 'size' => [ 980, 980, 980, 980 ], 'unit' => 'px', 'locked' => true ] );\\n\\tset_theme_mod( 'buttons_typography', [ 'size' => [ 'desktop' => 15 ], 'family' => 'inherit', 'weight' => '400', 'variant' => 'regular', 'letterSpacing' => [ 'desktop' => -0.01 ], 'letterType' => 'em' ] );\\n\\tset_theme_mod( 'buttons_padding', [ 'size' => [ 'desktop' => [ 11, 22, 11, 22 ] ], 'unit' => [ 'desktop' => 'px' ], 'locked' => [ 'desktop' => false ] ] );\\n\\n\\t\\\/\\\/ Header: one slim frosted bar, logo left, menu and bag right.\\n\\tset_theme_mod(\\n\\t\\t'header_desktop_items',\\n\\t\\t[\\n\\t\\t\\t'top'    => [ 'top_left' => [], 'top_left_center' => [], 'top_center' => [], 'top_right_center' => [], 'top_right' => [] ],\\n\\t\\t\\t'main'   => [ 'main_left' => [ 'logo' ], 'main_left_center' => [], 'main_center' => [], 'main_right_center' => [], 'main_right' => [ 'navigation', 'cart' ] ],\\n\\t\\t\\t'bottom' => [ 'bottom_left' => [], 'bottom_left_center' => [], 'bottom_center' => [], 'bottom_right_center' => [], 'bottom_right' => [] ],\\n\\t\\t]\\n\\t);\\n\\tset_theme_mod( 'header_sticky', 'main' );\\n\\tset_theme_mod( 'header_main_height', [ 'size' => [ 'mobile' => 52, 'tablet' => 52, 'desktop' => 52 ], 'unit' => [ 'mobile' => 'px', 'tablet' => 'px', 'desktop' => 'px' ] ] );\\n\\tset_theme_mod( 'header_main_background', [ 'desktop' => [ 'color' => 'rgba(255,255,255,0.8)' ] ] );\\n\\tset_theme_mod( 'header_sticky_background', [ 'desktop' => [ 'color' => 'rgba(255,255,255,0.8)' ] ] );\\n\\tset_theme_mod( 'header_main_bottom_border', [ 'desktop' => [ 'width' => 1, 'unit' => 'px', 'style' => 'solid', 'color' => 'rgba(0,0,0,0.08)' ] ] );\\n\\tset_theme_mod( 'brand_typography', [ 'size' => [ 'desktop' => 19 ], 'family' => 'inherit', 'google' => false, 'weight' => '600', 'variant' => '600', 'letterSpacing' => [ 'desktop' => -0.02 ], 'letterType' => 'em', 'color' => 'palette3' ] );\\n\\tset_theme_mod( 'primary_navigation_typography', [ 'size' => [ 'desktop' => 13 ], 'family' => 'inherit', 'weight' => '400', 'variant' => 'regular' ] );\\n\\tset_theme_mod( 'primary_navigation_color', [ 'color' => 'palette4', 'hover' => 'palette1', 'active' => 'palette3' ] );\\n\\n\\tset_theme_mod( 'page_title_layout', 'above' );\\n\\tset_theme_mod( 'page_title_background', [ 'desktop' => [ 'color' => 'palette8' ] ] );\\n\\tset_theme_mod( 'product_archive_title_background', [ 'desktop' => [ 'color' => 'palette8' ] ] );\\n\\tset_theme_mod( 'footer_html_content', '{copyright} {year} {site-title}. A DevDiggers live demo store.' );\\n\\tset_theme_mod( 'footer_bottom_background', [ 'desktop' => [ 'color' => 'palette8' ] ] );\\n\\n\\t$css = <<<'DDWCCSS'\\n:root{--dk-accent:__A__;--dk-accent-dark:__D__;--dk-tint:__T__;--dk-ink:#1d1d1f;--dk-gray:#6e6e73;--dk-line:#d2d2d7;--dk-fog:#f5f5f7;--dk-ease:cubic-bezier(.25,.1,.25,1)}\\nbody{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;letter-spacing:-.022em;color:var(--dk-ink)}\\nh1,h2,h3,h4{letter-spacing:-.015em}\\na{color:var(--dk-accent)}\\n#masthead .site-main-header-wrap,#masthead .item-is-fixed{-webkit-backdrop-filter:saturate(180%) blur(20px);backdrop-filter:saturate(180%) blur(20px)}\\n.main-navigation .primary-menu-container>ul>li.menu-item>a{padding-left:.9em;padding-right:.9em;opacity:.85}\\n.main-navigation .primary-menu-container>ul>li.menu-item>a:hover{opacity:1}\\n.button,button.button,.wp-element-button,input[type=submit],.woocommerce a.button,.woocommerce button.button{background:var(--dk-accent);color:#fff;border:0;box-shadow:none!important;transition:background .2s var(--dk-ease)}\\n.button:hover,.wp-element-button:hover,input[type=submit]:hover,.woocommerce a.button:hover,.woocommerce button.button:hover{background:var(--dk-accent-dark);color:#fff}\\ninput[type=text],input[type=email],input[type=tel],input[type=password],input[type=number],input[type=search],textarea,select{border:1px solid var(--dk-line)!important;border-radius:12px!important;padding:12px 14px!important;background:#fff!important;font-size:17px;transition:border-color .2s,box-shadow .2s}\\ninput:focus,textarea:focus,select:focus{border-color:var(--dk-accent)!important;box-shadow:0 0 0 4px var(--dk-tint)!important;outline:0}\\n.entry-hero .entry-header,.page-title{padding-top:56px;padding-bottom:40px}\\n.entry-hero h1,.page-title h1,.woocommerce-products-header__title{font-size:48px;letter-spacing:-.02em}\\n.woocommerce-store-notice{display:none!important}\\n.woocommerce-message,.woocommerce-info,.woocommerce-error{border:0!important;border-radius:14px;background:var(--dk-fog)!important;color:var(--dk-ink)}\\n\\\/* Product cards, Apple Store style. *\\\/\\nul.products li.product{background:#fff;border-radius:18px;padding:0 0 22px;overflow:hidden;box-shadow:0 2px 12px rgba(0,0,0,.08);transition:box-shadow .3s var(--dk-ease),transform .3s var(--dk-ease)}\\nul.products li.product:hover{box-shadow:0 10px 34px rgba(0,0,0,.14);transform:translateY(-2px)}\\nul.products li.product img{border-radius:0!important;aspect-ratio:1\\\/1;object-fit:cover;margin:0!important}\\nul.products li.product .woocommerce-loop-product__title,ul.products li.product .product-details,ul.products li.product .entry-content-wrap{padding-left:20px;padding-right:20px}\\nul.products li.product .woocommerce-loop-product__title{font-size:17px;font-weight:600;margin-top:16px}\\nul.products li.product .price{color:var(--dk-gray);font-size:15px;font-weight:400}\\nul.products li.product .button{font-size:14px;padding:8px 16px}\\nul.products li.product .onsale{border-radius:980px;background:var(--dk-accent);font-weight:600;font-size:12px}\\n.single-product div.product .product_title{font-size:40px;letter-spacing:-.02em}\\n.single-product div.product .price{font-size:21px;color:var(--dk-ink)}\\n.single-product div.product .woocommerce-product-gallery img{border-radius:24px}\\n\\\/* My account, as a settings sidebar. *\\\/\\n.woocommerce-MyAccount-navigation ul{background:var(--dk-fog);border-radius:18px;padding:8px!important}\\n.woocommerce-MyAccount-navigation ul li{border:0!important}\\n.woocommerce-MyAccount-navigation ul li a{display:block;padding:10px 14px!important;border-radius:10px;color:var(--dk-ink)!important;font-size:15px}\\n.woocommerce-MyAccount-navigation ul li.is-active a{background:#fff;font-weight:600;box-shadow:0 1px 4px rgba(0,0,0,.08)}\\ntable.shop_table{border-radius:18px!important;border-color:var(--dk-line)!important;overflow:hidden}\\n.site-footer{font-size:12px;color:var(--dk-gray)}\\n\\\/* Home page building blocks. *\\\/\\n.dk-wrap{max-width:1180px;margin:0 auto;padding-left:22px;padding-right:22px}\\n.dk-hero{text-align:center;padding-top:72px}\\n.dk-eyebrow{display:inline-block;color:var(--dk-accent);font-size:17px;font-weight:600;letter-spacing:-.01em;margin-bottom:10px}\\n.dk-hero h1{font-size:clamp(44px,7vw,80px);line-height:1.05;letter-spacing:-.025em;margin:0 auto 14px!important;max-width:16ch;text-wrap:balance}\\n.dk-lead{font-size:clamp(19px,2.2vw,24px);line-height:1.38;color:var(--dk-gray);max-width:36em;margin:0 auto 28px!important;text-wrap:pretty;letter-spacing:-.015em}\\n.dk-ctas{display:flex;gap:28px;justify-content:center;align-items:center;flex-wrap:wrap}\\n.dk-btn{display:inline-flex;align-items:center;padding:12px 24px;border-radius:980px;background:var(--dk-accent);color:#fff!important;font-size:17px;text-decoration:none!important;transition:background .2s var(--dk-ease)}\\n.dk-btn:hover{background:var(--dk-accent-dark)}\\n.dk-btn.is-light{background:#fff;color:var(--dk-ink)!important}.dk-btn.is-light:hover{background:#e8e8ed}\\n.dk-more{font-size:17px;color:var(--dk-accent)!important;text-decoration:none!important}\\n.dk-more:after{content:\\\"\\\\203A\\\";display:inline-block;margin-left:6px;font-size:1.2em;line-height:0;transition:transform .2s var(--dk-ease)}\\n.dk-more:hover{text-decoration:underline!important}.dk-more:hover:after{transform:translateX(3px)}\\n.dk-stage{position:relative;margin:56px auto 0;max-width:1180px;border-radius:32px;overflow:hidden;background:var(--dk-fog);aspect-ratio:16\\\/9}\\n.dk-stage>img{width:100%;height:100%;object-fit:cover;display:block}\\n.dk-stage.is-trio{display:grid;grid-template-columns:repeat(3,1fr);gap:4px;background:#fff}\\n.dk-stage.is-trio>img{min-width:0}\\n.dk-stage .dk-widget{position:absolute;right:32px;bottom:32px}\\n.dk-widget{width:330px;max-width:calc(100% - 32px);text-align:left;padding:18px;border-radius:22px;background:rgba(255,255,255,.78);-webkit-backdrop-filter:saturate(180%) blur(24px);backdrop-filter:saturate(180%) blur(24px);box-shadow:0 22px 60px rgba(0,0,0,.18),inset 0 0 0 .5px rgba(255,255,255,.6);color:var(--dk-ink);font-size:15px;line-height:1.35}\\n.dk-w-head{display:flex;align-items:center;gap:10px;margin-bottom:12px}\\n.dk-w-app{flex:none;display:grid;place-items:center;width:34px;height:34px;border-radius:9px;background:var(--dk-accent);color:#fff}\\n.dk-w-app svg{width:19px;height:19px}\\n.dk-w-label{display:block;font-size:12px;font-weight:600;color:var(--dk-gray);letter-spacing:0;text-transform:uppercase}\\n.dk-w-title{display:block;font-size:16px;font-weight:600;letter-spacing:-.02em}\\n.dk-w-big{display:block;font-size:40px;font-weight:600;letter-spacing:-.03em;line-height:1.1;margin:4px 0}\\n.dk-w-row{display:flex;justify-content:space-between;gap:12px;padding:9px 0;border-top:.5px solid rgba(0,0,0,.12);font-size:14px}\\n.dk-w-row span:last-child{color:var(--dk-gray)}\\n.dk-w-row.is-total{font-weight:600}.dk-w-row.is-total span:last-child{color:var(--dk-ink)}\\n.dk-w-pill{display:inline-flex;align-items:center;gap:6px;padding:4px 10px;border-radius:980px;background:var(--dk-tint);color:var(--dk-accent);font-size:13px;font-weight:600}\\n.dk-w-avatar{flex:none;width:34px;height:34px;border-radius:50%;object-fit:cover}\\n.dk-w-bubble{padding:10px 14px;border-radius:18px 18px 18px 6px;background:#e9e9eb;font-size:15px;margin-top:6px}\\n.dk-w-bubble.is-me{border-radius:18px 18px 6px 18px;background:var(--dk-accent);color:#fff;margin-left:30px}\\n.dk-w-note{display:block;font-size:12px;color:var(--dk-gray);margin-top:10px}\\n.dk-cd{display:flex;gap:8px;margin:10px 0 4px}.dk-cd span{flex:1;text-align:center;padding:10px 0 8px;border-radius:12px;background:var(--dk-ink);color:#fff;font-size:22px;font-weight:600;font-variant-numeric:tabular-nums;letter-spacing:-.02em}\\n.dk-cd small{display:block;font-size:10px;font-weight:500;color:#a1a1a6;letter-spacing:.04em;text-transform:uppercase;margin-top:3px}\\n.dk-band{background:var(--dk-fog);margin-top:96px;padding:88px 0}\\n.dk-title{text-align:center!important;font-size:clamp(34px,4.6vw,56px);letter-spacing:-.025em;line-height:1.08;margin:0 auto 48px!important;max-width:20ch;text-wrap:balance}\\n.dk-shelf-title{font-size:clamp(28px,3.4vw,40px);letter-spacing:-.02em;line-height:1.1;margin:0 0 28px}\\n.dk-shelf-title span{color:var(--dk-gray)}\\n.dk-features{display:grid;grid-template-columns:repeat(3,1fr);gap:20px}\\n.dk-feature{background:#fff;border-radius:28px;padding:36px 32px 40px}\\n.dk-feature svg{color:var(--dk-accent);width:36px;height:36px}\\n.dk-feature h3{font-size:24px;letter-spacing:-.02em;margin:18px 0 8px}\\n.dk-feature p{margin:0;font-size:17px;color:var(--dk-gray)}\\n.dk-shelf{padding-top:96px}\\n.dk-shelf-head{display:flex;justify-content:space-between;align-items:baseline;gap:16px;flex-wrap:wrap}\\n.dk-tile{margin:96px auto 104px;display:grid;grid-template-columns:1fr 1fr;align-items:center;border-radius:32px;overflow:hidden;background:#000;color:#f5f5f7}\\n.dk-tile-copy{padding:64px}\\n.dk-tile h2{color:#f5f5f7;font-size:clamp(32px,3.8vw,48px);letter-spacing:-.025em;line-height:1.08;margin:0 0 16px}\\n.dk-tile p{color:#a1a1a6;font-size:19px;line-height:1.42}\\n.dk-tile .dk-more{color:#2997ff!important}\\n.dk-tile img{width:100%;height:100%;min-height:440px;max-height:600px;object-fit:cover;display:block}\\n.dk-stats{display:flex;gap:36px;margin:28px 0}.dk-stats b{display:block;font-size:32px;font-weight:600;letter-spacing:-.02em;color:#f5f5f7}.dk-stats span{font-size:14px;color:#a1a1a6}\\n.dk-list{list-style:none;padding:0;margin:24px 0}.dk-list li{display:flex;gap:12px;align-items:center;padding:10px 0;border-top:.5px solid rgba(255,255,255,.16);color:#f5f5f7;font-size:17px}\\n.dk-list li b{display:grid;place-items:center;flex:none;width:26px;height:26px;border-radius:50%;background:#f5f5f7;color:#000;font-size:13px}\\n@media(max-width:900px){.dk-features,.dk-tile{grid-template-columns:1fr}.dk-stage{aspect-ratio:4\\\/5;border-radius:24px}.dk-stage .dk-widget{left:16px;right:16px;bottom:16px;width:auto}.dk-stage.is-trio{grid-template-columns:1fr 1fr}.dk-stage.is-trio>img:nth-child(3){display:none}.dk-tile-copy{padding:40px 28px}.dk-tile img{min-height:300px}.dk-band{padding:64px 0}}\\n@media(prefers-reduced-motion:reduce){*{transition:none!important}}\\nDDWCCSS;\\n\\t$css  = str_replace( [ '__A__', '__D__', '__T__' ], [ $accent, $dark, $tint ], $css ) . $a['css'];\\n\\t$code = \\\"<?php\\\\n\\\/\\\/ Live Preview only: the demo kit's global stylesheet.\\\\nadd_action( 'wp_head', function () { echo '<style id=\\\\\\\"ddwc-demo-kit\\\\\\\">' . \\\" . var_export( $css, true ) . \\\" . '<\\\/style>'; }, 99 );\\\\n\\\";\\n\\twp_mkdir_p( WPMU_PLUGIN_DIR );\\n\\tfile_put_contents( WPMU_PLUGIN_DIR . '\\\/demo-style.php', $code );\\n}\\n\\n\\\/**\\n * Build the home page and make it the front page.\\n *\\n * @param array $h eyebrow, title, lead, ctas [ [label, url], [label, url] ] (first is the filled button),\\n *                 hero_img (url, or three urls for a photo trio), hero_alt, widget (HTML on the hero stage), after_ctas (HTML, e.g. a shortcode),\\n *                 features_title, features [ [title, text, icon] ], shelf_title, shelf_sub, shelf (shortcode),\\n *                 shelf_more [label, url], tile [title, text, cta [label, url], img, alt, extra HTML], script.\\n * @return int Page ID.\\n *\\\/\\nfunction ddwc_demo_kit_home( $h ) {\\n\\t$ctas = '';\\n\\tforeach ( $h['ctas'] as $i => $cta ) {\\n\\t\\t$ctas .= '<a class=\\\"' . ( $i ? 'dk-more' : 'dk-btn' ) . '\\\" href=\\\"' . esc_url( $cta[1] ) . '\\\">' . esc_html( $cta[0] ) . '<\\\/a>';\\n\\t}\\n\\t$features = '';\\n\\tforeach ( $h['features'] as $f ) {\\n\\t\\t$features .= '<div class=\\\"dk-feature\\\">' . ddwc_demo_kit_icon( $f[2], 36 ) . '<h3>' . $f[0] . '<\\\/h3><p>' . $f[1] . '<\\\/p><\\\/div>';\\n\\t}\\n\\t\\\/\\\/ One wide photo, or three product photos side by side (works with portrait shots).\\n\\t$trio = is_array( $h['hero_img'] );\\n\\t$imgs = '';\\n\\tforeach ( (array) $h['hero_img'] as $src ) {\\n\\t\\t$imgs .= '<img src=\\\"' . esc_url( $src ) . '\\\" alt=\\\"' . esc_attr( $h['hero_alt'] ) . '\\\"\\\/>';\\n\\t}\\n\\t$t    = $h['tile'];\\n\\t$more = $h['shelf_more'] ?? [ 'View all products', wc_get_page_permalink( 'shop' ) ];\\n\\t$html = '<!-- wp:html --><section class=\\\"dk-hero dk-wrap\\\"><span class=\\\"dk-eyebrow\\\">' . $h['eyebrow'] . '<\\\/span><h1>' . $h['title'] . '<\\\/h1><p class=\\\"dk-lead\\\">' . $h['lead'] . '<\\\/p><div class=\\\"dk-ctas\\\">' . $ctas . ( $h['after_ctas'] ?? '' ) . '<\\\/div>'\\n\\t\\t. '<div class=\\\"dk-stage' . ( $trio ? ' is-trio' : '' ) . '\\\">' . $imgs . $h['widget'] . '<\\\/div><\\\/section><!-- \\\/wp:html -->'\\n\\t\\t. '<!-- wp:html --><section class=\\\"dk-band\\\"><div class=\\\"dk-wrap\\\"><h2 class=\\\"dk-title\\\">' . $h['features_title'] . '<\\\/h2><div class=\\\"dk-features\\\">' . $features . '<\\\/div><\\\/div><\\\/section><!-- \\\/wp:html -->'\\n\\t\\t. '<!-- wp:html --><section class=\\\"dk-shelf dk-wrap\\\"><div class=\\\"dk-shelf-head\\\"><h2 class=\\\"dk-shelf-title\\\">' . $h['shelf_title'] . ' <span>' . $h['shelf_sub'] . '<\\\/span><\\\/h2><a class=\\\"dk-more\\\" href=\\\"' . esc_url( $more[1] ) . '\\\">' . esc_html( $more[0] ) . '<\\\/a><\\\/div><!-- \\\/wp:html -->'\\n\\t\\t. '<!-- wp:shortcode -->' . $h['shelf'] . '<!-- \\\/wp:shortcode -->'\\n\\t\\t. '<!-- wp:html --><\\\/section><!-- \\\/wp:html -->'\\n\\t\\t. '<!-- wp:html --><section class=\\\"dk-wrap\\\"><div class=\\\"dk-tile\\\"><div class=\\\"dk-tile-copy\\\"><h2>' . $t['title'] . '<\\\/h2><p>' . $t['text'] . '<\\\/p>' . ( $t['extra'] ?? '' ) . '<p><a class=\\\"dk-btn is-light\\\" href=\\\"' . esc_url( $t['cta'][1] ) . '\\\">' . esc_html( $t['cta'][0] ) . '<\\\/a><\\\/p><\\\/div><img src=\\\"' . esc_url( $t['img'] ) . '\\\" alt=\\\"' . esc_attr( $t['alt'] ) . '\\\"\\\/><\\\/div><\\\/section>' . ( $h['script'] ?? '' ) . '<!-- \\\/wp:html -->';\\n\\n\\t$id = wp_insert_post( [ 'post_type' => 'page', 'post_status' => 'publish', 'post_title' => 'Home', 'post_content' => $html ] );\\n\\tforeach ( [ '_kad_post_title' => 'hide', '_kad_post_layout' => 'fullwidth', '_kad_post_content_style' => 'unboxed', '_kad_post_vertical_padding' => 'hide', '_kad_post_feature' => 'hide' ] as $k => $v ) {\\n\\t\\tupdate_post_meta( $id, $k, $v );\\n\\t}\\n\\tupdate_option( 'show_on_front', 'page' );\\n\\tupdate_option( 'page_on_front', $id );\\n\\twp_delete_post( 2, true ); \\\/\\\/ Sample Page.\\n\\twp_delete_post( 1, true ); \\\/\\\/ Hello world.\\n\\tupdate_option( 'permalink_structure', '\\\/%postname%\\\/' );\\n\\tflush_rewrite_rules();\\n\\treturn $id;\\n}\\n\\n\\\/**\\n * Primary menu.\\n *\\n * @param array $items label => url.\\n *\\\/\\nfunction ddwc_demo_kit_menu( $items ) {\\n\\t$menu_id = wp_create_nav_menu( 'Main menu' );\\n\\tif ( is_wp_error( $menu_id ) ) {\\n\\t\\t$menu_id = wp_get_nav_menu_object( 'Main menu' )->term_id;\\n\\t}\\n\\tforeach ( $items as $label => $url ) {\\n\\t\\twp_update_nav_menu_item( $menu_id, 0, [ 'menu-item-title' => $label, 'menu-item-url' => $url, 'menu-item-status' => 'publish', 'menu-item-type' => 'custom' ] );\\n\\t}\\n\\t$locations            = get_theme_mod( 'nav_menu_locations', [] );\\n\\t$locations['primary'] = $menu_id;\\n\\t$locations['mobile']  = $menu_id;\\n\\tset_theme_mod( 'nav_menu_locations', $locations );\\n}\\n\\n\\\/**\\n * Write the \\\"View as\\\" switcher mu-plugin.\\n *\\n * Pass a PHP expression string that returns the views array; each view:\\n *   [ 'label' => 'Store admin', 'hint' => 'Dashboard & settings', 'icon' => 'admin', 'user' => 'admin', 'url' => admin_url(...) ]\\n * user '' logs the visitor out. icon: any ddwc_demo_kit_icon() name.\\n *\\n * @param string $views_php PHP code (no <?php) that returns the array.\\n *\\\/\\nfunction ddwc_demo_kit_switcher( $views_php ) {\\n\\t$icons = [];\\n\\tforeach ( [ 'admin', 'customer', 'store', 'product', 'cart', 'chat', 'wallet', 'star', 'register', 'link', 'cube', 'rx', 'phone', 'orders', 'calendar' ] as $n ) {\\n\\t\\t$icons[ $n ] = ddwc_demo_kit_icon( $n, 18 );\\n\\t}\\n\\t$code = <<<'DDWCKIT'\\n<?php\\n\\\/**\\n * Live Preview only: \\\"View as\\\" switcher so visitors can jump between the store owner's\\n * admin and the shopper's side without any password.\\n *\\\/\\nfunction ddwc_demo_switcher_views() {\\n\\t__VIEWS__\\n}\\n\\nadd_action(\\n\\t'init',\\n\\tfunction () {\\n\\t\\tif ( ! isset( $_GET['ddwc_demo_view'] ) ) { \\\/\\\/ phpcs:ignore WordPress.Security.NonceVerification -- demo site only.\\n\\t\\t\\treturn;\\n\\t\\t}\\n\\t\\t$views = ddwc_demo_switcher_views();\\n\\t\\t$view  = $views[ absint( $_GET['ddwc_demo_view'] ) ] ?? null; \\\/\\\/ phpcs:ignore WordPress.Security.NonceVerification\\n\\t\\tif ( ! $view ) {\\n\\t\\t\\treturn;\\n\\t\\t}\\n\\t\\twp_clear_auth_cookie();\\n\\t\\twp_set_current_user( 0 );\\n\\t\\t$user = $view['user'] ? get_user_by( 'login', $view['user'] ) : null;\\n\\t\\tif ( $user ) {\\n\\t\\t\\twp_set_current_user( $user->ID );\\n\\t\\t\\twp_set_auth_cookie( $user->ID, true );\\n\\t\\t}\\n\\t\\twp_safe_redirect( $view['url'] );\\n\\t\\texit;\\n\\t},\\n\\t1\\n);\\n\\n\\\/\\\/ Live Preview: keep the storefront clean, the admin bar only appears in wp-admin.\\nadd_filter( 'show_admin_bar', '__return_false' );\\n\\nfunction ddwc_demo_switcher_render() {\\n\\t$icons   = __ICONS__;\\n\\t$views   = ddwc_demo_switcher_views();\\n\\t$current = wp_get_current_user();\\n\\t$here    = untrailingslashit( set_url_scheme( 'http:\\\/\\\/' . ( $_SERVER['HTTP_HOST'] ?? '' ) . ( $_SERVER['REQUEST_URI'] ?? '' ) ) ); \\\/\\\/ phpcs:ignore\\n\\t$items   = '';\\n\\t$label   = '';\\n\\tforeach ( $views as $i => $view ) {\\n\\t\\t$active = untrailingslashit( set_url_scheme( $view['url'] ) ) === $here && ( ! $view['user'] || ( $current->exists() && $current->user_login === $view['user'] ) );\\n\\t\\tif ( $active ) {\\n\\t\\t\\t$label = $view['label'];\\n\\t\\t}\\n\\t\\t$items .= sprintf(\\n\\t\\t\\t'<a href=\\\"%s\\\" class=\\\"ddwc-ds-item%s\\\"><span class=\\\"ddwc-ds-ico\\\">%s<\\\/span><span class=\\\"ddwc-ds-text\\\"><strong>%s<\\\/strong><small>%s<\\\/small><\\\/span><\\\/a>',\\n\\t\\t\\tesc_url( add_query_arg( 'ddwc_demo_view', $i, home_url( '\\\/' ) ) ),\\n\\t\\t\\t$active ? ' is-active' : '',\\n\\t\\t\\t$icons[ $view['icon'] ?? 'store' ] ?? $icons['store'],\\n\\t\\t\\tesc_html( $view['label'] ),\\n\\t\\t\\tesc_html( $view['hint'] ?? '' )\\n\\t\\t);\\n\\t}\\n\\t?>\\n\\t<div id=\\\"ddwc-demo-switcher\\\">\\n\\t\\t<div class=\\\"ddwc-ds-panel\\\" role=\\\"menu\\\">\\n\\t\\t\\t<div class=\\\"ddwc-ds-head\\\">Explore this live demo<\\\/div>\\n\\t\\t\\t<?php echo $items; \\\/\\\/ phpcs:ignore WordPress.Security.EscapeOutput -- escaped above. ?>\\n\\t\\t<\\\/div>\\n\\t\\t<button type=\\\"button\\\" class=\\\"ddwc-ds-toggle\\\" aria-expanded=\\\"false\\\">\\n\\t\\t\\t<span class=\\\"ddwc-ds-dot\\\"><\\\/span>\\n\\t\\t\\t<span>View as<?php echo $label ? ' <b>' . esc_html( $label ) . '<\\\/b>' : ''; ?><\\\/span>\\n\\t\\t\\t<svg viewBox=\\\"0 0 24 24\\\" width=\\\"14\\\" height=\\\"14\\\" fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-width=\\\"2.2\\\" stroke-linecap=\\\"round\\\" aria-hidden=\\\"true\\\"><path d=\\\"m6 15 6-6 6 6\\\"\\\/><\\\/svg>\\n\\t\\t<\\\/button>\\n\\t<\\\/div>\\n\\t<style>\\n\\t\\t#ddwc-demo-switcher{position:fixed;left:20px;bottom:20px;z-index:999999;font:400 14px\\\/1.3 -apple-system,BlinkMacSystemFont,\\\"SF Pro Text\\\",\\\"Helvetica Neue\\\",Arial,sans-serif;letter-spacing:-.01em;color:#1d1d1f;-webkit-font-smoothing:antialiased}\\n\\t\\t#ddwc-demo-switcher .ddwc-ds-toggle{display:flex;align-items:center;gap:9px;padding:10px 16px;border:0;border-radius:980px;background:rgba(29,29,31,.82);-webkit-backdrop-filter:saturate(180%) blur(20px);backdrop-filter:saturate(180%) blur(20px);color:#f5f5f7;font:inherit;cursor:pointer;box-shadow:0 10px 30px rgba(0,0,0,.24)}\\n\\t\\t#ddwc-demo-switcher .ddwc-ds-toggle b{font-weight:600;color:#fff}\\n\\t\\t#ddwc-demo-switcher .ddwc-ds-toggle svg{transition:transform .25s cubic-bezier(.25,.1,.25,1);opacity:.7}\\n\\t\\t#ddwc-demo-switcher.is-open .ddwc-ds-toggle svg{transform:rotate(180deg)}\\n\\t\\t#ddwc-demo-switcher .ddwc-ds-dot{width:7px;height:7px;border-radius:50%;background:#30d158;box-shadow:0 0 0 3px rgba(48,209,88,.28)}\\n\\t\\t#ddwc-demo-switcher .ddwc-ds-panel{position:absolute;left:0;bottom:calc(100% + 10px);width:300px;max-width:calc(100vw - 40px);padding:6px;border-radius:18px;background:rgba(255,255,255,.82);-webkit-backdrop-filter:saturate(180%) blur(24px);backdrop-filter:saturate(180%) blur(24px);box-shadow:0 24px 70px rgba(0,0,0,.22),inset 0 0 0 .5px rgba(0,0,0,.08);opacity:0;visibility:hidden;transform:translateY(6px) scale(.98);transform-origin:bottom left;transition:opacity .2s,transform .25s cubic-bezier(.25,.1,.25,1),visibility .2s}\\n\\t\\t#ddwc-demo-switcher.is-open .ddwc-ds-panel{opacity:1;visibility:visible;transform:none}\\n\\t\\t#ddwc-demo-switcher .ddwc-ds-head{padding:10px 12px 8px;font-size:12px;font-weight:600;color:#6e6e73}\\n\\t\\t#ddwc-demo-switcher .ddwc-ds-item{display:flex;align-items:center;gap:12px;padding:9px 10px;border-radius:12px;color:#1d1d1f;text-decoration:none}\\n\\t\\t#ddwc-demo-switcher .ddwc-ds-item:hover{background:rgba(0,0,0,.05)}\\n\\t\\t#ddwc-demo-switcher .ddwc-ds-item.is-active{background:rgba(0,113,227,.1)}\\n\\t\\t#ddwc-demo-switcher .ddwc-ds-ico{flex:none;display:grid;place-items:center;width:32px;height:32px;border-radius:9px;background:#e8e8ed;color:#1d1d1f}\\n\\t\\t#ddwc-demo-switcher .ddwc-ds-item.is-active .ddwc-ds-ico{background:#0071e3;color:#fff}\\n\\t\\t#ddwc-demo-switcher .ddwc-ds-text{display:flex;flex-direction:column;min-width:0}\\n\\t\\t#ddwc-demo-switcher .ddwc-ds-text strong{font-weight:600;font-size:14px}\\n\\t\\t#ddwc-demo-switcher .ddwc-ds-text small{font-size:12px;color:#6e6e73}\\n\\t\\t@media (max-width:782px){#ddwc-demo-switcher{left:12px;bottom:auto;top:58px}#ddwc-demo-switcher .ddwc-ds-panel{bottom:auto;top:calc(100% + 10px);transform-origin:top left}}\\n\\t<\\\/style>\\n\\t<script>\\n\\t\\t(function(){var w=document.getElementById('ddwc-demo-switcher');if(!w)return;var b=w.querySelector('.ddwc-ds-toggle');b.addEventListener('click',function(e){e.stopPropagation();var o=w.classList.toggle('is-open');b.setAttribute('aria-expanded',o)});document.addEventListener('click',function(e){if(!w.contains(e.target)){w.classList.remove('is-open');b.setAttribute('aria-expanded','false')}});try{if(!sessionStorage.getItem('ddwcDemoSeen')){w.classList.add('is-open');sessionStorage.setItem('ddwcDemoSeen','1');setTimeout(function(){w.classList.remove('is-open');b.setAttribute('aria-expanded','false')},5000)}}catch(e){}})();\\n\\t<\\\/script>\\n\\t<?php\\n}\\nadd_action( 'wp_footer', 'ddwc_demo_switcher_render' );\\nadd_action( 'admin_footer', 'ddwc_demo_switcher_render' );\\nDDWCKIT;\\n\\twp_mkdir_p( WPMU_PLUGIN_DIR );\\n\\tfile_put_contents( WPMU_PLUGIN_DIR . '\\\/demo-switcher.php', str_replace( [ '__VIEWS__', '__ICONS__' ], [ $views_php, var_export( $icons, true ) ], $code ) );\\n}\\n\\n\\n\\\/\\\/ ---- Storefront (Kadence + DevDiggers demo kit) ----\\nwp_set_current_user( 1 );\\nddwc_demo_kit_style( [\\n\\t'accent' => '#248a3d',\\n\\t'css'    => '.dk-ctas .ddwppwa-install-button,.dk-ctas [class*=\\\"ddwppwa\\\"][class*=\\\"button\\\"]{border-radius:980px!important;font-size:17px!important}',\\n] );\\n\\n$shop    = wc_get_page_permalink( 'shop' );\\n$account = wc_get_page_permalink( 'myaccount' );\\n$icon    = wp_get_attachment_url( (int) get_option( '_ddwppwa_app_icon196' ) );\\n$home_id = ddwc_demo_kit_home( [\\n\\t'eyebrow'        => 'Now on your home screen',\\n\\t'title'          => 'Your favourite store. One tap away.',\\n\\t'lead'           => 'Hand-thrown ceramics, glass and oak from small studios. Install the Lumen &amp; Leaf app for a faster shop that works offline and pings you when a sold-out piece is back.',\\n\\t'ctas'           => [ [ 'Shop the collection', $shop ], [ 'My account', $account ] ],\\n\\t'after_ctas'     => '[ddwppwa_install_button label=\\\"Install the app\\\"]',\\n\\t'hero_img'       => [ $photos['Sage Ceramic Vase'], $photos['Ivory Pattern Table Lamp'], $photos['Eucalyptus Bud Vase'] ],\\n\\t'hero_alt'       => 'Lumen & Leaf ceramics',\\n\\t'widget'         => '<div class=\\\"dk-widget\\\"><div class=\\\"dk-w-head\\\"><img class=\\\"dk-w-avatar\\\" style=\\\"border-radius:9px\\\" src=\\\"' . esc_url( $icon ) . '\\\" alt=\\\"\\\"\\\/><span><span class=\\\"dk-w-label\\\">Lumen &amp; Leaf &middot; now<\\\/span><span class=\\\"dk-w-title\\\">Back in stock: Sage Ceramic Vase<\\\/span><\\\/span><\\\/div>'\\n\\t\\t. 'Only 12 left from this firing. Tap to grab yours.'\\n\\t\\t. '<div class=\\\"dk-w-row\\\" style=\\\"margin-top:12px\\\"><span>Installed on home screen<\\\/span><span>Works offline<\\\/span><\\\/div><\\\/div>',\\n\\t'features_title' => 'All the store. None of the app store.',\\n\\t'features'       => [\\n\\t\\t[ 'Install it like an app', 'Add the store to your home screen in one tap. It opens full screen, with no download.', 'phone' ],\\n\\t\\t[ 'Keeps working offline', 'Lost signal on the train? Pages you opened stay readable, and we pick up again when you are back online.', 'wifi' ],\\n\\t\\t[ 'Never miss a restock', 'Allow notifications and we tell you when a sold-out piece returns or a new collection drops.', 'bell' ],\\n\\t],\\n\\t'shelf_title'    => 'New this season.',\\n\\t'shelf_sub'      => 'Faster in the app.',\\n\\t'shelf'          => '[products limit=\\\"8\\\" columns=\\\"4\\\" orderby=\\\"date\\\"]',\\n\\t'tile'           => [\\n\\t\\t'title' => 'The whole store, in your pocket.',\\n\\t\\t'text'  => 'Sign in once and your orders, wishlist and cart follow you into the app.',\\n\\t\\t'extra' => '<ul class=\\\"dk-list\\\"><li><b>1<\\\/b>Track every order from your home screen<\\\/li><li><b>2<\\\/b>Early access to new drops<\\\/li><li><b>3<\\\/b>10% off your first app order with HELLOAPP<\\\/li><\\\/ul>',\\n\\t\\t'cta'   => [ 'See my orders', wc_get_endpoint_url( 'orders', '', $account ) ],\\n\\t\\t'img'   => $photos['Window Reading Chair'],\\n\\t\\t'alt'   => 'Reading chair by a window',\\n\\t],\\n] );\\nddwc_demo_kit_menu( [ 'Shop' => $shop, 'My account' => $account ] );\\n\\nddwc_demo_kit_switcher( <<<'DDWCVIEWS'\\n$account = wc_get_page_permalink( 'myaccount' );\\n\\t$product = get_page_by_path( 'sage-ceramic-vase', OBJECT, 'product' );\\n\\treturn [\\n\\t\\t[ 'label' => 'App dashboard', 'hint' => 'Installs, subscribers and pushes', 'icon' => 'admin', 'user' => 'admin', 'url' => admin_url( 'admin.php?page=ddwppwa-dashboard' ) ],\\n\\t\\t[ 'label' => 'Shopper app home', 'hint' => 'The installable store, signed in', 'icon' => 'phone', 'user' => 'amara', 'url' => home_url( '\\\/' ) ],\\n\\t\\t[ 'label' => 'Customer orders', 'hint' => 'Amara tracks her orders', 'icon' => 'orders', 'user' => 'amara', 'url' => wc_get_endpoint_url( 'orders', '', $account ) ],\\n\\t\\t[ 'label' => 'Product page', 'hint' => 'A product as shoppers see it', 'icon' => 'product', 'user' => '', 'url' => $product ? get_permalink( $product ) : wc_get_page_permalink( 'shop' ) ],\\n\\t];\\nDDWCVIEWS\\n);\\n\\n\\\/\\\/ Write the manifest and service worker with the new settings, then drop cached stats.\\nif ( class_exists( 'DDWPPWA_Service_Worker_Activate' ) || ( defined( 'DDWPPWA_PLUGIN_FILE' ) && require_once DDWPPWA_PLUGIN_FILE . 'includes\\\/service-worker-activate.php' ) ) {\\n\\tDDWPPWA_Service_Worker_Activate::ddwppwa_generate_new_manifest_file( '' );\\n\\tDDWPPWA_Service_Worker_Activate::ddwppwa_write_service_worker();\\n}\\n$wpdb->query( \\\"DELETE FROM {$wpdb->options} WHERE option_name LIKE '\\\\_transient%ddwppwa\\\\_dashboard\\\\_stats%'\\\" );\\n\"},{\"step\":\"login\",\"username\":\"admin\",\"password\":\"password\"}]}"}},"all_blocks":[],"tagged_versions":["1.0.0","1.1.0","1.1.1"],"block_files":[],"assets_screenshots":{"screenshot-1.png":{"filename":"screenshot-1.png","revision":3682482,"resolution":"1","location":"assets","locale":"","width":600,"height":1300},"screenshot-10.png":{"filename":"screenshot-10.png","revision":3682482,"resolution":"10","location":"assets","locale":"","width":1600,"height":1000},"screenshot-11.png":{"filename":"screenshot-11.png","revision":3682482,"resolution":"11","location":"assets","locale":"","width":1600,"height":1000},"screenshot-2.png":{"filename":"screenshot-2.png","revision":3682482,"resolution":"2","location":"assets","locale":"","width":600,"height":1300},"screenshot-3.png":{"filename":"screenshot-3.png","revision":3682482,"resolution":"3","location":"assets","locale":"","width":1600,"height":1000},"screenshot-4.png":{"filename":"screenshot-4.png","revision":3682482,"resolution":"4","location":"assets","locale":"","width":1600,"height":1000},"screenshot-5.png":{"filename":"screenshot-5.png","revision":3682482,"resolution":"5","location":"assets","locale":"","width":1600,"height":1000},"screenshot-6.png":{"filename":"screenshot-6.png","revision":3682482,"resolution":"6","location":"assets","locale":"","width":1600,"height":1000},"screenshot-7.png":{"filename":"screenshot-7.png","revision":3682482,"resolution":"7","location":"assets","locale":"","width":1600,"height":1000},"screenshot-8.png":{"filename":"screenshot-8.png","revision":3682482,"resolution":"8","location":"assets","locale":"","width":1600,"height":1000},"screenshot-9.png":{"filename":"screenshot-9.png","revision":3682482,"resolution":"9","location":"assets","locale":"","width":1600,"height":1000}},"screenshots":{"1":"Dashboard with installs, subscribers, notifications sent, charts and the setup checklist.","2":"Delivery Log with the type, status and audience of every push.","3":"The notification list and the add notification screen.","4":"Automations: auto push on publish and the welcome notification.","5":"Configuration tabs for the manifest, app icons and offline behaviour.","6":"Status tab with the setup checks and the browser support list.","7":"The install prompt and a push notification on the storefront."}},"plugin_section":[],"plugin_tags":[227279,77056,1155,77057,37677],"plugin_category":[],"plugin_contributors":[220149],"plugin_business_model":[],"class_list":["post-361892","plugin","type-plugin","status-publish","hentry","plugin_tags-add-to-home-screen","plugin_tags-progressive-web-apps","plugin_tags-push-notifications","plugin_tags-pwa","plugin_tags-web-push","plugin_contributors-devdiggers","plugin_committers-devdiggers"],"banners":{"banner":"https:\/\/ps.w.org\/devdiggers-pwa\/assets\/banner-772x250.png?rev=3682482","banner_2x":"https:\/\/ps.w.org\/devdiggers-pwa\/assets\/banner-1544x500.png?rev=3682482","banner_rtl":false,"banner_2x_rtl":false},"icons":{"svg":false,"icon":"https:\/\/ps.w.org\/devdiggers-pwa\/assets\/icon-128x128.gif?rev=3682482","icon_2x":"https:\/\/ps.w.org\/devdiggers-pwa\/assets\/icon-256x256.gif?rev=3682482","generated":false},"screenshots":[{"src":"https:\/\/ps.w.org\/devdiggers-pwa\/assets\/screenshot-1.png?rev=3682482","caption":"Dashboard with installs, subscribers, notifications sent, charts and the setup checklist."},{"src":"https:\/\/ps.w.org\/devdiggers-pwa\/assets\/screenshot-2.png?rev=3682482","caption":"Delivery Log with the type, status and audience of every push."},{"src":"https:\/\/ps.w.org\/devdiggers-pwa\/assets\/screenshot-3.png?rev=3682482","caption":"The notification list and the add notification screen."},{"src":"https:\/\/ps.w.org\/devdiggers-pwa\/assets\/screenshot-4.png?rev=3682482","caption":"Automations: auto push on publish and the welcome notification."},{"src":"https:\/\/ps.w.org\/devdiggers-pwa\/assets\/screenshot-5.png?rev=3682482","caption":"Configuration tabs for the manifest, app icons and offline behaviour."},{"src":"https:\/\/ps.w.org\/devdiggers-pwa\/assets\/screenshot-6.png?rev=3682482","caption":"Status tab with the setup checks and the browser support list."},{"src":"https:\/\/ps.w.org\/devdiggers-pwa\/assets\/screenshot-7.png?rev=3682482","caption":"The install prompt and a push notification on the storefront."},{"src":"https:\/\/ps.w.org\/devdiggers-pwa\/assets\/screenshot-8.png?rev=3682482","caption":""},{"src":"https:\/\/ps.w.org\/devdiggers-pwa\/assets\/screenshot-9.png?rev=3682482","caption":""},{"src":"https:\/\/ps.w.org\/devdiggers-pwa\/assets\/screenshot-10.png?rev=3682482","caption":""},{"src":"https:\/\/ps.w.org\/devdiggers-pwa\/assets\/screenshot-11.png?rev=3682482","caption":""}],"raw_content":"<!--section=description-->\n<p>This plugin turns your WordPress or WooCommerce site into a progressive web app: an app people can install on their phone from the browser, with your icon on their home screen, your splash screen when it opens, pages that still load when the signal drops, and web push notifications that bring them back.<\/p>\n\n<p>You get the three things a <a href=\"https:\/\/devdiggers.com\/product\/progressive-web-app-for-wordpress\/\">WordPress PWA Plugin<\/a> is made of, all configurable from one menu. A web app manifest, so browsers offer to install your site. A service worker, so visited pages stay readable offline. And web push notifications through Firebase Cloud Messaging, so you can reach people on Android, Windows, macOS and iOS after they leave.<\/p>\n\n<h4>Push notifications without a monthly bill<\/h4>\n\n<p>Most push notification plugins are the front end of a paid service. You connect an account, and once you pass a few thousand subscribers you start paying every month to message people who already opted in on your site. Your list lives on their servers.<\/p>\n\n<p>This one works differently. You create a free Firebase project, paste three values into the settings, and notifications go out from your own project. There is no subscriber limit, no per message price and no trial period. The subscriber list sits in your own database tables, on your own server, and you can export or delete it whenever you want.<\/p>\n\n<p>https:\/\/www.youtube.com\/watch?v=1drP_CkHkNo<\/p>\n\n<h4>Quick links<\/h4>\n\n<ul>\n<li><a href=\"https:\/\/demo.devdiggers.com\/wordpress-pwa\/\">Live Demo<\/a><\/li>\n<li><a href=\"https:\/\/www.youtube.com\/watch?v=1drP_CkHkNo\">Video Walkthrough<\/a><\/li>\n<li><a href=\"https:\/\/demo.devdiggers.com\/progressive-web-app-for-wordpress\/\">Pro Demo<\/a><\/li>\n<li><a href=\"https:\/\/devdiggers.com\/product\/progressive-web-app-for-wordpress\/\">Get Pro<\/a><\/li>\n<li><a href=\"https:\/\/docs.devdiggers.com\/progressive-web-app-for-wordpress\/\">Documentation<\/a><\/li>\n<li><a href=\"https:\/\/devdiggers.com\/contact\/\">Support<\/a><\/li>\n<\/ul>\n\n<h4>Turn your website into an installable app<\/h4>\n\n<p>Visitors get an install prompt on Android and desktop Chrome, and Add to Home Screen on iOS. Once installed, your site opens in its own window with no browser bar, its own app icon and its own splash screen.<\/p>\n\n<ul>\n<li>Full web app manifest control: name, short name, theme and background colour, display mode, orientation, scope, start URL, description and categories.<\/li>\n<li>Upload your own app icon set, including a maskable icon so Android does not crop your logo into a circle.<\/li>\n<li>Store screenshots for the richer, app store style install dialog Chrome shows when both a wide and a narrow screenshot are supplied.<\/li>\n<li>iOS splash screens, so the installed app does not open on a blank white screen, plus an iOS status bar style.<\/li>\n<li>An install banner with your own title, message, button label and colours, plus a [ddwppwa_install_button] shortcode for putting an install button in a page, post or widget.<\/li>\n<li>Optional campaign parameters on the start URL, so app launches can be told apart from ordinary visits in your analytics.<\/li>\n<\/ul>\n\n<h4>Offline browsing with a service worker<\/h4>\n\n<p>A service worker caches the pages people visit, so a dropped signal on a train or in a lift does not end the visit.<\/p>\n\n<ul>\n<li>Network first caching. A visitor with a connection always sees your current content, and the cache is only reached for when the network is not there.<\/li>\n<li>Set how long a cached page stays usable.<\/li>\n<li>Point the offline fallback at any page you have written, or use the built in offline screen.<\/li>\n<li>Keep pages out of the cache with a never cache list. Your cart, checkout and account pages are filled in for you, because a cached cart is a support ticket.<\/li>\n<\/ul>\n\n<h4>Web push notifications on Android, desktop and iOS<\/h4>\n\n<p>Notifications are delivered by Firebase Cloud Messaging using your own project, so there is no cap on subscribers and no per message pricing from us.<\/p>\n\n<ul>\n<li>Write a push notification with a title, message, image and click URL, then send it to everyone who subscribed.<\/li>\n<li>Send a quick push straight from any post with the Send Push box in the editor.<\/li>\n<li>Send a push automatically when you publish a new post, with a per post switch for the ones that should stay quiet, and placeholders for the title, URL, excerpt and site name.<\/li>\n<li>Welcome new subscribers with a one time notification.<\/li>\n<li>Style the permission prompt with your own wording and colours, delay it by a number of seconds or page views, and exclude the pages where asking would be rude.<\/li>\n<li>A Test Notification button, so you find out your setup is wrong before your subscribers do.<\/li>\n<li>A [ddwppwa_subscribe_button] shortcode for asking permission from inside your content.<\/li>\n<\/ul>\n\n<h4>See who subscribed and what you sent<\/h4>\n\n<ul>\n<li>A dashboard with total installs, active installs, subscribers and notifications sent.<\/li>\n<li>An app readiness score out of 100, worked out from the same checks as the Status tab, with whatever is holding it back listed underneath.<\/li>\n<li>Charts for app performance over time, notifications sent, notifications by type and which browsers your visitors are on.<\/li>\n<li>A Delivery Log with a row for every push: type, status, how many devices it reached, and the reason when a send fails.<\/li>\n<li>A Subscribers screen listing every known device with its platform, browser, status, signed in user, when it was installed and when it was last seen.<\/li>\n<li>A setup checklist, and a Status tab that checks HTTPS, OpenSSL, your generated files, your icons and your Firebase credentials, then shows what each browser lets your app do.<\/li>\n<\/ul>\n\n<h4>Works with AMP, multisite, WPML and WooCommerce<\/h4>\n\n<ul>\n<li>AMP. On an AMP page no script is enqueued, so the page stays valid and keeps its AMP cache listing. The manifest and the app meta tags are valid AMP and are still added, so the page is still installable. Works with the official AMP plugin and with AMP for WP.<\/li>\n<li>Multisite. Every site in the network gets its own manifest, its own settings and its own subscriber list. Nothing is shared between them.<\/li>\n<li>WPML and Polylang. The app name, short name, description, install banner and permission prompt are registered for translation under the DevDiggers PWA string group, and the manifest is served in the language the visitor is reading, so a French visitor installs an app named in French.<\/li>\n<li>WooCommerce. HPOS compatible, and the cart, checkout and account pages are kept out of the offline cache by default.<\/li>\n<\/ul>\n\n<h4>Turn off what you do not need<\/h4>\n\n<p>The installable app, offline support, push notifications and analytics tracking each have their own switch, at the top of the tab where that feature is configured. Switching one off unregisters its hooks completely, which also makes it the quickest way to find out whether another plugin is conflicting.<\/p>\n\n<h4>Free vs Pro<\/h4>\n\n<p>Everything above is in this free plugin, with no subscriber cap, no sending fees and no trial period.<\/p>\n\n<p><a href=\"https:\/\/devdiggers.com\/product\/progressive-web-app-for-wordpress\/\">DevDiggers PWA Pro<\/a> adds:<\/p>\n\n<ul>\n<li>Click tracking and click through rate. See how many people opened each notification, per send and across the site, with an engagement chart on the dashboard.<\/li>\n<li>Audience targeting. Send to logged in users, guests, installed app users or WooCommerce customers instead of everyone.<\/li>\n<li>Scheduled sends. Pick a future date and time and the notification sends itself.<\/li>\n<li>Action buttons and badges. Up to two buttons on a notification, each opening its own URL, plus a badge icon for the Android status bar.<\/li>\n<li>WooCommerce push notifications. New product and on sale pushes, back in stock alerts for the shoppers who asked, order status updates to the buyer's own devices, and abandoned cart reminders.<\/li>\n<li>Recurring digest. A daily or weekly broadcast on the day and time you choose.<\/li>\n<li>App shortcuts and a QR code. Quick links in the menu that opens when someone long presses your app icon, plus a printable QR code for the app start URL.<\/li>\n<li>Cache first strategy. Serve the stored copy of a page immediately for a noticeably faster repeat visit. Free is always network first.<\/li>\n<li>App Experience. A bottom tab bar built from a WordPress menu, pull to refresh, page transitions, a loading bar, a dark mode toggle, the device share sheet, haptic feedback, a screen wake lock and saved form entries.<\/li>\n<li>Android app for the Play Store. Store readiness checks, the packaging file for PWABuilder or Bubblewrap, and the asset links file served from your own site so the installed app opens with no browser bar. You keep your own signing key. The iOS project is generated the same way.<\/li>\n<li>Send when back online. A comment or contact form submitted with no signal is queued on the device and sent as soon as the connection returns, instead of being lost to a browser error page.<\/li>\n<li>Save for offline. A button that keeps a page on the device for reading later, listed in the browser's own downloads area.<\/li>\n<\/ul>\n\n<h4>A note from the founder<\/h4>\n\n<p>Hi, I am Yash Kapoor, founder of DevDiggers. Most mobile readers close the tab and never come back, and a native app costs more than most site owners want to spend. I built this plugin so any site can be installed like an app and send its own push notifications. My team keeps it updated for every new WordPress release, and the developers who wrote it answer the support forum.<\/p>\n\n<p>Have a question? Ask in the support forum or message me directly on <a href=\"https:\/\/www.linkedin.com\/in\/iamyashkapoor\/\">LinkedIn<\/a>.<\/p>\n\n<h3>External services<\/h3>\n\n<p>This plugin uses the following external services. Each is described here so you know what is sent, why, and when.<\/p>\n\n<p><strong>1. Firebase Cloud Messaging (server side)<\/strong><\/p>\n\n<ul>\n<li>What it is: Firebase Cloud Messaging, a Google service, delivers web push notifications to browsers that subscribed on your site. It runs under the Firebase project you create yourself and connect under Configuration &gt; Push Setup.<\/li>\n<li>What it is used for: Sending your push notifications, and managing the topics a device is subscribed to.<\/li>\n<li>When data is sent: Only when a notification is sent, whether you sent it by hand, the Test Notification button sent it, or an automation such as Auto Push on Publish or the welcome notification triggered it. Also when a device subscribes or unsubscribes, so its token can be added to or removed from a topic. Nothing is sent on a page load or on a schedule.<\/li>\n<li>What data is sent: The notification title, message, image URL and click URL, together with the browser push tokens of the devices it is going to. Your service account credentials are exchanged for a short lived access token first. No visitor name, email or order data is sent.<\/li>\n<li>Endpoints: https:\/\/oauth2.googleapis.com\/token, https:\/\/fcm.googleapis.com\/v1\/projects\/{your-project-id}\/messages:send, https:\/\/iid.googleapis.com\/iid\/v1:batchAdd and https:\/\/iid.googleapis.com\/iid\/v1:batchRemove<\/li>\n<li>Terms of Service: https:\/\/firebase.google.com\/terms<\/li>\n<li>Privacy Policy: https:\/\/policies.google.com\/privacy<\/li>\n<\/ul>\n\n<p><strong>2. Firebase installations and registration (visitor's browser)<\/strong><\/p>\n\n<ul>\n<li>What it is: The same Firebase project, contacted by the Firebase JavaScript SDK running in the visitor's browser rather than by your server.<\/li>\n<li>What it is used for: Issuing the push token that identifies that browser, so notifications can reach it. Without this step no device can receive a notification.<\/li>\n<li>When data is sent: Only after a visitor allows notifications in the browser permission prompt, and afterwards when the browser refreshes an expiring token. Visitors who never allow notifications never contact these endpoints.<\/li>\n<li>What data is sent: The public Firebase config you entered, the VAPID public key, the browser's own push endpoint and keys, and the usual request data such as IP address and user agent. The token that comes back is stored in your database so you can send to that device.<\/li>\n<li>Endpoints: https:\/\/firebaseinstallations.googleapis.com\/v1 and https:\/\/fcmregistrations.googleapis.com\/v1<\/li>\n<li>Terms of Service: https:\/\/firebase.google.com\/terms<\/li>\n<li>Privacy Policy: https:\/\/policies.google.com\/privacy<\/li>\n<\/ul>\n\n<p>The Firebase JavaScript SDK itself ships inside this plugin and is served from your own domain, so no script is loaded from a third party. Only the requests described above leave the browser.<\/p>\n\n<p><strong>3. DevDiggers extensions directory<\/strong><\/p>\n\n<ul>\n<li>What it is: A read only API on devdiggers.com that returns the public list of DevDiggers extensions.<\/li>\n<li>What it is used for: Showing available DevDiggers extensions on the Extensions admin page.<\/li>\n<li>When data is sent: Only when a logged in administrator opens the Extensions admin page. The response is cached for 24 hours, so the request does not repeat on every page load.<\/li>\n<li>What data is sent: A standard outbound HTTP request only, meaning your server's IP address and a plugin user agent string, as with any web request. No personal data and no site data are sent.<\/li>\n<li>Endpoint: https:\/\/devdiggers.com\/wp-json\/ddwcs\/v1\/plugins<\/li>\n<\/ul>\n\n<p><strong>4. Newsletter subscription (optional)<\/strong><\/p>\n\n<ul>\n<li>What it is: A contact and newsletter endpoint on devdiggers.com.<\/li>\n<li>What it is used for: Adding your email address to the DevDiggers newsletter, only if you choose to subscribe.<\/li>\n<li>When data is sent: Only when an administrator submits the optional newsletter form in the plugin dashboard. Nothing is sent automatically.<\/li>\n<li>What data is sent: The email address you enter and your site URL.<\/li>\n<li>Endpoint: https:\/\/devdiggers.com\/<\/li>\n<\/ul>\n\n<p><strong>5. Gravatar (avatar images)<\/strong><\/p>\n\n<ul>\n<li>What it is: Gravatar, the globally recognised avatar service operated by Automattic.<\/li>\n<li>What it is used for: WordPress core's <code>get_avatar_url()<\/code> is used to show the logged in administrator's avatar in the plugin dashboard header. This is standard WordPress behaviour and is not specific to this plugin.<\/li>\n<li>When data is sent: Only when a logged in administrator opens the plugin dashboard and WordPress avatars are enabled in Settings &gt; Discussion. Disabling avatars there stops the request completely.<\/li>\n<li>What data is sent: An MD5 hash of the administrator's email address, inside the image URL requested by the browser, plus the usual request data such as IP address and user agent.<\/li>\n<li>Endpoint: https:\/\/secure.gravatar.com\/avatar\/<\/li>\n<li>Terms of Service: https:\/\/automattic.com\/terms\/<\/li>\n<li>Privacy Policy: https:\/\/automattic.com\/privacy\/<\/li>\n<\/ul>\n\n<p>The DevDiggers services above (3 and 4) are provided by DevDiggers. By using them you agree to the DevDiggers Terms and Conditions (https:\/\/devdiggers.com\/terms-and-conditions\/) and Privacy Policy (https:\/\/devdiggers.com\/privacy-policy\/).<\/p>\n\n<p>This plugin contains no licensing checks, no update checks and no telemetry.<\/p>\n\n<h3>Source code and build tools<\/h3>\n\n<p>The admin and frontend JavaScript and CSS are compiled from the <code>src\/<\/code> folder with webpack and Babel. The complete, human readable source code, including all build configuration, is published in our public GitHub repository:<\/p>\n\n<p>https:\/\/github.com\/DevDiggers-Technologies\/pwa<\/p>\n\n<p>To build the assets from source:<\/p>\n\n<ol>\n<li>Run <code>npm install<\/code> to install the build dependencies listed in <code>package.json<\/code>.<\/li>\n<li>Run <code>npm run build<\/code> to compile the production assets into <code>assets\/js<\/code> and <code>assets\/css<\/code>.<\/li>\n<\/ol>\n\n<p>The build configuration is in <code>webpack.config.js<\/code> and <code>babel.config.js<\/code>. The shared DevDiggers framework in <code>devdiggers-framework\/<\/code> builds the same way from its own <code>src\/<\/code> folder and its own <code>package.json<\/code>.<\/p>\n\n<p>Three third-party libraries ship unmodified, exactly as their projects release them, all under the MIT licence: Chart.js 4.5.1 (https:\/\/www.chartjs.org), Select2 4.1.0 (https:\/\/select2.org) and the Firebase JavaScript SDK 10.12.3 (https:\/\/firebase.google.com\/docs\/web\/setup), in <code>assets\/js\/vendor\/<\/code>.<\/p>\n\n<!--section=installation-->\n<ol>\n<li>Upload the devdiggers-pwa folder to \/wp-content\/plugins\/, or install the zip from Plugins &gt; Add New.<\/li>\n<li>Activate the plugin through the Plugins menu.<\/li>\n<li>Open the Progressive Web App menu and run the Setup Wizard.<\/li>\n<li>For push notifications, create a free Firebase project, then paste the Firebase config, the VAPID key and the service account JSON into Configuration &gt; Push Setup.<\/li>\n<\/ol>\n\n<p>Push notifications and offline browsing need a secure context, so serve your site over HTTPS. Localhost is fine while you are developing. The plugin warns you in the admin if it is not.<\/p>\n\n<!--section=faq-->\n<dl>\n<dt id=\"how%20do%20i%20turn%20my%20wordpress%20site%20into%20an%20app%3F\"><h3>How do I turn my WordPress site into an app?<\/h3><\/dt>\n<dd><p>Activate the plugin and run the Setup Wizard. It writes the web app manifest and the service worker for you, and asks for an app name, an icon and a theme colour. After that, browsers on Android and desktop offer an install prompt, and iPhone users can add your site to the home screen from the Safari share sheet.<\/p><\/dd>\n<dt id=\"do%20i%20need%20an%20ssl%20certificate%3F\"><h3>Do I need an SSL certificate?<\/h3><\/dt>\n<dd><p>Yes. Browsers only allow service workers and web push over HTTPS, or on localhost during development. Without it the app cannot be installed and no notification can be delivered.<\/p><\/dd>\n<dt id=\"is%20there%20a%20limit%20on%20subscribers%20or%20notifications%3F\"><h3>Is there a limit on subscribers or notifications?<\/h3><\/dt>\n<dd><p>No. Notifications are delivered by Firebase Cloud Messaging using a Firebase project you create yourself, so the sending limits are Google's rather than ours. There is no subscriber cap in the plugin, no per message price and nothing to pay us for the free version.<\/p><\/dd>\n<dt id=\"do%20i%20need%20a%20firebase%20account%3F\"><h3>Do I need a Firebase account?<\/h3><\/dt>\n<dd><p>Yes, for push notifications. It is free to create. Add the Firebase config, the VAPID key and the service account JSON under Push Setup, then use the Test Notification button to confirm it works. The installable app and offline browsing need no Firebase account at all.<\/p><\/dd>\n<dt id=\"do%20web%20push%20notifications%20work%20on%20iphone%3F\"><h3>Do web push notifications work on iPhone?<\/h3><\/dt>\n<dd><p>Yes, within the limits Apple sets. iOS delivers web push only after the visitor has added your site to the home screen from the Safari share sheet. Once it is added, the installed app gets your icon and splash screen and can receive notifications like any other device.<\/p><\/dd>\n<dt id=\"does%20it%20work%20with%20woocommerce%3F\"><h3>Does it work with WooCommerce?<\/h3><\/dt>\n<dd><p>Yes. It is HPOS compatible, and the cart, checkout and account pages are kept out of the offline cache by default so nobody is shown a stale basket. Store event pushes, such as order status updates, back in stock alerts and abandoned cart reminders, are part of the Pro version.<\/p><\/dd>\n<dt id=\"does%20it%20work%20with%20amp%3F\"><h3>Does it work with AMP?<\/h3><\/dt>\n<dd><p>Yes. On an AMP page no script is enqueued, so the page stays valid, but the manifest and app meta tags are still added and the page is still installable. Both the official AMP plugin and AMP for WP are detected.<\/p><\/dd>\n<dt id=\"can%20i%20send%20a%20push%20automatically%20when%20i%20publish%20a%20post%3F\"><h3>Can I send a push automatically when I publish a post?<\/h3><\/dt>\n<dd><p>Yes. Turn on Auto Push on Publish under Automations and every published post broadcasts to your subscribers, using a title template with placeholders for the post title, URL, excerpt and site name. Any single post can opt out from the Send Push box in the editor.<\/p><\/dd>\n<dt id=\"can%20i%20see%20what%20i%20have%20sent%3F\"><h3>Can I see what I have sent?<\/h3><\/dt>\n<dd><p>Yes. Every push is recorded in the Delivery Log with its type, delivery status, the number of devices it reached and the reason when a send fails. Knowing how many people opened each notification, as a click count and a click through rate, is part of the Pro version.<\/p><\/dd>\n<dt id=\"can%20i%20use%20it%20on%20a%20multisite%20network%20or%20a%20translated%20site%3F\"><h3>Can I use it on a multisite network or a translated site?<\/h3><\/dt>\n<dd><p>Yes. Each site in a network keeps its own manifest, settings and subscriber list. With WPML or Polylang, the app name, description, install banner and permission prompt are registered for translation and the manifest is served in the visitor's language.<\/p><\/dd>\n<dt id=\"will%20this%20slow%20my%20site%20down%3F\"><h3>Will this slow my site down?<\/h3><\/dt>\n<dd><p>The front end adds a small script and a service worker. Caching usually makes repeat visits faster rather than slower, and if you do not want a particular feature, each one has its own switch.<\/p><\/dd>\n<dt id=\"can%20i%20publish%20the%20app%20on%20google%20play%3F\"><h3>Can I publish the app on Google Play?<\/h3><\/dt>\n<dd><p>Not from the free version. Free gives you the installable web app that Android and iOS both accept from the browser. The packaging file for PWABuilder or Bubblewrap, the store readiness checks and the asset links file needed for a Play Store listing are part of the Pro version.<\/p><\/dd>\n<dt id=\"what%20happens%20to%20my%20subscribers%20if%20i%20delete%20the%20plugin%3F\"><h3>What happens to my subscribers if I delete the plugin?<\/h3><\/dt>\n<dd><p>They are kept. Deleting the plugin removes the generated service worker and manifest, because a stale service worker would keep serving cached pages, but your notifications, subscribers and delivery log stay in the database unless you tick the setting that asks for them to be removed.<\/p><\/dd>\n\n<\/dl>\n\n<!--section=changelog-->\n<h4>1.1.1<\/h4>\n\n<ul>\n<li>Fixed a fatal error (undefined function ddwppwa_print_notification) when saving a push notification in the admin.<\/li>\n<\/ul>\n\n<h4>1.1.0<\/h4>\n\n<ul>\n<li>Upgraded to DevDiggers Framework 1.1.0: admin screens now open instantly without a full page reload, with skeleton placeholders while a screen loads.<\/li>\n<li>Improved list screens: the title action buttons, search box, bulk actions and pagination now follow one consistent layout.<\/li>\n<li>Fixed minor PHP warnings and JavaScript errors seen after in-place navigation.<\/li>\n<li>Bug fixes and stability improvements.<\/li>\n<\/ul>\n\n<h4>1.0.0<\/h4>\n\n<ul>\n<li>Initial release of the free version.<\/li>\n<\/ul>","raw_excerpt":"Turn your site into an installable app with offline browsing and unlimited web push notifications sent from your own Firebase project.","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/ceb.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin\/361892","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/ceb.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin"}],"about":[{"href":"https:\/\/ceb.wordpress.org\/plugins\/wp-json\/wp\/v2\/types\/plugin"}],"replies":[{"embeddable":true,"href":"https:\/\/ceb.wordpress.org\/plugins\/wp-json\/wp\/v2\/comments?post=361892"}],"author":[{"embeddable":true,"href":"https:\/\/ceb.wordpress.org\/plugins\/wp-json\/wporg\/v1\/users\/devdiggers"}],"wp:attachment":[{"href":"https:\/\/ceb.wordpress.org\/plugins\/wp-json\/wp\/v2\/media?parent=361892"}],"wp:term":[{"taxonomy":"plugin_section","embeddable":true,"href":"https:\/\/ceb.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_section?post=361892"},{"taxonomy":"plugin_tags","embeddable":true,"href":"https:\/\/ceb.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_tags?post=361892"},{"taxonomy":"plugin_category","embeddable":true,"href":"https:\/\/ceb.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_category?post=361892"},{"taxonomy":"plugin_contributors","embeddable":true,"href":"https:\/\/ceb.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_contributors?post=361892"},{"taxonomy":"plugin_business_model","embeddable":true,"href":"https:\/\/ceb.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_business_model?post=361892"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}