<?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>Forem: Touseef Ibn Khaleel</title>
    <description>The latest articles on Forem by Touseef Ibn Khaleel (@txlabs).</description>
    <link>https://forem.com/txlabs</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%2F3710672%2Fcabadf1e-c922-4118-97a4-d4df177310da.jpg</url>
      <title>Forem: Touseef Ibn Khaleel</title>
      <link>https://forem.com/txlabs</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/txlabs"/>
    <language>en</language>
    <item>
      <title>Why We Ditched iFrames for Our Testimonial Widget (And Wrote 1,000 Lines of Vanilla JS Instead)</title>
      <dc:creator>Touseef Ibn Khaleel</dc:creator>
      <pubDate>Wed, 29 Apr 2026 18:27:41 +0000</pubDate>
      <link>https://forem.com/txlabs/why-we-ditched-iframes-for-our-testimonial-widget-and-wrote-1000-lines-of-vanilla-js-instead-5d3d</link>
      <guid>https://forem.com/txlabs/why-we-ditched-iframes-for-our-testimonial-widget-and-wrote-1000-lines-of-vanilla-js-instead-5d3d</guid>
      <description>&lt;p&gt;Every embeddable widget tutorial recommends an iFrame. Drop it in, it's isolated, it can't affect the host page, done. We followed that advice for the first version of &lt;a href="https://proofly.shipquick.app" rel="noopener noreferrer"&gt;Proofly&lt;/a&gt;'s Wall of Love embed. It lasted about three weeks before we tore it out.&lt;/p&gt;

&lt;p&gt;The problems weren't subtle. They showed up immediately, in the first round of user testing, on every host site that wasn't a plain white page.&lt;/p&gt;

&lt;h2&gt;
  
  
  What broke
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Height synchronization.&lt;/strong&gt; An iFrame doesn't know how tall its content is unless you tell it. Our Wall of Love renders a grid of testimonial cards — the number varies by plan, the cards vary in height based on how long the testimonial text is, and the layout reflows when the viewport changes. Keeping the iFrame height synchronized with its content required a &lt;code&gt;postMessage&lt;/code&gt; listener inside the frame, a &lt;code&gt;ResizeObserver&lt;/code&gt; watching the content, and a handler outside the frame that updated the &lt;code&gt;height&lt;/code&gt; style in response. This worked until it didn't — race conditions on initial load, missed resize events on certain mobile browsers, and a 300ms flash of the wrong height on every page load.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scroll isolation on mobile.&lt;/strong&gt; Touch-scroll on iOS inside an iFrame is famously broken. Depending on how the iFrame is sized and positioned, the user either scrolls the frame content, scrolls the parent page, or both fight each other in a way that makes the section feel stuck. We tried &lt;code&gt;-webkit-overflow-scrolling: touch&lt;/code&gt; and various &lt;code&gt;overflow&lt;/code&gt; combinations. Nothing was clean.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Background color bleed.&lt;/strong&gt; iFrames have a default white background. If the host page section has a dark or colored background, the iFrame box shows as a white rectangle until the content loads, then snaps to transparent (if you've set &lt;code&gt;background: transparent&lt;/code&gt; on the frame body). The flash is ugly. More importantly, some host configurations — Webflow, certain WordPress themes — override the iFrame background regardless of what we set inside it.&lt;/p&gt;

&lt;p&gt;Any one of these would have been acceptable to ship around. All three together meant the embed looked bad in ways we couldn't fully control.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we built instead
&lt;/h2&gt;

&lt;p&gt;The replacement is a script tag that, when loaded, injects the widget directly into the host page's DOM. No iFrame. The host page just needs a container div with a &lt;code&gt;data-proofly-wall&lt;/code&gt; attribute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;data-proofly-wall=&lt;/span&gt;&lt;span class="s"&gt;"your-embed-slug"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://proofly.shipquick.app/embed.js"&lt;/span&gt; &lt;span class="na"&gt;async&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The embed script does the following on load:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;containers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-proofly-wall]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;containers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;container&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;slug&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data-proofly-wall&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nf"&gt;initWall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;})();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;initWall&lt;/code&gt; fetches the wall data from our API using the &lt;code&gt;embedSlug&lt;/code&gt; as the only credential, builds the DOM, injects the styles, and appends everything to the container. The slug is a random 16-character string — unguessable, not rotatable, and intentionally not tied to an auth session. If someone steals your embed slug, the worst outcome is they display your testimonials on their site, which is not a meaningful threat model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scoping CSS without Shadow DOM
&lt;/h2&gt;

&lt;p&gt;The first thing you reach for when you need style isolation is Shadow DOM. We looked at it. The browser support is fine in 2026. The problem is that Shadow DOM makes it hard to let host page fonts inherit into the widget, which matters for design-conscious customers who want the testimonial cards to match their site's typography.&lt;/p&gt;

&lt;p&gt;Instead, every CSS rule in the embed is prefixed with &lt;code&gt;.proofly-root&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.proofly-root&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;inherit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;box-sizing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;border-box&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.proofly-root&lt;/span&gt; &lt;span class="nc"&gt;.wall-grid&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;grid-template-columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auto-fill&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;minmax&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;280px&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="py"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.proofly-root&lt;/span&gt; &lt;span class="nc"&gt;.testimonial-card&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--proofly-card-bg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;#ffffff&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--proofly-radius&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;8px&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1.25rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;CSS custom properties (&lt;code&gt;--proofly-card-bg&lt;/code&gt;, &lt;code&gt;--proofly-radius&lt;/code&gt;, etc.) let customers theme the widget from their host page without us needing to expose a configuration API. They set variables on the container and the widget inherits them.&lt;/p&gt;

&lt;p&gt;The inject-once check is simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;injectStyles&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;proofly-embed-styles&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;style&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;proofly-embed-styles&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;EMBED_CSS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// minified at build time&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;head&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The slug as the security model
&lt;/h2&gt;

&lt;p&gt;The Wall of Love is intentionally public — the whole point is to display approved testimonials to anyone who visits the host page. There's no sensitive data in the embed response. The &lt;code&gt;embedSlug&lt;/code&gt; isn't a secret key; it's more like a stable, opaque identifier.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fetchWallData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`https://proofly.shipquick/app/embed/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The API endpoint for embed data is public and rate-limited but not authenticated. It returns only the approved testimonials for that wall — no user data, no account information. If a customer wants to take their wall private (hide it without deleting it), they can disable the embed from their dashboard, which stops the endpoint from returning data for that slug.&lt;/p&gt;

&lt;h2&gt;
  
  
  The ~1,000 lines
&lt;/h2&gt;

&lt;p&gt;The file is currently 1,140 lines, including comments. The bulk of it is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The card renderer (~200 lines): handles text cards, video thumbnail cards, audio cards, and star ratings&lt;/li&gt;
&lt;li&gt;The grid layout manager (~150 lines): responsive column logic, masonry-style height balancing, lazy loading&lt;/li&gt;
&lt;li&gt;The CSS injection (~180 lines): the full stylesheet, minified at build time&lt;/li&gt;
&lt;li&gt;Video modal (~200 lines): click-to-expand video playback, keyboard close, scroll lock while open&lt;/li&gt;
&lt;li&gt;Utilities (~100 lines): debounce, intersection observer for lazy loading, DOM helpers&lt;/li&gt;
&lt;li&gt;Initialization and public API (~150 lines): &lt;code&gt;initWall&lt;/code&gt;, the &lt;code&gt;data-&lt;/code&gt; attribute scanner, the &lt;code&gt;window.Proofly&lt;/code&gt; object
It's vanilla JS — no bundler, no framework dependencies. This was a deliberate choice. The embed runs on every customer's host site, which might be a React app, a Webflow site, a plain HTML page, or a WordPress theme from 2019. Shipping a dependency graph into that environment is asking for conflicts. Vanilla JS with no imports is the safest thing to put in someone else's page.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What the iFrame would have gotten right
&lt;/h2&gt;

&lt;p&gt;I want to be fair to the alternative. An iFrame would have handled:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Complete CSS isolation.&lt;/strong&gt; The host page's resets and overrides can't touch iframe content.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JavaScript isolation.&lt;/strong&gt; A bug in our widget code can't throw an error that breaks the host page. With our inline script, a runtime error in &lt;code&gt;initWall&lt;/code&gt; could theoretically interrupt a host page script that runs after ours.
The JavaScript isolation point is real and we take it seriously. The embed script has a try/catch around the entire initialization:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;initWall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[Proofly] Widget failed to initialize:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// silent failure — the container just stays empty&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A failed widget initialization shows an empty div, not a broken page. That's the best we can do without the sandbox boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Whether it was worth it
&lt;/h2&gt;

&lt;p&gt;For our use case: yes. The height sync and mobile scroll problems were visible on every non-trivial host page. The background flash was the kind of thing that makes a product look unpolished in a demo. None of those go away with an iFrame without significant complexity.&lt;/p&gt;

&lt;p&gt;The tradeoff is 1,140 lines of code we own and have to maintain, plus occasional CSS conflict reports from customers on unusual host configurations. We'd rather have the better default experience and handle edge cases than have the worse default experience and blame the iFrame.&lt;/p&gt;

&lt;p&gt;If you're building an embeddable widget and your content is truly static — fixed height, no user interaction, no dark mode — an iFrame is still the answer. For anything dynamic, the direct DOM approach is worth the extra work.&lt;/p&gt;

</description>
      <category>software</category>
      <category>javascript</category>
      <category>startup</category>
      <category>webdev</category>
    </item>
    <item>
      <title>From Lag Spike to Lightning Fast: The Engineering Behind Gaze Guard's Massive Performance Overhaul</title>
      <dc:creator>Touseef Ibn Khaleel</dc:creator>
      <pubDate>Thu, 15 Jan 2026 17:47:19 +0000</pubDate>
      <link>https://forem.com/txlabs/from-lag-spike-to-lightning-fast-the-engineering-behind-gaze-guards-massive-performance-overhaul-2370</link>
      <guid>https://forem.com/txlabs/from-lag-spike-to-lightning-fast-the-engineering-behind-gaze-guards-massive-performance-overhaul-2370</guid>
      <description>&lt;p&gt;As developers, we know the pain of a slow browser extension. The moment a tool designed to help you starts hogging resources, it becomes a liability. That was the challenge we faced with &lt;strong&gt;Gaze Guard&lt;/strong&gt;, our image classification extension. The initial, naive implementation — a simple "scan everything all the time" approach — was functional but created noticeable performance bottlenecks on image-heavy sites.&lt;/p&gt;

&lt;p&gt;We decided to go back to the drawing board and completely re-engineer the core scanning logic. The goal was simple: deliver powerful, real-time image classification with near-zero impact on the user's browsing experience. This article breaks down the eight key architectural changes that transformed Gaze Guard from a resource hog into a performance powerhouse.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Viewport-Based Scanning: The IntersectionObserver Revolution
&lt;/h2&gt;

&lt;p&gt;The most significant change was moving away from classifying every image on the page immediately. This led to massive, synchronous classification spikes on page load.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Fix:&lt;/strong&gt; We adopted a viewport-based scanning strategy using the &lt;code&gt;IntersectionObserver&lt;/code&gt; API.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Lazy Discovery:&lt;/strong&gt; Images are only processed when they come near the viewport, using a generous &lt;code&gt;rootMargin&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Minimal Workload:&lt;/strong&gt; As soon as an image is handled (blurred or cleared), it is immediately unobserved. This keeps the observer's workload small and prevents the system from being overwhelmed by hundreds or thousands of off-screen elements.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This change alone eliminated the "big synchronous classification spikes" that plagued the previous version, ensuring a smooth initial page load.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Event-Driven DOM Updates: Replacing Polling with MutationObserver
&lt;/h2&gt;

&lt;p&gt;The old architecture relied on "aggressive interval scanning" — a fixed timer that constantly woke up to check for new images. This is a classic anti-pattern for performance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Fix:&lt;/strong&gt; We replaced polling with an event-driven approach using &lt;code&gt;MutationObserver&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Initial Scan:&lt;/strong&gt; A single, non-recurring &lt;code&gt;scanImagesOnce()&lt;/code&gt; handles the initial page content.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Dynamic Content:&lt;/strong&gt; For sites with infinite scroll or dynamic feeds, a &lt;code&gt;MutationObserver&lt;/code&gt; watches for added nodes. It only scans the subtrees of the newly added elements.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Throttling:&lt;/strong&gt; To handle bursts of DOM changes, the observer's callback is debounced by 100ms, collapsing multiple mutations into a single, efficient scan.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result is that work only happens when the page actually changes, eliminating the constant, unnecessary overhead of fixed timers.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Batching and Throttling the ML Pipeline
&lt;/h2&gt;

&lt;p&gt;Even with smart discovery, the machine learning classification process is the heaviest part of the workload. Running a large batch of classifications synchronously will inevitably freeze the UI.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Fix:&lt;/strong&gt; We introduced batching and cooperative scheduling for the ML pipeline.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Batch Processing:&lt;/strong&gt; Images waiting for classification are put into an &lt;code&gt;analysisQueue&lt;/code&gt;. The &lt;code&gt;processQueue()&lt;/code&gt; function handles them in small batches (e.g., &lt;code&gt;BATCH_SIZE = 5&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Yielding to the Browser:&lt;/strong&gt; Crucially, between batches, the process yields to the browser using a tiny &lt;code&gt;setTimeout(..., 5)&lt;/code&gt; and &lt;code&gt;tf.nextFrame()&lt;/code&gt;. This allows layout and paint operations to run, keeping the UI responsive and preventing jank.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Timeouts:&lt;/strong&gt; Each classification is wrapped in a timeout (&lt;code&gt;TIMEOUT_MS = 3000&lt;/code&gt;). This prevents "stuck" images (due to slow network or bad CORS) from blocking the entire queue.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4. Caching Classification Results to Avoid Rework
&lt;/h2&gt;

&lt;p&gt;Why classify the same image twice? On sites with repeated elements, or during back/forward navigation, re-running the ML model is pure waste.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Fix:&lt;/strong&gt; A robust, multi-layered verdict cache.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;In-Memory Cache:&lt;/strong&gt; &lt;code&gt;srcVerdicts&lt;/code&gt; provides fast, O(1) lookups for the current session.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Persistent Cache:&lt;/strong&gt; &lt;code&gt;persistentVerdicts&lt;/code&gt; stores results in &lt;code&gt;chrome.storage.local&lt;/code&gt;, ensuring that if a user navigates away and comes back, or even restarts the browser, known images are instantly recognized.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When an image is spotted, the extension checks the cache first. If a verdict exists, it immediately applies the blur or clears the image, skipping the entire classification pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Smarter and Cheaper DOM Operations (Micro-Optimizations)
&lt;/h2&gt;

&lt;p&gt;Performance often comes down to avoiding expensive browser operations, particularly those that trigger layout thrashing (reflows).&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Optimization&lt;/th&gt;
&lt;th&gt;Old Approach (Expensive)&lt;/th&gt;
&lt;th&gt;New Approach (Efficient)&lt;/th&gt;
&lt;th&gt;Why it Matters&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Image Dimensions&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Accessing &lt;code&gt;element.width/height&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Using &lt;code&gt;naturalWidth / naturalHeight&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Accessing &lt;code&gt;element.width/height&lt;/code&gt; can force a synchronous layout/reflow.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Background Images&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Using &lt;code&gt;getComputedStyle&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Using only inline &lt;code&gt;element.style.backgroundImage&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;getComputedStyle&lt;/code&gt; can also trigger reflows, while inline style access is cheap.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Irrelevant Content&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Scanning all images&lt;/td&gt;
&lt;td&gt;Skipping images smaller than 16x16px&lt;/td&gt;
&lt;td&gt;Avoids wasting cycles on icons, tracking pixels, and other irrelevant elements.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Vector Graphics&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Feeding SVGs to the raster classifier&lt;/td&gt;
&lt;td&gt;Short-circuiting SVGs as "safe"&lt;/td&gt;
&lt;td&gt;Prevents feeding vector graphics into a model designed for raster images, saving classification time.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;DOM Traversal&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;querySelectorAll('*')&lt;/code&gt; deep scan&lt;/td&gt;
&lt;td&gt;Targeted &lt;code&gt;findAllImages()&lt;/code&gt; strategy&lt;/td&gt;
&lt;td&gt;The previous deep scan was explicitly marked as "too expensive" and replaced with a targeted search for &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; tags and selective iframe handling.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  6. More Efficient Model and Backend Usage
&lt;/h2&gt;

&lt;p&gt;The machine learning model itself needs to be managed efficiently.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Model Caching:&lt;/strong&gt; The NSFWJS model is loaded once and cached in memory, preventing repeated heavy initializations.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;TensorFlow Backend:&lt;/strong&gt; &lt;code&gt;ensureTfReady()&lt;/code&gt; is used to enable TensorFlow.js production mode and allow it to select the best available backend (WebGL, WASM, etc.), rather than forcing a slower CPU-based backend.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Clean Cleanup:&lt;/strong&gt; Dangerous blanket cleanup calls like &lt;code&gt;tf.disposeVariables()&lt;/code&gt; (which could wipe model weights) were removed, ensuring the model stays "warm" and ready for immediate use.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  7. Background Fetching for Problematic Images
&lt;/h2&gt;

&lt;p&gt;Some images, particularly those with strict CORS policies or special hosting, can fail to load in the content script, leading to repeated, failing attempts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Fix:&lt;/strong&gt; We defer to the background service worker (&lt;code&gt;background.js&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;For images that fail to load directly, the content script sends a message to the background worker, which fetches the image and returns a data URI. This offloads network details and retries to Chrome's more robust extension architecture, keeping the main content script simpler and less prone to getting stuck.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Turning Off Heavy Work on Unsupported Contexts
&lt;/h2&gt;

&lt;p&gt;Finally, we prevent the extension from running in contexts where it can't or shouldn't work, such as PDF viewers.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Robust Detection:&lt;/strong&gt; A comprehensive &lt;code&gt;isPdfEnvironment()&lt;/code&gt; check detects PDF viewers based on URLs, content types, and embedded tags.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Immediate Stop:&lt;/strong&gt; When a PDF is detected, &lt;code&gt;stopAll()&lt;/code&gt; is called immediately. This cancels all observers, clears queues, and removes any existing blur markers, eliminating all overhead in a context where image classification is not useful.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Conclusion: Performance as a Feature
&lt;/h2&gt;

&lt;p&gt;The performance improvements in Gaze Guard are not just a nice-to-have; they are a core feature. By applying principles of lazy loading, event-driven architecture, cooperative scheduling, and aggressive caching, we've managed to integrate a heavy machine learning workload into the browser without compromising the user experience.&lt;/p&gt;

&lt;p&gt;This journey highlights that in extension development, optimizing for the DOM and the browser's event loop is just as critical as optimizing the core algorithm. We hope this deep dive provides valuable insights for anyone building performance-sensitive tools for the web.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>tutorial</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Gaze Guard: Your Privacy-First AI Shield Against Unwanted Online Content</title>
      <dc:creator>Touseef Ibn Khaleel</dc:creator>
      <pubDate>Wed, 14 Jan 2026 11:00:51 +0000</pubDate>
      <link>https://forem.com/txlabs/gaze-guard-your-privacy-first-ai-shield-against-unwanted-online-content-2o09</link>
      <guid>https://forem.com/txlabs/gaze-guard-your-privacy-first-ai-shield-against-unwanted-online-content-2o09</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2loj5nip9ex37dgkkdkc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2loj5nip9ex37dgkkdkc.png" alt=" " width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In today's digital landscape, the internet is an indispensable tool, but it often comes with the unwelcome surprise of inappropriate or distracting imagery. Whether you're browsing for work, studying, or simply enjoying your leisure time, encountering explicit or sensitive content can be jarring and disruptive. Traditional content filters often fall short, either by compromising your privacy or failing to keep up with the dynamic nature of the web. This is where Gaze Guard, a revolutionary Chrome extension, steps in to redefine your online experience with intelligent, privacy-focused content moderation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Traditional Content Filters Miss the Mark
&lt;/h2&gt;

&lt;p&gt;Many existing content filtering solutions operate by sending your browsing data, including images, to external servers for analysis. While this approach can block some content, it introduces significant drawbacks:&lt;/p&gt;

&lt;p&gt;• &lt;strong&gt;Privacy Compromise:&lt;/strong&gt; Your images and browsing habits are transmitted to third parties, raising legitimate concerns about data privacy and security.&lt;/p&gt;

&lt;p&gt;• &lt;strong&gt;Performance Lag:&lt;/strong&gt; The constant back-and-forth communication with remote servers can lead to noticeable delays, slowing down your browsing and making real-time content moderation less effective.&lt;/p&gt;

&lt;p&gt;• &lt;strong&gt;Outdated Blacklists:&lt;/strong&gt; Relying on static blacklists means these filters often fail to catch newly emerging or dynamically generated inappropriate content, leaving gaps in your protection.&lt;/p&gt;

&lt;p&gt;Gaze Guard was engineered from the ground up to overcome these limitations, offering a superior, user-centric solution.&lt;/p&gt;

&lt;p&gt;The Power of On-Device AI: Unmatched Privacy and Speed&lt;/p&gt;

&lt;p&gt;The true innovation behind Gaze Guard lies in its groundbreaking use of on-device AI. This means all image analysis and content detection happen directly within your Chrome browser, eliminating the need to send any data to external servers. This commitment to local processing ensures your privacy remains absolute while delivering lightning-fast performance.&lt;/p&gt;

&lt;p&gt;At its core, Gaze Guard leverages a sophisticated tech stack:&lt;/p&gt;

&lt;p&gt;• &lt;strong&gt;TensorFlow.js:&lt;/strong&gt; The robust open-source library that enables machine learning models to run efficiently within the browser environment.&lt;/p&gt;

&lt;p&gt;• &lt;strong&gt;NSFWJS:&lt;/strong&gt; A specialized, pre-trained model built on TensorFlow.js, expertly designed for accurate detection of Not Safe For Work (NSFW) content across various categories.&lt;/p&gt;

&lt;p&gt;• &lt;strong&gt;MobileNet V2:&lt;/strong&gt; A lightweight yet powerful neural network model, optimized for performance on client-side devices. This ensures Gaze Guard can classify images in real-time without bogging down your system.&lt;/p&gt;

&lt;p&gt;This powerful combination allows Gaze Guard to intelligently identify and blur inappropriate images as you browse, all without ever compromising your data or slowing down your internet speed. It's a genuine privacy-focused AI content filter that puts you in control.&lt;/p&gt;

&lt;p&gt;Seamless Integration and Intuitive Control&lt;/p&gt;

&lt;p&gt;Gaze Guard is designed for effortless integration into your daily browsing routine. Once installed, it works silently in the background, constantly scanning for potentially sensitive content. However, you're always in command:&lt;/p&gt;

&lt;p&gt;• &lt;strong&gt;Real-time Blurring:&lt;/strong&gt; Images identified as inappropriate are instantly blurred, providing an immediate visual shield.&lt;/p&gt;

&lt;p&gt;• &lt;strong&gt;Customizable Sensitivity:&lt;/strong&gt; Adjust the detection threshold to match your personal comfort level, making the blurring more or less aggressive as needed.&lt;/p&gt;

&lt;p&gt;• &lt;strong&gt;Category Selection:&lt;/strong&gt; Choose which specific categories of content (e.g., Porn, Hentai, Sexy) you wish to have blurred.&lt;/p&gt;

&lt;p&gt;• &lt;strong&gt;Click-to-Reveal:&lt;/strong&gt; Accidentally blurred an image you wanted to see? Simply click on it to temporarily reveal the content, giving you full control.&lt;/p&gt;

&lt;p&gt;Under the hood, Gaze Guard employs smart optimizations like MutationObserver to detect dynamically loaded images and IntersectionObserver to only process images currently in your viewport. This ensures comprehensive coverage without wasting resources on unseen content.&lt;/p&gt;

&lt;h2&gt;
  
  
  Open Source: Transparency You Can Trust
&lt;/h2&gt;

&lt;p&gt;In an age where trust is paramount, Gaze Guard stands out with its commitment to transparency. The entire project is open source, meaning its code is publicly available for anyone to inspect and verify. This not only fosters community collaboration but also provides undeniable proof of its privacy claims – no hidden data collection, just pure, on-device protection.&lt;/p&gt;

&lt;p&gt;Explore the codebase and contribute to its development on GitHub: &lt;a href="https://github.com/realtouseef/gaze-guard" rel="noopener noreferrer"&gt;https://github.com/realtouseef/gaze-guard&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Elevate Your Browsing Experience Today&lt;/p&gt;

&lt;p&gt;Say goodbye to unexpected distractions and reclaim your online peace of mind. Gaze Guard offers a robust, intelligent, and completely private solution to content moderation. It's more than just an extension; it's your personal guardian, ensuring a safer and more focused browsing environment.&lt;/p&gt;

&lt;p&gt;Ready to experience the future of content filtering? Add Gaze Guard to your Chrome browser today:&lt;/p&gt;

&lt;p&gt;Install Gaze Guard from the Chrome Web Store&lt;/p&gt;

&lt;p&gt;Transform your browsing with the power of on-device AI – discreet, fast, and utterly private. Get Gaze Guard and browse with confidence.&lt;/p&gt;

&lt;p&gt;Keywords: Chrome extension, AI content filter, privacy-focused, NSFW blocker, on-device AI, blur inappropriate images, content moderation, TensorFlow.js, NSFWJS, MobileNet V2, web privacy, safe browsing.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>productivity</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Gaze Guard: How On-Device AI is Revolutionizing Content Moderation in Your Browser</title>
      <dc:creator>Touseef Ibn Khaleel</dc:creator>
      <pubDate>Wed, 14 Jan 2026 10:24:29 +0000</pubDate>
      <link>https://forem.com/txlabs/gaze-guard-how-on-device-ai-is-revolutionizing-content-moderation-in-your-browser-584i</link>
      <guid>https://forem.com/txlabs/gaze-guard-how-on-device-ai-is-revolutionizing-content-moderation-in-your-browser-584i</guid>
      <description>&lt;p&gt;In an increasingly digital world, navigating the vast ocean of online content can sometimes feel like walking through a minefield. Unwanted or inappropriate imagery can pop up unexpectedly, disrupting workflows or causing discomfort. While many content filters exist, they often rely on server-side processing, raising privacy concerns and introducing latency. This is where &lt;a href="https://chromewebstore.google.com/detail/gaze-guard-%E2%80%94-blur-inappro/ojbcgfmeecpilopnollmbiioainmdjeb?authuser=3&amp;amp;hl=en" rel="noopener noreferrer"&gt;Gaze Guard&lt;/a&gt;, an innovative Chrome extension, steps in, offering a refreshing, privacy-first approach to content moderation right within your browser.&lt;/p&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/realtouseef/gaze-guard" rel="noopener noreferrer"&gt;https://github.com/realtouseef/gaze-guard&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem with Traditional Content Filters
&lt;/h2&gt;

&lt;p&gt;Before diving into &lt;a href="https://chromewebstore.google.com/detail/gaze-guard-%E2%80%94-blur-inappro/ojbcgfmeecpilopnollmbiioainmdjeb?authuser=3&amp;amp;hl=en" rel="noopener noreferrer"&gt;Gaze Guard's&lt;/a&gt; solution, it's worth considering the limitations of conventional content filtering. Most solutions involve sending your browsing data, including images, to external servers for analysis. This process comes with inherent drawbacks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Privacy Concerns:&lt;/strong&gt; Transmitting images to third-party servers means your browsing habits are no longer entirely private. For many, this is a significant compromise.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Latency and Performance:&lt;/strong&gt; The round trip to a server for analysis introduces delays, leading to a noticeable lag in content filtering, especially on dynamic web pages.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Dependence on Blacklists:&lt;/strong&gt; Many filters rely on pre-defined blacklists of URLs. This approach struggles with new or dynamically generated content, often failing to catch emerging inappropriate material.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://chromewebstore.google.com/detail/gaze-guard-%E2%80%94-blur-inappro/ojbcgfmeecpilopnollmbiioainmdjeb?authuser=3&amp;amp;hl=en" rel="noopener noreferrer"&gt;Gaze Guard&lt;/a&gt; was conceived to address these issues, providing a robust, real-time, and entirely private content filtering experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Brains Behind the Blur: On-Device AI
&lt;/h2&gt;

&lt;p&gt;The core innovation of &lt;a href="https://chromewebstore.google.com/detail/gaze-guard-%E2%80%94-blur-inappro/ojbcgfmeecpilopnollmbiioainmdjeb?authuser=3&amp;amp;hl=en" rel="noopener noreferrer"&gt;Gaze Guard&lt;/a&gt; lies in its commitment to on-device AI. Instead of sending your data elsewhere, the heavy lifting of image analysis happens directly within your Chrome browser. This is made possible by a powerful combination of technologies:&lt;/p&gt;

&lt;p&gt;• &lt;strong&gt;TensorFlow.js:&lt;/strong&gt; This library allows machine learning models to run entirely in the browser, leveraging the user's local computing power.&lt;/p&gt;

&lt;p&gt;• &lt;strong&gt;NSFWJS:&lt;/strong&gt; Built on top of TensorFlow.js, NSFWJS is a specialized library designed for detecting Not Safe For Work (NSFW) content.&lt;/p&gt;

&lt;p&gt;• &lt;strong&gt;MobileNet V2:&lt;/strong&gt; &lt;a href="https://chromewebstore.google.com/detail/gaze-guard-%E2%80%94-blur-inappro/ojbcgfmeecpilopnollmbiioainmdjeb?authuser=3&amp;amp;hl=en" rel="noopener noreferrer"&gt;Gaze Guard&lt;/a&gt; utilizes a lightweight yet effective pre-trained model called MobileNet V2. Optimized for edge devices, it strikes an excellent balance between classification accuracy and computational efficiency.&lt;/p&gt;

&lt;p&gt;This trio enables &lt;a href="https://chromewebstore.google.com/detail/gaze-guard-%E2%80%94-blur-inappro/ojbcgfmeecpilopnollmbiioainmdjeb?authuser=3&amp;amp;hl=en" rel="noopener noreferrer"&gt;Gaze Guard&lt;/a&gt; to perform real-time image classification without ever sending your images to an external server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deconstructing Gaze Guard: An Architectural Deep Dive
&lt;/h2&gt;

&lt;p&gt;To truly appreciate &lt;a href="https://chromewebstore.google.com/detail/gaze-guard-%E2%80%94-blur-inappro/ojbcgfmeecpilopnollmbiioainmdjeb?authuser=3&amp;amp;hl=en" rel="noopener noreferrer"&gt;Gaze Guard&lt;/a&gt;, let's examine its architecture, as revealed by its open-source GitHub repository. The extension is built upon the modern Chrome Extension Manifest V3, ensuring it adheres to the latest security and performance standards.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Manifest: The Extension's Blueprint
&lt;/h2&gt;

&lt;p&gt;The manifest.json file is the heart of the extension, defining its properties and permissions. Gaze Guard's manifest reveals several key aspects:&lt;/p&gt;

&lt;p&gt;• &lt;strong&gt;Permissions:&lt;/strong&gt; It requests activeTab and storage to save user settings locally. Crucially, it does not request broad access to your browsing history.&lt;/p&gt;

&lt;p&gt;• &lt;strong&gt;Content Scripts:&lt;/strong&gt; The manifest injects &lt;code&gt;libs/nsfwjs.min.js&lt;/code&gt;, &lt;code&gt;tf.min.js&lt;/code&gt;, &lt;code&gt;styles.css&lt;/code&gt;, and &lt;code&gt;content.js&lt;/code&gt; into every web page. This means the AI model and the core logic are loaded directly into the context of the page you're viewing.&lt;/p&gt;

&lt;p&gt;• &lt;strong&gt;Background Service Worker&lt;/strong&gt;: &lt;code&gt;background.js&lt;/code&gt; runs in the background, handling events and managing the extension's state.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Content Script: The On-Page Sentinel (content.js)
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;content.js&lt;/code&gt; file is the most critical component, responsible for the real-time detection and blurring of images. Its sophisticated logic ensures efficient content moderation:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Model Loading:&lt;/strong&gt; Upon page load, &lt;code&gt;content.js&lt;/code&gt; initializes and loads the NSFWJS model. Since the model files are bundled with the extension, this process is fast and doesn't require external network requests.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Image Detection:&lt;/strong&gt; The script scans the DOM for &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; tags and background images. To handle dynamic content like infinite scrolling feeds, it employs a MutationObserver to detect new images as they are added.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Performance Optimization with IntersectionObserver:&lt;/strong&gt; Gaze Guard uses an IntersectionObserver to only process images that are currently within the user's viewport. This significantly reduces unnecessary processing, leading to a smoother browsing experience.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Classification Logic:&lt;/strong&gt; Once an image is detected in the viewport, it's converted into a tensor and analyzed by the model. If the probability for categories like &lt;strong&gt;Porn&lt;/strong&gt;, &lt;strong&gt;Hentai&lt;/strong&gt;, or &lt;strong&gt;Sexy&lt;/strong&gt; exceeds the user-defined threshold (defaulting to 50%), the image is flagged.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Censoring Action:&lt;/strong&gt; For flagged images, Gaze Guard applies a CSS class, gaze-guard-blur, which visually blurs the content. A thoughtful feature allows users to click on a blurred image to temporarily reveal it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Intelligent Caching:&lt;/strong&gt; To avoid redundant work, Gaze Guard stores classification "verdicts" in chrome.storage.local. If the same image URL is encountered again, the extension quickly retrieves its previous verdict without re-running the AI model.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Background Script: The Silent Manager (background.js)
&lt;/h2&gt;

&lt;p&gt;While &lt;code&gt;content.js&lt;/code&gt; is on the front lines, &lt;code&gt;background.js&lt;/code&gt; operates behind the scenes. Its primary responsibilities include:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Settings Management:&lt;/strong&gt; It acts as the central hub for storing and retrieving user preferences, such as sensitivity and selected categories.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;CORS Bypass:&lt;/strong&gt; Gaze Guard includes a mechanism in &lt;code&gt;background.js&lt;/code&gt; to fetch images that might otherwise be blocked by Cross-Origin Resource Sharing (CORS) policies, converting them into Data URLs for safe processing by the content script.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Unseen Benefits: Privacy and Performance
&lt;/h2&gt;

&lt;p&gt;Gaze Guard's design prioritizes the user in several ways:&lt;/p&gt;

&lt;p&gt;• &lt;strong&gt;Uncompromised Privacy:&lt;/strong&gt; Your images and browsing data never leave your browser. There are no external servers to trust and no third parties analyzing your content.&lt;/p&gt;

&lt;p&gt;• &lt;strong&gt;Blazing Fast Performance:&lt;/strong&gt; By eliminating network latency and optimizing image processing with IntersectionObserver and caching, Gaze Guard delivers near real-time moderation.&lt;/p&gt;

&lt;p&gt;• &lt;strong&gt;Always Up-to-Date:&lt;/strong&gt; Unlike blacklist-based filters, the AI model can adapt to new content patterns, making it more resilient to novel forms of inappropriate imagery.&lt;/p&gt;

&lt;p&gt;• &lt;strong&gt;Transparency:&lt;/strong&gt; Being open-source, anyone can inspect the code and verify its privacy claims, fostering trust and community involvement.&lt;/p&gt;

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

&lt;p&gt;Gaze Guard stands out as a pioneering example of how on-device AI can empower users with greater control over their digital environment while upholding the highest standards of privacy. By bringing sophisticated machine learning directly into the browser, it offers a powerful, efficient, and transparent solution to the pervasive challenge of unwanted content. It's more than just an extension; it's a personal guardian for your gaze, ensuring a safer and more comfortable browsing experience without compromising your data. &lt;/p&gt;

&lt;p&gt;In an era where digital privacy is paramount, Gaze Guard offers a glimpse into a future where intelligent tools work for us, on our terms, right where we need them most.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>opensource</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
