visual redesign: landing, dashboard overview, session calendar (2.3.1)
- Redesign landing page hero with split layout, features section, and in-app session/result demos - Add fixed sidebar scroll isolation (app-shell overflow lock pattern) - Redesign leagues index cards with initial badge, podium leader chips, and action links - Redesign league dashboard: color-coded nav tiles, cash/stats overview strip, session history heatmap calendar - Dashboard cash stats use real cash only (excludes rollovers and carry-ins) - Session history calendar: JS-driven full-year grid, year navigation, hover tooltips, full-width responsive layout - Bump leaderboard tile color from gold to blue (distinct from ledger orange) - Bump version to 2.3.1
This commit is contained in:
1 parent
56c9a1084c
commit
a5cb488b91
22 files changed
+2975
-597
No files matched your search
@@ -9,7 +9,7 @@ DATA_PATH = BASE_DIR / "data" / "entries.csv"
|
||||
DEFAULT_DATABASE_URL = f"sqlite:///{BASE_DIR / 'data' / 'boker-dev.sqlite3'}"
|
||||
|
||||
ELIGIBLE_MIN_SESSIONS = 3
|
||||
APP_VERSION = "2.3.0"
|
||||
APP_VERSION = "2.3.1"
|
||||
|
||||
|
||||
def load_local_env(env_path: Path) -> None:
|
||||
|
||||
@@ -222,16 +222,61 @@ def dashboard(league_ref: str):
|
||||
return redirect(url_for("public.home"))
|
||||
|
||||
from league_repositories import league_counts, user_has_league_role
|
||||
from ledger_repositories import list_event_rows_for_league
|
||||
|
||||
league, resp = get_league_with_visibility_gate(league_ref)
|
||||
if resp:
|
||||
return resp
|
||||
is_owner = user_has_league_role(current_user_id() or "", league.id, {"owner"})
|
||||
|
||||
all_sessions = build_session_summaries(list_event_rows_for_league(league.id))
|
||||
closed_sessions = [s for s in all_sessions if not s.is_open]
|
||||
closed_count = len(closed_sessions)
|
||||
|
||||
total_cash_in = sum(s.total_real_cash_in_cents for s in closed_sessions)
|
||||
total_paid_out = sum(s.total_paid_out_cents for s in closed_sessions)
|
||||
total_fronts = sum(s.total_front_cents for s in all_sessions)
|
||||
closed_entries = sum(len(s.entries) for s in closed_sessions)
|
||||
all_entries = sum(len(s.entries) for s in all_sessions)
|
||||
|
||||
# Avg sessions per month since the first session
|
||||
session_date_strs = [s.session_date[:10] for s in all_sessions if s.session_date]
|
||||
avg_sessions_per_month = 0.0
|
||||
if session_date_strs:
|
||||
first_date = date.fromisoformat(min(session_date_strs))
|
||||
today_d = date.today()
|
||||
months_elapsed = (today_d.year - first_date.year) * 12 + (today_d.month - first_date.month) + 1
|
||||
avg_sessions_per_month = round(len(all_sessions) / months_elapsed, 1)
|
||||
|
||||
# Session calendar data: date → count of sessions on that date
|
||||
sessions_by_date: dict[str, int] = {}
|
||||
for s in all_sessions:
|
||||
if s.session_date:
|
||||
d = s.session_date[:10]
|
||||
sessions_by_date[d] = sessions_by_date.get(d, 0) + 1
|
||||
first_session_year = int(min(sessions_by_date)[:4]) if sessions_by_date else date.today().year
|
||||
|
||||
cash_stats = {
|
||||
"total_cash_in": total_cash_in,
|
||||
"total_paid_out": total_paid_out,
|
||||
"total_fronts": total_fronts,
|
||||
"avg_pot": total_cash_in // closed_count if closed_count else 0,
|
||||
"avg_players": round(all_entries / len(all_sessions)) if all_sessions else 0,
|
||||
"avg_buyin": sum(s.total_buy_in_cents for s in closed_sessions) // closed_entries if closed_entries else 0,
|
||||
"biggest_pot": max((s.total_real_cash_in_cents for s in all_sessions), default=0),
|
||||
"avg_sessions_per_month": avg_sessions_per_month,
|
||||
"session_map": sessions_by_date,
|
||||
"first_session_year": first_session_year,
|
||||
"has_data": closed_count > 0,
|
||||
"has_sessions": bool(all_sessions),
|
||||
}
|
||||
|
||||
return render_template(
|
||||
"league_dashboard.html",
|
||||
league=league,
|
||||
counts=league_counts(league.id),
|
||||
is_owner=is_owner,
|
||||
cash_stats=cash_stats,
|
||||
)
|
||||
|
||||
|
||||
|
||||
+1915
-166
File diff suppressed because it is too large.
Load diff
+40
-27
@@ -1,34 +1,37 @@
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap');
|
||||
|
||||
:root {
|
||||
/* ---- surfaces (darkest → lightest) ---- */
|
||||
--bg: #0f0f12;
|
||||
--surface: #16161c;
|
||||
--surface-raised:#1a1a20;
|
||||
--surface-head: #1c1c24;
|
||||
--surface-sunk: #131318;
|
||||
--field: #1f1f27;
|
||||
--field-alt: #20202a;
|
||||
--bg: #0a0a0d;
|
||||
--surface: #111115;
|
||||
--surface-raised:#16161c;
|
||||
--surface-head: #18181f;
|
||||
--surface-sunk: #0d0d11;
|
||||
--field: #1a1a22;
|
||||
--field-alt: #1c1c25;
|
||||
|
||||
/* ---- borders / dividers ---- */
|
||||
--border: #26262f;
|
||||
--border-strong: #2e2e38;
|
||||
--border-hi: #34343f;
|
||||
--divider: #1d1d25;
|
||||
--slash: #3a3a45;
|
||||
--border: #22222b;
|
||||
--border-strong: #2a2a34;
|
||||
--border-hi: #32323d;
|
||||
--divider: #191921;
|
||||
--slash: #383843;
|
||||
|
||||
/* ---- text ---- */
|
||||
--text: #f3f1f8;
|
||||
--text-strong: #ecebf1;
|
||||
--text-body: #e7e5ec;
|
||||
--text-2: #bcbac6;
|
||||
--num: #b0aeba;
|
||||
--muted: #8f8d99;
|
||||
--muted-2: #84828e;
|
||||
--faint: #7d7b87;
|
||||
--faint-2: #757380;
|
||||
--faintest: #66646f;
|
||||
--faintest-2: #57555f;
|
||||
--text: #f4f2f9;
|
||||
--text-strong: #edeaf5;
|
||||
--text-body: #e8e6ef;
|
||||
--text-2: #bdbbc8;
|
||||
--num: #b2b0bb;
|
||||
--muted: #8c8a96;
|
||||
--muted-2: #82808c;
|
||||
--faint: #7a7885;
|
||||
--faint-2: #72707d;
|
||||
--faintest: #64626d;
|
||||
--faintest-2: #55535e;
|
||||
|
||||
/* ---- accent (violet) ---- */
|
||||
/* ---- accent ---- */
|
||||
/* To swap brand color later: edit these 7 vars only */
|
||||
--accent: #9b8cf0;
|
||||
--accent-ink: #12101e;
|
||||
--accent-tint: #1f1d2e;
|
||||
@@ -65,12 +68,22 @@
|
||||
--line-1: #9b8cf0; --line-2: #6fc093; --line-3: #e0b15c;
|
||||
--line-4: #cf6f86; --line-5: #8f93c2;
|
||||
|
||||
/* ---- elevation shadows ---- */
|
||||
--shadow-sm: 0 1px 3px rgba(0,0,0,.30), 0 1px 2px rgba(0,0,0,.22);
|
||||
--shadow-md: 0 4px 16px rgba(0,0,0,.34), 0 2px 6px rgba(0,0,0,.24);
|
||||
--shadow-lg: 0 12px 36px rgba(0,0,0,.42), 0 4px 12px rgba(0,0,0,.28);
|
||||
--shadow-xl: 0 24px 64px rgba(0,0,0,.52), 0 8px 24px rgba(0,0,0,.32);
|
||||
|
||||
/* ---- glass panel effect ---- */
|
||||
--glass-border: 1px solid rgba(255,255,255,.055);
|
||||
--glass-bg: rgba(255,255,255,.02);
|
||||
|
||||
/* ---- spacing scale (4pt) ---- */
|
||||
--s1:4px; --s2:8px; --s3:12px; --s4:16px; --s5:20px;
|
||||
--s6:24px; --s8:32px; --s10:40px; --s12:48px;
|
||||
|
||||
/* ---- radii ---- */
|
||||
--r-pill:6px; --r-sm:9px; --r-md:11px; --r-lg:14px;
|
||||
--r-pill:6px; --r-sm:9px; --r-md:12px; --r-lg:16px;
|
||||
|
||||
/* ---- containers ---- */
|
||||
--w-public:1320px;
|
||||
@@ -79,6 +92,6 @@
|
||||
--gutter: clamp(16px, 4vw, 40px);
|
||||
|
||||
/* ---- Chart.js backward compat (read as raw strings by JS) ---- */
|
||||
--text-muted: #bcbac6;
|
||||
--line: #26262f;
|
||||
--text-muted: #bdbbc8;
|
||||
--line: #22222b;
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 26 26">
|
||||
<rect width="26" height="26" rx="5" fill="#9b8cf0"/>
|
||||
<path d="M13 5.22 20.78 13 13 20.78 5.22 13Z" fill="#0f0f12"/>
|
||||
<path d="M13 5.22 20.78 13 13 20.78 5.22 13Z" fill="#12101e"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 188 B After Width: | Height: | Size: 188 B |
@@ -0,0 +1,87 @@
|
||||
<aside class="sidebar">
|
||||
<div class="sidebar__header">
|
||||
<div class="sidebar__badge">{{ league.name[0]|upper }}</div>
|
||||
<div class="sidebar__meta">
|
||||
<span class="sidebar__league-name">{{ league.name }}</span>
|
||||
{% if is_owner is defined and is_owner %}
|
||||
<span class="sidebar__role">Owner</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav class="sidebar__nav" aria-label="League navigation">
|
||||
<a class="sidebar__link {{ 'is-active' if request.endpoint == 'leagues.dashboard' }}"
|
||||
href="{{ url_for('leagues.dashboard', league_ref=league.url_ref) }}">
|
||||
<svg viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
||||
<rect x="1" y="1" width="6" height="6" rx="1.5" stroke="currentColor" stroke-width="1.4"/>
|
||||
<rect x="9" y="1" width="6" height="6" rx="1.5" stroke="currentColor" stroke-width="1.4"/>
|
||||
<rect x="1" y="9" width="6" height="6" rx="1.5" stroke="currentColor" stroke-width="1.4"/>
|
||||
<rect x="9" y="9" width="6" height="6" rx="1.5" stroke="currentColor" stroke-width="1.4"/>
|
||||
</svg>
|
||||
<span>Overview</span>
|
||||
</a>
|
||||
|
||||
<a class="sidebar__link {{ 'is-active' if request.endpoint in ('leagues.sessions', 'leagues.session_detail', 'leagues.session_view') }}"
|
||||
href="{{ url_for('leagues.sessions', league_ref=league.url_ref) }}">
|
||||
<svg viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
||||
<rect x="1" y="3" width="14" height="12" rx="2" stroke="currentColor" stroke-width="1.4"/>
|
||||
<path d="M5 1v4M11 1v4M1 7h14" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/>
|
||||
</svg>
|
||||
<span>Sessions</span>
|
||||
</a>
|
||||
|
||||
<a class="sidebar__link {{ 'is-active' if request.endpoint in ('leagues.players', 'leagues.player_detail') }}"
|
||||
href="{{ url_for('leagues.players', league_ref=league.url_ref) }}">
|
||||
<svg viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
||||
<circle cx="6" cy="5.5" r="2.5" stroke="currentColor" stroke-width="1.4"/>
|
||||
<path d="M1 14c0-2.761 2.239-5 5-5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/>
|
||||
<circle cx="11.5" cy="5.5" r="2" stroke="currentColor" stroke-width="1.3"/>
|
||||
<path d="M9 14c0-1.657 1.119-3.084 2.5-3.084S14 12.343 14 14" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/>
|
||||
</svg>
|
||||
<span>Players</span>
|
||||
</a>
|
||||
|
||||
<a class="sidebar__link {{ 'is-active' if request.endpoint == 'leagues.leaderboard' }}"
|
||||
href="{{ url_for('leagues.leaderboard', league_ref=league.url_ref) }}">
|
||||
<svg viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
||||
<rect x="1" y="9" width="4" height="6" rx="1" stroke="currentColor" stroke-width="1.4"/>
|
||||
<rect x="6" y="5" width="4" height="10" rx="1" stroke="currentColor" stroke-width="1.4"/>
|
||||
<rect x="11" y="1" width="4" height="14" rx="1" stroke="currentColor" stroke-width="1.4"/>
|
||||
</svg>
|
||||
<span>Leaderboard</span>
|
||||
</a>
|
||||
|
||||
{% if is_logged_in %}
|
||||
<a class="sidebar__link {{ 'is-active' if request.endpoint == 'leagues.ledger' }}"
|
||||
href="{{ url_for('leagues.ledger', league_ref=league.url_ref) }}">
|
||||
<svg viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
||||
<rect x="2" y="1" width="12" height="14" rx="2" stroke="currentColor" stroke-width="1.4"/>
|
||||
<path d="M5 5h6M5 8h6M5 11h3" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/>
|
||||
</svg>
|
||||
<span>Ledger</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
{% if is_owner is defined and is_owner %}
|
||||
<a class="sidebar__link {{ 'is-active' if request.endpoint == 'leagues.league_settings' }}"
|
||||
href="{{ url_for('leagues.league_settings', league_ref=league.url_ref) }}">
|
||||
<svg viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
||||
<circle cx="8" cy="8" r="2.5" stroke="currentColor" stroke-width="1.4"/>
|
||||
<path d="M8 1v2M8 13v2M1 8h2M13 8h2M2.93 2.93l1.41 1.41M11.66 11.66l1.41 1.41M2.93 13.07l1.41-1.41M11.66 4.34l1.41-1.41" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/>
|
||||
</svg>
|
||||
<span>Settings</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
</nav>
|
||||
|
||||
<div class="sidebar__footer">
|
||||
{% if is_logged_in %}
|
||||
<a class="sidebar__back" href="{{ url_for('leagues.index') }}">
|
||||
<svg viewBox="0 0 12 12" fill="none" aria-hidden="true">
|
||||
<path d="M7 2L3 6l4 4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
All leagues
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</aside>
|
||||
@@ -1,27 +1,68 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Login · myboker.org{% endblock %}
|
||||
{% block title %}Sign in · myboker.org{% endblock %}
|
||||
{% block page_class %}page--auth{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
<div class="form-shell narrow">
|
||||
<form class="panel form-card" method="post">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<div>
|
||||
<p class="eyebrow">Owner access</p>
|
||||
<h1 style="font-size:28px;margin-bottom:8px;">Account login</h1>
|
||||
<p class="muted-text" style="margin-bottom:8px;">Manage leagues, players, sessions, and ledger entries.</p>
|
||||
<div class="auth-split">
|
||||
<div class="auth-split__form-col">
|
||||
<div class="auth-split__form-inner">
|
||||
<a class="auth-split__brand" href="{{ url_for('public.home') }}">
|
||||
<div class="auth-split__brand-mark"></div>
|
||||
<span class="auth-split__brand-name">myboker<span class="auth-split__brand-tld">.org</span></span>
|
||||
</a>
|
||||
|
||||
<h1 class="auth-split__title">Welcome back.</h1>
|
||||
<p class="auth-split__sub">Sign in to manage your leagues, sessions, and ledger.</p>
|
||||
|
||||
<form method="post">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<div class="auth-field">
|
||||
<label for="email">Email</label>
|
||||
<input id="email" type="email" name="email" autocomplete="email"
|
||||
value="{{ form.email or '' }}" placeholder="you@example.com" required>
|
||||
</div>
|
||||
<div class="auth-field">
|
||||
<label for="password">Password</label>
|
||||
<input id="password" type="password" name="password"
|
||||
autocomplete="current-password" placeholder="••••••••" required>
|
||||
</div>
|
||||
<div class="auth-links">
|
||||
<a href="{{ url_for('account.forgot_password') }}">Forgot password?</a>
|
||||
</div>
|
||||
<button class="btn btn--primary auth-submit" type="submit">Sign in →</button>
|
||||
</form>
|
||||
|
||||
<p class="auth-alt">No account yet? <a href="{{ url_for('account.register') }}">Create one</a></p>
|
||||
</div>
|
||||
<label>
|
||||
<span>Email</span>
|
||||
<input type="email" name="email" autocomplete="email" value="{{ form.email or '' }}" required>
|
||||
</label>
|
||||
<label>
|
||||
<span>Password</span>
|
||||
<input type="password" name="password" autocomplete="current-password" required>
|
||||
</label>
|
||||
<button class="btn btn--primary" type="submit">Login</button>
|
||||
<a class="btn btn--ghost" href="{{ url_for('account.register') }}">Create account</a>
|
||||
<a style="font:400 12px/1 var(--font-ui);color:var(--text-3);text-align:center;margin-top:4px;" href="{{ url_for('account.forgot_password') }}">Forgot password?</a>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="auth-split__visual" aria-hidden="true">
|
||||
<div class="auth-visual__body">
|
||||
<div class="auth-visual__icon">
|
||||
<svg width="26" height="26" viewBox="0 0 26 26" fill="none">
|
||||
<path d="M13 2L22 7.5V18.5L13 24L4 18.5V7.5L13 2Z" stroke="rgba(155,140,240,.65)" stroke-width="1.4" fill="none"/>
|
||||
<path d="M13 8L18 11V17L13 20L8 17V11L13 8Z" fill="rgba(155,140,240,.25)" stroke="rgba(155,140,240,.55)" stroke-width="1"/>
|
||||
</svg>
|
||||
</div>
|
||||
<p class="auth-visual__title">Track every chip.</p>
|
||||
<p class="auth-visual__sub">Sessions, leaderboards, and debts — all in one place for your private poker league.</p>
|
||||
<div class="auth-visual__preview">
|
||||
<div class="auth-visual__preview-kicker">Live leaderboard</div>
|
||||
<div class="auth-visual__preview-row">
|
||||
<span>Rivera</span>
|
||||
<strong style="color:var(--pos);">+$1,420</strong>
|
||||
</div>
|
||||
<div class="auth-visual__preview-row">
|
||||
<span>Chen</span>
|
||||
<strong style="color:var(--pos);">+$640</strong>
|
||||
</div>
|
||||
<div class="auth-visual__preview-row" style="opacity:.5">
|
||||
<span>Okafor</span>
|
||||
<strong style="color:var(--neg);">-$210</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
@@ -1,30 +1,71 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Create Account · myboker.org{% endblock %}
|
||||
{% block page_class %}page--auth{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
<div class="form-shell narrow">
|
||||
<form class="panel form-card" method="post">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<div>
|
||||
<p class="eyebrow">Owner account</p>
|
||||
<h1 style="font-size:28px;margin-bottom:8px;">Create account</h1>
|
||||
<p class="muted-text" style="margin-bottom:8px;">Accounts are for league owners and managers.</p>
|
||||
<div class="auth-split">
|
||||
<div class="auth-split__form-col">
|
||||
<div class="auth-split__form-inner">
|
||||
<a class="auth-split__brand" href="{{ url_for('public.home') }}">
|
||||
<div class="auth-split__brand-mark"></div>
|
||||
<span class="auth-split__brand-name">myboker<span class="auth-split__brand-tld">.org</span></span>
|
||||
</a>
|
||||
|
||||
<h1 class="auth-split__title">Start your league.</h1>
|
||||
<p class="auth-split__sub">Create an account to manage leagues, players, and sessions.</p>
|
||||
|
||||
<form method="post">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<div class="auth-field">
|
||||
<label for="email">Email</label>
|
||||
<input id="email" type="email" name="email" autocomplete="email"
|
||||
value="{{ form.email or '' }}" placeholder="you@example.com" required>
|
||||
</div>
|
||||
<div class="auth-field">
|
||||
<label for="password">Password</label>
|
||||
<input id="password" type="password" name="password"
|
||||
autocomplete="new-password" placeholder="At least 8 characters" minlength="8" required>
|
||||
</div>
|
||||
<div class="auth-field">
|
||||
<label for="confirm_password">Confirm password</label>
|
||||
<input id="confirm_password" type="password" name="confirm_password"
|
||||
autocomplete="new-password" placeholder="Repeat your password" minlength="8" required>
|
||||
</div>
|
||||
<button class="btn btn--primary auth-submit" type="submit">Create account →</button>
|
||||
</form>
|
||||
|
||||
<p class="auth-alt">Already have an account? <a href="{{ url_for('account.login') }}">Sign in</a></p>
|
||||
</div>
|
||||
<label>
|
||||
<span>Email</span>
|
||||
<input type="email" name="email" autocomplete="email" value="{{ form.email or '' }}" required>
|
||||
</label>
|
||||
<label>
|
||||
<span>Password</span>
|
||||
<input type="password" name="password" autocomplete="new-password" minlength="8" required>
|
||||
</label>
|
||||
<label>
|
||||
<span>Confirm password</span>
|
||||
<input type="password" name="confirm_password" autocomplete="new-password" minlength="8" required>
|
||||
</label>
|
||||
<button class="btn btn--primary" type="submit">Create account</button>
|
||||
<a class="btn btn--ghost" href="{{ url_for('account.login') }}">Login instead</a>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="auth-split__visual" aria-hidden="true">
|
||||
<div class="auth-visual__body">
|
||||
<div class="auth-visual__icon">
|
||||
<svg width="26" height="26" viewBox="0 0 26 26" fill="none">
|
||||
<rect x="3" y="5" width="20" height="16" rx="3" stroke="rgba(155,140,240,.65)" stroke-width="1.4" fill="none"/>
|
||||
<path d="M3 10h20" stroke="rgba(155,140,240,.45)" stroke-width="1.2"/>
|
||||
<path d="M8 15h4M8 18h6" stroke="rgba(155,140,240,.55)" stroke-width="1.2" stroke-linecap="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
<p class="auth-visual__title">Free to start.</p>
|
||||
<p class="auth-visual__sub">No credit card, no monthly fees, no payment processor taking a cut of your buy-ins.</p>
|
||||
<div class="auth-visual__preview">
|
||||
<div class="auth-visual__preview-kicker">What you get</div>
|
||||
<div class="auth-visual__preview-row">
|
||||
<span>Multiple leagues</span>
|
||||
<strong style="color:var(--pos);">✓</strong>
|
||||
</div>
|
||||
<div class="auth-visual__preview-row">
|
||||
<span>Full ledger history</span>
|
||||
<strong style="color:var(--pos);">✓</strong>
|
||||
</div>
|
||||
<div class="auth-visual__preview-row">
|
||||
<span>Platform fee</span>
|
||||
<strong style="color:var(--muted);">$0</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
@@ -2,14 +2,13 @@
|
||||
{% block title %}Account settings · myboker.org{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
<div class="hero" style="padding-bottom:18px;">
|
||||
<p class="hero__kicker">Account</p>
|
||||
<h1 class="hero__title">Settings</h1>
|
||||
<p class="hero__sub">Manage your email address, password, and account.</p>
|
||||
</div>
|
||||
|
||||
<div class="toolbar">
|
||||
<div class="toolbar__left">
|
||||
<div class="settings-profile">
|
||||
<div class="settings-avatar">{{ user.email[0] | upper }}</div>
|
||||
<div class="settings-profile__meta">
|
||||
<h1 class="settings-profile__title">Account Settings</h1>
|
||||
<p class="settings-profile__email">{{ user.email }}</p>
|
||||
</div>
|
||||
<div style="margin-left:auto;">
|
||||
<a class="btn btn--ghost btn--sm" href="{{ url_for('leagues.index') }}">My leagues</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+18
-5
@@ -15,7 +15,7 @@
|
||||
<div class="topbar__inner">
|
||||
<a class="brand" href="{{ url_for('public.home') }}">
|
||||
<span class="brand__mark"></span>
|
||||
<span class="brand__name">myboker.org</span>
|
||||
<span class="brand__name">myboker<span class="brand__tld">.org</span></span>
|
||||
</a>
|
||||
<button class="nav-toggle" type="button" aria-controls="mainnav" aria-expanded="false" aria-label="Open navigation">
|
||||
<span></span>
|
||||
@@ -51,8 +51,10 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
</header>
|
||||
{% block subnav %}{% endblock %}
|
||||
<div class="app-shell">
|
||||
{% block sidebar %}{% endblock %}
|
||||
<main class="page {% block page_class %}page--public{% endblock %}">
|
||||
{% block subnav %}{% endblock %}
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
<div class="flash-stack">
|
||||
@@ -66,11 +68,22 @@
|
||||
{% block content %}{% endblock %}
|
||||
|
||||
<footer class="site-footer" aria-label="Site footer">
|
||||
<span>© 2026 myboker.org</span>
|
||||
<span>Version {{ app_version }}</span>
|
||||
<span><a href="{{ url_for('public.help') }}" style="color:inherit;text-decoration:none;">Help</a> · cedarline.digital</span>
|
||||
<div class="site-footer__copy">
|
||||
<span>© 2026 <strong>myboker.org</strong></span>
|
||||
<span class="site-footer__version">v{{ app_version }}</span>
|
||||
</div>
|
||||
<nav class="site-footer__links" aria-label="Footer links">
|
||||
<a href="{{ url_for('public.help') }}">Help</a>
|
||||
<a href="#">Contact</a>
|
||||
<a href="#">Privacy</a>
|
||||
<a href="#">Terms</a>
|
||||
</nav>
|
||||
<div class="site-footer__brand">
|
||||
<a href="https://cedarline.digital" target="_blank" rel="noopener">cedarline.digital</a>
|
||||
</div>
|
||||
</footer>
|
||||
</main>
|
||||
</div><!-- /.app-shell -->
|
||||
<script>
|
||||
(() => {
|
||||
const toggle = document.querySelector('.nav-toggle');
|
||||
|
||||
+29
-25
@@ -2,58 +2,62 @@
|
||||
{% block title %}Explore leagues · myboker.org{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
<div class="hero" style="padding-bottom:8px;">
|
||||
<p class="hero__kicker">Public leagues</p>
|
||||
<h1 class="hero__title">Explore</h1>
|
||||
<p class="hero__sub">Browse poker leagues that have made their results publicly visible.</p>
|
||||
<div class="explore-header">
|
||||
<p class="explore-header__eyebrow">Public leagues</p>
|
||||
<h1 class="explore-header__title">Explore</h1>
|
||||
<p class="explore-header__sub">Browse poker leagues that have made their results publicly visible.</p>
|
||||
</div>
|
||||
|
||||
<form method="get" action="{{ url_for('public.explore') }}" class="explore-search">
|
||||
<input class="field-input explore-search__input" type="text" name="q" value="{{ q }}" placeholder="Search by league name…">
|
||||
<form method="get" action="{{ url_for('public.explore') }}" class="explore-search-row">
|
||||
<input class="field-input" type="text" name="q" value="{{ q }}" placeholder="Search by league name…" autocomplete="off">
|
||||
<button class="btn btn--primary" type="submit">Search</button>
|
||||
{% if q %}<a class="btn btn--ghost" href="{{ url_for('public.explore') }}">Clear</a>{% endif %}
|
||||
</form>
|
||||
|
||||
{% if leagues %}
|
||||
<div class="explore-grid">
|
||||
<div class="explore-cards">
|
||||
{% for league in leagues %}
|
||||
{% set c = counts.get(league.id, {}) %}
|
||||
<a class="explore-card" href="{{ url_for('leagues.dashboard', league_ref=league.url_ref) }}">
|
||||
<div class="explore-card__head">
|
||||
<h2 class="explore-card__name">{{ league.name }}</h2>
|
||||
{% if league.description %}
|
||||
<p class="explore-card__desc">{{ league.description }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="explore-card__stats">
|
||||
<div class="explore-card__stat">
|
||||
<a class="ex-card" href="{{ url_for('leagues.dashboard', league_ref=league.url_ref) }}">
|
||||
<div class="ex-card__initial">{{ league.name[0] | upper }}</div>
|
||||
<h2 class="ex-card__name">{{ league.name }}</h2>
|
||||
{% if league.description %}
|
||||
<p class="ex-card__desc">{{ league.description }}</p>
|
||||
{% else %}
|
||||
<p class="ex-card__desc" style="opacity:.35;">No description</p>
|
||||
{% endif %}
|
||||
<div class="ex-card__stats">
|
||||
<div class="ex-card__stat">
|
||||
<span class="kicker">Players</span>
|
||||
<strong>{{ c.get('players', 0) }}</strong>
|
||||
</div>
|
||||
<div class="explore-card__stat">
|
||||
<div class="ex-card__stat">
|
||||
<span class="kicker">Sessions</span>
|
||||
<strong>{{ c.get('sessions', 0) }}</strong>
|
||||
</div>
|
||||
<div class="explore-card__stat">
|
||||
<span class="kicker">Open</span>
|
||||
<strong>{{ c.get('open_sessions', 0) }}</strong>
|
||||
{% if c.get('open_sessions', 0) > 0 %}
|
||||
<div class="ex-card__stat">
|
||||
<span class="kicker">Live</span>
|
||||
<strong style="color:var(--pos);">{{ c.get('open_sessions', 0) }}</strong>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="explore-card__footer">
|
||||
<span class="explore-card__link">View league →</span>
|
||||
<div class="ex-card__footer">
|
||||
<span class="ex-card__link">View league →</span>
|
||||
</div>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% elif q %}
|
||||
<div class="empty-panel" style="max-width:480px;">
|
||||
<div class="panel empty-panel" style="max-width:480px;">
|
||||
<h3 class="panel__title">No results for "{{ q }}"</h3>
|
||||
<p class="muted-text">Try a shorter search or browse all leagues.</p>
|
||||
<a class="btn btn--ghost btn--sm" href="{{ url_for('public.explore') }}">Browse all</a>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="empty-panel" style="max-width:480px;">
|
||||
<div class="panel empty-panel" style="max-width:480px;">
|
||||
<h3 class="panel__title">No public leagues yet</h3>
|
||||
<p class="muted-text">League owners can set their league to public in settings.</p>
|
||||
<p class="muted-text">League owners can set their league to public in league settings.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
+283
-137
@@ -1,157 +1,303 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}myboker.org · Poker league records{% endblock %}
|
||||
{% block page_class %}page--landing{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
<section class="l-hero">
|
||||
<p class="l-hero__kicker">For private poker leagues</p>
|
||||
<h1 class="l-hero__h1">Your home game,<br>properly tracked.</h1>
|
||||
<p class="l-hero__sub">Sessions, ledgers, leaderboards, and settlement visibility — built for home groups that want clean records without a payment app or social network in the way.</p>
|
||||
<div class="l-hero__cta">
|
||||
{% if is_logged_in %}
|
||||
<a class="btn btn--primary" href="{{ url_for('leagues.index') }}">Open leagues</a>
|
||||
{% else %}
|
||||
<a class="btn btn--primary" href="{{ url_for('account.register') }}">Create account</a>
|
||||
<a class="btn btn--ghost" href="{{ url_for('account.login') }}">Sign in</a>
|
||||
<a class="btn btn--ghost" href="{{ url_for('public.explore') }}">Explore league stats</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="l-preview" aria-hidden="true">
|
||||
<div class="l-preview__panel">
|
||||
<div class="l-preview__head">
|
||||
<span class="kicker" style="margin:0;">Leaderboard</span>
|
||||
<span class="l-preview__tag">All-time</span>
|
||||
<div class="lp-hero">
|
||||
<div class="lp-hero__content">
|
||||
<span class="lp-eyebrow">Home poker tracking</span>
|
||||
<h1 class="lp-h1">
|
||||
<span class="lp-h1__line">Your</span>
|
||||
<span class="lp-h1__line">league,</span>
|
||||
<span class="lp-h1__accent">on record.</span>
|
||||
</h1>
|
||||
<p class="lp-sub">Sessions, leaderboards, and settlement tracking for home poker groups. No fees, no payment apps in the way.</p>
|
||||
<div class="lp-cta">
|
||||
{% if is_logged_in %}
|
||||
<a class="btn btn--primary btn--lg" href="{{ url_for('leagues.index') }}">Open my leagues →</a>
|
||||
{% else %}
|
||||
<a class="btn btn--primary btn--lg" href="{{ url_for('account.register') }}">Start your league →</a>
|
||||
<a class="btn btn--ghost btn--lg" href="{{ url_for('account.login') }}">Sign in</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="l-preview__rows">
|
||||
<div class="l-preview__row">
|
||||
<span class="l-preview__rank">1</span>
|
||||
<span class="l-preview__player">Rivera</span>
|
||||
<span class="l-preview__val l-preview__val--pos">+$1,420</span>
|
||||
</div>
|
||||
<div class="l-preview__row">
|
||||
<span class="l-preview__rank">2</span>
|
||||
<span class="l-preview__player">Chen</span>
|
||||
<span class="l-preview__val l-preview__val--pos">+$640</span>
|
||||
</div>
|
||||
<div class="l-preview__row">
|
||||
<span class="l-preview__rank">3</span>
|
||||
<span class="l-preview__player">Okafor</span>
|
||||
<span class="l-preview__val l-preview__val--neg">-$210</span>
|
||||
</div>
|
||||
<div class="l-preview__row l-preview__row--faded">
|
||||
<span class="l-preview__rank">4</span>
|
||||
<span class="l-preview__player">Walsh</span>
|
||||
<span class="l-preview__val l-preview__val--neg">-$850</span>
|
||||
</div>
|
||||
<div class="lp-trust">
|
||||
<span>No fees</span>
|
||||
<span class="lp-trust__sep">·</span>
|
||||
<span>Append-only ledger</span>
|
||||
<span class="lp-trust__sep">·</span>
|
||||
<span>Private by default</span>
|
||||
{% if not is_logged_in %}
|
||||
<span class="lp-trust__sep">·</span>
|
||||
<a href="{{ url_for('public.explore') }}" style="color:inherit;text-decoration:underline;text-decoration-color:rgba(255,255,255,.2);">Explore public leagues</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="l-preview__panel l-preview__panel--featured">
|
||||
<div class="l-preview__head">
|
||||
<span class="kicker" style="margin:0;">Session #18</span>
|
||||
<span class="live-badge">live</span>
|
||||
</div>
|
||||
<div class="l-preview__stats">
|
||||
<div class="l-preview__stat">
|
||||
<span class="kicker" style="margin:0;">Cash in</span>
|
||||
<strong>$1,200</strong>
|
||||
<div class="lp-hero__visual" aria-hidden="true">
|
||||
<div class="lp-demo__card">
|
||||
<div class="lp-demo__head">
|
||||
<div>
|
||||
<span class="lp-demo__kicker">Friday Night Poker</span>
|
||||
<span class="lp-demo__title">Leaderboard · Eligible</span>
|
||||
</div>
|
||||
<span class="lp-demo__meta">18 sessions · 5 players</span>
|
||||
</div>
|
||||
<div class="l-preview__stat">
|
||||
<span class="kicker" style="margin:0;">Players</span>
|
||||
<strong>8</strong>
|
||||
</div>
|
||||
<div class="l-preview__stat">
|
||||
<span class="kicker" style="margin:0;">Open debts</span>
|
||||
<strong style="color:var(--warn);">$180</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div class="l-preview__evts">
|
||||
<div class="l-preview__evt">
|
||||
<span class="pill pill-buyin">buy-in</span>
|
||||
<span class="l-preview__evt-name">Rivera</span>
|
||||
<span class="l-preview__evt-val">$200</span>
|
||||
</div>
|
||||
<div class="l-preview__evt">
|
||||
<span class="pill pill-front">front</span>
|
||||
<span class="l-preview__evt-name">Chen</span>
|
||||
<span class="l-preview__evt-val">$200</span>
|
||||
</div>
|
||||
<div class="l-preview__evt">
|
||||
<span class="pill pill-cashout">cashout</span>
|
||||
<span class="l-preview__evt-name">Okafor</span>
|
||||
<span class="l-preview__evt-val">$450</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="l-preview__panel">
|
||||
<div class="l-preview__head">
|
||||
<span class="kicker" style="margin:0;">Open debts</span>
|
||||
<span class="l-preview__tag">Session #17</span>
|
||||
</div>
|
||||
<div class="l-preview__rows">
|
||||
<div class="l-preview__row">
|
||||
<span class="l-preview__player">Walsh</span>
|
||||
<span class="l-preview__val" style="color:var(--owes);">owes $120</span>
|
||||
</div>
|
||||
<div class="l-preview__row">
|
||||
<span class="l-preview__player">Okafor</span>
|
||||
<span class="l-preview__val" style="color:var(--owes);">owes $60</span>
|
||||
</div>
|
||||
<div class="l-preview__row l-preview__row--faded">
|
||||
<span class="l-preview__player">Chen</span>
|
||||
<span class="l-preview__val" style="color:var(--pos);">settled</span>
|
||||
</div>
|
||||
<div class="l-preview__row l-preview__row--faded">
|
||||
<span class="l-preview__player">Rivera</span>
|
||||
<span class="l-preview__val" style="color:var(--pos);">settled</span>
|
||||
<div class="lp-demo__table">
|
||||
<div class="lp-demo__thead lp-demo__thead--lb">
|
||||
<span class="lp-demo__th">#</span>
|
||||
<span class="lp-demo__th">Player</span>
|
||||
<span class="lp-demo__th">Δ</span>
|
||||
<span class="lp-demo__th">Net</span>
|
||||
<span class="lp-demo__th">Win%</span>
|
||||
<span class="lp-demo__th">ROI</span>
|
||||
</div>
|
||||
<div class="lp-demo__row lp-demo__row--lb">
|
||||
<span class="lp-demo__rank lp-demo__rank--1">1</span>
|
||||
<span class="lp-demo__player">Rivera</span>
|
||||
<span class="lp-demo__delta lp-demo__delta--up">▲1</span>
|
||||
<span class="lp-demo__num lp-demo__num--pos">+$1,420</span>
|
||||
<span class="lp-demo__num lp-demo__num--pos">72%</span>
|
||||
<span class="lp-demo__num lp-demo__num--pos">+46%</span>
|
||||
</div>
|
||||
<div class="lp-demo__row lp-demo__row--lb">
|
||||
<span class="lp-demo__rank lp-demo__rank--2">2</span>
|
||||
<span class="lp-demo__player">Chen</span>
|
||||
<span class="lp-demo__delta">—</span>
|
||||
<span class="lp-demo__num lp-demo__num--pos">+$640</span>
|
||||
<span class="lp-demo__num lp-demo__num--pos">56%</span>
|
||||
<span class="lp-demo__num lp-demo__num--pos">+18%</span>
|
||||
</div>
|
||||
<div class="lp-demo__row lp-demo__row--lb">
|
||||
<span class="lp-demo__rank lp-demo__rank--3">3</span>
|
||||
<span class="lp-demo__player">Okafor</span>
|
||||
<span class="lp-demo__delta lp-demo__delta--down">▼1</span>
|
||||
<span class="lp-demo__num lp-demo__num--neg">−$210</span>
|
||||
<span class="lp-demo__num lp-demo__num--neg">43%</span>
|
||||
<span class="lp-demo__num lp-demo__num--neg">−7%</span>
|
||||
</div>
|
||||
<div class="lp-demo__row lp-demo__row--lb lp-demo__row--fade">
|
||||
<span class="lp-demo__rank">4</span>
|
||||
<span class="lp-demo__player">Walsh</span>
|
||||
<span class="lp-demo__delta">—</span>
|
||||
<span class="lp-demo__num lp-demo__num--neg">−$850</span>
|
||||
<span class="lp-demo__num lp-demo__num--neg">31%</span>
|
||||
<span class="lp-demo__num lp-demo__num--neg">−27%</span>
|
||||
</div>
|
||||
<div class="lp-demo__row lp-demo__row--lb lp-demo__row--fade2">
|
||||
<span class="lp-demo__rank">5</span>
|
||||
<span class="lp-demo__player">Novak</span>
|
||||
<span class="lp-demo__delta lp-demo__delta--down">▼2</span>
|
||||
<span class="lp-demo__num lp-demo__num--neg">−$1,000</span>
|
||||
<span class="lp-demo__num lp-demo__num--neg">25%</span>
|
||||
<span class="lp-demo__num lp-demo__num--neg">−42%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="l-features">
|
||||
<div class="l-feature">
|
||||
<div class="l-feature__icon">
|
||||
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" aria-hidden="true">
|
||||
<rect x="1" y="7" width="14" height="13" rx="2" stroke="currentColor" stroke-width="1.5"/>
|
||||
<path d="M5 7V5a2 2 0 0 1 2-2h11a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2h-2" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
<!-- Features -->
|
||||
<section class="lp-features">
|
||||
<div class="lp-features__inner">
|
||||
<div class="lp-sec-head">
|
||||
<span class="lp-eyebrow">What's included</span>
|
||||
<h2 class="lp-sec-title">No spreadsheets. No Venmo math. No guessing.</h2>
|
||||
</div>
|
||||
<h3 class="l-feature__title">League workspaces</h3>
|
||||
<p class="l-feature__desc">Each league has its own players, sessions, leaderboard, and accounting. One account can manage multiple leagues independently.</p>
|
||||
</div>
|
||||
<div class="l-feature">
|
||||
<div class="l-feature__icon">
|
||||
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" aria-hidden="true">
|
||||
<rect x="3" y="3" width="16" height="16" rx="2" stroke="currentColor" stroke-width="1.5"/>
|
||||
<path d="M7 8h8M7 11h6M7 14h4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
||||
</svg>
|
||||
<div class="lp-feat-grid">
|
||||
<div class="lp-feat">
|
||||
<div class="lp-feat__icon">
|
||||
<svg viewBox="0 0 20 20" fill="none" aria-hidden="true">
|
||||
<rect x="2" y="4" width="16" height="14" rx="2.5" stroke="currentColor" stroke-width="1.5"/>
|
||||
<path d="M6 2v4M14 2v4M2 9h16" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
||||
<path d="M6 12.5h3M6 15.5h5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="lp-feat__title">Game-night sessions</h3>
|
||||
<p class="lp-feat__desc">Open a session at the start of the night, log buy-ins and cashouts as they happen, then close it when the last player leaves. Multiple sessions on the same date auto-sequence as S1, S2.</p>
|
||||
</div>
|
||||
<div class="lp-feat">
|
||||
<div class="lp-feat__icon">
|
||||
<svg viewBox="0 0 20 20" fill="none" aria-hidden="true">
|
||||
<rect x="1.5" y="11" width="4.5" height="7" rx="1.2" stroke="currentColor" stroke-width="1.5"/>
|
||||
<rect x="7.75" y="7" width="4.5" height="11" rx="1.2" stroke="currentColor" stroke-width="1.5"/>
|
||||
<rect x="14" y="2" width="4.5" height="16" rx="1.2" stroke="currentColor" stroke-width="1.5"/>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="lp-feat__title">Rankings that earn their spot</h3>
|
||||
<p class="lp-feat__desc">Set a minimum session count so regulars rank and one-time guests go provisional until they qualify. Sort by net, ROI, win rate, or recent form. Every number is derived directly from raw ledger events.</p>
|
||||
</div>
|
||||
<div class="lp-feat">
|
||||
<div class="lp-feat__icon">
|
||||
<svg viewBox="0 0 20 20" fill="none" aria-hidden="true">
|
||||
<rect x="3" y="2" width="14" height="16" rx="2" stroke="currentColor" stroke-width="1.5"/>
|
||||
<path d="M7 7h6M7 10.5h6M7 14h3.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="lp-feat__title">Settlement without the spreadsheet</h3>
|
||||
<p class="lp-feat__desc">Every event is logged and kept: buy-ins, cashouts, fronts, corrections. Nothing is deleted; mistakes get fixed by appending. When a session closes, the house knows exactly what it holds and who is owed what.</p>
|
||||
</div>
|
||||
</div>
|
||||
<h3 class="l-feature__title">Append-only ledger</h3>
|
||||
<p class="l-feature__desc">Buy-ins, fronts, cashouts, rollovers, debt repayments, and write-offs stay permanently on record. Events are never deleted.</p>
|
||||
</div>
|
||||
<div class="l-feature">
|
||||
<div class="l-feature__icon">
|
||||
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" aria-hidden="true">
|
||||
<path d="M3 17L8 11L13 14L20 6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M3 20h18" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="l-feature__title">Performance analytics</h3>
|
||||
<p class="l-feature__desc">Net, ROI, win rate, and all-time cash movement are computed from ledger events. Rankings stay separate from settlement records.</p>
|
||||
</div>
|
||||
<div class="l-feature">
|
||||
<div class="l-feature__icon">
|
||||
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" aria-hidden="true">
|
||||
<path d="M7 5h8M15 5l-3-3m3 3l-3 3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M15 17H7m0 0l3 3m-3-3l3-3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="l-feature__title">Settlement visibility</h3>
|
||||
<p class="l-feature__desc">Open debts, player payables, and house receivables are always tracked. Share settlement status with the group, not just final numbers.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- App demos -->
|
||||
<section class="lp-showcase">
|
||||
<div class="lp-showcase__inner">
|
||||
<div class="lp-sec-head">
|
||||
<span class="lp-eyebrow">In the app</span>
|
||||
<h2 class="lp-sec-title">Sessions and results</h2>
|
||||
<p class="lp-sec-sub">Every number your group tracks, in one place.</p>
|
||||
</div>
|
||||
<div class="lp-showcase__grid" aria-hidden="true">
|
||||
|
||||
<!-- Sessions panel -->
|
||||
<div class="lp-demo__card">
|
||||
<div class="lp-demo__head">
|
||||
<div>
|
||||
<span class="lp-demo__kicker">Friday Night Poker</span>
|
||||
<span class="lp-demo__title">Sessions</span>
|
||||
</div>
|
||||
<span class="lp-demo__meta">6 total</span>
|
||||
</div>
|
||||
<div class="lp-sess-list">
|
||||
<div class="lp-sess-row">
|
||||
<div class="lp-sess-date">
|
||||
<span class="lp-sess-date__mon">Jun</span>
|
||||
<span class="lp-sess-date__day">21</span>
|
||||
</div>
|
||||
<div class="lp-sess-info">
|
||||
<span class="lp-sess-label">Session 1</span>
|
||||
<span class="lp-sess-meta">5 players · $250 pot</span>
|
||||
</div>
|
||||
<span class="lp-sess-pill lp-sess-pill--open">open</span>
|
||||
</div>
|
||||
<div class="lp-sess-row">
|
||||
<div class="lp-sess-date">
|
||||
<span class="lp-sess-date__mon">Jun</span>
|
||||
<span class="lp-sess-date__day">14</span>
|
||||
</div>
|
||||
<div class="lp-sess-info">
|
||||
<span class="lp-sess-label">Session 1</span>
|
||||
<span class="lp-sess-meta">5 players · $300 pot</span>
|
||||
</div>
|
||||
<span class="lp-sess-pill lp-sess-pill--closed">closed</span>
|
||||
</div>
|
||||
<div class="lp-sess-row">
|
||||
<div class="lp-sess-date">
|
||||
<span class="lp-sess-date__mon">Jun</span>
|
||||
<span class="lp-sess-date__day">7</span>
|
||||
</div>
|
||||
<div class="lp-sess-info">
|
||||
<span class="lp-sess-label">Session 1</span>
|
||||
<span class="lp-sess-meta">4 players · $200 pot</span>
|
||||
</div>
|
||||
<span class="lp-sess-pill lp-sess-pill--closed">closed</span>
|
||||
</div>
|
||||
<div class="lp-sess-row lp-sess-row--fade">
|
||||
<div class="lp-sess-date">
|
||||
<span class="lp-sess-date__mon">May</span>
|
||||
<span class="lp-sess-date__day">31</span>
|
||||
</div>
|
||||
<div class="lp-sess-info">
|
||||
<span class="lp-sess-label">Session 1</span>
|
||||
<span class="lp-sess-meta">6 players · $350 pot</span>
|
||||
</div>
|
||||
<span class="lp-sess-pill lp-sess-pill--closed">closed</span>
|
||||
</div>
|
||||
<div class="lp-sess-row lp-sess-row--fade2">
|
||||
<div class="lp-sess-date">
|
||||
<span class="lp-sess-date__mon">May</span>
|
||||
<span class="lp-sess-date__day">24</span>
|
||||
</div>
|
||||
<div class="lp-sess-info">
|
||||
<span class="lp-sess-label">Session 1</span>
|
||||
<span class="lp-sess-meta">5 players · $250 pot</span>
|
||||
</div>
|
||||
<span class="lp-sess-pill lp-sess-pill--closed">closed</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Session results panel -->
|
||||
<div class="lp-demo__card">
|
||||
<div class="lp-demo__head">
|
||||
<div>
|
||||
<span class="lp-demo__kicker">Friday Night Poker</span>
|
||||
<span class="lp-demo__title">Session #006 · Jun 21</span>
|
||||
</div>
|
||||
<span class="lp-demo__meta">5 players · open</span>
|
||||
</div>
|
||||
<div class="lp-recon-grid">
|
||||
<div class="lp-recon-cell">
|
||||
<span class="lp-recon-label">Cash in</span>
|
||||
<span class="lp-recon-val">$300</span>
|
||||
</div>
|
||||
<div class="lp-recon-cell">
|
||||
<span class="lp-recon-label">Paid out</span>
|
||||
<span class="lp-recon-val lp-demo__num--pos">$190</span>
|
||||
</div>
|
||||
<div class="lp-recon-cell">
|
||||
<span class="lp-recon-label">House owes players</span>
|
||||
<span class="lp-recon-val lp-demo__num--neg">$110</span>
|
||||
</div>
|
||||
<div class="lp-recon-cell">
|
||||
<span class="lp-recon-label">Players owe house</span>
|
||||
<span class="lp-recon-val">$0</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="lp-result-thead">
|
||||
<span>Player</span>
|
||||
<span>Invested</span>
|
||||
<span>Net</span>
|
||||
<span>Open</span>
|
||||
</div>
|
||||
<div class="lp-result-row">
|
||||
<span class="lp-demo__player">Rivera</span>
|
||||
<span class="lp-demo__num">$50</span>
|
||||
<span class="lp-demo__num lp-demo__num--pos">+$120</span>
|
||||
<span class="lp-result-due">due $120</span>
|
||||
</div>
|
||||
<div class="lp-result-row">
|
||||
<span class="lp-demo__player">Chen</span>
|
||||
<span class="lp-demo__num">$100</span>
|
||||
<span class="lp-demo__num lp-demo__num--pos">+$90</span>
|
||||
<span class="lp-result-due">due $90</span>
|
||||
</div>
|
||||
<div class="lp-result-row">
|
||||
<span class="lp-demo__player">Walsh</span>
|
||||
<span class="lp-demo__num">$50</span>
|
||||
<span class="lp-demo__num lp-demo__num--neg">-$50</span>
|
||||
<span class="lp-result-settled">settled</span>
|
||||
</div>
|
||||
<div class="lp-result-row lp-demo__row--fade">
|
||||
<span class="lp-demo__player">Okafor</span>
|
||||
<span class="lp-demo__num">$50</span>
|
||||
<span class="lp-demo__num lp-demo__num--pos">+$30</span>
|
||||
<span class="lp-result-due">due $30</span>
|
||||
</div>
|
||||
<div class="lp-result-row lp-demo__row--fade2">
|
||||
<span class="lp-demo__player">Novak</span>
|
||||
<span class="lp-demo__num">$100</span>
|
||||
<span class="lp-demo__num lp-demo__num--neg">-$100</span>
|
||||
<span class="lp-result-settled">settled</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{% if not is_logged_in %}
|
||||
<section class="lp-bottom-cta">
|
||||
<div class="lp-bottom-cta__inner">
|
||||
<h2 class="lp-bottom-cta__title">Start tracking your league</h2>
|
||||
<p class="lp-bottom-cta__sub">Free to use. No payment processor. Your data stays yours.</p>
|
||||
<div class="lp-cta" style="justify-content:center;">
|
||||
<a class="btn btn--primary btn--lg" href="{{ url_for('account.register') }}">Create an account →</a>
|
||||
<a class="btn btn--ghost btn--lg" href="{{ url_for('public.explore') }}">Browse public leagues</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
+292
-68
@@ -1,102 +1,326 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}{{ league.name }} · myboker.org{% endblock %}
|
||||
{% block subnav %}{% include "_league_subnav.html" %}{% endblock %}
|
||||
{% block sidebar %}{% include "_league_sidebar.html" %}{% endblock %}
|
||||
{% block page_class %}page--app{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
{% if is_logged_in %}
|
||||
<a class="league-back-link" href="{{ url_for('leagues.index') }}">← Leagues</a>
|
||||
{% endif %}
|
||||
|
||||
<div class="db-hero">
|
||||
<div class="db-hero__bg-letter" aria-hidden="true">{{ league.name[0] | upper }}</div>
|
||||
<div class="db-hero__body">
|
||||
<div class="db-hero__chips">
|
||||
{% if is_owner %}
|
||||
<span class="db-chip db-chip--accent">Owner</span>
|
||||
{% endif %}
|
||||
<div class="db-header">
|
||||
<div class="db-header__info">
|
||||
<div class="db-header__chips">
|
||||
<span class="db-chip">{{ league.visibility }}</span>
|
||||
{% if is_owner %}<span class="db-chip db-chip--accent">Owner</span>{% endif %}
|
||||
</div>
|
||||
<h1 class="hero__title" style="margin-bottom:8px;">{{ league.name }}</h1>
|
||||
<p class="hero__sub" style="margin:0;">{{ league.description or 'No description set.' }}</p>
|
||||
<h1 class="db-header__title">{{ league.name }}</h1>
|
||||
{% if league.description %}<p class="db-header__sub">{{ league.description }}</p>{% endif %}
|
||||
</div>
|
||||
{% if is_owner %}
|
||||
<div class="db-header__actions">
|
||||
<a class="btn btn--ghost btn--sm" href="{{ url_for('leagues.league_settings', league_ref=league.url_ref) }}">Settings</a>
|
||||
<a class="btn btn--primary btn--sm" href="{{ url_for('leagues.sessions', league_ref=league.url_ref) }}">New session</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if counts.open_sessions > 0 %}
|
||||
<div class="db-live-bar">
|
||||
<span class="live-badge">{{ counts.open_sessions }} live</span>
|
||||
<span class="db-live-bar__text">
|
||||
{% if counts.open_sessions == 1 %}A session is currently in progress.{% else %}{{ counts.open_sessions }} sessions are in progress.{% endif %}
|
||||
<div class="db-live-alert">
|
||||
<div class="db-live-alert__pulse"></div>
|
||||
<span class="db-live-alert__text">
|
||||
{{ counts.open_sessions }} session{{ 's' if counts.open_sessions != 1 else '' }} in progress
|
||||
</span>
|
||||
<a class="btn btn--sm db-live-bar__btn" href="{{ url_for('leagues.sessions', league_ref=league.url_ref) }}">View sessions →</a>
|
||||
<a class="db-live-alert__link" href="{{ url_for('leagues.sessions', league_ref=league.url_ref) }}">View sessions →</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="db-stats">
|
||||
<div class="stat stat--rail" style="--rail:var(--pos);">
|
||||
<span class="kicker">Active players</span>
|
||||
<div class="stat__val" style="color:var(--text-strong);">{{ counts.players }}</div>
|
||||
<div class="db-stat-grid">
|
||||
<div class="db-stat">
|
||||
<div class="db-stat__icon db-stat__icon--accent">
|
||||
<svg width="18" height="18" viewBox="0 0 20 20" fill="none" aria-hidden="true">
|
||||
<circle cx="8" cy="7" r="3" stroke="currentColor" stroke-width="1.5"/>
|
||||
<path d="M2 17c0-3.314 2.686-6 6-6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
||||
<circle cx="14" cy="8" r="2.5" stroke="currentColor" stroke-width="1.5"/>
|
||||
<path d="M18 17c0-2.761-1.79-5-4-5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<span class="db-stat__num">{{ counts.players }}</span>
|
||||
<span class="db-stat__label">Players</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat stat--rail" style="--rail:var(--accent);">
|
||||
<span class="kicker">Sessions</span>
|
||||
<div class="stat__val" style="color:var(--text-strong);">{{ counts.sessions }}</div>
|
||||
{% if counts.open_sessions > 0 %}
|
||||
<div class="stat__sub" style="color:var(--pos);">{{ counts.open_sessions }} open now</div>
|
||||
{% endif %}
|
||||
<div class="db-stat">
|
||||
<div class="db-stat__icon db-stat__icon--pos">
|
||||
<svg width="18" height="18" viewBox="0 0 20 20" fill="none" aria-hidden="true">
|
||||
<rect x="2.5" y="3.5" width="15" height="13" rx="2" stroke="currentColor" stroke-width="1.5"/>
|
||||
<path d="M6 2v3M14 2v3M2.5 8h15" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<span class="db-stat__num">{{ counts.sessions }}</span>
|
||||
<span class="db-stat__label">Sessions{% if counts.open_sessions > 0 %} · <span style="color:var(--pos);">{{ counts.open_sessions }} live</span>{% endif %}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat stat--rail" style="--rail:var(--warn);">
|
||||
<span class="kicker">Ledger events</span>
|
||||
<div class="stat__val" style="color:var(--text-strong);">{{ counts.events }}</div>
|
||||
<div class="db-stat">
|
||||
<div class="db-stat__icon db-stat__icon--warn">
|
||||
<svg width="18" height="18" viewBox="0 0 20 20" fill="none" aria-hidden="true">
|
||||
<path d="M5 3h10l3 3v11a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1Z" stroke="currentColor" stroke-width="1.5"/>
|
||||
<path d="M7 8h6M7 11h4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<span class="db-stat__num">{{ counts.events }}</span>
|
||||
<span class="db-stat__label">Ledger events</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat stat--rail" style="--rail:var(--rolled);">
|
||||
<span class="kicker">Seasons</span>
|
||||
<div class="stat__val" style="color:var(--text-strong);">{{ counts.seasons }}</div>
|
||||
<div class="db-stat">
|
||||
<div class="db-stat__icon">
|
||||
<svg width="18" height="18" viewBox="0 0 20 20" fill="none" aria-hidden="true">
|
||||
<path d="M10 2v3M10 15v3M2 10h3M15 10h3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
||||
<circle cx="10" cy="10" r="4.5" stroke="currentColor" stroke-width="1.5"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<span class="db-stat__num">{{ counts.seasons }}</span>
|
||||
<span class="db-stat__label">Seasons</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dashboard-grid">
|
||||
{% if cash_stats.has_data %}
|
||||
<div class="db-cash">
|
||||
<div class="db-cash__stat">
|
||||
<span class="db-cash__label">Total cash in</span>
|
||||
<span class="db-cash__val">{{ cash_stats.total_cash_in | money }}</span>
|
||||
</div>
|
||||
<div class="db-cash__div"></div>
|
||||
<div class="db-cash__stat">
|
||||
<span class="db-cash__label">Total paid out</span>
|
||||
<span class="db-cash__val">{{ cash_stats.total_paid_out | money }}</span>
|
||||
</div>
|
||||
<div class="db-cash__div"></div>
|
||||
<div class="db-cash__stat">
|
||||
<span class="db-cash__label">Avg pot / session</span>
|
||||
<span class="db-cash__val">{{ cash_stats.avg_pot | money }}</span>
|
||||
</div>
|
||||
<div class="db-cash__div"></div>
|
||||
<div class="db-cash__stat">
|
||||
<span class="db-cash__label">Avg players</span>
|
||||
<span class="db-cash__val">{{ cash_stats.avg_players }}</span>
|
||||
</div>
|
||||
<div class="db-cash__div"></div>
|
||||
<div class="db-cash__stat">
|
||||
<span class="db-cash__label">Avg buy-in</span>
|
||||
<span class="db-cash__val">{{ cash_stats.avg_buyin | money }}</span>
|
||||
</div>
|
||||
<div class="db-cash__div"></div>
|
||||
<div class="db-cash__stat">
|
||||
<span class="db-cash__label">Biggest session</span>
|
||||
<span class="db-cash__val">{{ cash_stats.biggest_pot | money }}</span>
|
||||
</div>
|
||||
{% if cash_stats.avg_sessions_per_month > 0 %}
|
||||
<div class="db-cash__div"></div>
|
||||
<div class="db-cash__stat">
|
||||
<span class="db-cash__label">Sessions / month</span>
|
||||
<span class="db-cash__val">{{ cash_stats.avg_sessions_per_month }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if cash_stats.total_fronts > 0 %}
|
||||
<div class="db-cash__div"></div>
|
||||
<div class="db-cash__stat">
|
||||
<span class="db-cash__label">Fronts issued</span>
|
||||
<span class="db-cash__val">{{ cash_stats.total_fronts | money }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<a class="dashboard-card panel" style="--card-accent:var(--warn);" href="{{ url_for('leagues.leaderboard', league_ref=league.url_ref) }}">
|
||||
<div class="dashboard-card__top">
|
||||
<span class="kicker">Stats</span>
|
||||
<span class="dashboard-card__count">{{ counts.players }} ranked</span>
|
||||
{% if cash_stats.has_sessions %}
|
||||
<div class="db-cal"
|
||||
id="db-cal"
|
||||
data-sessions='{{ cash_stats.session_map | tojson }}'
|
||||
data-first-year="{{ cash_stats.first_session_year }}">
|
||||
<div class="db-cal__hdr">
|
||||
<span class="db-cal__title">Session history</span>
|
||||
<div class="db-cal__nav-group">
|
||||
<button class="db-cal__nav" id="db-cal-prev" aria-label="Previous year">‹</button>
|
||||
<span class="db-cal__year" id="db-cal-year"></span>
|
||||
<button class="db-cal__nav" id="db-cal-next" aria-label="Next year">›</button>
|
||||
</div>
|
||||
<strong class="dashboard-card__title">Leaderboard</strong>
|
||||
<p class="dashboard-card__desc">All-time results, ROI, settlement totals, and standings.</p>
|
||||
<span class="dashboard-card__arrow">→</span>
|
||||
</div>
|
||||
<div class="db-cal__grid" id="db-cal-grid"></div>
|
||||
<div class="db-cal__tip" id="db-cal-tip" hidden></div>
|
||||
</div>
|
||||
<script>
|
||||
(function () {
|
||||
var cal = document.getElementById('db-cal');
|
||||
if (!cal) return;
|
||||
var sessions = JSON.parse(cal.dataset.sessions);
|
||||
var firstYear = parseInt(cal.dataset.firstYear) || new Date().getFullYear();
|
||||
var MONTHS = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
|
||||
var today = new Date(); today.setHours(0,0,0,0);
|
||||
var todayStr = today.toISOString().slice(0,10);
|
||||
var currentYear = today.getFullYear();
|
||||
var year = currentYear;
|
||||
var grid = document.getElementById('db-cal-grid');
|
||||
var yearLbl = document.getElementById('db-cal-year');
|
||||
var prevBtn = document.getElementById('db-cal-prev');
|
||||
var nextBtn = document.getElementById('db-cal-next');
|
||||
var tip = document.getElementById('db-cal-tip');
|
||||
|
||||
function ds(d) { return d.toISOString().slice(0,10); }
|
||||
|
||||
function buildCalendar(y) {
|
||||
var start = new Date(y, 0, 1);
|
||||
var dow = start.getDay();
|
||||
start.setDate(start.getDate() - (dow === 0 ? 6 : dow - 1));
|
||||
var end = new Date(y, 11, 31);
|
||||
var edow = end.getDay();
|
||||
if (edow !== 0) end.setDate(end.getDate() + (7 - edow));
|
||||
var weeks = [], cur = new Date(start);
|
||||
while (cur <= end) {
|
||||
var days = [], monthLabel = '';
|
||||
for (var i = 0; i < 7; i++) {
|
||||
var d = new Date(cur.getFullYear(), cur.getMonth(), cur.getDate() + i);
|
||||
var s = ds(d);
|
||||
if (d.getDate() === 1 && !monthLabel) monthLabel = MONTHS[d.getMonth()];
|
||||
days.push({ date: s, count: sessions[s] || 0, future: s > todayStr, out: d.getFullYear() !== y });
|
||||
}
|
||||
weeks.push({ days: days, month: monthLabel });
|
||||
cur.setDate(cur.getDate() + 7);
|
||||
}
|
||||
return weeks;
|
||||
}
|
||||
|
||||
function render(y) {
|
||||
var weeks = buildCalendar(y);
|
||||
grid.innerHTML = '';
|
||||
weeks.forEach(function(week) {
|
||||
var mc = document.createElement('div');
|
||||
mc.className = 'db-cal__mc';
|
||||
mc.textContent = week.month;
|
||||
grid.appendChild(mc);
|
||||
week.days.forEach(function(day) {
|
||||
var cell = document.createElement('div');
|
||||
var cls = 'db-cal__cell';
|
||||
if (day.out) cls += ' db-cal__cell--out';
|
||||
else if (day.future) cls += ' db-cal__cell--dim';
|
||||
else if (day.count) cls += ' db-cal__cell--on' + (day.count > 1 ? ' db-cal__cell--multi' : '');
|
||||
cell.className = cls;
|
||||
cell.dataset.date = day.date;
|
||||
cell.dataset.count = day.count;
|
||||
grid.appendChild(cell);
|
||||
});
|
||||
});
|
||||
yearLbl.textContent = y;
|
||||
prevBtn.disabled = y <= firstYear;
|
||||
nextBtn.disabled = y >= currentYear;
|
||||
}
|
||||
|
||||
function showTip(cell, e) {
|
||||
var d = cell.dataset.date;
|
||||
if (!d) return;
|
||||
var parts = d.split('-').map(Number);
|
||||
var dateObj = new Date(parts[0], parts[1] - 1, parts[2]);
|
||||
var label = dateObj.toLocaleDateString('en-US', { weekday:'short', month:'short', day:'numeric', year:'numeric' });
|
||||
var count = parseInt(cell.dataset.count) || 0;
|
||||
tip.textContent = count ? label + ' · ' + count + ' session' + (count > 1 ? 's' : '') : label;
|
||||
tip.hidden = false;
|
||||
moveTip(e);
|
||||
}
|
||||
|
||||
function moveTip(e) {
|
||||
var tw = tip.offsetWidth, th = tip.offsetHeight;
|
||||
var x = e.clientX - tw / 2, y = e.clientY - th - 10;
|
||||
x = Math.max(8, Math.min(x, window.innerWidth - tw - 8));
|
||||
if (y < 8) y = e.clientY + 14;
|
||||
tip.style.left = x + 'px';
|
||||
tip.style.top = y + 'px';
|
||||
}
|
||||
|
||||
grid.addEventListener('mouseover', function(e) {
|
||||
var cell = e.target.closest('.db-cal__cell');
|
||||
if (cell && !cell.classList.contains('db-cal__cell--out')) showTip(cell, e);
|
||||
else tip.hidden = true;
|
||||
});
|
||||
grid.addEventListener('mousemove', function(e) { if (!tip.hidden) moveTip(e); });
|
||||
grid.addEventListener('mouseleave', function() { tip.hidden = true; });
|
||||
prevBtn.addEventListener('click', function() { if (year > firstYear) render(--year); });
|
||||
nextBtn.addEventListener('click', function() { if (year < currentYear) render(++year); });
|
||||
|
||||
render(year);
|
||||
})();
|
||||
</script>
|
||||
{% endif %}
|
||||
|
||||
<div class="db-nav-grid">
|
||||
|
||||
<a class="db-nav-tile db-nav-tile--sessions {% if counts.open_sessions > 0 %}db-nav-tile--live{% endif %}" href="{{ url_for('leagues.sessions', league_ref=league.url_ref) }}">
|
||||
<div class="db-nav-tile__icon">
|
||||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<rect x="3" y="4" width="18" height="16" rx="2.5" stroke="currentColor" stroke-width="1.6"/>
|
||||
<path d="M3 9h18M8 3v2M16 3v2" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/>
|
||||
<circle cx="8" cy="14" r="1.5" fill="currentColor"/>
|
||||
<circle cx="12" cy="14" r="1.5" fill="currentColor"/>
|
||||
<circle cx="16" cy="14" r="1.5" fill="currentColor"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="db-nav-tile__body">
|
||||
<div class="db-nav-tile__title-row">
|
||||
<strong class="db-nav-tile__title">Sessions</strong>
|
||||
{% if counts.open_sessions > 0 %}
|
||||
<span class="live-badge">{{ counts.open_sessions }} live</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<p class="db-nav-tile__desc">Open a session to start tracking buy-ins and cashouts in real time. Close it when everyone is done.</p>
|
||||
</div>
|
||||
<span class="db-nav-tile__arrow">→</span>
|
||||
</a>
|
||||
|
||||
<a class="dashboard-card panel" style="--card-accent:var(--pos);" href="{{ url_for('leagues.players', league_ref=league.url_ref) }}">
|
||||
<div class="dashboard-card__top">
|
||||
<span class="kicker">Roster</span>
|
||||
<span class="dashboard-card__count">{{ counts.players }} active</span>
|
||||
<a class="db-nav-tile db-nav-tile--leaderboard" href="{{ url_for('leagues.leaderboard', league_ref=league.url_ref) }}">
|
||||
<div class="db-nav-tile__icon">
|
||||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<path d="M3 20h18M6 20V13M10 20V9M14 20V11M18 20V5" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
<strong class="dashboard-card__title">Players</strong>
|
||||
<p class="dashboard-card__desc">Add and manage player records for stats and ledger entry.</p>
|
||||
<span class="dashboard-card__arrow">→</span>
|
||||
<div class="db-nav-tile__body">
|
||||
<div class="db-nav-tile__title-row">
|
||||
<strong class="db-nav-tile__title">Leaderboard</strong>
|
||||
</div>
|
||||
<p class="db-nav-tile__desc">Net profit, ROI, win rate, and rank delta. Filter by eligible players or switch to recent form.</p>
|
||||
</div>
|
||||
<span class="db-nav-tile__arrow">→</span>
|
||||
</a>
|
||||
|
||||
<a class="dashboard-card panel {% if counts.open_sessions > 0 %}dashboard-card--live{% endif %}" style="--card-accent:var(--accent);" href="{{ url_for('leagues.sessions', league_ref=league.url_ref) }}">
|
||||
<div class="dashboard-card__top">
|
||||
<span class="kicker">Schedule</span>
|
||||
{% if counts.open_sessions > 0 %}
|
||||
<span class="live-badge">{{ counts.open_sessions }} live</span>
|
||||
{% else %}
|
||||
<span class="dashboard-card__count">{{ counts.sessions }} total</span>
|
||||
{% endif %}
|
||||
<a class="db-nav-tile db-nav-tile--players" href="{{ url_for('leagues.players', league_ref=league.url_ref) }}">
|
||||
<div class="db-nav-tile__icon">
|
||||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<circle cx="9" cy="8" r="3.5" stroke="currentColor" stroke-width="1.6"/>
|
||||
<path d="M2.5 21c0-3.866 2.91-7 6.5-7" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/>
|
||||
<circle cx="16.5" cy="9.5" r="3" stroke="currentColor" stroke-width="1.6"/>
|
||||
<path d="M21.5 21c0-3.314-2.239-6-5-6" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
<strong class="dashboard-card__title">Sessions</strong>
|
||||
<p class="dashboard-card__desc">Create sessions, record events, and manage open/closed state.</p>
|
||||
<span class="dashboard-card__arrow">→</span>
|
||||
<div class="db-nav-tile__body">
|
||||
<div class="db-nav-tile__title-row">
|
||||
<strong class="db-nav-tile__title">Players</strong>
|
||||
</div>
|
||||
<p class="db-nav-tile__desc">View each player's full session history, net trend, and individual stats over time.</p>
|
||||
</div>
|
||||
<span class="db-nav-tile__arrow">→</span>
|
||||
</a>
|
||||
|
||||
<a class="dashboard-card panel" style="--card-accent:var(--rolled);" href="{{ url_for('leagues.ledger', league_ref=league.url_ref) }}">
|
||||
<div class="dashboard-card__top">
|
||||
<span class="kicker">Accounting</span>
|
||||
<span class="dashboard-card__count">{{ counts.events }} events</span>
|
||||
<a class="db-nav-tile db-nav-tile--ledger" href="{{ url_for('leagues.ledger', league_ref=league.url_ref) }}">
|
||||
<div class="db-nav-tile__icon">
|
||||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<path d="M5 3h14a1 1 0 0 1 1 1v14a1 1 0 0 1-1 1H9l-4-4V4a1 1 0 0 1 1-1Z" stroke="currentColor" stroke-width="1.6"/>
|
||||
<path d="M9 19v-4H5M8 9h8M8 13h5" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
<strong class="dashboard-card__title">League ledger</strong>
|
||||
<p class="dashboard-card__desc">Review all events, open debts, cash movement, and settlements.</p>
|
||||
<span class="dashboard-card__arrow">→</span>
|
||||
<div class="db-nav-tile__body">
|
||||
<div class="db-nav-tile__title-row">
|
||||
<strong class="db-nav-tile__title">League ledger</strong>
|
||||
</div>
|
||||
<p class="db-nav-tile__desc">Track what the house holds, what it owes, and outstanding fronts across all sessions.</p>
|
||||
</div>
|
||||
<span class="db-nav-tile__arrow">→</span>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Leaderboard · {{ league.name }} · myboker.org{% endblock %}
|
||||
{% block subnav %}{% include "_league_subnav.html" %}{% endblock %}
|
||||
{% block sidebar %}{% include "_league_sidebar.html" %}{% endblock %}
|
||||
{% block page_class %}page--app{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
<div class="hero" style="display:flex;flex-wrap:wrap;gap:var(--s6);align-items:flex-start;justify-content:space-between;">
|
||||
<div class="page-header">
|
||||
<div>
|
||||
<h1 class="hero__title">Leaderboard</h1>
|
||||
<p class="hero__sub">{{ session_count }} session{{ 's' if session_count != 1 else '' }}{% if selected_session_date %} through {{ selected_session_label }}{% endif %}</p>
|
||||
<h1 class="page-header__title">Leaderboard</h1>
|
||||
<p class="page-header__sub">{{ session_count }} session{{ 's' if session_count != 1 else '' }}{% if selected_session_date %} through {{ selected_session_label }}{% endif %}</p>
|
||||
</div>
|
||||
<div class="leaderboard-hero-stats">
|
||||
<div class="stat">
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Ledger · {{ league.name }} · myboker.org{% endblock %}
|
||||
{% block subnav %}{% include "_league_subnav.html" %}{% endblock %}
|
||||
{% block sidebar %}{% include "_league_sidebar.html" %}{% endblock %}
|
||||
{% block page_class %}page--app{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
<div class="cash-strip">
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}{{ player.player_name }} · {{ league.name }} · myboker.org{% endblock %}
|
||||
{% block subnav %}{% include "_league_subnav.html" %}{% endblock %}
|
||||
{% block sidebar %}{% include "_league_sidebar.html" %}{% endblock %}
|
||||
{% block page_class %}page--app{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Players · {{ league.name }} · myboker.org{% endblock %}
|
||||
{% block subnav %}{% include "_league_subnav.html" %}{% endblock %}
|
||||
{% block sidebar %}{% include "_league_sidebar.html" %}{% endblock %}
|
||||
{% block page_class %}page--app{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
<div class="page-header">
|
||||
<div>
|
||||
<h1 class="page-header__title">Players</h1>
|
||||
<p class="page-header__sub">Roster for {{ league.name }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if can_manage %}
|
||||
<form class="panel form-card" method="post" style="margin-bottom:20px;">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}{{ session_model.display_label }} · {{ league.name }} · myboker.org{% endblock %}
|
||||
{% block subnav %}{% include "_league_subnav.html" %}{% endblock %}
|
||||
{% block sidebar %}{% include "_league_sidebar.html" %}{% endblock %}
|
||||
{% block page_class %}page--app{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
<div class="hero" style="padding-top:0;padding-bottom:12px;display:flex;flex-wrap:wrap;gap:12px;align-items:flex-end;justify-content:space-between;">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}{{ session_model.display_label }} · {{ league.name }} · myboker.org{% endblock %}
|
||||
{% block subnav %}{% include "_league_subnav.html" %}{% endblock %}
|
||||
{% block page_class %}page--session{% endblock %}
|
||||
{% block sidebar %}{% include "_league_sidebar.html" %}{% endblock %}
|
||||
{% block page_class %}page--app{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
<div class="breadcrumb">
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Sessions · {{ league.name }} · myboker.org{% endblock %}
|
||||
{% block subnav %}{% include "_league_subnav.html" %}{% endblock %}
|
||||
{% block sidebar %}{% include "_league_sidebar.html" %}{% endblock %}
|
||||
{% block page_class %}page--app{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
<div class="page-header">
|
||||
<div>
|
||||
<h1 class="page-header__title">Sessions</h1>
|
||||
<p class="page-header__sub">All game sessions for {{ league.name }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stack">
|
||||
|
||||
{% if can_manage %}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Settings · {{ league.name }} · myboker.org{% endblock %}
|
||||
{% block subnav %}{% include "_league_subnav.html" %}{% endblock %}
|
||||
{% block sidebar %}{% include "_league_sidebar.html" %}{% endblock %}
|
||||
{% block page_class %}page--app{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
|
||||
|
||||
+100
-105
@@ -2,133 +2,128 @@
|
||||
{% block title %}Leagues · myboker.org{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
<div class="hero" style="padding-bottom:12px;">
|
||||
<p class="hero__kicker">Leagues</p>
|
||||
<h1 class="hero__title">Your poker leagues</h1>
|
||||
<p class="hero__sub">Owner and manager workspace.</p>
|
||||
</div>
|
||||
|
||||
<div class="toolbar">
|
||||
<div class="toolbar__left">
|
||||
<a class="btn btn--primary" href="{{ url_for('leagues.new') }}">New league</a>
|
||||
<div class="page-header">
|
||||
<div>
|
||||
<h1 class="page-header__title">Your leagues</h1>
|
||||
{% if leagues %}
|
||||
<p class="page-header__sub">
|
||||
{{ page_summary.league_count }} league{{ 's' if page_summary.league_count != 1 else '' }}
|
||||
{% if page_summary.live_count > 0 %} · <span style="color:var(--pos);">{{ page_summary.live_count }} live</span>{% endif %}
|
||||
· {{ page_summary.sessions_count }} session{{ 's' if page_summary.sessions_count != 1 else '' }}
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="page-header__actions">
|
||||
<a class="btn btn--primary btn--sm" href="{{ url_for('leagues.new') }}">New league</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if leagues %}
|
||||
<div class="league-hub-summary">
|
||||
<div class="stat stat--rail" style="--rail:var(--accent);">
|
||||
<span class="kicker">Leagues</span>
|
||||
<div class="stat__val" style="color:var(--num)">{{ page_summary.league_count }}</div>
|
||||
</div>
|
||||
<div class="stat stat--rail" style="--rail:var(--pos);">
|
||||
<span class="kicker">Live</span>
|
||||
<div class="stat__val" style="color:var(--num)">{{ page_summary.live_count }}</div>
|
||||
</div>
|
||||
<div class="stat stat--rail" style="--rail:var(--rolled);">
|
||||
<span class="kicker">Sessions</span>
|
||||
<div class="stat__val" style="color:var(--num)">{{ page_summary.sessions_count }}</div>
|
||||
</div>
|
||||
<div class="stat stat--rail" style="--rail:var(--warn);">
|
||||
<span class="kicker">Open items</span>
|
||||
<div class="stat__val {{ 'num-neg' if page_summary.open_items_cents > 0 else 'num-muted' }}">{{ page_summary.open_items_cents | money }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="league-board">
|
||||
<div class="li-board">
|
||||
{% for item in leagues %}
|
||||
{% set league = item.league %}
|
||||
{% set membership = item.membership %}
|
||||
<article class="panel league-row">
|
||||
<div class="league-row__identity">
|
||||
<a class="league-row__title" href="{{ url_for('leagues.dashboard', league_ref=league.url_ref) }}">
|
||||
<div>
|
||||
<span class="kicker">{{ membership.role }}</span>
|
||||
<h2>{{ league.name }}</h2>
|
||||
</div>
|
||||
{% if item.live_session %}
|
||||
<span class="live-badge">{{ session_label(item.live_session) }}</span>
|
||||
{% else %}
|
||||
<span class="status-pill status-pill--closed">idle</span>
|
||||
{% endif %}
|
||||
</a>
|
||||
<div class="league-row__chips">
|
||||
<span class="db-chip {% if league.visibility == 'public' %}db-chip--public{% endif %}">
|
||||
{% if league.visibility == 'public' %}
|
||||
<svg width="11" height="11" viewBox="0 0 20 20" fill="none" aria-hidden="true">
|
||||
<circle cx="10" cy="10" r="8" stroke="currentColor" stroke-width="1.6"/>
|
||||
<path d="M10 2c-2.5 3-2.5 13 0 16" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" fill="none"/>
|
||||
<path d="M10 2c2.5 3 2.5 13 0 16" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" fill="none"/>
|
||||
<path d="M2.5 10h15" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/>
|
||||
</svg>
|
||||
public
|
||||
{% else %}
|
||||
<svg width="10" height="12" viewBox="0 0 10 12" fill="none" aria-hidden="true">
|
||||
<rect x="1" y="5" width="8" height="6.5" rx="1.5" stroke="currentColor" stroke-width="1.5"/>
|
||||
<path d="M3 5V3.5A2 2 0 0 1 7 3.5V5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
||||
</svg>
|
||||
private
|
||||
<article class="li-card{% if item.live_session %} li-card--live{% endif %}">
|
||||
|
||||
<div class="li-card__aside">
|
||||
<span class="li-card__initial">{{ league.name[0]|upper }}</span>
|
||||
</div>
|
||||
|
||||
<div class="li-card__body">
|
||||
<div class="li-card__head">
|
||||
<div class="li-card__info">
|
||||
<div class="li-card__name-row">
|
||||
<a class="li-card__name" href="{{ url_for('leagues.dashboard', league_ref=league.url_ref) }}">{{ league.name }}</a>
|
||||
{% if item.live_session %}
|
||||
<span class="live-badge">{{ session_label(item.live_session) }}</span>
|
||||
{% else %}
|
||||
<span class="status-pill status-pill--closed">idle</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="li-card__chips">
|
||||
<span class="db-chip db-chip--accent">{{ membership.role }}</span>
|
||||
<span class="db-chip {% if league.visibility == 'public' %}db-chip--public{% endif %}">
|
||||
{% if league.visibility == 'public' %}
|
||||
<svg width="10" height="10" viewBox="0 0 20 20" fill="none" aria-hidden="true">
|
||||
<circle cx="10" cy="10" r="8" stroke="currentColor" stroke-width="1.6"/>
|
||||
<path d="M10 2c-2.5 3-2.5 13 0 16" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" fill="none"/>
|
||||
<path d="M10 2c2.5 3 2.5 13 0 16" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" fill="none"/>
|
||||
<path d="M2.5 10h15" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/>
|
||||
</svg>
|
||||
public
|
||||
{% else %}
|
||||
<svg width="9" height="11" viewBox="0 0 10 12" fill="none" aria-hidden="true">
|
||||
<rect x="1" y="5" width="8" height="6.5" rx="1.5" stroke="currentColor" stroke-width="1.5"/>
|
||||
<path d="M3 5V3.5A2 2 0 0 1 7 3.5V5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
||||
</svg>
|
||||
private
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
{% if league.description %}
|
||||
<p class="li-card__desc">{{ league.description }}</p>
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
<p class="league-row__desc">
|
||||
{% if league.description %}{{ league.description }}{% else %}{{ league.slug }}{% endif %}
|
||||
</p>
|
||||
<div class="league-row__meta">
|
||||
{% if item.latest_session %}
|
||||
<span>Latest {{ session_label(item.latest_session) }}</span>
|
||||
{% else %}
|
||||
<span>No sessions yet</span>
|
||||
{% endif %}
|
||||
{% if item.leader %}
|
||||
<span>Leader {{ item.leader.player_name }} · {{ item.leader.total_net_cents | money }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="league-row__metrics">
|
||||
<div><span>Players</span><strong>{{ item.players_count }}</strong></div>
|
||||
<div><span>Sessions</span><strong>{{ item.sessions_count }}</strong></div>
|
||||
<div><span>Paid out</span><strong>{{ item.cash_paid_out_cents | money }}</strong></div>
|
||||
<div><span>Open</span><strong class="{{ 'num-neg' if item.open_items_cents > 0 else 'num-muted' }}">{{ item.open_items_cents | money }}</strong></div>
|
||||
</div>
|
||||
|
||||
<div class="league-row__leaders">
|
||||
<div class="league-row__section-head">
|
||||
<span class="kicker">Top 3</span>
|
||||
<a href="{{ url_for('leagues.leaderboard', league_ref=league.url_ref) }}">Full board</a>
|
||||
<div class="li-card__stats">
|
||||
<div class="li-stat">
|
||||
<span>Players</span>
|
||||
<strong>{{ item.players_count }}</strong>
|
||||
</div>
|
||||
<div class="li-stat">
|
||||
<span>Sessions</span>
|
||||
<strong>{{ item.sessions_count }}</strong>
|
||||
</div>
|
||||
<div class="li-stat">
|
||||
<span>Paid out</span>
|
||||
<strong>{{ item.cash_paid_out_cents | money }}</strong>
|
||||
</div>
|
||||
<div class="li-stat">
|
||||
<span>Open</span>
|
||||
<strong class="{{ 'num-neg' if item.open_items_cents > 0 else 'num-muted' }}">{{ item.open_items_cents | money }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if item.top_players %}
|
||||
<ol class="league-top-list">
|
||||
{% for player in item.top_players %}
|
||||
<li>
|
||||
<span>{{ player.player_name }}</span>
|
||||
<strong class="{{ 'num-pos' if player.total_net_cents > 0 else 'num-neg' if player.total_net_cents < 0 else 'num-muted' }}">
|
||||
{{ '+' if player.total_net_cents > 0 else '' }}{{ player.total_net_cents | money }}
|
||||
</strong>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ol>
|
||||
{% else %}
|
||||
<div class="league-row__empty">
|
||||
<span>No results yet</span>
|
||||
<a href="{{ url_for('leagues.sessions', league_ref=league.url_ref) }}">Create a session</a>
|
||||
<div class="li-card__leaders">
|
||||
<span class="li-card__leaders-label">Leaders</span>
|
||||
<div class="li-podium">
|
||||
{% for player in item.top_players %}
|
||||
<div class="li-podium-item li-podium-item--{{ loop.index }}">
|
||||
<span class="li-podium-rank">#{{ loop.index }}</span>
|
||||
<span class="li-podium-name">{{ player.player_name }}</span>
|
||||
<strong class="li-podium-net {{ 'num-pos' if player.total_net_cents > 0 else 'num-neg' if player.total_net_cents < 0 else 'num-muted' }}">
|
||||
{{ '+' if player.total_net_cents > 0 else '' }}{{ player.total_net_cents | money }}
|
||||
</strong>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="li-card__footer">
|
||||
<span class="li-card__meta">
|
||||
{% if item.latest_session %}Latest {{ session_label(item.latest_session) }}{% elif not item.top_players %}No sessions yet{% endif %}
|
||||
</span>
|
||||
<div class="li-card__actions">
|
||||
<a class="btn btn--primary btn--sm" href="{{ url_for('leagues.dashboard', league_ref=league.url_ref) }}">Open league</a>
|
||||
<a class="li-action-link" href="{{ url_for('leagues.sessions', league_ref=league.url_ref) }}">Sessions</a>
|
||||
<a class="li-action-link" href="{{ url_for('leagues.leaderboard', league_ref=league.url_ref) }}">Leaderboard</a>
|
||||
<a class="li-action-link" href="{{ url_for('leagues.ledger', league_ref=league.url_ref) }}">Ledger</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="league-row__actions">
|
||||
<a class="btn btn--primary btn--sm" href="{{ url_for('leagues.dashboard', league_ref=league.url_ref) }}">Open</a>
|
||||
<a class="btn btn--ghost btn--sm" href="{{ url_for('leagues.sessions', league_ref=league.url_ref) }}">Sessions</a>
|
||||
<a class="btn btn--ghost btn--sm" href="{{ url_for('leagues.ledger', league_ref=league.url_ref) }}">Ledger</a>
|
||||
<a class="btn btn--ghost btn--sm" href="{{ url_for('leagues.players', league_ref=league.url_ref) }}">Players</a>
|
||||
</div>
|
||||
</article>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{% else %}
|
||||
<div class="panel empty-panel">
|
||||
<h2 class="panel__title">No leagues yet</h2>
|
||||
<p class="muted-text">Create the first league to start adding players, sessions, and ledger events.</p>
|
||||
<p class="muted-text">Create your first league to start adding players, sessions, and ledger events.</p>
|
||||
<a class="btn btn--primary" href="{{ url_for('leagues.new') }}">Create league</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
Reference in new issue
Block a user