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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions public/ai-debate-practice/index.html

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion public/best-ai-for-debate/index.html

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion public/llms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ Free to use, no account needed, and they run in the browser.

## Guides

- [AI Debate Practice for Stronger Cases, Rebuttals, and Speeches](https://debateowl.com/ai-debate-practice): Practice debate with DebateOwl by running timed rounds against an AI opponent and getting feedback on your cases, rebuttals and speeches.
- [AI Debate Practice for Stronger Cases, Rebuttals, and Speeches](https://debateowl.com/ai-debate-practice): How to choose an AI debate practice tool, with a checklist and a comparison of DebateOwl, ArguFight, MindDuel, PublicForumAI and Debate Ladder.
- [AI Debate Case Generator with Citations](https://debateowl.com/ai-debate-case-generator): DebateOwl turns a motion into a ready-to-read debate case: an opening, arguments with evidence, If they say… replies, a closing line and sources to check.
- [Practice Rebuttals Before Your Opponent Finds the Weakness](https://debateowl.com/debate-rebuttal-practice): Paste an opposing argument into DebateOwl to find its weak links, missing evidence and logic gaps, then practice the rebuttal you would give.
- [Ask an AI Debate Coach for Round-Ready Prep](https://debateowl.com/ai-debate-coach): Ask DebateOwl's AI debate coach about strategy, arguments, rebuttals and speeches, with answers tied to your own motion, side and speaker role.
Expand Down
2 changes: 2 additions & 0 deletions public/seo-pages.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions scripts/check_templating.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"hu/melyik-ai-a-legjobb-vitara",
"debateowl-vs-argufight",
"debateowl-vs-mindduel",
"ai-debate-practice",
]
MAX_WORDS = 12
MAX_PAGES = 2
Expand Down
23 changes: 18 additions & 5 deletions scripts/generate_seo_content.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,13 @@ def site_sentence(key):
.goal-page .picks{list-style:none;padding:0;margin:12px 0}
.goal-page .picks li{padding:4px 0}
.check{color:#14B37D;font-weight:800;margin-right:.35em}
.checklist{list-style:none;padding:0;margin:14px 0;display:grid;gap:8px}.checklist label{display:flex;gap:10px;align-items:flex-start;border:1px solid var(--line);border-radius:14px;padding:10px 14px;background:rgba(255,255,255,.05);cursor:pointer}.checklist input{accent-color:#14B37D;width:1.1rem;height:1.1rem;margin-top:5px;flex:none}
.hero .price{max-width:800px;color:var(--muted);margin:14px 0 0}
.callout{max-width:840px;border:1px solid var(--line);border-radius:16px;padding:14px 18px;background:rgba(255,255,255,.05);margin:18px 0 0}
""".strip()

PAGES = [
{"slug": "ai-debate-practice", "title": "AI Debate Practice | {product}", "description": "Practice debate with {product} by running timed rounds against an AI opponent and getting feedback on your cases, rebuttals and speeches.", "eyebrow": "{product} guide", "h1": "AI Debate Practice for Stronger Cases, Rebuttals, and Speeches", "lead": "Use a focused debate workspace for cases, rebuttals, sources, flowcharts, coaching, and timed practice."},
{"slug": "ai-debate-practice", "title": "Best AI Debate Practice Tools (2026): Sparring and Timed Rounds | {product}", "headline": "Best AI Debate Practice Tools (2026): Sparring and Timed Rounds", "description": "How to choose an AI debate practice tool, with a checklist and a comparison of {product}, ArguFight, MindDuel, PublicForumAI and Debate Ladder.", "eyebrow": "{product} guide", "h1": "AI Debate Practice for Stronger Cases, Rebuttals, and Speeches", "lead": "Use a focused debate workspace for cases, rebuttals, sources, flowcharts, coaching, and timed practice."},
{"slug": "ai-debate-case-generator", "title": "AI Debate Case Generator with Citations | {product}", "description": "{product} turns a motion into a ready-to-read debate case: an opening, arguments with evidence, If they say… replies, a closing line and sources to check.", "eyebrow": "Tool guide", "h1": "AI Debate Case Generator with Citations", "lead": "Draft faster, verify carefully, and walk into the round with a case you can read out."},
{"slug": "debate-rebuttal-practice", "title": "Debate Rebuttal Practice | {product}", "description": "Paste an opposing argument into {product} to find its weak links, missing evidence and logic gaps, then practice the rebuttal you would give.", "eyebrow": "Feature guide", "h1": "Practice Rebuttals Before Your Opponent Finds the Weakness", "lead": "Choose the link to attack, explain why it matters, and turn pressure into round-winning collapse."},
{"slug": "ai-debate-coach", "title": "AI Debate Coach | {product}", "description": "Ask {product}'s AI debate coach about strategy, arguments, rebuttals and speeches, with answers tied to your own motion, side and speaker role.", "eyebrow": "Feature guide", "h1": "Ask an AI Debate Coach for Round-Ready Prep", "lead": "Get focused help with strategy, arguments, rebuttals, speeches, and practice feedback."},
Expand Down Expand Up @@ -509,6 +511,9 @@ def render_block(block, spans_of=lambda: None):
return "<ol>" + "".join(f"<li>{linked(item, spans_of())}</li>" for item in block["steps"]) + "</ol>"
if "list" in block:
return "<ul>" + "".join(f"<li>{linked(item, spans_of())}</li>" for item in block["list"]) + "</ul>"
if "checklist" in block:
# Tick boxes for the reader's own count; nothing is scored or stored.
return '<ul class="checklist">' + "".join(f'<li><label><input type="checkbox" /> {linked(item)}</label></li>' for item in block["checklist"]) + "</ul>"
if "speech_times" in block:
block = speech_times_table()
if "table" in block:
Expand Down Expand Up @@ -538,7 +543,7 @@ def about(slug):
def structured_data(page, question, answer, faqs, published, modified):
# sitemap.mjs reads the first dateModified in the file, so the Article stays first.
graph = [
{"@type": "Article", "headline": page["h1"], "name": page["title"], "url": page_url(page["slug"]), "description": page["description"], "inLanguage": "en", "dateModified": modified, "datePublished": published, "author": ORGANIZATION_REF, "publisher": ORGANIZATION_REF, "about": {"@id": SOFTWARE_ID}},
{"@type": "Article", "headline": page.get("headline", page["h1"]), "name": page["title"], "url": page_url(page["slug"]), "description": page["description"], "inLanguage": "en", "dateModified": modified, "datePublished": published, "author": ORGANIZATION_REF, "publisher": ORGANIZATION_REF, "about": {"@id": SOFTWARE_ID}},
{"@type": "FAQPage", "mainEntity": [{"@type": "Question", "name": q, "acceptedAnswer": {"@type": "Answer", "text": a}} for q, a in [(question, answer), *faqs]]},
{"@type": "BreadcrumbList", "itemListElement": [{"@type": "ListItem", "position": 1, "name": BRAND, "item": BASE + "/"}, {"@type": "ListItem", "position": 2, "name": page["h1"], "item": page_url(page["slug"])}]},
]
Expand Down Expand Up @@ -595,7 +600,9 @@ def spans_of():
if "tool" in page:
label, href = page["tool"]
cta = f'<a class="btn primary" href="{esc(href)}">{esc(label)}</a><a class="btn secondary" href="/app">Start practicing</a>'
return f'''<!doctype html>
# A guide that compares paid tools states the app-versus-free-tools pricing once, beside its buttons.
price = f'<p class="price">{FREE_PLAN_HTML}</p>' if page.get("pricing_cta") else ""
html_out = f'''<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
Expand Down Expand Up @@ -633,7 +640,7 @@ def spans_of():
<main class="wrap">
<nav class="breadcrumb" aria-label="Breadcrumb"><a href="/">{PRODUCT}</a> / <span aria-current="page">{esc(page["h1"])}</span></nav>
<article>
<header class="hero"><span class="eyebrow">{esc(page["eyebrow"])}</span><h1>{esc(page["h1"])}</h1><p class="lead">{linked(page["lead"])}</p><p class="dates">{dates}</p><p class="dates">By the <a href="/">{esc(PRODUCT)} team</a></p><div class="cta">{cta}</div></header>{callout}
<header class="hero"><span class="eyebrow">{esc(page["eyebrow"])}</span><h1>{esc(page["h1"])}</h1><p class="lead">{linked(page["lead"])}</p><p class="dates">{dates}</p><p class="dates">By the <a href="/">{esc(PRODUCT)} team</a></p><div class="cta">{cta}</div>{price}</header>{callout}
<section class="answer" aria-labelledby="short-answer"><h2 id="short-answer">{esc(question)}</h2><p>{esc(answer)}</p></section>
{definition_block(page)}
<section class="grid" aria-label="Highlights">{feature_cards}</section>
Expand All @@ -647,6 +654,11 @@ def spans_of():
</body>
</html>
'''
if page.get("pricing_cta"):
visible = unescape(re.sub(r"<[^>]+>", " ", re.sub(r"<script[\s\S]*?</script>", " ", html_out.split("<body", 1)[1])))
if visible.count(FREE_PLAN.split(";")[0]) != 1:
raise SystemExit(f"{page['slug']} must state the pricing sentence exactly once")
return html_out

# The Hungarian and Spanish pages (src/config/localized-routes.json), named by their own titles.
LOCALIZED_ROUTES = json.loads((ROOT / "src" / "config" / "localized-routes.json").read_text())
Expand Down Expand Up @@ -776,7 +788,8 @@ def spans_of():
return html

# The comparison pages, in the order llms.txt lists them under "## Compare". Pages from
# PAGES use their own H1 and description; the seven from seo_comparison_pages.py get a line here.
# PAGES use their own H1 and description; the English pages from seo_comparison_pages.py get a line here
# (the Spanish and Hungarian ones are listed under their language by localized_sections).
COMPARE_LINKS = [
("best-ai-for-debate", None),
("best-ai-debate-tools", "six AI debate tools side by side on cases, replies, timed rounds and price, plus a one-hour test to run on any tool"),
Expand Down
4 changes: 2 additions & 2 deletions scripts/seo_answers.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@
"They solve different problems. As of October 2026, DebateClub is voice-only sparring for high-stakes conversations such as negotiations, pitches and meetings, with opponent personas and rhetorical technique scoring, and Pro costs $19/month. It names no competitive debate formats. {product} is built for school and competitive debate: cases, rebuttals, evidence, a timer and timed rounds in School 4-6-6-3, Lincoln-Douglas and Public Forum, at {pro_price} after a 3-day trial. Choose DebateClub for workplace persuasion practice and {product} for tournament prep.",
),
"ai-debate-practice": (
"Can practising debate against an AI actually make you better?",
"Yes, if it shortens the loop of giving a speech, finding what failed, fixing one thing and giving it again. A useful session needs a motion, a side, real speech times, an opponent that responds to what you said and a verdict that names why you won or lost. {product}'s Sparring runs School 4-6-6-3, Lincoln-Douglas and Public Forum rounds this way. AI practice adds repetitions between team practices but does not replace them: judge adaptation, delivery under pressure and strategic judgement still develop fastest with a coach and real rounds.",
"What is the best AI debate practice tool?",
"{product}'s practice runs full Sparring rounds in School 4-6-6-3, Lincoln-Douglas and Public Forum at real speech times, with typed or spoken speeches and a judged verdict, then Crossfire and drills on the weakest point. A general chatbot such as ChatGPT or Claude is enough for open back-and-forth, and as of October 2026 ArguFight, MindDuel, PublicForumAI and Debate Ladder suit written rounds against people, short spoken turns on Android, Public Forum only and ranked voice reps.",
),
"ai-debate-case-generator": (
"Can an AI debate case generator write a case I can actually use?",
Expand Down
Loading
Loading