Kapsula runs your HTML in a sandboxed iframe and injects one global object, window.kapsula. Every method returns a Promise. The same file runs unchanged in a desktop browser — just check if (window.kapsula) before using it.
localStorage, indexedDB and cookies do not persist — use kapsula.storage. Requests to outside servers (fetch, XMLHttpRequest) are blocked for now; inline everything (CSS, JS, data). Fonts from Google Fonts load.kapsula.storage and restore it on load — never rely on in-memory variables or DOM alone.kapsula.ready is a Promise you can await explicitly).→ { name, projectId, platform, version, lang, limits }. platform is 'android', 'ios' or 'web'; lang is the shell's interface language ('en', 'ru', 'es', …); limits.alarms is the alarm cap (16). The language is also available synchronously as window.__KAPSULA_PROJECT__.lang.
Schedules an exact OS alarm. at — epoch milliseconds or a Date, must be in the future; title ≤ 120 chars (defaults to the project name); body ≤ 300 chars. Returns { id }. On first use the system asks the user for notification permission; if denied, the Promise rejects. Sound and vibration are handled by Kapsula (a built-in alarm sound on both platforms), you don't configure them.
const { id } = await kapsula.notify.schedule({
at: Date.now() + 25 * 60 * 1000,
title: 'Break time',
body: 'Stand up, look away from the screen'
});
Cancels one alarm by id, or all alarms of this project when called without arguments. Returns { cancelled }. Tip: call cancel() before re-scheduling so the 16-slot limit doesn't fill up.
→ [{ id, at, title, body }] — alarms still pending for this project.
Per-project key–value storage that survives app restarts and code updates. value is any JSON-serialisable value; get resolves to it or null. Keep it small (settings, session state), not megabytes.
await kapsula.storage.set('session', { startedAt: Date.now(), step: 3 });
const s = await kapsula.storage.get('session'); // → object or null
Plays a short built-in sound while the page is open: 'beep' (default), 'triple', 'alarm'. For sounds while the app is closed, use an alarm instead.
Vibrates for ms milliseconds (default 300).
Rejections carry a message: time in the past, alarm limit reached, notification permission denied, unknown id. Wrap calls in try/catch and show the message in your UI — there is no developer console inside the sandbox yet (planned for 0.3).
Works in a browser tab (without alarms) and in Kapsula (with a real alarm). Save as tea.html, or paste the code into Kapsula.
<!DOCTYPE html>
<html><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Tea timer</title>
<style>
body{font-family:system-ui;background:#0A111F;color:#F0EBE0;margin:0;padding:24px;text-align:center}
button{font:inherit;font-size:18px;padding:14px 22px;border:0;border-radius:12px;background:#FFB020;margin:8px}
#left{font-size:64px;font-variant-numeric:tabular-nums;margin:24px 0}
</style></head>
<body>
<h1>Tea timer</h1>
<div id="left">–</div>
<button onclick="start(3)">3 min</button>
<button onclick="start(5)">5 min</button>
<button onclick="stop()">Stop</button>
<script>
const K = window.kapsula || null; // null in a plain browser
let endAt = 0, timer = null;
async function save(){ if (K) await K.storage.set('endAt', endAt); }
async function load(){ endAt = K ? (await K.storage.get('endAt')) || 0 : 0; tick(); }
async function start(min){
endAt = Date.now() + min*60*1000;
await save();
if (K) {
try {
await K.notify.cancel(); // free the slot
await K.notify.schedule({ at: endAt, title: 'Tea is ready', body: min + ' minutes are up' });
} catch (e) { alert(e.message); }
}
tick();
}
async function stop(){ endAt = 0; await save(); if (K) K.notify.cancel(); tick(); }
function tick(){
clearTimeout(timer);
const left = endAt - Date.now();
if (!endAt) { document.getElementById('left').textContent = '–'; return; }
if (left <= 0) { document.getElementById('left').textContent = 'Ready!'; if (K) K.sound.play('triple'); endAt = 0; save(); return; }
const m = Math.floor(left/60000), s = Math.floor(left/1000)%60;
document.getElementById('left').textContent = m + ':' + String(s).padStart(2,'0');
timer = setTimeout(tick, 250);
}
load();
</script>
</body></html>
kapsula.fetch — network requests from the sandbox with per-project permission.kapsula://add?url=… and "Share to Kapsula" from other apps.console.log from your page shown in the shell.Kapsula executes only code you add yourself. Questions: hello@kapsula.app. See also the prompt for AI assistants.