additional info for seo

This commit is contained in:
SowinskiBraeden committed 2026-06-29 23:17:21 -07:00
1 parent 49d9574bf6
commit 3760af285b
5 files changed
+444

No files matched your search

+206
View File
@@ -10,6 +10,180 @@ from sqlalchemy.exc import SQLAlchemyError
public_bp = Blueprint("public", __name__) public_bp = Blueprint("public", __name__)
SEO_USE_CASES = {
"free-poker-tracker": {
"title": "Free Poker Tracker for Home Games",
"meta": "A free poker tracker for home games: record sessions, buy-ins, cashouts, player stats, settlements, and league leaderboards.",
"keywords": "free poker tracker, home poker tracker, poker session tracker, poker league tracker",
"eyebrow": "Free poker tracker",
"h1": "Track home poker without another spreadsheet.",
"intro": "myboker gives home games one place to record sessions, buy-ins, cashouts, results, and standings. It is built for groups that want reliable records without turning game night into admin work.",
"primary_cta": "Start tracking",
"sections": [
{
"heading": "Built around the session",
"body": "Open a session when the game starts, add ledger events during the night, then close it when the books are settled. Same-day games are sequenced automatically, so a busy night can still stay organized.",
},
{
"heading": "Stats come from the records",
"body": "Net profit, ROI, win rate, recent form, and leaderboard rank are calculated from the buy-ins and cashouts already in the ledger. No duplicate data entry is needed after the game.",
},
{
"heading": "Private by default",
"body": "Leagues start private. Owners can invite managers or viewers, and public pages are optional when a group wants to share results.",
},
],
"examples": [
"Track buy-ins and re-buys while the game is running.",
"Close a session with cash in, paid out, and open items visible.",
"Rank regulars while keeping one-time guests provisional.",
],
"faqs": [
("Is myboker free?", "Yes. myboker is free to use for home poker tracking."),
("Do players need accounts?", "No. Players can be tracked by display name. Only league members need accounts to manage or view private data."),
("Can I use it for multiple leagues?", "Yes. One account can create or manage multiple home poker leagues."),
],
"related": ["poker-ledger", "poker-stats-tracker", "home-poker-league-tracker"],
},
"poker-ledger": {
"title": "Poker Ledger for Buy-ins, Cashouts, and Settlements",
"meta": "Use a poker ledger for home games to record buy-ins, cashouts, fronts, corrections, payouts, and settlement totals.",
"keywords": "poker ledger, poker ledger tracker, poker buy in tracker, poker cashout tracker, poker settlement tracker",
"eyebrow": "Poker ledger",
"h1": "Keep every buy-in, cashout, and settlement in order.",
"intro": "A good poker ledger should answer the basic money questions quickly: what came in, what went out, who is up, and what is still open. myboker keeps those records attached to each session.",
"primary_cta": "Create a ledger",
"sections": [
{
"heading": "Append instead of overwrite",
"body": "Ledger events are kept as history. When someone makes a mistake, the fix is a new correction entry, not a silent edit that makes the old total impossible to explain.",
},
{
"heading": "Settlement stays visible",
"body": "Cash in, cashout, paid out, house holds, players owe house, and house owes players are shown from the same ledger events. That makes end-of-night cleanup easier.",
},
{
"heading": "Export when you need it",
"body": "League managers can export the raw ledger to CSV for backups, review, or migration.",
},
],
"examples": [
"Add a buy-in, re-buy, cashout, front, payout, or correction.",
"See open settlement items before closing a session.",
"Export the raw ledger as CSV.",
],
"faqs": [
("What does the poker ledger track?", "It tracks session events such as buy-ins, cashouts, fronts, repayments, payouts, corrections, and notes."),
("Can I fix mistakes?", "Yes. Mistakes are handled with correction entries so the ledger history stays clear."),
("Can I back up the ledger?", "Yes. Leagues can export ledger data as CSV."),
],
"related": ["free-poker-tracker", "poker-profit-loss-tracker", "home-poker-league-tracker"],
},
"poker-stats-tracker": {
"title": "Poker Stats Tracker for Home Game Leaderboards",
"meta": "Track poker stats for home games: net profit, ROI, win rate, sessions played, recent form, rank movement, and league leaderboards.",
"keywords": "poker stats tracker, poker leaderboard, poker win rate tracker, poker ROI tracker, home poker stats",
"eyebrow": "Poker stats tracker",
"h1": "Turn session results into useful poker stats.",
"intro": "Stats are only useful when they come from clean records. myboker builds leaderboards and player summaries from the same ledger events your group enters on game night.",
"primary_cta": "Track stats",
"sections": [
{
"heading": "Rank the regulars",
"body": "Set a minimum session count before a player is eligible for the ranked leaderboard. New players and guests can still appear without skewing the main standings.",
},
{
"heading": "Use more than one stat",
"body": "Sort by net profit, ROI, win rate, recent form, or sessions played. Different views help show who is running hot and who has been consistent over time.",
},
{
"heading": "Filter by season",
"body": "Create seasons and view standings for a specific period, while preserving the full league history.",
},
],
"examples": [
"See net profit and ROI by player.",
"Track win rate and recent form.",
"Use eligibility rules to keep rankings fair.",
],
"faqs": [
("Which poker stats are tracked?", "myboker tracks net, ROI, win rate, sessions played, recent form, rank movement, biggest wins, and biggest losses."),
("Can guests be excluded from rankings?", "Yes. Set a minimum sessions threshold so guests stay provisional until they qualify."),
("Can stats be filtered by season?", "Yes. Seasons let you view standings for a selected period."),
],
"related": ["free-poker-tracker", "poker-profit-loss-tracker", "home-poker-league-tracker"],
},
"home-poker-league-tracker": {
"title": "Home Poker League Tracker for Private Games",
"meta": "Manage a home poker league with sessions, players, seasons, ledger records, private access, public result pages, and leaderboards.",
"keywords": "home poker league tracker, poker league tracker, private poker league, poker league leaderboard",
"eyebrow": "Home poker league tracker",
"h1": "Run a home poker league without losing the history.",
"intro": "A league is more than one session. myboker keeps players, seasons, sessions, ledger records, and standings together so your group can build a real history over time.",
"primary_cta": "Start a league",
"sections": [
{
"heading": "Players do not need accounts",
"body": "Add players by display name and start tracking results. Accounts are for owners, managers, and viewers who need access to league data.",
},
{
"heading": "Roles keep access simple",
"body": "Owners control settings and membership, managers can run sessions, and viewers can inspect results without changing records.",
},
{
"heading": "Public pages are optional",
"body": "Keep a league private, or make selected results public when your group wants a shareable standings page.",
},
],
"examples": [
"Invite a manager to run sessions.",
"Group sessions into seasons.",
"Share public league results only when you choose.",
],
"faqs": [
("Can a league stay private?", "Yes. Leagues are private by default."),
("Can one account manage multiple leagues?", "Yes. One account can create and manage separate leagues."),
("Can I archive players?", "Yes. Archived players leave the active roster while preserving their history."),
],
"related": ["free-poker-tracker", "poker-ledger", "poker-stats-tracker"],
},
"poker-profit-loss-tracker": {
"title": "Poker Profit and Loss Tracker for Home Games",
"meta": "Track poker profit and loss by player using buy-ins, cashouts, net results, ROI, win rate, and session history.",
"keywords": "poker profit loss tracker, poker profit tracker, poker loss tracker, poker bankroll tracker, track poker winnings",
"eyebrow": "Poker profit and loss tracker",
"h1": "Know who is up, who is down, and why.",
"intro": "Profit and loss tracking should come from the actual session records. myboker calculates player results from buy-ins, cashouts, and corrections instead of a separate summary sheet.",
"primary_cta": "Track results",
"sections": [
{
"heading": "Net results per session",
"body": "Each player's invested amount, cashout, and net result are visible inside the session, with league totals updating automatically.",
},
{
"heading": "Long-term player history",
"body": "Player pages show cumulative results, win rate, result mix, and session history so the full record is easy to inspect.",
},
{
"heading": "No payment processing",
"body": "myboker records what happened at the table. It does not hold money, transfer funds, or get between your group and its own settlement process.",
},
],
"examples": [
"Calculate net profit from buy-ins and cashouts.",
"Compare ROI and win rate across players.",
"Review a player's session-by-session history.",
],
"faqs": [
("Does myboker track bankrolls?", "It tracks league and session results. It is not a personal bankroll app or payment processor."),
("How is profit calculated?", "Profit is based on cashout minus invested amount for each session, then aggregated over time."),
("Can I see losing sessions too?", "Yes. Losing, winning, and break-even sessions are preserved in player history."),
],
"related": ["poker-ledger", "poker-stats-tracker", "free-poker-tracker"],
},
}
def _absolute_url(path: str) -> str: def _absolute_url(path: str) -> str:
base_url = current_app.config.get("APP_BASE_URL", "https://myboker.org").rstrip("/") base_url = current_app.config.get("APP_BASE_URL", "https://myboker.org").rstrip("/")
return f"{base_url}{path}" return f"{base_url}{path}"
@@ -55,6 +229,10 @@ def sitemap_xml():
_sitemap_url(url_for("public.privacy"), "0.3", "yearly"), _sitemap_url(url_for("public.privacy"), "0.3", "yearly"),
_sitemap_url(url_for("public.terms"), "0.3", "yearly"), _sitemap_url(url_for("public.terms"), "0.3", "yearly"),
] ]
urls.extend(
_sitemap_url(url_for("public.use_case", slug=slug), "0.8", "monthly")
for slug in SEO_USE_CASES
)
body = ( body = (
'<?xml version="1.0" encoding="UTF-8"?>\n' '<?xml version="1.0" encoding="UTF-8"?>\n'
@@ -80,6 +258,34 @@ def terms():
return render_template("terms.html") return render_template("terms.html")
@public_bp.get("/<slug>")
def use_case(slug: str):
page = SEO_USE_CASES.get(slug)
if page is None:
from flask import abort
abort(404)
related_pages = [(related_slug, SEO_USE_CASES[related_slug]) for related_slug in page["related"]]
faq_schema = [
{
"@type": "Question",
"name": question,
"acceptedAnswer": {
"@type": "Answer",
"text": answer,
},
}
for question, answer in page["faqs"]
]
return render_template(
"seo_use_case.html",
slug=slug,
page=page,
related_pages=related_pages,
faq_schema=faq_schema,
)
@public_bp.get("/explore") @public_bp.get("/explore")
def explore(): def explore():
from boker.auth import is_logged_in from boker.auth import is_logged_in
+137
View File
@@ -4843,6 +4843,140 @@ select.control { cursor: pointer; }
line-height: 1.7; line-height: 1.7;
color: var(--muted); color: var(--muted);
} }
.lp-seo__link {
display: inline-flex;
margin-top: 14px;
color: var(--accent);
font-size: .8rem;
font-weight: 700;
text-decoration: none;
}
.lp-seo__link:hover { color: var(--text-strong); }
/* ================================================================
USE CASE / SEO PAGES
================================================================ */
.usecase {
width: min(100%, 980px);
margin: 0 auto;
}
.usecase-hero {
padding: clamp(30px, 5vw, 62px) 0 28px;
border-bottom: 1px solid var(--border);
}
.usecase-eyebrow {
margin: 0 0 10px;
font-family: var(--font-mono);
font-size: .68rem;
font-weight: 700;
letter-spacing: .14em;
text-transform: uppercase;
color: var(--accent);
}
.usecase-title {
max-width: 780px;
margin: 0;
color: var(--text-strong);
font-size: clamp(34px, 6vw, 64px);
line-height: 1.02;
letter-spacing: -.04em;
}
.usecase-intro {
max-width: 720px;
margin: 18px 0 0;
color: var(--muted);
font-size: clamp(1rem, 2vw, 1.15rem);
line-height: 1.7;
}
.usecase-actions {
display: flex;
gap: 10px;
flex-wrap: wrap;
margin-top: 26px;
}
.usecase-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 1px;
margin: 28px 0;
overflow: hidden;
border: 1px solid var(--border);
border-radius: var(--r-lg);
background: var(--border);
}
.usecase-card {
padding: 24px;
background: var(--surface);
}
.usecase-card h2 {
margin: 0 0 10px;
color: var(--text-strong);
font-size: 1rem;
}
.usecase-card p,
.usecase-checks,
.faq-item__a p {
color: var(--muted);
line-height: 1.7;
font-size: .9rem;
}
.usecase-card p { margin: 0; }
.usecase-band,
.usecase-faq,
.usecase-related {
display: grid;
gap: 18px;
padding: 30px 0;
border-top: 1px solid var(--border);
}
.usecase-section-head {
display: grid;
gap: 4px;
}
.usecase-section-title {
margin: 0;
color: var(--text-strong);
font-size: clamp(22px, 3vw, 30px);
letter-spacing: -.025em;
}
.usecase-checks {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 12px;
margin: 0;
padding: 0;
list-style: none;
}
.usecase-checks li {
padding: 16px;
border: 1px solid var(--border);
border-radius: var(--r-md);
background: var(--surface-sunk);
}
.usecase-faq-list {
display: grid;
gap: 10px;
}
.usecase-related__links {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.usecase-related__links a {
display: inline-flex;
padding: 10px 12px;
border: 1px solid var(--border-strong);
border-radius: var(--r-sm);
background: var(--field);
color: var(--text-2);
font-size: .85rem;
font-weight: 700;
text-decoration: none;
}
.usecase-related__links a:hover {
border-color: var(--accent-chip-bd);
color: var(--accent);
}
/* ================================================================ /* ================================================================
BOTTOM CTA SECTION BOTTOM CTA SECTION
@@ -4886,11 +5020,14 @@ select.control { cursor: pointer; }
.lp-feat-grid { grid-template-columns: 1fr; } .lp-feat-grid { grid-template-columns: 1fr; }
.lp-feat + .lp-feat { border-left: none; border-top: 1px solid var(--border); } .lp-feat + .lp-feat { border-left: none; border-top: 1px solid var(--border); }
.lp-seo__grid { grid-template-columns: repeat(2, 1fr); } .lp-seo__grid { grid-template-columns: repeat(2, 1fr); }
.usecase-grid,
.usecase-checks { grid-template-columns: 1fr; }
} }
@media (max-width: 680px) { @media (max-width: 680px) {
.lp-hero__visual { display: none; } .lp-hero__visual { display: none; }
.lp-showcase__grid { grid-template-columns: 1fr; } .lp-showcase__grid { grid-template-columns: 1fr; }
.lp-seo__grid { grid-template-columns: 1fr; } .lp-seo__grid { grid-template-columns: 1fr; }
.usecase-actions .btn { width: 100%; justify-content: center; }
.lp-demo__thead--lb, .lp-demo__thead--lb,
.lp-demo__row--lb { grid-template-columns: 28px 1fr 32px 80px; } .lp-demo__row--lb { grid-template-columns: 28px 1fr 32px 80px; }
.lp-demo__thead--lb > span:nth-child(5), .lp-demo__thead--lb > span:nth-child(5),
+1
View File
@@ -100,6 +100,7 @@
<span class="site-footer__version">v{{ app_version }}</span> <span class="site-footer__version">v{{ app_version }}</span>
</div> </div>
<nav class="site-footer__links" aria-label="Footer links"> <nav class="site-footer__links" aria-label="Footer links">
<a href="{{ url_for('public.use_case', slug='free-poker-tracker') }}">Use cases</a>
<a href="{{ url_for('public.help') }}">Help</a> <a href="{{ url_for('public.help') }}">Help</a>
<a href="{{ url_for('public.help') }}#contact">Contact</a> <a href="{{ url_for('public.help') }}#contact">Contact</a>
<a href="{{ url_for('public.privacy') }}">Privacy</a> <a href="{{ url_for('public.privacy') }}">Privacy</a>
+4
View File
@@ -478,18 +478,22 @@
<article class="lp-seo__item"> <article class="lp-seo__item">
<h3>Track poker profits and losses</h3> <h3>Track poker profits and losses</h3>
<p>Record each buy-in, re-buy, cashout, correction, and note as it happens. Player profit and loss updates from the ledger automatically.</p> <p>Record each buy-in, re-buy, cashout, correction, and note as it happens. Player profit and loss updates from the ledger automatically.</p>
<a class="lp-seo__link" href="{{ url_for('public.use_case', slug='poker-profit-loss-tracker') }}">Profit and loss tracking</a>
</article> </article>
<article class="lp-seo__item"> <article class="lp-seo__item">
<h3>Keep a clean poker ledger</h3> <h3>Keep a clean poker ledger</h3>
<p>The append-only ledger keeps sessions, settlements, fronts, and payouts in order while preserving the history behind every total.</p> <p>The append-only ledger keeps sessions, settlements, fronts, and payouts in order while preserving the history behind every total.</p>
<a class="lp-seo__link" href="{{ url_for('public.use_case', slug='poker-ledger') }}">Poker ledger details</a>
</article> </article>
<article class="lp-seo__item"> <article class="lp-seo__item">
<h3>Build poker stats automatically</h3> <h3>Build poker stats automatically</h3>
<p>League leaderboards, net winnings, ROI, win rate, session counts, recent form, and rank movement update from the same data your group enters on game night.</p> <p>League leaderboards, net winnings, ROI, win rate, session counts, recent form, and rank movement update from the same data your group enters on game night.</p>
<a class="lp-seo__link" href="{{ url_for('public.use_case', slug='poker-stats-tracker') }}">Poker stats tracking</a>
</article> </article>
<article class="lp-seo__item"> <article class="lp-seo__item">
<h3>Built for home games</h3> <h3>Built for home games</h3>
<p>Private leagues, shared results, and public league pages are all optional. Your group decides what stays private and what gets shared.</p> <p>Private leagues, shared results, and public league pages are all optional. Your group decides what stays private and what gets shared.</p>
<a class="lp-seo__link" href="{{ url_for('public.use_case', slug='home-poker-league-tracker') }}">League tracking</a>
</article> </article>
</div> </div>
</div> </div>
+96
View File
@@ -0,0 +1,96 @@
{% extends "base.html" %}
{% block title %}{{ page.title }} · myboker.org{% endblock %}
{% block meta_description %}{{ page.meta }}{% endblock %}
{% block meta_keywords %}{{ page.keywords }}{% endblock %}
{% block structured_data %}
<script type="application/ld+json">
{{ {
"@context": "https://schema.org",
"@type": "WebPage",
"name": page.title,
"url": seo_canonical_url,
"description": page.meta,
"isPartOf": {
"@type": "WebSite",
"name": "myboker.org",
"url": seo_site_url
}
} | tojson }}
</script>
<script type="application/ld+json">
{{ {
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": faq_schema
} | tojson }}
</script>
{% endblock %}
{% block content %}
<article class="usecase">
<header class="usecase-hero">
<p class="usecase-eyebrow">{{ page.eyebrow }}</p>
<h1 class="usecase-title">{{ page.h1 }}</h1>
<p class="usecase-intro">{{ page.intro }}</p>
<div class="usecase-actions">
{% 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') }}">{{ page.primary_cta }}</a>
<a class="btn btn--ghost btn--lg" href="{{ url_for('public.home') }}">View homepage</a>
{% endif %}
</div>
</header>
<section class="usecase-grid" aria-label="What myboker does">
{% for section in page.sections %}
<div class="usecase-card">
<h2>{{ section.heading }}</h2>
<p>{{ section.body }}</p>
</div>
{% endfor %}
</section>
<section class="usecase-band">
<div>
<p class="usecase-eyebrow">In practice</p>
<h2 class="usecase-section-title">What your group can track</h2>
</div>
<ul class="usecase-checks">
{% for example in page.examples %}
<li>{{ example }}</li>
{% endfor %}
</ul>
</section>
<section class="usecase-faq">
<div class="usecase-section-head">
<p class="usecase-eyebrow">Questions</p>
<h2 class="usecase-section-title">Common questions</h2>
</div>
<div class="usecase-faq-list">
{% for question, answer in page.faqs %}
<details class="faq-item">
<summary class="faq-item__q">{{ question }}</summary>
<div class="faq-item__a">
<p>{{ answer }}</p>
</div>
</details>
{% endfor %}
</div>
</section>
<section class="usecase-related">
<div>
<p class="usecase-eyebrow">Related</p>
<h2 class="usecase-section-title">More ways to use myboker</h2>
</div>
<div class="usecase-related__links">
{% for related_slug, related in related_pages %}
<a href="{{ url_for('public.use_case', slug=related_slug) }}">{{ related.title }}</a>
{% endfor %}
</div>
</section>
</article>
{% endblock %}