split-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 9ed962174401
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 20ace674a35b849ffda63b30
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/ui.tsx
// Small shared pieces: dialog sheet, menu, toast, copy, share, icons.
import { useEffect, useRef, useState, type ReactNode } from "react";

/** A dialog's open state with an animated close: call close(), and onClose runs once the exit animation ends. */
export function useSheet(onClose: () => void) {
  const [closing, setClosing] = useState(false);
  return { closing, close: () => setClosing(true), onClosed: onClose };
}

const FOCUSABLE = "a[href], button:not(:disabled), input:not(:disabled), select:not(:disabled), textarea:not(:disabled), [tabindex]:not([tabindex='-1'])";

export function Modal({ title, sheet, children, footer }: {
  title: string; sheet: ReturnType<typeof useSheet>; children: ReactNode; footer?: ReactNode;
}) {
  const ref = useRef<HTMLDivElement>(null);
  const close = useRef(sheet.close);
  close.current = sheet.close;
  useEffect(() => {
    const prev = document.activeElement as HTMLElement | null;
    const onKey = (e: KeyboardEvent) => {
      if (e.key === "Escape") close.current();
      if (e.key !== "Tab" || !ref.current) return;
      // Keep Tab inside the dialog.
      const items = [...ref.current.querySelectorAll<HTMLElement>(FOCUSABLE)];
      const first = items[0], last = items[items.length - 1];
      if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last?.focus(); }
      else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first?.focus(); }
    };
    document.addEventListener("keydown", onKey);
    document.body.style.overflow = "hidden";
    (ref.current?.querySelector<HTMLElement>("[data-autofocus]") ?? ref.current)?.focus();
    return () => {
      document.removeEventListener("keydown", onKey);
      document.body.style.overflow = "";
      prev?.focus?.();
    };
  }, []);
  return (
    <div className={`dialog-backdrop${sheet.closing ? " leaving" : ""}`}
      onMouseDown={(e) => { if (e.target === e.currentTarget) sheet.close(); }}
      onAnimationEnd={(e) => { if (sheet.closing && e.target === ref.current) sheet.onClosed(); }}>
      <div className="dialog" role="dialog" aria-modal="true" aria-labelledby="dialog-title" ref={ref} tabIndex={-1}>
        <div className="dialog-head">
          <h2 className="dialog-title" id="dialog-title">{title}</h2>
          <button className="btn btn-quiet btn-icon" type="button" onClick={sheet.close} aria-label="Close"><Icon name="close" /></button>
        </div>
        <div className="dialog-body">{children}</div>
        {footer && <div className="dialog-foot">{footer}</div>}
      </div>
    </div>
  );
}

/** A "more" menu: a button that opens a short list of actions. */
export function Menu({ label, children }: { label: string; children: ReactNode }) {
  const ref = useRef<HTMLDetailsElement>(null);
  useEffect(() => {
    const onDown = (e: PointerEvent) => { if (ref.current?.open && !ref.current.contains(e.target as Node)) ref.current.open = false; };
    const onKey = (e: KeyboardEvent) => {
      if (e.key === "Escape" && ref.current?.open) { ref.current.open = false; ref.current.querySelector("summary")?.focus(); }
    };
    document.addEventListener("pointerdown", onDown);
    document.addEventListener("keydown", onKey);
    return () => { document.removeEventListener("pointerdown", onDown); document.removeEventListener("keydown", onKey); };
  }, []);
  return (
    <details className="dropdown" ref={ref}>
      <summary className="btn btn-line btn-icon" aria-label={label} title={label}><Icon name="more" /></summary>
      {/* Closing moves focus to the menu button, so a sheet opened from the menu returns focus there. */}
      <div className="dropdown-menu dropdown-menu-end" onClick={() => { if (ref.current) { ref.current.open = false; ref.current.querySelector("summary")?.focus(); } }}>{children}</div>
    </details>
  );
}

type ToastAction = { label: string; run: () => void };
let pushToast: ((text: string, action?: ToastAction) => void) | null = null;
export function toast(text: string, action?: ToastAction) { pushToast?.(text, action); }

export function Toaster() {
  const [item, setItem] = useState<{ text: string; action?: ToastAction; key: number; leaving?: boolean } | null>(null);
  useEffect(() => {
    let timer: number | undefined;
    pushToast = (text, action) => {
      setItem({ text, action, key: Date.now() });
      window.clearTimeout(timer);
      timer = window.setTimeout(() => setItem((t) => (t ? { ...t, leaving: true } : t)), action ? 6000 : 2600);
    };
    return () => { pushToast = null; window.clearTimeout(timer); };
  }, []);
  if (!item) return null;
  return (
    <div key={item.key} className={`toast${item.leaving ? " leaving" : ""}`} role="status"
      onAnimationEnd={() => { if (item.leaving) setItem(null); }}>
      {item.text}
      {item.action && (
        <button className="toast-action" type="button" onClick={() => { item.action!.run(); setItem({ ...item, action: undefined, leaving: true }); }}>
          {item.action.label}
        </button>
      )}
    </div>
  );
}

export async function copy(text: string, done = "Copied") {
  try {
    await navigator.clipboard.writeText(text);
    toast(done);
  } catch {
    window.prompt("Copy this:", text);
  }
}

/** The phone's share sheet where there is one; otherwise copies to the clipboard. */
export async function share(data: { url: string; title: string; text?: string }, done: string) {
  if (navigator.share && matchMedia("(pointer: coarse)").matches) {
    try { await navigator.share(data); return; } catch { /* cancelled */ }
  }
  await copy(data.text ? `${data.text}\n${data.url}` : data.url, done);
}

const PATHS: Record<string, ReactNode> = {
  close: <path d="M5 5l10 10M15 5 5 15" />,
  plus: <path d="M10 4v12M4 10h12" />,
  more: <><circle cx="5" cy="10" r="1.2" /><circle cx="10" cy="10" r="1.2" /><circle cx="15" cy="10" r="1.2" /></>,
  link: <path d="M8 12l4-4M7 9.5 5.2 11.3a2.8 2.8 0 1 0 4 4L11 13.5M13 10.5l1.8-1.8a2.8 2.8 0 1 0-4-4L9 6.5" />,
  arrow: <path d="M4 10h12m-4.5-4.5L16 10l-4.5 4.5" />,
  search: <><circle cx="9" cy="9" r="5" /><path d="m13 13 3.5 3.5" /></>,
  right: <path d="M7.5 4.5 13 10l-5.5 5.5" />,
};

export function Icon({ name, size = 18 }: { name: keyof typeof PATHS | string; size?: number }) {
  return (
    <svg width={size} height={size} viewBox="0 0 20 20" aria-hidden="true" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round">
      {PATHS[name]}
    </svg>
  );
}

Change history

Publish quality pass from owner chat
9ed9621744 · 5 hours ago
Bind agent address in policy constants
7effc1cbbd · 5 hours ago
Quality pass: cleaner group page, phone polish, fuller API, sourced comparison
5345219781 · 5 hours ago
Bounded source sync
277fb9ad96 · 1 day ago
Bounded source sync
e944552325 · 1 day ago