<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ciprian Popescu</title>
    <description>The latest articles on DEV Community by Ciprian Popescu (@wolffe).</description>
    <link>https://dev.to/wolffe</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F455263%2F80394f4f-c71c-4f05-8934-fa745ff10584.jpeg</url>
      <title>DEV Community: Ciprian Popescu</title>
      <link>https://dev.to/wolffe</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/wolffe"/>
    <language>en</language>
    <item>
      <title>10+ years building WordPress plugins at getbutterfly.com — Some reflections &amp; stats</title>
      <dc:creator>Ciprian Popescu</dc:creator>
      <pubDate>Thu, 18 Sep 2025 16:05:40 +0000</pubDate>
      <link>https://dev.to/wolffe/10-years-building-wordpress-plugins-at-getbutterflycom-some-reflections-stats-58p</link>
      <guid>https://dev.to/wolffe/10-years-building-wordpress-plugins-at-getbutterflycom-some-reflections-stats-58p</guid>
      <description>&lt;p&gt;Hey everyone — I run &lt;a href="https://getbutterfly.com" rel="noopener noreferrer"&gt;getbutterfly.com&lt;/a&gt;, where I build &amp;amp; sell WordPress plugins. It’s been over a decade in this business now, and I wanted to share some thoughts + data (because I love numbers) on the WordPress/plugin ecosystem, what’s changed, and why I’m still bullish. Would love to hear others’ experiences too.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A bit about me&lt;/strong&gt;&lt;br&gt;
I’ve been developing WordPress plugins for 10+ years, covering various niches (security, optimization, UX, etc.).&lt;/p&gt;

&lt;p&gt;Over time I’ve seen major shifts: in how people build sites, what they expect from plugins (performance, compatibility, security), how they buy, etc.&lt;/p&gt;

&lt;p&gt;Changes over the past 10 years &amp;amp; things I’ve learned&lt;br&gt;
Here are a few patterns I’ve noticed, plus what they mean for someone building plugins:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expectations on performance &amp;amp; compatibility have escalated&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;What was acceptable 10+ years ago in terms of speed, code design, plugin conflicts, etc., is no longer working. Users expect lean, well-architected, fast plugins that don’t bog down a site.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Security matters more than ever&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With so many sites running WordPress, and so many plugins in play, vulnerabilities (in plugins/themes) are a big risk. Keeping up with security best practices, regular maintenance, and good support is essential, not optional.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Plugin visibility is harder&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With tens of thousands of plugins out there, standing out is tough. Good documentation, clean UX, solid marketing, responsive support — all of that makes a big difference.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Freemium / licensing models have become standard&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Many plugin users expect at least a free version; premium or paid upgrades must justify their cost clearly (features, stability, support). Licenses, update frequency, add-ons: these all play into what people will pay for.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;User expectations around updates / compatibility&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;WordPress core evolves, PHP versions evolve, hosting environments evolve. Plugins must maintain compatibility and be tested across environments.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What makes &lt;a href="https://getbutterfly.com" rel="noopener noreferrer"&gt;getbutterfly.com&lt;/a&gt; different / what I focus on&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here are a few things I try to do to stay relevant and deliver value:&lt;br&gt;
I aim to make plugins that are modular and lightweight, so users can activate only the features they need, avoiding “feature bloat.”&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Rigorous testing (especially with WP core updates, PHP version changes, conflicts with popular themes/plugins).&lt;/li&gt;
&lt;li&gt;Good support/documentation — reducing friction for users.&lt;/li&gt;
&lt;li&gt;Transparency on updates and roadmap.&lt;/li&gt;
&lt;li&gt;Listening to user feedback &amp;amp; using it to shape future features.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Challenges &amp;amp; what I’m working on&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No startup/plugin business is without its struggles. Some of the ones I’ve faced:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Discoverability: Being found in a huge plugin market is hard. Good SEO, marketplace relationships, content &amp;amp; marketing help, but it’s a long game.&lt;/li&gt;
&lt;li&gt;Maintenance vs innovation trade-off: spending time fixing bugs, ensuring compatibility takes away from new features sometimes.&lt;/li&gt;
&lt;li&gt;Pricing pressures: Many customers are price-sensitive; some expect a lot for free. Balancing what you offer for free vs premium, without devaluing the product, is tricky.&lt;/li&gt;
&lt;li&gt;Fragmentation: different hosting, different environment setups, PHP versions, themes — ensuring broad compatibility is tough.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why I’m still bullish (10 years in &amp;amp; counting)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The massive install base of WordPress means there will always be demand. Even as things evolve, new plugin needs emerge (e.g. performance, SEO, AI, security).&lt;/p&gt;

&lt;p&gt;New challenges = new opportunity: as hosting improves, as users demand better speed / mobile performance / security / AI integrations — plugin makers who adapt well can thrive.&lt;/p&gt;

&lt;p&gt;The barrier to entry (at least for basic-level plugins) is relatively low compared to building a full app; but the upside (if you build something good, well-supported, and with a loyal user base) remains high.&lt;/p&gt;

&lt;p&gt;Community matters: WordPress has a big user/developer community. WordCamps, forums, groups — those help spread the word and improve best practices.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Open question / for the community&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What are your go-to strategies for plugin discovery (especially in the crowded free + freemium space)?&lt;/li&gt;
&lt;li&gt;How do you balance pricing vs value vs free version limitations?&lt;/li&gt;
&lt;li&gt;Anyone else with decade-long plugin experience: what has changed the most for you (in dev tools, user expectations, marketing, etc.)?&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>career</category>
      <category>php</category>
      <category>wordpress</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Lighthouse Performance Tips &amp; Tricks for Your Website</title>
      <dc:creator>Ciprian Popescu</dc:creator>
      <pubDate>Mon, 22 Apr 2024 17:14:16 +0000</pubDate>
      <link>https://dev.to/wolffe/lighthouse-performance-tips-tricks-for-your-website-2gif</link>
      <guid>https://dev.to/wolffe/lighthouse-performance-tips-tricks-for-your-website-2gif</guid>
      <description>&lt;p&gt;This is an exhaustive list of technical tips and tricks that will help improve your page speed and increase your performance score.&lt;/p&gt;

&lt;h2&gt;
  
  
  DNS Lookup &amp;amp; Connection
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Check your DNS records and remove any that you don’t need. Use a DNS server, such as Cloudflare, to improve the DNS lookup speed.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  TTFB (Time To First Byte)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Upgrade your server software (Apache, Nginx, Litespeed, IIS) to the latest available version.&lt;/li&gt;
&lt;li&gt;Upgrade your server PHP to the latest available version (8+).&lt;/li&gt;
&lt;li&gt;Upgrade your server database engine (MySQL, MariaDB, Percona, SQLite) to the latest available version.&lt;/li&gt;
&lt;li&gt;Create a static &lt;code&gt;robots.txt&lt;/code&gt; file to avoid WordPress initializing every time a bot requests it.&lt;/li&gt;
&lt;li&gt;Create a static &lt;code&gt;favicon.ico&lt;/code&gt; file to avoid slowdowns every time the browser requests it.&lt;/li&gt;
&lt;li&gt;Set up a backup server before taking the steps below.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Response Time, Page Speed &amp;amp; Performance
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Upgrade your WordPress plugins to the latest available version.&lt;/li&gt;
&lt;li&gt;Upgrade your WordPress CMS to the latest available version.&lt;/li&gt;
&lt;li&gt;Upgrade your WordPress theme to the latest available version.&lt;/li&gt;
&lt;li&gt;Do not use a redirection plugin, if possible. Set your redirects server-side, in your &lt;code&gt;.htaccess&lt;/code&gt; file or your Nginx configuration file.&lt;/li&gt;
&lt;li&gt;Make sure your database engine is InnoDB.&lt;/li&gt;
&lt;li&gt;Make sure your database encoding/collation is utf8mb4.&lt;/li&gt;
&lt;li&gt;Increase your WordPress memory constant to 128M+.&lt;/li&gt;
&lt;li&gt;Consider disabling revisions – &lt;code&gt;WP_POST_REVISIONS&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Consider disabling the trash – &lt;code&gt;MEDIA_TRASH&lt;/code&gt; and &lt;code&gt;EMPTY_TRASH_DAYS&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;See code samples inside your &lt;strong&gt;&lt;a href="https://getbutterfly.com/wordpress-plugins/lighthouse/"&gt;Lighthouse&lt;/a&gt;&lt;/strong&gt; plugin → Tips &amp;amp; Tricks tab.&lt;/p&gt;

&lt;h2&gt;
  
  
  Assets &amp;amp; Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Reduce the number of JavaScript resources – use Google Tag Manager to load all external tracking snippets, analytics and more.&lt;/li&gt;
&lt;li&gt;Optimize your theme to load all JavaScript resources in the footer.&lt;/li&gt;
&lt;li&gt;Reduce the number of JavaScript resources and CSS stylesheets by cleaning up your plugins, refactoring your website’s functionality or finding alternative plugins.&lt;/li&gt;
&lt;li&gt;Reduce the number of JavaScript resources and CSS stylesheets by combining them (either by refactoring your theme/plugins or by programmatically concatenating them).&lt;/li&gt;
&lt;li&gt;Minify your JavaScript resources and CSS stylesheets, either manually or by using Cloudflare.&lt;/li&gt;
&lt;li&gt;Load your custom fonts locally.&lt;/li&gt;
&lt;li&gt;Get rid of all unnecessary font subsets (based on your target audience’s language).&lt;/li&gt;
&lt;li&gt;Get rid of all font versions, except for WOFF2.&lt;/li&gt;
&lt;li&gt;Use resource hints and early hints.&lt;/li&gt;
&lt;li&gt;Use server caching (OPcache, XCache, Varnish, Redis, etc.).&lt;/li&gt;
&lt;li&gt;Use a caching plugin.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Prefetching
&lt;/h2&gt;

&lt;p&gt;Because &lt;code&gt;dns-prefetch&lt;/code&gt; resolves only the domain name but doesn’t &lt;code&gt;preconnect&lt;/code&gt; to the remote server or preload the resource, it requires little bandwidth. However, it can significantly improve DNS latency — the total request-response time between the DNS server and the user’s browser.&lt;/p&gt;

&lt;p&gt;You’ll only need to use &lt;code&gt;dns-prefetch&lt;/code&gt; when the resource is hosted on a different domain, since you don’t need to resolve your own domain name. &lt;code&gt;dns-prefetch&lt;/code&gt; is typically recommended when prefetching domain names for: web fonts, such as Google Fonts or custom CDN fonts, analytics scripts, scripts coming from third-party resources, social media widgets or any widget that loads third-party content via the &lt;code&gt;script&lt;/code&gt; tag, resources hosted on a CDN.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preconnecting
&lt;/h2&gt;

&lt;p&gt;For the most part, you can use the &lt;code&gt;preconnect&lt;/code&gt; resource hint for the same things as &lt;code&gt;dns-prefetch&lt;/code&gt;. You should choose it only if you are sure the user will request the script, font, stylesheet, or other resource from the third-party server.&lt;/p&gt;

&lt;p&gt;Since &lt;code&gt;preconnect&lt;/code&gt; exchanges more data, it also needs more bandwidth. So you have to be more careful with it to avoid slowing down the page and wasting your user’s bandwidth with redundant data.&lt;/p&gt;

&lt;p&gt;Be careful to not add too many resource hints, as they could quite easily negatively impact performance, especially on mobile.&lt;/p&gt;

&lt;p&gt;Check the &lt;a href="https://getbutterfly.com/using-resource-hints-to-optimize-wordpress-performance/"&gt;Using Resource Hints to Optimize WordPress Performance&lt;/a&gt; guide.&lt;/p&gt;

&lt;h2&gt;
  
  
  Content
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Get rid of your current page builder or reconsider its usability. Use the native WordPress block editor instead.&lt;/li&gt;
&lt;li&gt;Make sure all your images are lazy loaded.&lt;/li&gt;
&lt;li&gt;Replace your vector images and/or icons with inline SVG.&lt;/li&gt;
&lt;li&gt;Resize and compress your JPEG images.&lt;/li&gt;
&lt;li&gt;Use a third-party service (not a plugin) to optimize your images before uploading them.&lt;/li&gt;
&lt;li&gt;Remove (or disable) your lazy-loading plugin (or script). Modern browsers now include native lazy-loading.&lt;/li&gt;
&lt;li&gt;Check the &lt;a href="https://getbutterfly.com/how-to-optimize-wordpress-native-settings-for-performance/"&gt;WordPress Native Settings Optimization&lt;/a&gt; guide to squeeze even more speed from your WordPress site.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Error Logging
&lt;/h2&gt;

&lt;p&gt;Stay on top of errors, warnings, and notices as they occur.&lt;/p&gt;

&lt;p&gt;See all errors, warnings, and notices in a file generally called &lt;code&gt;debug.log&lt;/code&gt; in &lt;code&gt;wp-content&lt;/code&gt; folder. If Apache does not have write permissions, you may need to create the file first and set the appropriate permissions (e.g. &lt;code&gt;0666&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;In order for errors to be logged, you need to edit your &lt;code&gt;wp-config.php&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;See code samples inside your &lt;strong&gt;Lighthouse&lt;/strong&gt; plugin → Tips &amp;amp; Tricks tab.&lt;/p&gt;

&lt;p&gt;Find your error log (in document root or in &lt;code&gt;wp-content&lt;/code&gt; directory):&lt;/p&gt;

&lt;p&gt;Example #1: &lt;code&gt;/home/user/example.com/debug.log&lt;/code&gt;&lt;br&gt;
Example #2: &lt;code&gt;/home/user/example.com/wp-content/debug.log&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Other
&lt;/h2&gt;

&lt;p&gt;The suggestions below might not apply to your website.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Remove Google Analytics and replace it with a lighter solution, such as &lt;a href="https://getbutterfly.com/wordpress-plugins/active-analytics/"&gt;Active Analytics&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Remove Google reCAPTCHA and replace it with Akismet or a lighter solution.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>tutorial</category>
      <category>wordpress</category>
    </item>
    <item>
      <title>What do you think of my new website design?</title>
      <dc:creator>Ciprian Popescu</dc:creator>
      <pubDate>Tue, 26 Mar 2024 16:40:40 +0000</pubDate>
      <link>https://dev.to/wolffe/what-do-you-think-of-my-new-website-design-2l21</link>
      <guid>https://dev.to/wolffe/what-do-you-think-of-my-new-website-design-2l21</guid>
      <description>&lt;p&gt;I have recently updated my website design, and I would like some feedback. What do you think?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://qbf.ie/"&gt;https://qbf.ie/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>showdev</category>
    </item>
    <item>
      <title>tail.select 1.0.0 Released: Vanilla JavaScript Library for Stylish and Functional (Multi) Select Fields</title>
      <dc:creator>Ciprian Popescu</dc:creator>
      <pubDate>Thu, 14 Dec 2023 16:51:22 +0000</pubDate>
      <link>https://dev.to/wolffe/tailselect-100-released-vanilla-javascript-library-for-stylish-and-functional-multi-select-fields-78j</link>
      <guid>https://dev.to/wolffe/tailselect-100-released-vanilla-javascript-library-for-stylish-and-functional-multi-select-fields-78j</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--m2fFpfJI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wsuqkzfzgoodj2bob290.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--m2fFpfJI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wsuqkzfzgoodj2bob290.png" alt="tail.select" width="800" height="366"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://getbutterfly.com/tail-select/"&gt;&lt;code&gt;tail.select&lt;/code&gt;&lt;/a&gt; library is making a comeback, now rewritten in pure, vanilla JavaScript. Say goodbye to complex dependencies and welcome a simple yet powerful tool for enhancing your HTML &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; fields. Version &lt;code&gt;1.0.0&lt;/code&gt; is here, and it brings a host of features to streamline and beautify your website's (multi) select fields.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's New?
&lt;/h2&gt;

&lt;p&gt;Vanilla JS, Plain JS, ES6, Modern Browsers: Embrace simplicity with a library that works seamlessly with vanilla JavaScript, plain JS, and is fully compatible with ES6 in modern browsers.&lt;/p&gt;

&lt;p&gt;Beautiful (Multiple) Select Field: Elevate the aesthetic appeal of your HTML  fields effortlessly.&lt;/p&gt;

&lt;p&gt;Awesome Search Function: Enhance user experience with an intuitive search bar that makes finding options a breeze.&lt;/p&gt;

&lt;p&gt;Move Selected Options Wherever You Want: Tailor your UI by moving selected options to the desired location.&lt;/p&gt;

&lt;p&gt;Over 30 Options &amp;amp; 5 Themes: Customize to your heart's content with a wide array of options and themes, ensuring a perfect fit for your website's design.&lt;/p&gt;

&lt;p&gt;Extendable and Translatable Environment: &lt;code&gt;tail.select&lt;/code&gt; provides an extendable and translatable environment, allowing for flexibility in your projects.&lt;/p&gt;

&lt;p&gt;Hackable with 3 Callbacks &amp;amp; 3 Events: Dive into the library's core functionalities with callbacks and events, making &lt;code&gt;tail.select&lt;/code&gt; a versatile tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  Notable Options
&lt;/h2&gt;

&lt;p&gt;The library comes with an initial set of options, allowing you to fine-tune &lt;code&gt;tail.select&lt;/code&gt; according to your project's requirements:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;multiTags&lt;/code&gt;: Enable or disable tags based on your preference.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;multiCounter&lt;/code&gt;: Keep track of selected options with the &lt;code&gt;multiCounter&lt;/code&gt; feature.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;theme&lt;/code&gt;: Choose between the 'light' or 'dark' themes to seamlessly integrate with your website's design.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;classNames&lt;/code&gt;: Add custom classes to tailor the appearance to your liking.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;strings&lt;/code&gt;: Customize the user interface by modifying strings like "All," "None," "Select an option...," and "Type in to search..."&lt;/p&gt;

&lt;h2&gt;
  
  
  Search, Descriptions, Tags, Themes, Custom Classes, and Translation
&lt;/h2&gt;

&lt;p&gt;Search: A search field is included in the custom dropdown by default, enhancing the discoverability of options.&lt;/p&gt;

&lt;p&gt;Descriptions: Display custom descriptions by adding a data-description attribute to your &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; field &lt;code&gt;&amp;lt;option&amp;gt;&lt;/code&gt; elements.&lt;/p&gt;

&lt;p&gt;Tags: Enable tags for a more organized and visually appealing selection experience.&lt;/p&gt;

&lt;p&gt;Themes: Choose between light and dark themes to match your website's overall aesthetic.&lt;/p&gt;

&lt;p&gt;Custom Classes: Add custom classes using the &lt;code&gt;classNames&lt;/code&gt; parameter for a tailored appearance.&lt;/p&gt;

&lt;p&gt;Translation: Translated strings can now be added directly into the caller function, offering greater control over the language used in the UI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;tail.select is back, and it's still free to use with an MIT License. Upgrade your (multi) select fields with the simplicity and elegance of pure, vanilla JavaScript. Check out the documentation and get started with &lt;code&gt;tail.select&lt;/code&gt; &lt;code&gt;1.0.0&lt;/code&gt; today!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>SpeedFactor moves to WP Lighthouse!</title>
      <dc:creator>Ciprian Popescu</dc:creator>
      <pubDate>Wed, 19 Oct 2022 17:05:48 +0000</pubDate>
      <link>https://dev.to/wolffe/speedfactor-moves-to-wp-lighthouse-1f8b</link>
      <guid>https://dev.to/wolffe/speedfactor-moves-to-wp-lighthouse-1f8b</guid>
      <description>&lt;p&gt;SpeedFactor, as initially developed 3 years ago, is no more. But 90% of the code has been transferred to my WP Lighthouse plugin (due to the fact that 90% of the existing websites were powered by WordPress).&lt;/p&gt;

&lt;p&gt;The code has been simplified, open-sourced and integrated into the plugin. The business model has also changed, and it's now easier to pay for updates and support (or not). The plugin is automatically kept up-to-date from a private GitHub organization.&lt;/p&gt;

&lt;p&gt;From a business point of view, I feel like this is a good change. Less code maintenance gives me more time for feature updates and client support.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why less maintenance?
&lt;/h2&gt;

&lt;p&gt;Because WP Lighthouse uses the #WordPress infrastructure - WPaaF (WordPress As A framework).&lt;/p&gt;

&lt;p&gt;If you are interested in migrating to WP Lighthouse or giving the plugin a test-drive, check out the link below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://getbutterfly.com/"&gt;https://getbutterfly.com/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>performance</category>
      <category>pagespeed</category>
      <category>saas</category>
    </item>
    <item>
      <title>BOTS: Mischievous Pixel Avatars</title>
      <dc:creator>Ciprian Popescu</dc:creator>
      <pubDate>Mon, 26 Sep 2022 14:57:23 +0000</pubDate>
      <link>https://dev.to/wolffe/bots-mischievous-pixel-avatars-4dai</link>
      <guid>https://dev.to/wolffe/bots-mischievous-pixel-avatars-4dai</guid>
      <description>&lt;p&gt;This year I have coded and launched a small app (available on Google Play Store and as a PWA), where you can generate an avatar (looking like a square bot), and give it a name, a rarity/probability and a class. The name is BOTS.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NfoNU3xs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xa9aglncgs992is57lm1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NfoNU3xs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xa9aglncgs992is57lm1.png" alt="Iron Carnival: BOTS" width="880" height="479"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--u27pfzty--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aq5ii98dsr50tz1bg00l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--u27pfzty--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aq5ii98dsr50tz1bg00l.png" alt="Iron Carnival: BOTS" width="880" height="442"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It started small, mostly in JavaScript and Canvas. Later, a database has been added, but things didn’t go according to plan, and I switched from PouchDB to SQLite3 to SleekDB and then back to PouchDB. When the number of bots grew from a few to a few hundreds, I decided to go with a proper PHP/MySQL implementation and a membership system. This is how BOTS 2 came to life – a user-generated Bot app, with no constraints, no microtransactions, no time limit.&lt;/p&gt;

&lt;p&gt;I am not looking to keep the Google Play Store app any more, so I will turn this into a website (also PWA-ready), with full membership capabilities, user ranks, badges, points, profiles and more.&lt;/p&gt;

&lt;p&gt;The logic behind the main Bot generation is based on random settings, while some of them are linked. There are custom eyes, custom textures, custom colours, random elements and items, random names, random classes and random rarities.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--78GpXRlq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/npt9mdpbj9lcq6vk6xn5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--78GpXRlq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/npt9mdpbj9lcq6vk6xn5.png" alt="Iron Carnival: BOTS" width="502" height="772"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is how BOTS started. We moved all the code into a Canvas element and started building everything in there, with zero HTML or CSS. BOTS evolved, and we got around 10 different parameters to play with and randomize their values. As my son wanted to be able to play with these parameters, I exposed them all as HTML checkboxes and  dropdowns.&lt;/p&gt;

&lt;p&gt;Fast-forward a few weeks, I had a fully working PWA and a native Google Play app on my phone. As the “requirements” evolved, I needed to save the generated Bots in a small database. So, as I was saying in my previous article, I switched from PouchDB to SQLite3 to SleekDB and then back to PouchDB. When the number of Bots grew from a few to a few hundreds, I decided to go with a proper PHP/MySQL implementation and a membership system.&lt;/p&gt;

&lt;p&gt;Fast-forward this week, I have a fully working website with a membership system and a dynamic Bot generation system. There are badges, texture packs, and upcoming trading and collection features.&lt;/p&gt;

&lt;p&gt;One of the features under heavy development is a parameter-based generation system with lots of variables. And I mean lots.&lt;/p&gt;

&lt;p&gt;So, finally, here it is, the BOTS website – &lt;a href="https://ironcarnival.com/"&gt;Iron Carnival: BOTS&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I wrote more about the process here – &lt;a href="https://getbutterfly.com/bots-v2-development-update/"&gt;BOTS v2: Development Update&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>art</category>
      <category>canvasjs</category>
      <category>programming</category>
    </item>
    <item>
      <title>Code Golfing Tips &amp; Tricks: How to Minify your JavaScript Code</title>
      <dc:creator>Ciprian Popescu</dc:creator>
      <pubDate>Tue, 09 Aug 2022 14:22:16 +0000</pubDate>
      <link>https://dev.to/wolffe/code-golfing-tips-tricks-how-to-minify-your-javascript-code-3ofg</link>
      <guid>https://dev.to/wolffe/code-golfing-tips-tricks-how-to-minify-your-javascript-code-3ofg</guid>
      <description>&lt;p&gt;If you are into JavaScript code golfing or just looking to minify your code, here’s a collection of tips and tricks to help you understand JavaScript intricacies and make your code shorter.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Javascript Golfing?
&lt;/h2&gt;

&lt;p&gt;JavaScript golfing is the process of writing the smallest amount of JavaScript code to do something awesome. It tests your ability to reduce, reuse, and recycle for the purpose of achieving the tiniest footprint possible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why and when should minified code be used?
&lt;/h2&gt;

&lt;p&gt;The purpose of code golfing is to test one’s abilities, during challenges. That said, you should not use techniques like this in your normal code, as it compromises readability. Ideally, you should only use these tricks during a challenge.&lt;/p&gt;

&lt;p&gt;As the article is way too long, here's a link to the original article:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://getbutterfly.com/code-golfing-tips-tricks-how-to-minify-your-javascript-code/"&gt;Code Golfing Tips &amp;amp; Tricks: How to Minify your JavaScript Code&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Quick tutorial: JavaScript Form Validation</title>
      <dc:creator>Ciprian Popescu</dc:creator>
      <pubDate>Mon, 04 Jul 2022 15:55:42 +0000</pubDate>
      <link>https://dev.to/wolffe/quick-tutorial-javascript-form-validation-221f</link>
      <guid>https://dev.to/wolffe/quick-tutorial-javascript-form-validation-221f</guid>
      <description>&lt;p&gt;&lt;a href="https://getbutterfly.com/javascript-form-validation/"&gt;JavaScript Form Validation&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This JavaScript code snippet adds inline validation to any form field (input, select and textarea).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Match an element against a given pattern and add a visual hint:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function cgPatternMatch(element, pattern) {
    if ((element.value).match(pattern)) {
        cgToggleError(element, 'valid');
    } else {
        cgToggleError(element, 'invalid');
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Match an element against a given value:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function cgValueMatch(e) {
    return (e.value !== '') ? true : false;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Check if email and email confirmation elements match and add a visual hint:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function cgEmailValidate(e1, e2) {
    let pattern = /^([\w-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([\w-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$/;

    if (
        e1.value === e2.value &amp;amp;&amp;amp;
        pattern.test(e1.value) &amp;amp;&amp;amp;
        pattern.test(e2.value)
    ) {
        cgToggleError(e2, 'valid');
    } else {
        cgToggleError(e2, 'invalid');
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Match an element against a given pattern (based on validation type) and add a visual hint:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function cgFormValidate(element, type) {
    if (type === 'alpha') {
        let pattern = /^[a-zA-Z\-'.\s]+$/;

        cgPatternMatch(element, pattern);
    } else if (type === 'numeric') {
        let pattern = /^[0-9]{1,10}$/;

        cgPatternMatch(element, pattern);
    } else if (type === 'alphanumeric') {
        let pattern = /^[,'a-zA-Z0-9\-\s]+$/;

        cgPatternMatch(element, pattern);
    } else if (type === 'date') {
        let pattern = /^[,'0-9\/\s]+$/;

        cgPatternMatch(element, pattern);
    } else if (type === 'email') {
        let pattern = /^([\w-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([\w-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$/;

        cgPatternMatch(element, pattern);
    } else if (type === 'dropdown') {
        if (element.value === '') {
            cgToggleError(element, 'invalid');
        } else {
            cgToggleError(element, 'valid');
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, custom data validation needs some additional HTML parameters and an &lt;code&gt;onload()&lt;/code&gt; event.&lt;/p&gt;

&lt;p&gt;Read more about &lt;a href="https://getbutterfly.com/javascript-form-validation/"&gt;custom JavaScript form validation&lt;/a&gt; and implementation on my development blog.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Quick tip: How I optimized my WordPress website using 6 lines</title>
      <dc:creator>Ciprian Popescu</dc:creator>
      <pubDate>Mon, 04 Jul 2022 14:47:51 +0000</pubDate>
      <link>https://dev.to/wolffe/quick-tip-how-i-optimized-my-wordpress-website-using-6-lines-3joi</link>
      <guid>https://dev.to/wolffe/quick-tip-how-i-optimized-my-wordpress-website-using-6-lines-3joi</guid>
      <description>&lt;p&gt;I optimized my WordPress website using 6 lines in my &lt;code&gt;.htaccess&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;This will only work with HTTP/2 websites (I suppose most of them are in 2022).&lt;/p&gt;

&lt;p&gt;Having tested my website with WebPageTest and GTMetrix, I noticed the same render-blocking behaviour for 6 files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;2 CSS files (one mine, one WordPress')&lt;/li&gt;
&lt;li&gt;1 JavaScript file&lt;/li&gt;
&lt;li&gt;3 font files (.woff2)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The solution was to use early hints (only available in Chrome 103, which is the bulk of my audience).&lt;/p&gt;

&lt;p&gt;Here's what I put in my &lt;code&gt;.htaccess&lt;/code&gt; file, in the root of my website:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;H2PushResource /wp-content/themes/my-theme/base.css?ver=6.6.2
H2PushResource /wp-includes/css/dist/block-library/style.min.css?ver=6.0
H2PushResource /wp-content/themes/my-theme/js/whiskey-functions.js?ver=6.6.2

H2PushResource /wp-content/themes/my-theme/fonts/bentonsans/bentonsans-regular.woff2
H2PushResource /wp-content/themes/my-theme/fonts/bentonsans/bentonsans-medium.woff2
H2PushResource /wp-content/themes/my-theme/fonts/bentonsans/bentonsans-bold.woff2

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The only caveat here, and the most important one, is that I needed to put the links exactly the way they are loaded, and that is &lt;strong&gt;including the version parameter&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That means that whenever I want to update my theme (or WordPress), I need to update my &lt;code&gt;.htaccess&lt;/code&gt; file and change the version number.&lt;/p&gt;

&lt;p&gt;If you want to see how fast it is, check out &lt;a href="https://getbutterfly.com/"&gt;https://getbutterfly.com/&lt;/a&gt; and browse a few pages. Everything loads almost instantly.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Question: How can I improve views on my ad networks (CPM)?</title>
      <dc:creator>Ciprian Popescu</dc:creator>
      <pubDate>Mon, 04 Jul 2022 14:31:08 +0000</pubDate>
      <link>https://dev.to/wolffe/question-how-can-i-improve-views-on-my-ad-networks-cpm-1e8</link>
      <guid>https://dev.to/wolffe/question-how-can-i-improve-views-on-my-ad-networks-cpm-1e8</guid>
      <description>&lt;p&gt;I have a developer blog with lots of technical articles – the code to text ratio is 4 to 1. I have a pretty low CTR rate, and I have 90% desktop visits vs 10% mobile visits. I have always assumed that my website's audience is more “desktop-inclined” as the mobile version is highly optimized.&lt;/p&gt;

&lt;p&gt;I currently use CarbonAds and Ethical Ads, both with similar earnings (so I can't quit one over the other).&lt;/p&gt;

&lt;p&gt;What are my options here (other than just to increase my traffic)?&lt;/p&gt;

&lt;p&gt;If relevant, here is a link to my website – &lt;a href="https://getbutterfly.com/"&gt;getButterfly.com&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>A JavaScript Slider in 8 Lines</title>
      <dc:creator>Ciprian Popescu</dc:creator>
      <pubDate>Thu, 31 Mar 2022 21:52:43 +0000</pubDate>
      <link>https://dev.to/wolffe/a-javascript-slider-in-8-lines-42bh</link>
      <guid>https://dev.to/wolffe/a-javascript-slider-in-8-lines-42bh</guid>
      <description>&lt;p&gt;In this post we’ll build a minimal JavaScript slider, with no dependencies. The smallest, actually, without the actual slides’ HTML content: 8 lines of JavaScript.&lt;/p&gt;

&lt;p&gt;Building a slider or a text rotator shouldn't use an insane amount of JavaScript and it should leverage modern CSS as much as possible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The trick in this tutorial is matching CSS animation timing with the JavaScript &lt;code&gt;setInterval()&lt;/code&gt; value.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is what we’ll build:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8_i3Yr7r--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/exs7utfb35ivew4bpnzm.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8_i3Yr7r--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/exs7utfb35ivew4bpnzm.gif" alt="Image description" width="880" height="495"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s start with the HTML code, which, in this case, is one &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; element:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;div id="slider--text"&amp;gt;&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We will populate this element later using JavaScript.&lt;/p&gt;

&lt;p&gt;Styling is optional, but, for the sake of this tutorial, I styled the slider to center align the content, both vertically and horizontally. I have also used a basic animation where I added opacity and a transform property.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.fade-in {
    animation: fade 4s infinite;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Note how my 4 second animation will match the 4000 milliseconds in the code below.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Next, we add the JavaScript "sliding" functionality by checking if the element exist, and, if it does, we create an array of strings to slide. Note that you can use HTML.&lt;/p&gt;

&lt;p&gt;Next, we create the slider by looping through the slides, and replacing the HTML inside the &lt;code&gt;#slider--text&lt;/code&gt; element with the slide content. &lt;strong&gt;That is all!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Next, we call the slider so that it runs immediately, and then we call it every 4 seconds using a &lt;code&gt;setInterval()&lt;/code&gt; function.&lt;/p&gt;

&lt;p&gt;The gist of the JavaScript code is below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const slider = () =&amp;gt; {
    document.getElementById("slider--text").innerHTML = slides[i];
    document.getElementById("slider--text").classList.add('fade-in');

    (i &amp;lt; slides.length - 1) ? i++ : i = 0;
};

setInterval(slider, 4000); // Slide every 4 seconds
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check out the &lt;a href="https://getbutterfly.com/how-to-create-a-minimal-javascript-slider-with-css-transitions/"&gt;JavaScript code for a full breakdown and a slider demo&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>programming</category>
      <category>tutorial</category>
      <category>css</category>
    </item>
    <item>
      <title>I created a draggable carousel with momentum scrolling and mobile support using Vanilla JavaScript</title>
      <dc:creator>Ciprian Popescu</dc:creator>
      <pubDate>Thu, 21 Oct 2021 11:24:27 +0000</pubDate>
      <link>https://dev.to/wolffe/i-created-a-draggable-carousel-with-momentum-scrolling-and-mobile-support-using-vanilla-javascript-17he</link>
      <guid>https://dev.to/wolffe/i-created-a-draggable-carousel-with-momentum-scrolling-and-mobile-support-using-vanilla-javascript-17he</guid>
      <description>&lt;p&gt;A few weeks ago I saw a really nice carousel on the Stripe blog. So I decided to replicate it and integrate it with WordPress. Here are my steps.&lt;/p&gt;

&lt;p&gt;See a demo on my homepage here - &lt;a href="https://getbutterfly.com/"&gt;https://getbutterfly.com/&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The WordPress Loop
&lt;/h2&gt;

&lt;p&gt;I am getting the last 10 posts in the JavaScript category, I am setting up a counter and changing background colours based on this counter.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;?php
function whiskey_carousel() {
    $args = [
        'post_status' =&amp;gt; 'publish',
        'post_type' =&amp;gt; 'post',
        'posts_per_page' =&amp;gt; 10,
        'category_name' =&amp;gt; 'javascript'
    ];

    $featuredQuery = new WP_Query($args);

    $colours = [
        '#02bcf5', '#0073e6', '#003ab9', '#635bff', '#002c59', '#09cbcb',
        '#02bcf5', '#0073e6', '#003ab9', '#635bff', '#002c59', '#09cbcb',
    ];
    $i = 0;

    $data = '&amp;lt;div class="whiskey-cards"&amp;gt;';

        if ($featuredQuery-&amp;gt;have_posts()) {
            while ($featuredQuery-&amp;gt;have_posts()) {
                $featuredQuery-&amp;gt;the_post();

                $postID = get_the_ID();
                $excerpt = html_entity_decode(wp_trim_words(get_the_excerpt(), 32));

                $data .= '&amp;lt;div class="whiskey-card" style="background-color: ' . $colours[$i] . ';"&amp;gt;
                    &amp;lt;h3&amp;gt;&amp;lt;a href="' . get_permalink($postID) . '"&amp;gt;' . get_the_title($postID) . '&amp;lt;/a&amp;gt;&amp;lt;/h3&amp;gt;
                    &amp;lt;p class="whiskey-card--content"&amp;gt;' . $excerpt . '&amp;lt;/p&amp;gt;
                    &amp;lt;p class="whiskey-card--link"&amp;gt;&amp;lt;a href="' . get_permalink($postID) . '"&amp;gt;Learn more &amp;lt;svg class="HoverArrow" width="10" height="10" viewBox="0 0 10 10" aria-hidden="true"&amp;gt;&amp;lt;g fill-rule="evenodd"&amp;gt;&amp;lt;path class="HoverArrow__linePath" d="M0 5h7"&amp;gt;&amp;lt;/path&amp;gt;&amp;lt;path class="HoverArrow__tipPath" d="M1 1l4 4-4 4"&amp;gt;&amp;lt;/path&amp;gt;&amp;lt;/g&amp;gt;&amp;lt;/svg&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
                &amp;lt;/div&amp;gt;';

                $i++;
            }
        }

    $data .= '&amp;lt;/div&amp;gt;';

    return $data;
}

add_shortcode('whiskey-carousel', 'whiskey_carousel');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The JavaScript Code
&lt;/h2&gt;

&lt;p&gt;The JavaScript features momentum scrolling (mouse wheel) and (almost) native HTML dragging behaviour. The dragging is also available to mobile devices, as the content is overflowing horizontally.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;document.addEventListener('DOMContentLoaded', () =&amp;gt; {
    if (document.querySelector('.whiskey-cards')) {
        // Slider dragging
        const slider = document.querySelector('.whiskey-cards');
        let isDown = false;
        let startX;
        let scrollLeft;

        slider.addEventListener('mousedown', (e) =&amp;gt; {
            isDown = true;
            slider.classList.add('active');
            startX = e.pageX - slider.offsetLeft;
            scrollLeft = slider.scrollLeft;
            cancelMomentumTracking();
        });

        slider.addEventListener('mouseleave', () =&amp;gt; {
            isDown = false;
            slider.classList.remove('active');
        });

        slider.addEventListener('mouseup', () =&amp;gt; {
            isDown = false;
            slider.classList.remove('active');
            beginMomentumTracking();
        });

        slider.addEventListener('mousemove', (e) =&amp;gt; {
            if (!isDown) return;
            e.preventDefault();
            const x = e.pageX - slider.offsetLeft;
            const walk = (x - startX); //scroll-fast
            var prevScrollLeft = slider.scrollLeft;
            slider.scrollLeft = scrollLeft - walk;
            velX = slider.scrollLeft - prevScrollLeft;
        });

        // Momentum 
        var velX = 0;
        var momentumID;

        slider.addEventListener('wheel', (e) =&amp;gt; {
            cancelMomentumTracking();
        });

        function beginMomentumTracking() {
            cancelMomentumTracking();
            momentumID = requestAnimationFrame(momentumLoop);
        }

        function cancelMomentumTracking() {
            cancelAnimationFrame(momentumID);
        }

        function momentumLoop() {
            slider.scrollLeft += velX * 2;
            velX *= 0.95;
            if (Math.abs(velX) &amp;gt; 0.5) {
                momentumID = requestAnimationFrame(momentumLoop);
            }
        }

        // Scroll
        const scrollContainer = document.querySelector(".whiskey-cards");

        scrollContainer.addEventListener("wheel", (evt) =&amp;gt; {
            evt.preventDefault();

            window.requestAnimationFrame(() =&amp;gt; {
                scrollContainer.scrollTo({ top: 0, left: scrollContainer.scrollLeft + (evt.deltaY * 2), behavior: "smooth" });
            });
        });
    }
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The CSS Style
&lt;/h2&gt;

&lt;p&gt;And finally, there are lots of opinionated styles below, so make sure you get what you need.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.whiskey-cards {
    display: flex;
    flex-wrap: nowrap;
    overflow-x: scroll;
    -webkit-overflow-scrolling: touch;
    -ms-overflow-style: none;
    scrollbar-width: none;

    padding: 48px 48px 0 48px;

}
.whiskey-cards::-webkit-scrollbar {
    -webkit-appearance: none;
    width: 5px;
    height: 5px;
}
.whiskey-cards::-webkit-scrollbar-thumb {
    border-radius: 0;
    background-color: rgba(0, 0, 0, .5);
    background: linear-gradient(90deg, #02bcf5, #0073e6, #003ab9, #635bff);
    box-shadow: 0 0 1px rgba(255, 255, 255, .5);
    border-radius: 16px;
    opacity: .5;
}
.whiskey-cards:hover::-webkit-scrollbar-thumb {
    opacity: 1;
}

.whiskey-card {
    display: flex;
    flex-direction: column;
    min-width: 244px;
    flex-basis: 244px;
    border-radius: 16px;
    margin: 8px;
    padding: 16px;
    box-shadow: 0 -16px 24px rgb(0 0 0 / 5%);
    color: #ffffff;

    transition: all 150ms cubic-bezier(0.215,0.61,0.355,1);
}
.whiskey-card:hover {
    background-color: #0a2540 !important;
    transform: scale(1.04) translateY(-16px);
    box-shadow: 0 -16px 24px rgb(0 0 0 / 10%);
}
.whiskey-card h3 {
    padding-top: 0;
    line-height: 1.35;
}
.whiskey-card .whiskey-card--content {
    line-height: 1.5;
    font-size: 15px;
    font-weight: 300;
}
.whiskey-card .whiskey-card--link {
    line-height: 1.5;
    font-size: 15px;
    font-weight: 700;
    opacity: .7;
    margin: auto 0 0 0;
}
.whiskey-card h3 a,
.whiskey-card .whiskey-card--link a {
    color: #ffffff;
}
.whiskey-card .whiskey-card--link a svg {
    --arrowSpacing: 5px;
    --arrowHoverTransition: 150ms cubic-bezier(0.215,0.61,0.355,1);
    --arrowHoverOffset: translateX(3px);
    --arrowTipTransform: none;
    --arrowLineOpacity: 0;
    position: relative;
    top: 1px;
    margin-left: var(--arrowSpacing);
    stroke-width: 2px;
    fill: none;
    stroke: currentColor;
}
.HoverArrow__linePath {
    opacity: var(--arrowLineOpacity);
    transition: opacity var(--hoverTransition,var(--arrowHoverTransition));
}
.HoverArrow__tipPath {
    transform: var(--arrowTipTransform);
    transition: transform var(--hoverTransition,var(--arrowHoverTransition));
}
.whiskey-card:hover .HoverArrow__linePath {
    --arrowLineOpacity: 1;
}
.whiskey-card:hover .HoverArrow__tipPath {
    --arrowTipTransform: var(--arrowHoverOffset);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I wrote about this and other &lt;a href="https://getbutterfly.com/how-to-create-a-draggable-carousel-using-vanilla-javascript/"&gt;JavaScript carousels and sliders here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>tutorial</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
