Documentation

Docs

Everything you need — whether you're studying or building on top of Focussstody.

What is Focussstody?

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.

Creating an Account

  1. Go to the login page and click Sign Up.
  2. Enter a username, email address, and password. No phone number required.
  3. Hit Create Account. You'll be taken straight to the app.

Tip: Your username is public — it shows on the leaderboard and in the live study room.

Starting a Session

  1. Pick a subject from the dropdown (or add a custom one via + Add).
  2. Press START FOCUS — or hit Space on your keyboard.
  3. The timer counts up. Every second adds 1 XP and 0.016 $STUDY tokens.
  4. Press PAUSE (or Space) to stop. You'll be prompted to add a session note.

Pomodoro Mode

Tap the Pomodoro toggle to enable automatic focus/break cycles.

  • Default: 25 min focus · 5 min break
  • Customise both durations in the settings that appear below the toggle.
  • A two-tone audio ping plays when a phase switches.
  • Pomodoro mode works alongside the regular timer — XP keeps accumulating.

Daily Goals

Each subject has a daily goal (default: 4 hours). A progress bar shows how far you are.

  • Tap Edit Goal to change it for the selected subject.
  • Goals are stored per-subject, so Coding and Math can have different targets.

XP & $STUDY Tokens

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.

Ranks

Your rank is based on total XP (= total seconds studied).

Apprentice — 0 XP
Scholar — 3,600 XP (1h)
Analyst — 10,800 XP (3h)
Researcher — 36,000 XP (10h)
Grand Master — 86,400 XP (24h)

Streaks

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.

How Challenges Work

A new challenge is available every day. Complete the goal and tap Claim to earn bonus $STUDY tokens.

  • study_time — study for X minutes today
  • sessions — complete X study sessions today
  • streak — reach a streak of X days

You can only claim each challenge once. Progress updates live while the timer runs.

Buying Items

Open the Store from the nav. Spend $STUDY tokens on:

Themes
Badges / Titles

Purchased themes appear in the Theme grid on the main page. Click any owned theme to apply it instantly.

Available Themes

Indigo (free)
Midnight — 20 $STUDY
Forest — 50 $STUDY
Sunset — 75 $STUDY
Ocean — 75 $STUDY
Mono — 100 $STUDY
Cherry — 150 $STUDY
Galaxy — 200 $STUDY
Gold — 300 $STUDY

What is Pro?

Pro is a one-time donation upgrade. Pro perks:

  • All themes available for free (no token cost)
  • All badges available for free
  • CSV export of your study history

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.

Studying Together

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.

  • Presence is powered by Supabase Realtime.
  • You're removed from the room as soon as you pause or close the tab.
  • No chat, no DMs — just shared presence.

Your 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.

Tor Mirror

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.

Architecture

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).

focussstody/ index.html # Landing / marketing app.html # Main timer app (auth-gated) login.html # Sign-up / sign-in stats.html # Personal stats page leaderboard.html # Global leaderboard friends.html # Friends system store.html # $STUDY token store faq.html privacy.html docs.html style.css # Global Tailwind-like utilities sw.js # Service worker (PWA) manifest.json js/ app.js # AppController — main orchestrator timer.js # StudyTimer class supabase.js # DB client + all queries authcheck.js # requireAuth() helper pro.js # ProManager + THEMES registry store.js # StoreManager + catalog room.js # StudyRoom (Realtime presence) challenges.js # ChallengeManager pomodoro.js # PomodoroManager sliding-tabs.js # Reusable tab component

Tables

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)

StudyTimer timer.js

Pure 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).

Authentication supabase.js · authcheck.js

All 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

ProManager & Themes pro.js

Handles 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.

StoreManager store.js

Manages 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.

StudyRoom — Realtime Presence room.js

Uses 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.

PomodoroManager pomodoro.js

Self-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 }

ChallengeManager challenges.js

const 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.

SlidingTabs sliding-tabs.js

Universal 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 Setup sw.js

Focussstody is a PWA — installable on mobile and desktop. The service worker caches core assets for offline use via a cache-first strategy.

  • Cache name: focussstody-v1 — bump the version string in sw.js to force a cache refresh on deploy.
  • The ASSETS array lists files that are pre-cached on install. Add new pages/scripts there.
  • Dynamic fetches fall through to the network if not cached.

CSS Variables Reference

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.

Reporting Bugs / Suggestions

DM @focussstody_official on Instagram. Bugs get fixed fast; good suggestions get built.