Web & apps

Websites and mobile apps built to order

We design and build fast websites and apps for iOS and Android. No templates and no lock-in: the code, the domain and every account are in your name.

What we build

Websites

Company sites and landing pages that load instantly, ready to rank on Google and to run in several languages from day one.

Mobile apps

iOS and Android from a single codebase: each feature is written once and works on both. Publishing to the App Store and Google Play included.

Backend and integrations

Database, user accounts, roles and permissions, card payments through Stripe, email, push notifications and API connections to outside services.

Support afterwards

A project does not end on launch day. We stay: we fix, measure, add features and keep the infrastructure running.

How we work

  1. The problem first

    Before a line of code we work out which process is eating your time or money. Sometimes the answer is not an app, and we say so.

  2. Parts that already work

    We do not reinvent authentication, payments or notifications. We use proven tools and spend the time on what is actually unique to your business.

  3. Everything in your name

    Code, domain, hosting and database are yours. You can take the whole thing to another team whenever you like, without asking us.

What we’ve built

Websites, web systems and iOS and Android apps — client work and our own products. Each comes with a detailed case study: the problem, what’s inside, the screens and the stack.

Projects

Engineering

The code this very site runs on

Excerpts exactly as they sit in the repository: the request form, contract signing, repair status and workshop hours. Anything we cut is marked “// …”.

Cloudflare Workerssrc/worker.js
if (form.company) return json({ ok: true });

const name = String(form.name ?? '').trim().slice(0, 200);
const contact = String(form.contact ?? '').trim().slice(0, 200);
const about = String(form.about ?? '').trim().slice(0, MAX_LEN);

if (!name || !contact || !about || form.consent !== true) {
  return json({ error: 'validation' }, 400);
}

if (env.TURNSTILE_SECRET) {
  const ip = request.headers.get('cf-connecting-ip');
  const ok = await verifyTurnstile(env.TURNSTILE_SECRET, form.turnstile, ip);
  if (!ok) return json({ error: 'captcha' }, 400);
}

A bot trap plus a Turnstile check: your inbox gets requests from people, and spam is dropped silently.

Cloudflare Workerssrc/contracts.js
export async function sha256Hex(text) {
  return toHex(await crypto.subtle.digest('SHA-256', enc.encode(text)));
}
// …
const now = new Date().toISOString();
await env.DB.prepare(
  `update contracts
      set status = 'signed', signed_at = ?, signer_name = ?, signer_image = ?,
          signer_ip = ?, signer_ua = ?, signed_hash = ?,
          signer_phone = ?, signer_address = ?
    where id = ? and status <> 'signed'`,
)
  .bind(
    // …
    await sha256Hex(row.body),
    // …
  )
  .run();

We record the time, IP, device and a SHA-256 of the text the person saw when signing: the signature can be proven and the document can’t be swapped.

Cloudflare Workerssrc/orders.js
const status = ALL_STATUSES.includes(b.status) ? b.status : row.status;
const now = new Date().toISOString();
// …
if (status !== row.status || clean(b.comment, 500)) {
  await addEvent(env, id, status, clean(b.comment, 500), now);
}

let mailed = false;
if (status === 'ready' && row.status !== 'ready') {
  mailed = await mailOrder(env, 'ready', {
    email: row.client_email,
    lang: row.lang,
    number: row.number,
    device: row.device,
    url: new URL(`/o/${id}`, SITE.url).href,
  });
}

Every status change goes into the history the client sees on their link, and the email goes out once — when the repair is ready.

browsersrc/scripts/shop-status.js
function canary(d) {
  const f = new Intl.DateTimeFormat('en-CA', {
    timeZone: 'Atlantic/Canary', year: 'numeric', month: '2-digit', day: '2-digit',
    hour: '2-digit', minute: '2-digit', hourCycle: 'h23', weekday: 'short',
  });
  const p = Object.fromEntries(f.formatToParts(d).map((x) => [x.type, x.value]));
  const dow = { Sun: 0, Mon: 1, Tue: 2, Wed: 3, Thu: 4, Fri: 5, Sat: 6 }[p.weekday];
  return { date: `${p.year}-${p.month}-${p.day}`, dow, mins: Number(p.hour) * 60 + Number(p.minute) };
}
const toMins = (hhmm) => Number(hhmm.slice(0, 2)) * 60 + Number(hhmm.slice(3, 5));
const workday = (c) => (HOURS[c.dow] && !HOLIDAYS.has(c.date) ? HOURS[c.dow] : null);

export function shopStatus(now = new Date()) {
  const c = canary(now);
  const h = workday(c);
  if (h && c.mins >= toMins(h[0]) && c.mins < toMins(h[1])) return { open: true, closes: h[1] };
  // …
}

The site knows on its own whether the workshop is open: Tenerife time, daylight saving and holidays, from the same source as the data Google reads.

The stack behind our products

Only what HostBase, Hourgrid, Mansaria and this site actually run on. Nothing listed for show.

  • Astro, Web
  • Next.js, Web
  • TypeScript, Web
  • Tailwind CSS, Web
  • Canvas, Web
  • React Native, Mobile
  • Expo, Mobile
  • PostgreSQL, Data
  • Supabase, Data
  • Cloudflare D1, Data
  • Cloudflare R2, Data
  • Workers KV, Data
  • Cloudflare Workers, Server
  • Cron Triggers, Server
  • Netlify, Server
  • Row Level Security, Security
  • Turnstile, Security
  • Web Crypto, Security
  • Stripe, Payments
  • Verifactu, Tax
  • Brevo, Email
  • Anthropic, AI
  • Groq, AI
  • Sentry, Monitoring

Already know what you need? Tick the tasks, the stack and how you want to work — the request builds itself.

Build your project in the configurator

Got an idea, or a process that eats your day?

Tell us on WhatsApp. The first conversation is free and exists to work out whether it is worth building at all.

WhatsApp