Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ your numbers follow you across devices. See [`SUPABASE_SETUP.md`](./SUPABASE_SET
game / day-of-week heatmap.
- **📈 Charts** — bankroll curve vs EV, session-result histogram, rolling
hourly rate, monthly P&L, session-length-vs-profit scatter, and a Monte Carlo
variance simulation. All dark-themed (Recharts).
variance simulation. Clean light theme (Recharts).
- **🧮 Calculators** — a bankroll calculator (required roll for a target Risk of
Ruin, months to build) and a variance simulator (10 Monte Carlo paths,
best/worst/median, probability of being up, expected max drawdown).
Expand Down
16 changes: 12 additions & 4 deletions index.html
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
<!doctype html>
<html lang="en" class="dark">
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
<meta name="theme-color" content="#0b0f0d" />
<meta name="theme-color" content="#f1f5f9" />
<meta name="description" content="Track your poker sessions, bankroll, win rate, and variance." />

<!-- Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap"
rel="stylesheet"
/>

<!-- Icons -->
<link rel="icon" href="/icon.svg" type="image/svg+xml" />
<link rel="icon" href="/icons/favicon-32.png" sizes="32x32" type="image/png" />
Expand All @@ -14,12 +22,12 @@
<!-- iOS add-to-home-screen / standalone -->
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
<meta name="apple-mobile-web-app-title" content="Felt Ledger" />

<title>Felt Ledger</title>
</head>
<body class="bg-ink-900">
<body class="bg-slate-100">
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
Expand Down
184 changes: 104 additions & 80 deletions src/App.jsx

Large diffs are not rendered by default.

34 changes: 17 additions & 17 deletions src/components/tabs/Calculators.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ import { recommendedBuyins, riskOfRuin, simulatePaths, simulateOutcomes } from '
import { formatMoney, formatNumber, formatPercent } from '../../utils/formatting.js';

const inputClass =
'w-full rounded-lg border border-white/10 bg-ink-900 px-3 py-2 text-sm text-white placeholder-white/30 outline-none focus:border-felt-400 focus:ring-1 focus:ring-felt-400';
'w-full rounded-lg border border-slate-200 bg-slate-100 px-3 py-2 text-sm text-slate-900 placeholder-slate-400 outline-none focus:border-felt-400 focus:ring-1 focus:ring-felt-400';

function NumField({ label, value, onChange, step = 'any', suffix, hint }) {
return (
<label className="block">
<div className="mb-1 flex items-center justify-between">
<span className="text-xs font-medium uppercase tracking-wide text-white/50">{label}</span>
{hint && <span className="text-[11px] text-white/35">{hint}</span>}
<span className="text-xs font-medium uppercase tracking-wide text-slate-500">{label}</span>
{hint && <span className="text-[11px] text-slate-400">{hint}</span>}
</div>
<div className="relative">
<input
Expand All @@ -34,7 +34,7 @@ function NumField({ label, value, onChange, step = 'any', suffix, hint }) {
className={inputClass}
/>
{suffix && (
<span className="pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 text-xs text-white/35">
<span className="pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 text-xs text-slate-400">
{suffix}
</span>
)}
Expand All @@ -45,8 +45,8 @@ function NumField({ label, value, onChange, step = 'any', suffix, hint }) {

function Card({ title, icon, children }) {
return (
<section className="rounded-2xl border border-white/10 bg-ink-850 p-4">
<h2 className="mb-3 flex items-center gap-2 text-sm font-semibold text-white/85">
<section className="rounded-2xl bg-white ring-1 ring-slate-200/70 shadow-card p-4">
<h2 className="mb-3 flex items-center gap-2 text-sm font-semibold text-slate-800">
<span>{icon}</span> {title}
</h2>
{children}
Expand Down Expand Up @@ -78,12 +78,12 @@ export default function Calculators({ stats, currency, settings }) {
: null;

const verdict = useMemo(() => {
if (!Number.isFinite(recDollars)) return { text: 'Win rate must be positive to ever be safely rolled.', tone: 'text-rose-400' };
if (!Number.isFinite(recDollars)) return { text: 'Win rate must be positive to ever be safely rolled.', tone: 'text-rose-600' };
if (stats.currentBankroll >= recDollars)
return { text: `You're rolled. Current bankroll covers this with ${formatMoney(stats.currentBankroll - recDollars, c)} to spare.`, tone: 'text-emerald-400' };
return { text: `You're rolled. Current bankroll covers this with ${formatMoney(stats.currentBankroll - recDollars, c)} to spare.`, tone: 'text-emerald-600' };
return {
text: `Short by ${formatMoney(recDollars - stats.currentBankroll, c)}. Build up before moving in.`,
tone: 'text-amber-400',
tone: 'text-amber-600',
};
}, [recDollars, stats.currentBankroll, c]);

Expand Down Expand Up @@ -134,7 +134,7 @@ export default function Calculators({ stats, currency, settings }) {
sub={monthlyProfit > 0 ? `${formatMoney(monthlyProfit, c)}/mo` : 'need a win rate'}
/>
</div>
<div className={`mt-3 rounded-lg border border-white/10 bg-ink-900 px-3 py-2 text-sm ${verdict.tone}`}>
<div className={`mt-3 rounded-lg border border-slate-200 bg-slate-100 px-3 py-2 text-sm ${verdict.tone}`}>
{verdict.text}
</div>
</Card>
Expand All @@ -144,7 +144,7 @@ export default function Calculators({ stats, currency, settings }) {
<div className="w-28">
<NumField label="Sessions" value={simN} onChange={setSimN} step="1" />
</div>
<label className="flex cursor-pointer items-center gap-2 pb-2 text-sm text-white/70">
<label className="flex cursor-pointer items-center gap-2 pb-2 text-sm text-slate-600">
<input
type="checkbox"
checked={useActual}
Expand Down Expand Up @@ -172,7 +172,7 @@ export default function Calculators({ stats, currency, settings }) {
</div>

{useActual && (
<div className="mb-3 text-xs text-white/45">
<div className="mb-3 text-xs text-slate-400">
Using mean {formatMoney(meanV, c, { sign: true })}/session, SD {formatMoney(sdV, c)}/session.
</div>
)}
Expand Down Expand Up @@ -200,11 +200,11 @@ export default function Calculators({ stats, currency, settings }) {

<div className="mt-3 grid grid-cols-2 gap-2 sm:grid-cols-3">
<MetricCard label="Median outcome" value={formatMoney(sim.outcomes.median, c, { sign: true })} accent />
<MetricCard label="Best (p100)" value={formatMoney(sim.outcomes.best, c, { sign: true })} valueClass="text-emerald-400" />
<MetricCard label="Worst (p0)" value={formatMoney(sim.outcomes.worst, c, { sign: true })} valueClass="text-rose-400" />
<MetricCard label="5th–95th pct" value={`${formatMoney(sim.outcomes.p05, c, { sign: true })} … ${formatMoney(sim.outcomes.p95, c, { sign: true })}`} valueClass="text-white/80 text-sm" />
<MetricCard label="Prob. Up" value={formatPercent(sim.outcomes.probUp)} valueClass="text-emerald-400" />
<MetricCard label="Exp. Max Drawdown" value={formatMoney(sim.outcomes.expectedMaxDrawdown, c)} valueClass="text-rose-400" />
<MetricCard label="Best (p100)" value={formatMoney(sim.outcomes.best, c, { sign: true })} valueClass="text-emerald-600" />
<MetricCard label="Worst (p0)" value={formatMoney(sim.outcomes.worst, c, { sign: true })} valueClass="text-rose-600" />
<MetricCard label="5th–95th pct" value={`${formatMoney(sim.outcomes.p05, c, { sign: true })} … ${formatMoney(sim.outcomes.p95, c, { sign: true })}`} valueClass="text-slate-700 text-sm" />
<MetricCard label="Prob. Up" value={formatPercent(sim.outcomes.probUp)} valueClass="text-emerald-600" />
<MetricCard label="Exp. Max Drawdown" value={formatMoney(sim.outcomes.expectedMaxDrawdown, c)} valueClass="text-rose-600" />
</div>
</Card>
</div>
Expand Down
6 changes: 3 additions & 3 deletions src/components/tabs/Charts.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ import { formatMoney, formatDateShort } from '../../utils/formatting.js';

function ChartCard({ title, subtitle, children, loading }) {
return (
<div className="rounded-2xl border border-white/10 bg-ink-850 p-3 sm:p-4">
<div className="rounded-2xl bg-white ring-1 ring-slate-200/70 shadow-card p-3 sm:p-4">
<div className="mb-3">
<h3 className="text-sm font-semibold text-white/85">{title}</h3>
{subtitle && <p className="text-xs text-white/40">{subtitle}</p>}
<h3 className="text-sm font-semibold text-slate-800">{title}</h3>
{subtitle && <p className="text-xs text-slate-400">{subtitle}</p>}
</div>
{loading ? <Skeleton height={240} label="Computing…" /> : children}
</div>
Expand Down
Loading