Site Performance Optimization Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Improve Lighthouse performance scores (mobile 51 -> ~70-75, desktop 78 -> ~85-90) by deferring render-blocking scripts, removing source maps from deployment, subsetting Google Fonts, and lazy-loading below-fold images.

Architecture: All changes are to HAML templates and git config. No SCSS, JS source, build pipeline, or infrastructure changes. The Gulp pipeline and Jenkins deploy are untouched.

Tech Stack: Awestruct 0.5 (HAML templates), Git

Spec: docs/superpowers/specs/2026-04-08-site-performance-optimization-design.md


Task 1: Defer jQuery and functions.min.js in base layout

Files: - Modify: _layouts/base.html.haml:224 (jQuery script tag) - Modify: _layouts/base.html.haml:229 (functions.min.js script tag)

In _layouts/base.html.haml line 224, change:

haml %script{:src => "https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"}

to:

haml %script{:src => "https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js", :defer => "true"}

In _layouts/base.html.haml line 229, change:

haml %script{:src => "/js/functions.min.js"}

to:

haml %script{:src => "/js/functions.min.js", :defer => "true"}

bash git add _layouts/base.html.haml git commit -m "perf: defer jQuery and functions.min.js in base layout"


Task 2: Defer jQuery in home layout

Files: - Modify: _layouts/home.html.haml:61 (jQuery script tag)

In _layouts/home.html.haml line 61, change:

haml %script{:src => "https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"}

to:

haml %script{:src => "https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js", :defer => "true"}

Note: functions.min.js (line 68) and jquery.matchHeight-min.js (line 69) already have :defer => "true" in this layout.

bash git add _layouts/home.html.haml git commit -m "perf: defer jQuery in home layout"


Task 3: Fix inline jQuery-dependent scripts to use DOMContentLoaded

With jQuery deferred, inline <script> blocks that call $() will break because inline scripts execute immediately when parsed, before deferred scripts run. Wrapping them in DOMContentLoaded works because deferred scripts execute before DOMContentLoaded fires.

Files: - Modify: _layouts/home.html.haml:78-82 (matchHeight inline script) - Modify: _layouts/base.html.haml:234-235 (page.javascript dynamic block) - Modify: _layouts/base.html.haml:245-265 (customers page mixItUp inline script)

In _layouts/home.html.haml lines 78-82, change:

haml :javascript // Match Height Function for Community page $(function() { $('.equal-testimonials').matchHeight(); });

to:

haml :javascript document.addEventListener('DOMContentLoaded', function() { $('.equal-testimonials').matchHeight(); });

In _layouts/base.html.haml lines 234-235, change:

haml :javascript #{page.javascript}

to:

haml -if page.javascript :javascript document.addEventListener('DOMContentLoaded', function() { #{page.javascript} });

This handles _layouts/landing-event.html.haml which sets javascript: $(document).ready(function(){...}) in front matter. The inner $(document).ready() becomes redundant but harmless inside DOMContentLoaded.

In _layouts/base.html.haml lines 245-265, change:

```haml :javascript // Function to get query parameter by name function getParameterByName(name) { name = name.replace(/[[]/, "\[").replace(/[]]/, "\]"); var regex = new RegExp("[\?&]" + name + "=([^&#]*)"), results = regex.exec(location.search); return results === null ? "" : decodeURIComponent(results[1].replace(/+/g, " ")); }

    // mixitup
    $(function() {
        var filterOnLoad = getParameterByName('filter') ? "." + getParameterByName('filter') : "all";
        $('.customer-logos').mixItUp({
            load: {
                filter: filterOnLoad
            }
        });
        if (filterOnLoad == '.case-study') {
            $(".case-studies-accordion .accordion-content").toggleClass("active").removeClass("start-closed");
        }
    }); ```

to:

```haml :javascript // Function to get query parameter by name function getParameterByName(name) { name = name.replace(/[[]/, "\[").replace(/[]]/, "\]"); var regex = new RegExp("[\?&]" + name + "=([^&#]*)"), results = regex.exec(location.search); return results === null ? "" : decodeURIComponent(results[1].replace(/+/g, " ")); }

    // mixitup
    document.addEventListener('DOMContentLoaded', function() {
        var filterOnLoad = getParameterByName('filter') ? "." + getParameterByName('filter') : "all";
        $('.customer-logos').mixItUp({
            load: {
                filter: filterOnLoad
            }
        });
        if (filterOnLoad == '.case-study') {
            $(".case-studies-accordion .accordion-content").toggleClass("active").removeClass("start-closed");
        }
    }); ```

bash git add _layouts/home.html.haml _layouts/base.html.haml git commit -m "perf: wrap inline jQuery scripts in DOMContentLoaded for defer compat"


Task 4: Remove source maps from git and deployment

Files: - Modify: .gitignore - Remove from tracking: css/maps/

Append to .gitignore:

# CSS source maps (generated locally by Gulp, not needed in production) css/maps/

bash git rm -r --cached css/maps/

Expected output: lists css/maps/styles.css.map, css/maps/mmenu.css.map, and any subdirectory files as removed from tracking.

bash git add .gitignore git commit -m "perf: remove CSS source maps from git (saves 458KB from production)"


Task 5: Subset Google Fonts

Files: - Modify: _layouts/base.html.haml:11-14 (four Google Fonts references) - Modify: _layouts/home.html.haml:15-18 (four Google Fonts references)

The current URL loads every weight 300-800 in both regular and italic. The SCSS only uses weights 400, 500, 600, 700, 800 with no italic.

Old URL: https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,300..800;1,300..800&display=swap New URL: https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;500;600;700;800&display=swap

In _layouts/base.html.haml lines 11-14, change:

haml %link{rel: "preload", as: "style", href: "https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,300..800;1,300..800&display=swap"} %link{rel: "stylesheet", href: "https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,300..800;1,300..800&display=swap", media: "print", onload: "this.onload=null;this.removeAttribute('media');"} %noscript %link{rel: "stylesheet", href: "https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,300..800;1,300..800&display=swap"}

to:

haml %link{rel: "preload", as: "style", href: "https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;500;600;700;800&display=swap"} %link{rel: "stylesheet", href: "https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;500;600;700;800&display=swap", media: "print", onload: "this.onload=null;this.removeAttribute('media');"} %noscript %link{rel: "stylesheet", href: "https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;500;600;700;800&display=swap"}

In _layouts/home.html.haml lines 15-18, change:

haml %link{rel: "preload", as: "style", href: "https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,300..800;1,300..800&display=swap"} %link{rel: "stylesheet", href: "https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,300..800;1,300..800&display=swap", media: "print", onload: "this.onload=null;this.removeAttribute('media');"} %noscript %link{rel: "stylesheet", href: "https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,300..800;1,300..800&display=swap"}

to:

haml %link{rel: "preload", as: "style", href: "https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;500;600;700;800&display=swap"} %link{rel: "stylesheet", href: "https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;500;600;700;800&display=swap", media: "print", onload: "this.onload=null;this.removeAttribute('media');"} %noscript %link{rel: "stylesheet", href: "https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;500;600;700;800&display=swap"}

bash git add _layouts/base.html.haml _layouts/home.html.haml git commit -m "perf: subset Google Fonts to 5 weights, drop italic"


Task 6: Add lazy loading to below-fold images

Files: - Modify: _partials/front_page/outcomes.html.haml:28,44,60 (3 customer logo images) - Modify: _partials/front_page/scroller_compliance.html.haml:9,13 (2 compliance logo images) - Modify: _partials/footer-home.html.haml:6,60 (footer logo + JASANZ logo)

In _partials/front_page/outcomes.html.haml:

Line 28 — change: haml %img.logo{:src => "/images/customers/tunedglobal.svg", :alt => "Tuned Global"} to: haml %img.logo{:src => "/images/customers/tunedglobal.svg", :alt => "Tuned Global", :loading => "lazy"}

Line 44 — change: haml %img.logo{:src => "/images/customers/logo-boddle.svg", :alt => "Boddle"} to: haml %img.logo{:src => "/images/customers/logo-boddle.svg", :alt => "Boddle", :loading => "lazy"}

Line 60 — change: haml %img.logo{:src => "/images/customers/parakeet-logo-pms-aero.svg", :alt => "Parakeet"} to: haml %img.logo{:src => "/images/customers/parakeet-logo-pms-aero.svg", :alt => "Parakeet", :loading => "lazy"}

In _partials/front_page/scroller_compliance.html.haml:

Line 9 — change: haml %img(src="/images/ISO-27001-certified_col.svg" alt="ISO 27001 Certified" height="#{80}px" width="auto" data="no-retina" )/ to: haml %img(src="/images/ISO-27001-certified_col.svg" alt="ISO 27001 Certified" height="#{80}px" width="auto" data="no-retina" loading="lazy")/

Line 13 — change: haml %img(src="/images/news/jasanz.svg" alt="JASANZ Certified" height="#{80}px" width="auto" data="no-retina" )/ to: haml %img(src="/images/news/jasanz.svg" alt="JASANZ Certified" height="#{80}px" width="auto" data="no-retina" loading="lazy")/

In _partials/footer-home.html.haml:

Line 6 — change: haml %img{:align => "base2Services", :src => "/images/base2_white.svg", :height => "128", :width => "128", :alt => "base2Services - The Cloud Services People", :data => {:no_retina => ""}}/ to: haml %img{:align => "base2Services", :src => "/images/base2_white.svg", :height => "128", :width => "128", :alt => "base2Services - The Cloud Services People", :data => {:no_retina => ""}, :loading => "lazy"}/

Line 60 — the JASANZ logo in the footer already does NOT have lazy loading. Change: haml %img{:src => "/images/jasanz.svg", :alt => "JASANZ Certified", :height => "106", :width => "105", :data => {:no_retina => ""}, :style => "opacity: 0.98;", :class => "jasanz"}/ to: haml %img{:src => "/images/jasanz.svg", :alt => "JASANZ Certified", :height => "106", :width => "105", :data => {:no_retina => ""}, :style => "opacity: 0.98;", :class => "jasanz", :loading => "lazy"}/

bash git add _partials/front_page/outcomes.html.haml _partials/front_page/scroller_compliance.html.haml _partials/footer-home.html.haml git commit -m "perf: add lazy loading to below-fold images"


Task 7: Verify with local build

powershell podman machine start podman run --rm --name ruby -v ${PWD}:/home/dev/website base2/awestruct:Awestruct-0.5 -P development --force --verbose clean preview

Expected: Build completes without errors.

In a separate terminal:

powershell docker run --rm -ti -p 8080:8080 --name http-server -v C:\Users\amari\development\base2website\_site:/public redsadic/docker-http-server

Open http://localhost:8080 and verify: 1. Homepage loads and renders correctly (typewriter animation works, menu works, accordions work) 2. Open DevTools > Network tab: confirm jQuery loads with defer attribute 3. Open DevTools > Network tab: confirm no .map files are requested 4. Navigate to /customers/?filter=case-study and verify filters work 5. Check footer renders correctly

In Chrome DevTools > Lighthouse tab, run audit for both mobile and desktop on http://localhost:8080. Compare scores to baseline (mobile: 51, desktop: 78).

In DevTools > Network tab, filter by "Font". Confirm only Open Sans regular weights (400, 500, 600, 700, 800) are requested — no italic variants.