DEV Community

Optistream
Optistream

Posted on

Building Interactive Web Tools with Pure HTML/CSS/JS: Lessons from a Streaming Site

When we set out to build 7 interactive calculators for Optistream — a French streaming analytics site — we made a deliberate choice: no React, no Vue, no frameworks. Just pure HTML, CSS, and vanilla JavaScript, embedded directly into WordPress pages.

Here's what we learned.

Why No Framework?

Our calculators needed to:

  • Load instantly (no 200KB+ bundle)
  • Work inside WordPress content areas
  • Be maintainable by a small team
  • Support LiteSpeed Cache without breaking

A framework would have been overkill. These are single-purpose tools: input some numbers, get results. The DOM manipulation is minimal, the state is simple, and the logic is pure math.

The 7 Calculators We Built

We built tools for streamers to calculate their potential earnings, subscription revenue, and platform comparisons:

  1. Twitch Sub Calculator — Estimate earnings from subs at different tiers
  2. Twitch Revenue Calculator — Full revenue breakdown (subs, bits, ads, sponsors)
  3. Revenue comparison tool (Twitch vs Kick vs YouTube)
  4. Bits-to-dollars converter
  5. Stream schedule optimizer
  6. Donation goal tracker
  7. Channel growth estimator

Each one is a self-contained block of HTML/CSS/JS inside a WordPress page.

Technical Challenges

1. The wpautop Problem

WordPress automatically wraps content in <p> tags and converts double line breaks into paragraphs. This is called wpautop, and it destroys inline JavaScript.

// WordPress turns this:
if (x > 0) {
    calculate();
}

// Into this mess:
<p>if (x > 0) {</p>
<p>    calculate();</p>
<p>}</p>
Enter fullscreen mode Exit fullscreen mode

Our solutions:

  • Wrap all JS in <script> tags (wpautop skips script blocks)
  • Use a custom shortcode that disables wpautop for calculator blocks
  • Minify everything to reduce line break opportunities

2. CSS Scoping Without Shadow DOM

When your CSS lives inside a WordPress page alongside theme styles, specificity wars are real. Our approach:

/* Prefix everything with a unique calculator ID */
#calc-twitch-subs .input-group { ... }
#calc-twitch-subs .result-display { ... }
#calc-twitch-subs button.calculate-btn { ... }
Enter fullscreen mode Exit fullscreen mode

No BEM, no CSS modules — just good old ID-scoped selectors with enough specificity to win against theme defaults.

3. LiteSpeed Cache Compatibility

LiteSpeed Cache is aggressive. It caches everything, including pages with dynamic JavaScript. Our fixes:

  • All calculations happen client-side (no AJAX calls to cache-bust)
  • No cookies or session-dependent content
  • Used data-* attributes for initial values instead of server-rendered PHP
  • Added calculator pages to the LiteSpeed "Do Not Cache" list only as a last resort

4. Mobile-First Input Design

Streamers check their stats on their phones. Every calculator uses:

<input type="number" inputmode="numeric" pattern="[0-9]*" 
       min="0" step="1" placeholder="Enter subscriber count">
Enter fullscreen mode Exit fullscreen mode

The inputmode="numeric" ensures the number pad opens on mobile — a small detail that massively improves UX.

Architecture Pattern

Each calculator follows the same structure:

<div id="calc-[name]" class="optistream-calculator">
  <!-- Inputs -->
  <div class="calc-inputs">
    <label>Subscribers <input type="number" id="subs"></label>
    <label>Tier <select id="tier">...</select></label>
  </div>

  <!-- Results -->
  <div class="calc-results" id="results" style="display:none">
    <div class="result-card">
      <span class="label">Monthly Revenue</span>
      <span class="value" id="revenue">$0</span>
    </div>
  </div>

  <!-- Calculate Button -->
  <button onclick="calculate()">Calculate</button>
</div>

<style>
  #calc-[name] { /* scoped styles */ }
</style>

<script>
  (function() {
    // IIFE to avoid global scope pollution
    function calculate() {
      const subs = parseInt(document.getElementById("subs").value) || 0;
      // ... math ...
      document.getElementById("results").style.display = "block";
    }
    // Expose to onclick
    document.querySelector("#calc-[name] button").onclick = calculate;
  })();
</script>
Enter fullscreen mode Exit fullscreen mode

Key Takeaways

  1. You don't always need a framework — For focused, interactive widgets, vanilla JS is faster to load and easier to maintain
  2. WordPress is hostile to inline JS — Plan for wpautop and cache plugins from day one
  3. Scope your CSS aggressively — ID-prefix everything when living inside a CMS
  4. Mobile input UX mattersinputmode and proper type attributes make a huge difference
  5. IIFE pattern is your friend — Wrap each calculator in an IIFE to avoid variable collisions

Performance Results

  • 0 KB of framework JS loaded
  • < 5KB per calculator (HTML + CSS + JS combined)
  • LiteSpeed PageSpeed score: 98/100 on calculator pages
  • Time to Interactive: < 1s on 3G

Sometimes the best tool is no tool at all.


Built for Optistream — streaming tools and analytics for content creators.

Top comments (0)