Documentation
Everything you need — whether you're studying or building on top of Focussstody.
Getting Started
Focussstody is a free study & focus timer. Every second you study earns you XP and $STUDY tokens, helps you climb the leaderboard, and builds your streak. There's also a live study room where you can see other people studying in real time.
Tip: Your username is public — it shows on the leaderboard and in the live study room.
The Timer
Tap the Pomodoro toggle to enable automatic focus/break cycles.
Each subject has a daily goal (default: 4 hours). A progress bar shows how far you are.
XP, Tokens & Ranks
Every second you study adds 1 XP. Tokens are calculated as XP × 0.016.
$STUDY tokens are in-app currency only — not real money. Spend them in the Store on themes, badges, and sound packs.
Your rank is based on total XP (= total seconds studied).
Study at least once every calendar day to maintain your streak. Skipping a day resets it to 1. The streak counter is shown at the top of the app.
Daily Challenges
A new challenge is available every day. Complete the goal and tap Claim to earn bonus $STUDY tokens.
You can only claim each challenge once. Progress updates live while the timer runs.
Store & Themes
Open the Store from the nav. Spend $STUDY tokens on:
Purchased themes appear in the Theme grid on the main page. Click any owned theme to apply it instantly.
Pro
Pro is a one-time donation upgrade. Pro perks:
How to get Pro: Tap the ₿ Donate button, send any amount via crypto, then DM @focussstody_official on Instagram. We'll send you an unlock code.
Live Study Room
While your timer is running, you automatically appear in the live study room. You can see other people's usernames, subjects, and how long they've been studying.
Privacy & Data
All study data (sessions, XP, streak, notes) is synced to Supabase. We never sell or advertise with your data. Full details in the Privacy Policy.
You can access Focussstody over Tor at lqp6oerxidpewa7zxpkqqo6pa3pqwurm24vgyspvry32exrhooqxgiyd.onion. The .onion site provides the same ui, timer as the clearnet version.
In our mirror, all your data remains only in your device. No sync, no tracker, no auth. Just stay real anonymous.
To use the .onion, simply open it in a Tor browser. Your progress will be saved locally in that browser's storage. You can switch back to the clearnet version anytime to sync your data with an account.
Note: In the Tor mirror, all themes are free and we provide an exclusive theme for .onion users.
Overview
Focussstody is a vanilla JS ES-module app — no bundler, no framework. Pages are plain HTML files that import JS modules directly. Backend is entirely Supabase (Postgres + Auth + Realtime).
Supabase Schema
profiles
id uuid PK (= auth.users.id)
username text UNIQUE
email text
is_pro bool DEFAULT false
study_data
user_id uuid FK → profiles.id
sessions jsonb -- { "CODING": 3600, "MATH": 1800 }
total_exp int
weekly_log jsonb -- { "2026-05-01": 3600 }
streak int
last_study_date date
goal_hours jsonb -- { "CODING": 4 }
notes jsonb -- [{ date, subject, text, duration }]
store_items jsonb -- ["midnight", "galaxy_brain"]
active_theme text
updated_at timestamptz
daily_challenges
id uuid PK
date date UNIQUE
type text -- 'study_time' | 'sessions' | 'streak'
target int
reward int -- XP bonus
challenge_completions
user_id uuid FK → profiles.id
challenge_id uuid FK → daily_challenges.id
PRIMARY KEY (user_id, challenge_id)
Core Modules
timer.jsPure timing logic — no DOM. Receives callbacks, returns data.
const timer = new StudyTimer(
(data) => render(data), // onTick — fires every second
(payload) => save(payload) // onSave — fires on pause/note/reset
);
timer.loadFromDB(row); // hydrate from Supabase row
timer.start('CODING'); // begin session
timer.pause(); // stop + save delta
timer.addNote(subject, text, duration);
timer.reset(); // wipe all data
timer.getStats(); // → { sessions, total_exp, rank, weeklyData, … }
timer.getRank(exp); // → { name, level, next, prev }
timer.getGoal(subject); // → number (hours)
timer.setGoal(subject, hours);
The onTick callback receives { formatted, raw, rawTotalExp, progress, rank } every second. raw is current subject seconds; rawTotalExp is live total (not yet persisted).
supabase.js · authcheck.jsAll auth flows live in supabase.js. Multi-page apps use requireAuth() from authcheck.js as a guard at the top of each page script.
// supabase.js exports
signUp(username, email, password)
signIn(username, password) // looks up email by username first
signOut()
getSession() // reads localStorage — no network
// authcheck.js
import { requireAuth } from './authcheck.js';
const { supabase, userId, username, profile, studyRow }
= await requireAuth();
// → redirects to login.html if no session
pro.jsHandles Pro status, theme switching, and CSV export.
import { ProManager, THEMES } from './pro.js';
const pro = new ProManager();
pro.isPro // bool
await pro.unlock(code) // → true/false
pro.applyTheme(key, ownedItems) // sets CSS vars on :root
pro.restoreTheme(ownedItems) // call on page load
pro.exportCSV(timerStats, username)
THEMES is a plain object keyed by theme ID. Each entry has: accent, accentDim, bgLight, bgFocus, textPrimary, miningFrom, miningTo, free.
To add a new theme, add an entry to THEMES in pro.js and a matching gradient in the gradients map inside buildThemeGrid() in app.js.
store.jsManages purchases stored in localStorage (keyed per username) and synced to Supabase's store_items column.
const store = new StoreManager(username);
store.owns(itemId) // → bool
store.buy(id, price, tokens, isPro)
// → { ok: true, cost } | { ok: false, reason }
store.setActiveSound(soundId)
store.setActiveBadge(badgeId)
store.playTickSound() // plays active sound each second
To add a catalog item, push to STORE_ITEMS.themes, .badges, or .sounds in store.js. Each item needs { id, name, price, proFree, emoji } — plus preview (two hex colours) for themes.
room.jsUses Supabase Realtime presence channels. Each user tracks { username, subject, startedAt }.
const room = new StudyRoom(username, (users) => render(users));
await room.join(); // subscribe to channel
await room.startStudying(subject); // track presence
await room.stopStudying(); // untrack
await room.leave(); // unsubscribe
The onUpdate callback receives an array of { username, subject, startedAt, isMe } sorted by join time.
pomodoro.jsSelf-contained — no Supabase. Settings saved to localStorage under fv_pomodoro.
const pomo = new PomodoroManager();
pomo.saveSettings(focusMin, breakMin);
pomo.start(onTick, onPhaseSwitch);
// onTick → { formatted, remaining, phase, pomosToday }
// onPhaseSwitch → 'focus' | 'break'
pomo.pause();
pomo.stop();
pomo.isRunning // bool
pomo.phase // 'focus' | 'break'
pomo.settings // { focusMin, breakMin }
challenges.jsconst cm = new ChallengeManager(userId, (reward) => {
// called on successful claim
});
await cm.load() // → { challenge, completed }
cm.getProgress(studyData) // → 0–1
cm.isClaimable(studyData) // → bool
await cm.claim() // → bool
Challenges are seeded in the daily_challenges table. The manager uses challenge_completions to prevent double-claiming. Token award falls back to a direct UPDATE if the increment_exp RPC isn't available.
sliding-tabs.jsUniversal animated pill-tab component. Works on any container with <button> children.
import { SlidingTabs } from './sliding-tabs.js';
const tabs = new SlidingTabs(trackEl, (index, btn) => {
// called when tab changes
});
tabs.select(index); // programmatically switch
tabs.refresh(); // recalculate pill if layout changes
PWA & Service Worker
sw.jsFocussstody is a PWA — installable on mobile and desktop. The service worker caches core assets for offline use via a cache-first strategy.
focussstody-v1 — bump the version string in sw.js to force a cache refresh on deploy.ASSETS array lists files that are pre-cached on install. Add new pages/scripts there.Theme System CSS Variables
All theme values are written to :root by ProManager.applyTheme(). Use these in any page or component:
--accent /* primary accent colour */
--accent-dim /* accent at ~40% opacity */
--text-primary /* dark foreground for light backgrounds */
--bg-light /* page background colour */
--bg-focus /* used in the mining/timer card gradient */
--dropdown-bg /* context-aware dropdown background */
The global --bg, --ink, --ink2, --glass, --border variables are set in each page's own CSS and flip via the html.dark class. Dark mode state is persisted in localStorage under darkMode.
Contributing & Contact
DM @focussstody_official on Instagram. Bugs get fixed fast; good suggestions get built.