still-free.openapps.xyz

Source

This app’s code and revision history are public. Download a copy or explore the files below.

Public
The app’s code and revision history are public.
Owned app · rev 0fa0e84d537b
Run it yourself
Take the whole tree and run it on your own machine. The download needs no account. Cloning needs a sign-in.
Download .zip
bounded clone a8c677472ab5691741798a3d
A copy you download is not bounded. The guarantees this app runs under - declared egress, proven invariants, writes that fail closed - live in the runtime serving this page, not in the files. What you get is auditable source you can read and run, with no runtime guarantees attached.
← All files
src/App.tsx
// The app itself, behind the landing page: Today, Breathe, Meditate, Sounds, Sleep and Progress,
// with tabs at the bottom on phones and in the header on larger screens.
import { useEffect } from "react";
import { createPortal } from "react-dom";
import { useLocation } from "./lib/router";
import { sync } from "./lib/progress";
import { STORIES } from "./content/stories";
import { SESSIONS } from "./content/sessions";
import { PATTERNS } from "./content/patterns";
import { BreatheRoute } from "./Breathe";
import { MeditateRoute, NIGHT_SESSIONS } from "./Meditate";
import { Sounds, MiniPlayer } from "./Sounds";
import { SleepRoute } from "./Sleep";
import { Today } from "./Today";
import { Progress } from "./Progress";
import { A, Icon } from "./ui";

const TABS = [
  { id: "today", label: "Today", icon: "today" },
  { id: "breathe", label: "Breathe", icon: "breathe" },
  { id: "meditate", label: "Meditate", icon: "meditate" },
  { id: "sounds", label: "Sounds", icon: "sounds" },
  { id: "sleep", label: "Sleep", icon: "sleep" },
] as const;

function titleFor(section: string, slug?: string): string {
  const name =
    (section === "breathe" && PATTERNS.find((p) => p.slug === slug)?.name) ||
    (section === "meditate" && (slug === "timer" ? "Meditation timer" : SESSIONS.find((s) => s.slug === slug)?.title)) ||
    (section === "sleep" && STORIES.find((s) => s.slug === slug)?.title) ||
    section.charAt(0).toUpperCase() + section.slice(1);
  return `${name} | Still`;
}

export default function App() {
  const { path, query } = useLocation();
  const [section, slug] = path.split("/").filter(Boolean);
  const night = section === "sleep" || (section === "meditate" && NIGHT_SESSIONS.has(slug ?? ""));

  useEffect(() => { void sync().catch(() => null); }, []);
  useEffect(() => { document.title = titleFor(section, slug); }, [section, slug]);
  useEffect(() => {
    document.body.classList.toggle("night", night);
    document.querySelector('meta[name="theme-color"]')?.setAttribute("content", night ? "#10131a" : "#fdfdfc");
    return () => { document.body.classList.remove("night"); };
  }, [night]);

  const nav = document.getElementById("app-nav");
  return (
    <>
      {nav && createPortal(
        <>
          {[...TABS, { id: "progress", label: "Progress", icon: "progress" } as const].map((t) => (
            <A key={t.id} href={`/${t.id}`} className={t.id === "progress" ? "nav-progress" : "nav-tab"} aria-current={section === t.id ? "page" : undefined} aria-label={t.id === "progress" ? "Progress" : undefined}>
              {t.id === "progress" && <Icon name="progress" size={20} />}<span>{t.label}</span>
            </A>
          ))}
        </>,
        nav,
      )}
      <div className="app-body">
        {section === "today" && <Today />}
        {section === "breathe" && <BreatheRoute slug={slug} query={query} />}
        {section === "meditate" && <MeditateRoute slug={slug} query={query} />}
        {section === "sounds" && <Sounds query={query} />}
        {section === "sleep" && <SleepRoute slug={slug} />}
        {section === "progress" && <Progress />}
      </div>
      {section !== "sounds" && section !== "sleep" && <MiniPlayer />}
      <nav className="tabbar" aria-label="Still">
        {TABS.map((t) => (
          <A key={t.id} href={`/${t.id}`} className="tab" aria-current={section === t.id ? "page" : undefined}>
            <Icon name={t.icon} />
            <span>{t.label}</span>
          </A>
        ))}
      </nav>
    </>
  );
}

Change history

Link to OpenApps pages and add a contribution strip
0fa0e84d53 · 19 hours ago
Bind the agent address in the policy
6f3baa1c72 · 1 day ago
Add favicon.ico so browsers that ask for it stop getting a 404
1a004fe0e3 · 1 day ago
Bounded source sync
afe642fe50 · 2 days ago
Bounded source sync
2c581f55ea · 2 days ago
Bounded source sync
375934c28b · 2 days ago
Bounded source sync
74ff37d51a · 2 days ago