Initial Website

This commit is contained in:
SJCoetz 2026-06-18 12:22:31 +02:00
commit 7b4bb96017
14 changed files with 3151 additions and 0 deletions

6
brand-mark.svg Normal file
View file

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 26 26" fill="none">
<circle cx="13" cy="4" r="2.6" fill="#F76857"/>
<circle cx="13" cy="22" r="2.6" fill="#F76857"/>
<path d="M5 9c0-2.8 2.2-5 5-5h1c2.8 0 5 2.2 5 5v0c0 1.4-1.1 2.5-2.5 2.5H7.5C6.1 11.5 5 12.6 5 14v0c0 2.8 2.2 5 5 5h1" stroke="#1DB897" stroke-width="2.6" stroke-linecap="round"/>
<path d="M11 11.5h6c1.4 0 2.5 1.1 2.5 2.5v0c0 1.4-1.1 2.5-2.5 2.5H11" stroke="#1DB897" stroke-width="2.6" stroke-linecap="round" opacity="0.55"/>
</svg>

After

Width:  |  Height:  |  Size: 510 B

39
brand-spec.md Normal file
View file

@ -0,0 +1,39 @@
# Stackless — Brand Specification
Extracted from the supplied brand identity sheet (`mqjbdjo3...jpg`) via pixel sampling.
## Palette (extracted hex from image)
| Token | Hex | Approx OKLch | Role |
|---|---|---|---|
| `--bg` | `#F4F2EC` | `oklch(96% 0.008 95)` | Paper / page |
| `--surface` | `#FFFFFF` | `oklch(100% 0 0)` | Cards / panels |
| `--ink` | `#212A40` | `oklch(28% 0.04 265)` | Primary navy text |
| `--ink-2` | `#1C2439` | `oklch(24% 0.04 265)` | Deepest neutral |
| `--muted` | `#6E7387` | `oklch(56% 0.015 265)` | Secondary text |
| `--border` | `#E5E2D8` | `oklch(91% 0.012 95)` | Hairline dividers |
| `--accent` | `#1DB897` | `oklch(70% 0.13 175)` | Teal — primary brand |
| `--accent-2` | `#F76857` | `oklch(72% 0.16 28)` | Coral — CTA accent |
## Typography
- Display: **Inter Tight** at heavy weight (600/700) with `-0.02em` tracking, all lowercase
- Body: **Inter** at 400/500, normal tracking
- Mono: **JetBrains Mono** for numerals / metadata
- Headlines are short and tight; the wordmark itself is lowercase, so brand copy follows suit
## Posture
- Hairline borders (1px), no shadows except on dropdowns/modals
- Generous whitespace — the brand sheet itself breathes
- Teal as the dominant brand surface (section markers, code-like chips, key numbers)
- Coral is the *single* CTA color across the site — used at most twice per screen, always the click target
- One decisive flourish per screen (a display headline or a code-style label, never both)
- No gradients on backgrounds, no emoji icons, no left-border "card with accent stripe" — these break the brand's quiet confidence
## Layout grammar
- Section rhythm: alternating wide / tight
- Cards use 16–20px radius, no shadow
- Stat blocks use tabular monospaced numerals
- Buttons: filled coral (primary), outline teal (secondary), ghost (tertiary)

555
contact.html Normal file
View file

@ -0,0 +1,555 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
<title>Start a project — Stackless</title>
<meta name="description" content="Tell us what's eating your week. We'll come back with a fixed quote within one business day." />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Inter+Tight:wght@500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="shared.css" />
<style>
/* ---------- contact-page layout ---------- */
.contact-hero { padding: 80px 0 24px; }
.contact-hero h1 { max-width: 22ch; }
.contact-hero p { margin-top: 18px; font-size: 18px; max-width: 60ch; color: var(--muted); }
.contact-grid {
display: grid;
grid-template-columns: 1.4fr 1fr;
gap: 56px;
margin-top: 56px;
}
/* ---------- form ---------- */
.form {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--r-lg);
padding: 36px 36px 32px;
box-shadow: var(--shadow-pop);
}
.progress {
display: flex; align-items: center; gap: 10px;
margin-bottom: 28px;
}
.progress .dot {
width: 28px; height: 28px;
border-radius: 50%;
background: var(--bg);
border: 1px solid var(--border);
color: var(--muted);
font: 500 12.5px/1 var(--font-mono);
letter-spacing: 0.02em;
display: grid; place-items: center;
transition: background 0.18s, color 0.18s, border-color 0.18s;
}
.progress .dot.is-on {
background: var(--accent); color: var(--ink-2); border-color: var(--accent);
}
.progress .dot.is-done {
background: var(--ink-2); color: var(--accent); border-color: var(--ink-2);
}
.progress .bar {
flex: 1; height: 1px; background: var(--border);
}
.step { display: none; }
.step.is-active { display: block; }
.step__head { margin-bottom: 22px; }
.step__head h2 {
font: 600 22px/1.2 var(--font-display);
letter-spacing: -0.02em;
color: var(--ink-2);
}
.step__head p { margin-top: 6px; font-size: 14px; color: var(--muted); }
.field { display: block; margin-bottom: 16px; }
.field label {
display: block;
font: 500 13px/1 var(--font-mono);
letter-spacing: 0.04em;
text-transform: uppercase;
color: var(--muted);
margin-bottom: 7px;
}
.field input, .field select, .field textarea {
width: 100%;
padding: 12px 14px;
background: var(--bg);
border: 1px solid var(--border);
border-radius: var(--r-sm);
font: 400 15px/1.4 var(--font-body);
color: var(--ink-2);
transition: border-color 0.15s, background 0.15s;
}
.field textarea { min-height: 110px; resize: vertical; }
.field input:focus, .field select:focus, .field textarea:focus {
outline: none;
border-color: var(--accent);
background: var(--surface);
}
.field.is-invalid input, .field.is-invalid select, .field.is-invalid textarea,
input.is-invalid, select.is-invalid, textarea.is-invalid {
border-color: var(--accent-2);
background: rgba(247, 104, 87, 0.05);
}
.field .hint {
display: block;
margin-top: 6px;
font-size: 12.5px;
color: var(--muted);
}
.field.is-invalid .hint,
.err {
color: var(--accent-2);
}
.services-err {
display: none;
margin: 6px 0 0;
font: 500 12.5px/1.4 var(--font-mono);
color: var(--accent-2);
letter-spacing: 0.02em;
}
.checklist {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px 12px;
margin-top: 4px;
}
.check {
display: flex; align-items: center; gap: 10px;
padding: 10px 12px;
border: 1px solid var(--border);
border-radius: var(--r-sm);
background: var(--bg);
font-size: 14px;
color: var(--ink);
cursor: pointer;
transition: border-color 0.15s, background 0.15s;
}
.check input { display: none; }
.check .box {
width: 16px; height: 16px;
border-radius: 4px;
border: 1.5px solid var(--muted);
flex-shrink: 0;
display: grid; place-items: center;
transition: background 0.15s, border-color 0.15s;
}
.check input:checked + .box {
background: var(--accent); border-color: var(--accent);
}
.check input:checked + .box::after {
content: ""; display: block;
width: 8px; height: 4px;
border-left: 2px solid var(--ink-2);
border-bottom: 2px solid var(--ink-2);
transform: rotate(-45deg) translate(1px, -1px);
}
.check:has(input:checked) {
border-color: var(--accent);
background: rgba(29, 184, 151, 0.08);
}
.err-banner {
display: none;
align-items: center; gap: 10px;
padding: 12px 14px;
border-radius: var(--r-sm);
background: rgba(247, 104, 87, 0.10);
border: 1px solid rgba(247, 104, 87, 0.3);
color: #c84434;
font-size: 13.5px;
margin-bottom: 18px;
}
.err-banner svg { width: 16px; height: 16px; flex-shrink: 0; }
.form__nav {
display: flex; justify-content: space-between; gap: 12px;
margin-top: 26px;
padding-top: 20px;
border-top: 1px solid var(--border);
}
.form__nav .btn:disabled { opacity: 0.4; cursor: not-allowed; }
/* ---------- side info ---------- */
.side { padding-top: 8px; }
.side__card {
background: var(--ink-2);
color: #F4F2EC;
border-radius: var(--r-lg);
padding: 32px 30px;
margin-bottom: 18px;
}
.side__card h3 {
font: 600 19px/1.2 var(--font-display);
letter-spacing: -0.015em;
color: #fff;
}
.side__card p {
margin-top: 12px;
font-size: 14.5px;
color: rgba(244, 242, 236, 0.7);
line-height: 1.55;
}
.side__list {
margin-top: 22px;
list-style: none;
border-top: 1px solid rgba(244, 242, 236, 0.15);
padding-top: 16px;
}
.side__list li {
display: flex; gap: 12px;
padding: 10px 0;
font-size: 14px;
color: rgba(244, 242, 236, 0.92);
}
.side__list li::before {
content: "→"; color: var(--accent); flex-shrink: 0;
}
.side__alt {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--r-lg);
padding: 24px 26px;
}
.side__alt h4 {
font: 500 12.5px/1 var(--font-mono);
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--muted);
margin-bottom: 12px;
}
.side__alt a {
display: flex; align-items: center; gap: 10px;
padding: 8px 0;
font-size: 14.5px;
color: var(--ink-2);
}
.side__alt a:hover { color: var(--accent); }
.side__alt a svg { width: 16px; height: 16px; color: var(--accent); }
/* ---------- success state ---------- */
.success {
display: none;
background: var(--surface);
border: 1px solid var(--accent);
border-radius: var(--r-lg);
padding: 56px 40px;
text-align: center;
}
.success__check {
width: 56px; height: 56px;
border-radius: 50%;
background: rgba(29, 184, 151, 0.16);
color: var(--accent);
display: grid; place-items: center;
margin: 0 auto 20px;
}
.success h2 {
font: 600 28px/1.15 var(--font-display);
letter-spacing: -0.02em;
color: var(--ink-2);
max-width: 22ch;
margin: 0 auto;
}
.success p {
margin-top: 14px;
color: var(--muted);
font-size: 16px;
max-width: 50ch;
margin-left: auto;
margin-right: auto;
}
.success__meta {
margin-top: 28px;
display: inline-flex; align-items: center; gap: 10px;
padding: 8px 14px;
background: var(--bg);
border: 1px solid var(--border);
border-radius: 999px;
font: 500 13px/1 var(--font-mono);
color: var(--muted);
letter-spacing: 0.04em;
}
.success__meta .dot {
width: 7px; height: 7px; border-radius: 50%;
background: var(--accent);
box-shadow: 0 0 0 4px rgba(29, 184, 151, 0.18);
}
@media (max-width: 960px) {
.contact-grid { grid-template-columns: 1fr; gap: 36px; }
.form { padding: 28px 24px; }
}
@media (max-width: 520px) {
.checklist { grid-template-columns: 1fr; }
}
</style>
</head>
<body>
<header class="header">
<div class="wrap header__row">
<a href="index.html" class="brand">
<span class="brand__mark"><img src="brand-mark.svg" alt="" width="26" height="26" /></span>
stackless
</a>
<nav class="nav" id="nav">
<a href="index.html#services">Services</a>
<a href="index.html#how">How it works</a>
<a href="pricing.html">Pricing</a>
<a href="index.html#why">Why us</a>
<a href="contact.html" class="nav__cta">Start a project</a>
</nav>
<button class="menu-btn" id="menuBtn" aria-label="Open menu">
<svg width="22" height="22" viewBox="0 0 22 22" fill="none"><path d="M3 6h16M3 11h16M3 16h16" stroke="#212A40" stroke-width="2" stroke-linecap="round"/></svg>
</button>
</div>
</header>
<section class="section contact-hero">
<div class="wrap">
<div style="max-width: 720px;">
<span class="section__kicker">Start a project</span>
<h1>Tell us what's eating your week.</h1>
<p>Three short steps, no marketing fluff. You'll get a fixed quote — or a polite "not a fit" — within one business day. Most replies go out within four hours.</p>
</div>
<div class="contact-grid">
<!-- ============== FORM ============== -->
<form class="form" id="contactForm" novalidate>
<div class="progress" aria-hidden="true">
<span class="dot is-on">1</span>
<span class="bar"></span>
<span class="dot">2</span>
<span class="bar"></span>
<span class="dot">3</span>
</div>
<div class="err-banner" id="errBanner">
<svg viewBox="0 0 16 16" fill="none"><circle cx="8" cy="8" r="7" stroke="currentColor" stroke-width="1.5"/><path d="M8 4v5M8 11.5v.01" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
<span>Please fix the highlighted fields before continuing.</span>
</div>
<!-- Step 1 -->
<div class="step is-active" data-step="0">
<div class="step__head">
<h2>What do you need built?</h2>
<p>Pick one or more — we'll route the right team for the mix.</p>
</div>
<div class="field" style="margin-bottom: 20px;">
<label>Which services are you interested in?</label>
<div class="checklist">
<label class="check"><input type="checkbox" name="services" value="website" /><span class="box"></span>Website build or upgrade</label>
<label class="check"><input type="checkbox" name="services" value="agent" /><span class="box"></span>AI agent (chat or voice)</label>
<label class="check"><input type="checkbox" name="services" value="rental" /><span class="box"></span>Rent-an-AI-Agent retainer</label>
<label class="check"><input type="checkbox" name="services" value="automation" /><span class="box"></span>Outreach or lead-gen automation</label>
<label class="check"><input type="checkbox" name="services" value="integrations" /><span class="box"></span>Workflow integrations</label>
<label class="check"><input type="checkbox" name="services" value="apps" /><span class="box"></span>Internal app or dashboard</label>
</div>
<div class="services-err" id="servicesErr">Pick at least one service.</div>
</div>
<div class="field">
<label for="budget">Approximate budget</label>
<select id="budget" name="budget" required>
<option value="">Choose a range…</option>
<option>R10k – R25k · small project</option>
<option>R25k – R80k · standard build</option>
<option>R80k – R250k · multi-week engagement</option>
<option>R250k+ · ongoing studio retainer</option>
<option>Not sure yet</option>
</select>
</div>
<div class="field">
<label for="timeline">Ideal start</label>
<select id="timeline" name="timeline" required>
<option value="">Choose a timeline…</option>
<option>ASAP — within 2 weeks</option>
<option>This month</option>
<option>Next month</option>
<option>Within the quarter</option>
<option>Just exploring</option>
</select>
</div>
</div>
<!-- Step 2 -->
<div class="step" data-step="1">
<div class="step__head">
<h2>Who's it for?</h2>
<p>Just enough to make a useful reply. We never sell or share what you write here.</p>
</div>
<div class="field">
<label for="name">Your name</label>
<input id="name" name="name" type="text" required autocomplete="name" placeholder="Jane Mokoena" />
</div>
<div class="field">
<label for="email">Work email</label>
<input id="email" name="email" type="email" required autocomplete="email" placeholder="jane@yourcompany.co.za" />
<span class="hint">We reply from hello@stackless.app.</span>
</div>
<div class="field">
<label for="company">Company</label>
<input id="company" name="company" type="text" required placeholder="Your business name" />
</div>
<div class="field">
<label for="role">Your role (optional)</label>
<input id="role" name="role" type="text" placeholder="Founder, COO, Head of Ops…" />
</div>
</div>
<!-- Step 3 -->
<div class="step" data-step="2">
<div class="step__head">
<h2>What's the actual problem?</h2>
<p>The more specific, the better your quote. Two sentences is fine.</p>
</div>
<div class="field">
<label for="problem">Describe what you're trying to fix or build</label>
<textarea id="problem" name="problem" required placeholder="e.g. Our support inbox eats 3 hours a day and 60% of tickets are the same three questions. We want an agent that drafts replies and only flags the weird ones for a human."></textarea>
</div>
<div class="field">
<label for="stack">Tools you already use (optional)</label>
<input id="stack" name="stack" type="text" placeholder="HubSpot · Xero · WhatsApp · Shopify" />
<span class="hint">Comma-separated. Helps us skip the discovery questions.</span>
</div>
<div class="field">
<label for="source">How did you find us? (optional)</label>
<select id="source" name="source">
<option value="">Choose one…</option>
<option>Referral</option>
<option>Search</option>
<option>Social</option>
<option>Article or podcast</option>
<option>We've worked together before</option>
</select>
</div>
</div>
<div class="form__nav">
<button type="button" class="btn btn--ghost btn--sm" id="prevBtn" disabled>← Back</button>
<div style="display: flex; gap: 10px;">
<button type="button" class="btn btn--primary btn--sm" id="nextBtn">Continue <span class="arrow">→</span></button>
<button type="submit" class="btn btn--primary btn--sm" id="submitBtn" style="display: none;">Send &amp; request a quote <span class="arrow">→</span></button>
</div>
</div>
</form>
<!-- Success state -->
<div class="success" id="formSuccess">
<div class="success__check">
<svg width="28" height="28" viewBox="0 0 28 28" fill="none"><path d="M6 14.5l5 5 11-11.5" stroke="currentColor" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round"/></svg>
</div>
<h2>Got it — we're on it.</h2>
<p>You'll get a reply from a real human (probably Gerhard or Mapula) within four working hours. If you'd rather skip the queue, mail us directly.</p>
<div class="success__meta">
<span class="dot"></span> Ref · #SL-<span id="refNum">—</span>
</div>
</div>
<!-- ============== SIDE INFO ============== -->
<aside class="side">
<div class="side__card">
<h3>What happens after you send this?</h3>
<p>Three steps, no surprises. We do the discovery properly so the quote holds up when you sign.</p>
<ul class="side__list">
<li>We read it (within four working hours).</li>
<li>We send a one-page quote — fixed scope, fixed price, fixed deadline.</li>
<li>If we're not a fit, we tell you who is.</li>
</ul>
</div>
<div class="side__alt">
<h4>Prefer email or a call?</h4>
<a href="mailto:hello@stackless.app">
<svg viewBox="0 0 16 16" fill="none"><rect x="2" y="3.5" width="12" height="9" rx="1.5" stroke="currentColor" stroke-width="1.5"/><path d="M2.5 4l5.5 4 5.5-4" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/></svg>
hello@stackless.app
</a>
<a href="tel:+27210000000">
<svg viewBox="0 0 16 16" fill="none"><path d="M3 3.5c0 6 3.5 9.5 9.5 9.5l1-2.5-2.5-1-1.2 1.2c-1.4-.7-2.5-1.8-3.2-3.2L8 6.5 9.5 4 7 3l-4 .5z" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/></svg>
+27 21 000 0000
</a>
<a href="https://wa.me/27210000000" rel="noopener">
<svg viewBox="0 0 16 16" fill="none"><path d="M8 2a6 6 0 00-5 9l-1 3 3-1A6 6 0 108 2zm3.4 7.6c-.2.5-1 1-1.5 1.1-.4.1-.9.1-1.4-.1-.3-.1-.7-.2-1.2-.4-2.1-.9-3.5-3-3.6-3.2-.1-.2-.9-1.1-.9-2.1s.5-1.5.7-1.7c.2-.2.4-.2.6-.2h.4c.1 0 .3 0 .5.4l.7 1.7c.1.2 0 .4-.1.6l-.3.4c-.1.1-.2.3-.1.4.1.2.5.9 1.1 1.4.7.7 1.4.9 1.6 1 .2.1.3 0 .4-.1l.5-.6c.1-.2.3-.1.5-.1l1.6.8c.2.1.4.2.4.3 0 .1 0 .7-.2 1.2z" fill="currentColor"/></svg>
WhatsApp us
</a>
</div>
</aside>
</div>
</div>
</section>
<footer>
<div class="wrap">
<div class="foot">
<div class="foot__brand">
<a href="index.html" class="brand">
<span class="brand__mark"><img src="brand-mark.svg" alt="" width="26" height="26" /></span>
stackless
</a>
<p>The on-call studio for small and medium businesses. Build, automate, integrate — without hiring an in-house team.</p>
</div>
<div>
<h5>Services</h5>
<ul>
<li><a href="index.html#services">Websites</a></li>
<li><a href="index.html#services">AI agents</a></li>
<li><a href="pricing.html">Rent-an-AI-Agent</a></li>
<li><a href="index.html#services">Integrations</a></li>
<li><a href="index.html#services">Apps &amp; dashboards</a></li>
</ul>
</div>
<div>
<h5>Studio</h5>
<ul>
<li><a href="index.html#how">How we work</a></li>
<li><a href="index.html#why">Why us</a></li>
<li><a href="pricing.html">Pricing</a></li>
<li><a href="contact.html">Start a project</a></li>
</ul>
</div>
<div>
<h5>Reach us</h5>
<ul>
<li><a href="mailto:hello@stackless.app">hello@stackless.app</a></li>
<li><a href="tel:+27210000000">+27 21 000 0000</a></li>
<li>Cape Town · Johannesburg</li>
<li>Mon–Fri, 08:00–17:00 SAST</li>
</ul>
</div>
</div>
<div class="foot__legal">
<span>© 2026 Stackless (Pty) Ltd · Reg. 2024/184256/07</span>
<span>Made on the African continent.</span>
</div>
</div>
</footer>
<script src="header.js"></script>
<script src="contact.js"></script>
<script>
// Generate a fake ref number on success (purely cosmetic for the demo)
document.addEventListener('DOMContentLoaded', function () {
var el = document.getElementById('refNum');
if (el) {
var n = Math.floor(1000 + Math.random() * 9000);
el.textContent = n.toString();
}
});
</script>
</body>
</html>

View file

@ -0,0 +1,19 @@
{
"version": 1,
"kind": "html",
"title": "contact.html",
"entry": "contact.html",
"renderer": "html",
"status": "complete",
"exports": [
"html",
"pdf",
"zip"
],
"createdAt": "2026-06-18T09:54:49.091Z",
"updatedAt": "2026-06-18T09:54:49.091Z",
"metadata": {
"inferred": true,
"reconciled": true
}
}

106
contact.js Normal file
View file

@ -0,0 +1,106 @@
// Stackless — contact form behaviour: step nav, validation, submit state
(function () {
var form = document.getElementById('contactForm');
if (!form) return;
var steps = Array.prototype.slice.call(form.querySelectorAll('.step'));
var dots = Array.prototype.slice.call(document.querySelectorAll('.progress .dot'));
var prevBtn = document.getElementById('prevBtn');
var nextBtn = document.getElementById('nextBtn');
var submitBtn = document.getElementById('submitBtn');
var successEl = document.getElementById('formSuccess');
var errBanner = document.getElementById('errBanner');
var current = 0;
function paint() {
steps.forEach(function (s, i) { s.classList.toggle('is-active', i === current); });
dots.forEach(function (d, i) {
d.classList.toggle('is-on', i === current);
d.classList.toggle('is-done', i < current);
});
prevBtn.disabled = current === 0;
if (current === steps.length - 1) {
nextBtn.style.display = 'none';
submitBtn.style.display = 'inline-flex';
} else {
nextBtn.style.display = 'inline-flex';
submitBtn.style.display = 'none';
}
}
function validateStep(idx) {
var step = steps[idx];
var fields = step.querySelectorAll('input[required], select[required], textarea[required]');
var ok = true;
fields.forEach(function (el) {
var bad = !el.checkValidity();
el.classList.toggle('is-invalid', bad);
el.parentElement.classList.toggle('is-invalid', bad);
if (bad) ok = false;
});
// Custom: at least one service checkbox picked if on step 1
if (idx === 0) {
var any = step.querySelectorAll('input[name="services"]:checked').length;
var servicesErr = document.getElementById('servicesErr');
if (any === 0) {
ok = false;
servicesErr.style.display = 'block';
} else {
servicesErr.style.display = 'none';
}
}
return ok;
}
function goto(i) {
if (i > current && !validateStep(current)) {
errBanner.style.display = 'flex';
return;
}
errBanner.style.display = 'none';
current = Math.max(0, Math.min(steps.length - 1, i));
paint();
// scroll the active step into view (only on mobile-ish where steps stack)
var active = steps[current];
if (active && window.innerWidth < 720) {
active.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
}
// live-clear invalid state as the user fixes it
form.addEventListener('input', function (e) {
var t = e.target;
if (t.classList && t.classList.contains('is-invalid') && t.checkValidity()) {
t.classList.remove('is-invalid');
if (t.parentElement) t.parentElement.classList.remove('is-invalid');
}
if (t.name === 'services') {
var any = form.querySelectorAll('input[name="services"]:checked').length;
var servicesErr = document.getElementById('servicesErr');
if (any > 0 && servicesErr) servicesErr.style.display = 'none';
}
});
prevBtn.addEventListener('click', function () { goto(current - 1); });
nextBtn.addEventListener('click', function () { goto(current + 1); });
// Submit (no real backend — show the success state, log the payload)
form.addEventListener('submit', function (e) {
e.preventDefault();
if (!validateStep(current)) { errBanner.style.display = 'flex'; return; }
var data = {};
new FormData(form).forEach(function (v, k) { data[k] = v; });
console.log('[Stackless] contact submission:', data);
submitBtn.disabled = true;
submitBtn.textContent = 'Sending…';
setTimeout(function () {
form.style.display = 'none';
successEl.style.display = 'block';
successEl.scrollIntoView({ behavior: 'smooth', block: 'start' });
}, 700);
});
paint();
})();

18
header.js Normal file
View file

@ -0,0 +1,18 @@
// Stackless — shared header behaviour (mobile menu + current page highlight)
(function () {
var btn = document.getElementById('menuBtn');
var nav = document.getElementById('nav');
if (btn && nav) {
btn.addEventListener('click', function () { nav.classList.toggle('is-open'); });
nav.addEventListener('click', function (e) {
if (e.target.tagName === 'A') nav.classList.remove('is-open');
});
}
// Mark current nav item
var here = (location.pathname.split('/').pop() || 'index.html').toLowerCase();
document.querySelectorAll('.nav a').forEach(function (a) {
var href = (a.getAttribute('href') || '').toLowerCase();
if (!href || href.startsWith('#')) return;
if (href === here) a.classList.add('is-current');
});
})();

1093
index.html Normal file

File diff suppressed because it is too large Load diff

19
index.html.artifact.json Normal file
View file

@ -0,0 +1,19 @@
{
"version": 1,
"kind": "html",
"title": "index.html",
"entry": "index.html",
"renderer": "html",
"status": "complete",
"exports": [
"html",
"pdf",
"zip"
],
"createdAt": "2026-06-18T09:55:12.947Z",
"updatedAt": "2026-06-18T09:55:12.947Z",
"metadata": {
"inferred": true,
"reconciled": true
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

610
pricing.html Normal file
View file

@ -0,0 +1,610 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
<title>Pricing — Stackless</title>
<meta name="description" content="Rent-an-AI-Agent retainers, project-based builds, and ongoing studio engagements. Fixed quotes, weekly demos, no surprises." />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Inter+Tight:wght@500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="shared.css" />
<style>
/* ---------- page-specific styles ---------- */
.lede { padding: 80px 0 24px; }
.lede h1 { max-width: 22ch; }
.lede p { margin-top: 18px; font-size: 18px; max-width: 60ch; color: var(--muted); }
.switcher {
display: inline-flex;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 999px;
padding: 4px;
margin: 36px 0 0;
}
.switcher button {
background: transparent; border: 0;
padding: 9px 16px;
border-radius: 999px;
font: 500 13.5px/1 var(--font-body);
color: var(--muted);
transition: background 0.15s, color 0.15s;
}
.switcher button.is-on {
background: var(--ink-2); color: #fff;
}
/* ---------- tier card variants (heavier than the index teaser) ---------- */
.tiers {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 18px;
margin-top: 16px;
}
.tier {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--r-lg);
padding: 32px 30px;
display: flex; flex-direction: column;
position: relative;
}
.tier.is-featured {
border-color: var(--ink-2);
background: var(--ink-2);
color: #F4F2EC;
}
.tier__tag {
position: absolute; top: 16px; right: 16px;
font: 500 11px/1 var(--font-mono);
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--accent);
padding: 5px 9px;
border-radius: 4px;
background: rgba(29, 184, 151, 0.12);
}
.tier.is-featured .tier__tag {
background: rgba(29, 184, 151, 0.18);
color: var(--accent);
}
.tier__name {
font: 500 13px/1 var(--font-mono);
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--muted);
}
.tier.is-featured .tier__name { color: var(--accent); }
.tier__price {
margin: 18px 0 6px;
font: 600 46px/1 var(--font-display);
letter-spacing: -0.035em;
color: var(--ink-2);
font-variant-numeric: tabular-nums;
}
.tier.is-featured .tier__price { color: #fff; }
.tier__price small {
font: 500 14px/1 var(--font-body);
color: var(--muted);
margin-left: 4px;
letter-spacing: 0;
}
.tier.is-featured .tier__price small { color: rgba(244, 242, 236, 0.6); }
.tier__billed {
font: 500 12px/1 var(--font-mono);
color: var(--muted);
letter-spacing: 0.02em;
margin-bottom: 16px;
}
.tier.is-featured .tier__billed { color: rgba(244, 242, 236, 0.55); }
.tier__desc {
font-size: 14.5px; color: var(--muted);
min-height: 48px;
line-height: 1.5;
}
.tier.is-featured .tier__desc { color: rgba(244, 242, 236, 0.7); }
.tier__feat {
list-style: none;
margin: 22px 0 26px;
border-top: 1px solid var(--border);
padding-top: 18px;
}
.tier.is-featured .tier__feat { border-top-color: rgba(244, 242, 236, 0.15); }
.tier__feat li {
font-size: 14px;
padding: 8px 0;
display: flex; gap: 10px;
color: var(--ink);
}
.tier.is-featured .tier__feat li { color: rgba(244, 242, 236, 0.92); }
.tier__feat li svg {
flex-shrink: 0;
width: 16px; height: 16px;
color: var(--accent);
margin-top: 3px;
}
.tier__feat li.muted { color: var(--muted); }
.tier__feat li.muted svg { color: rgba(110, 115, 135, 0.5); }
.tier.is-featured .tier__feat li.muted { color: rgba(244, 242, 236, 0.4); }
.tier.is-featured .tier__feat li.muted svg { color: rgba(244, 242, 236, 0.3); }
.tier .btn { justify-content: center; margin-top: auto; }
/* ---------- add-ons ---------- */
.addons {
margin-top: 110px;
padding: 56px;
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--r-lg);
}
.addons__head { margin-bottom: 36px; }
.addons__head h2 {
font: 600 30px/1.15 var(--font-display);
letter-spacing: -0.02em;
color: var(--ink-2);
}
.addons__head p { margin-top: 8px; color: var(--muted); font-size: 15.5px; }
.addons__grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16px;
}
.addon {
border: 1px solid var(--border);
border-radius: var(--r-md);
padding: 22px;
}
.addon h4 {
font: 600 16px/1.3 var(--font-display);
letter-spacing: -0.01em;
color: var(--ink-2);
margin-bottom: 6px;
}
.addon__price {
font: 500 13px/1 var(--font-mono);
color: var(--accent);
letter-spacing: 0.02em;
margin-bottom: 10px;
}
.addon p { font-size: 14px; color: var(--muted); line-height: 1.5; }
/* ---------- FAQ ---------- */
.faq { margin-top: 110px; }
.faq__list {
margin-top: 36px;
border-top: 1px solid var(--border);
}
.faq__item {
border-bottom: 1px solid var(--border);
padding: 22px 0;
}
.faq__q {
width: 100%;
display: flex; justify-content: space-between; align-items: center;
background: transparent; border: 0;
text-align: left;
font: 500 17px/1.4 var(--font-display);
letter-spacing: -0.01em;
color: var(--ink-2);
padding: 0;
}
.faq__q .ic {
width: 22px; height: 22px;
color: var(--accent);
flex-shrink: 0;
transition: transform 0.2s;
}
.faq__item.is-open .faq__q .ic { transform: rotate(45deg); }
.faq__a {
display: none;
margin-top: 14px;
font-size: 15px;
line-height: 1.6;
color: var(--muted);
max-width: 70ch;
}
.faq__item.is-open .faq__a { display: block; }
/* ---------- compare table ---------- */
.compare { margin-top: 110px; }
.compare__wrap {
margin-top: 32px;
border: 1px solid var(--border);
border-radius: var(--r-lg);
overflow: hidden;
background: var(--surface);
overflow-x: auto;
}
table.compare__tbl {
width: 100%;
border-collapse: collapse;
font-size: 14.5px;
min-width: 720px;
}
.compare__tbl th, .compare__tbl td {
padding: 16px 22px;
text-align: left;
border-bottom: 1px solid var(--border);
vertical-align: top;
}
.compare__tbl thead th {
background: var(--bg);
font: 500 12.5px/1 var(--font-mono);
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--muted);
font-weight: 500;
}
.compare__tbl thead th.is-col {
color: var(--ink-2);
font: 600 14.5px/1 var(--font-display);
letter-spacing: -0.01em;
text-transform: none;
}
.compare__tbl tbody tr:last-child td { border-bottom: 0; }
.compare__tbl td:first-child { color: var(--ink); font-weight: 500; }
.compare__tbl td svg {
display: inline-block; vertical-align: middle;
width: 18px; height: 18px;
color: var(--accent);
}
.compare__tbl td.no svg { color: rgba(110, 115, 135, 0.4); }
.compare__tbl td.no { color: var(--muted); }
@media (max-width: 960px) {
.tiers, .addons__grid { grid-template-columns: 1fr; }
.addons { padding: 32px; }
}
</style>
</head>
<body>
<header class="header">
<div class="wrap header__row">
<a href="index.html" class="brand">
<span class="brand__mark"><img src="brand-mark.svg" alt="" width="26" height="26" /></span>
stackless
</a>
<nav class="nav" id="nav">
<a href="index.html#services">Services</a>
<a href="index.html#how">How it works</a>
<a href="pricing.html">Pricing</a>
<a href="index.html#why">Why us</a>
<a href="contact.html" class="nav__cta">Start a project</a>
</nav>
<button class="menu-btn" id="menuBtn" aria-label="Open menu">
<svg width="22" height="22" viewBox="0 0 22 22" fill="none"><path d="M3 6h16M3 11h16M3 16h16" stroke="#212A40" stroke-width="2" stroke-linecap="round"/></svg>
</button>
</div>
</header>
<section class="section lede">
<div class="wrap">
<div class="section__head" style="margin: 0; text-align: left;">
<span class="section__kicker">Pricing</span>
<h1>Three ways to work with us.</h1>
<p>One-off projects for a single deliverable, a monthly retainer for an AI agent that lives in your business, or a studio engagement for ongoing builds. Prices are in South African Rand, exclusive of VAT, and exclude third-party software licences (we'll flag those up front).</p>
<div class="switcher" role="tablist">
<button type="button" class="is-on" data-period="monthly">Monthly</button>
<button type="button" data-period="annual">Annual · save 12%</button>
</div>
</div>
</div>
</section>
<section class="section" style="padding-top: 24px;">
<div class="wrap">
<div class="tiers">
<article class="tier">
<span class="tier__name">Project</span>
<div class="tier__price" data-monthly="R18 000" data-annual="R18 000">R18 000<small>starting</small></div>
<div class="tier__billed">one-off · fixed quote</div>
<p class="tier__desc">A single website, integration, or app build. Best when you know exactly what you need and just want it shipped.</p>
<ul class="tier__feat">
<li><svg viewBox="0 0 16 16" fill="none"><path d="M3.5 8.5l3 3 6-6.5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>Discovery call &amp; written scope</li>
<li><svg viewBox="0 0 16 16" fill="none"><path d="M3.5 8.5l3 3 6-6.5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>Weekly demo every Friday</li>
<li><svg viewBox="0 0 16 16" fill="none"><path d="M3.5 8.5l3 3 6-6.5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>30 days post-launch support</li>
<li><svg viewBox="0 0 16 16" fill="none"><path d="M3.5 8.5l3 3 6-6.5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>All code, content &amp; accounts handed over</li>
<li class="muted"><svg viewBox="0 0 16 16" fill="none"><path d="M4 4l8 8M12 4l-8 8" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>Dedicated account lead</li>
<li class="muted"><svg viewBox="0 0 16 16" fill="none"><path d="M4 4l8 8M12 4l-8 8" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>Monthly refinement call</li>
</ul>
<a class="btn btn--ghost btn--sm" href="contact.html">Get a quote</a>
</article>
<article class="tier is-featured">
<span class="tier__tag">Most popular</span>
<span class="tier__name">Rent-an-AI-Agent</span>
<div class="tier__price" data-monthly="R9 800" data-annual="R8 624">R9 800<small>/ month</small></div>
<div class="tier__billed" data-monthly-billed="billed monthly · cancel any time" data-annual-billed="billed annually · R103 488 / year">billed monthly · cancel any time</div>
<p class="tier__desc">A dedicated AI agent trained on your business, refined weekly. Lives in your inbox, CRM and WhatsApp. Best for teams losing hours every week to routine work.</p>
<ul class="tier__feat">
<li><svg viewBox="0 0 16 16" fill="none"><path d="M3.5 8.5l3 3 6-6.5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>One dedicated agent, named &amp; trained on you</li>
<li><svg viewBox="0 0 16 16" fill="none"><path d="M3.5 8.5l3 3 6-6.5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>Email, CRM &amp; WhatsApp integrations</li>
<li><svg viewBox="0 0 16 16" fill="none"><path d="M3.5 8.5l3 3 6-6.5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>Weekly 30-min refinement call</li>
<li><svg viewBox="0 0 16 16" fill="none"><path d="M3.5 8.5l3 3 6-6.5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>Human escalation paths &amp; guardrails</li>
<li><svg viewBox="0 0 16 16" fill="none"><path d="M3.5 8.5l3 3 6-6.5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>Monthly usage &amp; impact report</li>
<li><svg viewBox="0 0 16 16" fill="none"><path d="M3.5 8.5l3 3 6-6.5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>Pause, swap or cancel any month</li>
</ul>
<a class="btn btn--accent btn--sm" href="contact.html">Start an agent</a>
</article>
<article class="tier">
<span class="tier__name">Studio</span>
<div class="tier__price" data-monthly="R48 000" data-annual="R42 240">R48 000<small>/ month</small></div>
<div class="tier__billed" data-monthly-billed="billed monthly · 90-day minimum" data-annual-billed="billed annually · R506 880 / year · 90-day minimum">billed monthly · 90-day minimum</div>
<p class="tier__desc">Ongoing builds, integrations, and agents — all in one retainer. Best for businesses that ship software or product changes every month.</p>
<ul class="tier__feat">
<li><svg viewBox="0 0 16 16" fill="none"><path d="M3.5 8.5l3 3 6-6.5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>Up to 3 active workstreams</li>
<li><svg viewBox="0 0 16 16" fill="none"><path d="M3.5 8.5l3 3 6-6.5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>Dedicated lead + on-call engineer</li>
<li><svg viewBox="0 0 16 16" fill="none"><path d="M3.5 8.5l3 3 6-6.5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>Same-day response, 4-hour SLA on P1</li>
<li><svg viewBox="0 0 16 16" fill="none"><path d="M3.5 8.5l3 3 6-6.5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>Up to 2 deployed AI agents</li>
<li><svg viewBox="0 0 16 16" fill="none"><path d="M3.5 8.5l3 3 6-6.5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>Quarterly roadmap review</li>
<li><svg viewBox="0 0 16 16" fill="none"><path d="M3.5 8.5l3 3 6-6.5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>Discounted project add-ons</li>
</ul>
<a class="btn btn--ghost btn--sm" href="contact.html">Book a discovery call</a>
</article>
</div>
<!-- add-ons -->
<div class="addons">
<div class="addons__head">
<h2>Common add-ons.</h2>
<p>Drop these onto any plan. We bill at the same hourly rate every time — no project premium.</p>
</div>
<div class="addons__grid">
<div class="addon">
<h4>Voice agent</h4>
<div class="addon__price">from R2 400 / month</div>
<p>AI that picks up the phone, routes calls, books appointments, and only hands to a human when the rule says so.</p>
</div>
<div class="addon">
<h4>WhatsApp Business setup</h4>
<div class="addon__price">R4 800 once-off</div>
<p>Verified WhatsApp Business account, template library, agent integration, and 14-day on-boarding.</p>
</div>
<div class="addon">
<h4>Reporting dashboard</h4>
<div class="addon__price">from R14 000</div>
<p>A live dashboard pulling from Xero, Shopify, HubSpot or whatever stack you use. Email-digest on Mondays.</p>
</div>
<div class="addon">
<h4>Knowledge base agent</h4>
<div class="addon__price">from R3 200 / month</div>
<p>Trained on your docs, SOPs and past tickets. Answers customer questions and surfaces the right human when it's stuck.</p>
</div>
<div class="addon">
<h4>Migration sprint</h4>
<div class="addon__price">from R28 000</div>
<p>Two-week sprint to move you off a platform you've outgrown — Webflow → Next.js, old CRM → HubSpot, etc.</p>
</div>
<div class="addon">
<h4>Security review</h4>
<div class="addon__price">R9 600 once-off</div>
<p>Independent review of your stack, agent prompts, and access controls. Written report with prioritised fixes.</p>
</div>
</div>
</div>
<!-- compare table -->
<div class="compare">
<div class="section__head" style="text-align: left; margin: 0 0 8px;">
<span class="section__kicker">Compare</span>
<h2 style="font-size: 30px;">What's in each plan.</h2>
</div>
<div class="compare__wrap">
<table class="compare__tbl">
<thead>
<tr>
<th></th>
<th class="is-col">Project</th>
<th class="is-col">Rent-an-AI-Agent</th>
<th class="is-col">Studio</th>
</tr>
</thead>
<tbody>
<tr>
<td>Web &amp; app builds</td>
<td>One-off</td>
<td>—</td>
<td>Up to 3 active</td>
</tr>
<tr>
<td>Deployed AI agents</td>
<td>—</td>
<td>1</td>
<td>Up to 2</td>
</tr>
<tr>
<td>Workflow integrations</td>
<td>Up to 2</td>
<td>Up to 4</td>
<td>Unlimited</td>
</tr>
<tr>
<td>Weekly refinement call</td>
<td class="no"><svg viewBox="0 0 16 16" fill="none"><path d="M4 4l8 8M12 4l-8 8" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg></td>
<td><svg viewBox="0 0 16 16" fill="none"><path d="M3.5 8.5l3 3 6-6.5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></td>
<td><svg viewBox="0 0 16 16" fill="none"><path d="M3.5 8.5l3 3 6-6.5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></td>
</tr>
<tr>
<td>Dedicated lead</td>
<td class="no"><svg viewBox="0 0 16 16" fill="none"><path d="M4 4l8 8M12 4l-8 8" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg></td>
<td class="no"><svg viewBox="0 0 16 16" fill="none"><path d="M4 4l8 8M12 4l-8 8" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg></td>
<td><svg viewBox="0 0 16 16" fill="none"><path d="M3.5 8.5l3 3 6-6.5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></td>
</tr>
<tr>
<td>Response SLA</td>
<td>Next business day</td>
<td>Same business day</td>
<td>4-hour P1, same-day P2</td>
</tr>
<tr>
<td>Cancellation</td>
<td>End of project</td>
<td>Any month</td>
<td>90-day minimum, then any month</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- FAQ -->
<div class="faq">
<div class="section__head" style="text-align: left; margin: 0;">
<span class="section__kicker">Frequently asked</span>
<h2 style="font-size: 30px;">The questions we hear most.</h2>
</div>
<div class="faq__list">
<div class="faq__item is-open">
<button class="faq__q" type="button">
<span>Who actually does the work — humans or AI?</span>
<svg class="ic" viewBox="0 0 22 22" fill="none"><path d="M11 4v14M4 11h14" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>
</button>
<div class="faq__a">
<p>Two named humans do the building, the architecture, and the reviews. AI handles the repeatable parts inside your workflows — drafting emails, qualifying leads, routing tickets — under rules we set up together and you approve. If the AI ever makes a call you're uncomfortable with, a human reviews it before it ships.</p>
</div>
</div>
<div class="faq__item">
<button class="faq__q" type="button">
<span>What does a typical agent cost in third-party fees?</span>
<svg class="ic" viewBox="0 0 22 22" fill="none"><path d="M11 4v14M4 11h14" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>
</button>
<div class="faq__a">
<p>Most Rent-an-AI-Agent clients spend an extra R1 500–R4 000 a month on LLM API usage and a chat/SMS provider. We pass those costs through at cost with no markup, and we cap usage so a surprise bill is impossible. Larger clients on the Studio plan can negotiate committed-use discounts.</p>
</div>
</div>
<div class="faq__item">
<button class="faq__q" type="button">
<span>Do I really own everything you build?</span>
<svg class="ic" viewBox="0 0 22 22" fill="none"><path d="M11 4v14M4 11h14" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>
</button>
<div class="faq__a">
<p>Yes. Code lives in your GitHub from day one. Content lives in your CMS. Agent prompts live in a workspace we share with you and you keep. Hosting and domain accounts are yours. If you fire us tomorrow, the only thing you lose is our weekly demo.</p>
</div>
</div>
<div class="faq__item">
<button class="faq__q" type="button">
<span>What happens after the 30 days of post-launch support?</span>
<svg class="ic" viewBox="0 0 22 22" fill="none"><path d="M11 4v14M4 11h14" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>
</button>
<div class="faq__a">
<p>Your build is yours to run. Anything broken that was our fault, we fix free. Anything new you want built, we send a fixed quote. If you'd rather have us on call, the Rent-an-AI-Agent and Studio retainers both cover ongoing support, refinement and small new builds.</p>
</div>
</div>
<div class="faq__item">
<button class="faq__q" type="button">
<span>How long until I see results?</span>
<svg class="ic" viewBox="0 0 22 22" fill="none"><path d="M11 4v14M4 11h14" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>
</button>
<div class="faq__a">
<p>Project builds ship in 10–14 working days. The first AI agent is in production within a week of kick-off, then refined over the next 4 weeks. Studio engagements usually have something live in week 2 and a clear roadmap by week 4.</p>
</div>
</div>
<div class="faq__item">
<button class="faq__q" type="button">
<span>What if my business is outside South Africa?</span>
<svg class="ic" viewBox="0 0 22 22" fill="none"><path d="M11 4v14M4 11h14" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>
</button>
<div class="faq__a">
<p>We work with clients across the UK, EU and the rest of Africa. Pricing switches to GBP/EUR/USD at the same ballpark converted, and we adjust SLAs to your timezone. Most client calls run on 09:00–11:00 SAST, which is a friendly hour for London and Lagos.</p>
</div>
</div>
</div>
</div>
</div>
</section>
<footer>
<div class="wrap">
<div class="foot">
<div class="foot__brand">
<a href="index.html" class="brand">
<span class="brand__mark"><img src="brand-mark.svg" alt="" width="26" height="26" /></span>
stackless
</a>
<p>The on-call studio for small and medium businesses. Build, automate, integrate — without hiring an in-house team.</p>
</div>
<div>
<h5>Services</h5>
<ul>
<li><a href="index.html#services">Websites</a></li>
<li><a href="index.html#services">AI agents</a></li>
<li><a href="pricing.html">Rent-an-AI-Agent</a></li>
<li><a href="index.html#services">Integrations</a></li>
<li><a href="index.html#services">Apps &amp; dashboards</a></li>
</ul>
</div>
<div>
<h5>Studio</h5>
<ul>
<li><a href="index.html#how">How we work</a></li>
<li><a href="index.html#why">Why us</a></li>
<li><a href="pricing.html">Pricing</a></li>
<li><a href="contact.html">Start a project</a></li>
</ul>
</div>
<div>
<h5>Reach us</h5>
<ul>
<li><a href="mailto:hello@stackless.app">hello@stackless.app</a></li>
<li><a href="tel:+27210000000">+27 21 000 0000</a></li>
<li>Cape Town · Johannesburg</li>
<li>Mon–Fri, 08:00–17:00 SAST</li>
</ul>
</div>
</div>
<div class="foot__legal">
<span>© 2026 Stackless (Pty) Ltd · Reg. 2024/184256/07</span>
<span>Made on the African continent.</span>
</div>
</div>
</footer>
<script src="header.js"></script>
<script>
// Billing-period switcher
(function () {
var btns = document.querySelectorAll('.switcher button');
var prices = document.querySelectorAll('.tier__price[data-monthly]');
var billeds = document.querySelectorAll('[data-monthly-billed]');
if (!btns.length) return;
function apply(period) {
btns.forEach(function (b) { b.classList.toggle('is-on', b.dataset.period === period); });
prices.forEach(function (el) {
var v = el.dataset[period];
if (!v) return;
var small = el.querySelector('small');
var smallHTML = small ? small.outerHTML : '';
// Strip the "/ month" suffix for annual values (which already encode the discount)
if (period === 'annual' && small) {
el.innerHTML = v + '<small>once-off / month equiv.</small>';
} else if (small) {
el.innerHTML = v + smallHTML;
} else {
el.textContent = v;
}
});
billeds.forEach(function (el) {
var v = el.getAttribute('data-' + period + '-billed');
if (v) el.textContent = v;
});
}
btns.forEach(function (b) {
b.addEventListener('click', function () { apply(b.dataset.period); });
});
})();
// FAQ accordion
(function () {
document.querySelectorAll('.faq__q').forEach(function (btn) {
btn.addEventListener('click', function () {
btn.parentElement.classList.toggle('is-open');
});
});
})();
</script>
</body>
</html>

View file

@ -0,0 +1,19 @@
{
"version": 1,
"kind": "html",
"title": "pricing.html",
"entry": "pricing.html",
"renderer": "html",
"status": "complete",
"exports": [
"html",
"pdf",
"zip"
],
"createdAt": "2026-06-18T09:53:38.037Z",
"updatedAt": "2026-06-18T09:53:38.037Z",
"metadata": {
"inferred": true,
"reconciled": true
}
}

180
shared.css Normal file
View file

@ -0,0 +1,180 @@
/* ============================================================
Stackless — shared brand + chrome (used by pricing.html,
contact.html, and any future sub-pages). Mirrors the tokens
and layout primitives from index.html so the whole site
stays visually consistent.
============================================================ */
:root {
--bg: #F4F2EC;
--surface: #FFFFFF;
--ink: #212A40;
--ink-2: #1C2439;
--muted: #6E7387;
--border: #E5E2D8;
--accent: #1DB897;
--accent-2: #F76857;
--font-display: 'Inter Tight', -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
--font-body: 'Inter', -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
--font-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
--r-sm: 8px;
--r-md: 14px;
--r-lg: 22px;
--shadow-pop: 0 14px 40px -18px rgba(28, 36, 57, 0.28);
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html { scroll-behavior: smooth; }
body {
background: var(--bg); color: var(--ink);
font: 16px/1.55 var(--font-body);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
a { color: inherit; text-decoration: none; }
img, svg { display: block; max-width: 100%; }
button { font: inherit; cursor: pointer; }
::selection { background: var(--accent); color: var(--ink-2); }
.wrap { width: 100%; max-width: 1180px; margin: 0 auto; padding: 0 24px; }
/* header */
.header {
position: sticky; top: 0; z-index: 50;
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
background: rgba(244, 242, 236, 0.78);
border-bottom: 1px solid var(--border);
}
.header__row {
display: flex; align-items: center; justify-content: space-between;
height: 68px;
}
.brand {
display: inline-flex; align-items: center; gap: 10px;
font: 600 19px/1 var(--font-display);
letter-spacing: -0.02em;
color: var(--ink-2);
}
.brand__mark { width: 26px; height: 26px; display: grid; place-items: center; }
.nav { display: flex; align-items: center; gap: 4px; }
.nav a {
padding: 8px 14px; border-radius: 999px;
font-size: 14.5px; font-weight: 500; color: var(--ink);
transition: background 0.15s, color 0.15s;
}
.nav a:hover { background: rgba(33, 42, 64, 0.06); }
.nav a.is-current { background: rgba(33, 42, 64, 0.08); }
.nav__cta {
margin-left: 12px; background: var(--accent-2); color: #fff !important;
padding: 9px 16px;
}
.nav__cta:hover { background: #e25645 !important; }
.menu-btn { display: none; background: transparent; border: 0; padding: 8px; }
/* buttons */
.btn {
display: inline-flex; align-items: center; gap: 10px;
padding: 14px 22px;
border-radius: 999px;
font: 500 15px/1 var(--font-body);
letter-spacing: -0.005em;
border: 1px solid transparent;
transition: transform 0.12s, background 0.15s, border-color 0.15s, color 0.15s;
}
.btn:active { transform: translateY(1px); }
.btn--primary { background: var(--accent-2); color: #fff; }
.btn--primary:hover { background: #e25645; }
.btn--ghost { background: transparent; color: var(--ink); border-color: var(--ink); }
.btn--ghost:hover { background: var(--ink); color: #fff; }
.btn--accent { background: var(--accent); color: var(--ink-2); }
.btn--accent:hover { background: #1aa888; }
.btn--sm { padding: 10px 16px; font-size: 14px; }
.btn .arrow { transition: transform 0.15s; }
.btn:hover .arrow { transform: translateX(3px); }
/* section primitives */
.section { padding: 100px 0 30px; }
.section__head { max-width: 720px; margin: 0 auto 56px; text-align: center; }
.section__kicker {
display: inline-flex; align-items: center; gap: 8px;
font: 500 12.5px/1 var(--font-mono);
text-transform: uppercase;
letter-spacing: 0.12em;
color: var(--accent);
margin-bottom: 18px;
}
.section__kicker::before {
content: ""; display: block; width: 22px; height: 1px; background: var(--accent);
}
.section h1, .section h2 {
font: 600 clamp(34px, 4.2vw, 54px)/1.08 var(--font-display);
letter-spacing: -0.03em;
color: var(--ink-2);
}
.section__sub {
margin: 16px auto 0;
font-size: 17px;
color: var(--muted);
max-width: 58ch;
}
/* footer (compact) */
footer {
border-top: 1px solid var(--border);
padding: 56px 0 36px;
margin-top: 100px;
}
.foot {
display: grid;
grid-template-columns: 1.5fr repeat(3, 1fr);
gap: 40px;
margin-bottom: 40px;
}
.foot__brand p {
margin-top: 12px;
font-size: 14px;
color: var(--muted);
max-width: 32ch;
}
.foot h5 {
font: 500 12px/1 var(--font-mono);
text-transform: uppercase;
letter-spacing: 0.1em;
color: var(--ink-2);
margin-bottom: 14px;
}
.foot ul { list-style: none; }
.foot li { padding: 4px 0; font-size: 14px; color: var(--muted); }
.foot li a:hover { color: var(--ink-2); }
.foot__legal {
display: flex; justify-content: space-between; align-items: center;
padding-top: 24px;
border-top: 1px solid var(--border);
font: 500 12.5px/1.4 var(--font-mono);
color: var(--muted);
letter-spacing: 0.02em;
}
/* responsive */
@media (max-width: 960px) {
.foot { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 680px) {
.nav { display: none; }
.menu-btn { display: inline-flex; }
.nav.is-open {
display: flex;
position: absolute; top: 68px; left: 0; right: 0;
flex-direction: column;
background: var(--surface);
border-bottom: 1px solid var(--border);
padding: 16px;
gap: 4px;
}
.nav.is-open a { padding: 12px 14px; }
.nav.is-open .nav__cta { margin-left: 0; margin-top: 8px; text-align: center; }
.foot { grid-template-columns: 1fr; gap: 28px; }
.foot__legal { flex-direction: column; gap: 8px; align-items: flex-start; }
}

466
stackless-site.html Normal file
View file

@ -0,0 +1,466 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
<title>Stackless — Build, automate, integrate. We handle it for you.</title>
<meta name="description" content="Stackless builds websites, installs AI agents, and automates the workflows small and medium businesses keep putting off. Ship in days, not quarters." />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Inter+Tight:wght@500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
<style>
/* Tokens — Stackless brand (paper / ink / teal / coral) */
:root {
--bg: #F4F2EC;
--surface: #FFFFFF;
--ink: #212A40;
--ink-2: #1C2439;
--muted: #6E7387;
--border: #E5E2D8;
--accent: #1DB897;
--accent-2: #F76857;
--font-display: 'Inter Tight', -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
--font-body: 'Inter', -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
--font-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
--r-sm: 8px; --r-md: 14px; --r-lg: 22px;
--shadow-pop: 0 14px 40px -18px rgba(28, 36, 57, 0.28);
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html { scroll-behavior: smooth; }
body { background: var(--bg); color: var(--ink); font: 16px/1.55 var(--font-body); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; overflow-x: hidden; }
a { color: inherit; text-decoration: none; }
img, svg { display: block; max-width: 100%; }
button { font: inherit; cursor: pointer; }
::selection { background: var(--accent); color: var(--ink-2); }
.wrap { width: 100%; max-width: 1180px; margin: 0 auto; padding: 0 24px; }
.header { position: sticky; top: 0; z-index: 50; backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); background: rgba(244, 242, 236, 0.78); border-bottom: 1px solid var(--border); }
.header__row { display: flex; align-items: center; justify-content: space-between; height: 68px; }
.brand { display: inline-flex; align-items: center; gap: 10px; font: 600 19px/1 var(--font-display); letter-spacing: -0.02em; color: var(--ink-2); }
.brand__mark { width: 26px; height: 26px; display: grid; place-items: center; }
.nav { display: flex; align-items: center; gap: 4px; }
.nav a { padding: 8px 14px; border-radius: 999px; font-size: 14.5px; font-weight: 500; color: var(--ink); transition: background 0.15s, color 0.15s; }
.nav a:hover { background: rgba(33, 42, 64, 0.06); }
.nav__cta { margin-left: 12px; background: var(--accent-2); color: #fff !important; padding: 9px 16px; }
.nav__cta:hover { background: #e25645 !important; }
.menu-btn { display: none; background: transparent; border: 0; padding: 8px; }
.btn { display: inline-flex; align-items: center; gap: 10px; padding: 14px 22px; border-radius: 999px; font: 500 15px/1 var(--font-body); letter-spacing: -0.005em; border: 1px solid transparent; transition: transform 0.12s, background 0.15s, border-color 0.15s; }
.btn:active { transform: translateY(1px); }
.btn--primary { background: var(--accent-2); color: #fff; }
.btn--primary:hover { background: #e25645; }
.btn--ghost { background: transparent; color: var(--ink); border-color: var(--ink); }
.btn--ghost:hover { background: var(--ink); color: #fff; }
.btn--sm { padding: 10px 16px; font-size: 14px; }
.btn .arrow { transition: transform 0.15s; }
.btn:hover .arrow { transform: translateX(3px); }
.hero { padding: 88px 0 60px; position: relative; }
.hero__eyebrow { display: inline-flex; align-items: center; gap: 10px; padding: 7px 14px; border: 1px solid var(--border); border-radius: 999px; background: var(--surface); font: 500 12.5px/1 var(--font-mono); letter-spacing: 0.04em; color: var(--ink); }
.hero__eyebrow .dot { width: 7px; height: 7px; border-radius: 50%; background: var(--accent); box-shadow: 0 0 0 4px rgba(29, 184, 151, 0.18); }
.hero h1 { margin-top: 22px; font: 700 clamp(40px, 5.6vw, 76px)/1.04 var(--font-display); letter-spacing: -0.035em; color: var(--ink-2); max-width: 14ch; }
.hero h1 em { font-style: normal; color: var(--accent); }
.hero__sub { margin-top: 22px; font-size: 19px; line-height: 1.55; color: var(--muted); max-width: 56ch; }
.hero__actions { margin-top: 34px; display: flex; gap: 14px; flex-wrap: wrap; }
.hero__meta { margin-top: 28px; display: flex; gap: 26px; flex-wrap: wrap; font: 500 13px/1.4 var(--font-mono); color: var(--muted); letter-spacing: 0.02em; }
.hero__meta span { display: inline-flex; align-items: center; gap: 7px; }
.hero__meta svg { color: var(--accent); }
.hero__panel { margin-top: 64px; background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-lg); box-shadow: var(--shadow-pop); overflow: hidden; }
.panel__bar { display: flex; align-items: center; gap: 8px; padding: 14px 18px; border-bottom: 1px solid var(--border); background: var(--bg); }
.panel__dot { width: 10px; height: 10px; border-radius: 50%; background: #d9d6cc; }
.panel__dot.is-on { background: var(--accent); }
.panel__url { margin-left: 14px; font: 500 12.5px/1 var(--font-mono); color: var(--muted); letter-spacing: 0.02em; }
.panel__body { display: grid; grid-template-columns: 1.2fr 1fr; min-height: 360px; }
.panel__col { padding: 30px 32px; }
.panel__col + .panel__col { border-left: 1px solid var(--border); }
.panel__col h3 { font: 600 14px/1 var(--font-mono); text-transform: uppercase; letter-spacing: 0.08em; color: var(--muted); margin-bottom: 16px; }
.panel__row { display: flex; align-items: center; justify-content: space-between; padding: 12px 0; border-top: 1px solid var(--border); font-size: 14.5px; }
.panel__row:first-of-type { border-top: 0; }
.panel__row .pill { font: 500 11.5px/1 var(--font-mono); padding: 4px 8px; border-radius: 4px; letter-spacing: 0.04em; background: rgba(29, 184, 151, 0.12); color: #117a64; }
.panel__row .pill.is-2 { background: rgba(247, 104, 87, 0.13); color: #c84434; }
.panel__row .pill.is-3 { background: rgba(33, 42, 64, 0.08); color: var(--ink); }
.panel__stat { display: grid; grid-template-columns: 1fr 1fr; gap: 18px 24px; }
.panel__stat .num { font: 600 30px/1 var(--font-display); letter-spacing: -0.02em; color: var(--ink-2); font-variant-numeric: tabular-nums; }
.panel__stat .lbl { margin-top: 6px; font: 500 12.5px/1.4 var(--font-mono); color: var(--muted); letter-spacing: 0.02em; }
.trust { padding: 36px 0 16px; border-top: 1px solid var(--border); border-bottom: 1px solid var(--border); margin-top: 80px; }
.trust__lbl { font: 500 12px/1 var(--font-mono); text-transform: uppercase; letter-spacing: 0.12em; color: var(--muted); text-align: center; margin-bottom: 22px; }
.trust__row { display: grid; grid-template-columns: repeat(6, 1fr); gap: 12px; align-items: center; }
.trust__logo { text-align: center; font: 600 16px/1 var(--font-display); letter-spacing: -0.01em; color: var(--ink); opacity: 0.55; }
.trust__logo small { display: block; font: 500 10px/1 var(--font-mono); color: var(--muted); letter-spacing: 0.1em; margin-top: 4px; text-transform: uppercase; opacity: 0.7; }
.section { padding: 110px 0 30px; }
.section__head { max-width: 720px; margin-bottom: 50px; }
.section__kicker { display: inline-flex; align-items: center; gap: 8px; font: 500 12.5px/1 var(--font-mono); text-transform: uppercase; letter-spacing: 0.12em; color: var(--accent); margin-bottom: 18px; }
.section__kicker::before { content: ""; display: block; width: 22px; height: 1px; background: var(--accent); }
.section h2 { font: 600 clamp(30px, 3.8vw, 46px)/1.1 var(--font-display); letter-spacing: -0.025em; color: var(--ink-2); }
.section__sub { margin-top: 14px; font-size: 17px; color: var(--muted); max-width: 60ch; }
.services { display: grid; grid-template-columns: repeat(3, 1fr); gap: 18px; }
.service { background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-md); padding: 28px 26px; display: flex; flex-direction: column; transition: border-color 0.18s, transform 0.18s; }
.service:hover { border-color: rgba(29, 184, 151, 0.5); transform: translateY(-2px); }
.service__num { font: 500 12px/1 var(--font-mono); color: var(--muted); letter-spacing: 0.06em; }
.service__icon { width: 40px; height: 40px; margin: 18px 0 20px; border-radius: 10px; background: rgba(29, 184, 151, 0.10); color: var(--accent); display: grid; place-items: center; }
.service__icon svg { width: 22px; height: 22px; }
.service h3 { font: 600 19px/1.25 var(--font-display); letter-spacing: -0.015em; color: var(--ink-2); margin-bottom: 8px; }
.service p { font-size: 14.5px; color: var(--muted); line-height: 1.55; margin-bottom: 18px; }
.service__list { list-style: none; margin-top: auto; border-top: 1px solid var(--border); padding-top: 16px; }
.service__list li { font: 500 13px/1.6 var(--font-mono); color: var(--ink); padding: 4px 0; letter-spacing: -0.005em; }
.service__list li::before { content: "→"; color: var(--accent); margin-right: 8px; }
.how { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; }
.step { background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-md); padding: 26px; position: relative; }
.step__num { font: 600 48px/1 var(--font-display); letter-spacing: -0.04em; color: var(--accent); opacity: 0.85; }
.step h3 { margin-top: 14px; font: 600 19px/1.25 var(--font-display); letter-spacing: -0.015em; color: var(--ink-2); }
.step p { margin-top: 8px; font-size: 14.5px; color: var(--muted); line-height: 1.55; }
.step__time { display: inline-flex; align-items: center; gap: 6px; margin-top: 18px; font: 500 12px/1 var(--font-mono); color: var(--muted); padding: 6px 10px; border-radius: 999px; border: 1px solid var(--border); }
.why { display: grid; grid-template-columns: 1fr 1fr; gap: 56px; align-items: start; }
.why__col h2 { max-width: 16ch; }
.why__col p { margin-top: 16px; max-width: 46ch; }
.why__list { display: grid; gap: 14px; }
.why__item { display: grid; grid-template-columns: 32px 1fr; gap: 14px; padding: 20px 22px; background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-md); }
.why__num { font: 600 18px/1 var(--font-mono); color: var(--accent-2); letter-spacing: -0.02em; }
.why__item h4 { font: 600 16px/1.3 var(--font-display); color: var(--ink-2); letter-spacing: -0.01em; }
.why__item p { margin-top: 4px; font-size: 14px; color: var(--muted); line-height: 1.5; }
.pricing { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }
.tier { background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-md); padding: 30px; display: flex; flex-direction: column; }
.tier.is-featured { border-color: var(--ink-2); background: var(--ink-2); color: #F4F2EC; }
.tier__name { font: 500 12.5px/1 var(--font-mono); letter-spacing: 0.1em; text-transform: uppercase; color: var(--muted); }
.tier.is-featured .tier__name { color: var(--accent); }
.tier__price { margin: 18px 0 4px; font: 600 42px/1 var(--font-display); letter-spacing: -0.03em; color: var(--ink-2); font-variant-numeric: tabular-nums; }
.tier.is-featured .tier__price { color: #fff; }
.tier__price small { font: 500 14px/1 var(--font-body); color: var(--muted); margin-left: 4px; letter-spacing: 0; }
.tier.is-featured .tier__price small { color: rgba(244, 242, 236, 0.6); }
.tier__desc { font-size: 14px; color: var(--muted); min-height: 42px; }
.tier.is-featured .tier__desc { color: rgba(244, 242, 236, 0.7); }
.tier__feat { list-style: none; margin: 22px 0 24px; border-top: 1px solid var(--border); padding-top: 18px; }
.tier.is-featured .tier__feat { border-top-color: rgba(244, 242, 236, 0.15); }
.tier__feat li { font-size: 14px; padding: 7px 0; display: flex; gap: 10px; color: var(--ink); }
.tier.is-featured .tier__feat li { color: rgba(244, 242, 236, 0.92); }
.tier__feat li::before { content: ""; display: block; width: 16px; height: 16px; flex-shrink: 0; margin-top: 2px; background: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><path d='M3.5 8.5l3 3 6-6.5' fill='none' stroke='%231DB897' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/></svg>") no-repeat center / contain; }
.tier .btn { justify-content: center; }
.cta { margin: 110px 0 100px; background: var(--ink-2); border-radius: var(--r-lg); padding: 60px 56px; color: #F4F2EC; display: grid; grid-template-columns: 1.5fr 1fr; gap: 40px; align-items: end; position: relative; overflow: hidden; }
.cta::after { content: ""; position: absolute; right: -40px; bottom: -40px; width: 240px; height: 240px; border-radius: 50%; background: radial-gradient(circle at center, rgba(29, 184, 151, 0.4), transparent 70%); }
.cta h2 { font: 600 clamp(28px, 3.4vw, 42px)/1.1 var(--font-display); letter-spacing: -0.025em; color: #fff; max-width: 18ch; }
.cta p { margin-top: 14px; font-size: 16px; color: rgba(244, 242, 236, 0.7); max-width: 50ch; }
.cta__actions { display: flex; flex-direction: column; gap: 12px; align-items: flex-end; position: relative; z-index: 1; }
.cta__actions .btn--primary { background: var(--accent-2); }
.cta__small { font: 500 12px/1.4 var(--font-mono); color: rgba(244, 242, 236, 0.55); letter-spacing: 0.02em; }
footer { border-top: 1px solid var(--border); padding: 56px 0 36px; }
.foot { display: grid; grid-template-columns: 1.5fr repeat(3, 1fr); gap: 40px; margin-bottom: 40px; }
.foot__brand p { margin-top: 12px; font-size: 14px; color: var(--muted); max-width: 30ch; }
.foot h5 { font: 500 12px/1 var(--font-mono); text-transform: uppercase; letter-spacing: 0.1em; color: var(--ink-2); margin-bottom: 14px; }
.foot ul { list-style: none; }
.foot li { padding: 4px 0; font-size: 14px; color: var(--muted); }
.foot li a:hover { color: var(--ink-2); }
.foot__legal { display: flex; justify-content: space-between; align-items: center; padding-top: 24px; border-top: 1px solid var(--border); font: 500 12.5px/1.4 var(--font-mono); color: var(--muted); letter-spacing: 0.02em; }
@media (max-width: 960px) {
.hero__panel .panel__body { grid-template-columns: 1fr; }
.panel__col + .panel__col { border-left: 0; border-top: 1px solid var(--border); }
.services, .how, .pricing { grid-template-columns: 1fr 1fr; }
.why { grid-template-columns: 1fr; gap: 36px; }
.cta { grid-template-columns: 1fr; padding: 40px; }
.cta__actions { align-items: flex-start; }
.foot { grid-template-columns: 1fr 1fr; }
.trust__row { grid-template-columns: repeat(3, 1fr); row-gap: 24px; }
}
@media (max-width: 680px) {
.nav { display: none; }
.menu-btn { display: inline-flex; }
.nav.is-open { display: flex; position: absolute; top: 68px; left: 0; right: 0; flex-direction: column; background: var(--surface); border-bottom: 1px solid var(--border); padding: 16px; gap: 4px; }
.nav.is-open a { padding: 12px 14px; }
.nav.is-open .nav__cta { margin-left: 0; margin-top: 8px; text-align: center; }
.hero { padding: 56px 0 36px; }
.hero__panel { margin-top: 36px; }
.panel__col { padding: 22px; }
.services, .how, .pricing { grid-template-columns: 1fr; }
.section { padding: 72px 0 16px; }
.foot { grid-template-columns: 1fr; gap: 28px; }
.foot__legal { flex-direction: column; gap: 8px; align-items: flex-start; }
.trust { margin-top: 48px; }
}
</style>
</head>
<body>
<header class="header">
<div class="wrap header__row">
<a href="/" class="brand">
<span class="brand__mark">
<svg viewBox="0 0 26 26" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<circle cx="13" cy="4" r="2.6" fill="#F76857"/><circle cx="13" cy="22" r="2.6" fill="#F76857"/>
<path d="M5 9c0-2.8 2.2-5 5-5h1c2.8 0 5 2.2 5 5v0c0 1.4-1.1 2.5-2.5 2.5H7.5C6.1 11.5 5 12.6 5 14v0c0 2.8 2.2 5 5 5h1" stroke="#1DB897" stroke-width="2.6" stroke-linecap="round"/>
<path d="M11 11.5h6c1.4 0 2.5 1.1 2.5 2.5v0c0 1.4-1.1 2.5-2.5 2.5H11" stroke="#1DB897" stroke-width="2.6" stroke-linecap="round" opacity="0.55"/>
</svg>
</span>
stackless
</a>
<nav class="nav" id="nav">
<a href="#services">Services</a>
<a href="#how">How it works</a>
<a href="pricing.html">Pricing</a>
<a href="#why">Why us</a>
<a href="contact.html" class="nav__cta">Start a project</a>
</nav>
<button class="menu-btn" id="menuBtn" aria-label="Open menu">
<svg width="22" height="22" viewBox="0 0 22 22" fill="none"><path d="M3 6h16M3 11h16M3 16h16" stroke="#212A40" stroke-width="2" stroke-linecap="round"/></svg>
</button>
</div>
</header>
<section class="hero">
<div class="wrap">
<span class="hero__eyebrow"><span class="dot"></span>Now booking Q3 builds</span>
<h1>Build less.<br/>Ship <em>more</em>.</h1>
<p class="hero__sub">Stackless is the on-call studio for small and medium businesses. We build your website, install AI agents that do the busywork, and wire up the tools you already pay for — so your team can stop wrestling with software and get back to the work that actually moves the business.</p>
<div class="hero__actions">
<a class="btn btn--primary" href="contact.html">Start a project <span class="arrow">→</span></a>
<a class="btn btn--ghost" href="#services">See what we do</a>
</div>
<div class="hero__meta">
<span><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M2 7l3 3 7-7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>Most builds ship in 10–14 working days</span>
<span><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M7 1v6l4 2" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>Fixed quote within one business day</span>
<span><svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M7 1l1.8 4 4.2.5-3.2 2.9 1 4.1L7 10.4 3.2 12.5l1-4.1L1 5.5l4.2-.5L7 1z" fill="currentColor"/></svg>Cape Town · Johannesburg studio</span>
</div>
<div class="hero__panel" aria-hidden="true">
<div class="panel__bar">
<span class="panel__dot"></span><span class="panel__dot is-on"></span><span class="panel__dot"></span>
<span class="panel__url">stackless.app / c / meridian-coffee</span>
</div>
<div class="panel__body">
<div class="panel__col">
<h3>Active workflows</h3>
<div class="panel__row"><span>Lead intake → CRM</span><span class="pill">RUNNING</span></div>
<div class="panel__row"><span>Quote follow-up email</span><span class="pill is-2">REVIEW</span></div>
<div class="panel__row"><span>Inventory sync (Shopify → Xero)</span><span class="pill">RUNNING</span></div>
<div class="panel__row"><span>WhatsApp support agent</span><span class="pill is-3">DRAFT</span></div>
<div class="panel__row"><span>Monthly client report</span><span class="pill">RUNNING</span></div>
</div>
<div class="panel__col">
<h3>This month</h3>
<div class="panel__stat">
<div><div class="num">142</div><div class="lbl">Hours saved</div></div>
<div><div class="num">38</div><div class="lbl">Leads handled by agent</div></div>
<div><div class="num">R48.2k</div><div class="lbl">Tools consolidated</div></div>
<div><div class="num">98.4%</div><div class="lbl">Uptime</div></div>
</div>
</div>
</div>
</div>
</div>
</section>
<section class="trust">
<div class="wrap">
<div class="trust__lbl">Trusted by operators at</div>
<div class="trust__row">
<div class="trust__logo">meridian<small>coffee co</small></div>
<div class="trust__logo">kaelo<small>logistics</small></div>
<div class="trust__logo">ovira<small>wellness</small></div>
<div class="trust__logo">stanfield<small>&amp; co</small></div>
<div class="trust__logo">nautilus<small>imports</small></div>
<div class="trust__logo">deux<small>studio</small></div>
</div>
</div>
</section>
<section class="section" id="services">
<div class="wrap">
<div class="section__head">
<span class="section__kicker">What we ship</span>
<h2>Six things we do, end to end.</h2>
<p class="section__sub">Pick one, or layer them. Every engagement is a fixed quote with weekly demos — no retainers you can't get out of, no hidden scope creep.</p>
</div>
<div class="services">
<article class="service">
<span class="service__num">/ 01</span>
<div class="service__icon"><svg viewBox="0 0 24 24" fill="none"><rect x="3" y="4" width="18" height="14" rx="2" stroke="currentColor" stroke-width="1.7"/><path d="M3 8h18" stroke="currentColor" stroke-width="1.7"/><circle cx="6.5" cy="6" r="0.7" fill="currentColor"/><circle cx="9" cy="6" r="0.7" fill="currentColor"/><path d="M8 18l-2 3M16 18l2 3" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/></svg></div>
<h3>Website builds &amp; upgrades</h3>
<p>Marketing sites, ecommerce, internal portals — designed, built and hosted. You get a real CMS, not a black box.</p>
<ul class="service__list"><li>Brand &amp; UX</li><li>Webflow · Next.js · Shopify</li><li>Hosting + analytics included</li></ul>
</article>
<article class="service">
<span class="service__num">/ 02</span>
<div class="service__icon"><svg viewBox="0 0 24 24" fill="none"><rect x="4" y="7" width="16" height="11" rx="2" stroke="currentColor" stroke-width="1.7"/><circle cx="9" cy="12.5" r="1.4" stroke="currentColor" stroke-width="1.7"/><circle cx="15" cy="12.5" r="1.4" stroke="currentColor" stroke-width="1.7"/><path d="M12 4v3" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/></svg></div>
<h3>AI agents, installed</h3>
<p>Trained on your docs, wired into your inbox and CRM. They draft, route, escalate and reply — under the rules you set.</p>
<ul class="service__list"><li>Sales &amp; support agents</li><li>Voice + chat</li><li>Human escalation paths</li></ul>
</article>
<article class="service">
<span class="service__num">/ 03</span>
<div class="service__icon"><svg viewBox="0 0 24 24" fill="none"><path d="M4 7l8-3 8 3-8 3-8-3z" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/><path d="M4 12l8 3 8-3M4 17l8 3 8-3" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/></svg></div>
<h3>Rent-an-AI-Agent</h3>
<p>A dedicated agent on a monthly retainer. Trained once, refined forever. Pause or swap any month.</p>
<ul class="service__list"><li>From R9 800 / month</li><li>Weekly refinement calls</li><li>Cancel any time</li></ul>
</article>
<article class="service">
<span class="service__num">/ 04</span>
<div class="service__icon"><svg viewBox="0 0 24 24" fill="none"><path d="M13 3L5 14h6l-1 7 8-11h-6l1-7z" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/></svg></div>
<h3>Outreach &amp; lead-gen automation</h3>
<p>Cold and warm pipelines that don't sound like a bot. Personalisation tokens pulled from live data, not scraped fluff.</p>
<ul class="service__list"><li>Email + LinkedIn + WhatsApp</li><li>Reply detection &amp; routing</li><li>CRM hand-off</li></ul>
</article>
<article class="service">
<span class="service__num">/ 05</span>
<div class="service__icon"><svg viewBox="0 0 24 24" fill="none"><circle cx="6" cy="6" r="2.5" stroke="currentColor" stroke-width="1.7"/><circle cx="18" cy="6" r="2.5" stroke="currentColor" stroke-width="1.7"/><circle cx="12" cy="18" r="2.5" stroke="currentColor" stroke-width="1.7"/><path d="M8 7l3 9M16 7l-3 9M8 6h8" stroke="currentColor" stroke-width="1.7"/></svg></div>
<h3>Workflow integrations</h3>
<p>Xero ↔ Shopify, HubSpot ↔ Slack, WhatsApp ↔ your inbox. The plumbing between the tools you already pay for, made reliable.</p>
<ul class="service__list"><li>n8n · Make · Zapier</li><li>Custom APIs when needed</li><li>Error alerts + retries</li></ul>
</article>
<article class="service">
<span class="service__num">/ 06</span>
<div class="service__icon"><svg viewBox="0 0 24 24" fill="none"><rect x="6" y="3" width="12" height="18" rx="2" stroke="currentColor" stroke-width="1.7"/><path d="M10 18h4" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/></svg></div>
<h3>Apps, dashboards &amp; internal tools</h3>
<p>Custom web apps your team actually opens on Monday morning. Field-ops tools, ops dashboards, customer portals.</p>
<ul class="service__list"><li>React + Postgres</li><li>Role-based access</li><li>Mobile-responsive by default</li></ul>
</article>
</div>
</div>
</section>
<section class="section" id="how">
<div class="wrap">
<div class="section__head">
<span class="section__kicker">How it works</span>
<h2>Three steps. No mystery.</h2>
<p class="section__sub">We start with a 30-minute call, send a fixed quote the next day, and demo something real every Friday until it's shipped.</p>
</div>
<div class="how">
<article class="step"><div class="step__num">01</div><h3>Tell us what's broken</h3><p>A short call. We map what's slowing you down — a missing site, a lead pipeline, an inbox that eats your mornings — and what would actually move the needle.</p><span class="step__time">~ 30 min call</span></article>
<article class="step"><div class="step__num">02</div><h3>Get a fixed quote</h3><p>You receive a one-page quote by the next business day: scope, deliverables, timeline, price. No retainer, no surprise hours, no fine print.</p><span class="step__time">~ 1 day turnaround</span></article>
<article class="step"><div class="step__num">03</div><h3>Watch it ship weekly</h3><p>Every Friday you get a Loom showing what's live that week. You sign off in Slack, not in a 90-minute status meeting.</p><span class="step__time">10–14 day builds</span></article>
</div>
</div>
</section>
<section class="section" id="why">
<div class="wrap why">
<div class="why__col">
<span class="section__kicker">Why Stackless</span>
<h2>The studio you call when your last agency ghosted you.</h2>
<p class="section__sub">We've been the people who fix what other people built. Most of our work comes from teams who tried once, got burned, and want a partner who actually answers the phone.</p>
</div>
<div class="why__list">
<div class="why__item"><span class="why__num">01</span><div><h4>You always know who's building</h4><p>Two named humans, plus the agent they supervise. No black-box dev shop, no junior team learning on your dime.</p></div></div>
<div class="why__item"><span class="why__num">02</span><div><h4>Fixed quotes, real demos</h4><p>We send a price and a deadline, then show you something live every Friday. If we miss it, the next build's on us.</p></div></div>
<div class="why__item"><span class="why__num">03</span><div><h4>You own everything we build</h4><p>Code, content, accounts, agent prompts — all yours. We hand over the keys on day one. Walk away whenever you want.</p></div></div>
<div class="why__item"><span class="why__num">04</span><div><h4>Built for small teams, not enterprises</h4><p>We work with 5 to 200-person businesses. No procurement theatre, no 200-slide decks — just a call, a quote, and a build.</p></div></div>
</div>
</div>
</section>
<section class="section" id="pricing">
<div class="wrap">
<div class="section__head">
<span class="section__kicker">Pricing</span>
<h2>Honest tiers. No surprise invoices.</h2>
<p class="section__sub">Project pricing for one-off builds, monthly retainers for ongoing work. All prices in South African Rand, excl. VAT.</p>
</div>
<div class="pricing">
<article class="tier">
<span class="tier__name">Project</span>
<div class="tier__price">R18k<small>starting</small></div>
<p class="tier__desc">One-off website build or single integration.</p>
<ul class="tier__feat"><li>Fixed quote, fixed deadline</li><li>Weekly demos</li><li>30 days of post-launch support</li><li>You own everything</li></ul>
<a class="btn btn--ghost btn--sm" href="contact.html">Get a quote</a>
</article>
<article class="tier is-featured">
<span class="tier__name">Rent-an-AI-Agent</span>
<div class="tier__price">R9.8k<small>/ month</small></div>
<p class="tier__desc">A dedicated AI agent, refined weekly, cancel anytime.</p>
<ul class="tier__feat"><li>Trained on your docs &amp; tone</li><li>Inbox, CRM &amp; WhatsApp</li><li>Weekly refinement call</li><li>Human escalation paths</li></ul>
<a class="btn btn--primary btn--sm" href="pricing.html">See full pricing</a>
</article>
<article class="tier">
<span class="tier__name">Studio</span>
<div class="tier__price">R48k<small>/ month</small></div>
<p class="tier__desc">Ongoing builds, agents, and integrations in one retainer.</p>
<ul class="tier__feat"><li>Up to 3 active workstreams</li><li>Dedicated lead + agent</li><li>Priority response, same day</li><li>Quarterly roadmap reviews</li></ul>
<a class="btn btn--ghost btn--sm" href="contact.html">Book a discovery call</a>
</article>
</div>
</div>
</section>
<section class="wrap">
<div class="cta">
<div>
<h2>You've got a business to run. Let us take the software off your plate.</h2>
<p>Tell us what's eating your week. We'll come back with a fixed quote and a real plan — usually within one business day.</p>
</div>
<div class="cta__actions">
<a class="btn btn--primary" href="contact.html">Start a project <span class="arrow">→</span></a>
<span class="cta__small">or email hello@stackless.app</span>
</div>
</div>
</section>
<footer>
<div class="wrap">
<div class="foot">
<div class="foot__brand">
<a href="/" class="brand"><span class="brand__mark"><svg viewBox="0 0 26 26" fill="none" aria-hidden="true"><circle cx="13" cy="4" r="2.6" fill="#F76857"/><circle cx="13" cy="22" r="2.6" fill="#F76857"/><path d="M5 9c0-2.8 2.2-5 5-5h1c2.8 0 5 2.2 5 5v0c0 1.4-1.1 2.5-2.5 2.5H7.5C6.1 11.5 5 12.6 5 14v0c0 2.8 2.2 5 5 5h1" stroke="#1DB897" stroke-width="2.6" stroke-linecap="round"/><path d="M11 11.5h6c1.4 0 2.5 1.1 2.5 2.5v0c0 1.4-1.1 2.5-2.5 2.5H11" stroke="#1DB897" stroke-width="2.6" stroke-linecap="round" opacity="0.55"/></svg></span>stackless</a>
<p>The on-call studio for small and medium businesses. Build, automate, integrate — without hiring an in-house team.</p>
</div>
<div><h5>Services</h5><ul><li><a href="#services">Websites</a></li><li><a href="#services">AI agents</a></li><li><a href="pricing.html">Rent-an-AI-Agent</a></li><li><a href="#services">Integrations</a></li><li><a href="#services">Apps &amp; dashboards</a></li></ul></div>
<div><h5>Studio</h5><ul><li><a href="#how">How we work</a></li><li><a href="#why">Why us</a></li><li><a href="pricing.html">Pricing</a></li><li><a href="contact.html">Start a project</a></li></ul></div>
<div><h5>Reach us</h5><ul><li><a href="mailto:hello@stackless.app">hello@stackless.app</a></li><li><a href="tel:+27210000000">+27 21 000 0000</a></li><li>Cape Town · Johannesburg</li><li>Mon–Fri, 08:00–17:00 SAST</li></ul></div>
</div>
<div class="foot__legal">
<span>© 2026 Stackless (Pty) Ltd · Reg. 2024/184256/07</span>
<span>Made on the African continent.</span>
</div>
</div>
</footer>
<script>
(function () {
var btn = document.getElementById('menuBtn');
var nav = document.getElementById('nav');
if (btn && nav) {
btn.addEventListener('click', function () { nav.classList.toggle('is-open'); });
nav.addEventListener('click', function (e) { if (e.target.tagName === 'A') nav.classList.remove('is-open'); });
}
var stats = document.querySelectorAll('.panel__stat .num');
if ('IntersectionObserver' in window && stats.length) {
var io = new IntersectionObserver(function (entries) {
entries.forEach(function (entry) {
if (!entry.isIntersecting) return;
var el = entry.target;
if (el.dataset.done) return;
el.dataset.done = '1';
var raw = el.textContent.trim();
var isPercent = raw.endsWith('%');
var isRand = raw.startsWith('R');
var suffix = isPercent ? '%' : (isRand ? 'k' : '');
var num = parseFloat(raw.replace(/[^\d.]/g, '')) || 0;
var prefix = isRand ? 'R' : '';
var duration = 900;
var start = null;
function step(t) {
if (!start) start = t;
var p = Math.min(1, (t - start) / duration);
var v = num * (0.6 + 0.4 * (1 - Math.pow(1 - p, 3)));
var formatted = num >= 100 ? Math.round(v) : v.toFixed(1);
el.textContent = prefix + formatted + suffix;
if (p < 1) requestAnimationFrame(step);
else el.textContent = raw;
}
requestAnimationFrame(step);
});
}, { threshold: 0.4 });
stats.forEach(function (el) { io.observe(el); });
}
})();
</script>
</body>
</html>

View file

@ -0,0 +1,21 @@
{
"version": 1,
"kind": "html",
"title": "Stackless — marketing site (3 pages)",
"entry": "stackless-site.html",
"renderer": "html",
"status": "complete",
"exports": [
"html",
"pdf",
"zip"
],
"primary": true,
"createdAt": "2026-06-18T09:57:16.514Z",
"updatedAt": "2026-06-18T09:57:16.523Z",
"metadata": {
"identifier": "stackless-site",
"artifactType": "text/html",
"inferred": false
}
}