Back to Experiments

Experiment 005

Stable

Building a Full Feature Services Business Website Using Only Local AI.

Can a full-feature business website with before-after slider, page animations, 44-item cost calculator, and full SEO be built end-to-end using only local open-source LLMs without cloud AI?

System Requirements

CPU

Intel Core I5 11400F

RAM

16GB DDR4 3200 MT/s

GPU

RX 6700 XT

OS

Ubuntu 26.04 LTS

Inference Engine

Llama.cpp

Coding Tools

OpenCode + Cline (VS Code)

The Model That Helped Me

ORNITH-1.0-35B-A3B-IQ4_NL-GGUF

Frontend Stack

Tailwind CDN + Vanilla JS

Status

STABLE

Screenshots

Screenshot 1 — homepage layout of InDeepCleaningID business website with hero, badge, CTA and services grid
Homepage Layout
Screenshot 2 — gallery layout with 14 before-after sliders and category filter buttons
Gallery Layout — 14 Before-After Sliders
Screenshot 3 — cost estimation calculator UI with 22 service categories and 44 price items and WhatsApp deep-link
Estimation Calculator UI/UX
Pagespeed Insights mobile version — performance 65, accessibility 86, best practices 100, SEO 100
Pagespeed — Mobile Version
Pagespeed Insights desktop version — performance 88, accessibility 86, best practices 100, SEO 100
Pagespeed — Desktop Version

Visualization

Lighthouse Pagespeed Scores — Mobile vs Desktop

Real scores from pagespeed.web.dev on the live 14-page site (https://indeepcleaningid.site). Performance is the only metric with clear headroom — SEO is already 100.

Mobile Version

Perf 65 — Needs Improvement
Low — 50 score / 100 — mobile High — 100

Desktop Version

Perf 88 — Good
Low — 86 score / 100 — desktop High — 100

Agentic Explorer is an experimental diagnostic in Pagespeed, not a Lighthouse core metric. The gap between Mobile (65) and Desktop (88) reflects image weight from 28 before-after .webp files — a known optimization headroom.

Experiment Details

Problem

I needed a home-service business website that could explain services clearly, prove results with before-after evidence, give transparent pricing through a cost calculator, and rank for search — without relying on cloud AI or an expensive agency. The question was whether a small local model could handle the full frontend complexity deterministically.

Specifications
  1. 1.

    Multi-page architecture: 14 pages — 7 main (index, pricing-estimator, gallery, coverage, about, contact, services/index), 5 detail (cuci-kasur, cuci-sofa, cuci-karpet-gordyn, interior-mobil, baby-stuff), 1 landingpage/v2 for Meta/Google Ads conversion, and 1 invoice/admin for data control.

  2. 2.

    Before-after slider: 14 pairs (28 .webp), draggable clip-path: inset(), divider + handle, data-slider, filter data-filter for all/kasur/sofa/mobil/baby/karpet.

  3. 3.

    Scroll animations on every page: reveal, reveal-left/right/scale via IntersectionObserver, stagger-1..8, counter data-counter="5000", magnetic-btn, tilt-card 3D, navbar-glass hide-on-scroll, float-organic, pulse-ring.

  4. 4.

    Cost estimation calculator: 22 service categories mapped to 44 price items (indeepPricelist), dependent dropdowns (service-type → item-size), dynamic extras (Kasur Latex +40k, Sofa Lepas Pasang +20k/seat, Lebar >75cm +20k/seat), live result + WhatsApp deep-link via encodeURIComponent.

  5. 5.

    Separate data source: assets/data/pricelist.js + assets/pricelist.json as single source of truth, so prices can be updated without touching UI logic.

  6. 6.

    Full SEO: canonical, OG 1200x630, Twitter, hreflang, preload Hero.webp, JSON-LD LocalBusiness + BreadcrumbList + Service + OfferCatalog + GeoCircle 50km, sitemap.xml, robots.txt, optimize-meta.sh / add-schema.sh.

  7. 7.

    Responsive & performant: assets/css/style.css 1306 lines, 15 animation types, 3 vanilla JS files (main 487, estimator 214, slider 76) — no heavy framework, Tailwind CDN only.

  8. 8.

    Consistent brand: primary #062C3D / accent #F4A100 + Poppins (heading) + Inter (body) for the business site (portfolio page itself stays in ink/paper/accent + Manrope/Inter for coherence).

Hypothesis

A local open-source LLM (ORNITH 35B IQ4_NL via Llama.cpp) is sufficient to generate production-ready vanilla JS + Tailwind syntax — if prompts are fully deterministic and the builder has enough HTML, Tailwind, JS, SEO, and chart/styling knowledge to judge whether the syntax is actually correct. AI can write the syntax; it cannot decide if the syntax is correct.

Experiment

I built the 14-page site by prompting the local model per component — slider, estimator, navbar, reveal animations — and per batch, using OpenCode + Cline in VS Code. Each component was generated, manually validated, and iterated in small context windows to preserve output quality, instead of asking the model to generate the whole site in one shot.

What Failed
  1. 1.

    Too many revisions — 33 revisions with ~4M total context split across sessions. Single-shot full-site generation collapsed after the first few pages; context bloat degraded coherence immediately.

  2. 2.

    slider.js before-after logic was surprisingly complex for a local LLM. It required zero-ambiguity deterministic instructions: exact clip-path: inset(0 0 0 50%) direction, divider/handle sync at the same percentage, and separate mouse vs touch handlers (mousedown/mousemove vs touchstart/touchmove). Any vague phrasing broke dragging on mobile.

  3. 3.

    The initial cost calculator did not match the real pricelist.js (44 items). The model hallucinated prices and sizes for Kasur 200/180/160 etc., until I isolated pricelist.js as the single source and forced dependent dropdowns to read from it verbatim.

  4. 4.

    First-pass designs for all pages were too templated and “safe” — generic Tailwind cards that did not match the brand preference (#062C3D / #F4A100, Poppins/Inter, premium minimalism). Required manual design direction per page.

  5. 5.

    Internal linking needed a separate optimization pass. The model generated basic <a href> but missed contextual anchor distribution between services/index and detail pages — manual pass fixed crawl depth.

  6. 6.

    Initial SEO was too basic — just title/description. Achieving OG/Twitter consistency, three JSON-LD schemas, preload, and correct GeoCircle radius required my own deeper SEO understanding; AI alone stayed at template level.

  7. 7.

    Initial sitemap.xml was not readable in Google Search Console (wrong loc / missing image:image namespaces). Required manual schema fix via add-schema.sh and re-submission.

  8. 8.

    Animations had to be revised piece-by-piece, per batch. Attempting to generate all 15 animation types at once (reveal, magnetic, tilt, counter, float, pulse) overflowed the context window and degraded output quality — batching restored determinism.

Iteration

The pivot was to stop relying 100% on AI. I kept a deterministic-first workflow: understand HTML, Tailwind CSS, Vanilla JS, SEO, chart types, and styling at a human level, then let AI help write the syntax — not decide it. By generating one component per session, validating against the real pricelist and real Lighthouse, and batching animations, the quality jumped from templated to production. AI is the assistant that types; the builder remains the reviewer who judges.

Evidence

Figures from the live site at `indeepcleaningid.site` (Hostinger). You can verify via the screenshots above and `pagespeed.web.dev`.

Total Pages

14

7 main + 5 detail + 1 landing + 1 admin

Before-After Pairs

14 (28 images .webp)

Calculator Items

44 / 22 categories

Custom JS Files

3

main / estimator / slider

Custom CSS

1306 lines

Animation Types

15

Pagespeed Mobile

65 perf / 100 SEO

Pagespeed Desktop

88 perf / 100 SEO

Result

The site is stable and live on Hostinger with 14 pages, 14 sliders with filter, a 44-item calculator with WA deep-link, 3 JSON-LD schemas, and honest Lighthouse scores (SEO 100 on both, performance 65 mobile / 88 desktop, accessibility 86, best practices 100). It is not pixel-perfect on performance yet, but it is sufficient and production-ready for the Jabodetabek service business it was built for.

What I Learned

Capability does not come from prompting harder — it comes from knowing the domain well enough to make prompts deterministic. Local AI (ORNITH 35B) is strong at syntax generation when instructions leave zero ambiguity (clip-path direction, pricelist binding, JSON-LD structure). The builder’s understanding of HTML/Tailwind/JS/SEO is the bottleneck, not the model size. Batch per component; keep context small.

Practical Implication

If you want to replicate this, do not ask local AI to “build a whole website.” Start with one component — the slider or the calculator — isolate your data (`pricelist.js`), master deterministic prompting for that slice, ship it, then batch the next. Learn Tailwind and vanilla JS first; AI will then accelerate you instead of replacing you. This pattern scales to the invoice (EXP006) and n8n (EXP007) you’ll see next.

Configuration Template

assets/js/estimator.js — dependent dropdown (deterministic pattern)

function updateSizeDropdown(service) {
  sizeSelect.innerHTML = '<option>-- Pilih Ukuran / Tipe --</option>';
  if (!service || !priceData[service]) {
    sizeSelect.disabled = true; return;
  }
  priceData[service].forEach(function (item) {
    var opt = document.createElement('option');
    opt.value = item.id;
    opt.textContent = item.nama_barang + ' (' + item.ukuran + ') — Rp ' + item.harga.toLocaleString('id-ID');
    opt.dataset.price = item.harga;
    sizeSelect.appendChild(opt);
  });
  sizeSelect.disabled = false;
}

// Keep data separate — single source of truth
// assets/data/pricelist.js → const indeepPricelist = { kasur: [{id:1, ukuran:"200", harga:300000}, ...], sofa: [...] };
Do not copy this snippet blindly; adapt it to your pricelist structure. The key lesson is to keep data (`pricelist.js`) separate from UI — AI can generate the loop, but you must ensure the 44 items are exactly correct.
  1. 1.

    I chose to keep indeepPricelist in a dedicated assets/data/pricelist.js file (22 keys, 44 items) so the estimator never hardcodes prices — any hallucination is caught by diffing against this file.

  2. 2.

    I chose dependent dropdowns (service-type → item-size) with disabled handling because the model initially generated two independent selects — which allowed invalid combinations (e.g., “Sofa — 200” size).

  3. 3.

    For slider.js, I required the exact clip-path: inset(0 0 0 50%) pattern with synced divider.style.left and handle.style.left — vague “slide” prompts produced inverted masks on mobile.

  4. 4.

    I batched animations (reveal, then magnetic, then tilt, then counter) because generating all 15 types at once overflowed the 35B context window — output quality dropped from 99% to ~60% coherence.

FAQ

Frequently Asked Questions

Can local AI really generate a production estimator with 44 price rules?

Yes, but only with deterministic prompts and manual validation. Initial calc hallucinated prices for Kasur 200/180 etc. Splitting pricelist.js as single source of truth (22 categories, 44 items) and forcing dependent dropdowns to read from it verbatim fixed it. AI drafted the loops and WA deep-link — human ensured every item matched.

What broke when you let AI build the slider fully?

Clip-path direction, divider sync, and touch vs mouse handlers. The local LLM needed zero-ambiguity instructions for inset() percentages and event handling (mousedown vs touchstart). One-shot full-page generation failed — batched per-component with explicit slider.js pattern succeeded.

How did you get SEO 100 without cloud tools?

Manual SEO layer: canonical, OG 1200x630, three JSON-LD schemas (LocalBusiness, Service, BreadcrumbList), preload Hero.webp, sitemap fix for Search Console, internal linking pass. AI gave baseline meta; human optimized beyond template. Performance still has headroom (65 mobile / 88 desktop) — SEO is 100, perf is not, and that honesty is intentional.

For those of you unable to read this data from technical standpoint, here is the conclusion

This was not a demo or a template bought online. It is a live business website with 14 pages that serves real customers in the Jabodetabek area.

Local AI helped write the code — the before-after sliders, the scroll animations on every page, and the price calculator — but it took 33 revisions and about 4 million tokens of context to get it right. The first versions were generic and broken.

The site now shows real proof of work (14 before-after pairs, 28 images), gives instant price estimates for 44 service variants, and is fully structured for search engines with sitemaps and business schemas.

It runs stably on consumer hardware with a 35-billion-parameter local model. You do not need a cloud subscription to build something this complete — but you do need to know what you are building and how to check the AI’s work.

It is not flawless. Performance can still be improved — especially on mobile. But it is sufficient, it is live, it is maintainable, and it proves that local AI is practical when you use it deterministically: one component at a time, with the builder as the reviewer.

Disclaimer

A Note on Results

From a purely objective standpoint, I wouldn’t claim this is the optimal outcome. Given the local model (ORNITH 35B IQ4_NL via Llama.cpp) and consumer-grade hardware used, I personally consider the result to be sufficient and practical for real business use. That said, there is clear room for further optimization — particularly in overall website performance (65 on mobile / 88 on desktop) and image weight from the 28 before-after files.