2026-06-18 10:22:31 +00:00
<!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 >
2026-06-18 11:54:52 +00:00
< meta name = "description" content = "Tell us what you need built, automated, or integrated. We reply within one business day with a clear scope and quote." / >
< meta name = "theme-color" content = "#1DB897" / >
<!-- Favicons -->
< link rel = "icon" type = "image/svg+xml" href = "favicon.svg" / >
< link rel = "icon" type = "image/png" sizes = "32x32" href = "favicon-32.png" / >
< link rel = "apple-touch-icon" sizes = "180x180" href = "apple-touch-icon.png" / >
< link rel = "manifest" href = "manifest.json" / >
<!-- Open Graph -->
< meta property = "og:type" content = "website" / >
< meta property = "og:site_name" content = "Stackless" / >
< meta property = "og:title" content = "Start a project — Stackless" / >
< meta property = "og:description" content = "Tell us what you need built, automated, or integrated. Reply within one business day with a clear scope and quote." / >
< meta property = "og:url" content = "https://stackless.co.za/contact.html" / >
< meta property = "og:image" content = "https://stackless.co.za/og-card.png" / >
< meta property = "og:image:width" content = "1200" / >
< meta property = "og:image:height" content = "630" / >
< meta property = "og:image:alt" content = "Stackless — build · automate · integrate" / >
< meta property = "og:locale" content = "en_ZA" / >
<!-- Twitter / X -->
< meta name = "twitter:card" content = "summary_large_image" / >
< meta name = "twitter:title" content = "Start a project — Stackless" / >
< meta name = "twitter:description" content = "Tell us what you need built, automated, or integrated. Reply within one business day with a clear scope and quote." / >
< meta name = "twitter:image" content = "https://stackless.co.za/og-card.png" / >
< meta name = "twitter:image:alt" content = "Stackless — build · automate · integrate" / >
2026-06-18 10:22:31 +00:00
< 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 & 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 & 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 >
2026-06-18 15:51:31 +00:00
< script src = "chat-widget.js" defer > < / script >
2026-06-18 10:22:31 +00:00
< / body >
< / html >